diff --git a/flake.nix b/flake.nix index 858dabff..df1b7e12 100644 --- a/flake.nix +++ b/flake.nix @@ -32,7 +32,7 @@ # When updating go.mod or go.sum, a new sha will need to be calculated, # update this if you have a mismatch after doing a change to thos files. - vendorHash = "sha256-SDJSFji6498WI9bJLmY62VGt21TtD2GxrxRAWyYyr0c="; + vendorHash = "sha256-CMkYTRjmhvTTrB7JbLj0cj9VEyzpG0iUWXkaOagwYTk="; subPackages = ["cmd/headscale"]; diff --git a/go.mod b/go.mod index 2bd17cfd..7eac4652 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.23.1 require ( github.com/AlecAivazis/survey/v2 v2.3.7 + github.com/chasefleming/elem-go v0.29.0 github.com/coder/websocket v1.8.12 github.com/coreos/go-oidc/v3 v3.11.0 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc diff --git a/go.sum b/go.sum index e2489aa2..cc15ef6c 100644 --- a/go.sum +++ b/go.sum @@ -90,6 +90,8 @@ github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyY github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chasefleming/elem-go v0.29.0 h1:WwrjQcVn6xldhexluvl2Z3sgKi9HTMuzWeEXO4PHsmg= +github.com/chasefleming/elem-go v0.29.0/go.mod h1:hz73qILBIKnTgOujnSMtEj20/epI+f6vg71RUilJAA4= github.com/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs= github.com/chromedp/chromedp v0.9.2/go.mod h1:LkSXJKONWTCHAfQasKFUZI+mxqS4tZqhmtGzzhLsnLs= github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww= diff --git a/hscontrol/handlers.go b/hscontrol/handlers.go index 9287eeff..72ec4e42 100644 --- a/hscontrol/handlers.go +++ b/hscontrol/handlers.go @@ -1,17 +1,19 @@ package hscontrol import ( - "bytes" "encoding/json" "errors" "fmt" - "html/template" "net/http" "strconv" "strings" "time" + "github.com/chasefleming/elem-go" + "github.com/chasefleming/elem-go/attrs" + "github.com/chasefleming/elem-go/styles" "github.com/gorilla/mux" + "github.com/juanfont/headscale/hscontrol/templates" "github.com/rs/zerolog/log" "tailscale.com/tailcfg" "tailscale.com/types/key" @@ -135,38 +137,37 @@ func (h *Headscale) HealthHandler( respond(nil) } -type registerWebAPITemplateConfig struct { - Key string +var codeStyleRegisterWebAPI = styles.Props{ + styles.Display: "block", + styles.Padding: "20px", + styles.Border: "1px solid #bbb", + styles.BackgroundColor: "#eee", } -var registerWebAPITemplate = template.Must( - template.New("registerweb").Parse(` - -
-- Run the command below in the headscale server to add this machine to your network: -
-headscale nodes register --user USERNAME --key {{.Key}}
-
-
-`))
+func registerWebHTML(key string) *elem.Element {
+ return elem.Html(nil,
+ elem.Head(
+ nil,
+ elem.Title(nil, elem.Text("Registration - Headscale")),
+ elem.Meta(attrs.Props{
+ attrs.Name: "viewport",
+ attrs.Content: "width=device-width, initial-scale=1",
+ }),
+ ),
+ elem.Body(attrs.Props{
+ attrs.Style: styles.Props{
+ styles.FontFamily: "sans",
+ }.ToInline(),
+ },
+ elem.H1(nil, elem.Text("headscale")),
+ elem.H2(nil, elem.Text("Machine registration")),
+ elem.P(nil, elem.Text("Run the command below in the headscale server to add this machine to your network:")),
+ elem.Code(attrs.Props{attrs.Style: codeStyleRegisterWebAPI.ToInline()},
+ elem.Text(fmt.Sprintf("headscale nodes register --user USERNAME --key %s", key)),
+ ),
+ ),
+ )
+}
type AuthProviderWeb struct {
serverURL string
@@ -220,34 +221,14 @@ func (a *AuthProviderWeb) RegisterHandler(
return
}
- var content bytes.Buffer
- if err := registerWebAPITemplate.Execute(&content, registerWebAPITemplateConfig{
- Key: machineKey.String(),
- }); err != nil {
- log.Error().
- Str("func", "RegisterWebAPI").
- Err(err).
- Msg("Could not render register web API template")
- writer.Header().Set("Content-Type", "text/plain; charset=utf-8")
- writer.WriteHeader(http.StatusInternalServerError)
- _, err = writer.Write([]byte("Could not render register web API template"))
- if err != nil {
+ writer.Header().Set("Content-Type", "text/html; charset=utf-8")
+ writer.WriteHeader(http.StatusOK)
+ if _, err := writer.Write([]byte(registerWebHTML(machineKey.String()).Render())); err != nil {
+ if _, err := writer.Write([]byte(templates.RegisterWeb(machineKey.String()).Render())); err != nil {
log.Error().
Caller().
Err(err).
Msg("Failed to write response")
}
-
- return
- }
-
- writer.Header().Set("Content-Type", "text/html; charset=utf-8")
- writer.WriteHeader(http.StatusOK)
- _, err = writer.Write(content.Bytes())
- if err != nil {
- log.Error().
- Caller().
- Err(err).
- Msg("Failed to write response")
}
}
diff --git a/hscontrol/platform_config.go b/hscontrol/platform_config.go
index 9844a606..dc6174a9 100644
--- a/hscontrol/platform_config.go
+++ b/hscontrol/platform_config.go
@@ -9,49 +9,19 @@ import (
"github.com/gofrs/uuid/v5"
"github.com/gorilla/mux"
+ "github.com/juanfont/headscale/hscontrol/templates"
"github.com/rs/zerolog/log"
)
-//go:embed templates/apple.html
-var appleTemplate string
-
-//go:embed templates/windows.html
-var windowsTemplate string
-
// 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,
) {
- winTemplate := template.Must(template.New("windows").Parse(windowsTemplate))
- 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")
-
- writer.Header().Set("Content-Type", "text/plain; charset=utf-8")
- writer.WriteHeader(http.StatusInternalServerError)
- _, err := writer.Write([]byte("Could not render Windows index template"))
- if err != nil {
- log.Error().
- Caller().
- Err(err).
- Msg("Failed to write response")
- }
-
- return
- }
-
writer.Header().Set("Content-Type", "text/html; charset=utf-8")
writer.WriteHeader(http.StatusOK)
- _, err := writer.Write(payload.Bytes())
- if err != nil {
+
+ if _, err := writer.Write([]byte(templates.Windows(h.cfg.ServerURL).Render())); err != nil {
log.Error().
Caller().
Err(err).
@@ -64,36 +34,10 @@ func (h *Headscale) AppleConfigMessage(
writer http.ResponseWriter,
req *http.Request,
) {
- appleTemplate := template.Must(template.New("apple").Parse(appleTemplate))
-
- 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")
-
- writer.Header().Set("Content-Type", "text/plain; charset=utf-8")
- writer.WriteHeader(http.StatusInternalServerError)
- _, err := writer.Write([]byte("Could not render Apple index template"))
- if err != nil {
- log.Error().
- Caller().
- Err(err).
- Msg("Failed to write response")
- }
-
- return
- }
-
writer.Header().Set("Content-Type", "text/html; charset=utf-8")
writer.WriteHeader(http.StatusOK)
- _, err := writer.Write(payload.Bytes())
- if err != nil {
+
+ if _, err := writer.Write([]byte(templates.Apple(h.cfg.ServerURL).Render())); err != nil {
log.Error().
Caller().
Err(err).
diff --git a/hscontrol/templates/apple.go b/hscontrol/templates/apple.go
new file mode 100644
index 00000000..93f0034d
--- /dev/null
+++ b/hscontrol/templates/apple.go
@@ -0,0 +1,149 @@
+package templates
+
+import (
+ "fmt"
+
+ "github.com/chasefleming/elem-go"
+ "github.com/chasefleming/elem-go/attrs"
+)
+
+func Apple(url string) *elem.Element {
+ return HtmlStructure(
+ elem.Title(nil,
+ elem.Text("headscale - Apple")),
+ elem.Body(attrs.Props{
+ attrs.Style: bodyStyle.ToInline(),
+ },
+ headerOne("headscale: iOS configuration"),
+ headerTwo("GUI"),
+ elem.Ol(nil,
+ elem.Li(nil,
+ elem.Text("Install the official Tailscale iOS client from the "),
+ elem.A(attrs.Props{attrs.Href: "https://apps.apple.com/app/tailscale/id1470499037"},
+ elem.Text("App store"),
+ ),
+ ),
+ elem.Li(nil,
+ elem.Text("Open Tailscale and make sure you are "),
+ elem.I(nil, elem.Text("not ")),
+ elem.Text("logged in to any account"),
+ ),
+ elem.Li(nil,
+ elem.Text("Open Settings on the iOS device"),
+ ),
+ elem.Li(nil,
+ elem.Text(`Scroll down to the "third party apps" section, under "Game Center" or "TV Provider"`),
+ ),
+ elem.Li(nil,
+ elem.Text("Find Tailscale and select it"),
+ elem.Ul(nil,
+ elem.Li(nil,
+ elem.Text(`If the iOS device was previously logged into Tailscale, switch the "Reset Keychain" toggle to "on"`),
+ ),
+ ),
+ ),
+ elem.Li(nil,
+ elem.Text(fmt.Sprintf(`Enter "%s" under "Alternate Coordination Server URL"`,url)),
+ ),
+ elem.Li(nil,
+ elem.Text("Restart the app by closing it from the iOS app switcher, open the app and select the regular sign in option "),
+ elem.I(nil, elem.Text("(non-SSO)")),
+ elem.Text(". It should open up to the headscale authentication page."),
+ ),
+ elem.Li(nil,
+ elem.Text("Enter your credentials and log in. Headscale should now be working on your iOS device"),
+ ),
+ ),
+ headerOne("headscale: macOS configuration"),
+ headerTwo("Command line"),
+ elem.P(nil,
+ elem.Text("Use Tailscale's login command to add your profile:"),
+ ),
+ elem.Pre(nil,
+ elem.Code(nil,
+ elem.Text(fmt.Sprintf("tailscale login --login-server %s",url)),
+ ),
+ ),
+ headerTwo("GUI"),
+ elem.Ol(nil,
+ elem.Li(nil,
+ elem.Text("ALT + Click the Tailscale icon in the menu and hover over the Debug menu"),
+ ),
+ elem.Li(nil,
+ elem.Text(`Under "Custom Login Server", select "Add Account..."`),
+ ),
+ elem.Li(nil,
+ elem.Text(fmt.Sprintf(`Enter "%s" of the headscale instance and press "Add Account"`,url)),
+ ),
+ elem.Li(nil,
+ elem.Text(`Follow the login procedure in the browser`),
+ ),
+ ),
+ headerTwo("Profiles"),
+ elem.P(nil,
+ elem.Text("Headscale can be set to the default server by installing a Headscale configuration profile:"),
+ ),
+ elem.P(nil,
+ elem.A(attrs.Props{attrs.Href: "/apple/macos-app-store", attrs.Download: "headscale_macos.mobileconfig"},
+ elem.Text("macOS AppStore profile "),
+ ),
+ elem.A(attrs.Props{attrs.Href: "/apple/macos-standalone", attrs.Download: "headscale_macos.mobileconfig"},
+ elem.Text("macOS Standalone profile"),
+ ),
+ ),
+ elem.Ol(nil,
+ elem.Li(nil,
+ elem.Text("Download the profile, then open it. When it has been opened, there should be a notification that a profile can be installed"),
+ ),
+ elem.Li(nil,
+ elem.Text(`Open System Preferences and go to "Profiles"`),
+ ),
+ elem.Li(nil,
+ elem.Text(`Find and install the Headscale profile`),
+ ),
+ elem.Li(nil,
+ elem.Text(`Restart Tailscale.app and log in`),
+ ),
+ ),
+ elem.P(nil, elem.Text("Or")),
+ elem.P(nil,
+ elem.Text("Use your terminal to configure the default setting for Tailscale by issuing:"),
+ ),
+ elem.Ul(nil,
+ elem.Li(nil,
+ elem.Text(`for app store client:`),
+ elem.Code(nil,
+ elem.Text(fmt.Sprintf(`defaults write io.tailscale.ipn.macos ControlURL %s`,url)),
+ ),
+ ),
+ elem.Li(nil,
+ elem.Text(`for standalone client:`),
+ elem.Code(nil,
+ elem.Text(fmt.Sprintf(`defaults write io.tailscale.ipn.macsys ControlURL %s`,url)),
+ ),
+ ),
+ ),
+ elem.P(nil,
+ elem.Text("Restart Tailscale.app and log in."),
+ ),
+ headerThree("Caution"),
+ elem.P(nil,
+ elem.Text("You should always download and inspect the profile before installing it:"),
+ ),
+ elem.Ul(nil,
+ elem.Li(nil,
+ elem.Text(`for app store client: `),
+ elem.Code(nil,
+ elem.Text(fmt.Sprintf(`curl %s/apple/macos-app-store`,url)),
+ ),
+ ),
+ elem.Li(nil,
+ elem.Text(`for standalone client: `),
+ elem.Code(nil,
+ elem.Text(fmt.Sprintf(`curl %s/apple/macos-standalone`,url)),
+ ),
+ ),
+ ),
+ ),
+ )
+}
diff --git a/hscontrol/templates/apple.html b/hscontrol/templates/apple.html
deleted file mode 100644
index 9582594a..00000000
--- a/hscontrol/templates/apple.html
+++ /dev/null
@@ -1,131 +0,0 @@
-
-
-
-
-
-
- Use Tailscale's login command to add your profile:
-tailscale login --login-server {{.URL}}
- - Headscale can be set to the default server by installing a Headscale - configuration profile: -
-- macOS AppStore profile - macOS Standalone profile -
-Or
-- Use your terminal to configure the default setting for Tailscale by - issuing: -
-defaults write io.tailscale.ipn.macos ControlURL {{.URL}}
- defaults write io.tailscale.ipn.macsys ControlURL {{.URL}}
- Restart Tailscale.app and log in.
-- You should always download and inspect the profile before installing it: -
-curl {{.URL}}/apple/macos-app-store
- curl {{.URL}}/apple/macos-standalone
- - Download - Tailscale for Windows - and install it. -
- -- Open a Command Prompt or Powershell and use Tailscale's login command to - connect with headscale: -
-tailscale login --login-server {{.URL}}
-
-