Something went wrong. Try again.
forked from tailscale/tailscale
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347// Copyright (c) Tailscale Inc & contributors// SPDX-License-Identifier: BSD-3-Clause
package version
import ( "os" "path/filepath" "runtime" "strconv" "strings" "sync"
"tailscale.com/tailcfg" "tailscale.com/types/lazy")
// AppIdentifierFn, if non-nil, is a callback function that returns the// application identifier of the running process or an empty string if unknown.//// tailscale(d) implementations can set an explicit callback to return an identifier// for the running process if such a concept exists. The Apple bundle identifier, for example.var AppIdentifierFn func() string // or nil
const ( macsysBundleID = "io.tailscale.ipn.macsys" // The macsys gui app and CLI appStoreBundleID = "io.tailscale.ipn.macos" // The App Store gui app and CLI macsysExtBundleId = "io.tailscale.ipn.macsys.network-extension" // The macsys system extension appStoreExtBundleId = "io.tailscale.ipn.macos.network-extension" // The App Store network extension tvOSExtBundleId = "io.tailscale.ipn.ios.network-extension-tvos" // The tvOS network extension iOSExtBundleId = "io.tailscale.ipn.ios.network-extension" // The iOS network extension)
// IsMobile reports whether this is a mobile client build.func IsMobile() bool { return runtime.GOOS == "android" || runtime.GOOS == "ios"}
// OS returns runtime.GOOS, except instead of returning "darwin" it returns// "iOS" or "macOS".func OS() string { // If you're wondering why we have this function that just returns // runtime.GOOS written differently: in the old days, Go reported // GOOS=darwin for both iOS and macOS, so we needed this function to // differentiate them. Then a later Go release added GOOS=ios as a separate // platform, but by then the "iOS" and "macOS" values we'd picked, with that // exact capitalization, were already baked into databases. if IsAppleTV() { return "tvOS" } if runtime.GOOS == "ios" { return "iOS" } if runtime.GOOS == "darwin" { return "macOS" } return runtime.GOOS}
// IsMacGUIVariant reports whether runtime.GOOS=="darwin" and this one of the// two GUI variants (that is, not tailscaled-on-macOS).// This predicate should not be used to determine sandboxing properties. It's// meant for callers to determine whether the NetworkExtension-like auto-netns// is in effect.func IsMacGUIVariant() bool { return IsMacAppStore() || IsMacSysExt()}
// IsSandboxedMacOS reports whether this process is a sandboxed macOS// process (either the app or the extension). It is true for the Mac App Store// and macsys (only its System Extension) variants on macOS, and false for// tailscaled and the macsys GUI process on macOS.func IsSandboxedMacOS() bool { return IsMacAppStore() || IsMacSysExt()}
// IsMacSys reports whether this process is part of the Standalone variant of// Tailscale for macOS, either the main GUI process (non-sandboxed) or the// system extension (sandboxed).func IsMacSys() bool { return IsMacSysExt() || IsMacSysGUI()}
var isMacSysApp lazy.SyncValue[bool]
// IsMacSysGUI reports whether this process is the main, non-sandboxed GUI process// that ships with the Standalone variant of Tailscale for macOS.func IsMacSysGUI() bool { if runtime.GOOS != "darwin" { return false } return isMacSysApp.Get(func() bool { if AppIdentifierFn != nil { return AppIdentifierFn() == macsysBundleID }
// TODO (barnstar): This check should be redundant once all relevant callers // use AppIdentifierFn. return strings.Contains(os.Getenv("HOME"), "/Containers/io.tailscale.ipn.macsys/") || strings.Contains(os.Getenv("XPC_SERVICE_NAME"), macsysBundleID) })}
var isMacSysExt lazy.SyncValue[bool]
// IsMacSysExt reports whether this binary is the system extension shipped as part of// the standalone "System Extension" (a.k.a. "macsys") version of Tailscale// for macOS.func IsMacSysExt() bool { if runtime.GOOS != "darwin" { return false } return isMacSysExt.Get(func() bool { if AppIdentifierFn != nil { return AppIdentifierFn() == macsysExtBundleId }
// TODO (barnstar): This check should be redundant once all relevant callers // use AppIdentifierFn. exe, err := os.Executable() if err != nil { return false } return filepath.Base(exe) == macsysExtBundleId })}
var isMacAppStore lazy.SyncValue[bool]
// IsMacAppStore returns whether this binary is from the App Store version of Tailscale// for macOS. Returns true for both the network extension and the GUI app.func IsMacAppStore() bool { if runtime.GOOS != "darwin" { return false } return isMacAppStore.Get(func() bool { if AppIdentifierFn != nil { id := AppIdentifierFn() return id == appStoreBundleID || id == appStoreExtBundleId } // TODO (barnstar): This check should be redundant once all relevant callers // use AppIdentifierFn. // Both macsys and app store versions can run CLI executable with // suffix /Contents/MacOS/Tailscale. Check $HOME to filter out running // as macsys. return strings.Contains(os.Getenv("HOME"), "/Containers/io.tailscale.ipn.macos/") || strings.Contains(os.Getenv("XPC_SERVICE_NAME"), appStoreBundleID) })}
var isMacAppStoreGUI lazy.SyncValue[bool]
// IsMacAppStoreGUI reports whether this binary is the GUI app from the App Store// version of Tailscale for macOS.func IsMacAppStoreGUI() bool { if runtime.GOOS != "darwin" { return false } return isMacAppStoreGUI.Get(func() bool { if AppIdentifierFn != nil { return AppIdentifierFn() == appStoreBundleID } // TODO (barnstar): This check should be redundant once all relevant callers // use AppIdentifierFn. exe, err := os.Executable() if err != nil { return false } // Check that this is the GUI binary, and it is not sandboxed. The GUI binary // shipped in the App Store will always have the App Sandbox enabled. return strings.Contains(exe, "/Tailscale") && !IsMacSysGUI() })}
var isAppleTV lazy.SyncValue[bool]
// IsAppleTV reports whether this binary is part of the Tailscale network extension for tvOS.// Needed because runtime.GOOS returns "ios" otherwise.func IsAppleTV() bool { if runtime.GOOS != "ios" { return false } return isAppleTV.Get(func() bool { if AppIdentifierFn != nil { return AppIdentifierFn() == tvOSExtBundleId }
// TODO (barnstar): This check should be redundant once all relevant callers // use AppIdentifierFn. return strings.EqualFold(os.Getenv("XPC_SERVICE_NAME"), tvOSExtBundleId) })}
var isWindowsGUI lazy.SyncValue[bool]
// IsWindowsGUI reports whether the current process is the Windows GUI.func IsWindowsGUI() bool { if runtime.GOOS != "windows" { return false } return isWindowsGUI.Get(func() bool { exe, err := os.Executable() if err != nil { return false } // It is okay to use GOARCH here because we're checking whether our // _own_ process is the GUI. return isGUIExeName(exe, runtime.GOARCH) })}
var isUnstableBuild lazy.SyncValue[bool]
// IsUnstableBuild reports whether this is an unstable build.// That is, whether its minor version number is odd.func IsUnstableBuild() bool { return isUnstableBuild.Get(func() bool { _, rest, ok := strings.Cut(Short(), ".") if !ok { return false } minorStr, _, ok := strings.Cut(rest, ".") if !ok { return false } minor, err := strconv.Atoi(minorStr) if err != nil { return false } return minor%2 == 1 })}
// osVariant returns the OS variant string for systems where we support// multiple ways of running tailscale(d), if any.//// For example: "appstore", "macsys", "darwin".func osVariant() string { if IsMacAppStore() { return "appstore" } if IsMacSys() { return "macsys" } if runtime.GOOS == "darwin" { return "darwin" } return ""}
var isDev = sync.OnceValue(func() bool { return strings.Contains(Short(), "-dev")})
// Meta is a JSON-serializable type that contains all the version// information.type Meta struct { // MajorMinorPatch is the "major.minor.patch" version string, without // any hyphenated suffix. MajorMinorPatch string `json:"majorMinorPatch"`
// IsDev is whether Short contains a -dev suffix. This is whether the build // is a development build (as opposed to an official stable or unstable // build stamped in the usual ways). If you just run "go install" or "go // build" on a dev branch, this will be true. IsDev bool `json:"isDev,omitempty"`
// Short is MajorMinorPatch but optionally adding "-dev" or "-devYYYYMMDD" // for dev builds, depending on how it was build. Short string `json:"short"`
// Long is the full version string, including git commit hash(es) as the // suffix. Long string `json:"long"`
// UnstableBranch is whether the build is from an unstable (development) // branch. That is, it reports whether the minor version is odd. UnstableBranch bool `json:"unstableBranch,omitempty"`
// GitCommit, if non-empty, is the git commit of the // github.com/tailscale/tailscale repository at which Tailscale was // built. Its format is the one returned by `git describe --always // --exclude "*" --dirty --abbrev=200`. GitCommit string `json:"gitCommit,omitempty"`
// GitDirty is whether Go stamped the binary as having dirty version // control changes in the working directory (debug.ReadBuildInfo // setting "vcs.modified" was true). GitDirty bool `json:"gitDirty,omitempty"`
// OSVariant is specific variant of the binary, if applicable. For example, // macsys/appstore/darwin for macOS builds. Nil/empty where not supported // or on oses without variants. OSVariant string `json:"osVariant,omitempty"`
// ExtraGitCommit, if non-empty, is the git commit of a "supplemental" // repository at which Tailscale was built. Its format is the same as // gitCommit. // // ExtraGitCommit is used to track the source revision when the main // Tailscale repository is integrated into and built from another // repository (for example, Tailscale's proprietary code, or the // Android OSS repository). Together, GitCommit and ExtraGitCommit // exactly describe what repositories and commits were used in a // build. ExtraGitCommit string `json:"extraGitCommit,omitempty"`
// DaemonLong is the version number from the tailscaled // daemon, if requested. DaemonLong string `json:"daemonLong,omitempty"`
// GitCommitTime is the commit time of the git commit in GitCommit. GitCommitTime string `json:"gitCommitTime,omitempty"`
// TailscaleGoGitHash is the git commit hash from // https://github.com/tailscale/go used to build this binary, if built // with the Tailscale Go toolchain. Otherwise it is empty. TailscaleGoGitHash string `json:"tailscaleGoGitHash,omitempty"`
// Cap is the current Tailscale capability version. It's a monotonically // incrementing integer that's incremented whenever a new capability is // added. Cap int `json:"cap"`}
var getMeta lazy.SyncValue[Meta]
// GetMeta returns version metadata about the current build.func GetMeta() Meta { return getMeta.Get(func() Meta { return Meta{ MajorMinorPatch: majorMinorPatch(), Short: Short(), Long: Long(), GitCommitTime: getEmbeddedInfo().commitTime, GitCommit: gitCommit(), GitDirty: gitDirty(), OSVariant: osVariant(), ExtraGitCommit: extraGitCommitStamp, IsDev: isDev(), UnstableBranch: IsUnstableBuild(), TailscaleGoGitHash: tailscaleToolchainRev(), Cap: int(tailcfg.CurrentCapabilityVersion), } })}