192 lines
4.6 KiB
Go
192 lines
4.6 KiB
Go
package distro
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"os"
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
// DistroInfo represents Linux distribution information
|
|
type DistroInfo struct {
|
|
ID string
|
|
Name string
|
|
Version string
|
|
Family string
|
|
Codename string
|
|
}
|
|
|
|
// SupportedDistros lists all distributions we can handle
|
|
var SupportedDistros = map[string]string{
|
|
"ubuntu": "debian",
|
|
"debian": "debian",
|
|
"mint": "debian",
|
|
"pop": "debian",
|
|
"elementary": "debian",
|
|
"kali": "debian",
|
|
"arch": "arch",
|
|
"manjaro": "arch",
|
|
"endeavouros": "arch",
|
|
"artix": "arch",
|
|
"fedora": "fedora",
|
|
"centos": "fedora",
|
|
"rhel": "fedora",
|
|
"rocky": "fedora",
|
|
"alma": "fedora",
|
|
"opensuse": "opensuse",
|
|
"sles": "opensuse",
|
|
"gentoo": "gentoo",
|
|
"alpine": "alpine",
|
|
"slackware": "slackware",
|
|
"void": "void",
|
|
}
|
|
|
|
// DetectDistribution attempts to detect the current Linux distribution
|
|
func DetectDistribution() (*DistroInfo, error) {
|
|
// Try /etc/os-release first (most reliable)
|
|
if info, err := parseOSRelease(); err == nil {
|
|
return info, nil
|
|
}
|
|
|
|
// Fallback to /etc/lsb-release
|
|
if info, err := parseLSBRelease(); err == nil {
|
|
return info, nil
|
|
}
|
|
|
|
// Last resort: check specific distribution files
|
|
if info, err := detectFromSpecificFiles(); err == nil {
|
|
return info, nil
|
|
}
|
|
|
|
return nil, fmt.Errorf("unable to detect Linux distribution - are you sure you're not running Windows? 🤔")
|
|
}
|
|
|
|
// parseOSRelease parses /etc/os-release file
|
|
func parseOSRelease() (*DistroInfo, error) {
|
|
return parseKeyValueFile("/etc/os-release")
|
|
}
|
|
|
|
// parseLSBRelease parses /etc/lsb-release file
|
|
func parseLSBRelease() (*DistroInfo, error) {
|
|
return parseKeyValueFile("/etc/lsb-release")
|
|
}
|
|
|
|
// parseKeyValueFile is a generic parser for key=value format files
|
|
func parseKeyValueFile(filename string) (*DistroInfo, error) {
|
|
file, err := os.Open(filename)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer file.Close()
|
|
|
|
info := &DistroInfo{}
|
|
scanner := bufio.NewScanner(file)
|
|
|
|
for scanner.Scan() {
|
|
line := strings.TrimSpace(scanner.Text())
|
|
if line == "" || strings.HasPrefix(line, "#") {
|
|
continue
|
|
}
|
|
|
|
parts := strings.SplitN(line, "=", 2)
|
|
if len(parts) != 2 {
|
|
continue
|
|
}
|
|
|
|
key := strings.TrimSpace(parts[0])
|
|
value := strings.Trim(strings.TrimSpace(parts[1]), `"'`)
|
|
|
|
switch key {
|
|
case "ID", "DISTRIB_ID":
|
|
info.ID = strings.ToLower(value)
|
|
case "NAME", "DISTRIB_DESCRIPTION":
|
|
info.Name = value
|
|
case "VERSION", "DISTRIB_RELEASE":
|
|
info.Version = value
|
|
case "VERSION_CODENAME", "DISTRIB_CODENAME":
|
|
info.Codename = value
|
|
}
|
|
}
|
|
|
|
if info.ID == "" {
|
|
return nil, fmt.Errorf("could not determine distribution ID")
|
|
}
|
|
|
|
// Determine family
|
|
if family, exists := SupportedDistros[info.ID]; exists {
|
|
info.Family = family
|
|
} else {
|
|
info.Family = "unknown"
|
|
}
|
|
|
|
return info, scanner.Err()
|
|
}
|
|
|
|
// detectFromSpecificFiles tries to detect distro from specific files
|
|
func detectFromSpecificFiles() (*DistroInfo, error) {
|
|
// Map of files to expected distribution IDs
|
|
checks := map[string]string{
|
|
"/etc/arch-release": "arch",
|
|
"/etc/gentoo-release": "gentoo",
|
|
"/etc/slackware-version": "slackware",
|
|
"/etc/alpine-release": "alpine",
|
|
"/etc/void-release": "void",
|
|
}
|
|
|
|
for file, distroID := range checks {
|
|
if _, err := os.Stat(file); err == nil {
|
|
info := &DistroInfo{
|
|
ID: distroID,
|
|
Name: strings.Title(distroID),
|
|
Family: SupportedDistros[distroID],
|
|
}
|
|
|
|
// Try to get version from file content
|
|
if content, err := os.ReadFile(file); err == nil {
|
|
versionRegex := regexp.MustCompile(`\d+\.?\d*\.?\d*`)
|
|
if match := versionRegex.FindString(string(content)); match != "" {
|
|
info.Version = match
|
|
}
|
|
}
|
|
|
|
return info, nil
|
|
}
|
|
}
|
|
|
|
return nil, fmt.Errorf("no recognizable distribution files found")
|
|
}
|
|
|
|
// IsSupported checks if the distribution is supported
|
|
func (d *DistroInfo) IsSupported() bool {
|
|
_, exists := SupportedDistros[d.ID]
|
|
return exists
|
|
}
|
|
|
|
// GetConfigFile returns the path to the main package configuration file
|
|
func (d *DistroInfo) GetConfigFile() string {
|
|
switch d.Family {
|
|
case "debian":
|
|
return "/etc/apt/sources.list"
|
|
case "arch":
|
|
return "/etc/pacman.d/mirrorlist"
|
|
case "fedora":
|
|
return "/etc/yum.repos.d" // Directory for multiple files
|
|
case "opensuse":
|
|
return "/etc/zypp/repos.d" // Directory for multiple files
|
|
case "gentoo":
|
|
return "/etc/portage/make.conf"
|
|
case "alpine":
|
|
return "/etc/apk/repositories"
|
|
case "slackware":
|
|
return "/etc/slackpkg/mirrors"
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
// String provides a human-readable representation
|
|
func (d *DistroInfo) String() string {
|
|
return fmt.Sprintf("%s %s (%s family)", d.Name, d.Version, d.Family)
|
|
}
|