feat: CPU SAT via stress-ng + BMC version via ipmitool

BMC:
- collector/board.go: collectBMCFirmware() via ipmitool mc info, graceful skip if /dev/ipmi0 absent
- collector/collector.go: append BMC firmware record to snap.Firmware
- app/panel.go: show BMC version in TUI right-panel header alongside BIOS

CPU SAT:
- platform/sat.go: RunCPUAcceptancePack(baseDir, durationSec) — lscpu + sensors before/after + stress-ng
- app/app.go: RunCPUAcceptancePack + RunCPUAcceptancePackResult methods, satRunner interface updated
- app/panel.go: CPU row now reads real PASS/FAIL from cpu-*/summary.txt via satStatuses(); cpuDetailResult shows last SAT summary + audit data
- tui/types.go: actionRunCPUSAT, confirmBody for CPU test with mode label
- tui/screen_health_check.go: hcCPUDurations [60,300,900]s; hcRunSingle(CPU)→confirm screen; executeRunAll uses RunCPUAcceptancePackResult
- tui/forms.go: actionRunCPUSAT → RunCPUAcceptancePackResult with mode duration
- cmd/bee/main.go: bee sat cpu [--duration N] subcommand

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Mikhail Chusavitin
2026-03-25 11:06:12 +03:00
parent 1c80906c1f
commit 36dff6e584
12 changed files with 179 additions and 37 deletions

View File

@@ -92,6 +92,13 @@ func (m model) updateConfirm(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
result, err := m.app.RunStorageAcceptancePackResult("")
return resultMsg{title: result.Title, body: result.Body, err: err, back: screenHealthCheck}
}
case actionRunCPUSAT:
m.busyTitle = "CPU test"
durationSec := hcCPUDurations[m.hcMode]
return m, func() tea.Msg {
result, err := m.app.RunCPUAcceptancePackResult("", durationSec)
return resultMsg{title: result.Title, body: result.Body, err: err, back: screenHealthCheck}
}
}
case "ctrl+c":
return m, tea.Quit
@@ -103,7 +110,7 @@ func (m model) confirmCancelTarget() screen {
switch m.pendingAction {
case actionExportBundle:
return screenExportTargets
case actionRunAll, actionRunMemorySAT, actionRunStorageSAT:
case actionRunAll, actionRunMemorySAT, actionRunStorageSAT, actionRunCPUSAT:
return screenHealthCheck
default:
return screenMain

View File

@@ -33,6 +33,9 @@ const (
// hcModeDurations maps mode index (0=Quick,1=Standard,2=Express) to GPU stress seconds.
var hcModeDurations = [3]int{600, 3600, 28800}
// hcCPUDurations maps mode index to CPU stress-ng seconds.
var hcCPUDurations = [3]int{60, 300, 900}
func (m model) enterHealthCheck() (tea.Model, tea.Cmd) {
m.screen = screenHealthCheck
if !m.hcInitialized {
@@ -126,12 +129,10 @@ func (m model) hcRunSingle(idx int) (tea.Model, tea.Cmd) {
m.cursor = 0
return m, nil
case hcCPU:
m.busy = true
m.busyTitle = "CPU"
return m, func() tea.Msg {
r := m.app.ComponentDetailResult("CPU")
return resultMsg{title: r.Title, body: r.Body, back: screenHealthCheck}
}
m.pendingAction = actionRunCPUSAT
m.screen = screenConfirm
m.cursor = 0
return m, nil
}
return m, nil
}
@@ -150,6 +151,7 @@ func (m model) hcRunAll() (tea.Model, tea.Cmd) {
func (m model) executeRunAll() (tea.Model, tea.Cmd) {
durationSec := hcModeDurations[m.hcMode]
durationIdx := m.hcMode
sel := m.hcSel
app := m.app
m.busy = true
@@ -197,8 +199,13 @@ func (m model) executeRunAll() (tea.Model, tea.Cmd) {
parts = append(parts, "=== STORAGE ===\n"+body)
}
if sel[hcCPU] {
r := app.ComponentDetailResult("CPU")
parts = append(parts, "=== CPU ===\n"+r.Body)
cpuDur := hcCPUDurations[durationIdx]
r, err := app.RunCPUAcceptancePackResult("", cpuDur)
body := r.Body
if err != nil {
body += "\nERROR: " + err.Error()
}
parts = append(parts, "=== CPU ===\n"+body)
}
combined := strings.Join(parts, "\n\n")
if combined == "" {

View File

@@ -38,6 +38,7 @@ const (
actionRunAll actionKind = "run_all"
actionRunMemorySAT actionKind = "run_memory_sat"
actionRunStorageSAT actionKind = "run_storage_sat"
actionRunCPUSAT actionKind = "run_cpu_sat"
)
type model struct {
@@ -173,6 +174,9 @@ func (m model) confirmBody() (string, string) {
return "Memory test", "Run memtester?"
case actionRunStorageSAT:
return "Storage test", "Run storage diagnostic pack?"
case actionRunCPUSAT:
modes := []string{"Quick (60s)", "Standard (300s)", "Express (900s)"}
return "CPU test", "Run stress-ng? Mode: " + modes[m.hcMode]
default:
return "Confirm", "Proceed?"
}