package sentinel import ( "os" "os/exec" "strings" ) // Snapshot captures host-level facts. type Snapshot struct { Hostname string `json:"hostname,omitempty"` Kernel string `json:"kernel,omitempty"` OSImage string `json:"os_image,omitempty"` K3sVersion string `json:"k3s_version,omitempty"` Containerd string `json:"containerd,omitempty"` PackageSample map[string]string `json:"package_sample,omitempty"` // small subset to detect drift DropInsSample map[string]string `json:"dropins_sample,omitempty"` // path->content hash/sample Notes string `json:"notes,omitempty"` } // Collect gathers a minimal set of facts; intended to run inside a DaemonSet pod with host mounts. func Collect() *Snapshot { return &Snapshot{ Hostname: runAndTrim("hostname"), Kernel: runAndTrim("uname", "-r"), OSImage: osRelease(), K3sVersion: runAndTrim("k3s", "version"), Containerd: runAndTrim("containerd", "--version"), PackageSample: pkgSample(), } } func runAndTrim(cmd string, args ...string) string { out, err := commandOutput(cmd, args...) if err != nil { return "" } return strings.TrimSpace(string(out)) } func osRelease() string { out, err := commandOutput("cat", "/etc/os-release") if err != nil { return "" } for _, line := range strings.Split(string(out), "\n") { if strings.HasPrefix(line, "PRETTY_NAME=") { return strings.Trim(line[len("PRETTY_NAME="):], "\"") } } return "" } // pkgSample grabs a tiny subset of package versions to detect drift without collecting everything. func pkgSample() map[string]string { names := []string{"containerd", "k3s", "nvidia-container-toolkit", "linux-image-raspi"} result := map[string]string{} for _, n := range names { v := pkgVersion(n) if v != "" { result[n] = v } } return result } func pkgVersion(name string) string { // Try dpkg-query first. out, err := commandOutput("dpkg-query", "-W", "-f", "${Version}", name) if err == nil && len(out) > 0 { return strings.TrimSpace(string(out)) } // Fallback rpm. out, err = commandOutput("rpm", "-q", "--qf", "%{VERSION}-%{RELEASE}", name) if err == nil && len(out) > 0 { return strings.TrimSpace(string(out)) } return "" } func commandOutput(cmd string, args ...string) ([]byte, error) { if os.Getenv("METIS_SENTINEL_NSENTER") == "1" { nsenterArgs := []string{"-t", "1", "-m", "-u", "-n", "-i", "-p", "--", cmd} nsenterArgs = append(nsenterArgs, args...) return exec.Command("nsenter", nsenterArgs...).Output() } return exec.Command(cmd, args...).Output() }