prop.go 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. package version
  4. import (
  5. "os"
  6. "path/filepath"
  7. "runtime"
  8. "strconv"
  9. "strings"
  10. "sync"
  11. "tailscale.com/tailcfg"
  12. "tailscale.com/types/lazy"
  13. )
  14. // IsMobile reports whether this is a mobile client build.
  15. func IsMobile() bool {
  16. return runtime.GOOS == "android" || runtime.GOOS == "ios"
  17. }
  18. // OS returns runtime.GOOS, except instead of returning "darwin" it returns
  19. // "iOS" or "macOS".
  20. func OS() string {
  21. // If you're wondering why we have this function that just returns
  22. // runtime.GOOS written differently: in the old days, Go reported
  23. // GOOS=darwin for both iOS and macOS, so we needed this function to
  24. // differentiate them. Then a later Go release added GOOS=ios as a separate
  25. // platform, but by then the "iOS" and "macOS" values we'd picked, with that
  26. // exact capitalization, were already baked into databases.
  27. if IsAppleTV() {
  28. return "tvOS"
  29. }
  30. if runtime.GOOS == "ios" {
  31. return "iOS"
  32. }
  33. if runtime.GOOS == "darwin" {
  34. return "macOS"
  35. }
  36. return runtime.GOOS
  37. }
  38. // IsMacGUIVariant reports whether runtime.GOOS=="darwin" and this one of the
  39. // two GUI variants (that is, not tailscaled-on-macOS).
  40. // This predicate should not be used to determine sandboxing properties. It's
  41. // meant for callers to determine whether the NetworkExtension-like auto-netns
  42. // is in effect.
  43. func IsMacGUIVariant() bool {
  44. return IsMacAppStore() || IsMacSysExt()
  45. }
  46. // IsSandboxedMacOS reports whether this process is a sandboxed macOS
  47. // process (either the app or the extension). It is true for the Mac App Store
  48. // and macsys (System Extension) version on macOS, and false for
  49. // tailscaled-on-macOS.
  50. func IsSandboxedMacOS() bool {
  51. return IsMacAppStore() || IsMacSysExt()
  52. }
  53. // IsMacSys reports whether this process is part of the Standalone variant of
  54. // Tailscale for macOS, either the main GUI process (non-sandboxed) or the
  55. // system extension (sandboxed).
  56. func IsMacSys() bool {
  57. return IsMacSysExt() || IsMacSysGUI()
  58. }
  59. var isMacSysApp lazy.SyncValue[bool]
  60. // IsMacSysGUI reports whether this process is the main, non-sandboxed GUI process
  61. // that ships with the Standalone variant of Tailscale for macOS.
  62. func IsMacSysGUI() bool {
  63. if runtime.GOOS != "darwin" {
  64. return false
  65. }
  66. return isMacSysApp.Get(func() bool {
  67. return strings.Contains(os.Getenv("HOME"), "/Containers/io.tailscale.ipn.macsys/") ||
  68. strings.Contains(os.Getenv("XPC_SERVICE_NAME"), "io.tailscale.ipn.macsys")
  69. })
  70. }
  71. var isMacSysExt lazy.SyncValue[bool]
  72. // IsMacSysExt reports whether this binary is the system extension shipped as part of
  73. // the standalone "System Extension" (a.k.a. "macsys") version of Tailscale
  74. // for macOS.
  75. func IsMacSysExt() bool {
  76. if runtime.GOOS != "darwin" {
  77. return false
  78. }
  79. return isMacSysExt.Get(func() bool {
  80. exe, err := os.Executable()
  81. if err != nil {
  82. return false
  83. }
  84. return filepath.Base(exe) == "io.tailscale.ipn.macsys.network-extension"
  85. })
  86. }
  87. var isMacAppStore lazy.SyncValue[bool]
  88. // IsMacAppStore returns whether this binary is from the App Store version of Tailscale
  89. // for macOS. Returns true for both the network extension and the GUI app.
  90. func IsMacAppStore() bool {
  91. if runtime.GOOS != "darwin" {
  92. return false
  93. }
  94. return isMacAppStore.Get(func() bool {
  95. // Both macsys and app store versions can run CLI executable with
  96. // suffix /Contents/MacOS/Tailscale. Check $HOME to filter out running
  97. // as macsys.
  98. return strings.Contains(os.Getenv("HOME"), "/Containers/io.tailscale.ipn.macos/") ||
  99. strings.Contains(os.Getenv("XPC_SERVICE_NAME"), "io.tailscale.ipn.macos")
  100. })
  101. }
  102. var isMacAppStoreGUI lazy.SyncValue[bool]
  103. // IsMacAppStoreGUI reports whether this binary is the GUI app from the App Store
  104. // version of Tailscale for macOS.
  105. func IsMacAppStoreGUI() bool {
  106. if runtime.GOOS != "darwin" {
  107. return false
  108. }
  109. return isMacAppStoreGUI.Get(func() bool {
  110. exe, err := os.Executable()
  111. if err != nil {
  112. return false
  113. }
  114. // Check that this is the GUI binary, and it is not sandboxed. The GUI binary
  115. // shipped in the App Store will always have the App Sandbox enabled.
  116. return strings.Contains(exe, "/Tailscale") && !IsMacSysGUI()
  117. })
  118. }
  119. var isAppleTV lazy.SyncValue[bool]
  120. // IsAppleTV reports whether this binary is part of the Tailscale network extension for tvOS.
  121. // Needed because runtime.GOOS returns "ios" otherwise.
  122. func IsAppleTV() bool {
  123. if runtime.GOOS != "ios" {
  124. return false
  125. }
  126. return isAppleTV.Get(func() bool {
  127. return strings.EqualFold(os.Getenv("XPC_SERVICE_NAME"), "io.tailscale.ipn.ios.network-extension-tvos")
  128. })
  129. }
  130. var isWindowsGUI lazy.SyncValue[bool]
  131. // IsWindowsGUI reports whether the current process is the Windows GUI.
  132. func IsWindowsGUI() bool {
  133. if runtime.GOOS != "windows" {
  134. return false
  135. }
  136. return isWindowsGUI.Get(func() bool {
  137. exe, err := os.Executable()
  138. if err != nil {
  139. return false
  140. }
  141. // It is okay to use GOARCH here because we're checking whether our
  142. // _own_ process is the GUI.
  143. return isGUIExeName(exe, runtime.GOARCH)
  144. })
  145. }
  146. var isUnstableBuild lazy.SyncValue[bool]
  147. // IsUnstableBuild reports whether this is an unstable build.
  148. // That is, whether its minor version number is odd.
  149. func IsUnstableBuild() bool {
  150. return isUnstableBuild.Get(func() bool {
  151. _, rest, ok := strings.Cut(Short(), ".")
  152. if !ok {
  153. return false
  154. }
  155. minorStr, _, ok := strings.Cut(rest, ".")
  156. if !ok {
  157. return false
  158. }
  159. minor, err := strconv.Atoi(minorStr)
  160. if err != nil {
  161. return false
  162. }
  163. return minor%2 == 1
  164. })
  165. }
  166. var isDev = sync.OnceValue(func() bool {
  167. return strings.Contains(Short(), "-dev")
  168. })
  169. // Meta is a JSON-serializable type that contains all the version
  170. // information.
  171. type Meta struct {
  172. // MajorMinorPatch is the "major.minor.patch" version string, without
  173. // any hyphenated suffix.
  174. MajorMinorPatch string `json:"majorMinorPatch"`
  175. // IsDev is whether Short contains a -dev suffix. This is whether the build
  176. // is a development build (as opposed to an official stable or unstable
  177. // build stamped in the usual ways). If you just run "go install" or "go
  178. // build" on a dev branch, this will be true.
  179. IsDev bool `json:"isDev,omitempty"`
  180. // Short is MajorMinorPatch but optionally adding "-dev" or "-devYYYYMMDD"
  181. // for dev builds, depending on how it was build.
  182. Short string `json:"short"`
  183. // Long is the full version string, including git commit hash(es) as the
  184. // suffix.
  185. Long string `json:"long"`
  186. // UnstableBranch is whether the build is from an unstable (development)
  187. // branch. That is, it reports whether the minor version is odd.
  188. UnstableBranch bool `json:"unstableBranch,omitempty"`
  189. // GitCommit, if non-empty, is the git commit of the
  190. // github.com/tailscale/tailscale repository at which Tailscale was
  191. // built. Its format is the one returned by `git describe --always
  192. // --exclude "*" --dirty --abbrev=200`.
  193. GitCommit string `json:"gitCommit,omitempty"`
  194. // GitDirty is whether Go stamped the binary as having dirty version
  195. // control changes in the working directory (debug.ReadBuildInfo
  196. // setting "vcs.modified" was true).
  197. GitDirty bool `json:"gitDirty,omitempty"`
  198. // ExtraGitCommit, if non-empty, is the git commit of a "supplemental"
  199. // repository at which Tailscale was built. Its format is the same as
  200. // gitCommit.
  201. //
  202. // ExtraGitCommit is used to track the source revision when the main
  203. // Tailscale repository is integrated into and built from another
  204. // repository (for example, Tailscale's proprietary code, or the
  205. // Android OSS repository). Together, GitCommit and ExtraGitCommit
  206. // exactly describe what repositories and commits were used in a
  207. // build.
  208. ExtraGitCommit string `json:"extraGitCommit,omitempty"`
  209. // DaemonLong is the version number from the tailscaled
  210. // daemon, if requested.
  211. DaemonLong string `json:"daemonLong,omitempty"`
  212. // GitCommitTime is the commit time of the git commit in GitCommit.
  213. GitCommitTime string `json:"gitCommitTime,omitempty"`
  214. // Cap is the current Tailscale capability version. It's a monotonically
  215. // incrementing integer that's incremented whenever a new capability is
  216. // added.
  217. Cap int `json:"cap"`
  218. }
  219. var getMeta lazy.SyncValue[Meta]
  220. // GetMeta returns version metadata about the current build.
  221. func GetMeta() Meta {
  222. return getMeta.Get(func() Meta {
  223. return Meta{
  224. MajorMinorPatch: majorMinorPatch(),
  225. Short: Short(),
  226. Long: Long(),
  227. GitCommitTime: getEmbeddedInfo().commitTime,
  228. GitCommit: gitCommit(),
  229. GitDirty: gitDirty(),
  230. ExtraGitCommit: extraGitCommitStamp,
  231. IsDev: isDev(),
  232. UnstableBranch: IsUnstableBuild(),
  233. Cap: int(tailcfg.CurrentCapabilityVersion),
  234. }
  235. })
  236. }