From 9e3513557f3ae79461a9f608969eb574f361830f Mon Sep 17 00:00:00 2001 From: kisfenyo Date: Thu, 11 Jun 2026 20:01:02 +0200 Subject: [PATCH] docs(v0.22.0): REPORT for durable_id exposure Co-Authored-By: Claude Opus 4.8 (1M context) --- REPORT.md | 79 ++++++++++++++++--------------------------------------- 1 file changed, 23 insertions(+), 56 deletions(-) diff --git a/REPORT.md b/REPORT.md index f4e20b2..ad2e9b8 100644 --- a/REPORT.md +++ b/REPORT.md @@ -1,63 +1,30 @@ -# REPORT — agent v0.21.0: agent-managed split-horizon LAN resolver (2026-06-11) +# REPORT — agent v0.22.0: expose durable_id in GET /disks (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). +**Repo:** `felhom-agent` · **Version:** 0.22.0 · **Pushed commit:** `4734d4a` · paired with +`felhom-controller` v0.43.0 (the controller-side storage-management rebuild that needs this). -## What shipped — `internal/lanresolver` +## What shipped (one read-only field) -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. +`internal/localapi/DiskInfo` gains `durable_id` (mapped directly from `StorageTarget.DurableID` — e.g. +`"uuid:"` for usb/local-dir, `"path:…"`/`"store:…"` otherwise). That's the entire change. -- **Renderer** — a base drop-in (`/etc/dnsmasq.d/felhom-resolver-base.conf`: `bind-interfaces`, - `listen-address=` + `127.0.0.1`, `no-resolv`, `server=`) + a per-customer - drop-in (`felhom-.conf`: `local=//` + `address=//`). 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. -- **`Manager`** — `EnsureDnsmasq` (apt-install if absent, base config, `systemctl enable --now`), - `ReconcileGuest` (discover live IPv4 via `pct exec -- 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//`) 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. +**Why it's necessary:** the de-privileged controller cannot read a device's filesystem UUID itself, yet +`POST /disks/assign` mounts **strictly by fs UUID** (`EnsureMount` → `/dev/disk/by-uuid/`). Without +the UUID surfaced somewhere the controller can reach, the guided init/attach flows could not complete the +mount. `GET /disks` already carried everything else (name/type/state/device/mount/class/data_bearing) but +dropped the durable id. The controller strips the `uuid:` prefix to get the assign key. -## 2A spike findings (host environment — all green, no blockers) -- **`:53` is FREE** on the host (no systemd-resolved/dnsmasq/named). -- **Host IP is STATIC** — `vmbr0 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 sourcing** — `pct exec -- docker exec felhom-controller cat …/controller.yaml` → - `customer.domain` (no new credential). +**Security:** no new privilege, no subprocess, no behaviour change to `format`/`assign`/`eject` or the +data-bearing signature gate. The agent still inspects the device itself at format time and refuses a +data-bearing wipe with a `pending_op` (the controller surfaces the `felhom-opsign` command). -## 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 `enable`d → starts on boot). +## Validation (live, guest 9201) +`GET /disks` (via the controller's `/api/disks` proxy) now returns `durable_id` per disk, e.g. +`felhom-usb` → `/dev/sdb1`, `data_bearing:true`, `durable_id:uuid:277a2179-a764-4758-b840-9ea741517914`. +The controller's guided init on `sdb` correctly drove the agent to a **data-bearing refusal** (HTTP 403 → +`pending_op{op:storage_wipe, host_scope:demo-felhom-01, durable_id:byid:wwn-0x5000039ddb108568-part1}`), +which the controller surfaced as the operator `felhom-opsign` command — the gate held end-to-end. -## 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//` 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.) +## Notes +This pairs with the controller's storage-management rebuild (build-golden default bumped to controller +0.43.0; golden rebaked). No other agent change.