diff --git a/app.go b/app.go index 4c1d2015..682d8e49 100644 --- a/app.go +++ b/app.go @@ -458,6 +458,8 @@ func (h *Headscale) createRouter(grpcMux *runtime.ServeMux) *gin.Engine { router.GET("/oidc/callback", h.OIDCCallback) router.GET("/apple", h.AppleConfigMessage) router.GET("/apple/:platform", h.ApplePlatformConfig) + router.GET("/windows", h.WindowsConfigMessage) + router.GET("/windows/tailscale.reg", h.WindowsRegConfig) router.GET("/swagger", SwaggerUI) router.GET("/swagger/v1/openapiv2.json", SwaggerAPIv1) diff --git a/apple_mobileconfig.go b/apple_mobileconfig.go index 137276d4..86273e78 100644 --- a/apple_mobileconfig.go +++ b/apple_mobileconfig.go @@ -11,6 +11,100 @@ import ( "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(ctx *gin.Context) { + 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") + ctx.Data( + http.StatusInternalServerError, + "text/html; charset=utf-8", + []byte("Could not render Windows index template"), + ) + + return + } + + ctx.Data(http.StatusOK, "text/html; charset=utf-8", payload.Bytes()) +} + +// WindowsRegConfig generates and serves the .reg file +// pre-configured to the headscale server address +func (h *Headscale) WindowsRegConfig(ctx *gin.Context) { + 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") + ctx.Data( + http.StatusInternalServerError, + "text/html; charset=utf-8", + []byte("Could not render Windows registry template"), + ) + + return + } + + ctx.Data( + http.StatusOK, + "text/x-ms-regedit; charset=utf-8", + 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(ctx *gin.Context) { @@ -193,6 +287,10 @@ func (h *Headscale) ApplePlatformConfig(ctx *gin.Context) { ) } +type WindowsRegistryConfig struct { + URL string +} + type AppleMobileConfig struct { UUID uuid.UUID URL string @@ -204,6 +302,14 @@ type AppleMobilePlatformConfig struct { 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(`