diff --git a/cmd/tailscale/cli/cli.go b/cmd/tailscale/cli/cli.go index 1a54bd723..0fa4cf6ad 100644 --- a/cmd/tailscale/cli/cli.go +++ b/cmd/tailscale/cli/cli.go @@ -15,6 +15,7 @@ "os" "os/signal" "runtime" + "strconv" "strings" "syscall" "text/tabwriter" @@ -31,15 +32,37 @@ // CLI based on os.Args, GOOS, the context the process is running in // (pty, parent PID), etc. func ActLikeCLI() bool { - if len(os.Args) < 2 { + // This function is only used on macOS. + if runtime.GOOS != "darwin" { return false } - switch os.Args[1] { - case "up", "down", "status", "netcheck", "ping", "version", - "debug", - "-V", "--version", "-h", "--help": + + // Escape hatch to let people force running the macOS + // GUI Tailscale binary as the CLI. + if v, _ := strconv.ParseBool(os.Getenv("TAILSCALE_BE_CLI")); v { return true } + + // If our parent is launchd, we're definitely not + // being run as a CLI. + if os.Getppid() == 1 { + return false + } + + // Looking at the environment of the GUI Tailscale app (ps eww + // $PID), empirically none of these environment variables are + // present. But all or some of these should be present with + // Terminal.all and bash or zsh. + for _, e := range []string{ + "SHLVL", + "TERM", + "TERM_PROGRAM", + "PS1", + } { + if os.Getenv(e) != "" { + return true + } + } return false }