Commit Graph

370 Commits

Author SHA1 Message Date
admin 8e26cdc3a5 docs(audit): closure appendix — F2 closed, F4-UX fixed, healthcheck class fixed
Pilot-blockers bundle shipped + live-validated: agent v0.73.0 (F2 mount-role
fallback), controller v0.102.0 (async restore family, F4 re-adjudicated as a UX
bug not a restore bug), catalog healthcheck sweep. All three accepted live on the
demo node.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
2026-07-06 20:43:52 +02:00
admin 66b955d054 docs(audit): P1-P3 re-run — accurate end-state + healthcheck-robustness observation
Correct the end-state: uptime-kuma redeployed healthy, notifications restored;
vaultwarden serves (127.0.0.1 /alive 200) but reads docker-unhealthy due to a
localhost->IPv6 healthcheck probe quirk (reproduced on fresh redeploy);
bookstack unhealthy at app level (HTTP 500, pre-existing). Recommend catalog
healthchecks use 127.0.0.1 not localhost. Neither is a P1-P3 defect.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
2026-07-06 19:28:15 +02:00
admin 1b19377f84 docs(audit): targeted P1-P3 backup/restore acceptance re-run (2026-07-06)
Pilot-gap CLOSED: F3 volume-tar backup (incl. volume-only apps, bit-identical),
vaultwarden restore drill (bit-identical), Tier-2 additive-only restore, off-box
SFTP round-trip — all proven on v0.101.0 + F1-fixed template with real data on a
healthy app set. Full F1-fix acceptance. F4 resolved as NON-bug (restore is a
long synchronous op; traefik proxy read-timeout returns 000/502 while the
controller completes it). ZERO product bugs in P1-P3; ~18 throwaway-harness bugs
fixed (path/sha/DB-client/proxy-timeout/test-ordering). No production code changed.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
2026-07-06 19:21:58 +02:00
admin 3eb31f50d6 docs(audit): findings triage addendum — F1 corrected root cause + F2 verdict + F3 fixed
F1: original "shim sets SMTP_FROM w/o SMTP_HOST" hypothesis was WRONG (shim
injects nothing when off) — real cause is defined-empty SMTP vars + upstream
_enable_smtp default true → crash; fixed via _ENABLE_SMTP gate (catalog),
live-validated. F2 verdict: REAL finding — roleForMountPath resolves an enrolled
user-data drive that isn't a PVE storage to system (fail-safe over-refusal)
while /disks calls it user-data; agent-side fix deferred. F3: fixed in
controller v0.101.0 (git subprocess deadline).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
2026-07-06 14:25:34 +02:00
admin 658e78d495 docs(audit): no-mercy full-system campaign report (2026-07-06)
Overnight unattended stress/regression on the demo node (9201). Platform
resilience + safety strong: all fault-recovery + reboot/hard-reset recovery
passed (58-126s; sysrq hard reset 103s), 2.5h soak 0 breaks, DR restore proven
sound. Raw 25 FAIL triaged: ~10 collateral of one app finding (F1 vaultwarden
SMTP crash-loop), ~8 harness mis-scores of correct product refusals
(single-flight 409, needs-confirmation), rest harness-calibration incl. the P9
volid-format bug (DR mechanism itself works - recovered 9201 manually from R1).
Genuine findings: F1 vaultwarden SMTP_HOST (HIGH), F2 decommission agent-403
(MED), F3 gitea-outage sync recovery (MED). No production code changed; demo
left running+healthy at R1, notifications restored.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
2026-07-06 12:23:12 +02:00
admin 61f4898d30 docs(H1): doc06 §4.5/§4.6 amendment + endpoint runbook §9 + scripts CHANGELOG + REPORT + CONTEXT
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
2026-07-05 23:03:33 +02:00
admin b70f2d0763 deploy(hub): v0.35.0 — OOB operator access (TASK H1)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
2026-07-05 22:35:00 +02:00
admin ccb378060d feat(install): --enable-oob installs felhom-sshd + static belt (H1 Part 5)
install_oob: /etc/felhom-sshd tree + dedicated host key + felhom-op user + scoped
sudoers + RuntimeDirectory-guarded felhom-sshd.service (enable-not-start; agent
renders config) + static felhom_oob nft table + boot loader. Uninstall removes all.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
2026-07-05 22:34:06 +02:00
admin f8fc09e5cc feat(hub): OOB access health ingest + degraded alert (H1 Part 4)
store.GetHostOOBStates parses the agent oob heartbeat stanza. monitor/host_oob:
transition-based oob_degraded/oob_recovered warning (felhom-sshd down while the
operator peer is configured, OR config invalid) — proactive "can the operator get
in right now" signal; unconfigured OOB never alerts. Wired into the 60s sweep.
Non-hollow tests + transitions.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
2026-07-05 22:30:03 +02:00
admin 0ec7555126 feat(hub): operator OOB peer + oob_peer_ip desired-state merge (H1 Part 1)
store.SetOperatorOOBPeer/GetOperatorOOBPeer (empty-host_id wg_peers row, explicit
/32, validated in-subnet/not-reserved/not-taken, last-write-wins rotation).
PUT/GET /admin/wg/operator-peer (global key). mergeWireguard adds oob_peer_ip when
an operator peer exists (absent = byte-identical). BumpAllHostGenerations forces
fleet re-fetch. Non-hollow tests both sides.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
2026-07-05 22:08:06 +02:00
admin a3ee93e97e docs(context): TASK G1 break-glass shipped + live-validated
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
2026-07-05 19:24:11 +02:00
admin f1d3922fcc docs(G1): REPORT + scripts CHANGELOG for break-glass (hub v0.34.1 live-validated)
Auto-heal drill (agent stopped) healed /run/sshd in 30.0s; mgmt_plane_healed
warning fired end-to-end; break-glass vault→retrieve→PVE-ticket proven.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
2026-07-05 19:22:51 +02:00
admin 012e5f3ecc fix(hub): mgmt_plane_healed alerts on the FIRST auto-heal (TASK G1) — v0.34.1
A heal marker is an event, not a baseline: construction seeds pre-existing markers
(startup false-alarm guard) but a newly-observed marker now raises the warning, so
the first auto-heal surfaces (matches the live drill). Added tests for both halves.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
2026-07-05 19:12:50 +02:00
admin 1bd054602d deploy(hub): bump to 0.34.0 (break-glass vault + mgmt_plane surfacing, TASK G1)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
2026-07-05 19:09:38 +02:00
admin 28ac5f575c docs(runbook): break-glass management-plane recovery (TASK G1)
The KEXINIT-reset diagnostic, the three-layer model, PVE-web-console recovery with
the vaulted root@pam credential, the /run/sshd one-line fix, finding a recurring
clobber, and the keep-the-180-token-fresh standing note.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
2026-07-05 19:04:34 +02:00
admin 05d81810d4 feat(hub,install): break-glass recovery vault + mgmt_plane surfacing (TASK G1)
Hub half of the management-plane break-glass (prereq for felhom-sshd/H1; agent
half = felhom-agent v0.71.0). Closes SPIKE-felhom-sshd §8/#9.

