Merge pull request #113 from kradalby/apple-mobileconfig
Apple macOS profile support
This commit is contained in:
commit
0bbf343348
4 changed files with 330 additions and 88 deletions
189
README.md
189
README.md
|
@ -22,21 +22,18 @@ Headscale implements this coordination server.
|
||||||
- [x] Namespace support (~equivalent to multi-user in Tailscale.com)
|
- [x] Namespace support (~equivalent to multi-user in Tailscale.com)
|
||||||
- [x] Routing (advertise & accept, including exit nodes)
|
- [x] Routing (advertise & accept, including exit nodes)
|
||||||
- [x] Node registration via pre-auth keys (including reusable keys, and ephemeral node support)
|
- [x] Node registration via pre-auth keys (including reusable keys, and ephemeral node support)
|
||||||
- [X] JSON-formatted output
|
- [x] JSON-formatted output
|
||||||
- [X] ACLs
|
- [x] ACLs
|
||||||
- [X] Taildrop (File Sharing)
|
- [x] Taildrop (File Sharing)
|
||||||
- [X] Support for alternative IP ranges in the tailnets (default Tailscale's 100.64.0.0/10)
|
- [x] Support for alternative IP ranges in the tailnets (default Tailscale's 100.64.0.0/10)
|
||||||
- [X] DNS (passing DNS servers to nodes)
|
- [x] DNS (passing DNS servers to nodes)
|
||||||
- [X] Share nodes between ~~users~~ namespaces
|
- [x] Share nodes between ~~users~~ namespaces
|
||||||
- [ ] MagicDNS / Smart DNS
|
- [ ] MagicDNS / Smart DNS
|
||||||
|
|
||||||
|
|
||||||
## Roadmap 🤷
|
## Roadmap 🤷
|
||||||
|
|
||||||
Suggestions/PRs welcomed!
|
Suggestions/PRs welcomed!
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Running it
|
## Running it
|
||||||
|
|
||||||
1. Download the Headscale binary https://github.com/juanfont/headscale/releases, and place it somewhere in your PATH or use the docker container
|
1. Download the Headscale binary https://github.com/juanfont/headscale/releases, and place it somewhere in your PATH or use the docker container
|
||||||
|
@ -44,109 +41,125 @@ Suggestions/PRs welcomed!
|
||||||
```shell
|
```shell
|
||||||
docker pull headscale/headscale:x.x.x
|
docker pull headscale/headscale:x.x.x
|
||||||
```
|
```
|
||||||
<!--
|
|
||||||
or
|
<!--
|
||||||
```shell
|
or
|
||||||
docker pull ghrc.io/juanfont/headscale:x.x.x
|
```shell
|
||||||
``` -->
|
docker pull ghrc.io/juanfont/headscale:x.x.x
|
||||||
|
``` -->
|
||||||
|
|
||||||
2. (Optional, you can also use SQLite) Get yourself a PostgreSQL DB running
|
2. (Optional, you can also use SQLite) Get yourself a PostgreSQL DB running
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
docker run --name headscale -e POSTGRES_DB=headscale -e \
|
docker run --name headscale -e POSTGRES_DB=headscale -e \
|
||||||
POSTGRES_USER=foo -e POSTGRES_PASSWORD=bar -p 5432:5432 -d postgres
|
POSTGRES_USER=foo -e POSTGRES_PASSWORD=bar -p 5432:5432 -d postgres
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Set some stuff up (headscale Wireguard keys & the config.json file)
|
3. Set some stuff up (headscale Wireguard keys & the config.json file)
|
||||||
```shell
|
|
||||||
wg genkey > private.key
|
|
||||||
wg pubkey < private.key > public.key # not needed
|
|
||||||
|
|
||||||
# Postgres
|
```shell
|
||||||
cp config.json.postgres.example config.json
|
wg genkey > private.key
|
||||||
# or
|
wg pubkey < private.key > public.key # not needed
|
||||||
# SQLite
|
|
||||||
cp config.json.sqlite.example config.json
|
# Postgres
|
||||||
```
|
cp config.json.postgres.example config.json
|
||||||
|
# or
|
||||||
|
# SQLite
|
||||||
|
cp config.json.sqlite.example config.json
|
||||||
|
```
|
||||||
|
|
||||||
4. Create a namespace (a namespace is a 'tailnet', a group of Tailscale nodes that can talk to each other)
|
4. Create a namespace (a namespace is a 'tailnet', a group of Tailscale nodes that can talk to each other)
|
||||||
```shell
|
|
||||||
headscale namespaces create myfirstnamespace
|
|
||||||
```
|
|
||||||
or docker:
|
|
||||||
|
|
||||||
the db.sqlite mount is only needed if you use sqlite
|
```shell
|
||||||
```shell
|
headscale namespaces create myfirstnamespace
|
||||||
touch db.sqlite
|
```
|
||||||
docker run -v $(pwd)/private.key:/private.key -v $(pwd)/config.json:/config.json -v $(pwd)/derp.yaml:/derp.yaml -v $(pwd)/db.sqlite:/db.sqlite -p 127.0.0.1:8000:8000 headscale/headscale:x.x.x headscale namespaces create myfirstnamespace
|
|
||||||
```
|
or docker:
|
||||||
or if your server is already running in docker:
|
|
||||||
```shell
|
the db.sqlite mount is only needed if you use sqlite
|
||||||
docker exec <container_name> headscale create myfirstnamespace
|
|
||||||
```
|
```shell
|
||||||
|
touch db.sqlite
|
||||||
|
docker run -v $(pwd)/private.key:/private.key -v $(pwd)/config.json:/config.json -v $(pwd)/derp.yaml:/derp.yaml -v $(pwd)/db.sqlite:/db.sqlite -p 127.0.0.1:8000:8000 headscale/headscale:x.x.x headscale namespaces create myfirstnamespace
|
||||||
|
```
|
||||||
|
|
||||||
|
or if your server is already running in docker:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
docker exec <container_name> headscale create myfirstnamespace
|
||||||
|
```
|
||||||
|
|
||||||
5. Run the server
|
5. Run the server
|
||||||
```shell
|
|
||||||
headscale serve
|
|
||||||
```
|
|
||||||
or docker:
|
|
||||||
|
|
||||||
the db.sqlite mount is only needed if you use sqlite
|
```shell
|
||||||
```shell
|
headscale serve
|
||||||
docker run -v $(pwd)/private.key:/private.key -v $(pwd)/config.json:/config.json -v $(pwd)/derp.yaml:/derp.yaml -v $(pwd)/db.sqlite:/db.sqlite -p 127.0.0.1:8000:8000 headscale/headscale:x.x.x headscale serve
|
```
|
||||||
```
|
|
||||||
|
or docker:
|
||||||
|
|
||||||
|
the db.sqlite mount is only needed if you use sqlite
|
||||||
|
|
||||||
|
```shell
|
||||||
|
docker run -v $(pwd)/private.key:/private.key -v $(pwd)/config.json:/config.json -v $(pwd)/derp.yaml:/derp.yaml -v $(pwd)/db.sqlite:/db.sqlite -p 127.0.0.1:8000:8000 headscale/headscale:x.x.x headscale serve
|
||||||
|
```
|
||||||
|
|
||||||
6. If you used tailscale.com before in your nodes, make sure you clear the tailscald data folder
|
6. If you used tailscale.com before in your nodes, make sure you clear the tailscald data folder
|
||||||
```shell
|
|
||||||
systemctl stop tailscaled
|
```shell
|
||||||
rm -fr /var/lib/tailscale
|
systemctl stop tailscaled
|
||||||
systemctl start tailscaled
|
rm -fr /var/lib/tailscale
|
||||||
```
|
systemctl start tailscaled
|
||||||
|
```
|
||||||
|
|
||||||
7. Add your first machine
|
7. Add your first machine
|
||||||
```shell
|
|
||||||
tailscale up -login-server YOUR_HEADSCALE_URL
|
```shell
|
||||||
```
|
tailscale up -login-server YOUR_HEADSCALE_URL
|
||||||
|
```
|
||||||
|
|
||||||
8. Navigate to the URL you will get with `tailscale up`, where you'll find your machine key.
|
8. Navigate to the URL you will get with `tailscale up`, where you'll find your machine key.
|
||||||
|
|
||||||
9. In the server, register your machine to a namespace with the CLI
|
9. In the server, register your machine to a namespace with the CLI
|
||||||
```shell
|
```shell
|
||||||
headscale -n myfirstnamespace node register YOURMACHINEKEY
|
headscale -n myfirstnamespace node register YOURMACHINEKEY
|
||||||
```
|
```
|
||||||
or docker:
|
or docker:
|
||||||
```shell
|
```shell
|
||||||
docker run -v $(pwd)/private.key:/private.key -v $(pwd)/config.json:/config.json -v $(pwd)/derp.yaml:/derp.yaml headscale/headscale:x.x.x headscale -n myfirstnamespace node register YOURMACHINEKEY
|
docker run -v $(pwd)/private.key:/private.key -v $(pwd)/config.json:/config.json -v $(pwd)/derp.yaml:/derp.yaml headscale/headscale:x.x.x headscale -n myfirstnamespace node register YOURMACHINEKEY
|
||||||
```
|
```
|
||||||
or if your server is already running in docker:
|
or if your server is already running in docker:
|
||||||
```shell
|
```shell
|
||||||
docker exec <container_name> headscale -n myfistnamespace node register YOURMACHINEKEY
|
docker exec <container_name> headscale -n myfistnamespace node register YOURMACHINEKEY
|
||||||
```
|
```
|
||||||
|
|
||||||
Alternatively, you can use Auth Keys to register your machines:
|
Alternatively, you can use Auth Keys to register your machines:
|
||||||
|
|
||||||
1. Create an authkey
|
1. Create an authkey
|
||||||
```shell
|
|
||||||
headscale -n myfirstnamespace preauthkeys create --reusable --expiration 24h
|
```shell
|
||||||
```
|
headscale -n myfirstnamespace preauthkeys create --reusable --expiration 24h
|
||||||
or docker:
|
```
|
||||||
```shell
|
|
||||||
docker run -v $(pwd)/private.key:/private.key -v $(pwd)/config.json:/config.json -v$(pwd)/derp.yaml:/derp.yaml -v $(pwd)/db.sqlite:/db.sqlite headscale/headscale:x.x.x headscale -n myfirstnamespace preauthkeys create --reusable --expiration 24h
|
or docker:
|
||||||
```
|
|
||||||
or if your server is already running in docker:
|
```shell
|
||||||
```shell
|
docker run -v $(pwd)/private.key:/private.key -v $(pwd)/config.json:/config.json -v$(pwd)/derp.yaml:/derp.yaml -v $(pwd)/db.sqlite:/db.sqlite headscale/headscale:x.x.x headscale -n myfirstnamespace preauthkeys create --reusable --expiration 24h
|
||||||
docker exec <container_name> headscale -n myfirstnamespace preauthkeys create --reusable --expiration 24h
|
```
|
||||||
```
|
|
||||||
|
or if your server is already running in docker:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
docker exec <container_name> headscale -n myfirstnamespace preauthkeys create --reusable --expiration 24h
|
||||||
|
```
|
||||||
|
|
||||||
2. Use the authkey from your machine to register it
|
2. Use the authkey from your machine to register it
|
||||||
```shell
|
```shell
|
||||||
tailscale up -login-server YOUR_HEADSCALE_URL --authkey YOURAUTHKEY
|
tailscale up -login-server YOUR_HEADSCALE_URL --authkey YOURAUTHKEY
|
||||||
```
|
```
|
||||||
|
|
||||||
If you create an authkey with the `--ephemeral` flag, that key will create ephemeral nodes. This implies that `--reusable` is true.
|
If you create an authkey with the `--ephemeral` flag, that key will create ephemeral nodes. This implies that `--reusable` is true.
|
||||||
|
|
||||||
Please bear in mind that all the commands from headscale support adding `-o json` or `-o json-line` to get a nicely JSON-formatted output.
|
Please bear in mind that all the commands from headscale support adding `-o json` or `-o json-line` to get a nicely JSON-formatted output.
|
||||||
|
|
||||||
|
|
||||||
## Configuration reference
|
## Configuration reference
|
||||||
|
|
||||||
|
@ -163,6 +176,7 @@ Headscale's configuration file is named `config.json` or `config.yaml`. Headscal
|
||||||
```
|
```
|
||||||
"log_level": "debug"
|
"log_level": "debug"
|
||||||
```
|
```
|
||||||
|
|
||||||
`log_level` can be used to set the Log level for Headscale, it defaults to `debug`, and the available levels are: `trace`, `debug`, `info`, `warn` and `error`.
|
`log_level` can be used to set the Log level for Headscale, it defaults to `debug`, and the available levels are: `trace`, `debug`, `info`, `warn` and `error`.
|
||||||
|
|
||||||
```
|
```
|
||||||
|
@ -193,7 +207,6 @@ Headscale's configuration file is named `config.json` or `config.yaml`. Headscal
|
||||||
|
|
||||||
The fields starting with `db_` are used for the PostgreSQL connection information.
|
The fields starting with `db_` are used for the PostgreSQL connection information.
|
||||||
|
|
||||||
|
|
||||||
### Running the service via TLS (optional)
|
### Running the service via TLS (optional)
|
||||||
|
|
||||||
```
|
```
|
||||||
|
@ -227,21 +240,21 @@ Alternatively, `tls_letsencrypt_challenge_type` can be set to `TLS-ALPN-01`. In
|
||||||
Headscale implements the same policy ACLs as Tailscale.com, adapted to the self-hosted environment.
|
Headscale implements the same policy ACLs as Tailscale.com, adapted to the self-hosted environment.
|
||||||
|
|
||||||
For instance, instead of referring to users when defining groups you must
|
For instance, instead of referring to users when defining groups you must
|
||||||
use namespaces (which are the equivalent to user/logins in Tailscale.com).
|
use namespaces (which are the equivalent to user/logins in Tailscale.com).
|
||||||
|
|
||||||
Please check https://tailscale.com/kb/1018/acls/, and `./tests/acls/` in this repo for working examples.
|
Please check https://tailscale.com/kb/1018/acls/, and `./tests/acls/` in this repo for working examples.
|
||||||
|
|
||||||
|
### Apple devices
|
||||||
|
|
||||||
|
An endpoint with information on how to connect your Apple devices (currently macOS only) is available at `/apple` on your running instance.
|
||||||
|
|
||||||
## Disclaimer
|
## Disclaimer
|
||||||
|
|
||||||
1. We have nothing to do with Tailscale, or Tailscale Inc.
|
1. We have nothing to do with Tailscale, or Tailscale Inc.
|
||||||
2. The purpose of writing this was to learn how Tailscale works.
|
2. The purpose of writing this was to learn how Tailscale works.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## More on Tailscale
|
## More on Tailscale
|
||||||
|
|
||||||
- https://tailscale.com/blog/how-tailscale-works/
|
- https://tailscale.com/blog/how-tailscale-works/
|
||||||
- https://tailscale.com/blog/tailscale-key-management/
|
- https://tailscale.com/blog/tailscale-key-management/
|
||||||
- https://tailscale.com/blog/an-unlikely-database-migration/
|
- https://tailscale.com/blog/an-unlikely-database-migration/
|
||||||
|
|
||||||
|
|
2
app.go
2
app.go
|
@ -168,6 +168,8 @@ func (h *Headscale) Serve() error {
|
||||||
r.GET("/register", h.RegisterWebAPI)
|
r.GET("/register", h.RegisterWebAPI)
|
||||||
r.POST("/machine/:id/map", h.PollNetMapHandler)
|
r.POST("/machine/:id/map", h.PollNetMapHandler)
|
||||||
r.POST("/machine/:id", h.RegistrationHandler)
|
r.POST("/machine/:id", h.RegistrationHandler)
|
||||||
|
r.GET("/apple", h.AppleMobileConfig)
|
||||||
|
r.GET("/apple/:platform", h.ApplePlatformConfig)
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
timeout := 30 * time.Second
|
timeout := 30 * time.Second
|
||||||
|
|
226
apple_mobileconfig.go
Normal file
226
apple_mobileconfig.go
Normal file
|
@ -0,0 +1,226 @@
|
||||||
|
package headscale
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"net/http"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/gofrs/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AppleMobileConfig shows a simple message in the browser to point to the CLI
|
||||||
|
// Listens in /register
|
||||||
|
func (h *Headscale) AppleMobileConfig(c *gin.Context) {
|
||||||
|
t := template.Must(template.New("apple").Parse(`
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<h1>Apple configuration profiles</h1>
|
||||||
|
<p>
|
||||||
|
This page provides <a href="https://support.apple.com/guide/mdm/mdm-overview-mdmbf9e668/web">configuration profiles</a> for the official Tailscale clients for <a href="https://apps.apple.com/us/app/tailscale/id1470499037?ls=1">iOS</a> and <a href="https://apps.apple.com/ca/app/tailscale/id1475387142?mt=12">macOS</a>.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
The profiles will configure Tailscale.app to use {{.Url}} as its control server.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>Caution</h3>
|
||||||
|
<p>You should always inspect the profile before installing it:</p>
|
||||||
|
<!--
|
||||||
|
<p><code>curl {{.Url}}/apple/ios</code></p>
|
||||||
|
-->
|
||||||
|
<p><code>curl {{.Url}}/apple/macos</code></p>
|
||||||
|
|
||||||
|
<h2>Profiles</h2>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
<h3>iOS</h3>
|
||||||
|
<p>
|
||||||
|
<a href="/apple/ios" download="headscale_ios.mobileconfig">iOS profile</a>
|
||||||
|
</p>
|
||||||
|
-->
|
||||||
|
|
||||||
|
<h3>macOS</h3>
|
||||||
|
<p>Headscale can be set to the default server by installing a Headscale configuration profile:</p>
|
||||||
|
<p>
|
||||||
|
<a href="/apple/macos" download="headscale_macos.mobileconfig">macOS profile</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Download the profile, then open it. When it has been opened, there should be a notification that a profile can be installed</li>
|
||||||
|
<li>Open System Preferences and go to "Profiles"</li>
|
||||||
|
<li>Find and install the Headscale profile</li>
|
||||||
|
<li>Restart Tailscale.app and log in</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<p>Or</p>
|
||||||
|
<p>Use your terminal to configure the default setting for Tailscale by issuing:</p>
|
||||||
|
<code>defaults write io.tailscale.ipn.macos ControlURL {{.Url}}</code>
|
||||||
|
|
||||||
|
<p>Restart Tailscale.app and log in.</p>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>`))
|
||||||
|
|
||||||
|
config := map[string]interface{}{
|
||||||
|
"Url": h.cfg.ServerURL,
|
||||||
|
}
|
||||||
|
|
||||||
|
var payload bytes.Buffer
|
||||||
|
if err := t.Execute(&payload, config); err != nil {
|
||||||
|
log.Error().
|
||||||
|
Str("handler", "AppleMobileConfig").
|
||||||
|
Err(err).
|
||||||
|
Msg("Could not render Apple index template")
|
||||||
|
c.Data(http.StatusInternalServerError, "text/html; charset=utf-8", []byte("Could not render Apple index template"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data(http.StatusOK, "text/html; charset=utf-8", payload.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Headscale) ApplePlatformConfig(c *gin.Context) {
|
||||||
|
platform := c.Param("platform")
|
||||||
|
|
||||||
|
id, err := uuid.NewV4()
|
||||||
|
if err != nil {
|
||||||
|
log.Error().
|
||||||
|
Str("handler", "ApplePlatformConfig").
|
||||||
|
Err(err).
|
||||||
|
Msg("Failed not create UUID")
|
||||||
|
c.Data(http.StatusInternalServerError, "text/html; charset=utf-8", []byte("Failed to create UUID"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
contentId, err := uuid.NewV4()
|
||||||
|
if err != nil {
|
||||||
|
log.Error().
|
||||||
|
Str("handler", "ApplePlatformConfig").
|
||||||
|
Err(err).
|
||||||
|
Msg("Failed not create UUID")
|
||||||
|
c.Data(http.StatusInternalServerError, "text/html; charset=utf-8", []byte("Failed to create UUID"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
platformConfig := AppleMobilePlatformConfig{
|
||||||
|
UUID: contentId,
|
||||||
|
Url: h.cfg.ServerURL,
|
||||||
|
}
|
||||||
|
|
||||||
|
var payload bytes.Buffer
|
||||||
|
|
||||||
|
switch platform {
|
||||||
|
case "macos":
|
||||||
|
if err := macosTemplate.Execute(&payload, platformConfig); err != nil {
|
||||||
|
log.Error().
|
||||||
|
Str("handler", "ApplePlatformConfig").
|
||||||
|
Err(err).
|
||||||
|
Msg("Could not render Apple macOS template")
|
||||||
|
c.Data(http.StatusInternalServerError, "text/html; charset=utf-8", []byte("Could not render Apple macOS template"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "ios":
|
||||||
|
if err := iosTemplate.Execute(&payload, platformConfig); err != nil {
|
||||||
|
log.Error().
|
||||||
|
Str("handler", "ApplePlatformConfig").
|
||||||
|
Err(err).
|
||||||
|
Msg("Could not render Apple iOS template")
|
||||||
|
c.Data(http.StatusInternalServerError, "text/html; charset=utf-8", []byte("Could not render Apple iOS template"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
c.Data(http.StatusOK, "text/html; charset=utf-8", []byte("Invalid platform, only ios and macos is supported"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
config := AppleMobileConfig{
|
||||||
|
UUID: id,
|
||||||
|
Url: h.cfg.ServerURL,
|
||||||
|
Payload: payload.String(),
|
||||||
|
}
|
||||||
|
|
||||||
|
var content bytes.Buffer
|
||||||
|
if err := commonTemplate.Execute(&content, config); err != nil {
|
||||||
|
log.Error().
|
||||||
|
Str("handler", "ApplePlatformConfig").
|
||||||
|
Err(err).
|
||||||
|
Msg("Could not render Apple platform template")
|
||||||
|
c.Data(http.StatusInternalServerError, "text/html; charset=utf-8", []byte("Could not render Apple platform template"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data(http.StatusOK, "application/x-apple-aspen-config; charset=utf-8", content.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
type AppleMobileConfig struct {
|
||||||
|
UUID uuid.UUID
|
||||||
|
Url string
|
||||||
|
Payload string
|
||||||
|
}
|
||||||
|
|
||||||
|
type AppleMobilePlatformConfig struct {
|
||||||
|
UUID uuid.UUID
|
||||||
|
Url string
|
||||||
|
}
|
||||||
|
|
||||||
|
var commonTemplate = template.Must(template.New("mobileconfig").Parse(`<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>PayloadUUID</key>
|
||||||
|
<string>{{.UUID}}</string>
|
||||||
|
<key>PayloadDisplayName</key>
|
||||||
|
<string>Headscale</string>
|
||||||
|
<key>PayloadDescription</key>
|
||||||
|
<string>Configure Tailscale login server to: {{.Url}}</string>
|
||||||
|
<key>PayloadIdentifier</key>
|
||||||
|
<string>com.github.juanfont.headscale</string>
|
||||||
|
<key>PayloadRemovalDisallowed</key>
|
||||||
|
<false/>
|
||||||
|
<key>PayloadType</key>
|
||||||
|
<string>Configuration</string>
|
||||||
|
<key>PayloadVersion</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
<key>PayloadContent</key>
|
||||||
|
<array>
|
||||||
|
{{.Payload}}
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</plist>`))
|
||||||
|
|
||||||
|
var iosTemplate = template.Must(template.New("iosTemplate").Parse(`
|
||||||
|
<dict>
|
||||||
|
<key>PayloadType</key>
|
||||||
|
<string>io.tailscale.ipn.ios</string>
|
||||||
|
<key>PayloadUUID</key>
|
||||||
|
<string>{{.UUID}}</string>
|
||||||
|
<key>PayloadIdentifier</key>
|
||||||
|
<string>com.github.juanfont.headscale</string>
|
||||||
|
<key>PayloadVersion</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
<key>PayloadEnabled</key>
|
||||||
|
<true/>
|
||||||
|
|
||||||
|
<key>ControlURL</key>
|
||||||
|
<string>{{.Url}}</string>
|
||||||
|
</dict>
|
||||||
|
`))
|
||||||
|
|
||||||
|
var macosTemplate = template.Must(template.New("macosTemplate").Parse(`
|
||||||
|
<dict>
|
||||||
|
<key>PayloadType</key>
|
||||||
|
<string>io.tailscale.ipn.macos</string>
|
||||||
|
<key>PayloadUUID</key>
|
||||||
|
<string>{{.UUID}}</string>
|
||||||
|
<key>PayloadIdentifier</key>
|
||||||
|
<string>com.github.juanfont.headscale</string>
|
||||||
|
<key>PayloadVersion</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
<key>PayloadEnabled</key>
|
||||||
|
<true/>
|
||||||
|
|
||||||
|
<key>ControlURL</key>
|
||||||
|
<string>{{.Url}}</string>
|
||||||
|
</dict>
|
||||||
|
`))
|
1
go.mod
1
go.mod
|
@ -10,6 +10,7 @@ require (
|
||||||
github.com/docker/cli v20.10.8+incompatible // indirect
|
github.com/docker/cli v20.10.8+incompatible // indirect
|
||||||
github.com/docker/docker v20.10.8+incompatible // indirect
|
github.com/docker/docker v20.10.8+incompatible // indirect
|
||||||
github.com/efekarakus/termcolor v1.0.1
|
github.com/efekarakus/termcolor v1.0.1
|
||||||
|
github.com/gofrs/uuid v4.0.0+incompatible // indirect
|
||||||
github.com/gin-gonic/gin v1.7.4
|
github.com/gin-gonic/gin v1.7.4
|
||||||
github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b
|
github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b
|
||||||
github.com/klauspost/compress v1.13.5
|
github.com/klauspost/compress v1.13.5
|
||||||
|
|
Loading…
Reference in a new issue