diff --git a/envknob/envknob.go b/envknob/envknob.go index f1925ccf4..59a6d90af 100644 --- a/envknob/envknob.go +++ b/envknob/envknob.go @@ -17,6 +17,7 @@ package envknob import ( "bufio" + "errors" "fmt" "io" "log" @@ -503,7 +504,7 @@ func ApplyDiskConfigError() error { return applyDiskConfigErr } // // On macOS, use one of: // -// - ~/Library/Containers/io.tailscale.ipn.macsys/Data/tailscaled-env.txt +// - /private/var/root/Library/Containers/io.tailscale.ipn.macsys.network-extension/Data/tailscaled-env.txt // for standalone macOS GUI builds // - ~/Library/Containers/io.tailscale.ipn.macos.network-extension/Data/tailscaled-env.txt // for App Store builds @@ -533,44 +534,73 @@ func ApplyDiskConfig() (err error) { return applyKeyValueEnv(f) } - name := getPlatformEnvFile() - if name == "" { + names := getPlatformEnvFiles() + if len(names) == 0 { return nil } - f, err = os.Open(name) - if os.IsNotExist(err) { - return nil - } - if err != nil { - return err + + var errs []error + for _, name := range names { + f, err = os.Open(name) + if os.IsNotExist(err) { + continue + } + if err != nil { + errs = append(errs, err) + continue + } + defer f.Close() + + return applyKeyValueEnv(f) } - defer f.Close() - return applyKeyValueEnv(f) + + // If we have any errors, return them; if all errors are such that + // os.IsNotExist(err) returns true, then errs is empty and we will + // return nil. + return errors.Join(errs...) } -// getPlatformEnvFile returns the current platform's path to an optional -// tailscaled-env.txt file. It returns an empty string if none is defined -// for the platform. -func getPlatformEnvFile() string { +// getPlatformEnvFiles returns a list of paths to the current platform's +// optional tailscaled-env.txt file. It returns an empty list if none is +// defined for the platform. +func getPlatformEnvFiles() []string { switch runtime.GOOS { case "windows": - return filepath.Join(os.Getenv("ProgramData"), "Tailscale", "tailscaled-env.txt") + return []string{ + filepath.Join(os.Getenv("ProgramData"), "Tailscale", "tailscaled-env.txt"), + } case "linux": if distro.Get() == distro.Synology { - return "/etc/tailscale/tailscaled-env.txt" + return []string{"/etc/tailscale/tailscaled-env.txt"} } case "darwin": if version.IsSandboxedMacOS() { // the two GUI variants (App Store or separate download) - // This will be user-visible as ~/Library/Containers/$VARIANT/Data/tailscaled-env.txt - // where $VARIANT is "io.tailscale.ipn.macsys" for macsys (downloadable mac GUI builds) - // or "io.tailscale.ipn.macos.network-extension" for App Store builds. - return filepath.Join(os.Getenv("HOME"), "tailscaled-env.txt") + // On the App Store variant, the home directory is set + // to something like: + // ~/Library/Containers/io.tailscale.ipn.macos.network-extension/Data + // + // On the macsys (downloadable Mac GUI) variant, the + // home directory can be unset, but we have a working + // directory that looks like: + // /private/var/root/Library/Containers/io.tailscale.ipn.macsys.network-extension/Data + // + // Try both and see if we can find the file in either + // location. + var candidates []string + if home := os.Getenv("HOME"); home != "" { + candidates = append(candidates, filepath.Join(home, "tailscaled-env.txt")) + } + if wd, err := os.Getwd(); err == nil { + candidates = append(candidates, filepath.Join(wd, "tailscaled-env.txt")) + } + + return candidates } else { // Open source / homebrew variable, running tailscaled-on-macOS. - return "/etc/tailscale/tailscaled-env.txt" + return []string{"/etc/tailscale/tailscaled-env.txt"} } } - return "" + return nil } // applyKeyValueEnv reads key=value lines r and calls Setenv for each.