- store.host_recovery + methods: per-host root@pam console password, at-rest,
  operator-retrievable (the PVE-web-console fallback when sshd + auto-heal both fail).
- API: PUT /hosts/{id}/recovery-credential (self-scoped, day-0 vaults) + GET
  /admin/hosts/{id}/recovery-credential (global key only). Secret never logged
  (red-proofed).
- monitor/host_mgmtplane: parses the agent mgmt_plane stanza, raises
  mgmt_plane_healed WARNING on a new privsep_healed_at (recurring clobber surfaces
  before lockout; complements host_staleness).
- host-install: step_break_glass generates a strong root@pam password (openssl
  rand, never logged/filed — stdin to chpasswd + curl), vaults via host key;
  idempotent unless --rotate-recovery. Installs the G1 host artifacts (tmpfiles +
  agent-independent watchdog timer), RuntimeDirectory-guarded; uninstall removes them.

Hub v0.34.0. Non-hollow tests + red-proofs; full suite green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
2026-07-05 19:03:18 +02:00
admin 2f97ce31dd docs(audit): dedicated felhom-sshd instance spike (2026-07-05) — GO
Validates a second sshd (own port/config/host-key/AuthorizedKeysFile/unit) as
Felhom's OOB entry point, coexisting with the customer's stock sshd on :22:
robust port probe-and-claim (skip/idempotent/loud-exhaustion), four-axis
coexistence independence, tunnel-scoped nft, reload-gate robustness, operator
identity isolated via AuthorizedKeysFile location, clean uninstall.

