6.3 KiB
AUDIT — a managed host's addresses are visible in the hub (2026-07-31)
Artifacts: felhom-agent v0.119.0 + hub v0.85.0. Method: endpoint-level on live
hardware (no browser on DooPlex), plus direct ip addr reads on both boxes as the ground truth the
rendered page is checked against.
1. The finding that started it
The operator asked to see the LAN and WireGuard IPs on the hosts page, "because it looks like it is not visible anywhere". That was very nearly exact:
- LAN IP — not visible, and not reportable.
HostReport/HostMetricscarried node, cpu, memory, disk, loadavg, uptime, cpu temp and the wrapper sha — no address of any kind. The hub could not render what it was never sent. - WireGuard IP — visible in one place, in the wrong direction.
/offsiterenders a peer table with an Assigned IP column and aHostlink, so an operator could go peer→host but never host→peer, which is the direction the question is actually asked in.GetWGPeerForHostalready existed with no UI consumer.
2. Two plausible LAN-IP sources were rejected BEFORE any code, on measurement
| Candidate | Why it fails |
|---|---|
lan_resolver.host_ip (agent config) |
OPTIONAL — set only when the split-horizon DNS feature is configured. Absent on a box that never enabled it. |
DeriveHostIP(local_api.listen_addr) |
Returns 169.254.253.1 — since the R-50 island migration the local API binds a link-local address that is byte-identical on every box. |
Both would have produced a confident wrong answer, which is worse than the blank they replace. The agent now reads the kernel's own interface table instead.
3. Ground truth — what the boxes actually hold
ip -o addr show, 2026-07-31:
demo-felhom demo-hp
lo 127.0.0.1/8 + ::1/128 (both)
tailscale0 100.70.170.35/32 + ULA + fe80 100.76.96.79/32 + ULA + fe80
vmbr0 192.168.0.162/24 + fe80 192.168.0.87/24 + fe80
vmbr9 169.254.253.1/30 + fe80 169.254.253.1/30 + fe80 <- IDENTICAL on both
wg-felhom 10.77.0.2/32 10.77.0.3/32
(no address at all: enp*/wlp*/wlo1, veth9201i0, veth9201i1)
The measurement decided the design. IsGlobalUnicast() alone drops loopback, fe80::/10 and
169.254/16 — that last one being the island literal above. And no veth/fwbr/tap denylist is
needed, because on a Proxmox host that per-guest plumbing carries no IP at all and self-excludes.
The unit-test fixtures are these tables transcribed verbatim, including the address-less rows,
because the no-denylist claim rests on those rows really being empty.
4. The wire, from the agent that will ship
felhom-agent --selftest=hub on demo-felhom running 0.119.0 — "the report it would send":
"addresses": [
{ "iface": "tailscale0", "cidr": "100.70.170.35/32" },
{ "iface": "tailscale0", "cidr": "fd7a:115c:a1e0::5236:aa24/128" },
{ "iface": "vmbr0", "cidr": "192.168.0.162/24" },
{ "iface": "wg-felhom", "cidr": "10.77.0.2/32" }
]
Correctly present: the LAN address. Correctly absent: lo, vmbr9's island address, every fe80::.
5. Rendered, live, on every enrolled host
Read off GET /hosts/<id> on the deployed hub 0.85.0:
| Host | Agent | Network card |
|---|---|---|
demo-felhom-8363b5 |
0.119.0 | WireGuard 10.77.0.2 confirmed; vmbr0 192.168.0.162/24, tailscale0 100.70.170.35/32 + ULA |
demo-hp-bb76ea |
0.119.0 | WireGuard 10.77.0.3 confirmed; vmbr0 192.168.0.87/24, tailscale0 100.76.96.79/32 + ULA |
drill-r50-0a4f9a |
0.113.0 | WireGuard 10.77.0.4 allocated; "This host's agent (0.113.0) does not report its addresses — they are unknown, not absent. Needs agent 0.119.0 or newer." |
sess-f-2670b5 |
old | same UNKNOWN rendering |
Both 0.119.0 rows match the ip addr ground truth in §3 exactly.
6. The two design points that carry the risk
WireGuard is rendered as TWO facts. WGAssignedIP is the hub's allocation (wg_peers, desired
state); WGConfirmed is whether the box reports holding it. Rendering the allocation alone would
make a peer that was never applied look healthy — the presence-is-not-result trap, in the shape
this project has already been bitten by twice (F-CRIT-2, R-100). A mismatch renders not confirmed by the box, and a red-proof pins it: hard-wiring WGConfirmed = true turns that test red.
The split is keyed on the allocation, never on the interface name wg-felhom — that is a systemd
unit name, and a UI keyed on it would silently mis-render the day it changes.
An old agent renders UNKNOWN, never "no addresses." Below 0.119.0 the field is absent from the
wire. An absent signal and a negative result are different facts, and conflating them would have the
page state something false about a live host. Proven live on drill-r50-0a4f9a (§5) and red-proofed:
deleting the branch makes the page claim the host has no routable address.
7. Release hygiene (R-115's leg, walked deliberately)
Deployed and installable, in the same session — the two states R-115 says drift silently:
- built v0.119.0, deployed to both demo boxes (
systemctl is-active→active,--version→ 0.119.0) - published to Gitea (
scripts/publish-agent.sh), shae37aca824d0f57bb…, round-trip GET verified - vouched in the Day-0 manifest and read back:
agent=0.119.0, with golden0.188.0,min_agent 0.113.0andwrapper_sha256preserved verbatim min_agentdeliberately NOT raised: the field has no controller coupling, so holding the controller floor for it would be an unnecessary fleet-wide write
A trap hit on the way, worth recording: the Gitea package list sorted lexicographically put
0.96.0 above 0.118.1 — the exact shape R-120 recorded for SQL MAX(). Re-sorted by semver before
choosing the version.
8. Not covered
- A box with two LAN bridges. The design handles it (one row per interface) and a unit test covers it, but no such box exists to observe.
- IPv4-only or IPv6-only hosts — every box here is dual-stacked via tailscale's ULA.
- A real WireGuard drift — the
not confirmedbranch is unit-tested and red-proofed, never seen in production, because no live peer is currently unapplied. - The card is operator-facing only; no customer surface changed.