# felhom.eu β€” task reports > **Overwrite** this file with a summary of the most recent task only (uniform with the other repos; not cumulative). The cumulative hub history lives in [hub/CHANGELOG.md](hub/CHANGELOG.md); the scripts history lives in [scripts/CHANGELOG.md](scripts/CHANGELOG.md). ## TASK β€” PBS DR tier SLICE 1: ep0 tenantsync surface + hub provisioning (hub v0.44.0) β€” 2026-07-10 **LIVE on k3s (Synced/Healthy, image 0.44.0, startup logs `WG peer-sync enabled` + `PBS DR tenantsync enabled`). NOT live-provisioned β€” no real customer was provisioned from this task; slice 2 (the agent apply-bridge) + the supervised Peti enable are pending.** Builds on SPIKE-pbs-tier-provisioning (00afadc). Commits: ce6a566 (code) + 32ca1f6 (live fix + image bump). ### What shipped - **ep0 surface (πŸ›‘ Viktor-approved, installed + smoked live):** `scripts/felhom-tenantsync.sh` v1.0.0 as a SECOND forced-command key for `felhom-peersync` (own sudoers drop-in; **peersync script + sudoers sha256 asserted byte-identical before/after**). Ops: `provision` (existing token β†’ typed `token_exists`; own-ns self-check with one regen retry then rollback), `reissue` (deleteβ†’recreateβ†’ re-grant β€” delete purges token ACLs, the spike fact), `fingerprint`. Secret hygiene: the token secret rides stdout ONLY (all tool stdout β†’ stderr; hub error paths never embed stdout). Smoke (throwaway `spike-smoke`, torn down): all ops green; **live fix caught by the smoke** β€” `proxmox-backup-client --output-format json` wraps as `{"data":[…]}` β†’ ns-exists check fixed to `(.data // .)` and reinstalled before the hub ever used the surface. **Orphan cleanup (spike flag #1):** `root@pam!spike` token + `/datastore/scratch` DatastoreAdmin ACL removed; ep0 now holds exactly the real `demo-felhom-01` tenancy. - **Hub `internal/tenantsync`:** wgsync-twin SSH client (pinned host key, constrained HostKeyAlgorithms, per-op JSON exec, typed `ErrTokenExists`). Contract test `TestErrors_NeverEmbedStdout` pins the no-stdout-in-errors rule. - **Provisioning flow (`web/pbsdr.go`):** config-form section "PBS DR tier (ep0)" β†’ on enable+save (detached-ctx atom): WG peer existence check (the agent self-registers its key; absence fail-closes with "has not reported a WG key yet") β†’ tenantsync provision β†’ secret stored consume-once HOST-scoped (`host_pbs_secrets`, the `one_time_secrets` twin) β†’ non-secret `pbs_dr` descriptor (`enabled, storage_id, pbs_tunnel_ip, datastore, namespace, token_id, fingerprint`) merged into the HOST `desired_json` + `SetHostDesired` generation bump. Already-provisioned re-save = success-no-op; disable = `enabled:false` descriptor, tenancy kept; **Re-issue PBS credentials** button = the F4 precedent (fresh consume-once secret + bump). - **Consume endpoint:** `POST /api/v1/hosts/{id}/pbs/consume-token` β€” per-host key self-scoped (global = operator recovery); 200 exactly once β†’ 404; a foreign key's 403 does NOT burn the secret. *(Deviation: task spec wrote `/host/{id}/…`; implemented under `/hosts/` matching every other agent-facing route β€” slice 2 must use the `/hosts/` form.)* - **Deploy:** `TENANTSYNC_SSH_KEY_FILE` env + optional `Secret/tenantsync` mount (key generated on 180, moved into the Secret, local copy shredded); same endpoint addr + pinned host key as peersync. ### Tests + red-proofs (all run, mutations reverted, gate green) - Happy path: descriptor lands in desired_json + gen bump exactly 1; secret consumable once and absent from ConfigJSON/desired-state/captured logs (grep-asserts). - **Red-proof consume-once:** dropping the `consumed_at` UPDATE β†’ store test re-served `"secret-1"`, API test returned 200 with the secret again β†’ both FAIL as designed. - **Red-proof fail-closed:** swallowing the applyPBSDR error in handleConfigUpdate β†’ all four fail-closed subtests FAIL with 303 (half-enabled save). - **Red-proof idempotency:** dropping the already-provisioned short-circuit β†’ re-save re-keyed the token (2 provision calls), staged a fresh secret, bumped spuriously β†’ FAIL. - Also covered: foreign-host 403 non-burning, reissue rotation (old secret dead), disable no-op on second save, storage-id edit updates descriptor without re-provision, tenantsync client round-trip against an in-process SSH server incl. wrong-host-key refusal. - UI leg: render tests (`TestPBSDR_FormRendersState` + suite) β€” the hub UI is password-gated, so render tests are the verification method per repo convention. ### Operator follow-ups - Slice 2 (agent apply-bridge) then the supervised Peti enable (floor + agent update first). - The demo host's missing Β§4b step-3 storage ACL grants (spike flag #2) β€” re-assert before slice 2 codifies the self-grant.