D44/D45 = all picks applied (R-1265, R-1266 closed; R-1280–R-1283 opened); R-232 dated check done; release-2026-10-26 evidence: fix A/B live on rc.6, D47 set-aside alarm on rc.7
gates / gates (push) Successful in 13m3s
gates / gates (push) Successful in 13m3s
This commit is contained in:
1 parent
a86ab64f6c
commit
ade357a14e
38 files changed
+4907
-58
No files matched your search
@@ -64,7 +64,8 @@ Explicitly does **not**:
|
||||
- Serve application traffic or sit in the data path. **Control plane, not data plane**: if the agent dies, apps keep serving (Docker + LXC run without it); only *management* degrades — no new backups, no provisioning, hub loses the heartbeat.
|
||||
- Hold or proxy customer application data.
|
||||
- Run inside a guest. It is the thing that recovers guests and the host; it cannot be one of them.
|
||||
- Manage **geo-restriction / the Cloudflare API**. Geo is hub-owned: the customer sets it in the controller UI, the controller reports the geo desired-state to the hub, and the **hub** (holding the CF API token) reconciles the WAF (S4). The agent manages only the *tunnel* service (`cloudflared`, §3/§5), never WAF rules.
|
||||
- Manage **geo-restriction / the Cloudflare API**. Geo is hub-owned: the customer sets it in the controller UI, the controller reports the geo desired-state to the hub, and the **hub** (holding the CF API token) reconciles the WAF (S4). ~~The agent manages only the *tunnel* service (`cloudflared`, §3/§5), never WAF rules.~~ The agent manages no
|
||||
Cloudflare piece: it only reads the guest's `cloudflared` container (§3) — corrected 2026-10-25, D45, R-1266.
|
||||
|
||||
## 3. Process model & host integration
|
||||
|
||||
@@ -240,10 +241,16 @@ alerts the operator when an agent misses its expected check-in window. This is t
|
||||
failure mode for a managed service, so it gets first-class treatment hub-side.
|
||||
|
||||
**Break-glass.** Standing inbound control is off. But when the poll loop *itself* is wedged
|
||||
(agent hung, host sick) you cannot fix it through the poll loop. So there is an explicit,
|
||||
**off-by-default, customer-consented, fully-audited** emergency path: SSH to the host via
|
||||
(agent hung, host sick) you cannot fix it through the poll loop. So there is an explicit emergency path.
|
||||
~~**off-by-default, customer-consented, fully-audited** emergency path: SSH to the host via
|
||||
the Cloudflare Tunnel behind Cloudflare Access (or on-site). Enabling it is itself a signed,
|
||||
logged operation; it auto-expires.
|
||||
logged operation; it auto-expires.~~ **[DESIGN — corrected 2026-10-25, D45 = all picks, R-1266]** The path is the operator door: the operator's WireGuard
|
||||
peer reaches a dedicated `felhom-sshd` on the host (a non-22 port, reachable only from the operator's `/32` over
|
||||
`wg-felhom`, key-only login; `06` §4.5, TASK H1). It does not use the Cloudflare Tunnel. It is installed by default on
|
||||
an appliance install, and a BYO install refuses it (the owner's call; `scripts/felhom-host-install.sh`). It stays on;
|
||||
it is not opened per incident. When SSH itself is broken, the hub-vaulted `root@pam` password opens the Proxmox web
|
||||
console (`runbooks/break-glass.md`). The website tells the household that the operator has root access for support
|
||||
(R-985).
|
||||
|
||||
## 6. Agent ↔ controller local API
|
||||
|
||||
@@ -315,11 +322,11 @@ A controller can only `POST /rollback` (or snapshot/backup) **its own** guest
|
||||
token → guest and authorizes per guest, so a compromised controller's blast radius is
|
||||
**self-scoped and bounded** to its own guest.
|
||||
|
||||
**[DESIGN — undocumented, found 2026-10-24, review round 2 — confirmed through R-1265]:** the surface listed above is
|
||||
the slice-8 design; on `main` the local API registers **41** routes (`felhom-agent/internal/localapi/server.go`; 24 of
|
||||
them are not listed above — the backup tiers and target, disk candidates, decommission, format status, guest attach,
|
||||
the network storage routes, the controller swap, the escrow stage/preflight/ceremony routes, the debug logs, guest
|
||||
memory). Among them the controller can start a HOST action: `POST /host/line-test/run` (about 55 s; bounded by the due
|
||||
**[DESIGN — confirmed by the operator 2026-10-25 (D44 = all picks, R-1265); every route listed below since
|
||||
2026-10-25]:** the surface listed above is the slice-8 design; on `main` the local API registers **41** routes
|
||||
(`felhom-agent/internal/localapi/server.go:562-634`). The list above names 15 of them; the other 26 are in the table
|
||||
below (recounted 2026-10-25 — the review counted 24). Every route is behind the same per-guest token (`withGuest`).
|
||||
Among them the controller can start a HOST action: `POST /host/line-test/run` (about 55 s; bounded by the due
|
||||
date, the window, 6 tries a day and the heavy-operation gate — R-1250, unreleased). The local API also listens on the
|
||||
R-50 island bridge (host `169.254.253.1`, guest `net1` `169.254.253.2/30`, `internal/config/config.go:260-269`). The
|
||||
weekly line test keeps its state in the OOB state dir (`line-test.json`) and has an off switch, `line_test.disable`.
|
||||
@@ -327,6 +334,37 @@ The served WireGuard block carries `oob_peer_ip` and `oob_operator_ssh_key` (`in
|
||||
runs only inside the tunnel watchdog, and its 15-minute count lives in memory — an agent restart starts it again (the
|
||||
once-an-hour bound survives in `wg/last-rekey`).
|
||||
|
||||
The routes the list above does not name (read from `server.go:562-634` and each handler's own comment, 2026-10-25):
|
||||
|
||||
| Route | What it does |
|
||||
|---|---|
|
||||
| `GET /backup/tiers` | this guest's backup tiers, each with its cadence and whether its Proxmox storage exists now (`present` / `absent` / `unknown`) |
|
||||
| `POST /backup/target` | moves the primary backup tier to a new storage: create → grant → config; answers `restart_required`, and the caller restarts the agent after its own in-flight check |
|
||||
| `GET /host/line-test` | HOST: the weekly upload line test's last measurement; `present:false` when never measured (R-1247) |
|
||||
| `POST /host/line-test/run` | HOST: starts a line test now — about 55 s, bounded as above (R-1250) |
|
||||
| `GET /disks/candidates` | the host disks free for Felhom to enroll (`initialize`, and `attach` for those with a mountable file system) |
|
||||
| `POST /disks/same-disk` | whether two of the guest's paths sit on the same physical disk, from the agent's own read (`16` §4) |
|
||||
| `POST /disks/decommission` | takes the household's own drive out for good: a permanent intent, the guest bind pruned, unmounted; never formats; a system or backup mount is refused (403) |
|
||||
| `GET /disks/format/status` | the newest or running format job, for a caller whose request timed out |
|
||||
| `POST /disks/guest-attach` | binds an enrolled drive's `felhom-data` folder into this guest (read-write); the rest of the drive stays hidden |
|
||||
| `GET /guest/memory` | this guest's RAM and its bounds |
|
||||
| `POST /guest/memory` | a bounded RAM resize, applied live (R-24) |
|
||||
| `POST /netstorage/add` | mounts a NAS share on the host and starts a verify job; a failed verify rolls it back |
|
||||
| `GET /netstorage` | the NAS shares |
|
||||
| `GET /netstorage/verify-status` | the verify job's state |
|
||||
| `POST /netstorage/remove` | removes a NAS share |
|
||||
| `POST /controller/swap` | swaps the controller image (202, then async); the agent verifies and rolls back |
|
||||
| `GET /controller/swap/status` | the swap's state |
|
||||
| `POST /escrow/stage-secret` | stages the off-site repository password for the escrow ceremony (0600, never logged) |
|
||||
| `DELETE /escrow/stage-secret` | wipes the staged password |
|
||||
| `POST /escrow/recover-offsite-password` | with the recovery code in the body, returns only the off-site repository password (§8a) |
|
||||
| `GET /escrow/preflight` | the ceremony's checklist |
|
||||
| `POST /escrow/ceremony` | starts the root ceremony job |
|
||||
| `GET /escrow/ceremony/status` | the ceremony's state |
|
||||
| `POST /escrow/ceremony/claim` | the one-shot hand-off of the new recovery code; a later claim is 410 |
|
||||
| `POST /escrow/ceremony/confirm` | the household's two words; only then does the package reach the hub (R-1151) |
|
||||
| `GET /debug/logs` | the agent's debug log ring, for the controller's Debug page |
|
||||
|
||||
### 6a. Implementation (slice 8A — implemented)
|
||||
|
||||
**Status: implemented** (agent v0.10.0 `internal/localapi`; controller v0.35.0 `internal/bootstrap`
|
||||
|
||||
Reference in new issue
Block a user