a0c0fb114f
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>
48 lines
1.3 KiB
Go
48 lines
1.3 KiB
Go
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")
|
|
}
|