From 3b666615d72b21506721f78aa744b35d86b2a106 Mon Sep 17 00:00:00 2001 From: MDietermann Date: Mon, 13 Apr 2026 19:59:04 +0200 Subject: [PATCH] feat(platform): detect distro, WSL, and architecture --- internal/platform/platform.go | 63 ++++++++++++++++++++++++++++++ internal/platform/platform_test.go | 45 +++++++++++++++++++++ 2 files changed, 108 insertions(+) create mode 100644 internal/platform/platform.go create mode 100644 internal/platform/platform_test.go diff --git a/internal/platform/platform.go b/internal/platform/platform.go new file mode 100644 index 0000000..fdbf6df --- /dev/null +++ b/internal/platform/platform.go @@ -0,0 +1,63 @@ +// Package platform detects the host OS/distro/WSL status. +package platform + +import ( + "bufio" + "fmt" + "io" + "os" + "runtime" + "strings" +) + +// Distro is a narrow enum of distros we care about. +type Distro string + +const ( + DistroArch Distro = "arch" + DistroUnknown Distro = "unknown" +) + +// Platform holds detected host information. +type Platform struct { + Distro Distro + IsWSL bool + Arch string // e.g. "amd64", "arm64" +} + +// Detect reads the live system. Returns DistroUnknown rather than an error +// when /etc/os-release is missing or unrecognized; callers decide what to do. +func Detect() (Platform, error) { + f, err := os.Open("/etc/os-release") + distro := DistroUnknown + if err == nil { + distro = parseDistro(f) + _ = f.Close() + } else if !os.IsNotExist(err) { + return Platform{}, fmt.Errorf("open /etc/os-release: %w", err) + } + + wsl := false + if data, err := os.ReadFile("/proc/sys/kernel/osrelease"); err == nil { + lower := strings.ToLower(string(data)) + wsl = strings.Contains(lower, "microsoft") || strings.Contains(lower, "wsl") + } + + return Platform{Distro: distro, IsWSL: wsl, Arch: runtime.GOARCH}, nil +} + +func parseDistro(r io.Reader) Distro { + sc := bufio.NewScanner(r) + for sc.Scan() { + line := sc.Text() + if !strings.HasPrefix(line, "ID=") { + continue + } + id := strings.Trim(strings.TrimPrefix(line, "ID="), `"`) + if id == "arch" { + return DistroArch + } + return DistroUnknown + } + return DistroUnknown +} diff --git a/internal/platform/platform_test.go b/internal/platform/platform_test.go new file mode 100644 index 0000000..ae79045 --- /dev/null +++ b/internal/platform/platform_test.go @@ -0,0 +1,45 @@ +package platform + +import ( + "strings" + "testing" +) + +func TestParseDistroArch(t *testing.T) { + osRelease := `NAME="Arch Linux" +ID=arch +ID_LIKE=archlinux +` + if got := parseDistro(strings.NewReader(osRelease)); got != DistroArch { + t.Fatalf("got %v", got) + } +} + +func TestParseDistroUbuntu(t *testing.T) { + osRelease := `NAME="Ubuntu" +ID=ubuntu +` + if got := parseDistro(strings.NewReader(osRelease)); got != DistroUnknown { + t.Fatalf("got %v", got) + } +} + +func TestParseDistroQuoted(t *testing.T) { + osRelease := `ID="arch" +` + if got := parseDistro(strings.NewReader(osRelease)); got != DistroArch { + t.Fatalf("got %v", got) + } +} + +func TestDetectReturnsArch(t *testing.T) { + p, err := Detect() + if err != nil { + t.Fatal(err) + } + if p.Arch == "" { + t.Fatal("arch is empty") + } + // We don't assert Distro here because the test host may not be Arch. + // The parse-level tests above cover that logic. +}