#1 TASK constraint (caught live): a second sshd unit must NEVER declare
RuntimeDirectory=sshd — it removes the SHARED /run/sshd privsep dir on stop and
takes the stock sshd down (LAN SSH lockout; recovered via PVE console + mkdir
/run/sshd). Use a tmpfiles.d entry instead. All spike artifacts removed; baseline
re-verified.

Docs-only; no code/hub/agent/manifest change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
2026-07-05 18:18:37 +02:00
admin 2db92c8837 docs(audit): OOB-over-WG operator-peer spike (2026-07-05) — GO
Validates operator-inbound access over the existing offsite WG arc (doc 06):
operator peer forwarded operator->box only, box sshd gated to the operator /32,
§4.5 box<->box isolation intact (both negatives counter-proven), mutual repair
real (agent self-healed a stopped tunnel in ~15s unaided). One TASK-shaping gap:
the operator /32 must be a RENDERED conf field — a runtime `wg set` is wiped by
the agent's own self-heal. All live-arc changes reverted + baseline re-verified.

Docs-only; no code/hub/agent/manifest change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
2026-07-05 17:01:35 +02:00
admin b6bad953b5 feat(host-install)+docs: D1 — install self-update artifacts on day-0 + architecture §11 self-update (implemented)
felhom-host-install.sh installs felhom-selfupdate-guarded (sh -n), the rollback
unit + the [Unit] start-limit drop-in (daemon-reload) so day-0 boxes get
operator-signed self-update from birth; non-fatal on pre-D1 agent repos.
03-host-agent.md §11 updated to the shipped mechanism (signed op, A/B wrapper,
OnFailure rollback, tuned start-limit backstop).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
2026-07-05 15:36:15 +02:00
admin 582917d12d docs(audit): SPIKE — agent self-update rollback mechanics (systemd 257 semantics on felhom-pve)
GO with two design-reshaping surprises: OnFailure= fires on EVERY crash on
systemd 257 (rollback triggers at first post-apply crash; marker-guard +
idempotency mandatory), and deliberate restarts consume start-limit budget.
S1 trap confirmed (default shape never reaches failed — 35 starts/180s);
tuned [Unit] StartLimitIntervalSec=120 + Burst=4 → failed in ~20s;
[Service] placement is an asymmetric half-applied trap; reset-failed required.
S2: all restart variants reliable, detached systemd-run wins. S3: A/B flip
atomic under kill -9, all verbs + guards pass. S4: StateDir + sudoers shapes OK.
Dummy artifacts only; real agent uptime unchanged (verified).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
2026-07-05 14:45:58 +02:00
admin 7e42ca64e5 docs(controller): backup-architecture — class-C in-place file restore (C2/v0.100.0, additive-only semantics + reindex caveat)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
2026-07-05 13:23:19 +02:00
admin b5d92a8e45 docs(controller): backup-architecture — v0.99.0 restore-path fixes (F1 snapshots endpoint, F3 volume dumps real again, O4 resettable-secret generation + residual case)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
2026-07-05 11:57:02 +02:00
admin cf1a9402f2 docs(audit): app-data restore drill 2026-07-04 (guest 9201)
Keep-side restore validated live: class-A DB replay, fail-closed data-key
gate + proven non-destruction, resettable-secret discrimination, Tier-2
class-C copy, and guardrails (traversal/missing/single-flight/restart).

Findings: F1 (HIGH) UI restore dead — /api/backup/snapshots has no handler;
F2 no one-click in-place class-C restore; F3 named-volume data never backed
up (DumpAppVolumes* has no caller).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
2026-07-04 22:53:07 +02:00
admin 87f2841692 docs(offsite): S5 safe halves shipped (WG-key install + directive→plan); drill operator-gated
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
2026-07-04 21:12:21 +02:00
admin c758c97a1a docs(offsite): §3.5 — offsite escrow CREATED (S4 Part 5 done, operator-present)
escrow-create -storage felhom-offsite -identity-bundle -directive -upload on
felhom-pve: fresh R (operator-recorded off-transcript) wraps K + WG key + offsite
PBS token; uploaded to hub. Independently verified in host_escrow (demo-felhom-01:
key_fingerprint=offsite b0:fe:2a…, identity blob 499B, directive present,
created_at=now) with zero knowledge of R. S5 (DR consume) now UNBLOCKED.

