From 7689213aaaa8550602cbc3417322f384d1bb60e1 Mon Sep 17 00:00:00 2001 From: Alex Brainman Date: Sun, 21 Feb 2021 15:38:19 +1100 Subject: [PATCH] cmd/tailscaled: add subcommands to install and remove tailscaled Windows service This change implements Windows version of install-system-daemon and uninstall-system-daemon subcommands. When running the commands the user will install or remove Tailscale Windows service. Updates #1232 Signed-off-by: Alex Brainman --- cmd/tailscaled/depaware.txt | 3 +- cmd/tailscaled/install_windows.go | 119 ++++++++++++++++++++++++++++++ 2 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 cmd/tailscaled/install_windows.go diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt index c4689dac8..4ac6ab97a 100644 --- a/cmd/tailscaled/depaware.txt +++ b/cmd/tailscaled/depaware.txt @@ -178,7 +178,8 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de LD golang.org/x/sys/unix from github.com/jsimonetti/rtnetlink/internal/unix+ W golang.org/x/sys/windows from github.com/tailscale/wireguard-go/conn+ W golang.org/x/sys/windows/registry from golang.zx2c4.com/wireguard/windows/tunnel/winipcfg+ - W golang.org/x/sys/windows/svc from tailscale.com/cmd/tailscaled + W golang.org/x/sys/windows/svc from tailscale.com/cmd/tailscaled+ + W golang.org/x/sys/windows/svc/mgr from tailscale.com/cmd/tailscaled golang.org/x/term from tailscale.com/logpolicy golang.org/x/text/secure/bidirule from golang.org/x/net/idna golang.org/x/text/transform from golang.org/x/text/secure/bidirule+ diff --git a/cmd/tailscaled/install_windows.go b/cmd/tailscaled/install_windows.go new file mode 100644 index 000000000..2f890967d --- /dev/null +++ b/cmd/tailscaled/install_windows.go @@ -0,0 +1,119 @@ +// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "context" + "errors" + "fmt" + "os" + "time" + + "golang.org/x/sys/windows" + "golang.org/x/sys/windows/svc" + "golang.org/x/sys/windows/svc/mgr" + "tailscale.com/logtail/backoff" + "tailscale.com/types/logger" +) + +func init() { + installSystemDaemon = installSystemDaemonWindows + uninstallSystemDaemon = uninstallSystemDaemonWindows +} + +func installSystemDaemonWindows(args []string) (err error) { + m, err := mgr.Connect() + if err != nil { + return fmt.Errorf("failed to connect to Windows service manager: %v", err) + } + + service, err := m.OpenService(serviceName) + if err == nil { + service.Close() + return fmt.Errorf("service %q is already installed", serviceName) + } + + // no such service; proceed to install the service. + + exe, err := os.Executable() + if err != nil { + return err + } + + c := mgr.Config{ + ServiceType: windows.SERVICE_WIN32_OWN_PROCESS, + StartType: mgr.StartAutomatic, + ErrorControl: mgr.ErrorNormal, + DisplayName: serviceName, + Description: "Connects this computer to others on the Tailscale network.", + } + + service, err = m.CreateService(serviceName, exe, c) + if err != nil { + return fmt.Errorf("failed to create %q service: %v", serviceName, err) + } + defer service.Close() + + // Exponential backoff is often too aggressive, so use (mostly) + // squares instead. + ra := []mgr.RecoveryAction{ + {mgr.ServiceRestart, 1 * time.Second}, + {mgr.ServiceRestart, 2 * time.Second}, + {mgr.ServiceRestart, 4 * time.Second}, + {mgr.ServiceRestart, 9 * time.Second}, + {mgr.ServiceRestart, 16 * time.Second}, + {mgr.ServiceRestart, 25 * time.Second}, + {mgr.ServiceRestart, 36 * time.Second}, + {mgr.ServiceRestart, 49 * time.Second}, + {mgr.ServiceRestart, 64 * time.Second}, + } + const resetPeriodSecs = 60 + err = service.SetRecoveryActions(ra, resetPeriodSecs) + if err != nil { + return fmt.Errorf("failed to set service recovery actions: %v", err) + } + + return nil +} + +func uninstallSystemDaemonWindows(args []string) (ret error) { + m, err := mgr.Connect() + if err != nil { + return fmt.Errorf("failed to connect to Windows service manager: %v", err) + } + defer m.Disconnect() + + service, err := m.OpenService(serviceName) + if err != nil { + return fmt.Errorf("failed to open %q service: %v", serviceName, err) + } + + st, err := service.Query() + if err != nil { + service.Close() + return fmt.Errorf("failed to query service state: %v", err) + } + if st.State != svc.Stopped { + service.Control(svc.Stop) + } + err = service.Delete() + service.Close() + if err != nil { + return fmt.Errorf("failed to delete service: %v", err) + } + + bo := backoff.NewBackoff("uninstall", logger.Discard, 30*time.Second) + end := time.Now().Add(15 * time.Second) + for time.Until(end) > 0 { + service, err = m.OpenService(serviceName) + if err != nil { + // service is no longer openable; success! + break + } + service.Close() + bo.BackOff(context.Background(), errors.New("service not deleted")) + } + return nil +}