| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227 |
- package logic
- import (
- "bytes"
- "os"
- "os/exec"
- "runtime"
- "strings"
- )
- type OSInfo struct {
- OS string `json:"os"` // e.g. "ubuntu", "windows", "macos"
- OSFamily string `json:"os_family"` // e.g. "linux-debian", "windows"
- OSVersion string `json:"os_version"` // e.g. "22.04", "10.0.22631"
- KernelVersion string `json:"kernel_version"` // e.g. "6.8.0"
- }
- /// --- classification helpers you already had ---
- func NormalizeOSName(raw string) string {
- return strings.ToLower(strings.TrimSpace(raw))
- }
- // OSFamily returns a normalized OS family string.
- // Examples: "linux-debian", "linux-redhat", "linux-arch", "linux-other", "windows", "darwin"
- func OSFamily(osName string) string {
- osName = NormalizeOSName(osName)
- // Non-Linux first
- if strings.Contains(osName, "windows") {
- return "windows"
- }
- if strings.Contains(osName, "darwin") || strings.Contains(osName, "mac") || strings.Contains(osName, "os x") {
- return "darwin"
- }
- // Linux families
- switch {
- // Debian family
- case containsAny(osName,
- "debian", "ubuntu", "pop", "linuxmint", "kali", "raspbian", "elementary"):
- return "linux-debian"
- // Red Hat family
- case containsAny(osName,
- "rhel", "red hat", "centos", "rocky", "alma", "fedora", "oracle linux", "ol"):
- return "linux-redhat"
- // SUSE family
- case containsAny(osName,
- "suse", "opensuse", "sles"):
- return "linux-suse"
- // Arch family
- case containsAny(osName,
- "arch", "manjaro", "endeavouros", "garuda"):
- return "linux-arch"
- // Gentoo
- case strings.Contains(osName, "gentoo"):
- return "linux-gentoo"
- // Alpine, Amazon, BusyBox, etc.
- case containsAny(osName,
- "alpine", "amazon", "busybox"):
- return "linux-other"
- }
- // Fallbacks
- if strings.Contains(osName, "linux") {
- return "linux-other"
- }
- return "unknown"
- }
- func containsAny(s string, subs ...string) bool {
- for _, sub := range subs {
- if strings.Contains(s, sub) {
- return true
- }
- }
- return false
- }
- /// --- public entrypoint ---
- // GetOSInfo returns OS, OSFamily, OSVersion and KernelVersion for the current platform.
- func GetOSInfo() OSInfo {
- switch runtime.GOOS {
- case "linux":
- return getLinuxOSInfo()
- case "darwin":
- return getDarwinOSInfo()
- case "windows":
- return getWindowsOSInfo()
- default:
- // Fallback for other UNIX-likes; best-effort
- kernel := strings.TrimSpace(runCmd("uname", "-r"))
- name := runtime.GOOS
- return OSInfo{
- OS: NormalizeOSName(name),
- OSFamily: OSFamily(name),
- OSVersion: "",
- KernelVersion: CleanVersion(kernel),
- }
- }
- }
- /// --- Linux ---
- func getLinuxOSInfo() OSInfo {
- var osName, osVersion string
- data, err := os.ReadFile("/etc/os-release")
- if err == nil {
- lines := strings.Split(string(data), "\n")
- for _, line := range lines {
- line = strings.TrimSpace(line)
- if line == "" || strings.HasPrefix(line, "#") {
- continue
- }
- parts := strings.SplitN(line, "=", 2)
- if len(parts) != 2 {
- continue
- }
- key := parts[0]
- value := strings.Trim(parts[1], `"'`)
- switch key {
- case "ID":
- osName = value
- case "VERSION_ID":
- osVersion = value
- }
- }
- }
- if osName == "" {
- // Fallback
- osName = "linux"
- }
- kernel := strings.TrimSpace(runCmd("uname", "-r"))
- // trim extras like -generic
- if idx := strings.Index(kernel, "-"); idx > 0 {
- kernel = kernel[:idx]
- }
- normName := NormalizeOSName(osName)
- return OSInfo{
- OS: "linux",
- OSFamily: OSFamily(normName),
- OSVersion: CleanVersion(osVersion),
- KernelVersion: CleanVersion(kernel),
- }
- }
- /// --- macOS (darwin) ---
- func getDarwinOSInfo() OSInfo {
- productName := strings.TrimSpace(runCmd("sw_vers", "-productName"))
- productVer := strings.TrimSpace(runCmd("sw_vers", "-productVersion"))
- if productName == "" {
- productName = "macos"
- }
- kernel := strings.TrimSpace(runCmd("uname", "-r"))
- if idx := strings.Index(kernel, "-"); idx > 0 {
- kernel = kernel[:idx]
- }
- normName := NormalizeOSName(productName)
- return OSInfo{
- OS: "darwin",
- OSFamily: OSFamily(normName), // "darwin"
- OSVersion: CleanVersion(productVer), // e.g. "15.0"
- KernelVersion: CleanVersion(kernel),
- }
- }
- /// --- Windows ---
- func getWindowsOSInfo() OSInfo {
- // OS name: we just say "windows"
- osName := "windows"
- // OS version via "wmic" or "ver" as fallback
- var version string
- // Try wmic first (may be missing on newer builds but often still present)
- out := runCmd("wmic", "os", "get", "Version", "/value")
- for _, line := range strings.Split(out, "\n") {
- line = strings.TrimSpace(line)
- if strings.HasPrefix(line, "Version=") {
- version = strings.TrimPrefix(line, "Version=")
- version = strings.TrimSpace(version)
- break
- }
- }
- if version == "" {
- // Fallback to "ver"
- raw := strings.TrimSpace(runCmd("cmd", "/C", "ver"))
- version = raw // you can add better parsing if you need
- }
- // On Windows, kernel and OS version are effectively tied; reuse
- kernel := version
- normName := NormalizeOSName(osName)
- return OSInfo{
- OS: "windows", // "windows"
- OSFamily: OSFamily(normName),
- OSVersion: CleanVersion(version), // e.g. "10.0.22631"
- KernelVersion: CleanVersion(kernel),
- }
- }
- /// --- small helper to run commands safely ---
- func runCmd(name string, args ...string) string {
- cmd := exec.Command(name, args...)
- var buf bytes.Buffer
- cmd.Stdout = &buf
- _ = cmd.Run() // ignore error; best-effort
- return buf.String()
- }
|