Files
felhom.eu/documentation/audits/evidence-drill-fresh-install-0242-2026-09-14/journal.md
T
admin 38848ffbeb
gates / gates (push) Successful in 21s
drill: a stranger's first hour on 0.242.0 — 1 intervention, not ready for a volunteer
Golden 0.242.0 baked, round-trip verified and vouched (cadence rule, R-468).
Fresh box from the public ISO on demo-hp: landed on the vouched set, two apps
deployed and used, backup, remove, byte-identical restore, power cut and code
typo all PASS. Stopped for a volunteer by R-493 (no instructions) and R-494
(the setup mail's dashboard link has no DNS; intervention I1). R-493..R-500
filed. Capability map: first-hour row added (PARTIAL), journey row scoped.
Stopgap Hungarian volunteer guide written. Hub teardown layer pending.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-14 16:17:47 +02:00

483 lines
32 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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/<app>/deploy {"values":{…}}`, then `GET /api/stacks/<app>` 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/<app>` 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.