README + REUSE: the PostgreSQL conversion (v0.273.0)
gates / gates (push) Successful in 25s

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
2026-09-25 13:30:08 +02:00
parent 1c5dd07604
commit 7162f489c9
2 changed files with 20 additions and 1 deletions
+1
View File
@@ -303,6 +303,7 @@
| `offboxCeremonyWaitState` + `escrowCeremonyGraceWindow` | controller/internal/web/handlers.go | pure pick: (awaiting, timedOut) from `OffboxTarget.{EscrowState,CeremonyCompletedAt}` — the v0.138.0 "megerősítésre vár" card. Stamp SET on claim (escrow_handlers.go), CLEARED on the flip (main.go Flip + offbox_handlers.go manual confirm) | escrow_wait_state_test.go truth table (escrowed/unstamped/unparseable → plain CTA; boundary via `>=`) |
| `Manager.sambaUpFn` / `sambaPasswdFn` / `sambaRunFn` / `sambaAddrFn` (func seams) | controller/internal/stacks/manager.go (fields) + samba.go | nil → `composeUp` / `docker exec smbpasswd` (STDIN) / `containerRunning("felhom-samba")` / `docker exec felhom-samba ip -4 -o addr show eth0` | injected in controller/internal/stacks/samba_test.go — the idempotency test asserts the up-seam is called **zero** times when config is unchanged; the passwd seam means no unit test ever handles a real secret or touches docker. **`sambaRunFn` has an EXPORTED setter (`SetSambaRunProbe`)** — internal/web's status-contract tests need a live-container world from another package. `sambaAddrFn` backs `SambaLANAddress()` (v0.151.0); its parse is separately pinned in samba_lanaddr_test.go and it returns "" on any failure — the page omits a line rather than printing a wrong address |
| `volumeCopier` + `Manager.undoCopier` / `updateUndoHealthFn` (v0.263.0) | controller/internal/stacks/undo.go | nil → `dockerVolumeCopier` (alpine helper: `cp -a` named volume → `<vol>.pre-update-<stamp>`, finished-marker LAST; restore re-checks the marker in the same shell) / nil → `waitUpdateHealthyMeta` with the OLD `.felhom.yml` | `fakeCopier` in controller/internal/stacks/undo_test.go — volume CONTENT as strings, so "the data came back" is a compare; a cut-off copy is a copy without its marker. **Judge a copy by the helper's own exit + the marker, never by the client** (killing `docker run` leaves the container copying — measured) |
| `pgConverter` + `Manager.pgConv` / `convertFreeFn` (v0.273.0) | controller/internal/stacks/pgconvert.go | nil → `dockerPGConverter` (docker exec psql/pg_dumpall over 127.0.0.1 — the entrypoint's temporary init server listens on the socket only; `Empty` re-checks the undo copy's marker in the same helper) / nil → statfs of the stack dir | `fakePG` in controller/internal/stacks/pgconvert_test.go — works on `fakeCopier`'s volume strings. **Never convert without the ladder's `engine_conversion` mark** (`planEngineConversion` refuses a PostgreSQL major move without it); `isPostgresImage` must match `appbackup.dbTypeForImage` (pinned by a source-reading test) |
| `Manager.SambaLANAddress()` | controller/internal/stacks/samba.go | `() string` — the guest's LAN IPv4 for the Megosztás connect card (v0.151.0, S-2) | Read from the SAMBA container's netns (`network_mode: host`), never `net.InterfaceAddrs()` — the controller is on a docker BRIDGE and would answer 172.x (the same trap `setup.DetectLocalIPs` needs `HOST_IP` for). **NEVER cache/persist it** — the guest holds it by DHCP (S-5); callers re-derive per render. `""` = omit the line |
| `Server.sambaAddrFn` (func seam) | controller/internal/web/server.go (field) + sharing_handlers.go `sambaLANAddress()` | nil → `stackMgr.SambaLANAddress()` | The web-side half of the connect card. Tests inject a COUNTED fn — the fresh-per-render assertion is what stops anyone memoizing a DHCP lease |
| `Manager.guestNetExecFn` (func seam) + `GuestGateway()` / `GuestNetSnapshot()` | controller/internal/stacks/manager.go (field) + guestnet.go | nil → `docker exec felhom-samba <args>` — ONE seam for all R-66 guest-netns reads (route/link/addr/resolv.conf); tests script canned outputs per argv | guestnet_test.go. **The netns door rule:** the controller's OWN netns is the docker bridge, so any in-process read (`net.Interfaces`, `/proc/net/route`, its own `/etc/resolv.conf` = 127.0.0.11) is the S-2 wrong answer — guest-net reads MUST go through the samba (`network_mode: host`) exec door. Megosztás off ⇒ door closed ⇒ "" / per-item error strings; NEVER substitute an in-process value. Same S-5 law as SambaLANAddress: live per render, never cached/persisted. Parsers (`parseDefaultRoute`, `parseGuestInterfaces`, `parseResolvConf`) are pure + separately pinned |
+19 -1
View File
@@ -577,7 +577,7 @@ job, and answers **202**. The page polls `GET /api/stacks/{name}`.
| field | meaning |
|---|---|
| `updating` | a guarded update is in progress |
| `update_phase` / `update_phase_label` | `checking`, `backing-up`, `safety-dump`, `pinning`, `pulling`, `copying` (v0.263.0), `starting`, `verifying`, `done`, `undoing` / `undone` (v0.263.0), `failed` — and the Hungarian label for each |
| `update_phase` / `update_phase_label` | `checking`, `backing-up`, `safety-dump`, `pinning`, `pulling`, `copying` (v0.263.0), `converting` (v0.273.0, a PostgreSQL major step only), `starting`, `verifying`, `done`, `undoing` / `undone` (v0.263.0), `failed` — and the Hungarian label for each |
| `update_error` | the customer sentence when the update did not complete |
| `hold_reason` | the hold's sentence while the app is held (failed update OR failed restore) |
@@ -650,6 +650,24 @@ the old version back; a power cut while undoing resumes the undo. Reasoning and
`felhom.eu/documentation/architecture/09-update-architecture.md` §6.1a,
`felhom.eu/documentation/audits/undo-bakeoff-2026-09-23/`.
**PostgreSQL majors are converted by the box (v0.273.0, `09` §3 decisions 35–38, §6.4 part 10).** Only on a
step whose ladder entry carries the harness's mark `engine_conversion {service, engine: postgres, from, to}`.
After the undo copy, a new phase `converting` („Adatbázis átalakítása" / "Converting the database"): the OLD
database container alone → the check (per database owner/encoding/collation, every role, every extension, every
table's row count) → `pg_dumpall` into `<stackdir>/pre-update-convert/`, refused without its completion line → the
old engine stops → **the DB volume is emptied only after the copy's finished-marker is validated again** (and
inside the emptying helper) → the NEW engine alone on the empty volume (`up -d --no-deps <svc>`) → the
entrypoint's empty databases dropped, `CREATE ROLE` skipped for roles that exist → the load with `ON_ERROR_STOP`
→ the check again, plus `$PGDATA/PG_VERSION` = the mark's `to`. Any failure, and a controller restart during
`converting`, runs the existing undo. The space check (the dump's bound = the DB volume's size × 1.25, plus the
2 GB floor, beside the stack dir) and the mark check refuse before anything moves: „A(z) %s adatbázisának
átalakításához %s szabad hely kell, de csak %s van. Nem változott semmi." / „Ez a lépés a(z) %s adatbázisának fő
verzióját váltaná, de nincs róla próba. Nem változott semmi." — a PostgreSQL major move WITHOUT the mark is
refused by the preflight and by the job. After success the old datadir's copy is kept (`app.yaml`
`conversion_copy`) until a backup is proven after the conversion; the hourly `conversion-copy-release` job then
removes it, logged by name. PostgreSQL 18 mounts its volume at `/var/lib/postgresql` — the step's definition
carries that. Code: `internal/stacks/pgconvert.go`. Proof: `felhom.eu/documentation/audits/night-2026-09-26/`.
**Held apps say so (v0.265.0, R-625).** While a hold stands, the update badge reads „Megállítva —
visszaállítás szükséges" / "Stopped — restore needed" (`tag-error`, title = the hold sentence's first
sentence, in the reader's language) and no Update button is rendered; the API still answers 409 `held`. A