diff --git a/cmd/tailscaled/install_darwin.go b/cmd/tailscaled/install_darwin.go new file mode 100644 index 000000000..34786df93 --- /dev/null +++ b/cmd/tailscaled/install_darwin.go @@ -0,0 +1,104 @@ +// 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 ( + "fmt" + "io" + "io/ioutil" + "os" + "os/exec" +) + +func init() { + installSystemDaemon = installSystemDaemonDarwin +} + +// darwinLaunchdPlist is the launchd.plist that's written to +// /Library/LaunchDaemons/com.tailscale.tailscaled.plist or (in the +// future) a user-specific location. +// +// See man launchd.plist. +const darwinLaunchdPlist = ` + + + + + + Label + com.tailscale.tailscaled + + ProgramArguments + + /usr/local/bin/tailscaled + + + RunAtLoad + + + + +` + +const sysPlist = "/Library/LaunchDaemons/com.tailscale.tailscaled.plist" +const targetBin = "/usr/local/bin/tailscaled" +const service = "system/com.tailscale.tailscaled" + +func installSystemDaemonDarwin() (err error) { + defer func() { + if err != nil && os.Getuid() != 0 { + err = fmt.Errorf("%w; try running tailscaled with sudo", err) + } + }() + + // Copy ourselves to /usr/local/bin/tailscaled. + exe, err := os.Executable() + if err != nil { + return fmt.Errorf("failed to find our own executable path: %w", err) + } + tmpBin := targetBin + ".tmp" + f, err := os.Create(tmpBin) + if err != nil { + return err + } + self, err := os.Open(exe) + if err != nil { + f.Close() + return err + } + _, err = io.Copy(f, self) + self.Close() + if err != nil { + f.Close() + return err + } + if err := f.Close(); err != nil { + return err + } + if err := os.Chmod(tmpBin, 0755); err != nil { + return err + } + if err := os.Rename(tmpBin, targetBin); err != nil { + return err + } + + // Two best effort commands to stop a previous run. + exec.Command("launchctl", "stop", "system/com.tailscale.tailscaled").Run() + exec.Command("launchctl", "unload", sysPlist).Run() + + if err := ioutil.WriteFile(sysPlist, []byte(darwinLaunchdPlist), 0700); err != nil { + return err + } + + if out, err := exec.Command("launchctl", "load", sysPlist).CombinedOutput(); err != nil { + return fmt.Errorf("error running launchctl load %s: %v, %s", sysPlist, err, out) + } + + if out, err := exec.Command("launchctl", "start", service).CombinedOutput(); err != nil { + return fmt.Errorf("error running launchctl start %s: %v, %s", service, err, out) + } + + return nil +} diff --git a/cmd/tailscaled/tailscaled.go b/cmd/tailscaled/tailscaled.go index 4ad437c22..5782fe3c1 100644 --- a/cmd/tailscaled/tailscaled.go +++ b/cmd/tailscaled/tailscaled.go @@ -71,6 +71,8 @@ var args struct { verbose int } +var installSystemDaemon func() error // non-nil on some platforms + func main() { // We aren't very performance sensitive, and the parts that are // performance sensitive (wireguard) try hard not to do any memory @@ -91,11 +93,23 @@ func main() { flag.StringVar(&args.socketpath, "socket", paths.DefaultTailscaledSocket(), "path of the service unix socket") flag.BoolVar(&printVersion, "version", false, "print version information and exit") - if len(os.Args) > 1 && os.Args[1] == "debug" { - if err := debugMode(os.Args[2:]); err != nil { - log.Fatal(err) + if len(os.Args) > 1 { + switch os.Args[1] { + case "debug": + if err := debugMode(os.Args[2:]); err != nil { + log.Fatal(err) + } + return + case "install-system-daemon": + if f := installSystemDaemon; f == nil { + log.SetFlags(0) + log.Fatalf("install-system-daemon not available on %v", runtime.GOOS) + } else if err := f(); err != nil { + log.SetFlags(0) + log.Fatal(err) + } + return } - return } if beWindowsSubprocess() {