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}} `))