# Journal — DRILL fresh install 0.242.0, 2026-09-14 Every observable in the order taken. Times are **UTC** unless marked. Hub log lines print **CEST** (UTC+2). Screens are in `screens/`, named by step. ## Baselines (re-verified 12:5x UTC) | repo | main | version | |---|---|---| | felhom-controller | `406755fa8fba` | v0.242.0 | | felhom-agent | `4586f0f7f6d1` | v0.130.0 | | felhom.eu | `41590f8ee618` | hub v0.112.0 | Hub, before the bake: agent `0.130.0` vouched · golden `0.236.0` vouched · `min_agent 0.129.0` · floor `min_controller_version 0.242.0`. Highest register id **R-492**. ## Phase 0.1 — golden 0.242.0 Baked 13:01:04 → finished before 13:06:42 · round-trip verified · vouched **13:08:41**. Full record: `documentation/tests/golden-0.242.0-2026-09-14/README.md`. ## Phase 0.2 — what a volunteer receives Searched, with what was searched: | source | what was looked for | result | |---|---|---| | `website/*.html` (9 pages) | `iso.felhom`, `iso`, `letolt`, `telepit` | **no mention of the installer, of `iso.felhom.eu`, or of any install step** (only `technologiak.html` "comparison" and two FAQ answers on power loss / backups matched the loose pattern) | | `https://iso.felhom.eu/` | the root, `index.html` | **HTTP 404** both. Only a named object answers: `felhom-installer-1.26.1-pve9.2-1.iso` (200, 1 705 322 496 B, last-modified 2026-07-31) | | `RUNBOOK-onboarding-draft-v4.md` | customer steps | **operator document**, operator present at the visit ("operator narrates"); C7 marked never executed | | `email.md` in the workspace | `find /mnt/5_hdd/felhom.eu -maxdepth 5 -iname '*email*.md'` | **absent** (only `audits/FINDING-app-email-rollout-2026-06-29.md`, unrelated) | | the R-11 tester one-pager | `find … -iname '*one-pager*'`, register | **never written** — ROADMAP-HISTORY: "RULED 2026-07-21 (channel); doc is the architect's" | | hub e-mails to a new customer | `hub/internal/notify/templates.go` | two: **„Kösd össze a Felhom dobozodat"** (self-bind link, at customer creation) and **„Elindult a Felhom szervered — beállító kód"** (claim code, after day-0). Neither says how to get or install the software | ## Harness limits, declared before the walk (not interventions — the volunteer is not limited this way) - **H1 — no mailbox.** Both customer mails go only to the registered address. The hub stores hashes; the Resend key on DooPlex is send-only (`401 restricted_api_key … only send emails`, probed 13:00). A volunteer reads their inbox; this harness cannot. - **H2 — keyboard.** `qm sendkey` emits US scancodes; the installer defaults to **Hungarian**. The layout was switched to U.S. English (as walk 5). A volunteer keeps Hungarian. - **H3 — the USB stick.** Auto-reboot was unticked so the VM does not re-enter the installer from the still-attached ISO; the ISO is detached by `qm set` after the install. A volunteer pulls the stick. - **H4 — the Terminal UI entry** was chosen over the graphical default for key-drivability. Both entries were release-gated. ## Scope choice - Customer created **with the DR tier OFF** (the form defaults it ON). The DR tier provisions an **ep0** namespace and token; the brief fences ep0. Offsite is OFF by default and stayed OFF. ## Step 1 — download 13:01:27 on demo-hp: `curl https://iso.felhom.eu/felhom-installer-1.26.1-pve9.2-1.iso` → rc 0, **32 s**, 1 705 322 496 B, sha256 `f3cc86d5f0ec68bba4155c994b4fa84e208d50209bb6e815636c99e5441059a6` = the published `.sha256` exactly. (No release report records a sha for 1.26.1; walk 5 recorded "same sha256" without the value.) **A volunteer must already know the exact file name** — the site lists nothing. ## Hub side — the operator's steps 13:03:03 `POST /configs/new` customer `drill0242`, name "Drill 0.242 first hour", domain `drill0242.felhom.eu`, e-mail `drill0242@felhom.eu`, DR tier off → **303** 13:03:04. Hub log (CEST): ``` 15:03:04 [INFO] Customer config created: drill0242 15:03:04 [INFO] self-bind link (hash 4270bdd2…, valid 7 days) emailed to the registered address of drill0242 15:03:04 [INFO] self-bind link auto-minted for drill0242 on customer creation (the console banner's promised email now exists) ``` The 5-word **Tulajdonosi jelmondat** was read from the customer page into a `0600` file on DooPlex (shape: 5 words, 42 chars). **The operator must hand it to the volunteer out of band — nothing sends it.** (The self-bind mail says „amelyet a beállításkor kaptál" — "which you received at setup".) ## Step 2 — install VM **330 `drill0242-fresh-install`** on demo-hp: q35/OVMF (`pre-enrolled-keys=0`), 4 cores, 8 GB, `cpu=host`, `scsi0` 200 G · `scsi1` 50 G · `scsi2` 50 G qcow2 on **`nvme-scratch`** (dir at `/mnt/hdd_1`, the mount root), MAC `bc:24:11:0c:23:cc`. | UTC | screen | what it said / what was done | |---|---|---| | 13:03:43 | power on | — | | 13:03:49 | GRUB (`s01`, `s02`) | Felhom logo, „Saját felhőd, saját szabályaid"; entries **„Felhom telepítés"** / **„Felhom telepítés (szöveges mód)"** — the only Hungarian the installer shows | | 13:05:09 | `s04` | **English** Proxmox EULA, „I agree" | | 13:05:27 | `s05` | „Target harddisk: /dev/sda (QEMU HARDDISK) (200.00 GiB)" — default is the right disk here; **three disks present, no guidance which** | | 13:05:43 | `s06` | Country Hungary · Timezone Europe/Budapest · **Keyboard Hungarian** (H2: changed to U.S. English) | | 13:09:18 | `s13` | Root password [at least 8 characters] · Confirm · **Administrator email prefilled `mail@example.invalid`** — the volunteer must decide what to type; set `drill0242@felhom.eu` | | 13:12:01 | `s16` | nic0 `bc:24:11:0c:23:cc` · **Hostname `pve.example.invalid`** · IP `192.168.0.134/24` (the DHCP lease, frozen as static) · GW `192.168.0.1` · DNS `192.168.0.250` | | 13:12:21 | `s17` | Next on the defaults → **„Invalid values: hostname does not look valid"**. The default is refused; the volunteer must invent a hostname. Set `drill0242.felhom.eu` | | 13:14:40 | `s22` | Summary: `ext4` · `/dev/sda` · `Europe/Budapest` · `U.S. English` · `drill0242@felhom.eu` · `nic0` · `drill0242.felhom.eu` · `192.168.0.134/24` · `192.168.0.1` · `192.168.0.250` · „[X] Automatically reboot" (H3: unticked, `s25`) | | 13:15:54 | Install pressed | — | | ≤13:19:18 | `s28` | „Success — Installation finished - reboot now?" (**≤ 3 m 24 s** of copying; disk 4.0 G) | | 13:19:44 | first boot | H3: `qm stop`, `--delete ide2`, `--boot order=scsi0` in its own `qm set` (verified `boot: order=scsi0`, ide2 lines 0), `qm start` | ## Step 3 — the first screen and the claim **13:21:10 — the hub lists an Unclaimed appliance (86 s after first power-on):** appliance 25, pairing code `***-***` (redacted), MAC `bc:24:11:0c:23:cc`, "Standard PC (Q35 + ICH9, 2009)", 7.7 GB, three SSH host keys. **The console (`s29`, code redacted), verbatim:** ``` Welcome to the Proxmox Virtual Environment. Please use your web browser to configure this server - connect to: https://192.168.0.134:8006/ drill0242 login: Felhom — a doboz készen áll, és a párosításra vár. Párosító kód: ***-*** Nyisd meg az e-mailben kapott linket, és add meg ezt a kódot és a jelszavadat. Ez a képernyő magától frissül — nincs teendő a doboznál, és nyugodtan itt hagyhatod bekapcsolva. ``` Two things a stranger meets here: **an English line telling them to open the Proxmox admin page**, above the Felhom text; and **„a jelszavadat" ("your password")** for the secret the self-bind mail calls **„Tulajdonosi jelmondat"** (R-323 renamed the mail, not the console). **The bind (H1 consequence).** The volunteer's route is the self-bind link, which is in a mailbox this harness cannot read. The operator's documented fallback was used: `POST /appliances/25/bind` (`customer_id=drill0242`) at **13:21:43** → 303 `flash=appliance_bound`. **The self-bind page itself was therefore NOT exercised.** Hub log (CEST = UTC+2): ``` 15:21:44 appliance 25 BOUND to customer drill0242 (mode=appliance) — delivery staged for its next poll 15:21:45 appliance credentials DELIVERED once to appliance 25 (… passphrase withheld) 15:21:52 [claim] claim code (gen 1) emailed to the registered address of drill0242 15:22:21 host enrolled: drill0242-3f4b42 (customer drill0242) 15:22:21 vaulted break-glass recovery credential for host drill0242-3f4b42 (user=root@pam, secret 32 chars) 15:22:22 Artifact manifest served for customer drill0242 (agent=0.130.0 golden=0.242.0) 15:22:38 wg registered: host=drill0242-3f4b42 … ip=10.77.0.5/32 changed=true gen=1 sync=ok 15:22:39 DR-recipe host-half stored for customer drill0242 (host drill0242-3f4b42, v1) 15:23:58 Event from drill0242: controller_started (info) — Controller elindult (0.242.0) 15:23:58 managed floor SERVED for drill0242: floor 0.242.0, agent requirement "0.129.0" from manifest (golden 0.242.0) ``` **Note on ep0:** `wg registered … sync=ok` is a WireGuard peer written on ep0 by enrolment itself, with the DR tier OFF. The brief fenced ep0; the product touches it on every enrolment. Teardown owes its removal. ## Step 4 — "ready", and the version | | vouched | landed | |---|---|---| | agent | 0.130.0 | **0.130.0** (`felhom-agent --version`) | | controller | golden 0.242.0 | **0.242.0** (`docker ps`: `felhom-controller:0.242.0 … (healthy)`) | **No self-update happened because none was needed** — golden == floor. Power-on → controller running **4 m 14 s**; bind → controller **2 m 15 s**. Infra: `traefik:v3.6.7`, `gtstef/filebrowser:1.3.3-stable`. First local whole-guest backup ran unaided 15:28:53–15:29:23 CEST, OK (seen as a `backup` lock). **The console still shows the pairing banner and the stale code after bind** (`s30`, 13:24:38) — R-214, still open. ## Intervention I1 — the dashboard has no address (R-494, filed 13:2x BEFORE acting) The claim mail says `https://felhom.drill0242.felhom.eu`. Measured: ``` felhom.drill0242.felhom.eu @1.1.1.1 A: (none) AAAA: (none) control felhom.enkisfelhom.hu @1.1.1.1 A: 104.21.3.175 172.67.130.252 https://felhom.drill0242.felhom.eu curl rc 6 (cannot resolve) @192.168.0.1 (router) (none) @192.168.0.250 (installer-offered DNS) (none) @192.168.0.134 (the box) 13:27:39Z (none) — google.com resolves: the resolver is alive agent 15:27:44 CEST lanresolver: applied split-horizon record vmid=9201 domain=drill0242.felhom.eu ip=192.168.0.158 @192.168.0.134 (the box) 13:29:18Z 192.168.0.158 ``` The hub has no tunnel/DNS creation code; `cf_tunnel_token` is an optional pasted field. **Act:** the dashboard is reached at the guest LAN address with the name forced (`--resolve felhom.drill0242.felhom.eu:443:192.168.0.158`), from demo-hp — which is where a browser on the household LAN would be. `GET /` → 302 `/claim`; the page, verbatim: > **A szerver beállítása** — Drill 0.242 first hour — „Add meg az e-mailben kapott beállító kódot, > majd válassz saját jelszót a vezérlőpult védelméhez." · Beállító kód (`szó-szó-szó`) · Új jelszó > (min. 12 karakter) · Új jelszó megerősítése · **Beállítás és belépés** · „Nem kaptad meg a kódot? > **Új kód kérése**" ## H1 — the setup code (harness substitution, not a volunteer act) The code is in the mail. Observe access to the appliance: `POST /hosts/drill0242-3f4b42/reveal-recovery-credential` → 200 (the designed operator path; it emits an audit event), stored `0600`, never printed. Then `pct exec 9201 -- docker exec felhom-controller … --print-reset-code`. **Harness slip, recorded:** the first mint at 13:29:28 (**generation 2**) was captured with a pattern `[a-z-]*` that does not match accented letters, and the raw output was shredded before the capture was checked — **that code was lost unseen**. Re-minted (generation 3) with a byte-agnostic capture. Both mints supersede the mailed generation-1 code. ## Step 3 (cont.) — the claim, through the front door All dashboard requests from here are made **from demo-hp** (the household LAN) to the guest's traefik on `192.168.0.158:443` with the dashboard name forced — intervention I1, once, for the whole walk. Secrets travel on stdin to curl, never on a command line; the dashboard password is a generated 24-character value in a `0600` file on DooPlex. | UTC | request | result | |---|---|---| | 13:31:19 | `GET /claim` | 200; `felhom_claim_csrf` cookie (82 B) + form token (64 hex) | | 13:31:20 | `POST /claim` `_csrf, code, new_password, confirm_password` | **302 → `/`**, `Set-Cookie: felhom_session` | | 13:31:2x | `GET /launcher` | **200** — „Indítópult — drill0242.felhom.eu", one tile **Filebrowser**, „Indítópult megosztása … A megosztás jelenleg ki van kapcsolva." Menu: Vezérlőpult · Alkalmazások · Tárhely (Meghajtók, Hálózati tárhely) · Biztonsági mentés (Áttekintés, Távoli mentés, Alkalmazások, Visszaállítás) · Megosztás · Rendszermonitor · **Debug** · Beállítások · version `0.242.0` | **Power-on → claimed dashboard: 27 m 37 s** (13:03:43 → 13:31:20), of which the harness's own keyboard driving is most of the install phase. ## Step 5 — the app list `GET /stacks` → 200, „Alkalmazások — drill0242.felhom.eu", filters Mind / Futó / Leállítva / Telepíthető, **52** „Telepítés" links. `GET /stacks/bookstack/deploy` and `/stacks/privatebin/deploy` → 200. Both pages, verbatim in the parts a stranger reads: > **BookStack — Telepítés** · „Egyszerű, könyv-szerű wiki és dokumentáció platform" · ~150M · Pi > kompatibilis · Memória 1058 MB / 3712 MB (28%) · **„Hol lesznek az adatok: ennél az alkalmazásnál > nincs külön adatmeghajtó-választás, ezért az adatai a rendszermeghajtóra kerülnek (/mnt/sys_drive). > A mentései így is elkészülnek."** · „Automatikusan generált értékek … Jegyezze fel a szükséges > jelszavakat!" (Alkalmazás kulcs, Adatbázis jelszó — Megjelenítés) · Aldomain `wiki` · „Az aldomain > telepítés után nem módosítható…" > **PrivateBin — Telepítés** · „Titkosított jegyzet és szöveg megosztás" · ~30M · Memória 1089 MB / > 3712 MB (29%) · same data sentence · Aldomain `paste` Observation, not yet a row: the drive page's „Meglévő meghajtó csatolása" candidate list (`GET /api/disks/candidates`) offers **the guest's own system volume** `/dev/mapper/pve-vm--9201--disk--1` (`mount_source /mnt/sys_drive`, `already_mounted: true`, `data_bearing: true`) beside the two blank 50 G disks. ## Step 5 (cont.) — deploy Both through the deploy page's own call: every named input of `#deploy-form` → `POST /api/stacks//deploy {"values":{…}}`, then `GET /api/stacks/` every 5 s (the page polls every 3 s). Generated secrets travelled on stdin and were shredded. | app | fields sent | POST | state transitions | running after | |---|---|---|---|---| | bookstack | `APP_KEY, DB_PASSWORD, SUBDOMAIN=wiki` | 13:34:38 → **202** „Telepítés elindítva – az állapot a kártyán követhető" | +6 s `deploying` · **+26 s `degraded`** · +42 s `starting` · +68 s `running` | **68 s** | | privatebin | `SUBDOMAIN=paste` | 13:35:47 → **202** | +5 s `starting` · +21 s `running` | **21 s** | Observation: the deploy page's poll handles `deploying / running / starting / unhealthy / exited / stopped` and has **no `degraded` branch** (`deploy.html` ~1040–1095), so for those 16 s the page keeps showing its previous step. Harmless here; recorded. **App pages** (`/apps/bookstack`, `/apps/privatebin`): „Fut" · **„Naprakész"** · „Megnyitás ↗". BookStack's „Első lépések": „Nyisd meg a **wiki.DOMAIN** címet a böngészőben · Jelentkezz be: admin@admin.com / password · Változtasd meg azonnal az admin jelszót és email címet …" and a box „Alapértelmezett belépés admin@admin.com / password — Az első bejelentkezés után azonnal változtasd meg!". PrivateBin: „Nyisd meg a **paste.DOMAIN** címet …" → **R-498** (52 of 53 templates). ## Step 6 — using both, through their front doors Front doors from demo-hp: `wiki.drill0242.felhom.eu` → 302 `/login`; `paste.drill0242.felhom.eu` → 200, 22 896 B, PrivateBin UI (English). **BookStack** (over HTTPS, so its `secure` cookies work — R-460's 419 was a plain-HTTP effect): | UTC | act | result | |---|---|---| | 13:37:18 | log in `admin@admin.com / password` (the page's own instruction) | 302 → `/`; logged-in markers 3 | | 13:37:19 | change password via `POST /users/1` | **405 — harness wrong route** (BookStack ≥23 is `/settings/users/1`) | | 13:37:52 | change admin e-mail + password via `POST /settings/users/1` `_method=PUT` | 302 → `/settings/users`; **old default login now → `/login` (refused); new → `/` (accepted)** | | 13:38:44 | create book „Családi receptek DRILL0242" | 302 → `/books/csaladi-receptek-drill0242` | | 13:38:4x | save a draft with `_method=PUT` | **405 — harness** (the route is POST) | | 13:39:35 | new draft 3 → `POST` name „Töltött káposzta", body `DRILL0242-OLDAL Töltött káposzta: árvíztűrő tükörfúrógép 2026-09-14` | 302 → `/books/csaladi-receptek-drill0242/page/toltott-kaposzta` | | 13:39:3x | upload attachment `nagymama-recept.bin`, 262 144 B random, sha256 `2e252913844f8931…` | 200, attachment id 1 | | 13:39:3x | read back | page 200, sentence present **2** (negative control **0**); attachment listed 1; `GET /attachments/1` 200 262 144 B, **sha256 equal: YES** | **PrivateBin** — the browser's own v2 format (AES-256-GCM, PBKDF2-SHA256 100 000, zlib), key only in the fragment, as a browser does: | UTC | act | result | |---|---|---| | 13:38:08 | `POST /` a paste „DRILL-0242 privatebin sentinel … — árvíztűrő tükörfúrógép", expire 1 week | 200 `{"status":0,"id":"3963448c37a87005",…}` | | 13:38:08 | `GET /?pasteid=3963448c37a87005`, decrypt | 200, 471 B, **decrypted == sent: True** (sha256 `5920d11290cca367…`); **wrong key → `InvalidTag`** | ## Step 7 — the backups pages, read as a first-timer (13:38:55) `/backups` verbatim, in order: „Csak egy másolat készül (nincs második meghajtó) — a 3-2-1 mentéshez csatlakoztasson egy második meghajtót vagy offsite tárolót." · „A rendszermentés jelenleg ugyanazon a lemezen van, mint a rendszer — így hibás fájlok ellen véd, lemezhiba ellen nem. …" · Rendszer (/) 1.96 GB / 68.7 GB (3%) · DB mentések – · **Utolsó teljes mentés 2026-09-14 15:28 (10 perce) 624.3 MB Helyi tároló (local) Naprakész** · **„Következő mentés — 0 órája — a mentési ablakon belül"** · Visszaállítás ellenőrizve „Még nem futott" · Mentés most · window 02:30 / 03:30 / 04:15 / 04:30–08:30 · Távoli rendszermentés „nincs beállítva". - **Honest:** both warnings; the whole-box backup's date and target. - **Confusing:** „Következő mentés" (next backup) with „0 órája" ("0 hours ago") under it — the value is the age of the LAST backup (`backups.html:101-102`, `.AgeHours`). `/backups/apps`: „Utolsó adatbázis mentés: Még nem futott"; BookStack „Konfig + DB + Adatok" and PrivateBin „Konfig + Adatok", each „1. mentés Auto helyi **Utolsó: most**" · „Nincs 2. (off-drive) másolat" · „3. mentés Nincs beállítva". **„Utolsó: most" was checked, not assumed:** `timeAgoStr` renders „most" only for a parseable time under a minute old (an empty value renders nothing, `funcmap.go:283-296`), and `GET /api/backup/snapshots?stack=…` returned for both apps `{"time":"2026-09-14T13:38:53Z","short_id":"helyi","tier":1,"drive_label":"Belső SSD (rendszer)"}` — 3 s before the page was read. **True.** (That unit predates the BookStack content of 13:39:35.) `/stacks/bookstack/backup` → **R-499**: „… már szerepelnek a teljes rendszermentésben (PBS) … ehhez az alkalmazáshoz nincs külön teendő." — this box has no PBS. `/backups/restore`: app select BookStack / PrivateBin, „Pillanatkép: — Válasszon alkalmazást —", „Még nincs mentés felhasználói adattal." ## Step 8 — „Mentés most" (the app-backups page's button, `POST /api/backup/run`) | UTC | observable | |---|---| | 13:41:21 | pressed → `{"ok":true,"message":"Mentés elindítva"}` | | +5 s | `{"enabled":true,"running":true}` | | +21 s | `{"db_dump":{"count":1,"duration":"15.752362463s","last_run":"2026-09-14T13:41:37.325510764Z","success":true},"enabled":true,"running":false}` | | 13:41:42 | both apps' restore points: `{"time":"2026-09-14T13:41:37Z","short_id":"helyi","tier":1,"drive_label":"Belső SSD (rendszer)"}` — **one entry each; the 13:38:53 unit is gone** (one local point kept) | | 13:41:4x | `/backups/apps`: „Utolsó adatbázis mentés: **2026-09-14 15:41 (most)**"; Adatbázisok: `bookstack · MariaDB · 59.0 KB · 15:41 · 41 tábla · OK` | **The date moved, to the true time, on both pages (R-476 class: holds).** 21 s end to end. ## Step 9 — version labels and Update Both app pages: „Fut · **Naprakész**". `GET /api/stacks/` at 13:42:28: ``` bookstack template_images {bookstack: lscr.io/linuxserver/bookstack:26.05.2, bookstack-db: mariadb:12.3} catalog_images {bookstack: lscr.io/linuxserver/bookstack:26.05.2, bookstack-db: mariadb:12.3} privatebin template_images {privatebin: privatebin/pdo:2.0.5} catalog_images {privatebin: privatebin/pdo:2.0.5} ``` **The catalog offers no newer version of either app, so there was nothing to press. Skipped, per the brief.** „Naprakész" is true on this evidence (installed == catalog). ## Step 10 — removal: what the dialog shows for PrivateBin The app page's delete opens `removeStack()` (`layout.html:396`), which loads `/api/stacks/privatebin/hdd-data` → `{"hdd_paths":null,"has_hdd_data":false}` and `/backup-data` → `[{"path":"/mnt/sys_drive/felhom-data/backups/primary/privatebin","size_human":"40K","exists":true}]`. Rendered text, in order: > **Alkalmazás eltávolítása: privatebin** · „Az alkalmazás visszaáll "Nincs telepítve" állapotba. A > sablon megmarad, újratelepíthető." · „Ez a művelet nem visszavonható!" · **Mindig törlődik:** Docker > kötetek (adatbázis, alkalmazás konfiguráció) · Telepítési konfiguráció (app.yaml) · Másodlagos mentés > ütemezése · **Mentési adatok:** /mnt/sys_drive/felhom-data/backups/primary/privatebin (40K) · > ☐ **Mentési adatok törlése** · „Az éjszakai restic pillanatképek nem törölhetők egyenként — a > megőrzési szabályok szerint automatikusan elavulnak." · Mégsem · Eltávolítás **No „delete my data" box appears** — PrivateBin has no drive data; its data is in a Docker volume, which the dialog says is **always** deleted. „Delete my data too" was therefore performed as **every delete box ticked**: `remove_backups: true` (the only box), `remove_hdd_data: false` (no box shown). Observation: the sentence about „éjszakai restic pillanatképek" appears on a box with no restic configured at all. ## Step 11 prep — the household loses a recipe (13:43:07) Through BookStack's own delete: `GET …/page/toltott-kaposzta/delete` → token; `POST …/page/toltott-kaposzta` `_method=DELETE` → 302 to the book. Then: page **404**, `/attachments/1` **404**, the book page lists the title **0** times. The last backup containing both is the 13:41:37 unit (step 8). ## Step 11 — restore BookStack from its backup, through the restore page The page's own submit: `POST /backup/restore` `_csrf` (from the page meta), `stack_name=bookstack`, `snapshot_id=helyi` (the only point, 13:41:37 — step 8). | UTC | observable | |---|---| | 13:44:13 | **302** → `/backups/restore?flash=Visszaállítás elindult — az állapot itt frissül.` | | +6 s | app `stopped` | | +11 s | `starting` | | +26 s | `running` (probe pending) | | +32 s (13:44:45) | `running`, `health_probe.healthy: true` | | 13:45:09 | **read back through the front door** — login with the post-change password → `/`; page `…/page/toltott-kaposzta` **200**; sentence present **2** (negative control **0**); `árvíztűrő tükörfúrógép` present **2**; `/attachments/1` **200, 262 144 B, sha256 equal to the pre-backup upload: YES** | **DATA: PASS.** The recipe the household deleted at 13:43:07 is back, with its attachment byte-identical and its Hungarian text intact, 32 s after pressing restore. Reading the page at 13:45:11, after completion: the one-shot flash is gone and the page shows the empty form again („Pillanatkép: — Válasszon alkalmazást — · Még nincs mentés felhasználói adattal."). **CORRECTED 13:46 — the first reading here was wrong.** The page's script polls `/api/backup/restore-status`, which at 13:46:03 returned `"last":{"op":"restore","stack":"bookstack","ok":true, "message":"A(z) bookstack: 2 adatkötet és az adatbázis visszaállítva — az alkalmazás újraindult.", "finished_at":"2026-09-14T13:44:39Z"},"last_recent":true`. So a Hungarian finish message exists and names what came back; **whether the page shows it is script-rendered and was not observed** (no browser here). The server-rendered HTML alone shows only the empty form. ## Step 10 — removal, done the way the screens lead **Harness shortcut, withdrawn as a finding.** At 13:43:36 the harness called `POST /api/stacks/privatebin/remove` on a **running** app and got **409** `stack "privatebin" is still running — stop it first before removing` (English). The screens never offer that: for an operational app `stacks.html:99-102` shows **Frissítés · Újraindítás · Leállítás** and no „Eltávolítás"; the app page shows none either. Nothing changed on the box (before/after identical, `step10-remove-observe.txt`). | UTC | act | result | |---|---|---| | 13:45:26 | „Leállítás" → `POST /api/stacks/privatebin/stop` | `{"ok":true,"message":"Stack privatebin stop completed"}` (English message; the UI shows its own text) · `stopped` at +6 s | | 13:45:32 | observe before | volume `privatebin_privatebin_data` ×1 · containers 0 · `/opt/docker/stacks/privatebin/.felhom.yml` · backup dir **40K** | | 13:45:33 | „Eltávolítás" with every box ticked → `POST …/remove {"remove_hdd_data":false,"remove_backups":true}` | **200** `{"removed":"privatebin","volumes_removed":["privatebin_privatebin_data"],"hdd_paths_removed":[],"hdd_paths_preserved":[],"backup_paths_removed":["/mnt/sys_drive/felhom-data/backups/primary/privatebin (40K)"]}` | | 13:45:39 | observe after | volumes **0** · containers **0** · backup dir **No such file or directory** · only the catalog template `.felhom.yml` remains (the dialog: „A sablon megmarad") | | 13:45:4x | front door / API | `paste.` → **404** · restore points `[]` · state `not_deployed`, `deployed=false` | **PASS — the drive is clean of the app's data and its backups, and the response names each thing it removed.** (`volumes_removed` is populated here; R-489 recorded `null` over removed volumes on demo-hp — this box did not reproduce it.) ## Step 12 — status pages, read as a customer would a week later (13:46) - `/launcher`: tiles **BookStack**, **Filebrowser** (PrivateBin gone — correct). - `/dashboard`: „3 Futó alkalmazás · 0 Leállítva · 55 Összes alkalmazás" · Memória 1.0 GB / 4.0 GB · Rendszer (/) 2.11 GB / 68.7 GB · **Lemezek állapota: QEMU QEMU HARDDISK „Nincs adat" „0 °C"** · **„Utolsó mentés: 2026-09-14 13:41"** while `/backups/apps` says **15:41** for the same run → **R-500** (`dashboard.html:154` formats without the box zone). - `/monitoring`: „Hub kapcsolat — Kapcsolódva … Utolsó sikeres jelentés: most"; graphs „Még nincsenek adatok…". The banner „A gazdagép metrikái jelenleg nem elérhetők" appeared in my text extraction, **but it is `display:none` by default and shown only by script** (`monitoring.html:15`) — **not evidence; withdrawn.** Whether the host card fills in was not observable without a browser. - Observation, not filed: „0 °C" beside „Nincs adat" on a virtual disk. ## Evidence off the machine, end of Phase 1 (13:46) `box-logs-phase1/`: `controller.log` (526 lines), `agent-journal.log` (2461), `bootstrap-journal.log` (173, pairing code **redacted**), `box-state.txt`. Secret sweep over the copies for the claim code, passphrase, dashboard password, BookStack password, break-glass credential and installer root password: **0 each**; control (passphrase planted in a throwaway copy) **1**. ## Accident A1 — power cut (`A1-power-cut.txt`) | UTC | observable | |---|---| | 13:47:31 | before: `felhom-controller:0.242.0` · `bookstack:26.05.2` · `mariadb:12.3` · `filebrowser:1.3.3-stable` · `traefik:v3.6.7`, all healthy · agent 0.130.0 | | 13:47:33 | `qm stop 330` (hard) | | 13:48:36 | `qm start 330` (60 s dark) | | +37 s | appliance answers SSH | | +2 m 07 s (13:50:43) | hub: `Event from drill0242: controller_started (info) — Controller elindult (0.242.0)` | | +2 m 9 s | dashboard `/api/health` 200 | | 13:58:11 | the dashboard session did not survive: `/api/stacks/bookstack` → **401 `authentication required`** (the customer logs in again; the harness loop misread this as unparseable for 8 min — harness fault, stopped) | | 13:58:33 | after re-login: bookstack **running, healthy**, template images **unchanged**; every container `Up 7 minutes (healthy)`, **same five image tags**; agent 0.130.0 | | 13:58:36 | BookStack front door: page 200, sentence **2** (control 0), Hungarian **2**, attachment **262 144 B, sha256 equal: YES** | Hub log across the cut (non-routine lines only): `DR-recipe host-half stored` 15:49:04 CEST, `controller_started` 15:50:43, `DR-recipe app-half stored` 15:50:44. **No alarm, no warning, no customer mail event.** **A1: PASS — every app back on the same version (Slice 3 holds), data intact, no false alarm, ≈2 min to a working dashboard. Cost to the household: one re-login.** ## Accident A2 — a typo in the code **Where it can be tried.** The setup code is single-use and this box is already claimed, so the first-claim screen cannot be re-entered. The login page's „Elfelejtett jelszó" opens the **same `/claim` form and the same code gate**, titled „Jelszó visszaállítása — Felhom · Add meg az e-mailben kapott beállító kódot, majd válassz új jelszót." A2 was run there, on the same box. **The pairing-code typo on the hub's self-bind page was NOT exercised** (H1: no link). Code: generation 4, minted by H1 at 13:59:30. The typo is the real code with its last letter changed — exactly one character different, same length (checked, never printed). Limiter, from source (`claim.go:32-33`, `:170-200`): **5 failures → 15-minute lock, counted per source AND globally.** | UTC | attempt | result | |---|---|---| | 14:00:09 | **typo** | **200**, form re-rendered (not accepted) | | 14:00:10 | **the right code**, new 24-char password | **302 → `/`** — accepted after the typo | | 14:00:10 | log in with the new password / the old one | **302** / **200** (old refused) | | 14:00:10–12 | four more wrong codes (the spent code) | 200 each | | 14:00:12 | the 5th failure | box: `[WARN] [web] claim: code lockout tripped (source 192.168.0.104) — 15 min` · `Event pushed: claim_lockout (warning) — Túl sok hibás beállító kód — a beállító oldal 15 percre zárolva` | | 14:00:12 | a 6th attempt, during the lock | 200 | | 14:00:16 | hub | `Event from drill0242: claim_lockout (warning) …` · **`Operator email sent for drill0242/claim_lockout`** | **Harness note:** the text filter used on these replies missed the refusal wording, so the on-screen messages are taken from the reply bodies below, and the plain „Hibás vagy lejárt kód" reading is repeated after the lock expires. | 14:15:26 | a wrong code **after** the 15-minute window | **200**, error box **„Hibás vagy lejárt kód"** — the form accepts attempts again | On-screen wording, read from the reply bodies: during the lock **„Túl sok próbálkozás — próbáld újra 15 perc múlva."**; after it **„Hibás vagy lejárt kód"**. Both Hungarian, both true. **A2: PASS** — a one-letter typo is refused without harm, the right code is accepted right after it, the lock is honest about its length and lifts on time. Two properties recorded, not filed: the lock is **global** as well as per source (`claim.go:188-200`) — five wrong guesses from anywhere lock the real household out for 15 minutes, by design against guessing; and the lockout e-mail went to the **operator only** (`Operator email sent for drill0242/claim_lockout`), while the screen tells the customer. ## Teardown — three layers (`teardown-before.txt`, `teardown-layer1.txt`, `teardown-layer2.txt`, `teardown-layer3-hub.txt`) Evidence was copied off the box **before** the destroy (`box-logs-final/`, 14:16, secret sweep 0). | layer | UTC | result | |---|---|---| | **1. machine** | 14:16:23 | `qm destroy 330 --purge` → `qm list` empty; `/mnt/hdd_1/images/330` gone; `images/9202` untouched | | **2. host** | 14:16:27 | ISO removed (0 left); `/root/drill0242` shredded, removed; `nvme-scratch` used **19 059 372 → 10 130 532 KiB**; `local` **24 768 200 → 23 013 832 KiB**; `local-lvm` 44.17 % before and after; 9201 and 9202 running; `vmbr9` pre-existed | | **3. hub** | 14:35:15 | host stale at 14:34:45 (true `host_stale` operator mail); **customer DELETE cascade COMPLETE** (journal #17); customer and host pages 404; lists 0 (control 1) | | 3b. ep0 peer | 14:40:00 | `10.77.0.5/32` removed at the 14:39:30 periodic push (5 → 4 peers); 4 m 14 s after the delete; control peer present | **Disposition of `drill0242`: DELETED.** The hub's event stream and the three operator mails (`claim_lockout`, `host_stale`, and the bind/enrol log lines) are append-only and stay, by design.