diff --git a/CONTEXT.md b/CONTEXT.md index c4b2141..8b07b48 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -3,6 +3,15 @@ > Created with the REUSE.md rollout (2026-07-03). Authoritative history: `hub/CHANGELOG.md` (hub), > `website/CHANGELOG.md`, `scripts/CHANGELOG.md`; end-of-task detail in `REPORT.md`. +- **2026-07-03 — GOLDEN 0.98.3 LIVE + VOUCHED; guide D.1b RETIRED (drill B1+B5 FIXED)** — + `build-golden.sh` v2.0.0 (mandatory controller tag + baked bootstrap `.path` unit); golden baked + with controller 0.98.3, clean-room validated (drill VM) through all four scenarios incl. the + published-artifact `--force-gitea-golden` path, published + operator-vouched (Day-0 manifest now: + agent 0.63.0 + golden 0.98.3). Fresh installs land CURRENT and self-manage — D.1b is a one-line + check; the prior "vouch agent 0.63.0" operator follow-up is DONE. Evidence: + `documentation/audits/DRILL-golden-098-2026-07-03.md`. NEW operator follow-ups: (1) SECURITY — + the customer-config `git.token` is a Gitea admin token with package-WRITE (proven during the + bake); scope down + rotate. (2) optionally raise the global controller floor to 0.98.3. - **2026-07-03 — drill findings B2+B3 FIXED (agent v0.63.0, live on felhom-pve + published)** — token reload-on-miss (no more fresh-install 401/manual restart) + guesthook snippets-dir mkdir. Guide D.1b restart step narrowed to "agent < v0.63.0"; drill ledger updated. **OPERATOR: vouch diff --git a/REPORT.md b/REPORT.md index 0815e4f..15de4c3 100644 --- a/REPORT.md +++ b/REPORT.md @@ -4,75 +4,75 @@ --- -# Go-live package — Day-0 install guide, clean-room drill, uninstall proof (2026-07-03) +# REPORT — Golden rebuild 0.98.3 (drill B5 + B1) — docs half (2026-07-03) + +Implementation half in `felhom-agent/REPORT.md` (build-golden.sh v2.0.0 @ `ceca355`). Full drill +evidence: **`documentation/audits/DRILL-golden-098-2026-07-03.md`** (A–D transcripts, unit states, +resolution-order + fetch/sha proofs, cleanup, observations). This repo's changes are docs-only. ## Baselines -| Repo | `main` before | After | -|---|---|---| -| felhom.eu | `996526273a` (script v1.9.0) | this commit — script **v1.9.1** + `documentation/runbooks/day0-install.md` (NEW) + `documentation/audits/DRILL-day0-cleanroom-2026-07-03.md` (NEW) | -| felhom-agent | `84f3f7ddb1` v0.62.0 | untouched (read-only; 3 findings recorded, not patched) | +| Repo | Base → head | +|---|---| +| felhom.eu | `2e33a8b` → this push (docs) | +| felhom-agent | `c9f963d` → `ceca355` (script + CHANGELOG) | +| felhom-controller | untouched; **0.98.3** reconfirmed current + pullable, and is the baked tag | -## What shipped +## What shipped (system-level) -1. **`documentation/runbooks/day0-install.md`** — the complete first-time deployment guide - (Parts A–F: hub onboarding / box-prereq checklist (OQ-3) / the canonical shared-box install - command / post-install verification incl. the mandatory one-time controller update D.1b / - uninstall + expected-remnants / troubleshooting from real drill hiccups). Header states the - drill date/environment/versions. Every command was executed verbatim during the drill. -2. **Clean-room drill (Phase 0 + D0–D6)** — throwaway nested PVE 9.2.2 under QEMU/KVM on the build - server (unattended install via `proxmox-auto-install-assistant`, virgin qcow2 snapshot, slirp - NAT; the primary Phase-0 mechanism worked — no fallback needed). Full cycle proven: baseline - manifest → hub onboarding of throwaway customer `drill-1` → install (real - `--force-gitea-golden` + `--cores 2 --memory 4096` + `--acl-storages "local local-lvm"`, - sha256-verified agent v0.62.0 + golden v0.85.1) → verify (selftest incl. `pve:pool-read`, - hub reporting, dashboard 200 behind traefik) → app deploy via the exact UI endpoint - (`bentopdf`, 200 at its own hostname) → uninstall → **residue diff** → reinstall - (host-enroll REUSED; fresh leaf on an empty box). Evidence doc: - `documentation/audits/DRILL-day0-cleanroom-2026-07-03.md`. -3. **`scripts/felhom-host-install.sh` v1.9.1** — drill-justified fixes, each re-verified live: - - header/version sync (v1.8.0 header vs 1.9.0 var) + keep-in-sync note; - - uninstall removes the five drill-found residue items (agent **config with the live hub - api_key**, shared-parent unit+script+`/mnt/felhom-drives`, guarded-mkfs wrapper, guest-hook - snippet, dnsmasq snippets) — v1.9.1 re-drill diff vs the virgin baseline shows **zero - Felhom-named leftovers**; - - **post-provision guest reboot + bounded verify wait (R6)** — without it the golden's - controller-bootstrap unit (boot-time `ConditionPathExists`) loses the race with the agent's - hot-plugged bootstrap mount on slower hardware and the controller never deploys; v1.9.1 - reinstall brought the controller up with no manual intervention. - Gates: `bash -n`, `--dry-run` previews, live re-drill of both paths. +- **Golden 0.98.3** — bakes controller 0.98.3 + the `felhom-controller-bootstrap.path` unit; + published to Gitea (`felhom-golden/0.98.3/golden.tar.zst`, HTTP 201, round-trip sha + `b9a02ef1b6f02b9b58babc4c6aad9cf6c053ebdfba116c78c8e7830de757fd01`) and **operator-vouched** in + the Day-0 manifest (now: agent 0.63.0 + golden 0.98.3 — verified via `/api/v1/artifacts`). +- **B5 dead:** clean-room Day-0 install (Scenario C, local golden; Scenario D, vouched Gitea + fetch + sha verify) lands controller **0.98.3 on first boot**; `selfupdate/check` reports + up-to-date → the box self-manages; agent selftest clean; bentopdf deploys + answers 200. +- **B1 dead:** isolated proof — service condition-failed + path `active (waiting)` on a mount-less + boot; `pct set -mp9 …` against the RUNNING guest started the controller in ~1 s, no reboot + (`uptime -s` unchanged). Installer v1.9.1 reboot retained as belt (removal = recorded cleanup). -## OQ answers (detail in the drill doc §8) +## Docs changed (this repo) -- **OQ-1:** go live **local-backups-only** (`--acl-storages "local local-lvm"`) — PBS is LAN-only - until Headscale; retrofit later via `--rescope-acl`. Documented as the guide's standard. -- **OQ-2 (evidence-based):** a fresh install lands controller **0.85.1** (golden-baked, pre-floor) - and **never self-updates** — raising the floor does not help a fresh box. The guide's D.1b - (restart `felhom-agent` → trigger the settings-page update → verify) is the mandatory - install-day step; drill-proven 0.85.1 → 0.98.3. Structural fix = rebuild+re-vouch the golden - (operator follow-up). -- **OQ-3:** Part B of the guide is the customer-confirmed pre-install checklist. +- `documentation/runbooks/day0-install.md` — **D.1b retired** to a one-line `selfupdate/check` + verification; old procedure → Part F troubleshooting row keyed on "golden older than 0.86.0"; + header versions line (script v1.9.1 / agent v0.63.0 / golden v0.98.3); A.3 drilled-known-good + pair + vouch-≥0.98.3 note; A.4 floor text rewritten + raise-floor recommendation. +- `documentation/audits/DRILL-day0-cleanroom-2026-07-03.md` — ledger **B1, B5 → FIXED**; R6 + belt-note. +- `documentation/backlog/FOLLOWUP-golden-default-controller-tag.md` + `backlog/README.md` — + **RESOLVED** (M18/M19 convention: file kept + annotated, README entry marked FIXED; the note's + `:0.43.0` numbers were history — the live default had already rotted to `:0.85.1`, which is the + form of the problem the mandatory arg kills). +- NEW `documentation/audits/DRILL-golden-098-2026-07-03.md` — the evidence doc. -## Go/no-go for Peti: **GO** +## Key proofs (short form; transcripts in the evidence doc) -With three install-day items: serve v1.9.1 (this push), execute D.1b, create the real CF tunnel + -git credentials in Part A. Full list + blockers ledger: drill doc §8–§9. +| Gate | Evidence | +|---|---| +| B5 red-proof | no-arg `build-golden.sh` dies with usage, exit 1, before any `pct` op (run on Windows + in the drill VM) | +| Scenario A | `[golden] build-golden.sh v2.0.0 — baking controller …0.98.3`; vzdump log: mp0 AND mp1 **included**; guest 9100 destroyed | +| Scenario B | before: `ConditionPathExists … not met` + path `active (waiting)`; after mp9 hot-plug: service SUCCESS @ +1 s, container `Up (healthy)` 0.98.3, boot time unchanged | +| Scenario C | `[SKIP] using local golden: …18_01_21.tar.zst` (resolution order); first boot 0.98.3; `update_available:false`; hub rows agent 0.63.0 / controller 0.98.3; bentopdf 200 | +| Publish | pre-delete 404 → PUT **201** → round-trip GET sha **matches** | +| Scenario D | `fetching golden v0.98.3 from Gitea` → `verified sha256 b9a02ef1… matches the hub manifest` → SUCCESS; first boot 0.98.3; up-to-date | +| Cleanup | all 8 drill-1 hub tables at count **0**, demo-felhom + peti-felhom intact; drill VM reverted to `virgin` (kept); bake cred file removed | -## Recorded findings NOT fixed here (per the gap policy) +Secrets: registry read-cred via 0600 env file only; the bake script's in-guest +`docker logout + rm /root/.docker/config.json` line is present and ran before archiving; publish +used the build server's out-of-band Gitea admin credential; nothing committed. -- **B1** (agent/golden, LOW): product-side fix for the bootstrap-race (golden path unit or - agent-side restart) — the installer reboot is a correct external fix. -- **B2** (agent, LOW): `guesthook.InstallSnippet` fails on fresh boxes (`/var/lib/vz/snippets` - missing; `install` can't create parents) → no pre-start self-heal hook, warn-only. -- **B3** (agent, MED, pre-existing): fresh-install local-API 401 until agent restart — new - consequence found: blocks the controller self-update swap. -- **B4** (hub, LOW): no host-delete path (UI or API); drill rows cleaned via direct SQL. -- **B5** (fleet, MED): golden bakes a pre-floor controller → D.1b needed on every fresh install - until the golden is rebuilt ≥ 0.86.0 and re-vouched. +## Observations / operator follow-ups -## Cleanup - -Hub `drill-1` rows fully deleted (all tables → 0; `demo-felhom` intact). Drill VM -**virgin-snapshotted and stopped** (kept at `~/drill/` on 192.168.0.180, ~6.7 GiB, for future -drills); evidence logs archived there. Nothing touched felhom-pve, guest 9201, or any real -customer/host record. No secrets in any committed file. +1. **SECURITY:** the customer-config `git.token` (held by every customer box) is a Gitea **admin** + token with **package-WRITE** — the bake proved it by successfully publishing with it. The + manifest-sha chain protects installs from tampered artifacts, but the capability shouldn't exist + customer-side: issue a scoped read-only account/token + rotate. +2. `build-golden.sh`'s publish block auto-fires whenever `REGISTRY_*` is set (needed for the pull + too) → it published BEFORE Scenario C; deleted (204) and re-published after the gate. Candidate + cleanup: a `GOLDEN_PUBLISH=1` opt-in flag. +3. The installer's post-provision reboot is now redundant (path unit wins first) — candidate + removal in a future installer version; kept per the task rules. +4. Recommended: raise the global controller floor to 0.98.3 (UI, 1 min) for drift protection. +5. Drill-environment note: launching the drill VM with `dhcpstart=10.0.2.30` (+ explicit + `hostfwd…-10.0.2.15:22`) eliminates the prior drill's slirp DHCP/IP-collision quirk — worth + using in every future drill. diff --git a/documentation/audits/DRILL-day0-cleanroom-2026-07-03.md b/documentation/audits/DRILL-day0-cleanroom-2026-07-03.md index c2119d4..13392b7 100644 --- a/documentation/audits/DRILL-day0-cleanroom-2026-07-03.md +++ b/documentation/audits/DRILL-day0-cleanroom-2026-07-03.md @@ -174,12 +174,12 @@ cores/RAM for the cap, root SSH, outbound reach incl. the three exact URLs+expec | ID | Where | Severity | What | Status | |---|---|---|---|---| | R1–R5 | installer | — | uninstall residue (config w/ secrets, shared-parent unit+script+mount, mkfs wrapper, hook snippet, dnsmasq snippets) | **FIXED v1.9.1**, residue-diff-proven | -| R6 | installer | — | controller-bootstrap unit skipped: no post-provision reboot (agent hot-plugs the mount; unit condition is boot-time) | **FIXED v1.9.1** (reboot + bounded verify wait), D6b-proven | -| B1 | agent/golden | LOW | structural fix for R6 belongs in the product too (path unit in the golden, or agent-side restart after back-half) — installer reboot is a correct but external crutch | RECORDED (agent/golden follow-up) | +| R6 | installer | — | controller-bootstrap unit skipped: no post-provision reboot (agent hot-plugs the mount; unit condition is boot-time) | **FIXED v1.9.1** (reboot + bounded verify wait), D6b-proven. NOTE (2026-07-03, golden-098 task): goldens ≥ 0.98.3 bake a `.path` unit that makes the reboot redundant — the reboot is RETAINED as a belt; its removal is a recorded candidate cleanup, not done | +| B1 | agent/golden | LOW | structural fix for R6 belongs in the product too (path unit in the golden, or agent-side restart after back-half) — installer reboot is a correct but external crutch | **FIXED** (build-golden.sh v2.0.0, golden 0.98.3): baked `felhom-controller-bootstrap.path` starts the service on the bootstrap-mount hot-plug — isolated + full-install proven, `DRILL-golden-098-2026-07-03.md` §4/§5 | | B2 | agent | LOW | `guesthook.InstallSnippet` fails on a fresh box: `/var/lib/vz/snippets` doesn't exist and `install` won't create it → no pre-start self-heal hook, warn-only (`install: cannot create regular file … No such file or directory`, agent journal 14:58:40) | **FIXED agent v0.63.0** (fenced `mkdir -p` precedes the install + the one sudoers grant; red-proofed — felhom-agent REPORT 2026-07-03) | | B3 | agent | MED | fresh-install local-API **401 until `systemctl restart felhom-agent`** — root cause: the daemon's TokenStore index is built once at open, while provisioning is a SEPARATE one-shot process minting into the shared file; NEW consequence found: it blocks the controller self-update agent-swap | **FIXED agent v0.63.0** (`Lookup` reload-on-miss, red-proofed + run on felhom-pve — felhom-agent REPORT 2026-07-03). Guide D.1b restart-step narrowed to "agent < v0.63.0" — applies until the Day-0 manifest vouches ≥ 0.63.0 | | B4 | hub | LOW | **no host-delete path** (UI or API) — drill host rows removed via direct SQL (spike precedent); customer-delete exists but leaves hosts/reports/guests/events orphaned | RECORDED (candidate small hub follow-up) | -| B5 | golden/fleet | MED | golden bakes a pre-floor controller (0.85.1) → every fresh install needs D.1b manually | RECORDED (operator follow-up: rebuild golden ≥ 0.86.0 + re-vouch in the Day-0 manifest UI) | +| B5 | golden/fleet | MED | golden bakes a pre-floor controller (0.85.1) → every fresh install needs D.1b manually | **FIXED** (build-golden.sh v2.0.0: mandatory controller tag; golden **0.98.3** baked, published, vouched): fresh install lands 0.98.3 on first boot, self-update reports up-to-date, guide D.1b retired — `DRILL-golden-098-2026-07-03.md` §5/§6 | ## 10. Cleanup confirmation diff --git a/documentation/audits/DRILL-golden-098-2026-07-03.md b/documentation/audits/DRILL-golden-098-2026-07-03.md new file mode 100644 index 0000000..0a24666 --- /dev/null +++ b/documentation/audits/DRILL-golden-098-2026-07-03.md @@ -0,0 +1,201 @@ +# DRILL — Golden rebuild 0.98.3: current-controller baseline (B5) + hot-plug path unit (B1) (2026-07-03) + +**Class:** implementation + clean-room drill. Companion deliverables: `felhom-agent/configs/build-golden.sh` +**v2.0.0** (commit `ceca355`) and the golden archive **0.98.3** (published + operator-vouched). +Source findings: `DRILL-day0-cleanroom-2026-07-03.md` §9 **B5** (golden bakes a pre-floor controller → +mandatory manual D.1b on every fresh install) and **B1** (controller-bootstrap only fires at boot; the +hot-plugged config mount needed the installer's reboot crutch). Also closes the stale backlog note +`documentation/backlog/FOLLOWUP-golden-default-controller-tag.md`. + +**Verdict (short):** both findings are FIXED in the product. A fresh Day-0 install now lands controller +**0.98.3** on first boot and self-manages from there (no D.1b), and the baked +`felhom-controller-bootstrap.path` unit starts the controller the moment the agent hot-plugs the +bootstrap mount — **no reboot needed** (the installer's v1.9.1 reboot is retained as a redundant belt). +Everything was proven clean-room on the drill VM BEFORE publish; the vouch is the only production change. + +No secrets appear in this document. The bake's registry credential was passed via env from a 0600 file +on the build server; the script logs out + removes `/root/.docker/config.json` in the build guest before +archiving (`build-golden.sh` — verified present in the bake transcript path). + +--- + +## 1. Environment & baselines + +| Item | Value | +|---|---| +| Drill VM | the DRILL-day0-cleanroom nested PVE 9.2.2 on the build server (192.168.0.180), restored from the `virgin` qcow2 snapshot before the bake and again before each install scenario | +| VM launch quirk fixed | slirp DHCP pool moved with `dhcpstart=10.0.2.30` (+ explicit `hostfwd=…-10.0.2.15:22`) so nested guests can't grab the PVE host's static 10.0.2.15 — the prior drill's IP-collision quirk is fully avoided, no guest IP pinning needed | +| felhom-agent base | `c9f963d` → script commit `ceca355` (build-golden.sh v2.0.0; no Go change, no agent version bump) | +| felhom-controller | **v0.98.3** reconfirmed as the current released tag (CHANGELOG top entry; manifest fetch HTTP 200 with the read credential) — the tag baked | +| Hub manifest at start | agent **0.63.0** (the v0.63.0 REPORT's operator follow-up was already done) + golden **0.85.1** | +| Debian template | `debian-13-standard_13.1-2_amd64.tar.zst` (pveam-downloaded into the virgin VM — not present on a virgin box) | +| Throwaway hub customer | `drill-1` re-created store-identically (SQL insert matching `SaveCustomerConfig`: plaintext retrieval passphrase reused from the prior drill's 0600 file, `RandomHex(32)`-format api_key, demo customer's config_json with **fake** CF tunnel + CF API tokens, real Gitea read credential); probes: config 200 with passphrase / 401 wrong; deleted after (§7) | + +## 2. Script changes (Phase 1) + red-proof + +`build-golden.sh` v2.0.0 (see felhom-agent CHANGELOG for the full rationale): + +1. **CONTROLLER_IMAGE (arg 6) mandatory** — no default; die-with-usage names the convention. +2. **`felhom-controller-bootstrap.path` baked + enabled** (`PathExists=/etc/felhom-bootstrap/bootstrap.json`, + `WantedBy=multi-user.target`); the service (oneshot/RemainAfterExit + ConditionPathExists) unchanged. +3. `GOLDEN_SCRIPT_VERSION=2.0.0` + a `[golden]` provenance line (script version + baked tag) in every transcript. + +Gates: `bash -n` clean (Windows + in-VM after CRLF strip). **B5 red-proof:** the no-arg invocation dies +with usage, exit 1, before any `pct` op: + +```text +[golden] FATAL: CONTROLLER_IMAGE (argument 6) is required — pass the released controller tag explicitly. +Usage: build-golden.sh [VMID] [TEMPLATE_VOLID] [ROOTFS_STORAGE] [ARCHIVE_STORAGE] [BRIDGE] CONTROLLER_IMAGE + e.g.: build-golden.sh 9100 local:vztmpl/debian-13-standard_13.1-2_amd64.tar.zst local-lvm local vmbr0 gitea.dooplex.hu/admin/felhom-controller:0.98.3 +exit=1 +``` + +(Re-run in the drill VM: rc=1 as well.) + +## 3. Scenario A — bake integrity (PASS) + +Bake on the virgin-restored drill VM as root@pam: +`build-golden-new.sh 9100 local:vztmpl/debian-13-standard_13.1-2_amd64.tar.zst local-lvm local vmbr0 gitea.dooplex.hu/admin/felhom-controller:0.98.3` +with `REGISTRY_USER/REGISTRY_TOKEN` from env (0600 file). Transcript (`bake-A.log`, archived at +`~/drill/` on the build server): + +```text +[golden] build-golden.sh v2.0.0 — baking controller gitea.dooplex.hu/admin/felhom-controller:0.98.3 +… +[golden] baking the controller-bootstrap PATH unit (starts the service on bootstrap-mount hot-plug — B1) … +… +INFO: including mount point rootfs ('/') in backup +INFO: including mount point mp0 ('/var/lib/docker') in backup +INFO: including mount point mp1 ('/mnt/sys_drive') in backup +INFO: archive file size: 583MB +[golden] DONE. golden archive volid: local:backup/vzdump-lxc-9100-2026_07_03-18_01_21.tar.zst +``` + +- **mp0 + mp1 both INCLUDED** (the load-bearing vzdump guard confirmed; no exclusion lines). +- Build guest 9100 torn down (`pct destroy 9100 --purge`, volumes removed). +- Archive sha256 `b9a02ef1b6f02b9b58babc4c6aad9cf6c053ebdfba116c78c8e7830de757fd01` (612 002 238 bytes), + identical after copy to the build server. +- **Deviation caught + corrected:** the script's opt-in Gitea publish block keys on the same + `REGISTRY_*` vars the pull needs, so the bake ALSO published immediately — before Scenario C. The + premature package was DELETED (HTTP 204, anon GET 404 verified) and re-published only after + Scenario C passed (§6). Recorded as an observation (§8-O2), script not changed (no-"while-here" rule). + +## 4. Scenario B — isolated hot-plug proof (PASS — the B1 STOP gate) + +Scratch guest 9300 `pct restore`d from the NEW archive (the bring-up mechanism, minus the bootstrap +mount — the exact provision race window), booted 16:05:58 UTC. + +**Before attach** (the R6 failure state, reproduced): + +```text +felhom-controller-bootstrap.service Active: inactive (dead) + Condition: start condition unmet … ConditionPathExists=/etc/felhom-bootstrap/bootstrap.json was not met + TriggeredBy: ● felhom-controller-bootstrap.path +felhom-controller-bootstrap.path Active: active (waiting) +docker ps: (no containers) ; /etc/felhom-bootstrap: No such file or directory +``` + +**Attach to the RUNNING guest** — the back-half's exact op (host dir + 0600 bootstrap.json + +`chown -R 100000:100000` + `pct set 9300 -mp9