prop.go 6.7 KB

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