package platform import ( "bufio" "os" "strings" "gitgud.io/mike/mpv-manager/pkg/log" ) // osReleasePaths lists the standard os-release locations, checked in order // (/usr/lib/os-release is the fallback mandated by the os-release spec). var osReleasePaths = []string{"/etc/os-release", "/usr/lib/os-release"} func detectLinuxDistro() (string, string) { if id, idLike, _ := readOSRelease(); id != "" { return id, GetDistroFamily(id, idLike) } if _, err := os.Stat("/etc/redhat-release"); err == nil { return parseRedHatRelease() } if _, err := os.Stat("/etc/debian_version"); err == nil { return "debian", "debian" } log.Warn("Could not detect Linux distribution") return "", "" } // readOSRelease parses the first available os-release file and returns its // ID, ID_LIKE, and VARIANT_ID fields (surrounding quotes stripped). func readOSRelease() (id, idLike, variantID string) { for _, path := range osReleasePaths { file, err := os.Open(path) if err != nil { continue } scanner := bufio.NewScanner(file) for scanner.Scan() { line := scanner.Text() switch { case strings.HasPrefix(line, "ID="): id = strings.Trim(strings.TrimPrefix(line, "ID="), `"`) case strings.HasPrefix(line, "ID_LIKE="): idLike = strings.Trim(strings.TrimPrefix(line, "ID_LIKE="), `"`) case strings.HasPrefix(line, "VARIANT_ID="): variantID = strings.Trim(strings.TrimPrefix(line, "VARIANT_ID="), `"`) } } file.Close() return id, idLike, variantID } return "", "", "" } // GetLinuxDistroFamily detects the distro family by reading the system // os-release files. Returns "" when no distribution can be identified. func GetLinuxDistroFamily() string { id, idLike, _ := readOSRelease() if id == "" { return "" } return GetDistroFamily(id, idLike) } func parseRedHatRelease() (string, string) { file, err := os.ReadFile("/etc/redhat-release") if err != nil { log.Debug("Failed to read /etc/redhat-release: " + err.Error()) return "", "" } content := string(file) content = strings.ToLower(content) switch { case strings.Contains(content, "fedora"): return "fedora", "rhel" case strings.Contains(content, "centos"): return "centos", "rhel" case strings.Contains(content, "red hat"): return "rhel", "rhel" default: return "rhel", "rhel" } } // GetDistroFamily maps an os-release ID / ID_LIKE pair to a distro family // (debian, rhel, arch, suse). ID is matched exactly; ID_LIKE is split into // its space-separated fields and each field is matched exactly. Unknown IDs // are returned unchanged so callers can report them. func GetDistroFamily(id, idLike string) string { id = strings.ToLower(strings.TrimSpace(id)) debianFamily := []string{"debian", "ubuntu", "linuxmint", "mint", "pop", "elementary"} rhelFamily := []string{"rhel", "fedora", "centos", "redhat", "rocky", "almalinux"} archFamily := []string{"arch", "archlinux", "manjaro", "endeavouros", "garuda", "cachyos"} suseFamily := []string{"opensuse", "opensuse-leap", "opensuse-tumbleweed", "opensuse-microos", "suse", "sles"} matches := func(family []string) bool { for _, d := range family { if id == d { return true } } for _, like := range strings.Fields(strings.ToLower(idLike)) { for _, d := range family { if like == d { return true } } } return false } switch { case matches(debianFamily): return "debian" case matches(rhelFamily): return "rhel" case matches(archFamily): return "arch" case matches(suseFamily): return "suse" } return id }