Files
felhom-agent/REPORT.md
T
2026-06-11 18:30:44 +02:00

4.7 KiB

REPORT — agent v0.21.0: agent-managed split-horizon LAN resolver (2026-06-11)

Repo: felhom-agent · Version: 0.21.0 · Pushed commit: a43e981 · paired with felhom-controller v0.42.1 (real wildcard cert — the GATE this depends on).

What shipped — internal/lanresolver

LAN clients reach their guest directly at the same public hostname with the same real wildcard cert (no Cloudflare hairpin), via a host-side dnsmasq the agent manages. The host is the stable anchor (static LAN IP); the guest stays DHCP/ephemeral and the agent tracks its live IP.

  • Renderer — a base drop-in (/etc/dnsmasq.d/felhom-resolver-base.conf: bind-interfaces, listen-address=<host-LAN-IP> + 127.0.0.1, no-resolv, server=<upstreams>) + a per-customer drop-in (felhom-<customer-id>.conf: local=/<domain>/ + address=/<domain>/<guest-ip>). The proven two-line shape: local= makes dnsmasq authoritative for the zone so AAAA → NODATA (no Cloudflare-AAAA split-brain — the guest has only link-local v6); address= is the wildcard A; everything else (and its AAAA) forwards upstream unchanged.
  • ManagerEnsureDnsmasq (apt-install if absent, base config, systemctl enable --now), ReconcileGuest (discover live IPv4 via pct exec <vmid> -- ip -4 -o addr show dev eth0; discover domain from the guest controller's pulled controller.yaml — the v2 bootstrap omits it; write-if- changed; systemctl reload), Remove (decommission). Tolerates the early-boot pre-lease window (empty IP → skip+retry, never a blank record); logs IP transitions. Never touches /etc/resolv.conf.
  • Loop — a 7th daemon goroutine; every interval (default 300s) enumerates provisioned guests (/var/lib/felhom-agent/guests/<vmid>/) and reconciles each, so the resolver follows DHCP IP moves.
  • Config lan_resolver.{enable,host_ip,upstreams,interval_seconds,state_dir} (host_ip defaults to the local-API bridge IP). --selftest=lanresolver -vmid N. New FELHOM_DNSMASQ sudoers alias.

2A spike findings (host environment — all green, no blockers)

  • :53 is FREE on the host (no systemd-resolved/dnsmasq/named).
  • Host IP is STATICvmbr0 inet static 192.168.0.162/24 (the stable anchor the router points at).
  • Host DNS intact/etc/resolv.conf (→ Pi-hole 192.168.0.250) is untouched; the host resolves upstream for itself independent of the dnsmasq we add for LAN clients.
  • Domain sourcingpct exec <vmid> -- docker exec felhom-controller cat …/controller.yamlcustomer.domain (no new credential).

Live validation (felhom-pve, guest 9201 = demo-felhom)

  • --selftest=lanresolver -vmid 9201: installed dnsmasq, wrote base + per-customer config, discovered IP 192.168.0.151 + domain demo-felhom.eu, dnsmasq listening on 192.168.0.162:53.
  • Full loop from dooplex (real LAN client): felhom.demo-felhom.eu A → 192.168.0.151, AAAA → NODATA, example.com → forwards, real cert 200 ssl_verify=0.
  • IP-change tracking: poisoned the drop-in with a stale IP → reconcile detected the live IP differs, re-rendered + reloaded → corrected.
  • Daemon loop runs (lanresolver: enabled host_ip=192.168.0.162 interval_s=300); gracefully skips a not-yet-leased / absent guest.
  • Box-down: with dnsmasq stopped, the primary (192.168.0.162) refuses; a secondary (1.1.1.1) still answers normal names — so a client configured [host-IP, secondary] fails over to the Cloudflare path; recovery confirmed on restart (dnsmasq is enabled → starts on boot).

Install step (the one irreducible manual action) + robustness

  • One-time: set the customer router's DNS servers to [host-IP (192.168.0.x) primary, an upstream secondary (e.g. 1.1.1.1)]. After that, split-horizon is automatic forever — new apps auto-resolve locally (the wildcard address= covers every subdomain) and serve the real wildcard cert.
  • Box-down: the secondary means a box reboot degrades to the public/Cloudflare (hairpin) path, not total DNS loss. Client DNS-failover is imperfect but standard; this is a required install config, not agent code.
  • Locked ISP routers (can't repoint DNS): LAN-direct isn't achievable; the Cloudflare tunnel remains the graceful fallback. Documented, not forced.
  • New host dependency the agent ensures: dnsmasq (installed/enabled by EnsureDnsmasq).

Notes / follow-ups

  • Stale guest state (/var/lib/felhom-agent/guests/<vmid>/ for a destroyed guest) makes the loop log a harmless per-tick skip; a decommission lifecycle should remove that dir + call Manager.Remove. Left for the decommission slice. (Cleaned the one stale 9200 dir on the demo by hand.)