docs: S1 shipped — hub v0.32.0/0.32.1 CHANGELOG, peersync v1.0.0, 06 S1 status, CONTEXT, REPORT

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
This commit is contained in:
2026-07-04 00:14:35 +02:00
parent d2cc02344c
commit 740cc42734
5 changed files with 154 additions and 44 deletions
+39
View File
@@ -1,5 +1,44 @@
# Felhom Hub — Changelog
## v0.32.0 + v0.32.1 — S1 offsite connectivity: WG endpoint record + peer registry + pinned-SSH peer-sync (2026-07-04)
The hub side of doc 06's roadmap row S1 (`documentation/architecture/06-offsite-connectivity.md`),
resolving the slice-1 design point: **peer-sync = hub pushes over SSH** to a forced-command
reconcile script on the endpoint (pull/signed-manifest rejected — weakens immediate revocation;
HTTPS push API rejected — a new versioned binary + third public port for nothing).
- **Store** (`internal/store/wg.go` + migration in `store.go`, commit `b18f6ae`): `wg_endpoints`
(single expected row "ep0") + `wg_peers` (presence = desired state; no status column — that's
the S2 host-join). `AddWGPeer` = one tx, idempotent on pubkey, lowest-free-host `/32` allocation
skipping network/`pbs_tunnel_ip`/broadcast, `UNIQUE(assigned_ip)` race backstop + one internal
retry; typed `ErrWGEndpointUnset`/`ErrWGSubnetExhausted`.
- **wgsync** (`internal/wgsync/`, commits `fbeeacb` + `0fa7ea1`): `x/crypto/ssh` push client with
`ssh.FixedHostKey` pin (no insecure fallback, ever) **+ `HostKeyAlgorithms` constrained to the
pinned key's type** — the live validation caught a stock multi-hostkey sshd presenting ECDSA
against the ed25519 pin (legitimate server refused); regression-tested with an in-process
dual-hostkey SSH server. Reconciler pushes the FULL peer list (never deltas — drift repair by
construction) on `Trigger()` or a 5-min tick; payload
`{"version":1,"interface":"wg0","peers":[{pubkey, allowed_ip}]}`, deterministic order.
- **API** (`internal/api/wg.go`): `PUT/GET /admin/wg/endpoint`, `POST/DELETE/GET /admin/wg/peers`
— GLOBAL key only (the `handleAdminSetDesiredState` gate); pubkey validated 44-b64/32-byte;
**DELETE takes the pubkey in the JSON body** (base64 `/`+`+` keep pubkeys out of URL paths);
mutation responses carry `sync: ok | deferred: <err> | disabled` — the DB is the source of
truth, a failed push defers to the reconciler.
- **Wiring** (`cmd/hub/main.go`): `WG_ENDPOINT_SSH_{ADDR,USER,KEY_FILE,HOSTKEY}` env (key from
the mounted `Secret/wg-endpoint-ssh`, host key non-secret plain env); any piece missing →
`[INFO] WG peer-sync disabled` and mutations still work DB-only.
- **Tests:** allocator (exact IPs, freed-IP reuse, /30 exhaustion), API auth/validation with a
fake syncer, SSH client against an in-process server (exact payload bytes, stderr surfacing,
wrong-host-key refusal, multi-hostkey pin), reconciler (full-list, retry-on-tick, no-mutation
drift push, removed-peer-absent negative). Four red-proofs run and reverted (allocator-ignores-
rows, gate removal, InsecureIgnoreHostKey, delta-only push) — each failed its test.
- **Live-validated** end-to-end on the dev endpoint (`felhom-hetzner`, runbook
`documentation/runbooks/offsite-endpoint.md`): add → `wg show` on the box; delete → gone
(+404/403 paths); malformed payloads leave wg state byte-identical; endpoint reboot → persisted
set + hub push converges; client tunnel `ep0.felhom.eu:443` → PBS login page via the wg0-only
8007 rule; public 8007 unreachable. v0.32.1 = the HostKeyAlgorithms fix (0.32.0 image was
already pulled by the cluster; tag kept immutable).
## docs — Felhom skills introduced + CLAUDE.md refresh (2026-07-03)
Repo-level docs work alongside v0.31.0 (no hub code in this entry):