distro.go 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. // Package distro reports which distro we're running on.
  4. package distro
  5. import (
  6. "bytes"
  7. "io"
  8. "os"
  9. "runtime"
  10. "strconv"
  11. "tailscale.com/types/lazy"
  12. "tailscale.com/util/lineread"
  13. )
  14. type Distro string
  15. const (
  16. Debian = Distro("debian")
  17. Arch = Distro("arch")
  18. Synology = Distro("synology")
  19. OpenWrt = Distro("openwrt")
  20. NixOS = Distro("nixos")
  21. QNAP = Distro("qnap")
  22. Pfsense = Distro("pfsense")
  23. OPNsense = Distro("opnsense")
  24. TrueNAS = Distro("truenas")
  25. Gokrazy = Distro("gokrazy")
  26. WDMyCloud = Distro("wdmycloud")
  27. Unraid = Distro("unraid")
  28. Alpine = Distro("alpine")
  29. )
  30. var distro lazy.SyncValue[Distro]
  31. var isWSL lazy.SyncValue[bool]
  32. // Get returns the current distro, or the empty string if unknown.
  33. func Get() Distro {
  34. return distro.Get(func() Distro {
  35. switch runtime.GOOS {
  36. case "linux":
  37. return linuxDistro()
  38. case "freebsd":
  39. return freebsdDistro()
  40. default:
  41. return Distro("")
  42. }
  43. })
  44. }
  45. // IsWSL reports whether we're running in the Windows Subsystem for Linux.
  46. func IsWSL() bool {
  47. return runtime.GOOS == "linux" && isWSL.Get(func() bool {
  48. // We could look for $WSL_INTEROP instead, however that may be missing if
  49. // the user has started to use systemd in WSL2.
  50. return have("/proc/sys/fs/binfmt_misc/WSLInterop") || have("/mnt/wsl")
  51. })
  52. }
  53. func have(file string) bool {
  54. _, err := os.Stat(file)
  55. return err == nil
  56. }
  57. func haveDir(file string) bool {
  58. fi, err := os.Stat(file)
  59. return err == nil && fi.IsDir()
  60. }
  61. func linuxDistro() Distro {
  62. switch {
  63. case haveDir("/usr/syno"):
  64. return Synology
  65. case have("/usr/local/bin/freenas-debug"):
  66. // TrueNAS Scale runs on debian
  67. return TrueNAS
  68. case have("/etc/debian_version"):
  69. return Debian
  70. case have("/etc/arch-release"):
  71. return Arch
  72. case have("/etc/openwrt_version"):
  73. return OpenWrt
  74. case have("/run/current-system/sw/bin/nixos-version"):
  75. return NixOS
  76. case have("/etc/config/uLinux.conf"):
  77. return QNAP
  78. case haveDir("/gokrazy"):
  79. return Gokrazy
  80. case have("/usr/local/wdmcserver/bin/wdmc.xml"): // Western Digital MyCloud OS3
  81. return WDMyCloud
  82. case have("/usr/sbin/wd_crontab.sh"): // Western Digital MyCloud OS5
  83. return WDMyCloud
  84. case have("/etc/unraid-version"):
  85. return Unraid
  86. case have("/etc/alpine-release"):
  87. return Alpine
  88. }
  89. return ""
  90. }
  91. func freebsdDistro() Distro {
  92. switch {
  93. case have("/etc/pfSense-rc"):
  94. return Pfsense
  95. case have("/usr/local/sbin/opnsense-shell"):
  96. return OPNsense
  97. case have("/usr/local/bin/freenas-debug"):
  98. // TrueNAS Core runs on FreeBSD
  99. return TrueNAS
  100. }
  101. return ""
  102. }
  103. var dsmVersion lazy.SyncValue[int]
  104. // DSMVersion reports the Synology DSM major version.
  105. //
  106. // If not Synology, it reports 0.
  107. func DSMVersion() int {
  108. if runtime.GOOS != "linux" {
  109. return 0
  110. }
  111. return dsmVersion.Get(func() int {
  112. if Get() != Synology {
  113. return 0
  114. }
  115. // This is set when running as a package:
  116. v, _ := strconv.Atoi(os.Getenv("SYNOPKG_DSM_VERSION_MAJOR"))
  117. if v != 0 {
  118. return v
  119. }
  120. // But when run from the command line, we have to read it from the file:
  121. lineread.File("/etc/VERSION", func(line []byte) error {
  122. line = bytes.TrimSpace(line)
  123. if string(line) == `majorversion="7"` {
  124. v = 7
  125. return io.EOF
  126. }
  127. if string(line) == `majorversion="6"` {
  128. v = 6
  129. return io.EOF
  130. }
  131. return nil
  132. })
  133. return v
  134. })
  135. }