done
This commit is contained in:
191
internal/distro/detect.go
Normal file
191
internal/distro/detect.go
Normal file
@@ -0,0 +1,191 @@
|
||||
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)
|
||||
}
|
Reference in New Issue
Block a user