@ -11,6 +11,7 @@ import (
"errors"
"fmt"
"io"
"io/fs"
"os"
"os/exec"
"path/filepath"
@ -83,6 +84,13 @@ func uninstallSystemDaemonDarwin(args []string) (ret error) {
ret = err
}
}
// Do not delete targetBin if it's a symlink, which happens if it was installed via
// Homebrew.
if isSymlink ( targetBin ) {
return ret
}
if err := os . Remove ( targetBin ) ; err != nil {
if os . IsNotExist ( err ) {
err = nil
@ -107,26 +115,56 @@ func installSystemDaemonDarwin(args []string) (err error) {
// 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"
same , err := sameFile ( exe , targetBin )
if err != nil {
return err
}
// Do not overwrite targetBin with the binary file if it it's already
// pointing to it. This is primarily to handle Homebrew that writes
// /usr/local/bin/tailscaled is a symlink to the actual binary.
if ! same {
if err := copyBinary ( exe , 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
}
// copyBinary copies binary file `src` into `dst`.
func copyBinary ( src , dst string ) error {
if err := os . MkdirAll ( filepath . Dir ( dst ) , 0755 ) ; err != nil {
return err
}
tmpBin := dst + ".tmp"
f , err := os . Create ( tmpBin )
if err != nil {
return err
}
self , err := os . Open ( exe )
s rcf, err := os . Open ( src )
if err != nil {
f . Close ( )
return err
}
_ , err = io . Copy ( f , self )
self . Close ( )
_ , err = io . Copy ( f , s rc f)
s rc f. Close ( )
if err != nil {
f . Close ( )
return err
@ -137,21 +175,27 @@ func installSystemDaemonDarwin(args []string) (err error) {
if err := os . Chmod ( tmpBin , 0755 ) ; err != nil {
return err
}
if err := os . Rename ( tmpBin , targetBin ) ; err != nil {
if err := os . Rename ( tmpBin , dst ) ; err != nil {
return err
}
if err := os . WriteFile ( sysPlist , [ ] byte ( darwinLaunchdPlist ) , 0700 ) ; err != nil {
return err
}
return nil
}
if out , err := exec . Command ( "launchctl" , "load" , sysPlist ) . CombinedOutput ( ) ; err != nil {
return fmt . Errorf ( "error running launchctl load %s: %v, %s" , sysPlist , err , out )
}
func isSymlink ( path string ) bool {
fi , err := os . Lstat ( path )
return err == nil && ( fi . Mode ( ) & os . ModeSymlink == os . ModeSymlink )
}
if out , err := exec . Command ( "launchctl" , "start" , service ) . CombinedOutput ( ) ; err != nil {
return fmt . Errorf ( "error running launchctl start %s: %v, %s" , service , err , out )
// sameFile returns true if both file paths exist and resolve to the same file.
func sameFile ( path1 , path2 string ) ( bool , error ) {
dst1 , err := filepath . EvalSymlinks ( path1 )
if err != nil && ! errors . Is ( err , fs . ErrNotExist ) {
return false , fmt . Errorf ( "EvalSymlinks(%s): %w" , path1 , err )
}
return nil
dst2 , err := filepath . EvalSymlinks ( path2 )
if err != nil && ! errors . Is ( err , fs . ErrNotExist ) {
return false , fmt . Errorf ( "EvalSymlinks(%s): %w" , path2 , err )
}
return dst1 == dst2 , nil
}