sysinfo.go 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. package logic
  2. import (
  3. "bytes"
  4. "os"
  5. "os/exec"
  6. "runtime"
  7. "strings"
  8. )
  9. type OSInfo struct {
  10. OS string `json:"os"` // e.g. "ubuntu", "windows", "macos"
  11. OSFamily string `json:"os_family"` // e.g. "linux-debian", "windows"
  12. OSVersion string `json:"os_version"` // e.g. "22.04", "10.0.22631"
  13. KernelVersion string `json:"kernel_version"` // e.g. "6.8.0"
  14. }
  15. /// --- classification helpers you already had ---
  16. func NormalizeOSName(raw string) string {
  17. return strings.ToLower(strings.TrimSpace(raw))
  18. }
  19. // OSFamily returns a normalized OS family string.
  20. // Examples: "linux-debian", "linux-redhat", "linux-arch", "linux-other", "windows", "darwin"
  21. func OSFamily(osName string) string {
  22. osName = NormalizeOSName(osName)
  23. // Non-Linux first
  24. if strings.Contains(osName, "windows") {
  25. return "windows"
  26. }
  27. if strings.Contains(osName, "darwin") || strings.Contains(osName, "mac") || strings.Contains(osName, "os x") {
  28. return "darwin"
  29. }
  30. // Linux families
  31. switch {
  32. // Debian family
  33. case containsAny(osName,
  34. "debian", "ubuntu", "pop", "linuxmint", "kali", "raspbian", "elementary"):
  35. return "linux-debian"
  36. // Red Hat family
  37. case containsAny(osName,
  38. "rhel", "red hat", "centos", "rocky", "alma", "fedora", "oracle linux", "ol"):
  39. return "linux-redhat"
  40. // SUSE family
  41. case containsAny(osName,
  42. "suse", "opensuse", "sles"):
  43. return "linux-suse"
  44. // Arch family
  45. case containsAny(osName,
  46. "arch", "manjaro", "endeavouros", "garuda"):
  47. return "linux-arch"
  48. // Gentoo
  49. case strings.Contains(osName, "gentoo"):
  50. return "linux-gentoo"
  51. // Alpine, Amazon, BusyBox, etc.
  52. case containsAny(osName,
  53. "alpine", "amazon", "busybox"):
  54. return "linux-other"
  55. }
  56. // Fallbacks
  57. if strings.Contains(osName, "linux") {
  58. return "linux-other"
  59. }
  60. return "unknown"
  61. }
  62. func containsAny(s string, subs ...string) bool {
  63. for _, sub := range subs {
  64. if strings.Contains(s, sub) {
  65. return true
  66. }
  67. }
  68. return false
  69. }
  70. /// --- public entrypoint ---
  71. // GetOSInfo returns OS, OSFamily, OSVersion and KernelVersion for the current platform.
  72. func GetOSInfo() OSInfo {
  73. switch runtime.GOOS {
  74. case "linux":
  75. return getLinuxOSInfo()
  76. case "darwin":
  77. return getDarwinOSInfo()
  78. case "windows":
  79. return getWindowsOSInfo()
  80. default:
  81. // Fallback for other UNIX-likes; best-effort
  82. kernel := strings.TrimSpace(runCmd("uname", "-r"))
  83. name := runtime.GOOS
  84. return OSInfo{
  85. OS: NormalizeOSName(name),
  86. OSFamily: OSFamily(name),
  87. OSVersion: "",
  88. KernelVersion: CleanVersion(kernel),
  89. }
  90. }
  91. }
  92. /// --- Linux ---
  93. func getLinuxOSInfo() OSInfo {
  94. var osName, osVersion string
  95. data, err := os.ReadFile("/etc/os-release")
  96. if err == nil {
  97. lines := strings.Split(string(data), "\n")
  98. for _, line := range lines {
  99. line = strings.TrimSpace(line)
  100. if line == "" || strings.HasPrefix(line, "#") {
  101. continue
  102. }
  103. parts := strings.SplitN(line, "=", 2)
  104. if len(parts) != 2 {
  105. continue
  106. }
  107. key := parts[0]
  108. value := strings.Trim(parts[1], `"'`)
  109. switch key {
  110. case "ID":
  111. osName = value
  112. case "VERSION_ID":
  113. osVersion = value
  114. }
  115. }
  116. }
  117. if osName == "" {
  118. // Fallback
  119. osName = "linux"
  120. }
  121. kernel := strings.TrimSpace(runCmd("uname", "-r"))
  122. // trim extras like -generic
  123. if idx := strings.Index(kernel, "-"); idx > 0 {
  124. kernel = kernel[:idx]
  125. }
  126. normName := NormalizeOSName(osName)
  127. return OSInfo{
  128. OS: "linux",
  129. OSFamily: OSFamily(normName),
  130. OSVersion: CleanVersion(osVersion),
  131. KernelVersion: CleanVersion(kernel),
  132. }
  133. }
  134. /// --- macOS (darwin) ---
  135. func getDarwinOSInfo() OSInfo {
  136. productName := strings.TrimSpace(runCmd("sw_vers", "-productName"))
  137. productVer := strings.TrimSpace(runCmd("sw_vers", "-productVersion"))
  138. if productName == "" {
  139. productName = "macos"
  140. }
  141. kernel := strings.TrimSpace(runCmd("uname", "-r"))
  142. if idx := strings.Index(kernel, "-"); idx > 0 {
  143. kernel = kernel[:idx]
  144. }
  145. normName := NormalizeOSName(productName)
  146. return OSInfo{
  147. OS: "darwin",
  148. OSFamily: OSFamily(normName), // "darwin"
  149. OSVersion: CleanVersion(productVer), // e.g. "15.0"
  150. KernelVersion: CleanVersion(kernel),
  151. }
  152. }
  153. /// --- Windows ---
  154. func getWindowsOSInfo() OSInfo {
  155. // OS name: we just say "windows"
  156. osName := "windows"
  157. // OS version via "wmic" or "ver" as fallback
  158. var version string
  159. // Try wmic first (may be missing on newer builds but often still present)
  160. out := runCmd("wmic", "os", "get", "Version", "/value")
  161. for _, line := range strings.Split(out, "\n") {
  162. line = strings.TrimSpace(line)
  163. if strings.HasPrefix(line, "Version=") {
  164. version = strings.TrimPrefix(line, "Version=")
  165. version = strings.TrimSpace(version)
  166. break
  167. }
  168. }
  169. if version == "" {
  170. // Fallback to "ver"
  171. raw := strings.TrimSpace(runCmd("cmd", "/C", "ver"))
  172. version = raw // you can add better parsing if you need
  173. }
  174. // On Windows, kernel and OS version are effectively tied; reuse
  175. kernel := version
  176. normName := NormalizeOSName(osName)
  177. return OSInfo{
  178. OS: "windows", // "windows"
  179. OSFamily: OSFamily(normName),
  180. OSVersion: CleanVersion(version), // e.g. "10.0.22631"
  181. KernelVersion: CleanVersion(kernel),
  182. }
  183. }
  184. /// --- small helper to run commands safely ---
  185. func runCmd(name string, args ...string) string {
  186. cmd := exec.Command(name, args...)
  187. var buf bytes.Buffer
  188. cmd.Stdout = &buf
  189. _ = cmd.Run() // ignore error; best-effort
  190. return buf.String()
  191. }