Files
felhom.eu/REPORT.md
T

7.2 KiB

REPORT — TASK-F: app lifecycle + HP USB write + R-55 live leg (2026-07-21)

Baselines at start (verified, all clean): controller a96226a v0.157.0, app-catalog 857ba53, felhom.eu 0b2c598, agent eba040d v0.93.0 (source only — untouched this task).

Part Status
1 — app lifecycle states DONE + LIVE-PROVEN — controller v0.158.0, plus v0.158.1 fixing a defect I shipped
2 — HP USB write DONE — byte-proven
3 — R-55 live leg DONE — PROVEN LIVE
4 — resolvability table SKIPPED by operator ruling (no Docker Hub login)

Part 2 — the HP boot stick

Written on felhom-pve under a format-operation evidence bar. Two USB-shaped devices were present, and that turned out to matter:

  • /dev/sdb — ADATA HD710 PRO, 932G. EXCLUDED. It is the live customer data drive, mounted at /mnt/hdd_1 and bind-mounted into guest 9201 (mp8) — immich and calibre-web data. Its by-id path is usb-ADATA_…, but udevadm reports ID_BUS=ata and sysfs removable=0, so the two required positive checks exclude it on their own. A bare-/dev/sdX workflow would have offered sdb and sdc side by side with nothing but a letter between them.
  • /dev/sdc — Samsung Flash Drive, serial 0375123010001586, 119.5 GiB. The target.

Checks, all passed: ID_BUS=usb; removable=1; absent from findmnt (device and every partition); absent from /etc/fstab; not an LVM PV (only /dev/sda3); no ZFS pools exist on the host; not referenced by any guest config; 0 open handles. Destroyed content: an old iso9660 "PVE" install image. Operator confirmed the printed device before any byte was written.

The identity was re-asserted at write time, not only at survey time, in case of re-enumeration between the confirm and the write.

1705338880 bytes (1.7 GB, 1.6 GiB) copied, 55.9 s, 30.5 MB/s
device sha256: 2b51d74c0eaf5155fd42a137dff803b9ab8692294cedcb727c9a80658c9f7823
iso    sha256: 2b51d74c0eaf5155fd42a137dff803b9ab8692294cedcb727c9a80658c9f7823
RESULT: MATCH - byte-proven

dd … oflag=direct conv=fsync, then the ISO's exact byte-length read back from the device and hashed — byte-proof, not exit-code-proof. The source ISO's sha256 was verified after transfer to felhom-pve and matches the published hash. The customer data drive was re-checked after the write and is still mounted at both its paths.

Boot cheat-sheet (unchanged, restated)

  1. Boot the stick with Secure Boot ON. This is the shim variant. Expect the Felhom boot screen, then the installer aborting having touched no disk — that is success.
  2. On a GRUB failure (relocation, "no bootable device", a hang before the boot screen): use the mkimage ISO and set Secure Boot OFF. It is unsigned; SB-enforcing firmware refuses it. That ISO is at 180:/mnt/5_hdd/felhom.eu/felhom-iso/out/…-hp-generic-mkimage.iso and is not on this stick.
  3. Both artifacts are inert (match-nothing disk filter). The real install ISO does not exist yet — it needs the HP's internal SSD serial, which needs the board in hand. Send me the serial and which loader worked, and I will build it.
  4. The first boot is yours. No remote install step was attempted.

Part 1 — app lifecycle (summary; detail in felhom-controller/REPORT.md)

available / hidden / abandoned as a .felhom.yml field, because the retired/ directory move shipped hours earlier would have orphaned every customer running a withdrawn app. Deploy gate is server-side and fail-closed; unknown values fail open; orphan detection never sees the field.

Live on 9201 through the real endpoints: plant-it renders 0 times on the catalog page (control app 10), a direct deploy POST returns 409 „Ez az alkalmazás jelenleg nem telepíthető.", the app page carries the permanent notice with no Telepítés button.

v0.158.0 shipped a defect that I caught during live validation, not from the suite: the lifecycle predicates had pointer receivers, and html/template cannot call those on the non-addressable value the handler passes — so every /apps/<slug> returned 500, for every app, while compiling cleanly with a fully green test suite. Fixed in v0.158.1. The lesson is recorded in the controller report and the R-57 row: a template method call is only checked when that template actually runs, so a render test is the only coverage for template-facing methods.

Part 3 — R-55 live leg, PROVEN

immich (drive-backed) stopped through the real UI endpointcompose down → 0 containers; calibre-web (drive-backed) and bookstack (not drive-backed) left running; pct reboot 9201.

14:29:28 [gate] … live bind confirmed — recreating drive-backed app calibre-web … onto /mnt/felhom-drives/hdd_1 14:29:29 [gate] … 1 drive-backed app(s) left stopped — zero containers means the customer stopped them on purpose

immich came back STOPPED. Hours earlier the identical fixture brought it back running — same experiment, opposite result. calibre-web recreated, bookstack back, [bootrecon] no boot-orphaned apps, zero alerts, reboot → steady state in ~15 s. The left stopped line fired in production for the first time, so the honoured path is observable rather than silent.

Part 4 — skipped

Operator ruled no Docker Hub login. The gate therefore still exits 2 (INCONCLUSIVE) on a full sweep rather than producing anonymous throttled accusations — which is the designed behaviour, not a failure. One docker login on DooPlex plus python3 scripts/check-image-resolvable.py closes it whenever convenient.

Remaining

  • Agent v0.93.0 is source-only — the recovery-code wordlist fix is committed but unbuilt and unpublished; it rides the next agent train. Nothing on any host runs it yet.
  • R-17 — deleting the u629193-sub1 archive is an operator console action; no tooling verb targets it. Exact steps are in the previous session's report.
  • HP first boot is physically yours (above).
  • The full catalog resolvability table is still owed (Part 4).
  • wanderer was re-pinned last session to a 3-service upstream shape and has never been deployed.
  • hidden lifecycle state has no catalog user yet; only abandoned is exercised end to end.

Observations

  1. I pushed a commit that does not build. A git stash/stash pop used to compare reuse_refs_check.py output silently discarded the staged index, so the first lifecycle commit carried only the new test files. Repaired with a follow-up commit rather than a force-push — rewriting already-pushed history is the worse trade. Never git stash mid-staging.
  2. The stale-credential trap bit again, and the memory note already described it. The controller password in ~/.config/credentials is wrapped in single quotes; I stripped only double quotes, sent the quotes as part of the password, and concluded the credential was stale. It was not. Strip both quote styles.
  3. Two USB devices, one of them live customer data, is the case the ID_BUS=usb + removable=1 rules are actually for — the dangerous device carried a usb-… by-id name and would have looked plausible in any listing that showed model and size alone.