Files
josh a0c0fb114f
CI / Lint + build + test (push) Has been cancelled
Add host-mode heartbeat: vetting-agent host + last-seen badge
vetting-agent gains a `host` subcommand that runs as a systemd service
installed by the quick-register one-liner, POSTing every 30s to
/api/v1/hosts/{mac}/heartbeat so the dashboard tile shows "online" or
"Nm ago" without waiting on WoL. Ships dormant client code for the
Phase 2 reboot_for_vetting command so the server can flip it on later
without a binary redeploy.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-17 23:34:15 -04:00

66 lines
1.8 KiB
Go

package hostmode
import (
"bufio"
"context"
"log"
"os"
"os/exec"
"strings"
)
// setPXEBootNext points the next boot at a PXE-capable BootOrder
// entry via efibootmgr --bootnext. Best-effort: absent efibootmgr,
// non-UEFI firmware, or zero PXE entries all fall through silently —
// the operator's BIOS/DHCP chain will still PXE-boot on most hosts.
func setPXEBootNext(ctx context.Context) {
if _, err := os.Stat("/sys/firmware/efi"); err != nil {
log.Printf("hostmode: not a UEFI system; skipping efibootmgr")
return
}
bin, err := exec.LookPath("efibootmgr")
if err != nil {
log.Printf("hostmode: efibootmgr not installed; skipping")
return
}
boots, err := exec.CommandContext(ctx, bin, "-v").Output()
if err != nil {
log.Printf("hostmode: efibootmgr -v: %v", err)
return
}
num := findPXEBootNum(string(boots))
if num == "" {
log.Printf("hostmode: no PXE boot entry found")
return
}
if err := exec.CommandContext(ctx, bin, "--bootnext", num).Run(); err != nil {
log.Printf("hostmode: efibootmgr --bootnext %s: %v", num, err)
return
}
log.Printf("hostmode: efibootmgr --bootnext %s", num)
}
// findPXEBootNum picks the first BootXXXX entry whose description
// looks like a network boot. efibootmgr -v output lines look like:
//
// Boot0003* UEFI: IPv4 Intel I225-V PciRoot(0x0)/Pci(...)/MAC(...)
// Boot0001* ubuntu HD(1,GPT,...)/File(\EFI\ubuntu\shimx64.efi)
func findPXEBootNum(out string) string {
scan := bufio.NewScanner(strings.NewReader(out))
for scan.Scan() {
line := scan.Text()
if !strings.HasPrefix(line, "Boot") || len(line) < 8 {
continue
}
low := strings.ToLower(line)
if !(strings.Contains(low, "pxe") ||
strings.Contains(low, "ipv4") ||
strings.Contains(low, "ipv6") ||
strings.Contains(low, "network")) {
continue
}
return line[4:8]
}
return ""
}