Initial commit: AetherForge Linux (forge-mesh) v0.1.0-dev
Some checks failed
Test / test (push) Has been cancelled
Some checks failed
Test / test (push) Has been cancelled
This commit is contained in:
151
internal/fleet/recon.go
Normal file
151
internal/fleet/recon.go
Normal file
@@ -0,0 +1,151 @@
|
||||
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")
|
||||
}
|
||||
Reference in New Issue
Block a user