Files
bee/audit/internal/collector/board.go
Mikhail Chusavitin 36dff6e584 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>
2026-03-25 11:06:12 +03:00

202 lines
5.0 KiB
Go

package collector
import (
"bee/audit/internal/schema"
"bufio"
"log/slog"
"os"
"os/exec"
"strings"
)
var execDmidecode = func(typeNum string) (string, error) {
out, err := exec.Command("dmidecode", "-t", typeNum).Output()
if err != nil {
return "", err
}
return string(out), nil
}
var execIpmitool = func(args ...string) (string, error) {
out, err := exec.Command("ipmitool", args...).Output()
if err != nil {
return "", err
}
return string(out), nil
}
// collectBoard runs dmidecode for types 0, 1, 2 and returns the board record
// plus the BIOS firmware entry. Any failure is logged and returns zero values.
func collectBoard() (schema.HardwareBoard, []schema.HardwareFirmwareRecord) {
type1, err := runDmidecode("1")
if err != nil {
slog.Warn("board: dmidecode type 1 failed", "err", err)
return schema.HardwareBoard{}, nil
}
type2, err := runDmidecode("2")
if err != nil {
slog.Warn("board: dmidecode type 2 failed", "err", err)
}
type0, err := runDmidecode("0")
if err != nil {
slog.Warn("board: dmidecode type 0 failed", "err", err)
}
board := parseBoard(type1, type2)
firmware := parseBIOSFirmware(type0)
slog.Info("board: collected", "serial", board.SerialNumber)
return board, firmware
}
// parseBoard extracts HardwareBoard from dmidecode type 1 (System) and type 2 (Baseboard) output.
func parseBoard(type1, type2 string) schema.HardwareBoard {
sys := parseDMIFields(type1, "System Information")
base := parseDMIFields(type2, "Base Board Information")
board := schema.HardwareBoard{}
if v := cleanDMIValue(sys["Manufacturer"]); v != "" {
board.Manufacturer = &v
}
if v := cleanDMIValue(sys["Product Name"]); v != "" {
board.ProductName = &v
}
if v := cleanDMIValue(sys["Serial Number"]); v != "" {
board.SerialNumber = v
}
if v := cleanDMIValue(sys["UUID"]); v != "" {
board.UUID = &v
}
// part number comes from baseboard Product Name
if v := cleanDMIValue(base["Product Name"]); v != "" {
board.PartNumber = &v
}
return board
}
// collectBMCFirmware collects BMC firmware version via ipmitool mc info.
// Returns nil if ipmitool is missing, /dev/ipmi0 is absent, or any error occurs.
func collectBMCFirmware() []schema.HardwareFirmwareRecord {
if _, err := exec.LookPath("ipmitool"); err != nil {
return nil
}
if _, err := os.Stat("/dev/ipmi0"); err != nil {
return nil
}
out, err := execIpmitool("mc", "info")
if err != nil {
slog.Info("bmc: ipmitool mc info unavailable", "err", err)
return nil
}
version := parseBMCFirmwareRevision(out)
if version == "" {
return nil
}
slog.Info("bmc: collected", "version", version)
return []schema.HardwareFirmwareRecord{
{DeviceName: "BMC", Version: version},
}
}
// parseBMCFirmwareRevision extracts the "Firmware Revision" field from ipmitool mc info output.
func parseBMCFirmwareRevision(out string) string {
for _, line := range strings.Split(out, "\n") {
line = strings.TrimSpace(line)
key, val, ok := strings.Cut(line, ":")
if !ok {
continue
}
if strings.TrimSpace(key) == "Firmware Revision" {
return strings.TrimSpace(val)
}
}
return ""
}
// parseBIOSFirmware extracts BIOS version from dmidecode type 0 output.
func parseBIOSFirmware(type0 string) []schema.HardwareFirmwareRecord {
fields := parseDMIFields(type0, "BIOS Information")
version := cleanDMIValue(fields["Version"])
if version == "" {
return nil
}
return []schema.HardwareFirmwareRecord{
{DeviceName: "BIOS", Version: version},
}
}
// parseDMIFields parses the key-value pairs from a dmidecode section.
// sectionTitle is the section header line to find (e.g. "System Information").
// Returns a map of trimmed field names to raw values.
func parseDMIFields(output, sectionTitle string) map[string]string {
fields := make(map[string]string)
inSection := false
scanner := bufio.NewScanner(strings.NewReader(output))
for scanner.Scan() {
line := scanner.Text()
if strings.TrimSpace(line) == sectionTitle {
inSection = true
continue
}
if inSection {
// blank line or new Handle line = end of section
if line == "" || strings.HasPrefix(line, "Handle ") {
break
}
// skip sub-list items (double tab indent)
if strings.HasPrefix(line, "\t\t") {
continue
}
// key: value line (single tab indent)
trimmed := strings.TrimPrefix(line, "\t")
if idx := strings.Index(trimmed, ": "); idx >= 0 {
key := trimmed[:idx]
val := trimmed[idx+2:]
fields[key] = val
}
}
}
return fields
}
// cleanDMIValue returns empty string for known placeholder values that vendors
// use when a field is unpopulated.
func cleanDMIValue(v string) string {
v = strings.TrimSpace(v)
if v == "" {
return ""
}
upper := strings.ToUpper(v)
placeholders := []string{
"TO BE FILLED BY O.E.M.",
"NOT SPECIFIED",
"NOT SETTABLE",
"NOT PRESENT",
"UNKNOWN",
"N/A",
"NONE",
"NULL",
"DEFAULT STRING",
"0",
}
for _, p := range placeholders {
if upper == p {
return ""
}
}
return v
}
// runDmidecode executes dmidecode -t <typeNum> and returns its stdout.
func runDmidecode(typeNum string) (string, error) {
return execDmidecode(typeNum)
}