This re-run also remediated an earlier escrow whose R had leaked into an agent
transcript (last-write-wins overwrote it with the clean-R blob).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
2026-07-04 20:35:34 +02:00
admin 73e5a003f2 docs(offsite): S4.1 — restore-test unattended (tier-aware deadline; VM.Allocate phantom)
Offsite restore-test now runs unattended on the agent-token path (agent v0.68.0):
tier-aware restore-task timeout (pbs→120m, local→10m). The scratch-band
VM.Allocate follow-up was a phantom — the scratch restores INTO /pool/felhom
(ACL already grants it); the earlier 403 was the 10m-timeout consequence. No
ACL/host-install change. doc-06 S4 row + runbook §4b updated.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
2026-07-04 19:54:48 +02:00
admin 1907dab296 docs(offsite): S4 SHIPPED — per-customer PBS tenancy + v4-pin/watchdog (live 2026-07-04)
- runbook offsite-endpoint.md: new §4a (endpoint per-customer tenancy) + §4b
  (box-side storage + agent wiring). Confirmed minimal ACL: DatastoreBackup on
  /datastore/<ds>/<ns> (NOT /ns/<ns>) to BOTH user felhom@pbs AND token
  felhom@pbs!<ns> (PBS privsep = intersection); cross-tenant 403; ns-scoped
  verify works with DatastoreBackup; DatastoreBackup can't prune (safety).
  Box: box-born encryption key, argv-safe .pw/.enc + storage.cfg, agent PVE
  FelhomAgentStore grant, pbs-secrets WARN fix, local_backup_target retarget.
- doc-06 §3.4 SHIPPED (ns-aware PBS client v0.67.0 rationale); §4.2 v4-pin +
  watchdog SHIPPED v0.66.0 (closes the dual-stack trap); S4 roadmap row done +
  follow-ups (restore-test WAN deadline + scratch VM.Allocate; Tier-1/Tier-2
  target split).

Live: real vzdump of 9201 → ciphertext in ns over the tunnel; verify=ok under
the box's own scoped token; WARN gone; restore decrypts with the box-born key.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
2026-07-04 17:21:05 +02:00
admin a7f954dde7 docs(offsite): resolve §4.3 MTU open decision → client MTU 1280 (S3.1)
The CGNAT smoke test's OPEN DECISION is made: fleet-wide, permanent,
family-agnostic client MTU 1280 (RFC 8200 IPv6-minimum floor), implemented in
felhom-agent v0.65.0.

- §4.3: OPEN DECISION → made decision + rationale + rejected alternatives
  (auto-probe/per-type = fragility for a non-metric; MSS-clamp = unneeded).
  "ships MTU 1420" → 1280; corrected "fixed-line unaffected" (DS-Lite ~1452,
  PPPoE would also black-hole at 1420 — one floor covers all).
- §4.2: 1280 is family-agnostic so MTU correctness is decoupled from v4/v6
  resolution; v4-pin noted as a separate optional determinism item.
- §3.2 wire-field note + §8 S6 row: MTU 1420 → 1280; S6 residual now only the
  true-CGNAT-SIM retest.
- REPORT.md overwritten.

Docs-only; the constant change is in felhom-agent v0.65.0.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
2026-07-04 14:02:48 +02:00
admin 2028785f5e docs(offsite): close §7 CGNAT open-validation; record mobile-path MTU finding
S3 appendix CGNAT smoke test run live on felhom-pve (agent v0.64.0) with a
USB-tethered phone. Tunnel established + held its NAT mapping across a 32-min
idle soak on PersistentKeepalive=25 alone (0 stalls, final ping no-bounce).

- §7: open-validation CLOSED; two honest caveats kept (SIM had a public mobile
  IPv4 so true CGNAT 100.64/10 not reproduced — retest-when-able; + MTU).
- §4.3: mobile outer path MTU ~1400 black-holed bulk data at the shipped 1420;
  MTU 1340 restored the PBS page. OPEN DECISION: permanent vs per-connection.
- §4.4: keepalive 32-min mobile-NAT hold datapoint.
- §4.2: dual-stack v6 trap (wg-quick prefers endpoint AAAA, rides un-NATed v6).
- §8: S6 row CGNAT closure marked done.
- REPORT.md: overwritten with this operational-validation summary.

