mirror of
https://github.com/tailscale/tailscale.git
synced 2026-02-09 09:41:49 +01:00
This file was never truly necessary and has never actually been used in the history of Tailscale's open source releases. A Brief History of AUTHORS files --- The AUTHORS file was a pattern developed at Google, originally for Chromium, then adopted by Go and a bunch of other projects. The problem was that Chromium originally had a copyright line only recognizing Google as the copyright holder. Because Google (and most open source projects) do not require copyright assignemnt for contributions, each contributor maintains their copyright. Some large corporate contributors then tried to add their own name to the copyright line in the LICENSE file or in file headers. This quickly becomes unwieldy, and puts a tremendous burden on anyone building on top of Chromium, since the license requires that they keep all copyright lines intact. The compromise was to create an AUTHORS file that would list all of the copyright holders. The LICENSE file and source file headers would then include that list by reference, listing the copyright holder as "The Chromium Authors". This also become cumbersome to simply keep the file up to date with a high rate of new contributors. Plus it's not always obvious who the copyright holder is. Sometimes it is the individual making the contribution, but many times it may be their employer. There is no way for the proejct maintainer to know. Eventually, Google changed their policy to no longer recommend trying to keep the AUTHORS file up to date proactively, and instead to only add to it when requested: https://opensource.google/docs/releasing/authors. They are also clear that: > Adding contributors to the AUTHORS file is entirely within the > project's discretion and has no implications for copyright ownership. It was primarily added to appease a small number of large contributors that insisted that they be recognized as copyright holders (which was entirely their right to do). But it's not truly necessary, and not even the most accurate way of identifying contributors and/or copyright holders. In practice, we've never added anyone to our AUTHORS file. It only lists Tailscale, so it's not really serving any purpose. It also causes confusion because Tailscalars put the "Tailscale Inc & AUTHORS" header in other open source repos which don't actually have an AUTHORS file, so it's ambiguous what that means. Instead, we just acknowledge that the contributors to Tailscale (whoever they are) are copyright holders for their individual contributions. We also have the benefit of using the DCO (developercertificate.org) which provides some additional certification of their right to make the contribution. The source file changes were purely mechanical with: git ls-files | xargs sed -i -e 's/\(Tailscale Inc &\) AUTHORS/\1 contributors/g' Updates #cleanup Change-Id: Ia101a4a3005adb9118051b3416f5a64a4a45987d Signed-off-by: Will Norris <will@tailscale.com>
341 lines
11 KiB
Go
341 lines
11 KiB
Go
// 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"`
|
|
|
|
// 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(),
|
|
Cap: int(tailcfg.CurrentCapabilityVersion),
|
|
}
|
|
})
|
|
}
|