summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorClaus Lensbøl <claus@tailscale.com>2025-08-07 11:51:15 -0400
committerGitHub <noreply@github.com>2025-08-07 11:51:15 -0400
commit89954fbceb78a2ecff529166da66ebee614e4253 (patch)
tree3910d821fe0743f6ca9dc41ca3dca7a4d4717c3a
parent4666d4ca2af5885329a6546d14c890d08e65c82e (diff)
downloadtailscale-89954fbceb78a2ecff529166da66ebee614e4253.tar.xz
tailscale-89954fbceb78a2ecff529166da66ebee614e4253.zip
client/systray: add startup script generator for systemd (#16801)
Updates #1708 Signed-off-by: Claus Lensbøl <claus@tailscale.com>
-rw-r--r--client/systray/startup-creator.go76
-rw-r--r--client/systray/tailscale-systray.service10
-rw-r--r--cmd/tailscale/cli/systray.go30
3 files changed, 113 insertions, 3 deletions
diff --git a/client/systray/startup-creator.go b/client/systray/startup-creator.go
new file mode 100644
index 000000000..cb354856d
--- /dev/null
+++ b/client/systray/startup-creator.go
@@ -0,0 +1,76 @@
+// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+//go:build cgo || !darwin
+
+// Package systray provides a minimal Tailscale systray application.
+package systray
+
+import (
+ "bufio"
+ "bytes"
+ _ "embed"
+ "fmt"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strings"
+)
+
+//go:embed tailscale-systray.service
+var embedSystemd string
+
+func InstallStartupScript(initSystem string) error {
+ switch initSystem {
+ case "systemd":
+ return installSystemd()
+ default:
+ return fmt.Errorf("unsupported init system '%s'", initSystem)
+ }
+}
+
+func installSystemd() error {
+ // Find the path to tailscale, just in case it's not where the example file
+ // has it placed, and replace that before writing the file.
+ tailscaleBin, err := exec.LookPath("tailscale")
+ if err != nil {
+ return fmt.Errorf("failed to find tailscale binary %w", err)
+ }
+
+ var output bytes.Buffer
+ scanner := bufio.NewScanner(strings.NewReader(embedSystemd))
+ for scanner.Scan() {
+ line := scanner.Text()
+ if strings.HasPrefix(line, "ExecStart=") {
+ line = fmt.Sprintf("ExecStart=%s systray", tailscaleBin)
+ }
+ output.WriteString(line + "\n")
+ }
+
+ configDir, err := os.UserConfigDir()
+ if err != nil {
+ homeDir, err := os.UserHomeDir()
+ if err != nil {
+ return fmt.Errorf("unable to locate user home: %w", err)
+ }
+ configDir = filepath.Join(homeDir, ".config")
+ }
+
+ systemdDir := filepath.Join(configDir, "systemd", "user")
+ if err := os.MkdirAll(systemdDir, 0o755); err != nil {
+ return fmt.Errorf("failed creating systemd uuser dir: %w", err)
+ }
+
+ serviceFile := filepath.Join(systemdDir, "tailscale-systray.service")
+
+ if err := os.WriteFile(serviceFile, output.Bytes(), 0o755); err != nil {
+ return fmt.Errorf("failed writing systemd user service: %w", err)
+ }
+
+ fmt.Printf("Successfully installed systemd service to: %s\n", serviceFile)
+ fmt.Println("To enable and start the service, run:")
+ fmt.Println(" systemctl --user daemon-reload")
+ fmt.Println(" systemctl --user enable --now tailscale-systray")
+
+ return nil
+}
diff --git a/client/systray/tailscale-systray.service b/client/systray/tailscale-systray.service
new file mode 100644
index 000000000..a4d987563
--- /dev/null
+++ b/client/systray/tailscale-systray.service
@@ -0,0 +1,10 @@
+[Unit]
+Description=Tailscale System Tray
+After=systemd.service
+
+[Service]
+Type=simple
+ExecStart=/usr/bin/tailscale systray
+
+[Install]
+WantedBy=default.target
diff --git a/cmd/tailscale/cli/systray.go b/cmd/tailscale/cli/systray.go
index 05d688faa..c0296ae26 100644
--- a/cmd/tailscale/cli/systray.go
+++ b/cmd/tailscale/cli/systray.go
@@ -7,17 +7,41 @@ package cli
import (
"context"
+ "flag"
+ "fmt"
"github.com/peterbourgon/ff/v3/ffcli"
"tailscale.com/client/systray"
)
+var systrayArgs struct {
+ initSystem string
+ installStartup bool
+}
+
var systrayCmd = &ffcli.Command{
Name: "systray",
ShortUsage: "tailscale systray",
ShortHelp: "Run a systray application to manage Tailscale",
- Exec: func(_ context.Context, _ []string) error {
- new(systray.Menu).Run(&localClient)
+ LongHelp: `Run a systray application to manage Tailscale.
+To have the application run on startup, use the --enable-startup flag.`,
+ Exec: runSystray,
+ FlagSet: (func() *flag.FlagSet {
+ fs := newFlagSet("systray")
+ fs.StringVar(&systrayArgs.initSystem, "enable-startup", "",
+ "Install startup script for init system. Currently supported systems are [systemd].")
+ return fs
+ })(),
+}
+
+func runSystray(ctx context.Context, _ []string) error {
+ if systrayArgs.initSystem != "" {
+ if err := systray.InstallStartupScript(systrayArgs.initSystem); err != nil {
+ fmt.Printf("%s\n\n", err.Error())
+ return flag.ErrHelp
+ }
return nil
- },
+ }
+ new(systray.Menu).Run(&localClient)
+ return nil
}