Runbook: RUNBOOK-s3-cgnat-smoke. Docs-only; no agent/hub/controller code.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
2026-07-04 13:34:00 +02:00
admin 76ee25e5e8 docs: 06-doc S3 SHIPPED (agent v0.64.0) + agent-side revocation semantics + CONTEXT/REPORT
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
2026-07-04 08:58:44 +02:00
admin 4fe895eaa4 docs: REPORT — fill actual commit hashes
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
2026-07-04 01:20:47 +02:00
admin 265f46c0e7 docs: S2 shipped — hub v0.33.0 CHANGELOG, peersync v1.0.1, 06 §3.2 wire shape + S2 status, CONTEXT, REPORT
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
2026-07-04 01:20:10 +02:00
admin 99246c3c7d manifests: hub 0.33.0 (S2)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
2026-07-04 00:49:45 +02:00
admin 30d762d520 scripts: felhom-peersync v1.0.1 — strip out of process substitution (exit-swallow fix)
<(wg-quick strip ...) hid the strip exit code: a corrupt head file could feed
syncconf partial input that wipes the live peer set with exit 0 (S1 REPORT
finding). Strip now writes a temp file; its failure aborts before wg runs.
Sandbox red-proof: pre-fix shape invoked wg with rc=0 despite strip exit 1;
fixed shape never reaches wg.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
2026-07-04 00:49:25 +02:00
admin 13203c2452 hub: S2 /offsite registry page (read-only) + nav + WGPeer.CreatedAt
Endpoint card + peers table (truncated pubkeys with full-value title attr,
bound peers link to /hosts/<id>); Offsite nav link in all 9 page templates;
render tests for endpoint/peers, empty, and not-configured states.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
2026-07-04 00:48:03 +02:00
admin ba52005e61 hub: S2 API — box-facing WG registration + merge-at-read + hub-owned-key guard
POST /hosts/{id}/wg (per-host self-scoped; global = operator/DR path): bind /
re-key-in-place / adopt; generation bump + endpoint push ONLY on real change.
mergeWireguard injects the hub-owned block into served desired-state at READ
time (stored operator blob never touched; fail-safe unmerged on any error;
no-peer = byte-identical pass-through — existing golden test untouched+green).
handleAdminSetDesiredState rejects top-level wireguard (400). Admin DELETE of a
BOUND peer bumps the owning host. NEW golden desired-state-wireguard.golden.json
= the S3 cross-repo contract. Red-proofs a/b/c/d run + reverted.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
2026-07-04 00:44:40 +02:00
admin fcf84a0c5c hub: S2 store — host-bound WG peers (register/re-key/adopt), one-per-host index, BumpHostDesired
allocateWGPeerTx extracted from addWGPeerOnce (behavior-neutral; S1 tests
unmodified+green). RegisterWGPeerForHost: idempotent / re-key-in-place-keep-ip /
adopt-unbound / ErrWGPubkeyBoundElsewhere. Partial unique index enforces one
bound peer per host. BumpHostDesired touches ONLY the generation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
2026-07-04 00:39:24 +02:00
admin 740cc42734 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
2026-07-04 00:14:35 +02:00
admin d2cc02344c runbook: offsite-endpoint.md — executed + validated live on felhom-hetzner (S1)
All 8 steps run on the dev endpoint; live-run corrections folded in: enterprise-
repo removal after PBS install, proxmox-backup-client is a separate package,
throwaway-token namespace proof, wg-show-dump-leaks-the-private-key warning
(incident: first server key leaked to session log -> rotated on the spot),
AAAA-must-be-::1 + DNS-propagation-lag notes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
2026-07-04 00:12:07 +02:00
admin 5e1933b166 manifests: hub 0.32.1 (wgsync HostKeyAlgorithms fix)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
2026-07-03 23:52:37 +02:00
admin 0fa7ea1da1 hub: wgsync fix — constrain HostKeyAlgorithms to the pinned key's type
Live S1 validation caught it: a stock multi-hostkey sshd presented ECDSA while
we pin ed25519 → FixedHostKey refused a legitimate server. Regression test with
an in-process dual-hostkey server (fails without the fix — red-proofed).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
2026-07-03 23:52:08 +02:00
admin 6f5fb19a64 scripts+manifests: S1 felhom-peersync.sh v1.0.0 + hub wg-endpoint-ssh deploy surface
Peersync script: validate-first (jq contract check before ANY state change),
head-file + generated-peers conf model, syncconf-from-tmp then atomic mv (live
conf never diverges in the failure direction), zero-peer payload valid (wipe).
hub.yaml: 0.32.0 image + WG_ENDPOINT_SSH_* env + optional Secret mount so the
pod starts before the runbook's step-6 Secret exists.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
2026-07-03 23:42:12 +02:00
admin fbeeacb124 hub: S1 wgsync (pinned-SSH push + declarative reconciler) + /admin/wg API + env wiring
internal/wgsync: x/crypto/ssh client with ssh.FixedHostKey pin (no insecure
fallback), forced-command exec, ok/applied response contract; Reconciler pushes
the FULL peer list on Trigger or 5-min tick (drift repair by construction).
internal/api/wg.go: PUT/GET /admin/wg/endpoint + POST/DELETE/GET /admin/wg/peers,
global-key-only, pubkey in body (base64 vs URL), sync ok|deferred|disabled.
main.go: WG_ENDPOINT_SSH_* env wiring, disabled-with-INFO when unconfigured.
Groups B/C/D tests incl. in-process SSH server; red-proofs b/c/d run + reverted.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
2026-07-03 23:40:22 +02:00
admin b18f6aee1b hub: S1 store — wg_endpoints/wg_peers tables + /32 allocator (doc 06 §3.2)
Additive migration; AddWGPeer = one tx, idempotent on pubkey, lowest-free-host
allocation skipping network/pbs_tunnel_ip/broadcast, UNIQUE(assigned_ip) race
backstop with one internal retry; typed ErrWGEndpointUnset/ErrWGSubnetExhausted.
Group-A tests + red-proof (allocator-ignores-rows mutation fails 3 tests).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
2026-07-03 23:32:39 +02:00
admin 7fb20d5fb0 docs: architecture Part 06 — offsite-connectivity design-of-record (slice roadmap S1-S6)
Records the settled transport decisions (plain WG, host-side agent-managed pilot,
one hub-driven endpoint VM running WG+PBS, hub source-of-truth over WireDesiredState,
one datastore + per-customer namespaces, relay-through-DooPlex rejected). Grounded
at file:line vs felhom.eu@bf099f6 + felhom-agent@4ba1b14. Backlog line resolved to
cite spike + design doc. Docs-only; slices are separate future tasks.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
2026-07-03 23:02:31 +02:00
admin bf099f6a10 docs: SPIKE — offsite-backup connectivity: plain WireGuard wins the ladder (transport decision made)
P0-P7 probe ladder on real ends (demo-felhom PVE host <-> throwaway Hetzner).
Verdict: GO at H1 — plain host-side WG, box-dials-out to a small public endpoint;
Headscale is a separable fleet layer, not a traversal necessity. Line is plain-NAT
(not CGNAT) with zero IPv6 — recorded honestly; CGNAT vantage deferred to Peti VM 110.
Real 2 GiB worst-case PBS backup over the tunnel at the full home uplink, pin intact.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
2026-07-03 22:04:51 +02:00
admin 3d7f0505b0 skills: NEW felhom-app-catalog (4th skill) — catalog authoring workflow, proven on SparkyFitness
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
2026-07-03 19:48:53 +02:00
admin 7ad111cfb5 docs: golden 0.98.3 live — D.1b retired, drill B1/B5 fixed, backlog note resolved + evidence doc
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
2026-07-03 18:38:28 +02:00
admin 2e33a8b86b docs: D.1b narrowed + drill B2/B3 marked FIXED (agent v0.63.0)
Restart-the-agent step in day0-install.md is now conditional on agent <
v0.63.0 (reload-on-miss shipped; Day-0 manifest still vouches 0.62.0).
Troubleshooting row + drill ledger + go/no-go item 8 updated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
2026-07-03 17:27:39 +02:00
admin 0fa79cd90d Go-live package: Day-0 install runbook + clean-room drill + host-install v1.9.1
- documentation/runbooks/day0-install.md (NEW): operator Day-0 guide, every
  command drill-executed verbatim (Parts A-F incl. the mandatory one-time
  controller update D.1b and the OQ-3 prereq checklist).
- documentation/audits/DRILL-day0-cleanroom-2026-07-03.md (NEW): nested-PVE
  clean-room drill evidence D0-D6, residue tables, OQ-1/2/3 answers, findings
  ledger (B1-B5), go/no-go for Peti (GO).
- scripts/felhom-host-install.sh v1.9.1: residue-free uninstall (agent config,
  shared-parent unit/script/mount, mkfs wrapper, hook snippet, dnsmasq
  snippets; zero-residue diff proven) + post-provision guest reboot (R6) +
  header/version sync.
- CONTEXT.md, REPORT.md, scripts/CHANGELOG.md updated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
2026-07-03 15:14:47 +02:00