// 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. //go:build go1.19 // +build go1.19 package main import ( "errors" "fmt" "io" "os" "os/exec" "path/filepath" ) func init() { installSystemDaemon = installSystemDaemonDarwin uninstallSystemDaemon = uninstallSystemDaemonDarwin } // 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 = "com.tailscale.tailscaled" func uninstallSystemDaemonDarwin(args []string) (ret error) { if len(args) > 0 { return errors.New("uninstall subcommand takes no arguments") } plist, err := exec.Command("launchctl", "list", "com.tailscale.tailscaled").Output() _ = plist // parse it? https://github.com/DHowett/go-plist if we need something. running := err == nil if running { out, err := exec.Command("launchctl", "stop", "com.tailscale.tailscaled").CombinedOutput() if err != nil { fmt.Printf("launchctl stop com.tailscale.tailscaled: %v, %s\n", err, out) ret = err } out, err = exec.Command("launchctl", "unload", sysPlist).CombinedOutput() if err != nil { fmt.Printf("launchctl unload %s: %v, %s\n", sysPlist, err, out) if ret == nil { ret = err } } } if err := os.Remove(sysPlist); err != nil { if os.IsNotExist(err) { err = nil } if ret == nil { ret = err } } if err := os.Remove(targetBin); err != nil { if os.IsNotExist(err) { err = nil } if ret == nil { ret = err } } return ret } func installSystemDaemonDarwin(args []string) (err error) { if len(args) > 0 { return errors.New("install subcommand takes no arguments") } defer func() { if err != nil && os.Getuid() != 0 { err = fmt.Errorf("%w; try running tailscaled with sudo", err) } }() // Best effort: uninstallSystemDaemonDarwin(nil) // Copy ourselves to /usr/local/bin/tailscaled. if err := os.MkdirAll(filepath.Dir(targetBin), 0755); err != nil { return err } 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 } if err := os.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 }