Files
drjones 3678b199d0
Some checks failed
Test / test (push) Has been cancelled
Initial commit: AetherForge Linux (forge-mesh) v0.1.0-dev
2026-07-04 09:31:23 +00:00

152 lines
3.9 KiB
Go

package fleet
import (
"encoding/json"
"os"
"os/exec"
"path/filepath"
"strings"
)
// ReconReport holds read-only host capability probes for triple-onion deploy.
type ReconReport struct {
Kernel string `json:"kernel"`
Arch string `json:"arch"`
CgroupsV2 bool `json:"cgroups_v2"`
PodmanAvailable bool `json:"podman_available"`
DockerAvailable bool `json:"docker_available"`
GPU GPUInfo `json:"gpu"`
Virt string `json:"virt,omitempty"`
ContainerRuntime string `json:"container_runtime,omitempty"`
Extra map[string]string `json:"extra,omitempty"`
}
type GPUInfo struct {
Available bool `json:"available"`
Vendor string `json:"vendor,omitempty"`
Devices []string `json:"devices,omitempty"`
}
// RunRecon executes read-only probes: kernel, cgroups, podman, GPU.
func RunRecon() (*ReconReport, error) {
report := &ReconReport{Extra: map[string]string{}}
if out, err := exec.Command("uname", "-r").Output(); err == nil {
report.Kernel = strings.TrimSpace(string(out))
}
if out, err := exec.Command("uname", "-m").Output(); err == nil {
report.Arch = strings.TrimSpace(string(out))
}
report.CgroupsV2 = probeCgroupsV2()
report.PodmanAvailable = commandExists("podman")
report.DockerAvailable = commandExists("docker")
report.GPU = probeGPU()
report.Virt = probeVirt()
report.ContainerRuntime = detectContainerRuntime(report)
return report, nil
}
func probeCgroupsV2() bool {
data, err := os.ReadFile("/sys/fs/cgroup/cgroup.controllers")
if err != nil {
return false
}
return len(strings.TrimSpace(string(data))) > 0
}
func probeGPU() GPUInfo {
info := GPUInfo{}
if commandExists("nvidia-smi") {
if out, err := exec.Command("nvidia-smi", "-L").Output(); err == nil {
lines := strings.Split(strings.TrimSpace(string(out)), "\n")
for _, line := range lines {
if line != "" {
info.Devices = append(info.Devices, line)
}
}
if len(info.Devices) > 0 {
info.Available = true
info.Vendor = "nvidia"
}
}
}
if !info.Available && commandExists("rocm-smi") {
if _, err := exec.Command("rocm-smi", "--showid").Output(); err == nil {
info.Available = true
info.Vendor = "amd"
}
}
return info
}
func probeVirt() string {
data, err := os.ReadFile("/sys/class/dmi/id/product_name")
if err != nil {
return "baremetal"
}
name := strings.ToLower(strings.TrimSpace(string(data)))
switch {
case strings.Contains(name, "vmware"), strings.Contains(name, "virtualbox"),
strings.Contains(name, "kvm"), strings.Contains(name, "qemu"):
return "vm"
default:
return "baremetal"
}
}
func detectContainerRuntime(r *ReconReport) string {
if r.PodmanAvailable {
return "podman"
}
if r.DockerAvailable {
return "docker"
}
return ""
}
func commandExists(name string) bool {
_, err := exec.LookPath(name)
return err == nil
}
// PhenotypeFromRecon builds a stable fingerprint key from recon data.
func PhenotypeFromRecon(r *ReconReport) string {
gpu := "none"
if r.GPU.Available {
gpu = r.GPU.Vendor
}
rt := r.ContainerRuntime
if rt == "" {
rt = "none"
}
parts := []string{r.Arch, r.Virt, gpu, rt}
key := strings.Join(parts, "|")
// Normalize kernel major for grouping
if idx := strings.Index(r.Kernel, "."); idx > 0 {
key = r.Kernel[:idx] + "." + strings.Split(r.Kernel[idx+1:], ".")[0] + "|" + key
}
return key
}
// ReconJSON serializes a recon report for lotl metadata.
func ReconJSON(r *ReconReport) string {
b, _ := json.Marshal(r)
return string(b)
}
// ParseReconReport loads recon from JSON metadata.
func ParseReconReport(raw string) (*ReconReport, error) {
var r ReconReport
if err := json.Unmarshal([]byte(raw), &r); err != nil {
return nil, err
}
return &r, nil
}
// HostReconPath returns optional cached recon file for a host.
func HostReconPath(dataDir, hostID string) string {
return filepath.Join(dataDir, "recon", hostID+".json")
}