package hscontrol
import (
"bytes"
_ "embed"
"net/http"
textTemplate "text/template"
"github.com/go-chi/chi/v5"
"github.com/gofrs/uuid/v5"
"github.com/juanfont/headscale/hscontrol/templates"
)
// WindowsConfigMessage shows a simple message in the browser for how to configure the Windows Tailscale client.
func (h *Headscale) WindowsConfigMessage(
writer http.ResponseWriter,
req *http.Request,
) {
writer.Header().Set("Content-Type", "text/html; charset=utf-8")
writer.WriteHeader(http.StatusOK)
_, _ = writer.Write([]byte(templates.Windows(h.cfg.ServerURL).Render()))
}
// AppleConfigMessage shows a simple message in the browser to point the user to the iOS/MacOS profile and instructions for how to install it.
func (h *Headscale) AppleConfigMessage(
writer http.ResponseWriter,
req *http.Request,
) {
writer.Header().Set("Content-Type", "text/html; charset=utf-8")
writer.WriteHeader(http.StatusOK)
_, _ = writer.Write([]byte(templates.Apple(h.cfg.ServerURL).Render()))
}
func (h *Headscale) ApplePlatformConfig(
writer http.ResponseWriter,
req *http.Request,
) {
platform := chi.URLParam(req, "platform")
if platform == "" {
httpError(writer, NewHTTPError(http.StatusBadRequest, "no platform specified", nil))
return
}
id, err := uuid.NewV4()
if err != nil {
httpError(writer, err)
return
}
contentID, err := uuid.NewV4()
if err != nil {
httpError(writer, err)
return
}
platformConfig := AppleMobilePlatformConfig{
UUID: contentID,
URL: h.cfg.ServerURL,
}
payloadType, ok := applePayloadType[platform]
if !ok {
httpError(writer, NewHTTPError(http.StatusBadRequest, "platform must be ios, macos-app-store or macos-standalone", nil))
return
}
platformConfig.PayloadType = payloadType
var payload bytes.Buffer
if err := payloadTemplate.Execute(&payload, platformConfig); err != nil { //nolint:noinlineerr
httpError(writer, err)
return
}
config := AppleMobileConfig{
UUID: id,
URL: h.cfg.ServerURL,
Payload: payload.String(),
}
var content bytes.Buffer
if err := commonTemplate.Execute(&content, config); err != nil { //nolint:noinlineerr
httpError(writer, err)
return
}
writer.Header().
Set("Content-Type", "application/x-apple-aspen-config; charset=utf-8")
writer.WriteHeader(http.StatusOK)
_, _ = writer.Write(content.Bytes())
}
type AppleMobileConfig struct {
UUID uuid.UUID
URL string
Payload string
}
type AppleMobilePlatformConfig struct {
UUID uuid.UUID
URL string
PayloadType string
}
// applePayloadType maps a platform request path to the Tailscale IPN
// PayloadType emitted in the rendered Apple profile.
var applePayloadType = map[string]string{
"ios": "io.tailscale.ipn.ios",
"macos-app-store": "io.tailscale.ipn.macos",
"macos-standalone": "io.tailscale.ipn.macsys",
}
var commonTemplate = textTemplate.Must(
textTemplate.New("mobileconfig").Parse(`
PayloadUUID
{{.UUID}}
PayloadDisplayName
Headscale
PayloadDescription
Configure Tailscale login server to: {{.URL}}
PayloadIdentifier
com.github.juanfont.headscale
PayloadRemovalDisallowed
PayloadType
Configuration
PayloadVersion
1
PayloadContent
{{.Payload}}
`),
)
var payloadTemplate = textTemplate.Must(textTemplate.New("payloadTemplate").Parse(`
PayloadType
{{.PayloadType}}
PayloadUUID
{{.UUID}}
PayloadIdentifier
com.github.juanfont.headscale
PayloadVersion
1
PayloadEnabled
ControlURL
{{.URL}}
`))