feat(firmware): install probe tools in live image + surface nic/hba gaps
mkosi.conf: add ipmitool, ethtool, nvme-cli so the Firmware stage can actually read BMC revisions, NIC firmware versions, and fall back to nvme-cli when sysfs firmware_rev is missing. firmware.go: probeNICFirmware and probeHBAFirmware now return (snapshots, warning) so a missing ethtool/lspci surfaces in the stage log the same way probeBIOS/probeBMC already do. Before, a host without ethtool silently reported "bios=1 nvme_fw=1 microcode=1" with no hint that nic coverage was dropped. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+18
-10
@@ -47,9 +47,17 @@ func Firmware(ctx context.Context) ([]FirmwareSnapshot, []string) {
|
|||||||
} else if warn != "" {
|
} else if warn != "" {
|
||||||
warnings = append(warnings, warn)
|
warnings = append(warnings, warn)
|
||||||
}
|
}
|
||||||
out = append(out, probeNICFirmware(ctx)...)
|
nicSnaps, nicWarn := probeNICFirmware(ctx)
|
||||||
|
out = append(out, nicSnaps...)
|
||||||
|
if nicWarn != "" {
|
||||||
|
warnings = append(warnings, nicWarn)
|
||||||
|
}
|
||||||
out = append(out, probeNVMeFirmware(ctx)...)
|
out = append(out, probeNVMeFirmware(ctx)...)
|
||||||
out = append(out, probeHBAFirmware(ctx)...)
|
hbaSnaps, hbaWarn := probeHBAFirmware(ctx)
|
||||||
|
out = append(out, hbaSnaps...)
|
||||||
|
if hbaWarn != "" {
|
||||||
|
warnings = append(warnings, hbaWarn)
|
||||||
|
}
|
||||||
if snap := probeMicrocode(); snap != nil {
|
if snap := probeMicrocode(); snap != nil {
|
||||||
out = append(out, *snap)
|
out = append(out, *snap)
|
||||||
}
|
}
|
||||||
@@ -214,13 +222,13 @@ func parseIpmitoolMCInfo(r io.Reader) *FirmwareSnapshot {
|
|||||||
// `ethtool -i <iface>` on each real NIC (skip lo, bridges, virtuals).
|
// `ethtool -i <iface>` on each real NIC (skip lo, bridges, virtuals).
|
||||||
// One snapshot per interface so a mismatched port lights up in the diff
|
// One snapshot per interface so a mismatched port lights up in the diff
|
||||||
// without silencing sibling ports.
|
// without silencing sibling ports.
|
||||||
func probeNICFirmware(ctx context.Context) []FirmwareSnapshot {
|
func probeNICFirmware(ctx context.Context) ([]FirmwareSnapshot, string) {
|
||||||
if _, err := exec.LookPath("ethtool"); err != nil {
|
if _, err := exec.LookPath("ethtool"); err != nil {
|
||||||
return nil
|
return nil, "nic: ethtool not installed"
|
||||||
}
|
}
|
||||||
ifaces, err := os.ReadDir("/sys/class/net")
|
ifaces, err := os.ReadDir("/sys/class/net")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil, ""
|
||||||
}
|
}
|
||||||
var out []FirmwareSnapshot
|
var out []FirmwareSnapshot
|
||||||
for _, entry := range ifaces {
|
for _, entry := range ifaces {
|
||||||
@@ -237,7 +245,7 @@ func probeNICFirmware(ctx context.Context) []FirmwareSnapshot {
|
|||||||
out = append(out, *snap)
|
out = append(out, *snap)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return out
|
return out, ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseEthtoolI extracts driver/firmware-version from `ethtool -i`
|
// parseEthtoolI extracts driver/firmware-version from `ethtool -i`
|
||||||
@@ -353,15 +361,15 @@ var lspciClassHBA = regexp.MustCompile(`(?i)(serial attached scsi|sas controller
|
|||||||
// "revision" on the device line. We capture what's printed and rely on
|
// "revision" on the device line. We capture what's printed and rely on
|
||||||
// SpecValidate to diff — this keeps us off tool-specific CLIs (storcli,
|
// SpecValidate to diff — this keeps us off tool-specific CLIs (storcli,
|
||||||
// mpt-status) that aren't always installed.
|
// mpt-status) that aren't always installed.
|
||||||
func probeHBAFirmware(ctx context.Context) []FirmwareSnapshot {
|
func probeHBAFirmware(ctx context.Context) ([]FirmwareSnapshot, string) {
|
||||||
if _, err := exec.LookPath("lspci"); err != nil {
|
if _, err := exec.LookPath("lspci"); err != nil {
|
||||||
return nil
|
return nil, "hba: lspci not installed"
|
||||||
}
|
}
|
||||||
out, err := runCmd(ctx, "lspci", "-Dvvnn")
|
out, err := runCmd(ctx, "lspci", "-Dvvnn")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil, fmt.Sprintf("hba: lspci failed: %v", trimErr(err, out))
|
||||||
}
|
}
|
||||||
return parseLspciHBA(strings.NewReader(out))
|
return parseLspciHBA(strings.NewReader(out)), ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseLspciHBA walks `lspci -Dvvnn` stanzas and picks SAS/RAID
|
// parseLspciHBA walks `lspci -Dvvnn` stanzas and picks SAS/RAID
|
||||||
|
|||||||
@@ -57,6 +57,12 @@ Packages=
|
|||||||
lm-sensors
|
lm-sensors
|
||||||
e2fsprogs
|
e2fsprogs
|
||||||
util-linux
|
util-linux
|
||||||
|
# Firmware probe tooling. Without these, the Firmware stage silently
|
||||||
|
# skips whole components (ethtool → nic, nvme-cli → nvme fallback) or
|
||||||
|
# emits a cosmetic "not installed" warning (ipmitool → bmc).
|
||||||
|
ipmitool
|
||||||
|
ethtool
|
||||||
|
nvme-cli
|
||||||
# Firmware. firmware-linux-nonfree on bookworm is a thin metapackage
|
# Firmware. firmware-linux-nonfree on bookworm is a thin metapackage
|
||||||
# that does NOT pull i915 GuC/HuC — those live in firmware-misc-nonfree.
|
# that does NOT pull i915 GuC/HuC — those live in firmware-misc-nonfree.
|
||||||
# Enumerate explicitly so the blob for whatever hardware we boot on
|
# Enumerate explicitly so the blob for whatever hardware we boot on
|
||||||
|
|||||||
Reference in New Issue
Block a user