package headscale import ( "bytes" "html/template" "net/http" textTemplate "text/template" "github.com/gofrs/uuid" "github.com/gorilla/mux" "github.com/rs/zerolog/log" ) // WindowsConfigMessage shows a simple message in the browser for how to configure the Windows Tailscale client. func (h *Headscale) WindowsConfigMessage( w http.ResponseWriter, r *http.Request, ) { winTemplate := template.Must(template.New("windows").Parse(`
This page provides Windows registry information for the official Windows Tailscale client.
The registry file will configure Tailscale to use {{.URL}}
as its control server.
You should always download and inspect the registry file before installing it:
curl {{.URL}}/windows/tailscale.reg
Headscale can be set to the default server by running the registry file:
Or
Open command prompt with Administrator rights. Issue the following commands to add the required registry entries:
REG ADD "HKLM\Software\Tailscale IPN" /v UnattendedMode /t REG_SZ /d always
REG ADD "HKLM\Software\Tailscale IPN" /v LoginURL /t REG_SZ /d "{{.URL}}"
Restart Tailscale and log in.
`)) config := map[string]interface{}{ "URL": h.cfg.ServerURL, } var payload bytes.Buffer if err := winTemplate.Execute(&payload, config); err != nil { log.Error(). Str("handler", "WindowsRegConfig"). Err(err). Msg("Could not render Windows index template") w.Header().Set("Content-Type", "text/plain; charset=utf-8") w.WriteHeader(http.StatusInternalServerError) w.Write([]byte("Could not render Windows index template")) return } w.Header().Set("Content-Type", "text/html; charset=utf-8") w.WriteHeader(http.StatusOK) w.Write(payload.Bytes()) } // WindowsRegConfig generates and serves a .reg file configured with the Headscale server address. func (h *Headscale) WindowsRegConfig( w http.ResponseWriter, r *http.Request, ) { config := WindowsRegistryConfig{ URL: h.cfg.ServerURL, } var content bytes.Buffer if err := windowsRegTemplate.Execute(&content, config); err != nil { log.Error(). Str("handler", "WindowsRegConfig"). Err(err). Msg("Could not render Apple macOS template") w.Header().Set("Content-Type", "text/plain; charset=utf-8") w.WriteHeader(http.StatusInternalServerError) w.Write([]byte("Could not render Windows registry template")) return } w.Header().Set("Content-Type", "text/x-ms-regedit; charset=utf-8") w.WriteHeader(http.StatusOK) w.Write(content.Bytes()) } // 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( w http.ResponseWriter, r *http.Request, ) { appleTemplate := template.Must(template.New("apple").Parse(`
This page provides configuration profiles for the official Tailscale clients for iOS and macOS.
The profiles will configure Tailscale.app to use {{.URL}}
as its control server.
You should always download and inspect the profile before installing it:
curl {{.URL}}/apple/macos
Headscale can be set to the default server by installing a Headscale configuration profile:
Or
Use your terminal to configure the default setting for Tailscale by issuing:
defaults write io.tailscale.ipn.macos ControlURL {{.URL}}
Restart Tailscale.app and log in.
`)) config := map[string]interface{}{ "URL": h.cfg.ServerURL, } var payload bytes.Buffer if err := appleTemplate.Execute(&payload, config); err != nil { log.Error(). Str("handler", "AppleMobileConfig"). Err(err). Msg("Could not render Apple index template") w.Header().Set("Content-Type", "text/plain; charset=utf-8") w.WriteHeader(http.StatusInternalServerError) w.Write([]byte("Could not render Apple index template")) return } w.Header().Set("Content-Type", "text/html; charset=utf-8") w.WriteHeader(http.StatusOK) w.Write(payload.Bytes()) } func (h *Headscale) ApplePlatformConfig( w http.ResponseWriter, r *http.Request, ) { vars := mux.Vars(r) platform, ok := vars["platform"] if !ok { log.Error(). Str("handler", "ApplePlatformConfig"). Msg("No platform specified") http.Error(w, "No platform specified", http.StatusBadRequest) return } id, err := uuid.NewV4() if err != nil { log.Error(). Str("handler", "ApplePlatformConfig"). Err(err). Msg("Failed not create UUID") w.Header().Set("Content-Type", "text/plain; charset=utf-8") w.WriteHeader(http.StatusInternalServerError) w.Write([]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") w.Header().Set("Content-Type", "text/plain; charset=utf-8") w.WriteHeader(http.StatusInternalServerError) w.Write([]byte("Failed to create content 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") w.Header().Set("Content-Type", "text/plain; charset=utf-8") w.WriteHeader(http.StatusInternalServerError) w.Write([]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") w.Header().Set("Content-Type", "text/plain; charset=utf-8") w.WriteHeader(http.StatusInternalServerError) w.Write([]byte("Could not render Apple iOS template")) return } default: w.Header().Set("Content-Type", "text/plain; charset=utf-8") w.WriteHeader(http.StatusBadRequest) w.Write([]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") w.Header().Set("Content-Type", "text/plain; charset=utf-8") w.WriteHeader(http.StatusInternalServerError) w.Write([]byte("Could not render Apple platform template")) return } w.Header().Set("Content-Type", "application/x-apple-aspen-config; charset=utf-8") w.WriteHeader(http.StatusOK) w.Write(content.Bytes()) } type WindowsRegistryConfig struct { URL string } type AppleMobileConfig struct { UUID uuid.UUID URL string Payload string } type AppleMobilePlatformConfig struct { UUID uuid.UUID URL string } var windowsRegTemplate = textTemplate.Must( textTemplate.New("windowsconfig").Parse(`Windows Registry Editor Version 5.00 [HKEY_LOCAL_MACHINE\SOFTWARE\Tailscale IPN] "UnattendedMode"="always" "LoginURL"="{{.URL}}" `)) var commonTemplate = textTemplate.Must( textTemplate.New("mobileconfig").Parse(`