v0.106.0: offsite provisioning SLICE 2 — controller apply-bridge

On startup reconcile the hub-served offsite: descriptor into a key-only offbox
target. internal/offsiteapply.Bridge: verify-pin box host key vs host_fingerprint
(NO blind TOFU) → consume the one-time password (single-use, never logged) →
sshpass ssh-copy-id -s -f install + verify → configure offbox → EscrowState=pending
(fork-4 via Manager.ApplyOffsiteTarget) → persist a descriptor-hash marker LAST.
Idempotent + fail-safe. Seams faked in tests; both red-proofs run+reverted.
Dockerfile + sshpass. NOT yet live-applied (supervised end-to-end next runbook).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
This commit is contained in:
2026-07-09 19:14:14 +02:00
parent fa9362f36f
commit aa61fb3411
10 changed files with 686 additions and 61 deletions
+29
View File
@@ -1,5 +1,34 @@
## Changelog
### v0.106.0 — offsite provisioning SLICE 2: controller apply-bridge (2026-07-09)
Pairs with hub v0.38.0. On startup the controller reconciles the hub-served `offsite:` descriptor into a
working key-only offbox target — closing the loop to a hands-off, hub-driven offsite target. (Auto-confirm =
SLICE 3; soft-quota = SLICE 4.)
- **`internal/config`:** `OffsiteConfig` (`offsite:` section) mirroring the hub descriptor
(enabled/type/host/user/port/repo_path/quota_gb/box_type/**host_fingerprint**) — deep-merged from `controller.yaml`.
- **`internal/offsiteapply` (the apply-bridge):** `Bridge.Reconcile` — idempotent (a descriptor-hash marker
at `<dataDir>/offbox/applied_marker` prevents re-consuming a spent password) and fail-safe (any step fails
→ nothing persisted, retried next cycle). Flow: **scan + VERIFY the box host key against `host_fingerprint`
(no blind TOFU)** → generate the controller keypair → **consume the one-time password**
(`POST /api/v1/offsite/consume-password/{id}`, Bearer APIKey, single-use, never logged) → install the
pubkey (`sshpass -e ssh-copy-id -p 23 -s -f`) + verify key auth → configure the offbox target →
`EscrowState="pending"` (fork-4 enable path via `Manager.ApplyOffsiteTarget`) → persist the marker LAST.
Seams (consume/scan/keygen/install/enable) so unit tests fake all I/O. A consumed-but-failed install logs a
loud "password is spent — reset on the hub" signal.
- **`internal/backup`:** `Manager.ApplyOffsiteTarget` reuses `WriteOffboxSecrets`/`SetOffboxTarget`/
`PushOffboxPasswordForEscrow``EscrowState="pending"`; the escrow stage-push is best-effort (agent-down ≠ apply failure).
- **`cmd/controller`:** wires the bridge (real seams — HTTP consumer, x/crypto/ssh host-key scanner, ed25519
keygen, sshpass installer) and runs `Reconcile` async at startup (non-blocking; the config-refresh restart re-runs it).
- **`Dockerfile`:** + `sshpass`.
- Tests (faked seams): apply-end-to-end (pinned known_hosts + key + pending + marker + **pw-not-logged**);
host-key mismatch → refuse **+ companion red-proof** (drop the verify → wrong key pinned → test fails);
idempotent (marker match → no re-consume); install-fail → fail-safe **+ companion red-proof** (persist
marker early → failed apply looks done → test fails).
- **NOT yet live-applied** — the supervised end-to-end (hub provisions on the new pool box → controller
consumes + installs + configures) is the next runbook, gated on the hub's new scoped `HETZNER_TOKEN`.
### v0.105.0 — fork-4: offsite password custody hand-off + atomicity gate + DR inject + DR coord (2026-07-09)
Pairs with agent v0.77.0 to make the restic-offsite repo password recoverable at DR (rides the customer-R