Initial commit: full Phases 1-6 implementation
CI / Lint + build + test (push) Has been cancelled

Post-repair hardware validation pipeline for Proxmox cluster hosts.
Go orchestrator + in-image agent + mkosi live image + bundled dnsmasq
PXE + SQLite + HTMX/SSE UI + notify registry + janitor + full docs.
This commit is contained in:
2026-04-17 21:32:10 -04:00
commit 9bb4b09a04
98 changed files with 11960 additions and 0 deletions
+153
View File
@@ -0,0 +1,153 @@
package tests
import (
"context"
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
)
// PSU walks /sys/class/hwmon for in*_input (mV) and in*_label to find
// PSU rails. In home-lab hosts the kernel surfaces a handful of named
// rails (12V, 5V, 3V3). No rails → auto-skip. Any rail outside a ±10%
// window of its nominal value → fail.
func PSU(ctx context.Context, d Deps) Outcome {
rails := scanPSURails()
if len(rails) == 0 {
d.Info("PSU: no voltage rails found under /sys/class/hwmon — skipping stage")
return Outcome{
Passed: true,
Summary: "skipped (no PSU sensors)",
Extras: map[string]any{"skipped": true, "reason": "no_hwmon_voltages"},
}
}
var samples []Sample
problems := []string{}
for _, rail := range rails {
samples = append(samples, Sample{Kind: "psu_volt", Key: rail.Label, Value: rail.Volts, Unit: "V"})
if ok, why := voltageInRange(rail); !ok {
problems = append(problems, fmt.Sprintf("%s=%.2fV (%s)", rail.Label, rail.Volts, why))
}
}
if d.Sensor != nil {
_ = d.Sensor(ctx, samples)
}
extras := map[string]any{
"rails": rails,
"problems": problems,
}
if len(problems) > 0 {
d.Error("PSU: out-of-range rails: " + strings.Join(problems, ", "))
return Outcome{
Passed: false,
Message: "PSU rails out of range: " + strings.Join(problems, ", "),
Summary: fmt.Sprintf("%d rails, %d failing", len(rails), len(problems)),
Extras: extras,
}
}
d.Info(fmt.Sprintf("PSU: %d rails within ±10%% nominal", len(rails)))
return Outcome{
Passed: true,
Summary: fmt.Sprintf("%d rails nominal", len(rails)),
Extras: extras,
}
}
type psuRail struct {
Label string `json:"label"`
Volts float64 `json:"volts"`
}
// scanPSURails walks every hwmon chip looking for in*_input files with
// an accompanying in*_label that mentions a known rail name. Unknown
// labels are skipped rather than flagged — motherboard VRMs report many
// rails that aren't PSU outputs.
func scanPSURails() []psuRail {
root := "/sys/class/hwmon"
chips, err := os.ReadDir(root)
if err != nil {
return nil
}
var out []psuRail
for _, c := range chips {
base := filepath.Join(root, c.Name())
files, err := os.ReadDir(base)
if err != nil {
continue
}
for _, f := range files {
name := f.Name()
if !strings.HasPrefix(name, "in") || !strings.HasSuffix(name, "_input") {
continue
}
n := strings.TrimSuffix(strings.TrimPrefix(name, "in"), "_input")
labelPath := filepath.Join(base, "in"+n+"_label")
label := strings.TrimSpace(readFileStr(labelPath))
if !isPSULabel(label) {
continue
}
raw := strings.TrimSpace(readFileStr(filepath.Join(base, name)))
mv, err := strconv.Atoi(raw)
if err != nil {
continue
}
out = append(out, psuRail{Label: label, Volts: float64(mv) / 1000})
}
}
return out
}
// isPSULabel filters labels that look like PSU rails. Keeps a small
// allowlist to avoid flagging CPU VRM rails as PSU failures.
func isPSULabel(label string) bool {
l := strings.ToLower(label)
switch {
case strings.Contains(l, "12v"), strings.Contains(l, "5v"),
strings.Contains(l, "3.3v"), strings.Contains(l, "3v3"),
strings.Contains(l, "vccin"):
return true
}
return false
}
// voltageInRange returns (ok, reason). A label like "12V" has a 12.0V
// nominal; we accept ±10%. Unknown labels pass.
func voltageInRange(r psuRail) (bool, string) {
nom := nominalFor(r.Label)
if nom == 0 {
return true, ""
}
delta := r.Volts - nom
if delta < 0 {
delta = -delta
}
if delta/nom > 0.10 {
return false, fmt.Sprintf("expected ~%.1fV", nom)
}
return true, ""
}
func nominalFor(label string) float64 {
l := strings.ToLower(label)
switch {
case strings.Contains(l, "12v"):
return 12.0
case strings.Contains(l, "5v"):
return 5.0
case strings.Contains(l, "3.3v"), strings.Contains(l, "3v3"):
return 3.3
}
return 0
}
func readFileStr(p string) string {
b, err := os.ReadFile(p)
if err != nil {
return ""
}
return string(b)
}