f66472fd37
Extends orca doctor with two new checks (REQ-052): - doctor os: re-runs OS detection from /etc/os-release, compares to stored localhost node's os field. Drift = WARN (re-run orca init); match = PASS; missing localhost node = FAIL. - doctor proxmox: iterates kind=proxmox nodes, SSH-probes each with `pveversion` (3s timeout per node, clones Network() pattern). Zero proxmox nodes = WARN; reachable = PASS; unreachable = FAIL. Changes: - internal/osdetect: new shared package (Detect + ParseID) extracted from internal/cli to avoid import cycle (cli + doctor both need it) - internal/cli/osdetect.go: thin wrapper delegating to osdetect package - internal/doctor/doctor.go: OS() and Proxmox() checks; All() extended; probeProxmoxPVEVersion uses orca SSH key + knownhosts TOFU - internal/cli/doctor.go: doctor os + doctor proxmox subcommands (--json) - internal/doctor/doctor_test.go: 5 new tests (OS match/drift/missing, proxmox no-nodes/unreachable) E2E: orca init -> orca doctor shows 6 PASS / 1 WARN (proxmox=none) / 1 FAIL (network=daemon not running). doctor os --json valid. ---ci--- project: orca phase: 3 milestone: v0.6 status: execute ---/ci---
64 lines
1.9 KiB
Go
64 lines
1.9 KiB
Go
// Package osdetect provides OS detection from /etc/os-release (D-032).
|
|
// It's a separate package to avoid import cycles between internal/cli
|
|
// and internal/doctor (both need to detect the local OS).
|
|
package osdetect
|
|
|
|
import (
|
|
"bufio"
|
|
"os"
|
|
"strings"
|
|
)
|
|
|
|
// osReleasePaths are checked in order for the os-release file. The
|
|
// freedesktop.org spec says /etc/os-release is the canonical path,
|
|
// with /usr/lib/os-release as a fallback for minimal containers that
|
|
// may not symlink the former.
|
|
var osReleasePaths = []string{"/etc/os-release", "/usr/lib/os-release"}
|
|
|
|
// Detect reads /etc/os-release (then /usr/lib/os-release as a
|
|
// fallback) and returns the value of the ID= field. Returns "linux"
|
|
// (the generic fallback per D-032) if the file is missing, the ID
|
|
// field is absent, or the value is empty. Unknown ID values (e.g.
|
|
// "fedora", "arch") are returned verbatim — doctor os can warn on
|
|
// unknown values, but orca init must not fail.
|
|
func Detect() string {
|
|
for _, p := range osReleasePaths {
|
|
data, err := os.ReadFile(p)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
if id := ParseID(data); id != "" {
|
|
return id
|
|
}
|
|
}
|
|
return "linux"
|
|
}
|
|
|
|
// ParseID extracts the ID= value from os-release content.
|
|
// The format is shell-compatible KEY=VALUE lines; values may be
|
|
// double-quoted. Returns "" if ID is absent or empty.
|
|
func ParseID(data []byte) string {
|
|
scanner := bufio.NewScanner(strings.NewReader(string(data)))
|
|
for scanner.Scan() {
|
|
line := strings.TrimSpace(scanner.Text())
|
|
if line == "" || strings.HasPrefix(line, "#") {
|
|
continue
|
|
}
|
|
key, value, ok := strings.Cut(line, "=")
|
|
if !ok {
|
|
continue
|
|
}
|
|
key = strings.TrimSpace(key)
|
|
if key != "ID" {
|
|
continue
|
|
}
|
|
value = strings.TrimSpace(value)
|
|
// Strip surrounding double quotes (freedesktop spec allows quoted values).
|
|
if len(value) >= 2 && value[0] == '"' && value[len(value)-1] == '"' {
|
|
value = value[1 : len(value)-1]
|
|
}
|
|
return value
|
|
}
|
|
return ""
|
|
}
|