Scenario-D green: real vzdump of 9201 → ciphertext in ns over the tunnel; ns-scoped verify=ok under the box's own DatastoreBackup token; WARN gone; restore round-tripped (decrypt with box-born key → boot → teardown). Findings recorded: retarget field is local_backup_target (reverted to local — single-target offsite is near-continuous given the ~30-min controller cadence; needs Tier-1/Tier-2 split); restore-test scheduler needs a WAN deadline + scratch VM.Allocate; escrow left as an operator-present step. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
5.7 KiB
REPORT — S4: PBS over the tunnel (agent v0.65→v0.67 + live tenancy on felhom-hetzner)
Date: 2026-07-04 · Class: implementation + live validation on felhom-pve (box) +
felhom-hetzner (endpoint). Design of record: felhom.eu/documentation/architecture/06-offsite-connectivity.md
§3.4 (SHIPPED), §4.2 (v4-pin SHIPPED), S4 roadmap row; runbook offsite-endpoint.md §4a/§4b.
1. Baselines & commits (felhom-agent, all pushed to main)
Start: c618fc6 (v0.65.0, published+vouched).
734f45cv0.66.0 — wgtunnel v4-pin + re-resolve watchdog; FELHOM_WG Critical flips.027948bv0.67.0 — namespace-aware PBS client (operator-approved, see §3).- this commit — CHANGELOG (0.66/0.67 above) + CONTEXT + this REPORT.
felhom.eu docs:
1907dab(runbook §4a/§4b + doc-06 §3.4/§4.2/S4 row).
2. Agent code (Parts 1+2 + the approved ns-aware addition)
- v4-pin (doc §4.2,
internal/wgtunnel).renderConftakes the pre-resolved IPv4 literal →Endpoint = <ip>:<port>(never the DNS name, never AAAA).Resolverseam (LookupNetIP "ip4", lowest address = deterministic fleet-wide); renderConf stays pure; resolved IP cached → steady-state Apply = zero DNS + zero execs. DNS failure keeps last conf (never a teardown). - Re-resolve watchdog (
Manager.Watchdog, loop-only). Handshake age >stale_after_seconds(default 180) → re-resolve; IP changed → re-render+restart (endpoint re-IP recovery); same → no churn (throttled). Reuseswg show latest-handshakes(neverdump). - Capability Critical flips.
wg-conf-install/wg-enable/wg-restart/wg-handshake-read→ Critical=true (backups ride the tunnel now); apt-install + disable stay non-critical. - Namespace-aware PBS client (v0.67.0,
internal/pbs). Config.Namespace →Snapshotspasses?ns=,Verifypassesns=. Root-ns clients unchanged. Why: Phase-1 found the ns-unaware datastore-root calls 403 for a per-tenant token, and whole-datastore verify neededDatastore.Verify(~admin, breaking isolation).proxmox.Storage.Namespaceparsed from/storage;pbsTargetsFromPVEthreads it. Green gate + red-proofs (v4-pin golden, watchdog negatives, capability set, ns-scoping) all fire.
3. Phase-1 endpoint tenancy probe (felhom-hetzner) — the empirical spike
Created ns demo-felhom-01 + privsep token felhom@pbs!demo-felhom-01; adversarial second tenant.
Confirmed minimal ACL: DatastoreBackup on /datastore/felhom-offsite/<ns> (namespace path,
NOT /ns/<ns>) granted to BOTH the user felhom@pbs and the token — PBS privsep =
intersection(user, token); isolation holds (cross-ns list/backup → 403, proven). DatastoreBackup
covers backup/list-own/restore-own and ns-scoped verify (no Datastore.Verify/admin), but
not prune/forget (Datastore.Modify|Prune) — the box can't delete its own backups (safety).
Probe tenants cleaned up; only the real demo-felhom-01 tenant remains.
4. Box-side + live Scenario-D validation (felhom-pve)
felhom-offsitePVE storage (Server=10.77.0.1over the tunnel, box-born encryption key, argv-safe.pw/.enc+storage.cfg direct-write) →pvesm statusactive. Agent PVE token grantedFelhomAgentStoreon the new storage (dual-grant). WARN fix:pbs-secrets/(0700 felhom-agent) with both.pwcopies +pbs_secret_dir→ zero WARN, both datastores in the verify cycle.- Scenario D — ALL GREEN: a real vzdump of guest 9201 (crash-consistent, ~4.6 GB compressed,
~20 min over the tunnel) landed as ciphertext in ns
demo-felhom-01(root.pxarcrypt-modeencrypt); the agent report listed it (ns-awareSnapshots); the ns-scoped verify ran under the box's ownfelhom@pbs!demo-felhom-01token and returnedok(no admin, no 403); the WARN grep = 0; a restore round-tripped — restored from offsite, decrypted with the box-born key (b0:fe:2a:4e…), booted (running, systemd, uptime 14 s), torn down clean.
5. NOT yet live-validated / findings / observations
- The restore-test SCHEDULER couldn't complete the offsite restore-test (the round-trip was
proven MANUALLY as root instead). Two box-side follow-ups: (a) its internal restore-task deadline
(~10 min) is too short for a WAN restore of a large volume; (b) the agent PVE token lacks
VM.Allocateon the scratch VMID band for teardown. Raise the deadline for thepbstier + grant the scratch-bandVM.Allocatebefore the offsite restore-test can run unattended. - Retarget reverted. The controller triggers a full backup ~every 30 min; with a SINGLE backup
target (
local_backup_target) retargeted tofelhom-offsite, each becomes a ~20-min offsite upload — near-continuous, uplink-saturating. Reverted tolocal. Offsite-as-default needs the Tier-1(local, frequent)/Tier-2(offsite, periodic) split — the single-target model can't be the default. The offsite storage + tenancy stay live for scheduled/on-demand offsite backups. - Spec field-name correction: the retarget field is
backup.local_backup_target(notbackup_target);defaultBackupTarget(felhom-pbs) unchanged in code. - Deferred: DR consume of the bundle incl. WG key + offsite restore with only R (S5); hub-driven
tenancy automation (hub creates ns+token, serves
pbs_namespaceon desired-state); endpoint datastore sizing for real volumes; true-CGNAT-SIM retest (parked). - OPERATOR-PRESENT (Part 5, NOT run): escrow-create of the offsite token + tunnel token + WG
key mints a NEW recovery code
Rthe operator must record — deliberately left for an interactive operator run (never--uploada blob whose R nobody recorded). OPERATOR: publish 0.66/0.67 + Day-0 vouch. Rollback binaries on the box:felhom-agent.bak-0.65.0/.bak-0.66.0.