Add host-mode heartbeat: vetting-agent host + last-seen badge
CI / Lint + build + test (push) Has been cancelled
CI / Lint + build + test (push) Has been cancelled
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>
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
package hostmode
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// primaryMAC resolves the MAC of the iface that carries the default
|
||||
// IPv4 route. Mirrors quick.sh.tmpl's primary_iface so the agent
|
||||
// reports the same MAC that was registered (important on Proxmox
|
||||
// where vmbr0 inherits its physical NIC's MAC).
|
||||
func primaryMAC() (string, error) {
|
||||
iface, err := defaultRouteIface()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
raw, err := os.ReadFile(fmt.Sprintf("/sys/class/net/%s/address", iface))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("read mac for %s: %w", iface, err)
|
||||
}
|
||||
return strings.ToLower(strings.TrimSpace(string(raw))), nil
|
||||
}
|
||||
|
||||
// defaultRouteIface shells out to `ip` because reading /proc/net/route
|
||||
// requires hex-swap logic and still misses the IPv4-only "dev"
|
||||
// qualification. The service runs as root on a Linux box; `ip` is
|
||||
// always present.
|
||||
func defaultRouteIface() (string, error) {
|
||||
out, err := exec.Command("ip", "-o", "-4", "route", "show", "default").Output()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("ip route: %w", err)
|
||||
}
|
||||
scan := bufio.NewScanner(strings.NewReader(string(out)))
|
||||
for scan.Scan() {
|
||||
fields := strings.Fields(scan.Text())
|
||||
for i, f := range fields {
|
||||
if f == "dev" && i+1 < len(fields) {
|
||||
return fields[i+1], nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return "", errors.New("no default IPv4 route")
|
||||
}
|
||||
Reference in New Issue
Block a user