mirror of
				https://github.com/tailscale/tailscale.git
				synced 2025-10-31 16:22:03 +01:00 
			
		
		
		
	On Windows arm64 we are going to need to ship two different GUI builds; one for Win10 (GOARCH=386) and one for Win11 (GOARCH=amd64, tags += winui). Due to quirks in MSI packaging, they cannot both share the same filename. This requires some fixes in places where we have hardcoded "tailscale-ipn" as the GUI filename. We also do some cleanup in clientupdate to ensure that autoupdates will continue to work correctly with the temporary "-winui" package variant. Fixes #17480 Updates https://github.com/tailscale/corp/issues/29940 Signed-off-by: Aaron Klotz <aaron@tailscale.com>
		
			
				
	
	
		
			274 lines
		
	
	
		
			8.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			274 lines
		
	
	
		
			8.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright (c) Tailscale Inc & AUTHORS
 | |
| // SPDX-License-Identifier: BSD-3-Clause
 | |
| 
 | |
| package version
 | |
| 
 | |
| import (
 | |
| 	"os"
 | |
| 	"path/filepath"
 | |
| 	"runtime"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| 	"sync"
 | |
| 
 | |
| 	"tailscale.com/tailcfg"
 | |
| 	"tailscale.com/types/lazy"
 | |
| )
 | |
| 
 | |
| // 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 (System Extension) version on macOS, and false for
 | |
| // tailscaled-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 {
 | |
| 		return strings.Contains(os.Getenv("HOME"), "/Containers/io.tailscale.ipn.macsys/") ||
 | |
| 			strings.Contains(os.Getenv("XPC_SERVICE_NAME"), "io.tailscale.ipn.macsys")
 | |
| 	})
 | |
| }
 | |
| 
 | |
| 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 {
 | |
| 		exe, err := os.Executable()
 | |
| 		if err != nil {
 | |
| 			return false
 | |
| 		}
 | |
| 		return filepath.Base(exe) == "io.tailscale.ipn.macsys.network-extension"
 | |
| 	})
 | |
| }
 | |
| 
 | |
| 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 {
 | |
| 		// 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"), "io.tailscale.ipn.macos")
 | |
| 	})
 | |
| }
 | |
| 
 | |
| 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 {
 | |
| 		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 {
 | |
| 		return strings.EqualFold(os.Getenv("XPC_SERVICE_NAME"), "io.tailscale.ipn.ios.network-extension-tvos")
 | |
| 	})
 | |
| }
 | |
| 
 | |
| 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
 | |
| 	})
 | |
| }
 | |
| 
 | |
| 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"`
 | |
| 
 | |
| 	// 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(),
 | |
| 			ExtraGitCommit:  extraGitCommitStamp,
 | |
| 			IsDev:           isDev(),
 | |
| 			UnstableBranch:  IsUnstableBuild(),
 | |
| 			Cap:             int(tailcfg.CurrentCapabilityVersion),
 | |
| 		}
 | |
| 	})
 | |
| }
 |