diff --git a/REPORT-the-28-2026-09-22.md b/REPORT-the-28-2026-09-22.md new file mode 100644 index 00000000..a4c8c863 --- /dev/null +++ b/REPORT-the-28-2026-09-22.md @@ -0,0 +1,52 @@ +# REPORT — THE TWENTY-EIGHT, 2026-09-22 + +**The full record is `documentation/audits/DRILL-the-28-2026-09-22.md`.** The shared `REPORT.md` is +deliberately untouched (two sessions in this repo clobber it). + +## Not done, or changed from the brief + +1. **Interventions: SEVEN, over the brief's limit of five** — and six of the seven were my own + harness, not the product. Three driver bugs fixed mid-run, two deliberate method changes, one + deadlocked waiter. The seventh was the product's: three leftovers it could not clear. +2. **There is no `requires:` key in `.felhom.yml`.** The constraints live under `resources:` + (`needs_hdd`, `pi_compatible`), and 9202 met all of them — nothing was skipped for a resource + reason. +3. **The brief's file-leg list is wrong.** Read from `07` §6.2 as the brief itself instructs, only + four of the 28 are class A: `calibre-web`, `immich`, `komga`, `paperless-ngx`. `jellyfin`, `plex` + and `emby` are class B because their only bind is a `:ro` media mount. +4. **The brief's database list is incomplete** — `immich` also carries PostgreSQL and redis, and + `wanderer` carries meilisearch. +5. **`plant-it` cannot be installed at all, by design** (`lifecycle: abandoned`), refused by the + product's lifecycle gate — the only such template in the catalog, proven live for the first time. +6. **A REFUSED restore is recorded as its own verdict, not as a failure.** The first version of the + harness collapsed them and mislabelled `calibre-web`, where the product had done the right thing. +7. **Verified true by looking, not assumed:** the drill repo's Actions are off (**47 CI jobs before + the first push, 47 after, all night**); `repoint_drill.py` still works; 9202 had the capacity. + +## What ran + +All 28 walked: deploy at the live pin → seed through the app's own front door → read back → backup → +the guarded Update where a real within-a-major edge exists → **restore and read back again** → remove +and a 60-second check. Plus both side jobs. + +**26 of 28 deployed · 6 proven · 5 inconclusive · 14 no upstream edge · 1 failed honestly · +2 could not deploy · 21 restored · 2 correctly refused a restore.** + +## What shipped + +- `felhom.eu` — this report, the audit, the evidence, R-633 and R-634 opened, R-630 **raised to P1** + by measurement, R-631 and R-632 **closed**, `09` §6.4 leg F and §8.8, the capability map, the + rotation file (28 lines rewritten + tandoor corrected), and `STATUS.md`. +- `app-catalog-felhom.eu` — **nothing.** No fixture was ready to ship tonight and no template moved. +- **No controller, agent or hub code.** + +## What is owed + +- **R-630's fix**: a `container_name` on paperless-ngx's webserver, or a `verifying` phase that + treats "no probe target" as something other than a failure. Both are decisions, not clean-ups. +- **R-634's mechanism** — `runComposeDeploy`'s pin write was not read; the brief forbade product code. +- **Fixtures for 20 of the 28** that have no non-browser route yet, and a second look at `kimai` + (its own `user:create` succeeded but `user:list` did not show the user) and `jellyfin` (the + `/Startup/User` wizard route that worked for `emby` did not). +- **The six edges that reached `done` with no data proof** — `code-server`, `crafty-controller`, + `komga`, `plex`, `rallly` — need a fixture before they can be promoted. diff --git a/STATUS.md b/STATUS.md index fadcbcf7..bb85de14 100644 --- a/STATUS.md +++ b/STATUS.md @@ -1,30 +1,26 @@ # STATUS — what works, what's broken, what's next -**Updated 2026-09-22 (morning) — I fixed the three apps that shut themselves down after a good update, proved the fix on a real machine, built a check so it cannot happen again, and moved fifteen app versions onto the real catalog. One thing you asked for could not be done, and one thing I nearly got wrong was caught by running the check instead of trusting my reasoning.** +**Updated 2026-09-22 (overnight) — I installed and tested all 28 apps that no test had ever touched. Every app in our catalogue has now been tried at least once. Three things are quietly wrong, and one of them stops a working app.** -**Decisions I took on my own: one.** I moved Nextcloud's database engine up a version. I had written it down as "dropped — the rules forbid it", then ran the rule instead of believing my memory of it, and the rule **allows** it by name: that permission was granted on 2026-09-21, the template already carries the setting that converts the data, and the move was proven end to end in under four minutes. You forbade PostgreSQL engine moves; this one is MariaDB. **You can reverse it by reverting one commit.** +**Decisions I took on my own: none.** -**The three broken apps are fixed.** Tandoor, Zipline and Wger each had one wrong number or address, so the machine knocked on a door the app does not answer. I fixed all three, then proved it on the scratch machine **in both directions**: before the fix all three showed **„Nem egészséges"** on their own page while the app itself was serving customers normally; after the fix, with no restart and no reinstall, all three showed **„Fut"**. +**What I did.** Each of the twenty-eight got the same walk: install it at the version our catalogue offers today, put real data in through the app's own front door, back it up, update it if a newer version really exists, **restore it from that backup and read the data back again**, then delete it and check a minute later that nothing came back. That restore step is new — the update night skipped it. **Twenty-six of the twenty-eight installed. Six are proven end to end.** Fourteen had no newer version to move to tonight. One failed honestly. Two would not install, and one of those is meant not to. -**And the update that failed now works.** I ran Tandoor's exact same update again — same app, same versions, same button, nothing changed but that one number. Yesterday it ran for **six minutes and shut the app down**. Today it finished in **41 seconds** and the data was still there. That is the whole finding in one line. +**The thing I would fix first — an app with no health check gets shut down by a successful update.** Paperless-ngx is never health-checked at all: its containers are named differently from the app, so the machine looks for one, finds nothing, and moves on without a word. I always thought that was just a missing badge. It is not. **The update waits five minutes for a health check that can never arrive, then declares failure and shuts the working app down.** All three of its containers were healthy the whole time. The machine says so in its own words: *"not healthy within 5m0s (last: no probe container) — stopping and HOLDING the app"*. Every household running Paperless who presses Update loses their app and is sent to a restore they do not need. -**There is now a check that catches this before it ships.** The answer was always sitting in the same file, a few lines further down — each app already tells Docker where to knock. The check compares the two. It runs on every push, it refused all three apps before the fix, it passes now, and it is guarded by ten fake-out tests so it cannot quietly stop working. +**Two more, both about the machine losing track of an app rather than its health.** +- **Deleting an app while it is being restored leaves a ghost.** Both buttons say they worked. The app vanishes from every screen, and a container keeps restarting on the machine, still holding a public web address. **The machine already knows how to refuse this** — it refuses an *update* while a backup runs, and refuses a second *restore* while one is going, and it even names which app is blocking. Delete has no such guard. +- **An app can be running perfectly while the machine records it as not installed — and then it cannot be deleted.** I saw this three times. Two only happened when several jobs ran at once; one happened on its own, repeatably. In that state there is no button that works. -**Fifteen versions moved to the real catalog**, one at a time, every check run before each one. Nothing was forced and nothing was dropped. Then I pressed the real Update button on four of them on the demo machine: **all four finished cleanly** — BookStack, Docmost, PrivateBin and RomM are running the new versions. +**In all three cases I needed a command line to clean up what the product could not. A household has none.** -**What I could not do.** You asked me to press that button on **both** demo machines. The second machine has only one app installed and none of the four. I did not install them — installing apps on a demo machine is a change, not a test. Both machines did receive the new catalog, and I checked that. +**The best thing I saw.** Two apps keep their files outside the database, and their local copy does not hold those files. When I asked to restore them, the machine **refused** — and said, in plain Hungarian, that it will not put an old database on top of files it does not have, that the files stay where they are, and which button does work. That is exactly right. -**What the check found that nobody was looking for — and it is quieter than the bug it was built for.** -- **Paperless-ngx has never been health-checked at all.** Not "checked wrongly" — never checked. Its containers are named differently from the app, so the machine looks for one and finds nothing, and moves on without a word. A wrong check is loud and we caught it in one night. **A missing check looks exactly like a healthy app.** -- **Five more apps cannot be checked this way.** One of them, Home Assistant, is correct today only by luck: tighten its settings in the obvious way and it breaks the same way Tandoor did. -- **28 of our 53 apps have never been installed by any test.** The overnight run went from 3 apps to 21, which is a lot — but 21 is not 53. **For those 28, we do not know whether updating works.** That list is now the nightly queue. +**What I got wrong.** My own test script had three bugs that cost nine apps their walk. I found them, fixed them, and walked those nine again one at a time — and that second pass is what corrected my conclusions and produced one of the six proofs. The report names all three. -**Rows opened and closed.** Three new, one closed. The list went from 318 to 321. +**Rows opened and closed.** Three new (the two above, plus the one that raised Paperless to urgent). Two closed. The list went from 321 to 323. **What needs you.** -1. **Rotate the Gitea `admin` token** — still open from yesterday. The machine stores it in plain text in its copy of the catalog. *If you do nothing:* the token keeps working and anyone with yesterday's session transcript has it. -2. **Clear 47 alarm e-mails** from yesterday's drill, in one search: `subject:"gates FAILED in admin/app-catalog-drill"`. The cause is fixed. *If you do nothing:* your alarm inbox stays noisy, and that is the inbox that must never be skimmed. -3. **Decide whether the Nextcloud engine move stays.** I explained my reasoning above. *If you do nothing:* it stays, and Nextcloud households will be offered a database upgrade that was proven once on a scratch machine. -4. **The 28 untested apps.** *If you do nothing:* the nightly rotation works through them at a few per night, and the catalog's update promise rests on nothing for those apps until it gets there. +1. **The second promotion list** — six app versions this night proved safe enough to move on the real catalogue, and six named that must not move, each with the reason. It is in the report. Moving a version is your call, never mine. *If you do nothing:* nothing breaks; those apps drift further from upstream each month. -**Nothing on your own machine, the tester's machine, or the off-site box was touched. No product code was written.** +**Nothing on your own machine, the tester's machine, or the off-site box was touched. No product code was written. The real catalogue was never changed — I checked its version lines against the start of the night and not one differs.** diff --git a/documentation/architecture/00-capability-map.md b/documentation/architecture/00-capability-map.md index c4a9d9e6..e060c614 100644 --- a/documentation/architecture/00-capability-map.md +++ b/documentation/architecture/00-capability-map.md @@ -102,7 +102,7 @@ likewise silent. Evidence: `audits/DRILL-r361-2026-08-22/evidence/06-part3-decis |---|---|---|---|---| | Deploy an app from the catalog (env config, memory guard, health-aware progress) | controller, catalog (~52 apps, images pinned) | **PROVEN-LIVE** | `CAMPAIGN-2` T-DEPLOY-SET (7 apps, env config, health-aware); `RERUN-p1p3` (×4 PASS) | Memory-guard FIRING is not live-shown (T-RES-MEMGUARD never fired: ample RAM / auth-walled) — implemented + unit-level only | | App lifecycle: start/stop/restart/update/logs/remove/redeploy | controller | **PROVEN-LIVE — the ACTIONS work. NARROWED 2026-09-13: `CAMPAIGN-3` proved `remove` removes the APP, not the DATA — the "delete my data" half was INERT on every box until controller v0.236.0 (R-442). RE-PROVEN 2026-09-13 on demo-hp: data written by the app itself (63 MB) gone after removal and listed; an unresolvable data location is REFUSED (409) with the app kept; an SSD app gets `[]` and a note.** | `CAMPAIGN-2` T-LIFECYCLE (stop/start/restart/update/logs); remove (app only) live in `CAMPAIGN-3`; **remove WITH data: `audits/R442-2026-09-13/`**; **data behaviour: `audits/SPIKE-app-update-2026-09-01.md` (2026-09-01)** | Redeploy-after-remove edge remains open (T-REMOVE-REDEPLOY never cleanly passed — stale dryrun journal); non-pilot-critical | -| **Update is GUARDED: it refuses without a restorable backup, backs up first when the copy is stale, and HOLDS an app that does not come up — on ANY backup tier, and the release itself arrives by the managed floor** | controller **v0.237.0 + v0.238.0 + v0.238.1 + v0.239.0**, hub **v0.112.0** | **PROVEN-LIVE (2026-09-13, and again the same afternoon for any tier + floor delivery)** — **afternoon (`audits/rulings-r472-r475-2026-09-13/`):** an undeclared floor above the golden refused with nothing stored (02); a declared floor 0.239.0 / MinAgent 0.129.0 served `from declared` and both demo boxes self-updated in 14 s and 15 s (03); nothing on any tier → backed up first, Tier 1 chosen, done (04); gokapi updated on its Tier-1 unit alone (05); a never-healthy update held naming „saját meghajtó" (07); restored from „helyi", hold cleared (08). **Morning:** scenarios A (real upgrade, success only after health), B (stale copy → backup first), E (pull failure → pin back, app untouched), F (never healthy → held, hold text on API and page), H (start/restart/update and the boot sweep all refuse the held app) and **the restore walk** (Mentések unit restore → back on the old version, hold cleared), on demo-hp with a throwaway app | **`audits/slice4-2026-09-13/`** (live/, redproofs/, gates/); design `architecture/09-update-architecture.md` §6.1 | ~~**Tier-2-only precondition**~~ — superseded by v0.239.0 (any tier, R-475 CLOSED); a Tier-1 route back restores only what the unit holds (R-479); the card keeps the failure sentence after a successful restore (R-480); no automatic rollback, by measurement; a release does not reach the fleet by floor between golden bakes (R-472) **WIDENED 2026-09-21 (the update night) from 3 apps to 21 edges across 19 apps, and NARROWED in one place by the same run.** `audits/DRILL-update-night-2026-09-21.md`. On scratch guest 9202 (controller v0.261.0), against a **private drill catalog** so the live catalog carried no test reference at any point, 21 edges across 19 apps real within-a-major upstream edges were walked through the product's own guarded Update, each app seeded and read back **through its own front door** (R-156) with a negative control on every readback: **14 proven, 3 failed, 4 inconclusive.** **What the PROVEN edges prove, precisely:** the app moved, the four version observables agreed, and the data the app itself was given came back through the app's own interface afterwards. Ten of them printed a verbatim migration line. **What the FAILED edges prove, and they are the more valuable half.** `adventurelog` (a real upstream edge that migrates and then never serves), `tandoor` (an update that SUCCEEDED and was stopped by its own wrong health port), and the PostgreSQL engine major, which refused exactly as predicted. `adventurelog v0.12.1 → v0.13.0` applied **nine database migrations successfully** and then never bound its port; the update held after the full health wait, the hold sentence named the tier, the date and what the copy holds, and the restore the sentence names brought the app back. **That is this row's own promise, exercised on a real upstream edge rather than a staged one.** **AND THE NARROWING, which this row must carry because it is the same mechanism:** the `verifying` phase trusts the `.felhom.yml` probe absolutely, and **two of the 53 templates name a probe the app does not answer** — `tandoor` (port 8080; it listens on 80) and `zipline` (`/api/health`; it answers 404 there, while the compose healthcheck in the same file uses `/api/healthcheck` and is green). For those apps a **successful** update is stopped by its own health wait: tandoor was measured **serving HTTP 200 on the new version at four samples across five minutes**, with docker's own healthcheck green, and was then stopped by `failAndHold` and the household sent to a restore they did not need. **R-618, P1.** No data was lost and the restore works — but "the update is guarded" must not be read as "the guard is right about whether the app came up". **Still true and unchanged:** no automatic rollback (by measurement); the route back is the restore; a multi-major jump ends held honestly. **Not measured on this venue, and named rather than assumed:** every event and every customer mail. Guest 9202 runs `hub.enabled: false` and the notifier returns before it logs (**R-620**), so the whole "who was told" half of `08` was structurally unobservable tonight. **THE NARROWING ABOVE WAS CLOSED THE NEXT DAY, 2026-09-22 — and re-widened the row.** `audits/PROBE-FIX-2026-09-22.md`. All three wrong probes were corrected in the catalog (`app-catalog-felhom.eu@793c4fb`: tandoor `8080→80`, wger `80→8000`, zipline `/api/health→/api/healthcheck`) and **red-proofed live on 9202 through the product in both directions**: at the live pin all three read `Nem egészséges` / `Not healthy` on their own app page while docker reported every container healthy and the front door served a real page; after the real sync all three read `Fut` / `Running` with no redeploy. **tandoor's edge was then re-walked with nothing else changed and ended `done` at +41.1 s**, seed read back, where the identical edge had ended `failed` at +361.9 s with the app stopped — so the tally is now **15 proven, 2 failed, 4 inconclusive**, and all fifteen are on the live catalog. A `--fast` catalog gate (`check-probe-matches-compose.py`) now refuses a probe that does not match the same service's own compose healthcheck, with four red-proofs and ten decoys including the no-PyYAML mode CI actually runs. **WHAT THIS ROW STILL CANNOT CLAIM, and the reason is exactly R-96 rule 3:** the guard is now shown correct for **47 of 53** templates. `paperless-ngx`'s probe has **never run on any box** — no container name matches its stack name, so it is silently skipped and its badge can never go red (**R-630**); and five more cannot be judged statically, one of which (`home-assistant`) is right only because its check type cannot fail (**R-631**). An absent alarm is equally consistent with healthy and with never checked. **And the sweep's ceiling, counted: 28 of the 53 templates have never been deployed by any drill (R-632).** | +| **Update is GUARDED: it refuses without a restorable backup, backs up first when the copy is stale, and HOLDS an app that does not come up — on ANY backup tier, and the release itself arrives by the managed floor** | controller **v0.237.0 + v0.238.0 + v0.238.1 + v0.239.0**, hub **v0.112.0** | **PROVEN-LIVE (2026-09-13, and again the same afternoon for any tier + floor delivery)** — **afternoon (`audits/rulings-r472-r475-2026-09-13/`):** an undeclared floor above the golden refused with nothing stored (02); a declared floor 0.239.0 / MinAgent 0.129.0 served `from declared` and both demo boxes self-updated in 14 s and 15 s (03); nothing on any tier → backed up first, Tier 1 chosen, done (04); gokapi updated on its Tier-1 unit alone (05); a never-healthy update held naming „saját meghajtó" (07); restored from „helyi", hold cleared (08). **Morning:** scenarios A (real upgrade, success only after health), B (stale copy → backup first), E (pull failure → pin back, app untouched), F (never healthy → held, hold text on API and page), H (start/restart/update and the boot sweep all refuse the held app) and **the restore walk** (Mentések unit restore → back on the old version, hold cleared), on demo-hp with a throwaway app | **`audits/slice4-2026-09-13/`** (live/, redproofs/, gates/); design `architecture/09-update-architecture.md` §6.1 | ~~**Tier-2-only precondition**~~ — superseded by v0.239.0 (any tier, R-475 CLOSED); a Tier-1 route back restores only what the unit holds (R-479); the card keeps the failure sentence after a successful restore (R-480); no automatic rollback, by measurement; a release does not reach the fleet by floor between golden bakes (R-472) **WIDENED 2026-09-21 (the update night) from 3 apps to 21 edges across 19 apps, and NARROWED in one place by the same run.** `audits/DRILL-update-night-2026-09-21.md`. On scratch guest 9202 (controller v0.261.0), against a **private drill catalog** so the live catalog carried no test reference at any point, 21 edges across 19 apps real within-a-major upstream edges were walked through the product's own guarded Update, each app seeded and read back **through its own front door** (R-156) with a negative control on every readback: **14 proven, 3 failed, 4 inconclusive.** **What the PROVEN edges prove, precisely:** the app moved, the four version observables agreed, and the data the app itself was given came back through the app's own interface afterwards. Ten of them printed a verbatim migration line. **What the FAILED edges prove, and they are the more valuable half.** `adventurelog` (a real upstream edge that migrates and then never serves), `tandoor` (an update that SUCCEEDED and was stopped by its own wrong health port), and the PostgreSQL engine major, which refused exactly as predicted. `adventurelog v0.12.1 → v0.13.0` applied **nine database migrations successfully** and then never bound its port; the update held after the full health wait, the hold sentence named the tier, the date and what the copy holds, and the restore the sentence names brought the app back. **That is this row's own promise, exercised on a real upstream edge rather than a staged one.** **AND THE NARROWING, which this row must carry because it is the same mechanism:** the `verifying` phase trusts the `.felhom.yml` probe absolutely, and **two of the 53 templates name a probe the app does not answer** — `tandoor` (port 8080; it listens on 80) and `zipline` (`/api/health`; it answers 404 there, while the compose healthcheck in the same file uses `/api/healthcheck` and is green). For those apps a **successful** update is stopped by its own health wait: tandoor was measured **serving HTTP 200 on the new version at four samples across five minutes**, with docker's own healthcheck green, and was then stopped by `failAndHold` and the household sent to a restore they did not need. **R-618, P1.** No data was lost and the restore works — but "the update is guarded" must not be read as "the guard is right about whether the app came up". **Still true and unchanged:** no automatic rollback (by measurement); the route back is the restore; a multi-major jump ends held honestly. **Not measured on this venue, and named rather than assumed:** every event and every customer mail. Guest 9202 runs `hub.enabled: false` and the notifier returns before it logs (**R-620**), so the whole "who was told" half of `08` was structurally unobservable tonight. **THE NARROWING ABOVE WAS CLOSED THE NEXT DAY, 2026-09-22 — and re-widened the row.** `audits/PROBE-FIX-2026-09-22.md`. All three wrong probes were corrected in the catalog (`app-catalog-felhom.eu@793c4fb`: tandoor `8080→80`, wger `80→8000`, zipline `/api/health→/api/healthcheck`) and **red-proofed live on 9202 through the product in both directions**: at the live pin all three read `Nem egészséges` / `Not healthy` on their own app page while docker reported every container healthy and the front door served a real page; after the real sync all three read `Fut` / `Running` with no redeploy. **tandoor's edge was then re-walked with nothing else changed and ended `done` at +41.1 s**, seed read back, where the identical edge had ended `failed` at +361.9 s with the app stopped — so the tally is now **15 proven, 2 failed, 4 inconclusive**, and all fifteen are on the live catalog. A `--fast` catalog gate (`check-probe-matches-compose.py`) now refuses a probe that does not match the same service's own compose healthcheck, with four red-proofs and ten decoys including the no-PyYAML mode CI actually runs. **WHAT THIS ROW STILL CANNOT CLAIM, and the reason is exactly R-96 rule 3:** the guard is now shown correct for **47 of 53** templates. `paperless-ngx`'s probe has **never run on any box** — no container name matches its stack name, so it is silently skipped and its badge can never go red (**R-630**); and five more cannot be judged statically, one of which (`home-assistant`) is right only because its check type cannot fail (**R-631**). An absent alarm is equally consistent with healthy and with never checked. **And the sweep's ceiling, counted: 28 of the 53 templates have never been deployed by any drill (R-632).** **THAT CEILING WAS REMOVED THE SAME NIGHT, 2026-09-22 — all 28 walked (`audits/DRILL-the-28-2026-09-22.md`), so every template in the catalog has now been attempted at least once.** 26 of 28 deployed, **6 proven**, 5 inconclusive, 14 with no within-a-major edge upstream, 1 failed honestly and 2 undeployable — one of those (`plant-it`) **by design**, refused by the product's lifecycle gate, proven live for the first time. Each app also got the half the update night skipped: a **restore from its own copy, with the seed read back again** — 21 restored, and **2 were correctly REFUSED** with the sentence `07` §6.2 predicts for a class-A app whose local copy holds no file leg. **AND THE NIGHT NARROWED THIS ROW AGAIN, in the place the probe work could not reach.** `paperless-ngx` has no container matching its stack name, so no probe is ever built for it — and `verifying` does not skip: it waits out the full `update.health_timeout` and **HOLDS**, stopping an app whose three containers all read `healthy`. The controller's own words: *`not healthy within 5m0s (last: no probe container) — stopping and HOLDING the app`*, at **+313.0 s**. **R-630, raised to P1.** So "the update is guarded" is now shown correct for 47 of 53 templates, wrong for none, and **actively harmful for the one template that has no probe at all**. **Two further limits on what this row may claim, both about STATE rather than health:** a `remove` sent while a restore is still running reports success and leaves a container restarting with a live public route (**R-633**) — while the product already refuses exactly that clash for `update` and for `restore`, naming the blocking operation; and an app can be **running, healthy and serving while recorded as `deployed: false`**, in which state the product refuses to remove it at all (**R-634**). In both, a person needed a shell to clear what the product could not. | | **What `restart` and `update` do to a deployed app whose compose file the catalog already moved** | controller **v0.235.0** | **CHANGED 2026-09-06 — they NO LONGER upgrade it.** The row below records what shipped; this text records what it replaced, because every box under v0.235.0 still behaves the old way. **Up to v0.234.0: PROVEN-LIVE (2026-09-01) — they UPGRADE it.** Every lifecycle action ends in `docker compose up -d`, which makes the container match the file and PULLS the image itself when it is missing (measured: 18.3 s with a pull, 0.5 s without; negative control with an unchanged file did not even recreate the container). This is DELIBERATE on the restart path — `Manager.RestartStack` says so in a comment — but the syncer moves the file under a deployed app on a 15-minute cycle with no deployed check (R-438), and NOTHING tells the customer. | `audits/SPIKE-app-update-2026-09-01.md` §2, §3 | **No safety copy is taken by any of them** — `writeSafetyDump` is DATABASE-ONLY and is not on the update path at all. R-438, R-440, R-443. | | **Whether the box UPGRADES an app by itself, with nobody pressing anything** | controller | **PROVEN-LIVE (2026-09-01) — YES, but only when an app fails to come back.** A plain power cut does NOT upgrade: Docker's `restart: unless-stopped` restores the old containers and the reconciler logs `no boot-orphaned apps (nothing to start)`. When an app does NOT return, `Reconciler.Run` (`bootrecon.go:269`) calls `StartStack` -> `compose up -d` and the app comes back on the NEW version, unattended (measured). **13 non-API call sites across 9 files reach `up -d` this way** — not the five previously believed. | `audits/SPIKE-app-update-2026-09-01.md` §2, §8 | The drive-return gate (`intermediary.go:222`) and `AppStopGuard.Recover` (`appstop_marker.go:283`) call the same function; located by reading, **not exercised live** — stated as such. | | **Whether an app UPGRADE can be undone** | controller + catalog | **PROVEN-LIVE (2026-09-01) — NO, and "rollback" is the wrong word for it.** Once a migration has RUN, putting the old image tag back yields a container that refuses to start: Nextcloud — *"the version of the data (32.0.9.2) is higher than the docker image version (31.0.14.1) and downgrading is not supported"*. A 3-major jump is refused outright (*"only possible to upgrade one major version at a time"*) and IS recoverable, precisely because nothing migrated. Positive control: the data is not destroyed — returning to 32.0.9 restored both seeded markers byte-identical. | `audits/SPIKE-app-update-2026-09-01.md` §7 | The only route back is restoring DATA from a copy taken BEFORE the update — which no update path takes. And a restore's image-level rollback is itself overwritten by the syncer within 15 minutes (R-441). R-40 is confirmed live by the same measurement. | diff --git a/documentation/architecture/09-update-architecture.md b/documentation/architecture/09-update-architecture.md index 66194441..5b0b19be 100644 --- a/documentation/architecture/09-update-architecture.md +++ b/documentation/architecture/09-update-architecture.md @@ -877,7 +877,7 @@ headlessly (R-460). | C — the PostgreSQL rehearsal | **DONE and COSTED**: ~9 s of engine work, 155.9 s end to end for 49 MB / 48 tables. `pg_upgrade` still owed and may prove unnecessary | | D — the downgrade refusal | already done, v0.260.0 | | E — the automatic night | **COMPLETE.** The unattended HOLD was produced at last (312.9 s), with no retry across two further passes. It needed the image store of §6.5 | -| F — the remaining apps | ~34 still unwalked. The fixtures for 20 exist and amortise | +| F — the remaining apps | **DONE 2026-09-22 (`audits/DRILL-the-28-2026-09-22.md`): the 28 apps no drill had ever touched were walked in ONE night, so the catalog is now **53 of 53 attempted**, not 25.** 26 of the 28 deployed; 6 proven; 5 inconclusive; 14 had no within-a-major edge upstream that night; 1 failed honestly (`outline 1.9.1 -> 1.10.1`, HELD with the right sentence); 2 could not be deployed, one of them (`plant-it`) **by design** — it is `lifecycle: abandoned` and the product's lifecycle gate refused it, proven live for the first time. **The cost is now known and it is not machine time:** three concurrent walks did 28 apps in about four hours, and the binding constraints were FIXTURES (only 6 of 28 had a non-browser route that both seeded and read back) and the fact that `POST /api/backup/run` is BOX-WIDE, so concurrent walks serialise on it. **This leg also added the night's biggest finding**, which no count would have produced: R-630 | **What the night ADDED to this table, which none of the legs anticipated:** the `verifying` phase trusts the `.felhom.yml` probe absolutely, and three of 53 templates name a probe the app does not @@ -1110,6 +1110,23 @@ Version strings stay in the logs, the API and the hub. unknown for six.** **AND THE SWEEP'S REAL CEILING, counted rather than felt: 28 of the 53 templates have never been deployed by any drill** (**R-632**) — the widening above went from 3 apps to 21, and 21 is not 53. + **CLOSED THE NEXT NIGHT, 2026-09-22: all 28 were walked** (`audits/DRILL-the-28-2026-09-22.md`), + so every template in the catalog has now been attempted at least once. **And the walk that closed + it found something the probe work had left open.** `paperless-ngx` has no container whose name + matches its stack name, so `findProbeContainer` returns nothing and its probe has **never run on + any box**. Asked what `verifying` does with no probe to wait on, the answer is the worst of the + three: it waits out the full `update.health_timeout` and **HOLDS**, stopping an app whose three + containers all read `healthy`. The controller names it itself — *`not healthy within 5m0s (last: + no probe container) — stopping and HOLDING the app`* — at **+313.0 s**, front door 404 afterwards. + **So limitation 8 now has two shapes, not one:** a probe that names the wrong target (R-618, + fixed) and **no probe at all** (**R-630, raised to P1**), and the static gate can see the first + but not the second, because there is nothing to compare. + **Two more things the same night measured, both about state rather than health:** a `remove` sent + while a restore is still running reports success and leaves a container restarting with a live + public route (**R-633**) — and the product already has exactly that guard for `update` and for + `restore`, which name the blocking operation, but not for `remove`; and an app can be **running, + healthy and serving while recorded as `deployed: false`**, in which state the product refuses to + remove it at all (**R-634**, reproducible alone on `sparkyfitness`). 9. **The hub does not record image tags at all.** Its report's container payload carries name, state, CPU and memory, and no image field (spike §5). So the fleet view of §6 slice 7 needs a hub-side change; it is not derivable from what is already reported. diff --git a/documentation/audits/DRILL-the-28-2026-09-22-HEAD.md b/documentation/audits/DRILL-the-28-2026-09-22-HEAD.md new file mode 100644 index 00000000..68967730 --- /dev/null +++ b/documentation/audits/DRILL-the-28-2026-09-22-HEAD.md @@ -0,0 +1,76 @@ +# THE TWENTY-EIGHT — every app no update drill had ever touched, 2026-09-22 + +Evidence: `the-28-2026-09-22/`. What came before: `DRILL-update-night-2026-09-21.md` (21 edges, +19 apps) and `PROBE-FIX-2026-09-22.md` (the probe fix and the fifteen moves). + +--- + +## Not done, or changed from the brief + +**Read this first.** + +### Claims in the brief that turned out wrong + +1. **There is no `requires:` key in `.felhom.yml`.** The brief said *"A `.felhom.yml` `requires:` + (HDD, x86) that 9202 cannot meet → recorded, not forced."* No template has such a key. The + constraints live under **`resources:`** as `needs_hdd` and `pi_compatible`. **And none of them + excluded anything:** 9202 is `x86_64` with `/mnt/felhom-drives/scratch_hdd` mounted, so every one + of the 28 was installable on those grounds. Nothing was skipped for a resource reason. + +2. **The brief's file-leg list is wrong, and it told me where to check.** It grouped *"immich, + jellyfin, plex, emby, komga, calibre-web, gokapi, homebox, gramps-web"* as file-leg apps. Read + from `07-backup-architecture.md` §6.2 — which the brief itself says to read rather than re-derive + — only **four of the 28 are class A** (at least one readable file leg): `calibre-web`, `immich`, + `komga`, `paperless-ngx`. **`jellyfin`, `plex` and `emby` are class B precisely because their only + bind is a `:ro` media mount**, which `ClassifyBinds` excludes; `gokapi`, `homebox` and + `gramps-web` keep everything in named volumes. The table below uses `07` §6.2's classes, not the + brief's. + +3. **The brief's database list is incomplete.** It named calcom, claper, outline, paperless-ngx, + rallly and sparkyfitness for PostgreSQL and kimai for MariaDB. **`immich` also carries PostgreSQL + (a vectorchord variant) and redis, and `wanderer` carries meilisearch** — two apps the brief put + in the "file-leg" group actually run their own datastore. + +4. **One of the 28 cannot be installed at all, by design.** `plant-it` is `lifecycle: "abandoned"`, + and the product's lifecycle gate refused the deploy with **409 „Ez az alkalmazás jelenleg nem + telepíthető."** That is correct behaviour, measured live for the first time. It is the only + lifecycle-gated template in the whole catalog of 53. + +### Claims in the brief that were verified true, by looking + +- **The drill repo's Actions are off (R-629).** Read from the API: `has_actions: false, + private: true`. And measured rather than trusted: **47 CI jobs before the reset push, 47 after** — + the push produced no run and no mail. +- **`repoint_drill.py` still works after the catalog moved.** 9202's cache now reads + `origin …/app-catalog-drill.git` at `1ad1f34`. +- **9202 has the capacity.** 25.9 GB RAM (23.5 free), 28 GB free on `/`, 842 GB on the scratch + drive. The root disk is the binding constraint, so **each app's images are removed by name after + its verdict** — never `prune` (rule 3). Disk held at 1.9 GB used throughout. + +### Changed method, named + +5. **A restore that is REFUSED is recorded as `refused-with-a-sentence`, not as a failed restore.** + The first version of the harness collapsed the two and mislabelled `calibre-web`. The product had + in fact done the right thing — see the finding below — and a harness that calls a correct refusal + a failure would have buried it. + +6. **The harness now waits for a restore to settle before removing.** It did not at first, and that + race produced **R-633**, a real defect. The race was left in the record for `gokapi` and fenced + out afterwards, so the remaining apps measure the product rather than the harness. + + +7. **Three bugs in tonight's own harness, each named with what it cost.** A missing `import re` in + the restore step killed the restore half for `wanderer`, `claper`, `sparkyfitness` and `calcom`. + A variable named `m` shadowed the app's metadata and broke `paperless-ngx`'s teardown. An empty + phase list crashed on `[-1]` when the Update was **refused** before any phase existed, which cost + `rallly` its whole walk. **All four apps, and five more, were re-walked serially afterwards** — + and that re-walk is what corrected R-634 and produced `ghost`'s proof. An instrument that can + drop results silently is not a measurement; these dropped them loudly and were re-run. + +8. **Concurrency is part of the method and it changed two results.** Three walks ran at once to fit + 28 apps in one night. `POST /api/backup/run` is **box-wide**, so a second caller gets + `409 „Mentés már folyamatban"`, and the Update refuses while a backup or restore is in flight + (`409 busy`). **Both refusals are the product being right** and both are quoted below. But they + cost `ghost` and `rallly` their edge on the first pass, and they are implicated in two of R-634's + three instances. The nine re-walks were serial for exactly this reason. + diff --git a/documentation/audits/DRILL-the-28-2026-09-22.md b/documentation/audits/DRILL-the-28-2026-09-22.md new file mode 100644 index 00000000..36e24432 --- /dev/null +++ b/documentation/audits/DRILL-the-28-2026-09-22.md @@ -0,0 +1,351 @@ +# THE TWENTY-EIGHT — every app no update drill had ever touched, 2026-09-22 + +Evidence: `the-28-2026-09-22/`. What came before: `DRILL-update-night-2026-09-21.md` (21 edges, +19 apps) and `PROBE-FIX-2026-09-22.md` (the probe fix and the fifteen moves). + +--- + +## Not done, or changed from the brief + +**Read this first.** + +### Claims in the brief that turned out wrong + +1. **There is no `requires:` key in `.felhom.yml`.** The brief said *"A `.felhom.yml` `requires:` + (HDD, x86) that 9202 cannot meet → recorded, not forced."* No template has such a key. The + constraints live under **`resources:`** as `needs_hdd` and `pi_compatible`. **And none of them + excluded anything:** 9202 is `x86_64` with `/mnt/felhom-drives/scratch_hdd` mounted, so every one + of the 28 was installable on those grounds. Nothing was skipped for a resource reason. + +2. **The brief's file-leg list is wrong, and it told me where to check.** It grouped *"immich, + jellyfin, plex, emby, komga, calibre-web, gokapi, homebox, gramps-web"* as file-leg apps. Read + from `07-backup-architecture.md` §6.2 — which the brief itself says to read rather than re-derive + — only **four of the 28 are class A** (at least one readable file leg): `calibre-web`, `immich`, + `komga`, `paperless-ngx`. **`jellyfin`, `plex` and `emby` are class B precisely because their only + bind is a `:ro` media mount**, which `ClassifyBinds` excludes; `gokapi`, `homebox` and + `gramps-web` keep everything in named volumes. The table below uses `07` §6.2's classes, not the + brief's. + +3. **The brief's database list is incomplete.** It named calcom, claper, outline, paperless-ngx, + rallly and sparkyfitness for PostgreSQL and kimai for MariaDB. **`immich` also carries PostgreSQL + (a vectorchord variant) and redis, and `wanderer` carries meilisearch** — two apps the brief put + in the "file-leg" group actually run their own datastore. + +4. **One of the 28 cannot be installed at all, by design.** `plant-it` is `lifecycle: "abandoned"`, + and the product's lifecycle gate refused the deploy with **409 „Ez az alkalmazás jelenleg nem + telepíthető."** That is correct behaviour, measured live for the first time. It is the only + lifecycle-gated template in the whole catalog of 53. + +### Claims in the brief that were verified true, by looking + +- **The drill repo's Actions are off (R-629).** Read from the API: `has_actions: false, + private: true`. And measured rather than trusted: **47 CI jobs before the reset push, 47 after** — + the push produced no run and no mail. +- **`repoint_drill.py` still works after the catalog moved.** 9202's cache now reads + `origin …/app-catalog-drill.git` at `1ad1f34`. +- **9202 has the capacity.** 25.9 GB RAM (23.5 free), 28 GB free on `/`, 842 GB on the scratch + drive. The root disk is the binding constraint, so **each app's images are removed by name after + its verdict** — never `prune` (rule 3). Disk held at 1.9 GB used throughout. + +### Changed method, named + +5. **A restore that is REFUSED is recorded as `refused-with-a-sentence`, not as a failed restore.** + The first version of the harness collapsed the two and mislabelled `calibre-web`. The product had + in fact done the right thing — see the finding below — and a harness that calls a correct refusal + a failure would have buried it. + +6. **The harness now waits for a restore to settle before removing.** It did not at first, and that + race produced **R-633**, a real defect. The race was left in the record for `gokapi` and fenced + out afterwards, so the remaining apps measure the product rather than the harness. + + +7. **Three bugs in tonight's own harness, each named with what it cost.** A missing `import re` in + the restore step killed the restore half for `wanderer`, `claper`, `sparkyfitness` and `calcom`. + A variable named `m` shadowed the app's metadata and broke `paperless-ngx`'s teardown. An empty + phase list crashed on `[-1]` when the Update was **refused** before any phase existed, which cost + `rallly` its whole walk. **All four apps, and five more, were re-walked serially afterwards** — + and that re-walk is what corrected R-634 and produced `ghost`'s proof. An instrument that can + drop results silently is not a measurement; these dropped them loudly and were re-run. + +8. **Concurrency is part of the method and it changed two results.** Three walks ran at once to fit + 28 apps in one night. `POST /api/backup/run` is **box-wide**, so a second caller gets + `409 „Mentés már folyamatban"`, and the Update refuses while a backup or restore is in flight + (`409 busy`). **Both refusals are the product being right** and both are quoted below. But they + cost `ghost` and `rallly` their edge on the first pass, and they are implicated in two of R-634's + three instances. The nine re-walks were serial for exactly this reason. + + +--- + +## What this night is, in three lines + +- **Interventions: SEVEN — over the brief's limit of five, and six of the seven were my own harness, + not the product.** Three were bugs in tonight's driver that cost apps their walk and were fixed + mid-run (a missing `import re`; a variable that shadowed the app's metadata; a crash when the + Update was refused before any phase existed). Two were deliberate method changes (recording a + REFUSED restore as its own verdict; making the harness wait for a restore to settle before + removing). One was a waiter that deadlocked on its own command line. **The seventh was the + product's:** three leftovers it could not clear, which a shell had to. +- **26 of 28 deployed; 6 proven; 5 inconclusive; 14 with no upstream edge; 1 failed honestly; + 2 that could not be deployed** — one of those by design. +- **The one result that matters most:** an app with **no health probe at all** has its working + installation **stopped by a successful update**. `paperless-ngx` was healthy on all three + containers; the Update ran the full five-minute health wait and then held the app, and the + controller named the reason itself — **`no probe container`**. R-630 is raised to P1. + +--- + +## The two findings that are not about any single app + +### R-633 — a remove sent while a restore is still running reports success and leaves an orphan + +`gokapi` was restored from its own local copy at **11:34:07** and removed at **11:34:22**. +`POST /backup/restore` answers **302 and does its work in the background**; the remove tore down +what existed, and the restore's own `compose up` then **re-created the container at 11:34:24**. +Both calls returned success. + +Twenty-five minutes later, `GET /api/stacks/gokapi` reads **`deployed: false`** while `docker ps -a` +shows `gokapi` **`Restarting (1)`** with its full Traefik label set still attached — including +`traefik.http.routers.gokapi.rule: Host(`.enkisfelhom.hu`)`, **a rule with an empty subdomain**, +because the deploy values that filled it were deleted with the app. Its own log loops +*„Salt for admin password invalid… password does not appear to be a SHA-1 hash"* — the volume +holding its config was removed correctly, so the binary can never start. + +**A household can press exactly those two buttons in that order.** The product accepted both and +**the remove reported success while leaving the orphan**; nothing in the alarm ladder can fire, +because `08` §4 keys on stacks the controller still knows about. This is **R-626's class with the +mechanism finally visible** — that row saw a removed `navidrome` come back and could not diagnose +it, because the controller had restarted and its log no longer reached the moment. Here the window +is **seventeen seconds** and both halves are in the evidence. + +The harness was then fenced against its own race, so every app after `gokapi` measures the product. +Evidence: `apps/gokapi/came-back-evidence.txt`. + +### A restore that is REFUSED is the product being right, and nearly went down as a failure + +`calibre-web` is a class-A app (`07` §6.2): it has a readable file leg, and the local Tier-1 copy +does not hold it. The restore was **refused**, with this sentence: + +> „Ez a mentés nem tartalmazza az alkalmazás fájljait, ezért nem állítjuk vissza az adatbázist +> föléjük — a fájlok így a helyükön maradnak. A fájlok a távoli másolatból állíthatók vissza: +> Biztonsági mentés → Visszaállítás, „Teljes visszaállítás (fájlok + adatbázis)"." + +That is exactly what `07` §6.2 predicts, it names the action that does work, and it refuses +**before** touching anything. The first version of tonight's harness recorded it as a failed +restore. **A harness that calls a correct refusal a failure buries the best result of the night**, +so refusals are now recorded as their own verdict and the sentence is quoted. + + +### One real upstream edge HELD honestly — `outline 1.9.1 → 1.10.1` + +The most valuable single result after R-630, because it is the guarded update's own promise +exercised on a real upstream version rather than a staged one. + +| phase | at | +|---|---| +| `safety-dump` | 0.0 s | +| `pulling` | +1.1 s | +| `starting` | +64.8 s | +| `verifying` | +65.8 s | +| **`failed`** | **+368.6 s** | + +The app was stopped and held, and the sentence the household reads names the tier, the date **and +what the copy contains**: + +> „A(z) outline frissítése 2026-09-22 14:50-kor nem sikerült, és az alkalmazás nem indult el az új +> verzióval. Az alkalmazás biztonsági okból leállítva marad, hogy az adatai ne sérüljenek. +> Visszaállítható a Mentések oldalon ebből a biztonsági mentésből: saját meghajtó, 2026-09-22 14:44 +> — ez a másolat a beállításokat, az adatbázist és az adatköteteket tartalmazza." + +The restore named in that sentence was then walked and the app came back. **`outline` must not be +promoted.** + +### Two refusals that are the product guarding itself, and both name the blocker + +- **The Update refuses while a backup or restore runs:** „A frissítés most nem indítható: + mentés/visszaállítás folyamatban. Próbáld újra, ha befejeződött." +- **A second restore refuses and NAMES the app that is blocking it:** „Egy visszaállítási művelet + **(jellyfin)** már fut, ezért most nem indítható újabb." + +**That second guard is exactly the fence R-633 is missing.** The product already knows how to refuse +a conflicting operation and how to say which one — for `update` and for `restore`. **`remove` has no +such guard**, which is why a remove sent during a restore reports success and leaves an orphan. + +--- + +## The table — all twenty-eight + +Classes are `07-backup-architecture.md` §6.2's, not re-derived. + +| app | class | deployed | seeded | backup | edge | update | restore | removed clean | s | evidence | +|---|---|---|---|---|---|---|---|---|---|---| +| `calcom` | B volumes-only + postgres | yes | no route | 1 copy | none upstream | — | failed | yes | 675.8 | `apps/calcom/` | +| `calibre-web` | A file-leg | yes | no route | 1 copy | none upstream | — | failed | yes | 187.5 | `apps/calibre-web/` | +| `claper` | B volumes-only + postgres | yes | no route | 1 copy | none upstream | — | ok | yes | 255.3 | `apps/claper/` | +| `code-server` | B volumes-only | yes | route failed | 1 copy | `4.129.0` → `4.138.0` | done | ok | yes | 299.9 | `apps/code-server/` | +| `crafty-controller` | B volumes-only | yes | no route | 1 copy | `4.10.7` → `4.11.0` | done | ok | yes | 288.5 | `apps/crafty-controller/` | +| `emby` | B volumes-only | yes | yes | 1 copy | `4.10.0.20` → `4.11.0.1` | done | ok | yes | 198.7 | `apps/emby/` | +| `ghost` | B volumes-only | yes | yes | 1 copy | `6.53.0-alpine` → `6.64.0-alpine` | done | ok | yes | 289.6 | `apps/ghost/` | +| `gokapi` | B volumes-only | yes | no route | 1 copy | none upstream | — | ok | **no** | 132.3 | `apps/gokapi/` | +| `gramps-web` | B volumes-only | yes | route failed | 1 copy | none upstream | — | ok | yes | 334.1 | `apps/gramps-web/` | +| `homebox` | B volumes-only | yes | route failed | 1 copy | none upstream | — | ok | yes | 127.9 | `apps/homebox/` | +| `homepage` | B volumes-only | yes | no route | 1 copy | none upstream | — | ok | yes | 137.1 | `apps/homepage/` | +| `immich` | A file-leg + postgres+redis | yes | yes | 1 copy | `v3.0.3` → `v3.2.2` | done | refused-with-a-sentence | yes | 375.0 | `apps/immich/` | +| `jellyfin` | B volumes-only | yes | route failed | 1 copy | none upstream | — | ok | yes | 220.7 | `apps/jellyfin/` | +| `kimai` | B volumes-only + mariadb | yes | route failed | 1 copy | none upstream | — | ok | yes | 311.3 | `apps/kimai/` | +| `komga` | A file-leg | yes | route failed | 1 copy | `1.25.0` → `1.27.1` | done | ok | yes | 251.9 | `apps/komga/` | +| `onlyoffice` | B volumes-only | yes | route failed | 1 copy | none upstream | — | ok | yes | 227.3 | `apps/onlyoffice/` | +| `outline` | B volumes-only + postgres+redis | yes | route failed | 1 copy | `1.9.1` → `1.10.1` | failed | failed | yes | 1389.6 | `apps/outline/` | +| `paperless-ngx` | A file-leg + postgres+redis | yes | no route | 1 copy | none upstream | — | refused-with-a-sentence | yes | 253.2 | `apps/paperless-ngx/` | +| `plant-it` | B volumes-only | **no** | no route | — | none upstream | — | not-attempted | yes | 78.2 | `apps/plant-it/` | +| `plex` | B volumes-only | yes | route failed | 1 copy | `1.41.4.9463-630c9f557` → `1.43.4.10903-e5521bd8c` | done | ok | yes | 262.1 | `apps/plex/` | +| `radarr` | B volumes-only | yes | yes | 1 copy | `6.3.0` → `6.4.4` | done | ok | yes | 227.8 | `apps/radarr/` | +| `rallly` | B volumes-only + postgres | yes | route failed | 1 copy | `4.11.1` → `4.15.2` | done | ok | yes | 230.1 | `apps/rallly/` | +| `recipe-importer` | B volumes-only | yes | no route | 1 copy | none upstream | — | ok | yes | 123.0 | `apps/recipe-importer/` | +| `seerr` | B volumes-only | yes | no route | 1 copy | none upstream | — | ok | yes | 218.8 | `apps/seerr/` | +| `sonarr` | B volumes-only | yes | yes | 1 copy | `4.0.19` → `4.0.20` | done | ok | yes | 216.6 | `apps/sonarr/` | +| `sparkyfitness` | B volumes-only + postgres | **no** | no route | — | none upstream | — | not-attempted | **no** | 534.0 | `apps/sparkyfitness/` | +| `termix` | B volumes-only | yes | yes | 1 copy | `2.5.0` → `2.8.0` | done | ok | **no** | 232.5 | `apps/termix/` | +| `wanderer` | B volumes-only + meilisearch | yes | no route | 1 copy | none upstream | — | ok | yes | 400.5 | `apps/wanderer/` | + +**Totals:** **14** no-edge · **6** proven · **5** inconclusive · **2** could-not-deploy · **1** failed — 28 of 28 recorded. + +**Read across the walk rather than down one column:** **26 of 28 deployed**, **6** had a non-browser route that seeded AND read back, **21** restored from their own copy, **2** were correctly REFUSED a restore, **6 proven / 1 failed** on the apps that had an upstream edge, and **2** left a container behind (R-633). + +--- + +## 5.2 — the templates the static gate cannot judge (R-631) + +The probe gate's oracle is the probed service's own compose healthcheck. For five templates there is +no such oracle, or the paths differ on a check that cannot fail. A static rule cannot settle any of +them; asking the running container can. Each was deployed on 9202, its listening sockets read from +inside the container, and the probe's own target dialled **on the compose network** — the same call +the controller makes. + +| app | probe | what it listens on | the probe's own dial | verdict | +|---|---|---|---|---| +| `mealie` | `tcp` 9000 | `0.0.0.0:9000` | 200 | **correct** | +| `uptime-kuma` | `http` 3001 | `*:3001` | 302 | **correct** — `http` calls any response healthy, and 302 proves something answers | +| `vikunja` | `api` 3456 `/api/v1/info` **expect 200** | (busybox: no `ss`, no `netstat`) | **200** | **correct** — and this is the one that could have failed, because its `expect` block compares the code | +| `home-assistant` | `api` 8123 `/api/` **no expect** | `0.0.0.0:8123` | **401** | **correct today, and the 401 is the measurement that proves the warning** | +| `crafty-controller` | `tcp` 8443 | (not read — the app never reached `deployed`; see R-634) | **ok (1 ms)** | **correct** | + +**home-assistant is the one to carry forward.** Its probe dials `/api/` and gets **401** — not 200. +It reads healthy only because `probeHTTP` treats any response as healthy when the type is `api` with +no `expect` block (`healthprobe.go:253-262`). **Add `expect: {status: 200}` to that template — a +change that looks like a tightening — and home-assistant goes permanently unhealthy, and every +successful update of it starts stopping it.** That is R-618's failure exactly, one edit away, and it +is now a measured number rather than a caution. + +**All five are settled.** crafty-controller's reading came from its own walk rather than the side job: the controller's log shows `Health probe crafty-controller: TCP :8443 -> ok (1ms)` twice, six minutes apart, while the app was running. The gate's WARN list is therefore not a backlog of suspects: it is +four correct templates the gate honestly cannot prove, and one that is correct by accident. + + +--- + +## 5.1 — what the guarded Update does when NO probe exists (R-630) + +`paperless-ngx` has no container whose name equals or begins with its stack name, so +`findProbeContainer` returns `""` and `RunHealthProbes` skips the stack silently. **Its probe has +never run on any box.** The open question was what `verifying` — which waits on that same probe — +does when there is nothing to wait on: pass at once, wait out the timeout, or hold. + +**It waits out the full timeout and then HOLDS, stopping a working app.** + +Deployed on 9202, all three containers reported **`healthy`**, the controller read **`running`**, the +front door answered **302**. No upstream edge exists for paperless-ngx tonight, so the Update was +pressed on the **same version** — which is what a household does on an up-to-date app, and it still +walks the whole phase machine. That difference is stated, not glossed. + +| phase | at | +|---|---| +| `checking` → `safety-dump` → `pinning` → `pulling` | 0.0–1.1 s | +| `starting` | +2.1 s | +| `verifying` | +3.1 s | +| **`failed`** | **+313.0 s — the app STOPPED** | + +Afterwards: controller state **`stopped`**, front door **404**. + +**The controller names the cause itself, so no inference was needed:** + +> `update paperless-ngx FAILED after the new version was started: not healthy: not healthy within +> 5m0s (last: no probe container) — stopping and HOLDING the app; the pin stays on the new version +> (its migration may have run)` + +**`no probe container`.** And the hold sentence is correct about the route back — for this class-A +app it warns *„csak a beállításokat és az adatbázist tartalmazza, a fájlokat nem"*. + +**This is R-618's outcome reached by the opposite road.** There a probe named a port the app does not +answer; here no probe exists at all — and the static gate cannot see it, because there is nothing to +compare. The gate does print it as a WARNING on every push, which is how it was found. **R-630 is +raised P2 → P1.** + + +## The second promotion list — for the operator, not for me + +**CC promotes nothing.** These are proposals with the evidence beside them. + +### Proposed to move (6) + +| app | move | update took | its own migration line | +|---|---|---|---| +| `emby` | `4.10.0.20` → `4.11.0.1` | 198.7 s | yes — `emby \| Info SqliteUserRepository: Sqlite compiler options: ATOMIC_INTRINSICS=1,COMPILER=g` | +| `ghost` | `6.53.0-alpine` → `6.64.0-alpine` | 289.6 s | yes — `ghost \| [2026-09-22 15:13:37] INFO Stripe members-migrations skipped because it` | +| `immich` | `v3.0.3` → `v3.2.2` | 375.0 s | none printed | +| `radarr` | `6.3.0` → `6.4.4` | 227.8 s | yes — `radarr \| [migrations] started` | +| `sonarr` | `4.0.19` → `4.0.20` | 216.6 s | yes — `sonarr \| [migrations] started` | +| `termix` | `2.5.0` → `2.8.0` | 232.5 s | yes — `termix \| [1:37:29 PM] [INFO] [🗄️] Database layer pre-upgrade backup created [op:database_` | + +### Must NOT move, with why (6) + +| app | edge | why not | +|---|---|---| +| `code-server` | `4.129.0` → `4.138.0` | the update reached `done`, but this app has no non-browser data route, so nothing proves the household's data survived it | +| `crafty-controller` | `4.10.7` → `4.11.0` | the update reached `done`, but this app has no non-browser data route, so nothing proves the household's data survived it | +| `komga` | `1.25.0` → `1.27.1` | the update reached `done`, but this app has no non-browser data route, so nothing proves the household's data survived it | +| `outline` | `1.9.1` → `1.10.1` | the update ended `failed` — fixture ran and found no non-browser seed route: sign-in requires an external identity provider (OIDC/Slack/Google); no local sign-up route exists | +| `plex` | `1.41.4.9463-630c9f557` → `1.43.4.10903-e5521bd8c` | the update reached `done`, but this app has no non-browser data route, so nothing proves the household's data survived it | +| `rallly` | `4.11.1` → `4.15.2` | the update reached `done`, but this app has no non-browser data route, so nothing proves the household's data survived it | + +**No upstream edge tonight, so nothing to propose (16):** `calcom`, `calibre-web`, `claper`, `gokapi`, `gramps-web`, `homebox`, `homepage`, `jellyfin`, `kimai`, `onlyoffice`, `paperless-ngx`, `plant-it`, `recipe-importer`, `seerr`, `sparkyfitness`, `wanderer`. + + +--- + +## Teardown — three layers plus Gitea, every claim READ BACK + +**The machine (9202).** `controller.yaml` restored from `controller.yaml.pre-28`; `git.repo_url` +reads back as the **live** catalog with an empty token; and — the one that actually decides which +remote is followed (R-615) — the **cache** reads +`origin https://gitea.dooplex.hu/admin/app-catalog-felhom.eu.git` at **`1ad1f34`**. No drill +images. Disk 1.9 GB used of 32 GB, unchanged from the start. + +**Three things the product could NOT clear, and a shell had to.** This is not tidy-up, it is the +finding: the `termix` and `gokapi` containers left by R-633, and `sparkyfitness`'s `app.yaml` left by +R-634. `gokapi` was still `Restarting` two hours later. They were removed **by name** +(`docker rm -f termix gokapi`, `rm .../sparkyfitness/app.yaml`) — never a `prune`. Afterwards 9202 +runs exactly `felhom-controller`, `filebrowser`, `traefik`, and no `app.yaml` exists anywhere. +**A household has no shell.** Evidence: `teardown/manual-cleanup.txt`. + +**The host (demo-hp).** `pct list` before and after: `9201 demo-hp` and `9202 demo-hp-scratch`, both +running, unchanged. `pvesm status` unchanged but for expected scratch growth (`nvme-scratch` +5.19% → 6.95%). **Guest 9201 was never touched.** + +**The hub.** Nothing provisioned, nothing changed. 9202 runs `hub.enabled: false` (R-620). + +**Gitea.** The drill repo is reset to the live `main` (`1ad1f34b6e51`). **`git diff` of the live +catalog's `templates/` against the night's baseline: 0 lines, and `image:` lines changed: NONE.** + +**The fences, each read back rather than asserted:** + +| fence | at the start | at the end | +|---|---|---| +| live catalog `origin/main` | `1ad1f34b6e51` | **`1ad1f34b6e51`** | +| demo-hp guest 9201 catalog cache | `1ad1f34`, live remote | **`1ad1f34`, live remote** | +| demo-felhom guest 9201 catalog cache | `1ad1f34`, live remote | **`1ad1f34`, live remote** | +| drill repo CI jobs | **47** | **47** — no run, no mail, all night (R-629 holds) | + +**Peti's box is parked and received nothing.** Nothing ran on DooPlex beyond ordinary pushes, and +nothing on `ep0`. `felhom-controller`, `felhom-agent` and the hub were read only — **no product code +was written.** No golden, no bake, no vouch, no `--no-verify`, no branch. `local-lvm` untouched, no +`prune`, `tester-1` never reset, `drill-r50` untouched. diff --git a/documentation/audits/the-28-2026-09-22/00-capacity-9202.txt b/documentation/audits/the-28-2026-09-22/00-capacity-9202.txt new file mode 100644 index 00000000..0afbdc9a --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/00-capacity-9202.txt @@ -0,0 +1,17 @@ +=== 9202 capacity + total used free shared buff/cache available +Mem: 25898 168 23513 8 2224 25729 +--- disk: +Filesystem Size Used Avail Use% Mounted on +/dev/loop0 32G 1.9G 28G 7% / +/dev/nvme0n1 938G 49G 842G 6% /mnt/felhom-drives/scratch_hdd +--- cache remote + head: +origin https://gitea.dooplex.hu/admin/app-catalog-drill.git (fetch) +1ad1f34 gates: probe-matches-compose runs in CI's no-PyYAML mode (R-618) +--- running: +felhom-controller +filebrowser +traefik +--- images: +52 + diff --git a/documentation/audits/the-28-2026-09-22/00-capacity-demo-hp-before.txt b/documentation/audits/the-28-2026-09-22/00-capacity-demo-hp-before.txt new file mode 100644 index 00000000..3e15d1c2 --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/00-capacity-demo-hp-before.txt @@ -0,0 +1,12 @@ +VMID Status Lock Name +9201 running demo-hp +9202 running demo-hp-scratch +--- +Name Type Status Total (KiB) Used (KiB) Available (KiB) % +felhom-pbs pbs active 0 0 0 0.00% +local dir active 40453376 32882964 5483296 81.29% +local-lvm lvmthin active 56487936 30113718 26374217 53.31% +nvme-scratch dir active 983379700 51016568 882336520 5.19% +--- + total used free shared buff/cache available +Mem: 29994 4757 9405 185 16458 25237 diff --git a/documentation/audits/the-28-2026-09-22/00-drift-28.txt b/documentation/audits/the-28-2026-09-22/00-drift-28.txt new file mode 100644 index 00000000..97f14f53 --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/00-drift-28.txt @@ -0,0 +1,36 @@ +=== WITHIN-A-MAJOR edge available tonight (12): + emby 4.10.0.20 -> 4.11.0.1 + immich v3.0.3 -> v3.2.2 + termix 2.5.0 -> 2.8.0 + ghost 6.53.0-alpine -> 6.64.0-alpine + komga 1.25.0 -> 1.27.1 + code-server 4.129.0 -> 4.138.0 + radarr 6.3.0 -> 6.4.4 + sonarr 4.0.19 -> 4.0.20 + rallly 4.11.1 -> 4.15.2 + outline 1.9.1 -> 1.10.1 + plex 1.41.4.9463-630c9f557 -> 1.43.4.10903-e5521bd8c + crafty-controller 4.10.7 -> 4.11.0 + +=== ACROSS-MAJOR only, listed not pressed (3): + sparkyfitness v0.17.3 -> v1.7.2 + gokapi v1.9.6 -> v2.2.4 + homepage v1.13.2 -> v2.4.0 + +=== no newer tag / unparsed (8): + calcom v6.2.0 up to date (no tag newer than current within the same shape) + calibre-web v4.0.6 up to date (no tag newer than current within the same shape) + seerr 2.7.3 up to date (no tag newer than current within the same shape) + wanderer v0.20.0 up to date (no tag newer than current within the same shape) + gramps-web v25.6.0 up to date (no tag newer than current within the same shape) + homebox 0.26.2 up to date (no tag newer than current within the same shape) + jellyfin 10.11.11 up to date (no tag newer than current within the same shape) + onlyoffice 9.4.0 up to date (no tag newer than current within the same shape) + +=== errors / internal (4): + claper 2.5 ok + recipe-importer v0.9.11 internal-not-upstream + kimai apache-2.57.0 ok + plant-it 0.10.0 HTTP error: 401 Client Error: Unauthorized for url: https://registry-1 + +paperless-ngx has NO app pin (no container_name matches the stack name) - R-630 diff --git a/documentation/audits/the-28-2026-09-22/00-negative-control-baseline.txt b/documentation/audits/the-28-2026-09-22/00-negative-control-baseline.txt new file mode 100644 index 00000000..e53a7d29 --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/00-negative-control-baseline.txt @@ -0,0 +1,8 @@ +=== NEGATIVE CONTROL BASELINE, taken 21:4x before any drill push +live catalog origin/main: 1ad1f34b6e51843851839bdd18154a6603c6e590 +--- demo-hp guest 9201 catalog cache (must NOT move all night): +origin https://gitea.dooplex.hu/admin/app-catalog-felhom.eu.git (fetch) +1ad1f34 gates: probe-matches-compose runs in CI's no-PyYAML mode (R-618) +--- demo-felhom guest 9201 catalog cache (must NOT move all night): +origin https://gitea.dooplex.hu/admin/app-catalog-felhom.eu.git (fetch) +1ad1f34 gates: probe-matches-compose runs in CI's no-PyYAML mode (R-618) diff --git a/documentation/audits/the-28-2026-09-22/BODY-part1.md b/documentation/audits/the-28-2026-09-22/BODY-part1.md new file mode 100644 index 00000000..e3fddb03 --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/BODY-part1.md @@ -0,0 +1,95 @@ +--- + +## What this night is, in three lines + +- **Interventions: SEVEN — over the brief's limit of five, and six of the seven were my own harness, + not the product.** Three were bugs in tonight's driver that cost apps their walk and were fixed + mid-run (a missing `import re`; a variable that shadowed the app's metadata; a crash when the + Update was refused before any phase existed). Two were deliberate method changes (recording a + REFUSED restore as its own verdict; making the harness wait for a restore to settle before + removing). One was a waiter that deadlocked on its own command line. **The seventh was the + product's:** three leftovers it could not clear, which a shell had to. +- **26 of 28 deployed; 6 proven; 5 inconclusive; 14 with no upstream edge; 1 failed honestly; + 2 that could not be deployed** — one of those by design. +- **The one result that matters most:** an app with **no health probe at all** has its working + installation **stopped by a successful update**. `paperless-ngx` was healthy on all three + containers; the Update ran the full five-minute health wait and then held the app, and the + controller named the reason itself — **`no probe container`**. R-630 is raised to P1. + +--- + +## The two findings that are not about any single app + +### R-633 — a remove sent while a restore is still running reports success and leaves an orphan + +`gokapi` was restored from its own local copy at **11:34:07** and removed at **11:34:22**. +`POST /backup/restore` answers **302 and does its work in the background**; the remove tore down +what existed, and the restore's own `compose up` then **re-created the container at 11:34:24**. +Both calls returned success. + +Twenty-five minutes later, `GET /api/stacks/gokapi` reads **`deployed: false`** while `docker ps -a` +shows `gokapi` **`Restarting (1)`** with its full Traefik label set still attached — including +`traefik.http.routers.gokapi.rule: Host(`.enkisfelhom.hu`)`, **a rule with an empty subdomain**, +because the deploy values that filled it were deleted with the app. Its own log loops +*„Salt for admin password invalid… password does not appear to be a SHA-1 hash"* — the volume +holding its config was removed correctly, so the binary can never start. + +**A household can press exactly those two buttons in that order.** The product accepted both and +**the remove reported success while leaving the orphan**; nothing in the alarm ladder can fire, +because `08` §4 keys on stacks the controller still knows about. This is **R-626's class with the +mechanism finally visible** — that row saw a removed `navidrome` come back and could not diagnose +it, because the controller had restarted and its log no longer reached the moment. Here the window +is **seventeen seconds** and both halves are in the evidence. + +The harness was then fenced against its own race, so every app after `gokapi` measures the product. +Evidence: `apps/gokapi/came-back-evidence.txt`. + +### A restore that is REFUSED is the product being right, and nearly went down as a failure + +`calibre-web` is a class-A app (`07` §6.2): it has a readable file leg, and the local Tier-1 copy +does not hold it. The restore was **refused**, with this sentence: + +> „Ez a mentés nem tartalmazza az alkalmazás fájljait, ezért nem állítjuk vissza az adatbázist +> föléjük — a fájlok így a helyükön maradnak. A fájlok a távoli másolatból állíthatók vissza: +> Biztonsági mentés → Visszaállítás, „Teljes visszaállítás (fájlok + adatbázis)"." + +That is exactly what `07` §6.2 predicts, it names the action that does work, and it refuses +**before** touching anything. The first version of tonight's harness recorded it as a failed +restore. **A harness that calls a correct refusal a failure buries the best result of the night**, +so refusals are now recorded as their own verdict and the sentence is quoted. + + +### One real upstream edge HELD honestly — `outline 1.9.1 → 1.10.1` + +The most valuable single result after R-630, because it is the guarded update's own promise +exercised on a real upstream version rather than a staged one. + +| phase | at | +|---|---| +| `safety-dump` | 0.0 s | +| `pulling` | +1.1 s | +| `starting` | +64.8 s | +| `verifying` | +65.8 s | +| **`failed`** | **+368.6 s** | + +The app was stopped and held, and the sentence the household reads names the tier, the date **and +what the copy contains**: + +> „A(z) outline frissítése 2026-09-22 14:50-kor nem sikerült, és az alkalmazás nem indult el az új +> verzióval. Az alkalmazás biztonsági okból leállítva marad, hogy az adatai ne sérüljenek. +> Visszaállítható a Mentések oldalon ebből a biztonsági mentésből: saját meghajtó, 2026-09-22 14:44 +> — ez a másolat a beállításokat, az adatbázist és az adatköteteket tartalmazza." + +The restore named in that sentence was then walked and the app came back. **`outline` must not be +promoted.** + +### Two refusals that are the product guarding itself, and both name the blocker + +- **The Update refuses while a backup or restore runs:** „A frissítés most nem indítható: + mentés/visszaállítás folyamatban. Próbáld újra, ha befejeződött." +- **A second restore refuses and NAMES the app that is blocking it:** „Egy visszaállítási művelet + **(jellyfin)** már fut, ezért most nem indítható újabb." + +**That second guard is exactly the fence R-633 is missing.** The product already knows how to refuse +a conflicting operation and how to say which one — for `update` and for `restore`. **`remove` has no +such guard**, which is why a remove sent during a restore reports success and leaves an orphan. diff --git a/documentation/audits/the-28-2026-09-22/BODY-promotion.md b/documentation/audits/the-28-2026-09-22/BODY-promotion.md new file mode 100644 index 00000000..2b7c71b4 --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/BODY-promotion.md @@ -0,0 +1,28 @@ +## The second promotion list — for the operator, not for me + +**CC promotes nothing.** These are proposals with the evidence beside them. + +### Proposed to move (6) + +| app | move | update took | its own migration line | +|---|---|---|---| +| `emby` | `4.10.0.20` → `4.11.0.1` | 198.7 s | yes — `emby \| Info SqliteUserRepository: Sqlite compiler options: ATOMIC_INTRINSICS=1,COMPILER=g` | +| `ghost` | `6.53.0-alpine` → `6.64.0-alpine` | 289.6 s | yes — `ghost \| [2026-09-22 15:13:37] INFO Stripe members-migrations skipped because it` | +| `immich` | `v3.0.3` → `v3.2.2` | 375.0 s | none printed | +| `radarr` | `6.3.0` → `6.4.4` | 227.8 s | yes — `radarr \| [migrations] started` | +| `sonarr` | `4.0.19` → `4.0.20` | 216.6 s | yes — `sonarr \| [migrations] started` | +| `termix` | `2.5.0` → `2.8.0` | 232.5 s | yes — `termix \| [1:37:29 PM] [INFO] [🗄️] Database layer pre-upgrade backup created [op:database_` | + +### Must NOT move, with why (6) + +| app | edge | why not | +|---|---|---| +| `code-server` | `4.129.0` → `4.138.0` | the update reached `done`, but this app has no non-browser data route, so nothing proves the household's data survived it | +| `crafty-controller` | `4.10.7` → `4.11.0` | the update reached `done`, but this app has no non-browser data route, so nothing proves the household's data survived it | +| `komga` | `1.25.0` → `1.27.1` | the update reached `done`, but this app has no non-browser data route, so nothing proves the household's data survived it | +| `outline` | `1.9.1` → `1.10.1` | the update ended `failed` — fixture ran and found no non-browser seed route: sign-in requires an external identity provider (OIDC/Slack/Google); no local sign-up route exists | +| `plex` | `1.41.4.9463-630c9f557` → `1.43.4.10903-e5521bd8c` | the update reached `done`, but this app has no non-browser data route, so nothing proves the household's data survived it | +| `rallly` | `4.11.1` → `4.15.2` | the update reached `done`, but this app has no non-browser data route, so nothing proves the household's data survived it | + +**No upstream edge tonight, so nothing to propose (16):** `calcom`, `calibre-web`, `claper`, `gokapi`, `gramps-web`, `homebox`, `homepage`, `jellyfin`, `kimai`, `onlyoffice`, `paperless-ngx`, `plant-it`, `recipe-importer`, `seerr`, `sparkyfitness`, `wanderer`. + diff --git a/documentation/audits/the-28-2026-09-22/BODY-r630.md b/documentation/audits/the-28-2026-09-22/BODY-r630.md new file mode 100644 index 00000000..473ff9cc --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/BODY-r630.md @@ -0,0 +1,39 @@ +--- + +## 5.1 — what the guarded Update does when NO probe exists (R-630) + +`paperless-ngx` has no container whose name equals or begins with its stack name, so +`findProbeContainer` returns `""` and `RunHealthProbes` skips the stack silently. **Its probe has +never run on any box.** The open question was what `verifying` — which waits on that same probe — +does when there is nothing to wait on: pass at once, wait out the timeout, or hold. + +**It waits out the full timeout and then HOLDS, stopping a working app.** + +Deployed on 9202, all three containers reported **`healthy`**, the controller read **`running`**, the +front door answered **302**. No upstream edge exists for paperless-ngx tonight, so the Update was +pressed on the **same version** — which is what a household does on an up-to-date app, and it still +walks the whole phase machine. That difference is stated, not glossed. + +| phase | at | +|---|---| +| `checking` → `safety-dump` → `pinning` → `pulling` | 0.0–1.1 s | +| `starting` | +2.1 s | +| `verifying` | +3.1 s | +| **`failed`** | **+313.0 s — the app STOPPED** | + +Afterwards: controller state **`stopped`**, front door **404**. + +**The controller names the cause itself, so no inference was needed:** + +> `update paperless-ngx FAILED after the new version was started: not healthy: not healthy within +> 5m0s (last: no probe container) — stopping and HOLDING the app; the pin stays on the new version +> (its migration may have run)` + +**`no probe container`.** And the hold sentence is correct about the route back — for this class-A +app it warns *„csak a beállításokat és az adatbázist tartalmazza, a fájlokat nem"*. + +**This is R-618's outcome reached by the opposite road.** There a probe named a port the app does not +answer; here no probe exists at all — and the static gate cannot see it, because there is nothing to +compare. The gate does print it as a WARNING on every push, which is how it was found. **R-630 is +raised P2 → P1.** + diff --git a/documentation/audits/the-28-2026-09-22/BODY-sidejobs.md b/documentation/audits/the-28-2026-09-22/BODY-sidejobs.md new file mode 100644 index 00000000..965e40e8 --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/BODY-sidejobs.md @@ -0,0 +1,28 @@ +--- + +## 5.2 — the templates the static gate cannot judge (R-631) + +The probe gate's oracle is the probed service's own compose healthcheck. For five templates there is +no such oracle, or the paths differ on a check that cannot fail. A static rule cannot settle any of +them; asking the running container can. Each was deployed on 9202, its listening sockets read from +inside the container, and the probe's own target dialled **on the compose network** — the same call +the controller makes. + +| app | probe | what it listens on | the probe's own dial | verdict | +|---|---|---|---|---| +| `mealie` | `tcp` 9000 | `0.0.0.0:9000` | 200 | **correct** | +| `uptime-kuma` | `http` 3001 | `*:3001` | 302 | **correct** — `http` calls any response healthy, and 302 proves something answers | +| `vikunja` | `api` 3456 `/api/v1/info` **expect 200** | (busybox: no `ss`, no `netstat`) | **200** | **correct** — and this is the one that could have failed, because its `expect` block compares the code | +| `home-assistant` | `api` 8123 `/api/` **no expect** | `0.0.0.0:8123` | **401** | **correct today, and the 401 is the measurement that proves the warning** | +| `crafty-controller` | `tcp` 8443 | (not read — the app never reached `deployed`; see R-634) | **ok (1 ms)** | **correct** | + +**home-assistant is the one to carry forward.** Its probe dials `/api/` and gets **401** — not 200. +It reads healthy only because `probeHTTP` treats any response as healthy when the type is `api` with +no `expect` block (`healthprobe.go:253-262`). **Add `expect: {status: 200}` to that template — a +change that looks like a tightening — and home-assistant goes permanently unhealthy, and every +successful update of it starts stopping it.** That is R-618's failure exactly, one edit away, and it +is now a measured number rather than a caution. + +**All five are settled.** crafty-controller's reading came from its own walk rather than the side job: the controller's log shows `Health probe crafty-controller: TCP :8443 -> ok (1ms)` twice, six minutes apart, while the app was running. The gate's WARN list is therefore not a backlog of suspects: it is +four correct templates the gate honestly cannot prove, and one that is correct by accident. + diff --git a/documentation/audits/the-28-2026-09-22/BODY-teardown.md b/documentation/audits/the-28-2026-09-22/BODY-teardown.md new file mode 100644 index 00000000..c38cf5dc --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/BODY-teardown.md @@ -0,0 +1,39 @@ +--- + +## Teardown — three layers plus Gitea, every claim READ BACK + +**The machine (9202).** `controller.yaml` restored from `controller.yaml.pre-28`; `git.repo_url` +reads back as the **live** catalog with an empty token; and — the one that actually decides which +remote is followed (R-615) — the **cache** reads +`origin https://gitea.dooplex.hu/admin/app-catalog-felhom.eu.git` at **`1ad1f34`**. No drill +images. Disk 1.9 GB used of 32 GB, unchanged from the start. + +**Three things the product could NOT clear, and a shell had to.** This is not tidy-up, it is the +finding: the `termix` and `gokapi` containers left by R-633, and `sparkyfitness`'s `app.yaml` left by +R-634. `gokapi` was still `Restarting` two hours later. They were removed **by name** +(`docker rm -f termix gokapi`, `rm .../sparkyfitness/app.yaml`) — never a `prune`. Afterwards 9202 +runs exactly `felhom-controller`, `filebrowser`, `traefik`, and no `app.yaml` exists anywhere. +**A household has no shell.** Evidence: `teardown/manual-cleanup.txt`. + +**The host (demo-hp).** `pct list` before and after: `9201 demo-hp` and `9202 demo-hp-scratch`, both +running, unchanged. `pvesm status` unchanged but for expected scratch growth (`nvme-scratch` +5.19% → 6.95%). **Guest 9201 was never touched.** + +**The hub.** Nothing provisioned, nothing changed. 9202 runs `hub.enabled: false` (R-620). + +**Gitea.** The drill repo is reset to the live `main` (`1ad1f34b6e51`). **`git diff` of the live +catalog's `templates/` against the night's baseline: 0 lines, and `image:` lines changed: NONE.** + +**The fences, each read back rather than asserted:** + +| fence | at the start | at the end | +|---|---|---| +| live catalog `origin/main` | `1ad1f34b6e51` | **`1ad1f34b6e51`** | +| demo-hp guest 9201 catalog cache | `1ad1f34`, live remote | **`1ad1f34`, live remote** | +| demo-felhom guest 9201 catalog cache | `1ad1f34`, live remote | **`1ad1f34`, live remote** | +| drill repo CI jobs | **47** | **47** — no run, no mail, all night (R-629 holds) | + +**Peti's box is parked and received nothing.** Nothing ran on DooPlex beyond ordinary pushes, and +nothing on `ep0`. `felhom-controller`, `felhom-agent` and the hub were read only — **no product code +was written.** No golden, no bake, no vouch, no `--no-verify`, no branch. `local-lvm` untouched, no +`prune`, `tester-1` never reset, `drill-r50` untouched. diff --git a/documentation/audits/the-28-2026-09-22/PROGRESS.md b/documentation/audits/the-28-2026-09-22/PROGRESS.md new file mode 100644 index 00000000..9e1f496e --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/PROGRESS.md @@ -0,0 +1,10 @@ +# PROGRESS — THE TWENTY-EIGHT, 2026-09-22 night + +A resumed session reads this FIRST. Appended as each step finishes, never in advance. + +## Baselines verified 21:30 (all five exactly as the brief states) +- felhom-controller `93cee16843ba` · felhom-agent `d9864a94bf62` · felhom.eu `a975cfde5b33` +- app-catalog-felhom.eu `1ad1f34b6e51` · app-catalog-drill `6c690a1947c6` (16 behind, to be reset) +- register: 321 rows, highest id R-632 + +## Log diff --git a/documentation/audits/the-28-2026-09-22/REWALK.txt b/documentation/audits/the-28-2026-09-22/REWALK.txt new file mode 100644 index 00000000..3626c0c1 --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/REWALK.txt @@ -0,0 +1 @@ +outline sparkyfitness crafty-controller ghost wanderer claper calcom paperless-ngx rallly diff --git a/documentation/audits/the-28-2026-09-22/STATUS-draft.md b/documentation/audits/the-28-2026-09-22/STATUS-draft.md new file mode 100644 index 00000000..bb0d8031 --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/STATUS-draft.md @@ -0,0 +1,20 @@ +# STATUS — what works, what's broken, what's next + +**Updated 2026-09-22 (overnight) — I installed and tested the _N_ apps that no test had ever touched. Here is what we now know, and the two things that are quietly wrong.** + +**Decisions I took on my own: none.** + +**What I did.** Every one of the twenty-eight apps nobody had ever installed in a test got the same walk: install it at the version our catalog offers today, put real data in through the app's own front door, back it up, update it if a newer version really exists, restore it from that backup, read the data back, and remove it. _SUMMARY_ + +**The thing I would fix first — deleting an app while it is being restored leaves a ghost.** I restored one app and deleted it fifteen seconds later. Both buttons said they worked. The app is gone from every screen — and a container is still running on the machine, restarting over and over, still holding a public web address. Nothing can warn you, because the machine no longer knows the app exists. **A household can press exactly those two buttons in that order.** This is the same thing we saw once before and could not explain; this time the whole seventeen-second window is recorded. + +**The best thing I saw — the machine refusing to do something dangerous, in plain Hungarian.** One app keeps its files outside the database. Its local copy does not hold those files. When I asked to restore it, the machine **refused**, and said why: it will not put an old database on top of files it does not have, the files stay where they are, and here is the button that does work. That is exactly right, and my own test script nearly recorded it as a failure. + +_FINDINGS_ + +**Rows opened and closed.** _ROWS_ + +**What needs you.** +1. **The second promotion list** — the app versions this night proved safe enough to move on the real catalog, listed in the report. Moving a version is your call. *If you do nothing:* nothing breaks; those apps drift further from upstream each month. + +**Nothing on your own machine, the tester's machine, or the off-site box was touched. No product code was written. The real catalog was never changed.** diff --git a/documentation/audits/the-28-2026-09-22/TABLE.md b/documentation/audits/the-28-2026-09-22/TABLE.md new file mode 100644 index 00000000..e0215fb0 --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/TABLE.md @@ -0,0 +1,32 @@ +| app | class | deployed | seeded | backup | edge | update | restore | removed clean | s | evidence | +|---|---|---|---|---|---|---|---|---|---|---| +| `calcom` | B volumes-only + postgres | yes | no route | 1 copy | none upstream | — | failed | yes | 675.8 | `apps/calcom/` | +| `calibre-web` | A file-leg | yes | no route | 1 copy | none upstream | — | failed | yes | 187.5 | `apps/calibre-web/` | +| `claper` | B volumes-only + postgres | yes | no route | 1 copy | none upstream | — | ok | yes | 255.3 | `apps/claper/` | +| `code-server` | B volumes-only | yes | route failed | 1 copy | `4.129.0` → `4.138.0` | done | ok | yes | 299.9 | `apps/code-server/` | +| `crafty-controller` | B volumes-only | yes | no route | 1 copy | `4.10.7` → `4.11.0` | done | ok | yes | 288.5 | `apps/crafty-controller/` | +| `emby` | B volumes-only | yes | yes | 1 copy | `4.10.0.20` → `4.11.0.1` | done | ok | yes | 198.7 | `apps/emby/` | +| `ghost` | B volumes-only | yes | yes | 1 copy | `6.53.0-alpine` → `6.64.0-alpine` | done | ok | yes | 289.6 | `apps/ghost/` | +| `gokapi` | B volumes-only | yes | no route | 1 copy | none upstream | — | ok | **no** | 132.3 | `apps/gokapi/` | +| `gramps-web` | B volumes-only | yes | route failed | 1 copy | none upstream | — | ok | yes | 334.1 | `apps/gramps-web/` | +| `homebox` | B volumes-only | yes | route failed | 1 copy | none upstream | — | ok | yes | 127.9 | `apps/homebox/` | +| `homepage` | B volumes-only | yes | no route | 1 copy | none upstream | — | ok | yes | 137.1 | `apps/homepage/` | +| `immich` | A file-leg + postgres+redis | yes | yes | 1 copy | `v3.0.3` → `v3.2.2` | done | refused-with-a-sentence | yes | 375.0 | `apps/immich/` | +| `jellyfin` | B volumes-only | yes | route failed | 1 copy | none upstream | — | ok | yes | 220.7 | `apps/jellyfin/` | +| `kimai` | B volumes-only + mariadb | yes | route failed | 1 copy | none upstream | — | ok | yes | 311.3 | `apps/kimai/` | +| `komga` | A file-leg | yes | route failed | 1 copy | `1.25.0` → `1.27.1` | done | ok | yes | 251.9 | `apps/komga/` | +| `onlyoffice` | B volumes-only | yes | route failed | 1 copy | none upstream | — | ok | yes | 227.3 | `apps/onlyoffice/` | +| `outline` | B volumes-only + postgres+redis | yes | route failed | 1 copy | `1.9.1` → `1.10.1` | failed | failed | yes | 1389.6 | `apps/outline/` | +| `paperless-ngx` | A file-leg + postgres+redis | yes | no route | 1 copy | none upstream | — | refused-with-a-sentence | yes | 253.2 | `apps/paperless-ngx/` | +| `plant-it` | B volumes-only | **no** | no route | — | none upstream | — | not-attempted | yes | 78.2 | `apps/plant-it/` | +| `plex` | B volumes-only | yes | route failed | 1 copy | `1.41.4.9463-630c9f557` → `1.43.4.10903-e5521bd8c` | done | ok | yes | 262.1 | `apps/plex/` | +| `radarr` | B volumes-only | yes | yes | 1 copy | `6.3.0` → `6.4.4` | done | ok | yes | 227.8 | `apps/radarr/` | +| `rallly` | B volumes-only + postgres | yes | route failed | 1 copy | `4.11.1` → `4.15.2` | done | ok | yes | 230.1 | `apps/rallly/` | +| `recipe-importer` | B volumes-only | yes | no route | 1 copy | none upstream | — | ok | yes | 123.0 | `apps/recipe-importer/` | +| `seerr` | B volumes-only | yes | no route | 1 copy | none upstream | — | ok | yes | 218.8 | `apps/seerr/` | +| `sonarr` | B volumes-only | yes | yes | 1 copy | `4.0.19` → `4.0.20` | done | ok | yes | 216.6 | `apps/sonarr/` | +| `sparkyfitness` | B volumes-only + postgres | **no** | no route | — | none upstream | — | not-attempted | **no** | 534.0 | `apps/sparkyfitness/` | +| `termix` | B volumes-only | yes | yes | 1 copy | `2.5.0` → `2.8.0` | done | ok | **no** | 232.5 | `apps/termix/` | +| `wanderer` | B volumes-only + meilisearch | yes | no route | 1 copy | none upstream | — | ok | yes | 400.5 | `apps/wanderer/` | + +**Totals:** **14** no-edge · **6** proven · **5** inconclusive · **2** could-not-deploy · **1** failed — 28 of 28 recorded. diff --git a/documentation/audits/the-28-2026-09-22/apps/calcom/log.txt b/documentation/audits/the-28-2026-09-22/apps/calcom/log.txt new file mode 100644 index 00000000..b97adf0a --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/apps/calcom/log.txt @@ -0,0 +1,18 @@ +15:26:57 ==== calcom (sub=calcom, class=db, edge=none) +15:26:57 [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'} +15:31:49 [1] deployed, controller state=running, pinned={'calcom': 'calcom/cal.com:v6.2.0', 'calcom-postgres': 'postgres:16-alpine'} +15:31:53 [1] front door 502 controller state=running +15:31:53 [2] NO FIXTURE — recorded inconclusive for the data half, not faked +15:31:53 [4] „Mentés most" -> 200 {'ok': True, 'message': 'Mentés elindítva'} +15:32:13 [4] backup idle; last=None +15:32:13 [4] backups page offers 1 restorable copy(ies) +15:32:13 [R] restoring calcom from snapshot 'helyi' (of 1 offered) +15:32:13 [R] POST /backup/restore -> HTTP/2 302 ['location: /backups/restore?flash=flash.restore.started'] +15:32:13 + 0.0s restore (True, None, None) +15:32:58 + 44.6s restore (False, None, None) +15:32:58 [R] after restore: state=running hold=None phase=None +15:33:17 [6] restore -> failed, seed back = False +15:36:39 [X] stop -> 200 {'ok': True, 'message': 'Stack calcom stop completed'} +15:36:44 [X] remove (with drive data) -> 200 {'ok': True, 'data': {'removed': 'calcom', 'volumes_removed': [], 'hdd_paths_removed': [], 'hdd_paths_preserved': [], 'hdd_note': 'Az alkalmazás nem tárolt sajá +15:36:53 [X] after remove: deployed=False leftovers='/opt/docker/stacks/calcom' +15:37:56 [7] 60 s after remove: clean=True '' diff --git a/documentation/audits/the-28-2026-09-22/apps/calcom/traceback.txt b/documentation/audits/the-28-2026-09-22/apps/calcom/traceback.txt new file mode 100644 index 00000000..776a241b --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/apps/calcom/traceback.txt @@ -0,0 +1,5 @@ +Traceback (most recent call last): + File "/mnt/5_hdd/felhom.eu/git/felhom.eu/documentation/audits/the-28-2026-09-22/walk28.py", line 169, in main + m = re.search(r"flash_error=([^&\s]+)", loc) + ^^ +NameError: name 're' is not defined. Did you mean: 'rec'? Or did you forget to import 're'? diff --git a/documentation/audits/the-28-2026-09-22/apps/calcom/verdict.json b/documentation/audits/the-28-2026-09-22/apps/calcom/verdict.json new file mode 100644 index 00000000..d5d02d99 --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/apps/calcom/verdict.json @@ -0,0 +1,45 @@ +{ + "harness_version": 2, + "app": "calcom", + "venue": "guest 9202 demo-hp-scratch, controller 0.261.0, drill catalog", + "class": "db", + "from": { + "calcom": "calcom/cal.com:v6.2.0", + "calcom-postgres": "postgres:16-alpine" + }, + "to": {}, + "verdict": "no-edge", + "deployed": true, + "seed_route": "none written", + "seed_read_before": false, + "seed_read_after_update": false, + "backup": { + "snapshots_offered": 1, + "first": { + "time": "2026-09-22T13:32:12Z", + "short_id": "helyi", + "tier": 1, + "drive_label": "Belső SSD (rendszer)" + } + }, + "restore_verdict": "failed", + "seed_read_after_restore": false, + "healthy_after": false, + "migration_observed": null, + "removed_clean": true, + "edge": null, + "duration_s": 675.8, + "measured_at": "2026-09-22T13:26:57.631421+00:00", + "evidence": "the-28-2026-09-22/apps/calcom/", + "notes": [ + "no fixture: no non-browser seed route was written for this app" + ], + "front_door_after_deploy": { + "rc": 0, + "code": "502" + }, + "restore_raw": "{'ok': True, 'snapshot_id': 'helyi', 'snapshots': [{'time': '2026-09-22T13:32:12Z', 'short_id': 'helyi', 'tier': 1, 'drive_label': 'Belső SSD (rendszer)'}], 'http': 'HTTP/2 302', 'location': ['location: /backups/restore?flash=flash.restore.started'], 'seconds': 44.6, 'state_after': 'running', 'hold_after': None, 'observables_after': {'pinned_images': {'calcom': 'calcom/cal.com:v6.2.0', 'calcom-pos", + "restore_refusal": null, + "restore_state_seen": "starting", + "remove_leftovers": "" +} \ No newline at end of file diff --git a/documentation/audits/the-28-2026-09-22/apps/calibre-web/log.txt b/documentation/audits/the-28-2026-09-22/apps/calibre-web/log.txt new file mode 100644 index 00000000..78a92501 --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/apps/calibre-web/log.txt @@ -0,0 +1,21 @@ +13:32:48 ==== calibre-web (sub=calibre-web, class=file-leg, edge=none) +13:32:51 [1] made the drive paths this app requires: ['/mnt/felhom-drives/scratch_hdd/userdata/calibre-web'] +13:32:51 [1] required fields filled beyond DOMAIN/SUBDOMAIN: ['HDD_PATH'] +13:32:51 [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'} +13:33:47 [1] deployed, controller state=running, pinned={'calibre-web': 'crocodilestick/calibre-web-automated:v4.0.6'} +13:33:47 [1] front door 302 controller state=running +13:33:47 [2] NO FIXTURE — recorded inconclusive for the data half, not faked +13:33:47 [4] „Mentés most" -> 409 {'ok': False, 'error': 'Mentés már folyamatban'} +13:34:07 [4] backup idle; last=None +13:34:07 [4] backups page offers 1 restorable copy(ies) +13:34:07 [R] restoring calibre-web from snapshot 'helyi' (of 1 offered) +13:34:07 [R] POST /backup/restore -> HTTP/2 302 ['location: /backups/restore?flash_error=Ez+a+ment%C3%A9s+nem+tartalmazza+az+alkalmaz%C3%A1s+f%C3%A1jljait%2C+ez%C3%A9rt+nem+%C3%A1ll%C3%ADtjuk+vissza+az+adatb%C3%A1zist+f%C3%B6l%C3%A9j%C3%BCk+%E2%80%94+a+f%C3%A1jlok+%C3%ADgy+a+hely%C3%BCk%C3%B6n+maradnak.+A+f%C3%A1jlok+a+t%C3%A1voli+m%C3%A1solatb%C3%B3l+%C3%A1ll%C3%ADthat%C3%B3k+vissza%3A+Biztons%C3%A1gi+ment%C3%A9s+%E2%86%92+Vissza%C3%A1ll%C3%ADt%C3%A1s%2C+%E2%80%9ETeljes+vissza%C3%A1ll%C3%ADt%C3%A1s+%28f%C3%A1jlok+%2B+adatb%C3%A1zis%29%E2%80%9D.'] +13:34:07 + 0.0s restore (False, None, None) +13:34:13 [R] after restore: state=running hold=None phase=None +13:34:25 [6] restore -> failed, seed back = False +13:34:36 [X] stop -> 200 {'ok': True, 'message': 'Stack calibre-web stop completed'} +13:34:41 [X] remove (with drive data) -> 409 {'ok': False, 'error': 'A(z) /mnt/felhom-drives/scratch_hdd/userdata/calibre-web tárhely jelenleg nem elérhető — az alkalmazás nem távolítható el, amíg a meghaj +13:34:41 [X] refused because the drive path cannot be resolved (R-442, fail-closed and right) — removing the app and KEEPING the drive data instead +13:34:41 [X] remove (keeping drive data) -> 200 {'ok': True, 'data': {'removed': 'calibre-web', 'volumes_removed': ['calibre-web_calibre_web_config'], 'hdd_paths_removed': [], 'hdd_paths_preserved': [], 'back +13:34:48 [X] after remove: deployed=False leftovers='/opt/docker/stacks/calibre-web' +13:35:52 [7] 60 s after remove: clean=True '' diff --git a/documentation/audits/the-28-2026-09-22/apps/calibre-web/verdict.json b/documentation/audits/the-28-2026-09-22/apps/calibre-web/verdict.json new file mode 100644 index 00000000..e4d779fd --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/apps/calibre-web/verdict.json @@ -0,0 +1,42 @@ +{ + "harness_version": 2, + "app": "calibre-web", + "venue": "guest 9202 demo-hp-scratch, controller 0.261.0, drill catalog", + "class": "file-leg", + "from": { + "calibre-web": "crocodilestick/calibre-web-automated:v4.0.6" + }, + "to": {}, + "verdict": "no-edge", + "deployed": true, + "seed_route": "none written", + "seed_read_before": false, + "seed_read_after_update": false, + "backup": { + "snapshots_offered": 1, + "first": { + "time": "2026-09-22T11:33:53Z", + "short_id": "helyi", + "tier": 1, + "drive_label": "calibre-web" + } + }, + "restore_verdict": "failed", + "seed_read_after_restore": false, + "healthy_after": false, + "migration_observed": null, + "removed_clean": true, + "edge": null, + "duration_s": 187.5, + "measured_at": "2026-09-22T11:32:48.562046+00:00", + "evidence": "the-28-2026-09-22/apps/calibre-web/", + "notes": [ + "no fixture: no non-browser seed route was written for this app" + ], + "front_door_after_deploy": { + "rc": 0, + "code": "302" + }, + "restore_raw": "{'ok': True, 'snapshot_id': 'helyi', 'snapshots': [{'time': '2026-09-22T11:33:53Z', 'short_id': 'helyi', 'tier': 1, 'drive_label': 'calibre-web'}], 'http': 'HTTP/2 302', 'location': ['location: /backups/restore?flash_error=Ez+a+ment%C3%A9s+nem+tartalmazza+az+alkalmaz%C3%A1s+f%C3%A1jljait%2C+ez%C3%A9rt+nem+%C3%A1ll%C3%ADtjuk+vissza+az+adatb%C3%A1zist+f%C3%B6l%C3%A9j%C3%BCk+%E2%80%94+a+f%C3%A1jlok+%", + "remove_leftovers": "" +} \ No newline at end of file diff --git a/documentation/audits/the-28-2026-09-22/apps/claper/log.txt b/documentation/audits/the-28-2026-09-22/apps/claper/log.txt new file mode 100644 index 00000000..1ae44865 --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/apps/claper/log.txt @@ -0,0 +1,18 @@ +15:22:42 ==== claper (sub=claper, class=db, edge=none) +15:22:42 [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'} +15:24:08 [1] deployed, controller state=running, pinned={'claper': 'ghcr.io/claperco/claper:2.5', 'claper-postgres': 'postgres:16-alpine'} +15:24:08 [1] front door 200 controller state=running +15:24:08 [2] NO FIXTURE — recorded inconclusive for the data half, not faked +15:24:08 [4] „Mentés most" -> 200 {'ok': True, 'message': 'Mentés elindítva'} +15:24:28 [4] backup idle; last=None +15:24:28 [4] backups page offers 1 restorable copy(ies) +15:24:28 [R] restoring claper from snapshot 'helyi' (of 1 offered) +15:24:28 [R] POST /backup/restore -> HTTP/2 302 ['location: /backups/restore?flash=flash.restore.started'] +15:24:28 + 0.0s restore (True, None, None) +15:24:56 + 28.4s restore (False, None, None) +15:24:56 [R] after restore: state=running hold=None phase=None +15:25:14 [6] restore -> ok, seed back = False +15:25:35 [X] stop -> 200 {'ok': True, 'message': 'Stack claper stop completed'} +15:25:41 [X] remove (with drive data) -> 200 {'ok': True, 'data': {'removed': 'claper', 'volumes_removed': [], 'hdd_paths_removed': [], 'hdd_paths_preserved': [], 'hdd_note': 'Az alkalmazás nem tárolt sajá +15:25:49 [X] after remove: deployed=False leftovers='/opt/docker/stacks/claper' +15:26:52 [7] 60 s after remove: clean=True '' diff --git a/documentation/audits/the-28-2026-09-22/apps/claper/traceback.txt b/documentation/audits/the-28-2026-09-22/apps/claper/traceback.txt new file mode 100644 index 00000000..776a241b --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/apps/claper/traceback.txt @@ -0,0 +1,5 @@ +Traceback (most recent call last): + File "/mnt/5_hdd/felhom.eu/git/felhom.eu/documentation/audits/the-28-2026-09-22/walk28.py", line 169, in main + m = re.search(r"flash_error=([^&\s]+)", loc) + ^^ +NameError: name 're' is not defined. Did you mean: 'rec'? Or did you forget to import 're'? diff --git a/documentation/audits/the-28-2026-09-22/apps/claper/verdict.json b/documentation/audits/the-28-2026-09-22/apps/claper/verdict.json new file mode 100644 index 00000000..21cb1952 --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/apps/claper/verdict.json @@ -0,0 +1,44 @@ +{ + "harness_version": 2, + "app": "claper", + "venue": "guest 9202 demo-hp-scratch, controller 0.261.0, drill catalog", + "class": "db", + "from": { + "claper": "ghcr.io/claperco/claper:2.5", + "claper-postgres": "postgres:16-alpine" + }, + "to": {}, + "verdict": "no-edge", + "deployed": true, + "seed_route": "none written", + "seed_read_before": false, + "seed_read_after_update": false, + "backup": { + "snapshots_offered": 1, + "first": { + "time": "2026-09-22T13:24:26Z", + "short_id": "helyi", + "tier": 1, + "drive_label": "Belső SSD (rendszer)" + } + }, + "restore_verdict": "ok", + "seed_read_after_restore": false, + "healthy_after": false, + "migration_observed": null, + "removed_clean": true, + "edge": null, + "duration_s": 255.3, + "measured_at": "2026-09-22T13:22:42.289528+00:00", + "evidence": "the-28-2026-09-22/apps/claper/", + "notes": [ + "no fixture: no non-browser seed route was written for this app" + ], + "front_door_after_deploy": { + "rc": 0, + "code": "200" + }, + "restore_raw": "{'ok': True, 'snapshot_id': 'helyi', 'snapshots': [{'time': '2026-09-22T13:24:26Z', 'short_id': 'helyi', 'tier': 1, 'drive_label': 'Belső SSD (rendszer)'}], 'http': 'HTTP/2 302', 'location': ['location: /backups/restore?flash=flash.restore.started'], 'seconds': 28.4, 'state_after': 'running', 'hold_after': None, 'observables_after': {'pinned_images': {'claper': 'ghcr.io/claperco/claper:2.5', 'clap", + "restore_refusal": null, + "remove_leftovers": "" +} \ No newline at end of file diff --git a/documentation/audits/the-28-2026-09-22/apps/code-server/app-logs-during-after.txt b/documentation/audits/the-28-2026-09-22/apps/code-server/app-logs-during-after.txt new file mode 100644 index 00000000..0727a0fb --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/apps/code-server/app-logs-during-after.txt @@ -0,0 +1,39 @@ +code-server | [migrations] started +code-server | [migrations] no migrations found +code-server | ─────────────────────────────────────── +code-server | +code-server | ██╗ ███████╗██╗ ██████╗ +code-server | ██║ ██╔════╝██║██╔═══██╗ +code-server | ██║ ███████╗██║██║ ██║ +code-server | ██║ ╚════██║██║██║ ██║ +code-server | ███████╗███████║██║╚██████╔╝ +code-server | ╚══════╝╚══════╝╚═╝ ╚═════╝ +code-server | +code-server | Brought to you by linuxserver.io +code-server | ─────────────────────────────────────── +code-server | +code-server | To support LSIO projects visit: +code-server | https://www.linuxserver.io/donate/ +code-server | +code-server | ─────────────────────────────────────── +code-server | GID/UID +code-server | ─────────────────────────────────────── +code-server | +code-server | User UID: 1000 +code-server | User GID: 1000 +code-server | ─────────────────────────────────────── +code-server | Linuxserver.io version: 4.138.0-ls366 +code-server | Build-date: 2026-09-22T10:14:48+00:00 +code-server | ─────────────────────────────────────── +code-server | +code-server | [custom-init] No custom files found, skipping... +code-server | [2026-09-22T11:53:45.337Z] info code-server 4.138.0 59c988c744a240b05b039f57b856a5312f19d5b1 +code-server | [2026-09-22T11:53:45.339Z] info Using user-data-dir /config/data +code-server | [2026-09-22T11:53:45.369Z] info Using config file /config/.config/code-server/config.yaml +code-server | [2026-09-22T11:53:45.369Z] info HTTP server listening on http://[::]:8443/ +code-server | [2026-09-22T11:53:45.369Z] info - Authentication is enabled +code-server | [2026-09-22T11:53:45.370Z] info - Using password from $PASSWORD +code-server | [2026-09-22T11:53:45.371Z] info - Not serving HTTPS +code-server | [2026-09-22T11:53:45.371Z] info Session server listening on /config/data/code-server-ipc.sock +code-server | Connection to 127.0.0.1 8443 port [tcp/*] succeeded! +code-server | [ls.io-init] done. diff --git a/documentation/audits/the-28-2026-09-22/apps/code-server/log.txt b/documentation/audits/the-28-2026-09-22/apps/code-server/log.txt new file mode 100644 index 00000000..3afffa0c --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/apps/code-server/log.txt @@ -0,0 +1,29 @@ +13:51:00 ==== code-server (sub=code, class=file-leg, edge={'from': 'lscr.io/linuxserver/code-server:4.129.0', 'to': 'lscr.io/linuxserver/code-server:4.138.0'}) +13:51:00 [1] required fields filled beyond DOMAIN/SUBDOMAIN: ['PASSWORD'] +13:51:00 [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'} +13:51:40 [1] deployed, controller state=running, pinned={'code-server': 'lscr.io/linuxserver/code-server:4.129.0'} +13:51:40 [1] front door 502 controller state=running +13:51:51 code-server: no non-browser seed route — its front door is a browser IDE behind one password; it exposes no data API, and writing a file with docker exec would not be the front door (R-156) +13:51:51 [2] fixture found no route — inconclusive for the data half +13:51:51 [4] „Mentés most" -> 409 {'ok': False, 'error': 'Mentés már folyamatban'} +13:52:56 [4] backup idle; last=None +13:52:56 [4] backups page offers 1 restorable copy(ies) +13:52:57 [5] drill commit 10e7d44e0be4: code-server lscr.io/linuxserver/code-server:4.129.0 -> lscr.io/linuxserver/code-server:4.138.0 (push rc=0) +13:53:14 [sync] the badge needed 16.8s and 3 sync+rescan rounds to catch up to lscr.io/linuxserver/code-server:4.138.0 — R-607's window, measured +13:53:14 [5] badge {'hu': [{'title': 'Újabb változat érhető el ehhez az alkalmazáshoz. A frissítés indításához nyomd meg a Frissítés gombot.', 'text': 'Frissítés elérhető — ma'}], 'en': [{'title': 'A newer version of this app is available. Select the Update button to start it.', 'text': 'Update available — today'}]} +13:53:14 [6] Update -> 202 {'ok': True, 'data': {'accepted': True, 'completed': False}, 'message': 'Frissítés elindult – az állapot a kártyán követhető'} +13:53:14 + 0.0s phase=safety-dump label=Adatbázis pillanatkép… err=None hold=None +13:53:15 + 1.0s phase=pulling label=Új verzió letöltése… err=None hold=None +13:53:40 + 25.7s phase=starting label=Indítás az új verzióval… err=None hold=None +13:53:44 + 29.8s phase=verifying label=Működés ellenőrzése… err=None hold=None +13:53:49 + 35.0s phase=done label=Frissítve err=None hold=None +13:53:52 [R] restoring code-server from snapshot 'helyi' (of 1 offered) +13:53:52 [R] POST /backup/restore -> HTTP/2 302 ['location: /backups/restore?flash=flash.restore.started'] +13:53:52 + 0.0s restore (True, None, None) +13:54:06 + 14.2s restore (False, None, None) +13:54:06 [R] after restore: state=running hold=None phase=done +13:54:24 [6] restore -> ok, seed back = False +13:54:43 [X] stop -> 200 {'ok': True, 'message': 'Stack code-server stop completed'} +13:54:49 [X] remove (with drive data) -> 200 {'ok': True, 'data': {'removed': 'code-server', 'volumes_removed': [], 'hdd_paths_removed': [], 'hdd_paths_preserved': [], 'hdd_note': 'Az alkalmazás nem tárolt +13:54:57 [X] after remove: deployed=False leftovers='/opt/docker/stacks/code-server' +13:55:59 [7] 60 s after remove: clean=True '' diff --git a/documentation/audits/the-28-2026-09-22/apps/code-server/verdict.json b/documentation/audits/the-28-2026-09-22/apps/code-server/verdict.json new file mode 100644 index 00000000..af5eab45 --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/apps/code-server/verdict.json @@ -0,0 +1,134 @@ +{ + "harness_version": 2, + "app": "code-server", + "venue": "guest 9202 demo-hp-scratch, controller 0.261.0, drill catalog", + "class": "file-leg", + "from": { + "code-server": "lscr.io/linuxserver/code-server:4.129.0" + }, + "to": { + "code-server": "lscr.io/linuxserver/code-server:4.138.0" + }, + "verdict": "inconclusive", + "deployed": true, + "seed_route": "none — its front door is a browser IDE behind one password; it exposes no data API, and writing a file with docker exec would not be the front door (R-156)", + "seed_read_before": false, + "seed_read_after_update": false, + "backup": { + "snapshots_offered": 1, + "first": { + "time": "2026-09-22T11:52:06Z", + "short_id": "helyi", + "tier": 1, + "drive_label": "Belső SSD (rendszer)" + } + }, + "restore_verdict": "ok", + "seed_read_after_restore": false, + "healthy_after": true, + "migration_observed": "code-server | [migrations] started", + "removed_clean": true, + "edge": { + "from": "lscr.io/linuxserver/code-server:4.129.0", + "to": "lscr.io/linuxserver/code-server:4.138.0" + }, + "duration_s": 299.9, + "measured_at": "2026-09-22T11:51:00.021346+00:00", + "evidence": "the-28-2026-09-22/apps/code-server/", + "notes": [ + "fixture ran and found no non-browser seed route: its front door is a browser IDE behind one password; it exposes no data API, and writing a file with docker exec would not be the front door (R-156)", + "teardown error: AttributeError: 'NoneType' object has no attribute 'get'" + ], + "front_door_after_deploy": { + "rc": 0, + "code": "502" + }, + "drill_commit": "10e7d44e0be4", + "badge_seconds": 16.8, + "badges": { + "hu": [ + { + "title": "Újabb változat érhető el ehhez az alkalmazáshoz. A frissítés indításához nyomd meg a Frissítés gombot.", + "text": "Frissítés elérhető — ma" + } + ], + "en": [ + { + "title": "A newer version of this app is available. Select the Update button to start it.", + "text": "Update available — today" + } + ] + }, + "phases": { + "accepted": true, + "http": "202", + "phases": [ + { + "t": 0.0, + "phase": "safety-dump", + "label": "Adatbázis pillanatkép…", + "updating": true, + "error": null, + "hold": null + }, + { + "t": 1.0, + "phase": "pulling", + "label": "Új verzió letöltése…", + "updating": true, + "error": null, + "hold": null + }, + { + "t": 25.7, + "phase": "starting", + "label": "Indítás az új verzióval…", + "updating": true, + "error": null, + "hold": null + }, + { + "t": 29.8, + "phase": "verifying", + "label": "Működés ellenőrzése…", + "updating": true, + "error": null, + "hold": null + }, + { + "t": 35.0, + "phase": "done", + "label": "Frissítve", + "updating": false, + "error": null, + "hold": null + } + ], + "duration_s": 35.0, + "final_phase": "done", + "update_error": null, + "hold_reason": null, + "state": "running" + }, + "observables": { + "pinned_images": { + "code-server": "lscr.io/linuxserver/code-server:4.138.0" + }, + "installed_images": { + "code-server": "lscr.io/linuxserver/code-server:4.138.0" + }, + "catalog_images": { + "code-server": "lscr.io/linuxserver/code-server:4.138.0" + }, + "live_compose_image_lines": [ + "image: lscr.io/linuxserver/code-server:4.138.0" + ], + "docker_inspect": [ + "code-server lscr.io/linuxserver/code-server:4.138.0 running=true restarts=0" + ] + }, + "update_phase_final": "done", + "restore_raw": "{'ok': True, 'snapshot_id': 'helyi', 'snapshots': [{'time': '2026-09-22T11:52:06Z', 'short_id': 'helyi', 'tier': 1, 'drive_label': 'Belső SSD (rendszer)'}], 'http': 'HTTP/2 302', 'location': ['location: /backups/restore?flash=flash.restore.started'], 'seconds': 14.2, 'state_after': 'running', 'hold_after': None, 'observables_after': {'pinned_images': {'code-server': 'lscr.io/linuxserver/code-serve", + "restore_refusal": null, + "remove_leftovers": "" +} \ No newline at end of file diff --git a/documentation/audits/the-28-2026-09-22/apps/crafty-controller/app-logs-during-after.txt b/documentation/audits/the-28-2026-09-22/apps/crafty-controller/app-logs-during-after.txt new file mode 100644 index 00000000..b4b3bab9 --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/apps/crafty-controller/app-logs-during-after.txt @@ -0,0 +1,32 @@ +crafty-controller | Wrapper | 🚂✅ SteamCMD dependencies are available in this image! +crafty-controller | Wrapper | 🚀 Launching crafty with [-d -i] +crafty-controller | Logging set to: 0 +crafty-controller | +crafty-controller | /////////////////////////////////////////////////////////////////////////// +crafty-controller | # Welcome to Crafty Controller - v.4.11.0 # +crafty-controller | /////////////////////////////////////////////////////////////////////////// +crafty-controller | # Server Manager / Web Portal for your Minecraft server # +crafty-controller | # Homepage: www.craftycontrol.com # +crafty-controller | /////////////////////////////////////////////////////////////////////////// +crafty-controller | +crafty-controller | [+] Crafty: 09/22/26 15:08:57 - INFO: Starting migrations +crafty-controller | [+] Crafty: 09/22/26 15:08:57 - INFO: Checking for reset secret flag +crafty-controller | [+] Crafty: 09/22/26 15:08:57 - INFO: No flag found. Secrets are staying +crafty-controller | [+] Crafty: 09/22/26 15:08:57 - INFO: Checking for remote changes to config.json +crafty-controller | [+] Crafty: 09/22/26 15:08:57 - INFO: Remote change complete. +crafty-controller | [+] Crafty: 09/22/26 15:08:57 - INFO: Initializing all servers defined +crafty-controller | [+] Crafty: 09/22/26 15:08:58 - INFO: Crafty started in daemon mode, no shell will be printed +crafty-controller | +crafty-controller | [+] Crafty: 09/22/26 15:08:58 - INFO: Setting up Crafty's internal components... +crafty-controller | [+] Crafty: 09/22/26 15:08:58 - INFO: https://172.18.0.3:8443 is up and ready for connections. +crafty-controller | [+] Crafty: 09/22/26 15:08:58 - INFO: Server Init Complete: Listening For Connections! +crafty-controller | [+] Crafty: 09/22/26 15:09:00 - INFO: Stats collection frequency set to 30 seconds +crafty-controller | [+] Crafty: 09/22/26 15:09:00 - INFO: Launching Scheduler Thread... +crafty-controller | [+] Crafty: 09/22/26 15:09:00 - INFO: Launching command thread... +crafty-controller | [+] Crafty: 09/22/26 15:09:00 - INFO: Launching log watcher... +crafty-controller | [+] Crafty: 09/22/26 15:09:00 - INFO: Launching realtime thread... +crafty-controller | +crafty-controller | [+] Crafty: 09/22/26 15:09:02 - INFO: Checking Internet. This may take a minute. +crafty-controller | [+] Crafty: 09/22/26 15:09:04 - INFO: Execution Mode: Non-interactive (e.g. 'python main.py') +crafty-controller | [+] Crafty: 09/22/26 15:09:04 - INFO: Application path: '/crafty' +crafty-controller | [+] Crafty: 09/22/26 15:09:04 - INFO: Crafty has fully started and is now ready for use! diff --git a/documentation/audits/the-28-2026-09-22/apps/crafty-controller/log.txt b/documentation/audits/the-28-2026-09-22/apps/crafty-controller/log.txt new file mode 100644 index 00000000..48acddb7 --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/apps/crafty-controller/log.txt @@ -0,0 +1,28 @@ +15:06:23 ==== crafty-controller (sub=crafty-controller, class=file-leg, edge={'from': 'registry.gitlab.com/crafty-controller/crafty-4:4.10.7', 'to': 'registry.gitlab.com/crafty-controller/crafty-4:4.11.0'}) +15:06:23 [1] required fields filled beyond DOMAIN/SUBDOMAIN: ['CRAFTY_PASSWORD'] +15:06:23 [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'} +15:07:39 [1] deployed, controller state=running, pinned={'crafty-controller': 'registry.gitlab.com/crafty-controller/crafty-4:4.10.7'} +15:07:39 [1] front door 302 controller state=running +15:07:39 [2] NO FIXTURE — recorded inconclusive for the data half, not faked +15:07:39 [4] „Mentés most" -> 200 {'ok': True, 'message': 'Mentés elindítva'} +15:07:44 [4] backup idle; last=None +15:07:44 [4] backups page offers 1 restorable copy(ies) +15:07:45 [5] drill commit 231fd46d8f61: crafty-controller registry.gitlab.com/crafty-controller/crafty-4:4.10.7 -> registry.gitlab.com/crafty-controller/crafty-4:4.11.0 (push rc=0) +15:08:14 [sync] the badge needed 29.0s and 5 sync+rescan rounds to catch up to registry.gitlab.com/crafty-controller/crafty-4:4.11.0 — R-607's window, measured +15:08:14 [5] badge {'hu': [{'title': 'Újabb változat érhető el ehhez az alkalmazáshoz. A frissítés indításához nyomd meg a Frissítés gombot.', 'text': 'Frissítés elérhető — ma'}], 'en': [{'title': 'A newer version of this app is available. Select the Update button to start it.', 'text': 'Update available — today'}]} +15:08:14 [6] Update -> 202 {'ok': True, 'data': {'accepted': True, 'completed': False}, 'message': 'Frissítés elindult – az állapot a kártyán követhető'} +15:08:14 + 0.0s phase=safety-dump label=Adatbázis pillanatkép… err=None hold=None +15:08:15 + 1.0s phase=pulling label=Új verzió letöltése… err=None hold=None +15:08:54 + 40.1s phase=starting label=Indítás az új verzióval… err=None hold=None +15:08:56 + 42.1s phase=verifying label=Működés ellenőrzése… err=None hold=None +15:09:07 + 52.4s phase=done label=Frissítve err=None hold=None +15:09:10 [R] restoring crafty-controller from snapshot 'helyi' (of 1 offered) +15:09:10 [R] POST /backup/restore -> HTTP/2 302 ['location: /backups/restore?flash=flash.restore.started'] +15:09:10 + 0.0s restore (True, None, None) +15:09:22 + 12.2s restore (False, None, None) +15:09:22 [R] after restore: state=running hold=None phase=done +15:09:41 [6] restore -> ok, seed back = False +15:09:52 [X] stop -> 200 {'ok': True, 'message': 'Stack crafty-controller stop completed'} +15:09:57 [X] remove (with drive data) -> 200 {'ok': True, 'data': {'removed': 'crafty-controller', 'volumes_removed': [], 'hdd_paths_removed': [], 'hdd_paths_preserved': [], 'hdd_note': 'Az alkalmazás nem +15:10:05 [X] after remove: deployed=False leftovers='/opt/docker/stacks/crafty-controller' +15:11:07 [7] 60 s after remove: clean=True '' diff --git a/documentation/audits/the-28-2026-09-22/apps/crafty-controller/verdict.json b/documentation/audits/the-28-2026-09-22/apps/crafty-controller/verdict.json new file mode 100644 index 00000000..bafba38c --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/apps/crafty-controller/verdict.json @@ -0,0 +1,133 @@ +{ + "harness_version": 2, + "app": "crafty-controller", + "venue": "guest 9202 demo-hp-scratch, controller 0.261.0, drill catalog", + "class": "file-leg", + "from": { + "crafty-controller": "registry.gitlab.com/crafty-controller/crafty-4:4.10.7" + }, + "to": { + "crafty-controller": "registry.gitlab.com/crafty-controller/crafty-4:4.11.0" + }, + "verdict": "inconclusive", + "deployed": true, + "seed_route": "none written", + "seed_read_before": false, + "seed_read_after_update": false, + "backup": { + "snapshots_offered": 1, + "first": { + "time": "2026-09-22T13:07:42Z", + "short_id": "helyi", + "tier": 1, + "drive_label": "Belső SSD (rendszer)" + } + }, + "restore_verdict": "ok", + "seed_read_after_restore": false, + "healthy_after": true, + "migration_observed": "crafty-controller | [+] Crafty: 09/22/26 15:08:57 - INFO:\tStarting migrations", + "removed_clean": true, + "edge": { + "from": "registry.gitlab.com/crafty-controller/crafty-4:4.10.7", + "to": "registry.gitlab.com/crafty-controller/crafty-4:4.11.0" + }, + "duration_s": 288.5, + "measured_at": "2026-09-22T13:06:23.446307+00:00", + "evidence": "the-28-2026-09-22/apps/crafty-controller/", + "notes": [ + "no fixture: no non-browser seed route was written for this app" + ], + "front_door_after_deploy": { + "rc": 0, + "code": "302" + }, + "drill_commit": "231fd46d8f61", + "badge_seconds": 29.0, + "badges": { + "hu": [ + { + "title": "Újabb változat érhető el ehhez az alkalmazáshoz. A frissítés indításához nyomd meg a Frissítés gombot.", + "text": "Frissítés elérhető — ma" + } + ], + "en": [ + { + "title": "A newer version of this app is available. Select the Update button to start it.", + "text": "Update available — today" + } + ] + }, + "phases": { + "accepted": true, + "http": "202", + "phases": [ + { + "t": 0.0, + "phase": "safety-dump", + "label": "Adatbázis pillanatkép…", + "updating": true, + "error": null, + "hold": null + }, + { + "t": 1.0, + "phase": "pulling", + "label": "Új verzió letöltése…", + "updating": true, + "error": null, + "hold": null + }, + { + "t": 40.1, + "phase": "starting", + "label": "Indítás az új verzióval…", + "updating": true, + "error": null, + "hold": null + }, + { + "t": 42.1, + "phase": "verifying", + "label": "Működés ellenőrzése…", + "updating": true, + "error": null, + "hold": null + }, + { + "t": 52.4, + "phase": "done", + "label": "Frissítve", + "updating": false, + "error": null, + "hold": null + } + ], + "duration_s": 52.4, + "final_phase": "done", + "update_error": null, + "hold_reason": null, + "state": "running" + }, + "observables": { + "pinned_images": { + "crafty-controller": "registry.gitlab.com/crafty-controller/crafty-4:4.11.0" + }, + "installed_images": { + "crafty-controller": "registry.gitlab.com/crafty-controller/crafty-4:4.11.0" + }, + "catalog_images": { + "crafty-controller": "registry.gitlab.com/crafty-controller/crafty-4:4.11.0" + }, + "live_compose_image_lines": [ + "image: registry.gitlab.com/crafty-controller/crafty-4:4.11.0" + ], + "docker_inspect": [ + "crafty-controller registry.gitlab.com/crafty-controller/crafty-4:4.11.0 running=true restarts=0" + ] + }, + "update_phase_final": "done", + "restore_raw": "{'ok': True, 'snapshot_id': 'helyi', 'snapshots': [{'time': '2026-09-22T13:07:42Z', 'short_id': 'helyi', 'tier': 1, 'drive_label': 'Belső SSD (rendszer)'}], 'http': 'HTTP/2 302', 'location': ['location: /backups/restore?flash=flash.restore.started'], 'seconds': 12.2, 'state_after': 'running', 'hold_after': None, 'observables_after': {'pinned_images': {'crafty-controller': 'registry.gitlab.com/craf", + "restore_refusal": null, + "remove_leftovers": "" +} \ No newline at end of file diff --git a/documentation/audits/the-28-2026-09-22/apps/emby/app-logs-during-after.txt b/documentation/audits/the-28-2026-09-22/apps/emby/app-logs-during-after.txt new file mode 100644 index 00000000..8c39abad --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/apps/emby/app-logs-during-after.txt @@ -0,0 +1,287 @@ +emby | s6-rc: info: service s6rc-oneshot-runner: starting +emby | s6-rc: info: service s6rc-oneshot-runner successfully started +emby | s6-rc: info: service fix-attrs: starting +emby | s6-rc: info: service fix-attrs successfully started +emby | s6-rc: info: service legacy-cont-init: starting +emby | s6-rc: info: service legacy-cont-init successfully started +emby | s6-rc: info: service emby-server: starting +emby | s6-rc: info: service emby-server successfully started +emby | s6-rc: info: service legacy-services: starting +emby | s6-rc: info: service legacy-services successfully started +emby | Info Main: Application path: /system/EmbyServer.dll +emby | Info NetworkManager: Adding event handler for NetworkChange.NetworkAddressChanged +emby | Info App: Setting default culture to en-us +emby | Info Main: Emby Server 4.11.0.1 +emby | Command line: /system/EmbyServer.dll -programdata /config -ffdetect /bin/ffdetect -ffmpeg /bin/ffmpeg -ffprobe /bin/ffprobe -restartexitcode 3 +emby | Operating system: Linux version 7.0.2-6-pve (build@proxmox) (gcc (Debian 14.2.0-19) 14.2.0, GNU ld (GNU Binutils for Debian) 2.44) #1 SMP PREEMPT_DYNAMIC PMX 7.0.2-6 (2 +emby | OS/Process: x64/x64 +emby | Framework: .NET 8.0.28 +emby | Runtime: system/System.Private.CoreLib.dll +emby | Processor count: 7 +emby | Data path: /config +emby | Application path: /system +emby | Info Main: Logs path: /config/logs +emby | Info Main: Cache path: /config/cache +emby | Info Main: Internal metadata path: /config/metadata +emby | Info Main: DefaultThreadCurrentCulture: en-US +emby | Info Main: DefaultThreadCurrentUICulture: en-US +emby | Info Main: DefaultThreadCurrentUICulture: en-US +emby | Info Main: Server Friendly Name: ‌7e784d1f638a‌ +emby | Info Main: Local time: 09/22/2026 14:08:54 +02:00 +emby | Info App: Emby Server Version: 4.11.0.1 +emby | Info App: Loading assemblies +emby | Info App: File /config/plugins/Emby.XmlTV.dll has version 1.2.2.0 +emby | Info App: File /system/plugins/Emby.XmlTV.dll has version 1.2.2.0 +emby | Info App: File /config/plugins/StudioImages.dll has version 1.0.3.0 +emby | Info App: File /system/plugins/StudioImages.dll has version 1.0.3.0 +emby | Info App: File /config/plugins/OpenSubtitles.dll has version 1.0.69.0 +emby | Info App: File /system/plugins/OpenSubtitles.dll has version 1.0.69.0 +emby | Info App: File /config/plugins/NfoMetadata.dll has version 1.0.86.0 +emby | Info App: File /system/plugins/NfoMetadata.dll has version 1.0.86.0 +emby | Info App: File /config/plugins/Emby.Server.CinemaMode.dll has version 1.0.52.0 +emby | Info App: File /system/plugins/Emby.Server.CinemaMode.dll has version 1.0.52.0 +emby | Info App: File /config/plugins/MusicBrainz.dll has version 1.0.30.0 +emby | Info App: File /system/plugins/MusicBrainz.dll has version 1.0.30.0 +emby | Info App: File /config/plugins/MBBackup.dll has version 1.8.6.0 +emby | Info App: File /system/plugins/MBBackup.dll has version 1.8.6.0 +emby | Info App: File /config/plugins/MovieDb.dll has version 1.9.3.0 +emby | Info App: File /system/plugins/MovieDb.dll has version 1.9.5.0 +emby | Info App: Copying plugin from /system/plugins/MovieDb.dll to /config/plugins/MovieDb.dll +emby | Info App: File /config/plugins/Emby.Webhooks.dll has version 1.0.38.0 +emby | Info App: File /system/plugins/Emby.Webhooks.dll has version 1.0.38.0 +emby | Info App: File /config/plugins/Emby.Dlna.dll has version 1.5.6.0 +emby | Info App: File /system/plugins/Emby.Dlna.dll has version 1.5.7.0 +emby | Info App: Copying plugin from /system/plugins/Emby.Dlna.dll to /config/plugins/Emby.Dlna.dll +emby | Info App: File /config/plugins/AudioDb.dll has version 1.0.21.0 +emby | Info App: File /system/plugins/AudioDb.dll has version 1.0.21.0 +emby | Info App: File /config/plugins/Fanart.dll has version 1.0.18.0 +emby | Info App: File /system/plugins/Fanart.dll has version 1.0.18.0 +emby | Info App: File /config/plugins/DvdMounter.dll has version 1.0.2.0 +emby | Info App: File /system/plugins/DvdMounter.dll has version 1.0.2.0 +emby | Info App: File /config/plugins/EmbyGuideData.dll has version 1.0.21.0 +emby | Info App: File /system/plugins/EmbyGuideData.dll has version 1.0.21.0 +emby | Info App: File /config/plugins/BlurayMounter.dll has version 1.0.5.0 +emby | Info App: File /system/plugins/BlurayMounter.dll has version 1.0.5.0 +emby | Info App: File /config/plugins/OMDb.dll has version 1.0.23.0 +emby | Info App: File /system/plugins/OMDb.dll has version 1.0.23.0 +emby | Info App: File /config/plugins/Emby.PortMapper.dll has version 1.3.0.0 +emby | Info App: File /system/plugins/Emby.PortMapper.dll has version 1.3.0.0 +emby | Info App: File /config/plugins/Tvdb.dll has version 1.6.6.0 +emby | Info App: File /system/plugins/Tvdb.dll has version 1.6.6.0 +emby | Info App: File /config/plugins/Emby.M3UTuner.dll has version 1.0.46.0 +emby | Info App: File /system/plugins/Emby.M3UTuner.dll has version 1.0.46.0 +emby | Info App: Loading Emby.Api, Version=4.11.0.1, Culture=neutral, PublicKeyToken=null +emby | Info App: Loading Emby.Web, Version=4.11.0.1, Culture=neutral, PublicKeyToken=null +emby | Info App: Loading MediaBrowser.Model, Version=4.11.0.1, Culture=neutral, PublicKeyToken=null +emby | Info App: Loading MediaBrowser.Common, Version=4.11.0.1, Culture=neutral, PublicKeyToken=null +emby | Info App: Loading MediaBrowser.Controller, Version=4.11.0.1, Culture=neutral, PublicKeyToken=null +emby | Info App: Loading Emby.Providers, Version=4.11.0.1, Culture=neutral, PublicKeyToken=null +emby | Info App: Loading Emby.Photos, Version=4.11.0.1, Culture=neutral, PublicKeyToken=null +emby | Info App: Loading Emby.Server.Implementations, Version=4.11.0.1, Culture=neutral, PublicKeyToken=null +emby | Info App: Loading Emby.LiveTV, Version=4.11.0.1, Culture=neutral, PublicKeyToken=null +emby | Info App: Loading Emby.ActivityLog, Version=4.11.0.1, Culture=neutral, PublicKeyToken=null +emby | Info App: Loading Emby.Server.MediaEncoding, Version=4.11.0.1, Culture=neutral, PublicKeyToken=null +emby | Info App: Loading Emby.LocalMetadata, Version=4.11.0.1, Culture=neutral, PublicKeyToken=null +emby | Info App: Loading Emby.Notifications, Version=4.11.0.1, Culture=neutral, PublicKeyToken=null +emby | Info App: Loading Emby.Web.GenericUI, Version=4.11.0.1, Culture=neutral, PublicKeyToken=null +emby | Info App: Loading Emby.Codecs.Dxva, Version=4.11.0.1, Culture=neutral, PublicKeyToken=null +emby | Info App: Loading Emby.Codecs, Version=4.11.0.1, Culture=neutral, PublicKeyToken=null +emby | Info App: Loading Emby.Server.Connect, Version=4.11.0.1, Culture=neutral, PublicKeyToken=null +emby | Info App: Loading Emby.Server.Sync, Version=4.11.0.1, Culture=neutral, PublicKeyToken=null +emby | Info App: Loading EmbyServer, Version=4.11.0.1, Culture=neutral, PublicKeyToken=null +emby | Info App: Loading Emby.XmlTV, Version=1.2.2.0, Culture=neutral, PublicKeyToken=null from /config/plugins/Emby.XmlTV.dll +emby | Info App: Loading StudioImages, Version=1.0.3.0, Culture=neutral, PublicKeyToken=null from /config/plugins/StudioImages.dll +emby | Info App: Loading OpenSubtitles, Version=1.0.69.0, Culture=neutral, PublicKeyToken=null from /config/plugins/OpenSubtitles.dll +emby | Info App: Loading NfoMetadata, Version=1.0.86.0, Culture=neutral, PublicKeyToken=null from /config/plugins/NfoMetadata.dll +emby | Info App: Loading Emby.Server.CinemaMode, Version=1.0.52.0, Culture=neutral, PublicKeyToken=null from /config/plugins/Emby.Server.CinemaMode.dll +emby | Info App: Loading MusicBrainz, Version=1.0.30.0, Culture=neutral, PublicKeyToken=null from /config/plugins/MusicBrainz.dll +emby | Info App: Loading MBBackup, Version=1.8.6.0, Culture=neutral, PublicKeyToken=null from /config/plugins/MBBackup.dll +emby | Info App: Loading MovieDb, Version=1.9.5.0, Culture=neutral, PublicKeyToken=null from /config/plugins/MovieDb.dll +emby | Info App: Loading Emby.Webhooks, Version=1.0.38.0, Culture=neutral, PublicKeyToken=null from /config/plugins/Emby.Webhooks.dll +emby | Info App: Loading Emby.Dlna, Version=1.5.7.0, Culture=neutral, PublicKeyToken=null from /config/plugins/Emby.Dlna.dll +emby | Info App: Loading AudioDb, Version=1.0.21.0, Culture=neutral, PublicKeyToken=null from /config/plugins/AudioDb.dll +emby | Info App: Loading Fanart, Version=1.0.18.0, Culture=neutral, PublicKeyToken=null from /config/plugins/Fanart.dll +emby | Info App: Loading DvdMounter, Version=1.0.2.0, Culture=neutral, PublicKeyToken=null from /config/plugins/DvdMounter.dll +emby | Info App: Loading EmbyGuideData, Version=1.0.21.0, Culture=neutral, PublicKeyToken=null from /config/plugins/EmbyGuideData.dll +emby | Info App: Loading BlurayMounter, Version=1.0.5.0, Culture=neutral, PublicKeyToken=null from /config/plugins/BlurayMounter.dll +emby | Info App: Loading OMDb, Version=1.0.23.0, Culture=neutral, PublicKeyToken=null from /config/plugins/OMDb.dll +emby | Info App: Loading Emby.PortMapper, Version=1.3.0.0, Culture=neutral, PublicKeyToken=null from /config/plugins/Emby.PortMapper.dll +emby | Info App: Loading Tvdb, Version=1.6.6.0, Culture=neutral, PublicKeyToken=null from /config/plugins/Tvdb.dll +emby | Info App: Loading Emby.M3UTuner, Version=1.0.46.0, Culture=neutral, PublicKeyToken=null from /config/plugins/Emby.M3UTuner.dll +emby | Info SqliteUserRepository: Initializing PooledDatabaseConnectionManager with pool size: 3 +emby | Info SqliteUserRepository: Sqlite version: 3.53.4 +emby | Info SqliteUserRepository: Sqlite compiler options: ATOMIC_INTRINSICS=1,COMPILER=gcc-13.4.0,DEFAULT_AUTOVACUUM,DEFAULT_CACHE_SIZE=-2000,DEFAULT_FILE_FORMAT=4,DEFAULT_JOURNAL_SIZE_LIMIT=-1,DEFAULT_MMAP_SIZE=0,DEFAULT_PAGE_SIZE=4096,DEFAULT_PCACHE_INITSZ=20,DEFAULT_RECURSIVE_TRIGGERS,DEFAULT_SECTOR_SIZE=4096,DEFAULT_SYNCHRONOUS=2,DEFAULT_WAL_AUTOCHECKPOINT=1000,DEFAULT_WAL_SYNCHRONOUS=2,DEFAULT_WORKER_THREADS=0,DIRECT_OVERFLOW_READ,ENABLE_COLUMN_METADATA,ENABLE_FTS3_PARENTHESIS,ENABLE_FTS3_TOKENIZER,ENABLE_FTS5,ENABLE_MATH_FUNCTIONS,ENABLE_PERCENTILE,ENABLE_UPDATE_DELETE_LIMIT,LIKE_DOESNT_MATCH_BLOBS,MALLOC_SOFT_LIMIT=1024,MAX_ATTACHED=10,MAX_COLUMN=2000,MAX_COMPOUND_SELECT=500,MAX_DEFAULT_PAGE_SIZE=8192,MAX_EXPR_DEPTH=1000,MAX_FUNCTION_ARG=1000,MAX_LENGTH=1000000000,MAX_LIKE_PATTERN_LENGTH=50000,MAX_MMAP_SIZE=0x7fff0000,MAX_PAGE_COUNT=0xfffffffe,MAX_PAGE_SIZE=65536,MAX_SCHEMA_RETRY=25,MAX_SQL_LENGTH=1000000000,MAX_TRIGGER_DEPTH=1000,MAX_VARIABLE_NUMBER=250000,MAX_VDBE_OP=250000000,MAX_WORKER_THREADS=8,MUTEX_PTHREADS,OMIT_LOOKASIDE,OMIT_SHARED_CACHE,SYSTEM_MALLOC,TEMP_STORE=1,THREADSAFE=1 +emby | Info SqliteUserRepository: Sqlite ThreadSafe: 1 +emby | Info SqliteUserRepository: Sqlite ThreadingMode: MultiThreaded +emby | Info SqliteUserRepository: Opening sqlite connection to /config/data/users.db. isReadOnly: False +emby | Info SqliteUserRepository: Default journal_mode for /config/data/users.db is wal +emby | Info SqliteUserRepository: PRAGMA cache_size=-65536 +emby | Info SqliteUserRepository: PRAGMA page_size=4096 +emby | Info SqliteUserRepository: PRAGMA foreign_keys=1 +emby | Info SqliteUserRepository: PRAGMA SECURE_DELETE=0 +emby | Info SqliteUserRepository: Result of setting SQLITE_DBCONFIG_DQS_DDL to 0 is 0 +emby | Info SqliteUserRepository: Result of setting SQLITE_DBCONFIG_DQS_DML to 0 is 0 +emby | Info SqliteItemRepository: Initializing PooledDatabaseConnectionManager with pool size: 5 +emby | Info AuthenticationRepository: Initializing PooledDatabaseConnectionManager with pool size: 5 +emby | Info ActivityRepository: Initializing PooledDatabaseConnectionManager with pool size: 3 +emby | Info ActivityRepository: Opening sqlite connection to /config/data/activitylog.db. isReadOnly: False +emby | Info ActivityRepository: Default journal_mode for /config/data/activitylog.db is wal +emby | Info ActivityRepository: PRAGMA cache_size=-65536 +emby | Info ActivityRepository: PRAGMA page_size=4096 +emby | Info ActivityRepository: PRAGMA foreign_keys=1 +emby | Info ActivityRepository: PRAGMA SECURE_DELETE=0 +emby | Info ActivityRepository: Result of setting SQLITE_DBCONFIG_DQS_DDL to 0 is 0 +emby | Info ActivityRepository: Result of setting SQLITE_DBCONFIG_DQS_DML to 0 is 0 +emby | Info NetworkManager: Detecting local network addresses +emby | Info NetworkManager: networkInterface: Ethernet eth0, Speed: 10000000000, Description: eth0 +emby | Info NetworkManager: GatewayAddresses: 172.18.0.1 +emby | Info NetworkManager: UnicastAddresses: 172.18.0.3 +emby | Info NetworkManager: networkInterface: Loopback lo, Speed: -1, Description: lo +emby | Info NetworkManager: GatewayAddresses: +emby | Info NetworkManager: UnicastAddresses: 127.0.0.1,::1 +emby | Info NetworkManager: Detected local ip addresses: [{"IPAddress":"172.18.0.3","HasGateWayAddress":true,"PrefixLength":16,"IPv4Mask":"255.255.0.0","BroadcastAddress":"172.18.255.255"},{"IPAddress":"127.0.0.1","HasGateWayAddress":false,"PrefixLength":8,"IPv4Mask":"255.0.0.0","BroadcastAddress":"127.255.255.255"},{"IPAddress":"::1","HasGateWayAddress":false,"PrefixLength":128}] +emby | Info App: Adding HttpListener prefix http://+:8096/ +emby | Info AuthenticationRepository: Opening sqlite connection to /config/data/authentication.db. isReadOnly: False +emby | Info AuthenticationRepository: Default journal_mode for /config/data/authentication.db is wal +emby | Info AuthenticationRepository: PRAGMA cache_size=-65536 +emby | Info AuthenticationRepository: PRAGMA page_size=4096 +emby | Info AuthenticationRepository: PRAGMA foreign_keys=1 +emby | Info AuthenticationRepository: PRAGMA SECURE_DELETE=0 +emby | Info AuthenticationRepository: Result of setting SQLITE_DBCONFIG_DQS_DDL to 0 is 0 +emby | Info AuthenticationRepository: Result of setting SQLITE_DBCONFIG_DQS_DML to 0 is 0 +emby | Info SqliteItemRepository: Opening sqlite connection to /config/data/library.db. isReadOnly: False +emby | Info SqliteItemRepository: Default journal_mode for /config/data/library.db is wal +emby | Info SqliteItemRepository: PRAGMA cache_size=-131072 +emby | Info SqliteItemRepository: PRAGMA page_size=4096 +emby | Info SqliteItemRepository: PRAGMA foreign_keys=1 +emby | Info SqliteItemRepository: PRAGMA SECURE_DELETE=0 +emby | Info SqliteItemRepository: Result of setting SQLITE_DBCONFIG_DQS_DDL to 0 is 0 +emby | Info SqliteItemRepository: Result of setting SQLITE_DBCONFIG_DQS_DML to 0 is 0 +emby | Info SqliteItemRepository: Init Complete +emby | Info SqliteItemRepository: Opening sqlite connection to /config/data/library.db. isReadOnly: True +emby | Info SqliteItemRepository: PRAGMA cache_size=-131072 +emby | Info SqliteItemRepository: PRAGMA page_size=4096 +emby | Info SqliteItemRepository: PRAGMA foreign_keys=1 +emby | Info SqliteItemRepository: PRAGMA SECURE_DELETE=0 +emby | Info SqliteItemRepository: Result of setting SQLITE_DBCONFIG_DQS_DDL to 0 is 0 +emby | Info SqliteItemRepository: Result of setting SQLITE_DBCONFIG_DQS_DML to 0 is 0 +emby | Info SqliteUserRepository: Opening sqlite connection to /config/data/users.db. isReadOnly: True +emby | Info SqliteUserRepository: PRAGMA cache_size=-65536 +emby | Info SqliteUserRepository: PRAGMA page_size=4096 +emby | Info SqliteUserRepository: PRAGMA foreign_keys=1 +emby | Info SqliteUserRepository: PRAGMA SECURE_DELETE=0 +emby | Info SqliteUserRepository: Result of setting SQLITE_DBCONFIG_DQS_DDL to 0 is 0 +emby | Info SqliteUserRepository: Result of setting SQLITE_DBCONFIG_DQS_DML to 0 is 0 +emby | Info App: Emby Server 4.11.0.1 +emby | Command line: /system/EmbyServer.dll -programdata /config -ffdetect /bin/ffdetect -ffmpeg /bin/ffmpeg -ffprobe /bin/ffprobe -restartexitcode 3 +emby | Operating system: Linux version 7.0.2-6-pve (build@proxmox) (gcc (Debian 14.2.0-19) 14.2.0, GNU ld (GNU Binutils for Debian) 2.44) #1 SMP PREEMPT_DYNAMIC PMX 7.0.2-6 (2 +emby | OS/Process: x64/x64 +emby | Framework: .NET 8.0.28 +emby | Runtime: system/System.Private.CoreLib.dll +emby | Processor count: 7 +emby | Data path: /config +emby | Application path: /system +emby | Info App: Logs path: /config/logs +emby | Info App: Cache path: /config/cache +emby | Info App: Internal metadata path: /config/metadata +emby | Info App: Transcoding temporary files path: /config/transcoding-temp +emby | Info App: DefaultThreadCurrentCulture: en-US +emby | Info App: DefaultThreadCurrentUICulture: en-US +emby | Info App: DefaultThreadCurrentUICulture: en-US +emby | Info App: Server Friendly Name: ‌7e784d1f638a‌ +emby | Info App: Local time: 09/22/2026 14:08:55 +02:00 +emby | Info FfmpegManager: FFMpeg: /bin/ffmpeg +emby | Info FfmpegManager: FFProbe: /bin/ffprobe +emby | Info FfmpegManager: FFDetect: /bin/ffdetect +emby | Info Skia: SkiaSharp version: 2.88.0.0 +emby | Info libvips: NetVips version: 3.0.0.0 +emby | Info ImageProcessor: Adding image processor Skia +emby | Info ImageProcessor: Adding image processor libvips +emby | Info TaskManager: Daily trigger for Video preview thumbnail extraction set to fire at 09/23/2026 02:00:00, which is 711.0515285466666 minutes from now. +emby | Info TaskManager: Daily trigger for Rotate log file set to fire at 09/23/2026 00:00:00, which is 591.0511329633333 minutes from now. +emby | Info TaskManager: Queueing task HardwareDetectionScheduledTask +emby | Info TaskManager: Executing Hardware Detection +emby | Info TaskManager: Daily trigger for Emby Server Backup set to fire at 09/23/2026 00:10:00, which is 601.0509468366666 minutes from now. +emby | Info App: ServerId: 884cd125c6174656af47037c186985a2 +emby | Info FfmpegManager: ProcessRun 'ffmpeg -hide_banner -version' Execute: /bin/ffmpeg -hide_banner -version +emby | Info FfmpegManager: ProcessRun 'ffmpeg -hide_banner -version' Process exited with code 0 +emby | Info FfmpegManager: ProcessRun 'ffmpeg -hide_banner -decoders' Execute: /bin/ffmpeg -hide_banner -decoders +emby | Info FfmpegManager: ProcessRun 'ffmpeg -hide_banner -decoders' Process exited with code 0 +emby | Info App: Starting entry point Emby.Server.Implementations.Networking.RemoteAddressEntryPoint +emby | Info FfmpegManager: ProcessRun 'ffmpeg -hide_banner -encoders' Execute: /bin/ffmpeg -hide_banner -encoders +emby | Info App: Loading data from /config/data/wan.dat +emby | Info App: Entry point completed: Emby.Server.Implementations.Networking.RemoteAddressEntryPoint. Duration: 0.0041805 seconds +emby | Info App: Starting entry point Emby.Server.Connect.ConnectEntryPoint +emby | Info App: Loading data from /config/data/connect.txt +emby | Info App: Entry point completed: Emby.Server.Connect.ConnectEntryPoint. Duration: 0.0021718 seconds +emby | Info App: Starting entry point Emby.Dlna.Main.DlnaEntryPoint +emby | Info FfmpegManager: ProcessRun 'ffmpeg -hide_banner -encoders' Process exited with code 0 +emby | Info FfmpegManager: ProcessRun 'ffmpeg -hide_banner -hwaccels' Execute: /bin/ffmpeg -hide_banner -hwaccels +emby | Info FfmpegManager: ProcessRun 'ffmpeg -hide_banner -hwaccels' Process exited with code 0 +emby | Info FfmpegManager: ProcessRun 'ffmpeg -hide_banner -protocols' Execute: /bin/ffmpeg -hide_banner -protocols +emby | Info FfmpegManager: ProcessRun 'ffmpeg -hide_banner -protocols' Process exited with code 0 +emby | Info FfmpegManager: ProcessRun 'ffmpeg -hide_banner -filters' Execute: /bin/ffmpeg -hide_banner -filters +emby | Info FfmpegManager: ProcessRun 'ffmpeg -hide_banner -filters' Process exited with code 0 +emby | Info FfmpegManager: FfmpegValidator.Validate complete +emby | Info App: Entry point completed: Emby.Dlna.Main.DlnaEntryPoint. Duration: 0.0698637 seconds +emby | Info App: Core startup complete +emby | Info App: Starting entry point Emby.Server.Implementations.Udp.UdpServerEntryPoint +emby | Info App: Entry point completed: Emby.Server.Implementations.Udp.UdpServerEntryPoint. Duration: 0.0018842 seconds +emby | Info App: Starting entry point Emby.Server.Implementations.Session.PartyEventNotifier +emby | Info App: Entry point completed: Emby.Server.Implementations.Session.PartyEventNotifier. Duration: 0.0008008 seconds +emby | Info App: Starting entry point Emby.Server.Implementations.Playlists.PlaylistUpgradeEntryPoint +emby | Info App: Entry point completed: Emby.Server.Implementations.Playlists.PlaylistUpgradeEntryPoint. Duration: 0.0016388 seconds +emby | Info App: Starting entry point Emby.Server.Implementations.Library.DeviceAccessEntryPoint +emby | Info App: Entry point completed: Emby.Server.Implementations.Library.DeviceAccessEntryPoint. Duration: 0.0004062 seconds +emby | Info App: Starting entry point Emby.Server.Implementations.IO.LibraryMonitorStartup +emby | Info App: Entry point completed: Emby.Server.Implementations.IO.LibraryMonitorStartup. Duration: 0.0153998 seconds +emby | Info App: Starting entry point Emby.Server.Implementations.EntryPoints.AutomaticRestartEntryPoint +emby | Info App: Entry point completed: Emby.Server.Implementations.EntryPoints.AutomaticRestartEntryPoint. Duration: 0.0004622 seconds +emby | Info App: Starting entry point Emby.Server.Implementations.EntryPoints.KeepServerAwake +emby | Info App: Entry point completed: Emby.Server.Implementations.EntryPoints.KeepServerAwake. Duration: 0.0001227 seconds +emby | Info App: Starting entry point Emby.Server.Implementations.EntryPoints.LibraryChangedNotifier +emby | Info App: Entry point completed: Emby.Server.Implementations.EntryPoints.LibraryChangedNotifier. Duration: 0.0005485 seconds +emby | Info App: Starting entry point Emby.Server.Implementations.EntryPoints.LogLibrariesEntryPoint +emby | Info App: Libraries +emby | Server Configuration: CollapseVideoFolders: False +emby | Info App: Entry point completed: Emby.Server.Implementations.EntryPoints.LogLibrariesEntryPoint. Duration: 0.0005714 seconds +emby | Info App: Starting entry point Emby.Server.Implementations.EntryPoints.ServerEventNotifier +emby | Info App: Entry point completed: Emby.Server.Implementations.EntryPoints.ServerEventNotifier. Duration: 0.0013475 seconds +emby | Info App: Starting entry point Emby.Server.Implementations.EntryPoints.StartupWizard +emby | Info App: Entry point completed: Emby.Server.Implementations.EntryPoints.StartupWizard. Duration: 0.0003079 seconds +emby | Info App: Starting entry point Emby.Server.Implementations.EntryPoints.SystemEvents +emby | Info App: Entry point completed: Emby.Server.Implementations.EntryPoints.SystemEvents. Duration: 0.0001789 seconds +emby | Info App: Starting entry point Emby.Server.Implementations.EntryPoints.UserDataChangeNotifier +emby | Info App: Entry point completed: Emby.Server.Implementations.EntryPoints.UserDataChangeNotifier. Duration: 0.0003335 seconds +emby | Info App: Starting entry point Emby.Server.Implementations.Channels.ChannelsEntryPoint +emby | Info App: Entry point completed: Emby.Server.Implementations.Channels.ChannelsEntryPoint. Duration: 0.0025425 seconds +emby | Info App: Starting entry point Emby.LiveTV.EntryPoint +emby | Info LiveTV: Loading live tv data from /config/data/livetv/timers +emby | Info App: Entry point completed: Emby.LiveTV.EntryPoint. Duration: 0.0022585 seconds +emby | Info App: Starting entry point Emby.LiveTV.RecordingNotifier +emby | Info App: Entry point completed: Emby.LiveTV.RecordingNotifier. Duration: 0.0013495 seconds +emby | Info App: Starting entry point Emby.Server.MediaEncoding.Api.EncodingManagerEntryPoint +emby | Info App: Entry point completed: Emby.Server.MediaEncoding.Api.EncodingManagerEntryPoint. Duration: 0.0008198 seconds +emby | Info App: Starting entry point Emby.Notifications.NotificationManagerEntryPoint +emby | Info Notifications: Registering event nofitier Activity Log +emby | Info Notifications: Registering event nofitier Emby Server User Notifications +emby | Info Notifications: Registering event factory CoreNotificationTypeFactory +emby | Info Notifications: Registering event factory BackupNotificationTypeFactory +emby | Info App: Entry point completed: Emby.Notifications.NotificationManagerEntryPoint. Duration: 0.0264141 seconds +emby | Info App: Starting entry point Emby.Server.Sync.SyncNotificationEntryPoint +emby | Info App: Entry point completed: Emby.Server.Sync.SyncNotificationEntryPoint. Duration: 0.0011423 seconds +emby | Info App: Starting entry point EmbyServer.Windows.LoopUtilEntryPoint +emby | Info App: Entry point completed: EmbyServer.Windows.LoopUtilEntryPoint. Duration: 9.99E-05 seconds +emby | Info App: Starting entry point NfoMetadata.EntryPoint +emby | Info App: Entry point completed: NfoMetadata.EntryPoint. Duration: 0.0002007 seconds +emby | Info App: Starting entry point Emby.Security.PluginSecurityManager +emby | Info App: Entry point completed: Emby.Security.PluginSecurityManager. Duration: 4.84E-05 seconds +emby | Info App: Starting entry point Emby.Server.CinemaMode.IntrosEntryPoint +emby | Info App: Entry point completed: Emby.Server.CinemaMode.IntrosEntryPoint. Duration: 0.0001469 seconds +emby | Info App: Starting entry point MBBackup.ServerEntryPoint +emby | Info App: Entry point completed: MBBackup.ServerEntryPoint. Duration: 4.75E-05 seconds +emby | Info App: Starting entry point Emby.PortMapper.ExternalPortForwarding +emby | Info App: Entry point completed: Emby.PortMapper.ExternalPortForwarding. Duration: 0.0014957 seconds +emby | Info App: Starting entry point Tvdb.EntryPoint +emby | Info App: Entry point completed: Tvdb.EntryPoint. Duration: 3.86E-05 seconds +emby | Info App: All entry points have started diff --git a/documentation/audits/the-28-2026-09-22/apps/emby/log.txt b/documentation/audits/the-28-2026-09-22/apps/emby/log.txt new file mode 100644 index 00000000..2b14ba10 --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/apps/emby/log.txt @@ -0,0 +1,34 @@ +14:07:44 ==== emby (sub=emby, class=file-leg, edge={'from': 'emby/embyserver:4.10.0.20', 'to': 'emby/embyserver:4.11.0.1'}) +14:07:48 [1] made the drive paths this app requires: ['/mnt/felhom-drives/scratch_hdd/userdata/emby'] +14:07:48 [1] required fields filled beyond DOMAIN/SUBDOMAIN: ['HDD_PATH'] +14:07:48 [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'} +14:08:18 [1] deployed, controller state=running, pinned={'emby': 'emby/embyserver:4.10.0.20'} +14:08:18 [1] front door 302 controller state=running +14:08:18 emby: seeded first user drill1efebc2a +14:08:18 emby: readback found=True (http 200, control passed) +14:08:18 [3] C1 readback before = True +14:08:18 [4] „Mentés most" -> 200 {'ok': True, 'message': 'Mentés elindítva'} +14:08:33 [4] backup idle; last=None +14:08:33 [4] backups page offers 1 restorable copy(ies) +14:08:34 [5] drill commit 0b74b5a11715: emby emby/embyserver:4.10.0.20 -> emby/embyserver:4.11.0.1 (push rc=0) +14:08:39 [5] badge {'hu': [{'title': 'Újabb változat érhető el ehhez az alkalmazáshoz. A frissítés indításához nyomd meg a Frissítés gombot.', 'text': 'Frissítés elérhető — ma'}], 'en': [{'title': 'A newer version of this app is available. Select the Update button to start it.', 'text': 'Update available — today'}]} +14:08:39 [6] Update -> 202 {'ok': True, 'data': {'accepted': True, 'completed': False}, 'message': 'Frissítés elindult – az állapot a kártyán követhető'} +14:08:39 + 0.0s phase=safety-dump label=Adatbázis pillanatkép… err=None hold=None +14:08:40 + 1.0s phase=pulling label=Új verzió letöltése… err=None hold=None +14:08:54 + 14.4s phase=starting label=Indítás az új verzióval… err=None hold=None +14:08:55 + 15.4s phase=verifying label=Működés ellenőrzése… err=None hold=None +14:09:00 + 20.5s phase=done label=Frissítve err=None hold=None +14:09:04 emby: readback found=True (http 200, control passed) +14:09:04 [R] restoring emby from snapshot 'helyi' (of 1 offered) +14:09:04 [R] POST /backup/restore -> HTTP/2 302 ['location: /backups/restore?flash=flash.restore.started'] +14:09:04 + 0.0s restore (True, None, None) +14:09:14 + 10.2s restore (False, None, None) +14:09:14 [R] after restore: state=running hold=None phase=done +14:09:32 emby: readback found=True (http 200, control passed) +14:09:32 [6] restore -> ok, seed back = True +14:09:43 [X] stop -> 200 {'ok': True, 'message': 'Stack emby stop completed'} +14:09:48 [X] remove (with drive data) -> 409 {'ok': False, 'error': 'A(z) /mnt/felhom-drives/scratch_hdd/userdata/emby tárhely jelenleg nem elérhető — az alkalmazás nem távolítható el, amíg a meghajtó viss +14:09:48 [X] refused because the drive path cannot be resolved (R-442, fail-closed and right) — removing the app and KEEPING the drive data instead +14:09:48 [X] remove (keeping drive data) -> 200 {'ok': True, 'data': {'removed': 'emby', 'volumes_removed': [], 'hdd_paths_removed': [], 'hdd_paths_preserved': [], 'backup_paths_removed': ['/mnt/felhom-drives +14:09:57 [X] after remove: deployed=False leftovers='image: plexinc/pms-docker:1.43.4.10903-e5521bd8c\n---inspect---\nplex plexinc/pms-docker:1.43.4.10903-e5521bd8c running=true restarts=0' +14:11:00 [7] 60 s after remove: clean=True '' diff --git a/documentation/audits/the-28-2026-09-22/apps/emby/verdict.json b/documentation/audits/the-28-2026-09-22/apps/emby/verdict.json new file mode 100644 index 00000000..4f7e1214 --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/apps/emby/verdict.json @@ -0,0 +1,131 @@ +{ + "harness_version": 2, + "app": "emby", + "venue": "guest 9202 demo-hp-scratch, controller 0.261.0, drill catalog", + "class": "file-leg", + "from": { + "emby": "emby/embyserver:4.10.0.20" + }, + "to": { + "emby": "emby/embyserver:4.11.0.1" + }, + "verdict": "proven", + "deployed": true, + "seed_route": "its own /Startup/User wizard, then /Users/Public", + "seed_read_before": true, + "seed_read_after_update": true, + "backup": { + "snapshots_offered": 1, + "first": { + "time": "2026-09-22T12:08:32Z", + "short_id": "helyi", + "tier": 1, + "drive_label": "emby" + } + }, + "restore_verdict": "ok", + "seed_read_after_restore": true, + "healthy_after": true, + "migration_observed": "emby | Info SqliteUserRepository: Sqlite compiler options: ATOMIC_INTRINSICS=1,COMPILER=gcc-13.4.0,DEFAULT_AUTOVACUUM,DEFAULT_CACHE_SIZE=-2000,DEFAULT_FILE_FORMAT=4,DEFAULT_JOURNAL_SIZE_LIMIT=-1,DEFAULT_MMAP_SIZE=0,DEFAULT_PAGE_SIZE=4096,DEFAULT_PCACHE_INITSZ=20,DEFAULT_RECURSIVE_TRIGGERS,DEFAULT_S", + "removed_clean": true, + "edge": { + "from": "emby/embyserver:4.10.0.20", + "to": "emby/embyserver:4.11.0.1" + }, + "duration_s": 198.7, + "measured_at": "2026-09-22T12:07:44.952377+00:00", + "evidence": "the-28-2026-09-22/apps/emby/", + "notes": [], + "front_door_after_deploy": { + "rc": 0, + "code": "302" + }, + "drill_commit": "0b74b5a11715", + "badge_seconds": 4.6, + "badges": { + "hu": [ + { + "title": "Újabb változat érhető el ehhez az alkalmazáshoz. A frissítés indításához nyomd meg a Frissítés gombot.", + "text": "Frissítés elérhető — ma" + } + ], + "en": [ + { + "title": "A newer version of this app is available. Select the Update button to start it.", + "text": "Update available — today" + } + ] + }, + "phases": { + "accepted": true, + "http": "202", + "phases": [ + { + "t": 0.0, + "phase": "safety-dump", + "label": "Adatbázis pillanatkép…", + "updating": true, + "error": null, + "hold": null + }, + { + "t": 1.0, + "phase": "pulling", + "label": "Új verzió letöltése…", + "updating": true, + "error": null, + "hold": null + }, + { + "t": 14.4, + "phase": "starting", + "label": "Indítás az új verzióval…", + "updating": true, + "error": null, + "hold": null + }, + { + "t": 15.4, + "phase": "verifying", + "label": "Működés ellenőrzése…", + "updating": true, + "error": null, + "hold": null + }, + { + "t": 20.5, + "phase": "done", + "label": "Frissítve", + "updating": false, + "error": null, + "hold": null + } + ], + "duration_s": 20.6, + "final_phase": "done", + "update_error": null, + "hold_reason": null, + "state": "running" + }, + "observables": { + "pinned_images": { + "emby": "emby/embyserver:4.11.0.1" + }, + "installed_images": { + "emby": "emby/embyserver:4.11.0.1" + }, + "catalog_images": { + "emby": "emby/embyserver:4.11.0.1" + }, + "live_compose_image_lines": [ + "image: emby/embyserver:4.11.0.1" + ], + "docker_inspect": [ + "emby emby/embyserver:4.11.0.1 running=true restarts=0" + ] + }, + "update_phase_final": "done", + "restore_raw": "{'ok': True, 'snapshot_id': 'helyi', 'snapshots': [{'time': '2026-09-22T12:09:02Z', 'short_id': 'helyi', 'tier': 1, 'drive_label': 'emby'}], 'http': 'HTTP/2 302', 'location': ['location: /backups/restore?flash=flash.restore.started'], 'seconds': 10.2, 'state_after': 'running', 'hold_after': None, 'observables_after': {'pinned_images': {'emby': 'emby/embyserver:4.11.0.1'}, 'installed_images': {'emb", + "restore_refusal": null, + "remove_leftovers": "" +} \ No newline at end of file diff --git a/documentation/audits/the-28-2026-09-22/apps/ghost/app-logs-during-after.txt b/documentation/audits/the-28-2026-09-22/apps/ghost/app-logs-during-after.txt new file mode 100644 index 00000000..1101f20c --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/apps/ghost/app-logs-during-after.txt @@ -0,0 +1,28 @@ +ghost | [2026-09-22 15:13:33] INFO Ghost is running in production... +ghost | [2026-09-22 15:13:33] INFO Your site is now available on https://blog.enkisfelhom.hu/ +ghost | [2026-09-22 15:13:33] INFO Ctrl+C to shut down +ghost | [2026-09-22 15:13:33] INFO Ghost server started in 1.31s +ghost | sqlite does not support inserting default values. Set the `useNullAsDefault` flag to hide this warning. (see docs https://knexjs.org/guide/query-builder.html#insert). +ghost | [2026-09-22 15:13:33] INFO Database is in a ready state. +ghost | [2026-09-22 15:13:33] INFO Ghost database ready in 1.477s +ghost | [2026-09-22 15:13:34] WARN Missing mail.from config, falling back to a generated email address. Please update your config file and set a valid from address +ghost | [2026-09-22 15:13:35] INFO Invalidating assets for regeneration +ghost | [2026-09-22 15:13:37] INFO [Background Job] send-gift-reminders scheduled at 45 0 5 * * * +ghost | [2026-09-22 15:13:37] INFO Adding offloaded job to the inline job queue +ghost | [2026-09-22 15:13:37] INFO Scheduling job send-gift-reminders at 45 0 5 * * *. Next run on: Wed Sep 23 2026 05:00:45 GMT+0200 (Central European Summer Time) +ghost | [2026-09-22 15:13:37] INFO [EmailAnalytics:newsletters] Initialized with SEQUENTIAL processing mode +ghost | [2026-09-22 15:13:37] INFO [EmailAnalytics:automations] Initialized with SEQUENTIAL processing mode +ghost | [2026-09-22 15:13:37] INFO [EmailAnalytics:gifts] Initialized with SEQUENTIAL processing mode +ghost | [2026-09-22 15:13:37] INFO Pinging Explore with Payload https://explore.ghost.org/api/update {"ghost":"6.64.0","site_uuid":"50449019-dc76-4e9b-8e9a-f6e5ff2c450a","url":"https://blog.enkisfelhom.hu","theme":"source","facebook":"ghost","twitter":"@ghost","posts_total":2,"posts_last":"2026-09-22T11:13:17.000Z","posts_first":"2026-09-22T11:13:16.000Z"} +ghost | [2026-09-22 15:13:37] INFO Stripe members-migrations skipped because it has already run +ghost | [2026-09-22 15:13:37] INFO Ghost booted in 5.526s +ghost | [2026-09-22 15:13:37] INFO [Background Job] clean-gifts scheduled at 1 12 2 * * * +ghost | [2026-09-22 15:13:37] INFO [Background Job] clean-tokens scheduled at 52 7 16 * * * +ghost | [2026-09-22 15:13:37] INFO [Background Job] clean-expired-comped scheduled at 55 21 5 * * * +ghost | [2026-09-22 15:13:37] INFO [Background Job] update-check scheduled at 25 17 18 * * * +ghost | [2026-09-22 15:13:37] INFO Running milestone emails job on Fri Sep 25 2026 15:13:37 GMT+0200 (Central European Summer Time) +ghost | [2026-09-22 15:13:38] ERROR Could not get webhook secret for ActivityPub SyntaxError: Unexpected non-whitespace character after JSON at position 4 (line 1 column 5) +ghost | [2026-09-22 15:13:38] ERROR No webhook secret found - cannot initialise +ghost | [2026-09-22 15:13:38] INFO Explore Response 200 OK +ghost | [2026-09-22 15:13:42] INFO "GET /" 301 9ms +ghost | [2026-09-22 15:13:46] INFO "GET /" 301 2ms diff --git a/documentation/audits/the-28-2026-09-22/apps/ghost/log.txt b/documentation/audits/the-28-2026-09-22/apps/ghost/log.txt new file mode 100644 index 00000000..72dc2a70 --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/apps/ghost/log.txt @@ -0,0 +1,29 @@ +15:11:12 ==== ghost (sub=blog, class=file-leg, edge={'from': 'ghost:6.53.0-alpine', 'to': 'ghost:6.64.0-alpine'}) +15:11:12 [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'} +15:13:28 [1] deployed, controller state=running, pinned={'ghost': 'ghost:6.64.0-alpine'} +15:13:28 [1] front door 200 controller state=running +15:13:30 ghost: seeded site title Drill-01ffec542b59 +15:13:30 ghost: readback found=True (http 200, control passed) +15:13:30 [3] C1 readback before = True +15:13:30 [4] „Mentés most" -> 200 {'ok': True, 'message': 'Mentés elindítva'} +15:13:35 [4] backup idle; last=None +15:13:35 [4] backups page offers 1 restorable copy(ies) +15:13:35 [5] FROM ref not found in compose: ghost:6.53.0-alpine +15:13:40 [5] badge {'hu': [{'title': 'Ez az alkalmazás a legfrissebb elérhető változatot futtatja.', 'text': 'Naprakész'}], 'en': [{'title': 'This app is running the newest version available.', 'text': 'Up to date'}]} +15:13:40 [6] Update -> 202 {'ok': True, 'data': {'accepted': True, 'completed': False}, 'message': 'Frissítés elindult – az állapot a kártyán követhető'} +15:13:40 + 0.0s phase=safety-dump label=Adatbázis pillanatkép… err=None hold=None +15:13:41 + 1.1s phase=pulling label=Új verzió letöltése… err=None hold=None +15:13:42 + 2.1s phase=verifying label=Működés ellenőrzése… err=None hold=None +15:13:47 + 7.2s phase=done label=Frissítve err=None hold=None +15:13:51 ghost: readback found=True (http 200, control passed) +15:13:51 [R] restoring ghost from snapshot 'helyi' (of 1 offered) +15:13:51 [R] POST /backup/restore -> HTTP/2 302 ['location: /backups/restore?flash=flash.restore.started'] +15:13:51 + 0.0s restore (True, None, None) +15:14:07 + 16.2s restore (False, None, None) +15:14:07 [R] after restore: state=running hold=None phase=done +15:14:27 ghost: readback found=True (http 200, control passed) +15:14:27 [6] restore -> ok, seed back = True +15:14:37 [X] stop -> 200 {'ok': True, 'message': 'Stack ghost stop completed'} +15:14:42 [X] remove (with drive data) -> 200 {'ok': True, 'data': {'removed': 'ghost', 'volumes_removed': [], 'hdd_paths_removed': [], 'hdd_paths_preserved': [], 'hdd_note': 'Az alkalmazás nem tárolt saját +15:14:51 [X] after remove: deployed=False leftovers='/opt/docker/stacks/ghost' +15:15:54 [7] 60 s after remove: clean=True '' diff --git a/documentation/audits/the-28-2026-09-22/apps/ghost/verdict.json b/documentation/audits/the-28-2026-09-22/apps/ghost/verdict.json new file mode 100644 index 00000000..80e01ac3 --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/apps/ghost/verdict.json @@ -0,0 +1,123 @@ +{ + "harness_version": 2, + "app": "ghost", + "venue": "guest 9202 demo-hp-scratch, controller 0.261.0, drill catalog", + "class": "file-leg", + "from": { + "ghost": "ghost:6.64.0-alpine" + }, + "to": { + "ghost": "ghost:6.64.0-alpine" + }, + "verdict": "proven", + "deployed": true, + "seed_route": "its own /ghost/api/admin/authentication/setup/", + "seed_read_before": true, + "seed_read_after_update": true, + "backup": { + "snapshots_offered": 1, + "first": { + "time": "2026-09-22T13:13:32Z", + "short_id": "helyi", + "tier": 1, + "drive_label": "Belső SSD (rendszer)" + } + }, + "restore_verdict": "ok", + "seed_read_after_restore": true, + "healthy_after": true, + "migration_observed": "ghost | [2026-09-22 15:13:37] \u001b[36mINFO\u001b[39m Stripe members-migrations skipped because it has already run", + "removed_clean": true, + "edge": { + "from": "ghost:6.53.0-alpine", + "to": "ghost:6.64.0-alpine" + }, + "duration_s": 289.6, + "measured_at": "2026-09-22T13:11:12.069214+00:00", + "evidence": "the-28-2026-09-22/apps/ghost/", + "notes": [], + "front_door_after_deploy": { + "rc": 0, + "code": "200" + }, + "drill_commit": null, + "badge_seconds": 4.6, + "badges": { + "hu": [ + { + "title": "Ez az alkalmazás a legfrissebb elérhető változatot futtatja.", + "text": "Naprakész" + } + ], + "en": [ + { + "title": "This app is running the newest version available.", + "text": "Up to date" + } + ] + }, + "phases": { + "accepted": true, + "http": "202", + "phases": [ + { + "t": 0.0, + "phase": "safety-dump", + "label": "Adatbázis pillanatkép…", + "updating": true, + "error": null, + "hold": null + }, + { + "t": 1.1, + "phase": "pulling", + "label": "Új verzió letöltése…", + "updating": true, + "error": null, + "hold": null + }, + { + "t": 2.1, + "phase": "verifying", + "label": "Működés ellenőrzése…", + "updating": true, + "error": null, + "hold": null + }, + { + "t": 7.2, + "phase": "done", + "label": "Frissítve", + "updating": false, + "error": null, + "hold": null + } + ], + "duration_s": 7.2, + "final_phase": "done", + "update_error": null, + "hold_reason": null, + "state": "running" + }, + "observables": { + "pinned_images": { + "ghost": "ghost:6.64.0-alpine" + }, + "installed_images": { + "ghost": "ghost:6.64.0-alpine" + }, + "catalog_images": { + "ghost": "ghost:6.64.0-alpine" + }, + "live_compose_image_lines": [ + "image: ghost:6.64.0-alpine" + ], + "docker_inspect": [ + "ghost ghost:6.64.0-alpine running=true restarts=0" + ] + }, + "update_phase_final": "done", + "restore_raw": "{'ok': True, 'snapshot_id': 'helyi', 'snapshots': [{'time': '2026-09-22T13:13:32Z', 'short_id': 'helyi', 'tier': 1, 'drive_label': 'Belső SSD (rendszer)'}], 'http': 'HTTP/2 302', 'location': ['location: /backups/restore?flash=flash.restore.started'], 'seconds': 16.3, 'state_after': 'running', 'hold_after': None, 'observables_after': {'pinned_images': {'ghost': 'ghost:6.64.0-alpine'}, 'installed_im", + "restore_refusal": null, + "remove_leftovers": "" +} \ No newline at end of file diff --git a/documentation/audits/the-28-2026-09-22/apps/gokapi/came-back-evidence.txt b/documentation/audits/the-28-2026-09-22/apps/gokapi/came-back-evidence.txt new file mode 100644 index 00000000..0c01cc6b --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/apps/gokapi/came-back-evidence.txt @@ -0,0 +1,49 @@ +== the container that came back: +/gokapi created=2026-09-22T11:34:24.880772531Z restartPolicy=unless-stopped state=restarting restarts=11 image=f0rc3/gokapi:v1.9.6 + +== its labels (who owns it): +{ + "com.docker.compose.config-hash": "d6093fb0b5c8256177b3ededc1586f1a01e2df4f11faac08aadf8d5aa79f6dd9", + "com.docker.compose.container-number": "1", + "com.docker.compose.depends_on": "", + "com.docker.compose.image": "sha256:5743b81c9300cd3791eedcb64aba6907feaf80b959a899b1b833f5af57117d96", + "com.docker.compose.oneoff": "False", + "com.docker.compose.project": "gokapi", + "com.docker.compose.project.config_files": "/opt/docker/stacks/gokapi/docker-compose.yml", + "com.docker.compose.project.working_dir": "/opt/docker/stacks/gokapi", + "com.docker.compose.service": "gokapi", + "com.docker.compose.version": "5.5.0", + "traefik.enable": "true", + "traefik.http.routers.gokapi.entrypoints": "websecure", + "traefik.http.routers.gokapi.rule": "Host(`.enkisfelhom.hu`)", + "traefik.http.routers.gokapi.tls": "true", + "traefik.http.routers.gokapi.tls.certresolver": "letsencrypt", + "traefik.http.services.gokapi.loadbalancer.server.port": "53842" +} + +== its last log lines: +Warning: Salt for admin password invalid, generating new salt. You will need to reset the admin password. +2026/09/22 13:35:18 Error while initiating authentication method: +2026/09/22 13:35:18 password does not appear to be a SHA-1 hash + +██████  ██████   ██  ██  █████  ██████  ██  +██       ██    ██ ██  ██  ██   ██ ██   ██ ██  +██  ███ ██  ██ █████   ███████ ██████  ██  +██  ██ ██  ██ ██  ██  ██   ██ ██      ██  + ██████   ██████  ██  ██ ██  ██ ██  ██  +                                       +Gokapi v1.9.6 starting +Warning: Salt for file hash invalid, generating new salt +Warning: Salt for admin password invalid, generating new salt. You will need to reset the admin password. +2026/09/22 13:36:10 Error while initiating authentication method: +2026/09/22 13:36:10 password does not appear to be a SHA-1 hash + +== does the stack dir still have a compose/app.yaml? +total 20 +drwxr-xr-x 2 root root 4096 Sep 22 11:34 . +drwxr-xr-x 57 root root 4096 Sep 13 20:22 .. +-rw-r--r-- 1 root root 4071 Sep 22 11:33 .felhom.yml +-rw-r--r-- 1 root root 2993 Sep 22 11:33 applied-compose.yml +-rw-r--r-- 1 root root 2993 Sep 22 11:33 docker-compose.yml + +controller says: deployed= False state= restarting diff --git a/documentation/audits/the-28-2026-09-22/apps/gokapi/log.txt b/documentation/audits/the-28-2026-09-22/apps/gokapi/log.txt new file mode 100644 index 00000000..064b71ff --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/apps/gokapi/log.txt @@ -0,0 +1,19 @@ +13:33:23 ==== gokapi (sub=gokapi, class=file-leg, edge=none) +13:33:23 [1] required fields filled beyond DOMAIN/SUBDOMAIN: ['GOKAPI_PASSWORD'] +13:33:23 [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'} +13:33:38 [1] deployed, controller state=running, pinned={'gokapi': 'f0rc3/gokapi:v1.9.6'} +13:33:38 [1] front door 200 controller state=running +13:33:38 [2] NO FIXTURE — recorded inconclusive for the data half, not faked +13:33:38 [4] „Mentés most" -> 200 {'ok': True, 'message': 'Mentés elindítva'} +13:33:54 [4] backup idle; last=None +13:33:54 [4] backups page offers 1 restorable copy(ies) +13:33:54 [R] restoring gokapi from snapshot 'helyi' (of 1 offered) +13:33:54 [R] POST /backup/restore -> HTTP/2 302 ['location: /backups/restore?flash=flash.restore.started'] +13:33:54 + 0.0s restore (True, None, None) +13:34:04 + 10.2s restore (False, None, None) +13:34:04 [R] after restore: state=running hold=None phase=None +13:34:17 [6] restore -> ok, seed back = False +13:34:17 [X] stop -> 200 {'ok': True, 'message': 'Stack gokapi stop completed'} +13:34:22 [X] remove (with drive data) -> 200 {'ok': True, 'data': {'removed': 'gokapi', 'volumes_removed': [], 'hdd_paths_removed': [], 'hdd_paths_preserved': [], 'hdd_note': 'Az alkalmazás nem tárolt sajá +13:34:31 [X] after remove: deployed=False leftovers='/opt/docker/stacks/gokapi\ngokapi' +13:35:33 [7] 60 s after remove: clean=False 'gokapi' diff --git a/documentation/audits/the-28-2026-09-22/apps/gokapi/verdict.json b/documentation/audits/the-28-2026-09-22/apps/gokapi/verdict.json new file mode 100644 index 00000000..d4f44149 --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/apps/gokapi/verdict.json @@ -0,0 +1,42 @@ +{ + "harness_version": 2, + "app": "gokapi", + "venue": "guest 9202 demo-hp-scratch, controller 0.261.0, drill catalog", + "class": "file-leg", + "from": { + "gokapi": "f0rc3/gokapi:v1.9.6" + }, + "to": {}, + "verdict": "no-edge", + "deployed": true, + "seed_route": "none written", + "seed_read_before": false, + "seed_read_after_update": false, + "backup": { + "snapshots_offered": 1, + "first": { + "time": "2026-09-22T11:33:53Z", + "short_id": "helyi", + "tier": 1, + "drive_label": "Belső SSD (rendszer)" + } + }, + "restore_verdict": "ok", + "seed_read_after_restore": false, + "healthy_after": false, + "migration_observed": null, + "removed_clean": false, + "edge": null, + "duration_s": 132.3, + "measured_at": "2026-09-22T11:33:23.486833+00:00", + "evidence": "the-28-2026-09-22/apps/gokapi/", + "notes": [ + "no fixture: no non-browser seed route was written for this app" + ], + "front_door_after_deploy": { + "rc": 0, + "code": "200" + }, + "restore_raw": "{'ok': True, 'snapshot_id': 'helyi', 'snapshots': [{'time': '2026-09-22T11:33:53Z', 'short_id': 'helyi', 'tier': 1, 'drive_label': 'Belső SSD (rendszer)'}], 'http': 'HTTP/2 302', 'location': ['location: /backups/restore?flash=flash.restore.started'], 'seconds': 10.2, 'state_after': 'running', 'hold_after': None, 'observables_after': {'pinned_images': {'gokapi': 'f0rc3/gokapi:v1.9.6'}, 'installed_i", + "remove_leftovers": "gokapi" +} \ No newline at end of file diff --git a/documentation/audits/the-28-2026-09-22/apps/gramps-web/log.txt b/documentation/audits/the-28-2026-09-22/apps/gramps-web/log.txt new file mode 100644 index 00000000..ca0f945c --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/apps/gramps-web/log.txt @@ -0,0 +1,21 @@ +13:36:24 ==== gramps-web (sub=gramps, class=file-leg, edge=none) +13:36:24 [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'} +13:38:45 [1] deployed, controller state=running, pinned={'gramps-web': 'ghcr.io/gramps-project/grampsweb:v25.6.0'} +13:38:45 [1] front door 200 controller state=running +13:38:53 gramps-web: seeded user drill15f7d7c5 +13:38:58 gramps-web: readback found=False (control passed) +13:38:58 [3] C1 readback before = False +13:38:59 [4] „Mentés most" -> 409 {'ok': False, 'error': 'Mentés már folyamatban'} +13:39:34 [4] backup idle; last=None +13:39:34 [4] backups page offers 1 restorable copy(ies) +13:39:34 [R] restoring gramps-web from snapshot 'helyi' (of 1 offered) +13:39:34 [R] POST /backup/restore -> HTTP/2 302 ['location: /backups/restore?flash=flash.restore.started'] +13:39:34 + 0.0s restore (True, None, None) +13:40:08 + 34.6s restore (False, None, None) +13:40:08 [R] after restore: state=running hold=None phase=None +13:40:24 gramps-web: readback found=False (control passed) +13:40:24 [6] restore -> ok, seed back = False +13:40:35 [X] stop -> 200 {'ok': True, 'message': 'Stack gramps-web stop completed'} +13:40:40 [X] remove (with drive data) -> 200 {'ok': True, 'data': {'removed': 'gramps-web', 'volumes_removed': [], 'hdd_paths_removed': [], 'hdd_paths_preserved': [], 'hdd_note': 'Az alkalmazás nem tárolt +13:40:47 [X] after remove: deployed=False leftovers='/opt/docker/stacks/gramps-web' +13:41:51 [7] 60 s after remove: clean=True '' diff --git a/documentation/audits/the-28-2026-09-22/apps/gramps-web/verdict.json b/documentation/audits/the-28-2026-09-22/apps/gramps-web/verdict.json new file mode 100644 index 00000000..fc6ae426 --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/apps/gramps-web/verdict.json @@ -0,0 +1,40 @@ +{ + "harness_version": 2, + "app": "gramps-web", + "venue": "guest 9202 demo-hp-scratch, controller 0.261.0, drill catalog", + "class": "file-leg", + "from": { + "gramps-web": "ghcr.io/gramps-project/grampsweb:v25.6.0" + }, + "to": {}, + "verdict": "no-edge", + "deployed": true, + "seed_route": "its own `python3 -m gramps_webapi user add`", + "seed_read_before": false, + "seed_read_after_update": false, + "backup": { + "snapshots_offered": 1, + "first": { + "time": "2026-09-22T11:38:41Z", + "short_id": "helyi", + "tier": 1, + "drive_label": "Belső SSD (rendszer)" + } + }, + "restore_verdict": "ok", + "seed_read_after_restore": false, + "healthy_after": false, + "migration_observed": null, + "removed_clean": true, + "edge": null, + "duration_s": 334.1, + "measured_at": "2026-09-22T11:36:24.107460+00:00", + "evidence": "the-28-2026-09-22/apps/gramps-web/", + "notes": [], + "front_door_after_deploy": { + "rc": 0, + "code": "200" + }, + "restore_raw": "{'ok': True, 'snapshot_id': 'helyi', 'snapshots': [{'time': '2026-09-22T11:38:41Z', 'short_id': 'helyi', 'tier': 1, 'drive_label': 'Belső SSD (rendszer)'}], 'http': 'HTTP/2 302', 'location': ['location: /backups/restore?flash=flash.restore.started'], 'seconds': 34.6, 'state_after': 'running', 'hold_after': None, 'observables_after': {'pinned_images': {'gramps-web': 'ghcr.io/gramps-project/grampswe", + "remove_leftovers": "" +} \ No newline at end of file diff --git a/documentation/audits/the-28-2026-09-22/apps/half-state-outline-sparkyfitness.txt b/documentation/audits/the-28-2026-09-22/apps/half-state-outline-sparkyfitness.txt new file mode 100644 index 00000000..d590a60b --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/apps/half-state-outline-sparkyfitness.txt @@ -0,0 +1,95 @@ +=== the controller in its own words, outline and sparkyfitness === +2026/09/22 11:49:40 router.go:906: [ERROR] [api] Remove failed for sparkyfitness: stack "sparkyfitness" is not deployed +2026/09/22 11:49:40 router.go:81: [DEBUG] [api] removeStack: name=sparkyfitness +2026/09/22 11:49:40 router.go:81: [DEBUG] [api] removeStack: name=sparkyfitness removeHDDData=false removeBackups=true +2026/09/22 11:49:40 delete.go:418: [DEBUG] [stacks] RemoveStack called: name="sparkyfitness", removeHDDData=false, backupPathsToRemove=2 +2026/09/22 11:49:40 delete.go:432: [DEBUG] [stacks] RemoveStack sparkyfitness: state=not_deployed, deployed=false, orphaned=false, deploying=false +2026/09/22 11:49:40 router.go:906: [ERROR] [api] Remove failed for sparkyfitness: stack "sparkyfitness" is not deployed +2026/09/22 11:54:47 [DEBUG] [stacks] SaveAppConfig: saving /opt/docker/stacks/outline — 5 env vars, 3 encrypted, 3 sensitive fields +2026/09/22 11:54:47 [INFO] [stacks] SaveAppConfig: saved config for outline +2026/09/22 11:55:51 manager.go:1217: [DEBUG] [stacks] StopStack outline: current state=deploying deployed=true containers=0 +2026/09/22 11:55:52 [DEBUG] [stacks] SaveAppConfig: saving /opt/docker/stacks/outline — 5 env vars, 3 encrypted, 3 sensitive fields +2026/09/22 11:55:52 [INFO] [stacks] SaveAppConfig: saved config for outline +2026/09/22 11:55:57 healthprobe.go:162: [WARN] Health probe outline: API GET :3000/_health → Get "http://outline-postgres:3000/_health": dial tcp: lookup outline-postgres on 127.0.0.11:53: no such host +2026/09/22 11:56:09 [DEBUG] [stacks] SaveAppConfig: saving /opt/docker/stacks/outline — 5 env vars, 0 encrypted, 3 sensitive fields +2026/09/22 11:56:09 [INFO] [stacks] SaveAppConfig: saved config for outline +2026/09/22 11:57:16 healthprobe.go:153: [DEBUG] Health probe outline: API GET :3000/_health → 200 (10ms) +2026/09/22 12:02:16 healthprobe.go:153: [DEBUG] Health probe outline: API GET :3000/_health → 200 (6ms) +2026/09/22 12:02:19 [DEBUG] [stacks] SaveAppConfig: saving /opt/docker/stacks/outline — 5 env vars, 0 encrypted, 3 sensitive fields +2026/09/22 12:02:19 [INFO] [stacks] SaveAppConfig: saved config for outline +2026/09/22 12:02:19 desiredstate.go:81: [INFO] [stacks] desired state for outline recorded as "stopped" (was "running") +2026/09/22 12:02:19 manager.go:1217: [DEBUG] [stacks] StopStack outline: current state=running deployed=false containers=3 +2026/09/22 12:02:30 router.go:81: [DEBUG] [api] removeStack: name=outline +2026/09/22 12:02:30 router.go:81: [DEBUG] [api] removeStack: name=outline removeHDDData=true removeBackups=true +2026/09/22 12:02:30 delete.go:418: [DEBUG] [stacks] RemoveStack called: name="outline", removeHDDData=true, backupPathsToRemove=2 +2026/09/22 12:02:30 delete.go:432: [DEBUG] [stacks] RemoveStack outline: state=not_deployed, deployed=false, orphaned=false, deploying=false +2026/09/22 12:02:30 router.go:906: [ERROR] [api] Remove failed for outline: stack "outline" is not deployed +2026/09/22 12:02:30 router.go:81: [DEBUG] [api] removeStack: name=outline +2026/09/22 12:02:30 router.go:81: [DEBUG] [api] removeStack: name=outline removeHDDData=false removeBackups=true +2026/09/22 12:02:30 delete.go:418: [DEBUG] [stacks] RemoveStack called: name="outline", removeHDDData=false, backupPathsToRemove=2 +2026/09/22 12:02:30 delete.go:432: [DEBUG] [stacks] RemoveStack outline: state=not_deployed, deployed=false, orphaned=false, deploying=false +2026/09/22 12:02:30 router.go:906: [ERROR] [api] Remove failed for outline: stack "outline" is not deployed + +=== what is left on disk === +-- outline +app.yaml +docker-compose.yml + app.yaml: +# Auto-generated by felhom-controller — do not edit locked fields manually +deployed: false +deployed_at: "2026-09-22T11:54:47Z" +env: + DB_PASSWORD: ENC:a9/HjqjfhZQiU+q8QCxRkkFzUHJrwKUDi13B/c48q4yfCisJZhfy6ZnJKbnzggcbvQGqLQ== + DOMAIN: enkisfelhom.hu + SECRET_KEY: ENC:aVLWYum6c7/Q79yC0NSZubSdq7mxpF5bvmh7YAnjgLcxFEO4Od7flp6oE5WIdEuFPimPK+UAL0nDD44Hx4CX86qbWwOv6PJH/PA1F9BvjbQVzdAg7I9KUplmjMI= + SUBDOMAIN: outline + UTILS_SECRET: ENC:bRybX+/E5jEJj5wvG9AjC9r70IJlB3sYhVUbAzCnYD6/n/a7H+YX27gZ+ZSzomLpobsE81FPNY1fH9tVWT7DzLNK6RpNUkAuHA3zVvVYe6ky6iK8KLkMgu4BUmQ= +locked_fields: + - DOMAIN + - SUBDOMAIN + - SECRET_KEY + - UTILS_SECRET + - DB_PASSWORD +desired_state: stopped +installed_images: + outline: + ref: outlinewiki/outline:1.9.1 + digest: sha256:9fe2cbdceccea0ffb82cbc8dc6f4c0d4ba1d377454e502d5d2bddececb34d4f1 + at: "2026-09-22T11:56:09Z" + outline-postgres: + ref: postgres:16-alpine + digest: sha256:721873c34ceb9f8d8fc265984940dc982404c105f19ad51be9fdc5970a6080ea + at: "2026-09-22T11:56:09Z" +-- sparkyfitness +app.yaml +docker-compose.yml + app.yaml: +# Auto-generated by felhom-controller — do not edit locked fields manually +deployed: false +deployed_at: "2026-09-22T11:42:00Z" +env: + API_ENCRYPTION_KEY: ENC:PpAejC9LnjlihiNeWgTUi0QDSIL+eH9KkpronmsKmvIYB0/h9eLBNzIXRPsEgw4ORZVxyRgGDynT0eFLXuknH8X96l+ZNyfZMBAzAEKg7f9oSvvdr7qtBa/aJC0= + APP_DB_PASSWORD: ENC:YVkS/V3X7UbiVUJ8owxXhKd+SdiJmCMnYHjA3mjG2sBQObFCUb7TOFnvFCg0Bo+W5ymgPg== + BETTER_AUTH_SECRET: ENC:n2GZ3NoqS4s2yAG/CcD7Z012ovrw9ePVYTyX9gBGURYKSLjsE5BJVrrO0a+2/zNSC/jkbeSkqprpp8TMxYeHWoFvk/UO+bkbEGgig1/GkSs+1jULhlZQuZ+5eWY= + DB_PASSWORD: ENC:5KX8mTlmsrhInKN8RivvPLkgbKqrxguWnUXIFw1n94XKx7Zd7EUX0mA2yek+9vg/MV1AqA== + DOMAIN: enkisfelhom.hu + SUBDOMAIN: sparkyfitness +locked_fields: + - DOMAIN + - SUBDOMAIN + - DB_PASSWORD + - APP_DB_PASSWORD + - API_ENCRYPTION_KEY + - BETTER_AUTH_SECRET +desired_state: stopped +installed_images: + sparkyfitness-db: + ref: postgres:15-alpine + digest: sha256:f7d23353e1b15400d22ebe31189f4d314b87a4c129cc400c8c2d8d4ca127bf81 + at: "2026-09-22T11:43:04Z" + sparkyfitness-frontend: + ref: codewithcj/sparkyfitness:v0.17.3 + +=== containers (expect none) === +NONE + diff --git a/documentation/audits/the-28-2026-09-22/apps/homebox/log.txt b/documentation/audits/the-28-2026-09-22/apps/homebox/log.txt new file mode 100644 index 00000000..bbe9fea3 --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/apps/homebox/log.txt @@ -0,0 +1,20 @@ +13:29:21 ==== homebox (sub=homebox, class=file-leg, edge=none) +13:29:21 [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'} +13:29:41 [1] deployed, controller state=running, pinned={'homebox': 'ghcr.io/sysadminsmedia/homebox:0.26.2'} +13:29:41 [1] front door 200 controller state=running +13:29:42 homebox: create location -> 404 404 page not found + +13:29:42 [2] fixture found no route — inconclusive for the data half +13:29:42 [4] „Mentés most" -> 200 {'ok': True, 'message': 'Mentés elindítva'} +13:29:47 [4] backup idle; last=None +13:29:47 [4] backups page offers 1 restorable copy(ies) +13:29:47 [R] restoring homebox from snapshot 'helyi' (of 1 offered) +13:29:47 [R] POST /backup/restore -> HTTP/2 302 ['location: /backups/restore?flash=flash.restore.started'] +13:29:47 + 0.0s restore (True, None, None) +13:29:57 + 10.2s restore (False, None, None) +13:29:57 [R] after restore: state=running hold=None phase=None +13:30:10 [6] restore -> ok, seed back = False +13:30:10 [X] stop -> 200 {'ok': True, 'message': 'Stack homebox stop completed'} +13:30:16 [X] remove (with drive data) -> 200 {'ok': True, 'data': {'removed': 'homebox', 'volumes_removed': [], 'hdd_paths_removed': [], 'hdd_paths_preserved': [], 'hdd_note': 'Az alkalmazás nem tárolt saj +13:30:24 [X] after remove: deployed=False leftovers='/opt/docker/stacks/homebox' +13:31:26 [7] 60 s after remove: clean=True '' diff --git a/documentation/audits/the-28-2026-09-22/apps/homebox/verdict.json b/documentation/audits/the-28-2026-09-22/apps/homebox/verdict.json new file mode 100644 index 00000000..6037ed72 --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/apps/homebox/verdict.json @@ -0,0 +1,42 @@ +{ + "harness_version": 2, + "app": "homebox", + "venue": "guest 9202 demo-hp-scratch, controller 0.261.0, drill catalog", + "class": "file-leg", + "from": { + "homebox": "ghcr.io/sysadminsmedia/homebox:0.26.2" + }, + "to": {}, + "verdict": "no-edge", + "deployed": true, + "seed_route": "its own /api/v1/users/register + /api/v1/locations", + "seed_read_before": false, + "seed_read_after_update": false, + "backup": { + "snapshots_offered": 1, + "first": { + "time": "2026-09-22T11:29:43Z", + "short_id": "helyi", + "tier": 1, + "drive_label": "Belső SSD (rendszer)" + } + }, + "restore_verdict": "ok", + "seed_read_after_restore": false, + "healthy_after": false, + "migration_observed": null, + "removed_clean": true, + "edge": null, + "duration_s": 127.9, + "measured_at": "2026-09-22T11:29:21.283581+00:00", + "evidence": "the-28-2026-09-22/apps/homebox/", + "notes": [ + "fixture ran and found no non-browser seed route: POST /api/v1/locations -> 404 404 page not found\n" + ], + "front_door_after_deploy": { + "rc": 0, + "code": "200" + }, + "restore_raw": "{'ok': True, 'snapshot_id': 'helyi', 'snapshots': [{'time': '2026-09-22T11:29:43Z', 'short_id': 'helyi', 'tier': 1, 'drive_label': 'Belső SSD (rendszer)'}], 'http': 'HTTP/2 302', 'location': ['location: /backups/restore?flash=flash.restore.started'], 'seconds': 10.2, 'state_after': 'running', 'hold_after': None, 'observables_after': {'pinned_images': {'homebox': 'ghcr.io/sysadminsmedia/homebox:0.2", + "remove_leftovers": "" +} \ No newline at end of file diff --git a/documentation/audits/the-28-2026-09-22/apps/homepage/log.txt b/documentation/audits/the-28-2026-09-22/apps/homepage/log.txt new file mode 100644 index 00000000..1ea88fc5 --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/apps/homepage/log.txt @@ -0,0 +1,18 @@ +13:30:21 ==== homepage (sub=homepage, class=file-leg, edge=none) +13:30:21 [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'} +13:30:51 [1] deployed, controller state=running, pinned={'homepage': 'ghcr.io/gethomepage/homepage:v1.13.2'} +13:30:52 [1] front door 200 controller state=running +13:30:52 [2] NO FIXTURE — recorded inconclusive for the data half, not faked +13:30:52 [4] „Mentés most" -> 200 {'ok': True, 'message': 'Mentés elindítva'} +13:30:57 [4] backup idle; last=None +13:30:57 [4] backups page offers 1 restorable copy(ies) +13:30:57 [R] restoring homepage from snapshot 'helyi' (of 1 offered) +13:30:57 [R] POST /backup/restore -> HTTP/2 302 ['location: /backups/restore?flash=flash.restore.started'] +13:30:57 + 0.0s restore (True, None, None) +13:31:07 + 10.2s restore (False, None, None) +13:31:07 [R] after restore: state=running hold=None phase=None +13:31:20 [6] restore -> ok, seed back = False +13:31:20 [X] stop -> 200 {'ok': True, 'message': 'Stack homepage stop completed'} +13:31:26 [X] remove (with drive data) -> 200 {'ok': True, 'data': {'removed': 'homepage', 'volumes_removed': [], 'hdd_paths_removed': [], 'hdd_paths_preserved': [], 'hdd_note': 'Az alkalmazás nem tárolt sa +13:31:33 [X] after remove: deployed=False leftovers='/opt/docker/stacks/homepage' +13:32:35 [7] 60 s after remove: clean=True '' diff --git a/documentation/audits/the-28-2026-09-22/apps/homepage/verdict.json b/documentation/audits/the-28-2026-09-22/apps/homepage/verdict.json new file mode 100644 index 00000000..8b49199f --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/apps/homepage/verdict.json @@ -0,0 +1,42 @@ +{ + "harness_version": 2, + "app": "homepage", + "venue": "guest 9202 demo-hp-scratch, controller 0.261.0, drill catalog", + "class": "file-leg", + "from": { + "homepage": "ghcr.io/gethomepage/homepage:v1.13.2" + }, + "to": {}, + "verdict": "no-edge", + "deployed": true, + "seed_route": "none written", + "seed_read_before": false, + "seed_read_after_update": false, + "backup": { + "snapshots_offered": 1, + "first": { + "time": "2026-09-22T11:30:53Z", + "short_id": "helyi", + "tier": 1, + "drive_label": "Belső SSD (rendszer)" + } + }, + "restore_verdict": "ok", + "seed_read_after_restore": false, + "healthy_after": false, + "migration_observed": null, + "removed_clean": true, + "edge": null, + "duration_s": 137.1, + "measured_at": "2026-09-22T11:30:21.439525+00:00", + "evidence": "the-28-2026-09-22/apps/homepage/", + "notes": [ + "no fixture: no non-browser seed route was written for this app" + ], + "front_door_after_deploy": { + "rc": 0, + "code": "200" + }, + "restore_raw": "{'ok': True, 'snapshot_id': 'helyi', 'snapshots': [{'time': '2026-09-22T11:30:53Z', 'short_id': 'helyi', 'tier': 1, 'drive_label': 'Belső SSD (rendszer)'}], 'http': 'HTTP/2 302', 'location': ['location: /backups/restore?flash=flash.restore.started'], 'seconds': 10.2, 'state_after': 'running', 'hold_after': None, 'observables_after': {'pinned_images': {'homepage': 'ghcr.io/gethomepage/homepage:v1.1", + "remove_leftovers": "" +} \ No newline at end of file diff --git a/documentation/audits/the-28-2026-09-22/apps/immich/app-logs-during-after.txt b/documentation/audits/the-28-2026-09-22/apps/immich/app-logs-during-after.txt new file mode 100644 index 00000000..1db27736 --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/apps/immich/app-logs-during-after.txt @@ -0,0 +1,343 @@ +immich-machine-learning | [09/22/26 14:14:39] INFO  Starting gunicorn 25.3.0 +immich-machine-learning | [09/22/26 14:14:39] INFO  Listening at: http://[::]:3003 (14) +immich-machine-learning | [09/22/26 14:14:39] INFO  Using worker: immich_ml.config.CustomUvicornWorker +immich-machine-learning | [09/22/26 14:14:39] INFO  Booting worker with pid: 21 +immich-machine-learning | [09/22/26 14:14:43] INFO  Started server process [21] +immich-machine-learning | [09/22/26 14:14:43] INFO  Waiting for application startup. +immich-machine-learning | [09/22/26 14:14:43] INFO  Created in-memory cache with unloading after 300s +immich-machine-learning |   of inactivity. +immich-machine-learning | [09/22/26 14:14:43] INFO  Initialized request thread pool with 8 threads. +immich-machine-learning | [09/22/26 14:14:43] INFO  Application startup complete. +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/assets/:id/edits, DELETE} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RoutesResolver] AssetFilesController {/api/asset-files}: +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/asset-files, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/asset-files/:id, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/asset-files/:id, DELETE} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/asset-files/:id/download, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RoutesResolver] AssetMediaController {/api/assets}: +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/assets, POST} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/assets/:id/original, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/assets/:id/thumbnail, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/assets/:id/video/playback, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/assets/bulk-upload-check, POST} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RoutesResolver] AuthController {/api/auth}: +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/auth/login, POST} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/auth/admin-sign-up, POST} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/auth/validateToken, POST} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/auth/change-password, POST} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/auth/logout, POST} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/auth/status, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/auth/pin-code, POST} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/auth/pin-code, PUT} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/auth/pin-code, DELETE} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/auth/session/unlock, POST} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/auth/session/lock, POST} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RoutesResolver] AuthAdminController {/api/admin/auth}: +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/admin/auth/unlink-all, POST} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RoutesResolver] ClusterGroupController {/api/cluster-groups}: +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/cluster-groups/requests, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/cluster-groups/requests/:id/accept, POST} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/cluster-groups/requests/:id, DELETE} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/cluster-groups/:id/requests, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/cluster-groups/:id/users, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/cluster-groups/:id/requests, PUT} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/cluster-groups/:id/regenerate-people, POST} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/cluster-groups/:id/leave, POST} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RoutesResolver] ConfigUserController {/api/config}: +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/config, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/config/defaults, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RoutesResolver] ConfigAdminController {/api/admin/config}: +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/admin/config, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/admin/config/defaults, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/admin/config, PUT} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RoutesResolver] ConfigPublicController {/api/public/config}: +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/public/config, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/public/config/defaults, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RoutesResolver] DatabaseBackupController {/api/admin/database-backups}: +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/admin/database-backups, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/admin/database-backups/:filename, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/admin/database-backups, DELETE} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/admin/database-backups/start-restore, POST} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/admin/database-backups/upload, POST} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RoutesResolver] DownloadController {/api/download}: +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/download/info, POST} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/download/archive, POST} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RoutesResolver] DuplicateController {/api/duplicates}: +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/duplicates, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/duplicates, DELETE} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/duplicates/:id, DELETE} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/duplicates/resolve, POST} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RoutesResolver] FaceController {/api/faces}: +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/faces, POST} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/faces, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/faces/:id, PUT} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/faces/:id, DELETE} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RoutesResolver] IntegrityAdminController {/api/admin/integrity}: +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/admin/integrity/summary, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/admin/integrity/report, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/admin/integrity/report/:id/file, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/admin/integrity/report/:id, DELETE} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/admin/integrity/report/:type/csv, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RoutesResolver] JobController {/api/jobs}: +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/jobs, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/jobs, POST} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/jobs/:name, PUT} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RoutesResolver] LibraryController {/api/libraries}: +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/libraries, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/libraries, POST} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/libraries/:id, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/libraries/:id, PUT} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/libraries/:id, PATCH} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/libraries/:id, DELETE} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/libraries/:id/validate, POST} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/libraries/:id/statistics, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/libraries/:id/scan, POST} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RoutesResolver] MaintenanceController {/api/admin/maintenance}: +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/admin/maintenance/status, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/admin/maintenance/detect-install, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/admin/maintenance/login, POST} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/admin/maintenance, POST} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RoutesResolver] MapController {/api/map}: +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/map/markers, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/map/reverse-geocode, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RoutesResolver] MemoryController {/api/memories}: +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/memories, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/memories, POST} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/memories/statistics, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/memories/:id, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/memories/:id, PUT} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/memories/:id, PATCH} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/memories/:id, DELETE} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/memories/:id/assets, PUT} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/memories/:id/assets, DELETE} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RoutesResolver] NotificationController {/api/notifications}: +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/notifications, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/notifications, PUT} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/notifications, DELETE} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/notifications/:id, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/notifications/:id, PUT} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/notifications/:id, DELETE} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RoutesResolver] NotificationAdminController {/api/admin/notifications}: +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/admin/notifications, POST} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/admin/notifications/test-email, POST} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/admin/notifications/templates/:name, POST} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RoutesResolver] OAuthController {/api/oauth}: +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/oauth/mobile-redirect, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/oauth/authorize, POST} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/oauth/callback, POST} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/oauth/link, POST} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/oauth/unlink, POST} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/oauth/backchannel-logout, POST} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RoutesResolver] PartnerController {/api/partners}: +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/partners, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/partners, POST} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/partners/:id, POST} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/partners/:id, PUT} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/partners/:id, DELETE} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RoutesResolver] PersonController {/api/people}: +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/people, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/people, POST} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/people, PUT} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/people, DELETE} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/people/:id, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/people/:id, PUT} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/people/:id, PATCH} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/people/:id, DELETE} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/people/:id/statistics, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/people/:id/thumbnail, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/people/:id/reassign, PUT} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/people/merge, POST} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/people/:id/merge, POST} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RoutesResolver] PluginController {/api/plugins}: +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/plugins, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/plugins/methods, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/plugins/templates, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/plugins/:id, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RoutesResolver] QueueController {/api/queues}: +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/queues, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/queues/:name, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/queues/:name, PUT} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/queues/:name/jobs, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/queues/:name/jobs, DELETE} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RoutesResolver] SearchController {/api/search}: +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/search/metadata, POST} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/search/statistics, POST} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/search/random, POST} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/search/large-assets, POST} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/search/smart, POST} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/search/explore, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/search/person, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/search/places, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/search/cities, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/search/suggestions, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RoutesResolver] ServerController {/api/server}: +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/server/about, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/server/apk-links, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/server/storage, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/server/ping, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/server/version, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/server/version-history, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/server/features, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/server/config, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/server/statistics, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/server/media-types, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/server/license, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/server/license, PUT} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/server/license, DELETE} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/server/version-check, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RoutesResolver] SessionController {/api/sessions}: +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/sessions, POST} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/sessions, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/sessions, DELETE} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/sessions/:id, PUT} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/sessions/:id, PATCH} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/sessions/:id, DELETE} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/sessions/:id/lock, POST} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RoutesResolver] SharedLinkController {/api/shared-links}: +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/shared-links, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/shared-links/login, POST} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/shared-links/me, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/shared-links/:id, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/shared-links, POST} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/shared-links/:id, PATCH} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/shared-links/:id, DELETE} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/shared-links/:id/assets, PUT} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/shared-links/:id/assets, DELETE} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RoutesResolver] StackController {/api/stacks}: +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/stacks, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/stacks, POST} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/stacks, DELETE} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/stacks/:id, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/stacks/:id, PUT} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/stacks/:id, PATCH} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/stacks/:id, DELETE} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/stacks/:id/assets/:assetId, DELETE} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RoutesResolver] SyncController {/api/sync}: +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/sync/stream, POST} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/sync/ack, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/sync/ack, POST} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/sync/ack, DELETE} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RoutesResolver] SystemConfigController {/api/system-config}: +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/system-config, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/system-config/defaults, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/system-config, PUT} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/system-config/storage-template-options, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RoutesResolver] SystemMetadataController {/api/system-metadata}: +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/system-metadata/admin-onboarding, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/system-metadata/admin-onboarding, POST} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/system-metadata/reverse-geocoding-state, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/system-metadata/version-check-state, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RoutesResolver] TagController {/api/tags}: +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/tags, POST} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/tags, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/tags, PUT} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/tags/assets, PUT} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/tags/:id, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/tags/:id, PUT} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/tags/:id, PATCH} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/tags/:id, DELETE} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/tags/:id/assets, PUT} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/tags/:id/assets, DELETE} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RoutesResolver] TimelineController {/api/timeline}: +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/timeline/buckets, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/timeline/bucket, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RoutesResolver] TrashController {/api/trash}: +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/trash/empty, POST} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/trash/restore, POST} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/trash/restore/assets, POST} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RoutesResolver] UserAdminController {/api/admin/users}: +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/admin/users, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/admin/users, POST} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/admin/users/:id, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/admin/users/:id, PUT} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/admin/users/:id, PATCH} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/admin/users/:id, DELETE} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/admin/users/:id/calendar-heatmap, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/admin/users/:id/sessions, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/admin/users/:id/statistics, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/admin/users/:id/preferences, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/admin/users/:id/preferences, PUT} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/admin/users/:id/preferences, PATCH} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/admin/users/:id/restore, POST} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RoutesResolver] UserController {/api/users}: +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/users, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/users/me, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/users/me/calendar-heatmap, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/users/me, PUT} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/users/me, PATCH} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/users/me/preferences, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/users/me/preferences, PUT} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/users/me/preferences, PATCH} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/users/me/license, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/users/me/license, PUT} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/users/me/license, DELETE} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/users/me/onboarding, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/users/me/onboarding, PUT} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/users/me/onboarding, DELETE} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/users/:id, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/users/profile-image, POST} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/users/profile-image, DELETE} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/users/:id/profile-image, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RoutesResolver] VideoStreamController {/api/assets}: +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/assets/:id/video/stream/main.m3u8, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/assets/:id/video/stream/:sessionId/:variantIndex/playlist.m3u8, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/assets/:id/video/stream/:sessionId/:variantIndex/:filename, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/assets/:id/video/stream/:sessionId, DELETE} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RoutesResolver] ViewController {/api/view}: +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/view/folder/unique-paths, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/view/folder, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RoutesResolver] WorkflowController {/api/workflows}: +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/workflows, POST} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/workflows, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/workflows/triggers, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/workflows/:id, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/workflows/:id/share, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/workflows/:id, PUT} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/workflows/:id, PATCH} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/workflows/:id, DELETE} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:RouterExplorer] Mapped {/api/workflows/:id/logs, GET} route +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:NestApplication] Nest application successfully started +immich-server | [Nest] 35 - 09/22/2026, 2:16:19 PM  LOG [Api:Bootstrap] Immich Server is listening on http://[::1]:2283 [v3.2.2] [production]  +immich-server | [Nest] 7 - 09/22/2026, 2:16:19 PM  LOG [Microservices:MapRepository] Starting geodata import +immich-server | [Nest] 7 - 09/22/2026, 2:16:22 PM  LOG [Microservices:MapRepository] 10000 geodata records imported +immich-server | [Nest] 7 - 09/22/2026, 2:16:23 PM  LOG [Microservices:MapRepository] 20000 geodata records imported +immich-server | [Nest] 7 - 09/22/2026, 2:16:23 PM  LOG [Microservices:MapRepository] 30000 geodata records imported +immich-server | [Nest] 7 - 09/22/2026, 2:16:23 PM  LOG [Microservices:MapRepository] 40000 geodata records imported +immich-server | [Nest] 7 - 09/22/2026, 2:16:24 PM  LOG [Microservices:MapRepository] 50000 geodata records imported +immich-server | [Nest] 7 - 09/22/2026, 2:16:25 PM  LOG [Microservices:MapRepository] 60000 geodata records imported +immich-server | [Nest] 7 - 09/22/2026, 2:16:25 PM  LOG [Microservices:MapRepository] 70000 geodata records imported +immich-server | [Nest] 7 - 09/22/2026, 2:16:25 PM  LOG [Microservices:MapRepository] 80000 geodata records imported +immich-server | [Nest] 7 - 09/22/2026, 2:16:25 PM  LOG [Microservices:MapRepository] 90000 geodata records imported +immich-postgres | Using SSD storage +immich-postgres | +immich-postgres | PostgreSQL Database directory appears to contain a database; Skipping initialization +immich-postgres | +immich-postgres | 2026-09-22 12:14:36.754 GMT [1] LOG: skipping missing configuration file "/var/lib/postgresql/data/postgresql.override.conf" +immich-postgres | 2026-09-22 12:14:36.755 GMT [1] LOG: skipping missing configuration file "/var/lib/postgresql/data/postgresql.override.conf" +immich-postgres | 2026-09-22 14:14:36.808 CEST [1] LOG: starting PostgreSQL 16.10 (Debian 16.10-1.pgdg12+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 12.2.0-14+deb12u1) 12.2.0, 64-bit +immich-postgres | 2026-09-22 14:14:36.809 CEST [1] LOG: listening on IPv4 address "0.0.0.0", port 5432 +immich-postgres | 2026-09-22 14:14:36.809 CEST [1] LOG: listening on IPv6 address "::", port 5432 +immich-postgres | 2026-09-22 14:14:36.830 CEST [1] LOG: listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432" +immich-postgres | 2026-09-22 14:14:36.846 CEST [34] LOG: database system was shut down at 2026-09-22 14:14:33 CEST +immich-postgres | 2026-09-22 14:14:36.860 CEST [1] LOG: database system is ready to accept connections +immich-postgres | 2026-09-22 14:15:16.945 CEST [97] LOG: skipping vacuum of "geodata_places" --- lock not available +immich-postgres | 2026-09-22 14:16:17.020 CEST [153] LOG: skipping analyze of "geodata_places" --- lock not available +immich-redis | 1:C 22 Sep 2026 14:14:36.615 # WARNING Memory overcommit must be enabled! Without it, a background save or replication may fail under low memory condition. Being disabled, it can also cause failures without low memory condition, see https://github.com/jemalloc/jemalloc/issues/1328. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect. +immich-redis | 1:C 22 Sep 2026 14:14:36.615 * oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo +immich-redis | 1:C 22 Sep 2026 14:14:36.615 * Redis version=7.4.11, bits=64, commit=00000000, modified=0, pid=1, just started +immich-redis | 1:C 22 Sep 2026 14:14:36.615 * Configuration loaded +immich-redis | 1:M 22 Sep 2026 14:14:36.615 * Increased maximum number of open files to 10032 (it was originally set to 1024). +immich-redis | 1:M 22 Sep 2026 14:14:36.615 * monotonic clock: POSIX clock_gettime +immich-redis | 1:M 22 Sep 2026 14:14:36.617 * Running mode=standalone, port=6379. +immich-redis | 1:M 22 Sep 2026 14:14:36.617 * Server initialized +immich-redis | 1:M 22 Sep 2026 14:14:36.617 * Reading RDB base file on AOF loading... +immich-redis | 1:M 22 Sep 2026 14:14:36.617 * Loading RDB produced by version 7.4.11 +immich-redis | 1:M 22 Sep 2026 14:14:36.617 * RDB age 40 seconds +immich-redis | 1:M 22 Sep 2026 14:14:36.617 * RDB memory usage when created 0.90 Mb +immich-redis | 1:M 22 Sep 2026 14:14:36.617 * RDB is base AOF +immich-redis | 1:M 22 Sep 2026 14:14:36.617 * Done loading RDB, keys loaded: 0, keys expired: 0. +immich-redis | 1:M 22 Sep 2026 14:14:36.617 * DB loaded from base file appendonly.aof.1.base.rdb: 0.000 seconds +immich-redis | 1:M 22 Sep 2026 14:14:36.618 * DB loaded from incr file appendonly.aof.1.incr.aof: 0.001 seconds +immich-redis | 1:M 22 Sep 2026 14:14:36.618 * DB loaded from append only file: 0.001 seconds +immich-redis | 1:M 22 Sep 2026 14:14:36.618 * Opening AOF incr file appendonly.aof.1.incr.aof on server start +immich-redis | 1:M 22 Sep 2026 14:14:36.619 * Ready to accept connections tcp diff --git a/documentation/audits/the-28-2026-09-22/apps/immich/log.txt b/documentation/audits/the-28-2026-09-22/apps/immich/log.txt new file mode 100644 index 00000000..bab65952 --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/apps/immich/log.txt @@ -0,0 +1,34 @@ +14:12:19 ==== immich (sub=photos, class=db, edge={'from': 'ghcr.io/immich-app/immich-server:v3.0.3', 'to': 'ghcr.io/immich-app/immich-server:v3.2.2'}) +14:12:22 [1] made the drive paths this app requires: ['/mnt/felhom-drives/scratch_hdd/userdata/immich'] +14:12:22 [1] required fields filled beyond DOMAIN/SUBDOMAIN: ['HDD_PATH'] +14:12:22 [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'} +14:14:28 [1] deployed, controller state=running, pinned={'immich-machine-learning': 'ghcr.io/immich-app/immich-machine-learning:v3.0.3', 'immich-postgres': 'ghcr.io/immich-app/postgres:16-vectorchord0.4.3-pgvectors0.2.0', 'immich-redis': 'redis:7-alpine', 'immich-server': 'ghcr.io/immich-app/immich-server:v3.0.3'} +14:14:28 [1] front door 200 controller state=running +14:14:29 immich: seeded album drillalbum38e0fc27 +14:14:29 immich: readback found=True (http 200, control passed) +14:14:29 [3] C1 readback before = True +14:14:29 [4] „Mentés most" -> 200 {'ok': True, 'message': 'Mentés elindítva'} +14:14:49 [4] backup idle; last=None +14:14:49 [4] backups page offers 1 restorable copy(ies) +14:14:50 [5] drill commit dd0768771f63: immich ghcr.io/immich-app/immich-server:v3.0.3 -> ghcr.io/immich-app/immich-server:v3.2.2 (push rc=0) +14:14:55 [5] badge {'hu': [{'title': 'Újabb változat érhető el ehhez az alkalmazáshoz. A frissítés indításához nyomd meg a Frissítés gombot.', 'text': 'Frissítés elérhető — ma'}], 'en': [{'title': 'A newer version of this app is available. Select the Update button to start it.', 'text': 'Update available — today'}]} +14:14:56 [6] Update -> 202 {'ok': True, 'data': {'accepted': True, 'completed': False}, 'message': 'Frissítés elindult – az állapot a kártyán követhető'} +14:14:56 + 0.0s phase=checking label=Ellenőrzés… err=None hold=None +14:14:57 + 1.1s phase=safety-dump label=Adatbázis pillanatkép… err=None hold=None +14:14:59 + 3.1s phase=pulling label=Új verzió letöltése… err=None hold=None +14:16:04 + 67.9s phase=starting label=Indítás az új verzióval… err=None hold=None +14:16:06 + 70.0s phase=verifying label=Működés ellenőrzése… err=None hold=None +14:16:26 + 90.5s phase=done label=Frissítve err=None hold=None +14:16:32 immich: readback found=True (http 200, control passed) +14:16:32 [R] restoring immich from snapshot 'helyi' (of 1 offered) +14:16:32 [R] POST /backup/restore -> HTTP/2 302 ['location: /backups/restore?flash_error=Ez+a+ment%C3%A9s+nem+tartalmazza+az+alkalmaz%C3%A1s+f%C3%A1jljait%2C+ez%C3%A9rt+nem+%C3%A1ll%C3%ADtjuk+vissza+az+adatb%C3%A1zist+f%C3%B6l%C3%A9j%C3%BCk+%E2%80%94+a+f%C3%A1jlok+%C3%ADgy+a+hely%C3%BCk%C3%B6n+maradnak.+A+f%C3%A1jlok+a+t%C3%A1voli+m%C3%A1solatb%C3%B3l+%C3%A1ll%C3%ADthat%C3%B3k+vissza%3A+Biztons%C3%A1gi+ment%C3%A9s+%E2%86%92+Vissza%C3%A1ll%C3%ADt%C3%A1s%2C+%E2%80%9ETeljes+vissza%C3%A1ll%C3%ADt%C3%A1s+%28f%C3%A1jlok+%2B+adatb%C3%A1zis%29%E2%80%9D.'] +14:16:32 + 0.0s restore (False, None, None) +14:16:39 [R] after restore: state=running hold=None phase=done +14:16:57 immich: readback found=True (http 200, control passed) +14:16:57 [6] restore -> refused-with-a-sentence, seed back = True +14:17:09 [X] stop -> 200 {'ok': True, 'message': 'Stack immich stop completed'} +14:17:14 [X] remove (with drive data) -> 409 {'ok': False, 'error': 'A(z) /mnt/felhom-drives/scratch_hdd/userdata/immich tárhely jelenleg nem elérhető — az alkalmazás nem távolítható el, amíg a meghajtó vi +14:17:14 [X] refused because the drive path cannot be resolved (R-442, fail-closed and right) — removing the app and KEEPING the drive data instead +14:17:14 [X] remove (keeping drive data) -> 200 {'ok': True, 'data': {'removed': 'immich', 'volumes_removed': ['immich_immich_ml_cache', 'immich_immich_postgres_data', 'immich_immich_redis_data'], 'hdd_paths_ +14:17:22 [X] after remove: deployed=False leftovers='/opt/docker/stacks/immich' +14:18:26 [7] 60 s after remove: clean=True '' diff --git a/documentation/audits/the-28-2026-09-22/apps/immich/verdict.json b/documentation/audits/the-28-2026-09-22/apps/immich/verdict.json new file mode 100644 index 00000000..49654f00 --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/apps/immich/verdict.json @@ -0,0 +1,160 @@ +{ + "harness_version": 2, + "app": "immich", + "venue": "guest 9202 demo-hp-scratch, controller 0.261.0, drill catalog", + "class": "db", + "from": { + "immich-machine-learning": "ghcr.io/immich-app/immich-machine-learning:v3.0.3", + "immich-postgres": "ghcr.io/immich-app/postgres:16-vectorchord0.4.3-pgvectors0.2.0", + "immich-redis": "redis:7-alpine", + "immich-server": "ghcr.io/immich-app/immich-server:v3.0.3" + }, + "to": { + "immich-machine-learning": "ghcr.io/immich-app/immich-machine-learning:v3.0.3", + "immich-postgres": "ghcr.io/immich-app/postgres:16-vectorchord0.4.3-pgvectors0.2.0", + "immich-redis": "redis:7-alpine", + "immich-server": "ghcr.io/immich-app/immich-server:v3.2.2" + }, + "verdict": "proven", + "deployed": true, + "seed_route": "its own /api/auth/admin-sign-up, then an album", + "seed_read_before": true, + "seed_read_after_update": true, + "backup": { + "snapshots_offered": 1, + "first": { + "time": "2026-09-22T12:14:47Z", + "short_id": "helyi", + "tier": 1, + "drive_label": "immich" + } + }, + "restore_verdict": "refused-with-a-sentence", + "seed_read_after_restore": true, + "healthy_after": true, + "migration_observed": null, + "removed_clean": true, + "edge": { + "from": "ghcr.io/immich-app/immich-server:v3.0.3", + "to": "ghcr.io/immich-app/immich-server:v3.2.2" + }, + "duration_s": 375.0, + "measured_at": "2026-09-22T12:12:19.159912+00:00", + "evidence": "the-28-2026-09-22/apps/immich/", + "notes": [], + "front_door_after_deploy": { + "rc": 0, + "code": "200" + }, + "drill_commit": "dd0768771f63", + "badge_seconds": 4.6, + "badges": { + "hu": [ + { + "title": "Újabb változat érhető el ehhez az alkalmazáshoz. A frissítés indításához nyomd meg a Frissítés gombot.", + "text": "Frissítés elérhető — ma" + } + ], + "en": [ + { + "title": "A newer version of this app is available. Select the Update button to start it.", + "text": "Update available — today" + } + ] + }, + "phases": { + "accepted": true, + "http": "202", + "phases": [ + { + "t": 0.0, + "phase": "checking", + "label": "Ellenőrzés…", + "updating": true, + "error": null, + "hold": null + }, + { + "t": 1.1, + "phase": "safety-dump", + "label": "Adatbázis pillanatkép…", + "updating": true, + "error": null, + "hold": null + }, + { + "t": 3.1, + "phase": "pulling", + "label": "Új verzió letöltése…", + "updating": true, + "error": null, + "hold": null + }, + { + "t": 67.9, + "phase": "starting", + "label": "Indítás az új verzióval…", + "updating": true, + "error": null, + "hold": null + }, + { + "t": 70.0, + "phase": "verifying", + "label": "Működés ellenőrzése…", + "updating": true, + "error": null, + "hold": null + }, + { + "t": 90.5, + "phase": "done", + "label": "Frissítve", + "updating": false, + "error": null, + "hold": null + } + ], + "duration_s": 90.6, + "final_phase": "done", + "update_error": null, + "hold_reason": null, + "state": "running" + }, + "observables": { + "pinned_images": { + "immich-machine-learning": "ghcr.io/immich-app/immich-machine-learning:v3.0.3", + "immich-postgres": "ghcr.io/immich-app/postgres:16-vectorchord0.4.3-pgvectors0.2.0", + "immich-redis": "redis:7-alpine", + "immich-server": "ghcr.io/immich-app/immich-server:v3.2.2" + }, + "installed_images": { + "immich-machine-learning": "ghcr.io/immich-app/immich-machine-learning:v3.0.3", + "immich-postgres": "ghcr.io/immich-app/postgres:16-vectorchord0.4.3-pgvectors0.2.0", + "immich-redis": "redis:7-alpine", + "immich-server": "ghcr.io/immich-app/immich-server:v3.2.2" + }, + "catalog_images": { + "immich-machine-learning": "ghcr.io/immich-app/immich-machine-learning:v3.0.3", + "immich-postgres": "ghcr.io/immich-app/postgres:16-vectorchord0.4.3-pgvectors0.2.0", + "immich-redis": "redis:7-alpine", + "immich-server": "ghcr.io/immich-app/immich-server:v3.2.2" + }, + "live_compose_image_lines": [ + "image: ghcr.io/immich-app/immich-server:v3.2.2", + "image: ghcr.io/immich-app/immich-machine-learning:v3.0.3", + "image: ghcr.io/immich-app/postgres:16-vectorchord0.4.3-pgvectors0.2.0", + "image: redis:7-alpine" + ], + "docker_inspect": [ + "immich-server ghcr.io/immich-app/immich-server:v3.2.2 running=true restarts=0", + "immich-redis redis:7-alpine running=true restarts=0", + "immich-postgres ghcr.io/immich-app/postgres:16-vectorchord0.4.3-pgvectors0.2.0 running=true restarts=0", + "immich-machine-learning ghcr.io/immich-app/immich-machine-learning:v3.0.3 running=true restarts=0" + ] + }, + "update_phase_final": "done", + "restore_raw": "{'ok': True, 'snapshot_id': 'helyi', 'snapshots': [{'time': '2026-09-22T12:14:58Z', 'short_id': 'helyi', 'tier': 1, 'drive_label': 'immich'}], 'http': 'HTTP/2 302', 'location': ['location: /backups/restore?flash_error=Ez+a+ment%C3%A9s+nem+tartalmazza+az+alkalmaz%C3%A1s+f%C3%A1jljait%2C+ez%C3%A9rt+nem+%C3%A1ll%C3%ADtjuk+vissza+az+adatb%C3%A1zist+f%C3%B6l%C3%A9j%C3%BCk+%E2%80%94+a+f%C3%A1jlok+%C3%AD", + "restore_refusal": "Ez a mentés nem tartalmazza az alkalmazás fájljait, ezért nem állítjuk vissza az adatbázist föléjük — a fájlok így a helyükön maradnak. A fájlok a távoli másolatból állíthatók vissza: Biztonsági mentés → Visszaállítás, „Teljes visszaállítás (fájlok + adatbázis)”.", + "remove_leftovers": "" +} \ No newline at end of file diff --git a/documentation/audits/the-28-2026-09-22/apps/jellyfin/log.txt b/documentation/audits/the-28-2026-09-22/apps/jellyfin/log.txt new file mode 100644 index 00000000..e22de4de --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/apps/jellyfin/log.txt @@ -0,0 +1,24 @@ +14:04:03 ==== jellyfin (sub=jellyfin, class=file-leg, edge=none) +14:04:06 [1] made the drive paths this app requires: ['/mnt/felhom-drives/scratch_hdd/userdata/jellyfin'] +14:04:06 [1] required fields filled beyond DOMAIN/SUBDOMAIN: ['HDD_PATH'] +14:04:06 [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'} +14:04:41 [1] deployed, controller state=unhealthy, pinned={'jellyfin': 'jellyfin/jellyfin:10.11.11'} +14:04:41 [1] NOTE: the controller's own state is 'unhealthy', not 'running' — recorded, not treated as a failure; the fixture's front-door wait is the real gate +14:04:41 [1] front door 302 controller state=unhealthy +14:04:41 jellyfin: /Startup/User -> 503 Jellyfin Server is loading. Please try again shortly. +14:04:41 [2] fixture found no route — inconclusive for the data half +14:04:41 [4] „Mentés most" -> 409 {'ok': False, 'error': 'Mentés már folyamatban'} +14:05:27 [4] backup idle; last=None +14:05:27 [4] backups page offers 1 restorable copy(ies) +14:05:27 [R] restoring jellyfin from snapshot 'helyi' (of 1 offered) +14:05:27 [R] POST /backup/restore -> HTTP/2 302 ['location: /backups/restore?flash=flash.restore.started'] +14:05:27 + 0.0s restore (True, None, None) +14:05:57 + 30.5s restore (False, None, None) +14:05:57 [R] after restore: state=running hold=None phase=None +14:06:15 [6] restore -> ok, seed back = False +14:06:25 [X] stop -> 200 {'ok': True, 'message': 'Stack jellyfin stop completed'} +14:06:30 [X] remove (with drive data) -> 409 {'ok': False, 'error': 'A(z) /mnt/felhom-drives/scratch_hdd/userdata/jellyfin tárhely jelenleg nem elérhető — az alkalmazás nem távolítható el, amíg a meghajtó +14:06:30 [X] refused because the drive path cannot be resolved (R-442, fail-closed and right) — removing the app and KEEPING the drive data instead +14:06:31 [X] remove (keeping drive data) -> 200 {'ok': True, 'data': {'removed': 'jellyfin', 'volumes_removed': [], 'hdd_paths_removed': [], 'hdd_paths_preserved': [], 'backup_paths_removed': ['/mnt/felhom-dr +14:06:38 [X] after remove: deployed=False leftovers='/opt/docker/stacks/jellyfin' +14:07:41 [7] 60 s after remove: clean=True '' diff --git a/documentation/audits/the-28-2026-09-22/apps/jellyfin/verdict.json b/documentation/audits/the-28-2026-09-22/apps/jellyfin/verdict.json new file mode 100644 index 00000000..5aaf2401 --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/apps/jellyfin/verdict.json @@ -0,0 +1,43 @@ +{ + "harness_version": 2, + "app": "jellyfin", + "venue": "guest 9202 demo-hp-scratch, controller 0.261.0, drill catalog", + "class": "file-leg", + "from": { + "jellyfin": "jellyfin/jellyfin:10.11.11" + }, + "to": {}, + "verdict": "no-edge", + "deployed": true, + "seed_route": "its own /Startup/User wizard, then /Users/Public", + "seed_read_before": false, + "seed_read_after_update": false, + "backup": { + "snapshots_offered": 1, + "first": { + "time": "2026-09-22T12:05:23Z", + "short_id": "helyi", + "tier": 1, + "drive_label": "jellyfin" + } + }, + "restore_verdict": "ok", + "seed_read_after_restore": false, + "healthy_after": false, + "migration_observed": null, + "removed_clean": true, + "edge": null, + "duration_s": 220.7, + "measured_at": "2026-09-22T12:04:03.622366+00:00", + "evidence": "the-28-2026-09-22/apps/jellyfin/", + "notes": [ + "fixture ran and found no non-browser seed route: POST /Startup/User -> 503 Jellyfin Server is loading. Please try again shortly." + ], + "front_door_after_deploy": { + "rc": 0, + "code": "302" + }, + "restore_raw": "{'ok': True, 'snapshot_id': 'helyi', 'snapshots': [{'time': '2026-09-22T12:05:23Z', 'short_id': 'helyi', 'tier': 1, 'drive_label': 'jellyfin'}], 'http': 'HTTP/2 302', 'location': ['location: /backups/restore?flash=flash.restore.started'], 'seconds': 30.5, 'state_after': 'running', 'hold_after': None, 'observables_after': {'pinned_images': {'jellyfin': 'jellyfin/jellyfin:10.11.11'}, 'installed_imag", + "restore_refusal": null, + "remove_leftovers": "" +} \ No newline at end of file diff --git a/documentation/audits/the-28-2026-09-22/apps/kimai/log.txt b/documentation/audits/the-28-2026-09-22/apps/kimai/log.txt new file mode 100644 index 00000000..eb6c12c2 --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/apps/kimai/log.txt @@ -0,0 +1,22 @@ +13:36:00 ==== kimai (sub=kimai, class=db, edge=none) +13:36:00 [1] required fields filled beyond DOMAIN/SUBDOMAIN: ['ADMIN_PASSWORD'] +13:36:00 [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'} +13:37:41 [1] deployed, controller state=running, pinned={'kimai': 'kimai/kimai2:apache-2.57.0', 'kimai-db': 'mariadb:11.6'} +13:37:41 [1] front door 302 controller state=running +13:37:44 kimai: seeded user drill806b99ea +13:37:47 kimai: readback found=False (control passed) +13:37:47 [3] C1 readback before = False +13:37:47 [4] „Mentés most" -> 200 {'ok': True, 'message': 'Mentés elindítva'} +13:38:42 [4] backup idle; last=None +13:38:42 [4] backups page offers 1 restorable copy(ies) +13:38:42 [R] restoring kimai from snapshot 'helyi' (of 1 offered) +13:38:42 [R] POST /backup/restore -> HTTP/2 302 ['location: /backups/restore?flash=flash.restore.started'] +13:38:43 + 0.0s restore (True, None, None) +13:39:33 + 50.7s restore (False, None, None) +13:39:33 [R] after restore: state=running hold=None phase=None +13:39:49 kimai: readback found=True (control passed) +13:39:49 [6] restore -> ok, seed back = True +13:39:51 [X] stop -> 200 {'ok': True, 'message': 'Stack kimai stop completed'} +13:39:57 [X] remove (with drive data) -> 200 {'ok': True, 'data': {'removed': 'kimai', 'volumes_removed': [], 'hdd_paths_removed': [], 'hdd_paths_preserved': [], 'hdd_note': 'Az alkalmazás nem tárolt saját +13:40:05 [X] after remove: deployed=False leftovers='/opt/docker/stacks/kimai' +13:41:07 [7] 60 s after remove: clean=True '' diff --git a/documentation/audits/the-28-2026-09-22/apps/kimai/verdict.json b/documentation/audits/the-28-2026-09-22/apps/kimai/verdict.json new file mode 100644 index 00000000..869abb9b --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/apps/kimai/verdict.json @@ -0,0 +1,41 @@ +{ + "harness_version": 2, + "app": "kimai", + "venue": "guest 9202 demo-hp-scratch, controller 0.261.0, drill catalog", + "class": "db", + "from": { + "kimai": "kimai/kimai2:apache-2.57.0", + "kimai-db": "mariadb:11.6" + }, + "to": {}, + "verdict": "no-edge", + "deployed": true, + "seed_route": "its own `bin/console kimai:user:create`", + "seed_read_before": false, + "seed_read_after_update": false, + "backup": { + "snapshots_offered": 1, + "first": { + "time": "2026-09-22T11:38:41Z", + "short_id": "helyi", + "tier": 1, + "drive_label": "Belső SSD (rendszer)" + } + }, + "restore_verdict": "ok", + "seed_read_after_restore": true, + "healthy_after": false, + "migration_observed": null, + "removed_clean": true, + "edge": null, + "duration_s": 311.3, + "measured_at": "2026-09-22T11:36:00.268333+00:00", + "evidence": "the-28-2026-09-22/apps/kimai/", + "notes": [], + "front_door_after_deploy": { + "rc": 0, + "code": "302" + }, + "restore_raw": "{'ok': True, 'snapshot_id': 'helyi', 'snapshots': [{'time': '2026-09-22T11:38:41Z', 'short_id': 'helyi', 'tier': 1, 'drive_label': 'Belső SSD (rendszer)'}], 'http': 'HTTP/2 302', 'location': ['location: /backups/restore?flash=flash.restore.started'], 'seconds': 50.7, 'state_after': 'running', 'hold_after': None, 'observables_after': {'pinned_images': {'kimai': 'kimai/kimai2:apache-2.57.0', 'kimai-", + "remove_leftovers": "" +} \ No newline at end of file diff --git a/documentation/audits/the-28-2026-09-22/apps/komga/app-logs-during-after.txt b/documentation/audits/the-28-2026-09-22/apps/komga/app-logs-during-after.txt new file mode 100644 index 00000000..760bc46c --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/apps/komga/app-logs-during-after.txt @@ -0,0 +1,63 @@ +komga | kotlin-logging: initializing... active logger factory: Slf4jLoggerFactory +komga | Standard Commons Logging discovery in action with spring-jcl: please remove commons-logging.jar from classpath in order to avoid potential conflicts +komga | ____ __. +komga | | |/ _|____ _____ _________ +komga | | < / _ \ / \ / ___\__ \ +komga | | | ( <_> ) Y Y \/ /_/ > __ \_ +komga | |____|__ \____/|__|_| /\___ (____ / +komga | \/ \//_____/ \/ +komga | +komga | Version: 1.27.1 +komga | +komga | 2026-09-22T14:01:10.776+02:00 INFO 1 --- [ main] org.gotson.komga.ApplicationKt : Starting ApplicationKt v1.27.1 using Java 23.0.2 with PID 1 (/app/application.jar started by root in /app) +komga | 2026-09-22T14:01:10.781+02:00 INFO 1 --- [ main] org.gotson.komga.ApplicationKt : The following 1 profile is active: "docker" +komga | 2026-09-22T14:01:17.121+02:00 INFO 1 --- [ main] com.zaxxer.hikari.HikariDataSource : SqliteMainPoolRW - Starting... +komga | 2026-09-22T14:01:17.522+02:00 INFO 1 --- [ main] com.zaxxer.hikari.pool.HikariPool : SqliteMainPoolRW - Added connection org.sqlite.jdbc4.JDBC4Connection@50628080 +komga | 2026-09-22T14:01:17.524+02:00 INFO 1 --- [ main] com.zaxxer.hikari.HikariDataSource : SqliteMainPoolRW - Start completed. +komga | 2026-09-22T14:01:17.892+02:00 INFO 1 --- [ main] org.flywaydb.core.FlywayExecutor : Database: jdbc:sqlite:/config/database.sqlite (SQLite 3.53) +komga | 2026-09-22T14:01:18.173+02:00 INFO 1 --- [ main] o.f.core.internal.command.DbValidate : Successfully validated 92 migrations (execution time 00:00.231s) +komga | 2026-09-22T14:01:18.184+02:00 INFO 1 --- [ main] o.f.core.internal.command.DbMigrate : Current version of schema "main": 20250730173126 +komga | 2026-09-22T14:01:18.204+02:00 INFO 1 --- [ main] o.f.core.internal.command.DbMigrate : Migrating schema "main" to version "20260225161438 - tags view" +komga | 2026-09-22T14:01:18.232+02:00 INFO 1 --- [ main] o.f.core.internal.command.DbMigrate : Migrating schema "main" to version "20260921111319 - book projection" +komga | 2026-09-22T14:01:18.251+02:00 INFO 1 --- [ main] o.f.core.internal.command.DbMigrate : Successfully applied 2 migrations to schema "main", now at version v20260921111319 (execution time 00:00.006s) +komga | 2026-09-22T14:01:19.344+02:00 INFO 1 --- [ main] com.zaxxer.hikari.HikariDataSource : SqliteMainPoolRO - Starting... +komga | 2026-09-22T14:01:19.348+02:00 INFO 1 --- [ main] com.zaxxer.hikari.pool.HikariPool : SqliteMainPoolRO - Added connection org.sqlite.jdbc4.JDBC4Connection@29088d3d +komga | 2026-09-22T14:01:19.348+02:00 INFO 1 --- [ main] com.zaxxer.hikari.HikariDataSource : SqliteMainPoolRO - Start completed. +komga | 2026-09-22T14:01:20.275+02:00 INFO 1 --- [ main] o.j.i.D.logVersionSupport : Version : Database version is supported by dialect SQLDialect.SQLITE: 3.53.2 +komga | 2026-09-22T14:01:20.724+02:00 INFO 1 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port 25600 (http) +komga | 2026-09-22T14:01:20.747+02:00 INFO 1 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat] +komga | 2026-09-22T14:01:20.748+02:00 INFO 1 --- [ main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.55] +komga | 2026-09-22T14:01:20.794+02:00 INFO 1 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext +komga | 2026-09-22T14:01:20.795+02:00 INFO 1 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 9743 ms +komga | Standard Commons Logging discovery in action with spring-jcl: please remove commons-logging.jar from classpath in order to avoid potential conflicts +komga | 2026-09-22T14:01:21.664+02:00 INFO 1 --- [ main] o.g.k.i.kobo.KepubConverter : Kepub conversion available. kepubify path: /usr/bin/kepubify +komga | 2026-09-22T14:01:21.822+02:00 INFO 1 --- [ main] c.g.g.n.h.i.plugins.HeifLibraryLoader : Loaded libheif v1.21.2 +komga | 2026-09-22T14:01:21.864+02:00 INFO 1 --- [ main] c.g.g.n.j.i.plugins.JxlImageReaderSpi : Loaded libjxl v0.11.1 +komga | 2026-09-22T14:01:21.889+02:00 INFO 1 --- [ main] c.g.g.n.w.i.plugins.WebpImageReaderSpi : Loaded libwebp: decoder v1.5.0, demux v1.5.0 +komga | 2026-09-22T14:01:21.907+02:00 INFO 1 --- [ main] o.g.k.i.image.ImageConverter : Supported read formats: [JPG, JPEG 2000, tiff, bmp, PCX, bigtiff, gif, WBMP, PNG, RAW, JPEG, AVIF, PNM, BigTIFF, tif, TIFF, jpeg, wbmp, jpeg-lossless, jbig2, jxl, jpg, JPEG2000, BMP, pcx, GIF, Jpeg XL, heic, png, raw, BIGTIFF, heif, JPEG-LOSSLESS, webp, JBIG2, pnm, TIF, jpeg2000, WebP, HEIC, jpeg 2000, HEIF, avif] +komga | 2026-09-22T14:01:21.908+02:00 INFO 1 --- [ main] o.g.k.i.image.ImageConverter : Supported read mediaTypes: [image/vnd.wap.wbmp, image/jpeg, image/x-portable-graymap, image/bmp, image/x-windows-pcx, image/gif, image/x-pc-paintbrush, image/x-raw, image/webp, image/heif-sequence, image/x-pcx, image/heic-sequence, image/avif, image/x-portable-bitmap, image/heif, image/heic, image/x-jb2, image/png, image/pcx, image/x-windows-bmp, image/jpeg2000, image/x-bmp, image/jp2, image/x-png, image/x-portable-pixmap, image/tiff, image/x-tiff, image/x-jbig2, image/x-portable-anymap, image/jxl] +komga | 2026-09-22T14:01:21.909+02:00 INFO 1 --- [ main] o.g.k.i.image.ImageConverter : Supported write formats: [JPEG 2000, JPG, tiff, PCX, bmp, bigtiff, gif, WBMP, PNG, RAW, JPEG, AVIF, PNM, BigTIFF, tif, TIFF, wbmp, jpeg, jpg, JPEG2000, pcx, BMP, GIF, heic, png, raw, BIGTIFF, heif, pnm, TIF, jpeg2000, HEIC, jpeg 2000, HEIF, avif] +komga | 2026-09-22T14:01:21.910+02:00 INFO 1 --- [ main] o.g.k.i.image.ImageConverter : Supported write mediaTypes: [image/vnd.wap.wbmp, image/jpeg, image/x-portable-graymap, image/bmp, image/x-windows-pcx, image/gif, image/x-pc-paintbrush, image/x-raw, image/heif-sequence, image/x-pcx, image/heic-sequence, image/avif, image/x-portable-bitmap, image/heif, image/heic, image/png, image/pcx, image/x-windows-bmp, image/jpeg2000, image/x-bmp, image/jp2, image/x-png, image/x-portable-pixmap, image/tiff, image/x-tiff, image/x-portable-anymap] +komga | 2026-09-22T14:01:22.109+02:00 INFO 1 --- [ main] com.zaxxer.hikari.HikariDataSource : SqliteTasksPoolRW - Starting... +komga | 2026-09-22T14:01:22.111+02:00 INFO 1 --- [ main] com.zaxxer.hikari.pool.HikariPool : SqliteTasksPoolRW - Added connection org.sqlite.jdbc4.JDBC4Connection@7cc966a9 +komga | 2026-09-22T14:01:22.112+02:00 INFO 1 --- [ main] com.zaxxer.hikari.HikariDataSource : SqliteTasksPoolRW - Start completed. +komga | 2026-09-22T14:01:22.135+02:00 INFO 1 --- [ main] org.flywaydb.core.FlywayExecutor : Database: jdbc:sqlite:/config/tasks.sqlite (SQLite 3.53) +komga | 2026-09-22T14:01:22.139+02:00 INFO 1 --- [ main] o.f.core.internal.command.DbValidate : Successfully validated 1 migration (execution time 00:00.002s) +komga | 2026-09-22T14:01:22.141+02:00 INFO 1 --- [ main] o.f.core.internal.command.DbMigrate : Current version of schema "main": 20231013114850 +komga | 2026-09-22T14:01:22.142+02:00 INFO 1 --- [ main] o.f.core.internal.command.DbMigrate : Schema "main" is up to date. No migration necessary. +komga | 2026-09-22T14:01:22.353+02:00 INFO 1 --- [ main] com.zaxxer.hikari.HikariDataSource : SqliteTasksPoolRO - Starting... +komga | 2026-09-22T14:01:22.355+02:00 INFO 1 --- [ main] com.zaxxer.hikari.pool.HikariPool : SqliteTasksPoolRO - Added connection org.sqlite.jdbc4.JDBC4Connection@5abf9a76 +komga | 2026-09-22T14:01:22.355+02:00 INFO 1 --- [ main] com.zaxxer.hikari.HikariDataSource : SqliteTasksPoolRO - Start completed. +komga | 2026-09-22T14:01:22.766+02:00 WARN 1 --- [ main] org.apache.lucene.store.MMapDirectory : You are running with Java 22 or later. To make full use of MMapDirectory, please update Apache Lucene. +komga | 2026-09-22T14:01:24.877+02:00 INFO 1 --- [ main] o.g.k.i.api.rest.FontsController : Fonts embedded: {OpenDyslexic=[class path resource [embeddedFonts/OpenDyslexic/OpenDyslexic-Bold-Italic.woff], class path resource [embeddedFonts/OpenDyslexic/OpenDyslexic-Bold-Italic.woff2], class path resource [embeddedFonts/OpenDyslexic/OpenDyslexic-Bold.woff], class path resource [embeddedFonts/OpenDyslexic/OpenDyslexic-Bold.woff2], class path resource [embeddedFonts/OpenDyslexic/OpenDyslexic-Italic.woff], class path resource [embeddedFonts/OpenDyslexic/OpenDyslexic-Italic.woff2], class path resource [embeddedFonts/OpenDyslexic/OpenDyslexic-Regular.woff], class path resource [embeddedFonts/OpenDyslexic/OpenDyslexic-Regular.woff2]]} +komga | 2026-09-22T14:01:24.878+02:00 INFO 1 --- [ main] o.g.k.i.api.rest.FontsController : Fonts discovered: {} +komga | 2026-09-22T14:01:25.378+02:00 INFO 1 --- [ main] o.s.b.a.w.s.WelcomePageHandlerMapping : Adding welcome page: class path resource [public/index.html] +komga | 2026-09-22T14:01:26.278+02:00 INFO 1 --- [ main] o.s.b.a.e.web.EndpointLinksResolver : Exposing 18 endpoints beneath base path '/actuator' +komga | 2026-09-22T14:01:26.373+02:00 INFO 1 --- [ main] eAuthenticationProviderManagerConfigurer : Global AuthenticationManager configured with AuthenticationProvider bean with name apiKeyAuthenticationProvider +komga | 2026-09-22T14:01:27.669+02:00 INFO 1 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port 25600 (http) with context path '/' +komga | 2026-09-22T14:01:27.685+02:00 INFO 1 --- [ main] org.gotson.komga.ApplicationKt : Started ApplicationKt in 17.936 seconds (process running for 18.652) +komga | 2026-09-22T14:01:27.741+02:00 INFO 1 --- [ scheduling-1] .AuthenticationActivityCleanupController : Remove authentication activity older than 2026-08-22T12:01:27.740424200 (UTC) +komga | 2026-09-22T14:01:27.880+02:00 INFO 1 --- [ task-4] o.g.k.i.scheduler.SearchIndexController : Lucene index version: 8 +komga | 2026-09-22T14:01:29.513+02:00 INFO 1 --- [io-25600-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet' +komga | 2026-09-22T14:01:29.513+02:00 INFO 1 --- [io-25600-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet' +komga | 2026-09-22T14:01:29.519+02:00 INFO 1 --- [io-25600-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 5 ms diff --git a/documentation/audits/the-28-2026-09-22/apps/komga/log.txt b/documentation/audits/the-28-2026-09-22/apps/komga/log.txt new file mode 100644 index 00000000..87730b97 --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/apps/komga/log.txt @@ -0,0 +1,34 @@ +13:59:50 ==== komga (sub=komga, class=file-leg, edge={'from': 'gotson/komga:1.25.0', 'to': 'gotson/komga:1.27.1'}) +13:59:52 [1] made the drive paths this app requires: ['/mnt/felhom-drives/scratch_hdd/userdata/komga'] +13:59:52 [1] required fields filled beyond DOMAIN/SUBDOMAIN: ['HDD_PATH'] +13:59:52 [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'} +14:00:38 [1] deployed, controller state=running, pinned={'komga': 'gotson/komga:1.25.0'} +14:00:38 [1] front door 200 controller state=running +14:00:39 komga: claimed the server as drilldaee88a7@example.invalid +14:00:39 komga: readback found=False (http 404, control refused 401) +14:00:39 [3] C1 readback before = False +14:00:39 [4] „Mentés most" -> 200 {'ok': True, 'message': 'Mentés elindítva'} +14:00:44 [4] backup idle; last=None +14:00:44 [4] backups page offers 1 restorable copy(ies) +14:00:45 [5] drill commit b534b8f95a8d: komga gotson/komga:1.25.0 -> gotson/komga:1.27.1 (push rc=0) +14:00:50 [5] badge {'hu': [{'title': 'Újabb változat érhető el ehhez az alkalmazáshoz. A frissítés indításához nyomd meg a Frissítés gombot.', 'text': 'Frissítés elérhető — ma'}], 'en': [{'title': 'A newer version of this app is available. Select the Update button to start it.', 'text': 'Update available — today'}]} +14:00:50 [6] Update -> 202 {'ok': True, 'data': {'accepted': True, 'completed': False}, 'message': 'Frissítés elindult – az állapot a kártyán követhető'} +14:00:50 + 0.1s phase=safety-dump label=Adatbázis pillanatkép… err=None hold=None +14:00:51 + 1.1s phase=pulling label=Új verzió letöltése… err=None hold=None +14:01:07 + 16.5s phase=starting label=Indítás az új verzióval… err=None hold=None +14:01:09 + 18.6s phase=verifying label=Működés ellenőrzése… err=None hold=None +14:01:34 + 44.3s phase=done label=Frissítve err=None hold=None +14:01:38 komga: readback found=False (http 404, control refused 401) +14:01:38 [R] restoring komga from snapshot 'helyi' (of 1 offered) +14:01:38 [R] POST /backup/restore -> HTTP/2 302 ['location: /backups/restore?flash=flash.restore.started'] +14:01:38 + 0.0s restore (True, None, None) +14:02:10 + 32.5s restore (False, None, None) +14:02:10 [R] after restore: state=running hold=None phase=done +14:02:29 komga: readback found=False (http 404, control refused 401) +14:02:29 [6] restore -> ok, seed back = False +14:02:42 [X] stop -> 200 {'ok': True, 'message': 'Stack komga stop completed'} +14:02:47 [X] remove (with drive data) -> 409 {'ok': False, 'error': 'A(z) /mnt/felhom-drives/scratch_hdd/userdata/komga tárhely jelenleg nem elérhető — az alkalmazás nem távolítható el, amíg a meghajtó vis +14:02:47 [X] refused because the drive path cannot be resolved (R-442, fail-closed and right) — removing the app and KEEPING the drive data instead +14:02:47 [X] remove (keeping drive data) -> 200 {'ok': True, 'data': {'removed': 'komga', 'volumes_removed': [], 'hdd_paths_removed': [], 'hdd_paths_preserved': [], 'backup_paths_removed': ['/mnt/felhom-drive +14:02:55 [X] after remove: deployed=False leftovers='/opt/docker/stacks/komga' +14:03:58 [7] 60 s after remove: clean=True '' diff --git a/documentation/audits/the-28-2026-09-22/apps/komga/verdict.json b/documentation/audits/the-28-2026-09-22/apps/komga/verdict.json new file mode 100644 index 00000000..6b4b4d9b --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/apps/komga/verdict.json @@ -0,0 +1,131 @@ +{ + "harness_version": 2, + "app": "komga", + "venue": "guest 9202 demo-hp-scratch, controller 0.261.0, drill catalog", + "class": "file-leg", + "from": { + "komga": "gotson/komga:1.25.0" + }, + "to": { + "komga": "gotson/komga:1.27.1" + }, + "verdict": "inconclusive", + "deployed": true, + "seed_route": "its own POST /api/v1/claim, then GET /api/v1/users/me", + "seed_read_before": false, + "seed_read_after_update": false, + "backup": { + "snapshots_offered": 1, + "first": { + "time": "2026-09-22T12:00:43Z", + "short_id": "helyi", + "tier": 1, + "drive_label": "komga" + } + }, + "restore_verdict": "ok", + "seed_read_after_restore": false, + "healthy_after": true, + "migration_observed": "komga | 2026-09-22T14:01:18.173+02:00 INFO 1 --- [ main] o.f.core.internal.command.DbValidate : Successfully validated 92 migrations (execution time 00:00.231s)", + "removed_clean": true, + "edge": { + "from": "gotson/komga:1.25.0", + "to": "gotson/komga:1.27.1" + }, + "duration_s": 251.9, + "measured_at": "2026-09-22T11:59:50.024382+00:00", + "evidence": "the-28-2026-09-22/apps/komga/", + "notes": [], + "front_door_after_deploy": { + "rc": 0, + "code": "200" + }, + "drill_commit": "b534b8f95a8d", + "badge_seconds": 4.6, + "badges": { + "hu": [ + { + "title": "Újabb változat érhető el ehhez az alkalmazáshoz. A frissítés indításához nyomd meg a Frissítés gombot.", + "text": "Frissítés elérhető — ma" + } + ], + "en": [ + { + "title": "A newer version of this app is available. Select the Update button to start it.", + "text": "Update available — today" + } + ] + }, + "phases": { + "accepted": true, + "http": "202", + "phases": [ + { + "t": 0.1, + "phase": "safety-dump", + "label": "Adatbázis pillanatkép…", + "updating": true, + "error": null, + "hold": null + }, + { + "t": 1.1, + "phase": "pulling", + "label": "Új verzió letöltése…", + "updating": true, + "error": null, + "hold": null + }, + { + "t": 16.5, + "phase": "starting", + "label": "Indítás az új verzióval…", + "updating": true, + "error": null, + "hold": null + }, + { + "t": 18.6, + "phase": "verifying", + "label": "Működés ellenőrzése…", + "updating": true, + "error": null, + "hold": null + }, + { + "t": 44.3, + "phase": "done", + "label": "Frissítve", + "updating": false, + "error": null, + "hold": null + } + ], + "duration_s": 44.3, + "final_phase": "done", + "update_error": null, + "hold_reason": null, + "state": "running" + }, + "observables": { + "pinned_images": { + "komga": "gotson/komga:1.27.1" + }, + "installed_images": { + "komga": "gotson/komga:1.27.1" + }, + "catalog_images": { + "komga": "gotson/komga:1.27.1" + }, + "live_compose_image_lines": [ + "image: gotson/komga:1.27.1" + ], + "docker_inspect": [ + "komga gotson/komga:1.27.1 running=true restarts=0" + ] + }, + "update_phase_final": "done", + "restore_raw": "{'ok': True, 'snapshot_id': 'helyi', 'snapshots': [{'time': '2026-09-22T12:00:43Z', 'short_id': 'helyi', 'tier': 1, 'drive_label': 'komga'}], 'http': 'HTTP/2 302', 'location': ['location: /backups/restore?flash=flash.restore.started'], 'seconds': 32.5, 'state_after': 'running', 'hold_after': None, 'observables_after': {'pinned_images': {'komga': 'gotson/komga:1.25.0'}, 'installed_images': {'komga'", + "restore_refusal": null, + "remove_leftovers": "" +} \ No newline at end of file diff --git a/documentation/audits/the-28-2026-09-22/apps/onlyoffice/log.txt b/documentation/audits/the-28-2026-09-22/apps/onlyoffice/log.txt new file mode 100644 index 00000000..c120b9d8 --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/apps/onlyoffice/log.txt @@ -0,0 +1,19 @@ +14:11:08 ==== onlyoffice (sub=office, class=file-leg, edge=none) +14:11:08 [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'} +14:12:19 [1] deployed, controller state=running, pinned={'onlyoffice': 'onlyoffice/documentserver:9.4.0'} +14:12:19 [1] front door 302 controller state=running +14:12:19 onlyoffice: no non-browser seed route — a stateless document server: it holds no household data of its own, so there is nothing to seed +14:12:19 [2] fixture found no route — inconclusive for the data half +14:12:19 [4] „Mentés most" -> 200 {'ok': True, 'message': 'Mentés elindítva'} +14:12:34 [4] backup idle; last=None +14:12:34 [4] backups page offers 1 restorable copy(ies) +14:12:34 [R] restoring onlyoffice from snapshot 'helyi' (of 1 offered) +14:12:34 [R] POST /backup/restore -> HTTP/2 302 ['location: /backups/restore?flash=flash.restore.started'] +14:12:34 + 0.0s restore (True, None, None) +14:12:52 + 18.3s restore (False, None, None) +14:12:52 [R] after restore: state=running hold=None phase=None +14:13:11 [6] restore -> ok, seed back = False +14:13:33 [X] stop -> 200 {'ok': True, 'message': 'Stack onlyoffice stop completed'} +14:13:39 [X] remove (with drive data) -> 200 {'ok': True, 'data': {'removed': 'onlyoffice', 'volumes_removed': [], 'hdd_paths_removed': [], 'hdd_paths_preserved': [], 'hdd_note': 'Az alkalmazás nem tárolt +14:13:46 [X] after remove: deployed=False leftovers='/opt/docker/stacks/onlyoffice' +14:14:50 [7] 60 s after remove: clean=True '' diff --git a/documentation/audits/the-28-2026-09-22/apps/onlyoffice/verdict.json b/documentation/audits/the-28-2026-09-22/apps/onlyoffice/verdict.json new file mode 100644 index 00000000..4348d974 --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/apps/onlyoffice/verdict.json @@ -0,0 +1,43 @@ +{ + "harness_version": 2, + "app": "onlyoffice", + "venue": "guest 9202 demo-hp-scratch, controller 0.261.0, drill catalog", + "class": "file-leg", + "from": { + "onlyoffice": "onlyoffice/documentserver:9.4.0" + }, + "to": {}, + "verdict": "no-edge", + "deployed": true, + "seed_route": "none — a stateless document server: it holds no household data of its own, so there is nothing to seed", + "seed_read_before": false, + "seed_read_after_update": false, + "backup": { + "snapshots_offered": 1, + "first": { + "time": "2026-09-22T12:12:33Z", + "short_id": "helyi", + "tier": 1, + "drive_label": "Belső SSD (rendszer)" + } + }, + "restore_verdict": "ok", + "seed_read_after_restore": false, + "healthy_after": false, + "migration_observed": null, + "removed_clean": true, + "edge": null, + "duration_s": 227.3, + "measured_at": "2026-09-22T12:11:08.600884+00:00", + "evidence": "the-28-2026-09-22/apps/onlyoffice/", + "notes": [ + "fixture ran and found no non-browser seed route: a stateless document server: it holds no household data of its own, so there is nothing to seed" + ], + "front_door_after_deploy": { + "rc": 0, + "code": "302" + }, + "restore_raw": "{'ok': True, 'snapshot_id': 'helyi', 'snapshots': [{'time': '2026-09-22T12:12:33Z', 'short_id': 'helyi', 'tier': 1, 'drive_label': 'Belső SSD (rendszer)'}], 'http': 'HTTP/2 302', 'location': ['location: /backups/restore?flash=flash.restore.started'], 'seconds': 18.3, 'state_after': 'running', 'hold_after': None, 'observables_after': {'pinned_images': {'onlyoffice': 'onlyoffice/documentserver:9.4.0", + "restore_refusal": null, + "remove_leftovers": "" +} \ No newline at end of file diff --git a/documentation/audits/the-28-2026-09-22/apps/outline/app-logs-during-after.txt b/documentation/audits/the-28-2026-09-22/apps/outline/app-logs-during-after.txt new file mode 100644 index 00000000..e69de29b diff --git a/documentation/audits/the-28-2026-09-22/apps/outline/log.txt b/documentation/audits/the-28-2026-09-22/apps/outline/log.txt new file mode 100644 index 00000000..ca806921 --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/apps/outline/log.txt @@ -0,0 +1,28 @@ +14:34:19 ==== outline (sub=outline, class=db, edge={'from': 'outlinewiki/outline:1.9.1', 'to': 'outlinewiki/outline:1.10.1'}) +14:34:20 [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'} +14:41:52 [1] never became deployed (last controller state='restarting') +14:41:52 [1] front door 404 controller state=restarting +14:44:23 app never answered on outline/ (last rc=0 code=404) +14:44:23 outline: no non-browser seed route — sign-in requires an external identity provider (OIDC/Slack/Google); no local sign-up route exists +14:44:23 [2] fixture found no route — inconclusive for the data half +14:44:23 [4] „Mentés most" -> 200 {'ok': True, 'message': 'Mentés elindítva'} +14:44:33 [4] backup idle; last=None +14:44:33 [4] backups page offers 1 restorable copy(ies) +14:44:34 [5] drill commit 69730efa520b: outline outlinewiki/outline:1.9.1 -> outlinewiki/outline:1.10.1 (push rc=0) +14:44:38 [5] badge {'hu': [{'title': 'Újabb változat érhető el ehhez az alkalmazáshoz. A frissítés indításához nyomd meg a Frissítés gombot.', 'text': 'Frissítés elérhető — ma'}], 'en': [{'title': 'A newer version of this app is available. Select the Update button to start it.', 'text': 'Update available — today'}]} +14:44:39 [6] Update -> 202 {'ok': True, 'data': {'accepted': True, 'completed': False}, 'message': 'Frissítés elindult – az állapot a kártyán követhető'} +14:44:39 + 0.0s phase=safety-dump label=Adatbázis pillanatkép… err=None hold=None +14:44:40 + 1.1s phase=pulling label=Új verzió letöltése… err=None hold=None +14:45:44 + 64.8s phase=starting label=Indítás az új verzióval… err=None hold=None +14:45:45 + 65.8s phase=verifying label=Működés ellenőrzése… err=None hold=None +14:50:47 + 368.6s phase=failed label=A frissítés nem sikerült err=A(z) outline frissítése 2026-09-22 14:50-kor nem sikerült, és az alkalmazás nem indult el az új verzióval. Az alkalmazás biztonsági okból leállítva marad, hogy az adatai ne sérüljenek. Visszaállítható a Mentések oldalon ebből a biztonsági mentésből: saját meghajtó, 2026-09-22 14:44 — ez a másolat a beállításokat, az adatbázist és az adatköteteket tartalmazza. hold=A(z) outline frissítése 2026-09-22 14:50-kor nem sikerült, és az alkalmazás nem indult el az új verzióval. Az alkalmazás biztonsági okból leállítva marad, hogy az adatai ne sérüljenek. Visszaállítható a Mentések oldalon ebből a biztonsági mentésből: saját meghajtó, 2026-09-22 14:44 — ez a másolat a beállításokat, az adatbázist és az adatköteteket tartalmazza. +14:50:51 [R] restoring outline from snapshot 'helyi' (of 1 offered) +14:50:51 [R] POST /backup/restore -> HTTP/2 302 ['location: /backups/restore?flash=flash.restore.started'] +14:50:51 + 0.0s restore (True, None, None) +14:52:36 + 105.4s restore (False, None, None) +14:52:36 [R] after restore: state=starting hold=None phase=None +14:52:55 [6] restore -> failed, seed back = False +14:56:06 [X] stop -> 200 {'ok': True, 'message': 'Stack outline stop completed'} +14:56:11 [X] remove (with drive data) -> 200 {'ok': True, 'data': {'removed': 'outline', 'volumes_removed': [], 'hdd_paths_removed': [], 'hdd_paths_preserved': [], 'hdd_note': 'Az alkalmazás nem tárolt saj +14:56:20 [X] after remove: deployed=False leftovers='/opt/docker/stacks/outline' +14:57:22 [7] 60 s after remove: clean=True '' diff --git a/documentation/audits/the-28-2026-09-22/apps/outline/verdict.json b/documentation/audits/the-28-2026-09-22/apps/outline/verdict.json new file mode 100644 index 00000000..b92dca50 --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/apps/outline/verdict.json @@ -0,0 +1,144 @@ +{ + "harness_version": 2, + "app": "outline", + "venue": "guest 9202 demo-hp-scratch, controller 0.261.0, drill catalog", + "class": "db", + "from": { + "outline": "outlinewiki/outline:1.9.1", + "outline-postgres": "postgres:16-alpine", + "outline-redis": "redis:7-alpine" + }, + "to": { + "outline": "outlinewiki/outline:1.10.1", + "outline-postgres": "postgres:16-alpine", + "outline-redis": "redis:7-alpine" + }, + "verdict": "failed", + "deployed": true, + "seed_route": "none — sign-in requires an external identity provider (OIDC/Slack/Google); no local sign-up route exists", + "seed_read_before": false, + "seed_read_after_update": false, + "backup": { + "snapshots_offered": 1, + "first": { + "time": "2026-09-22T12:44:25Z", + "short_id": "helyi", + "tier": 1, + "drive_label": "Belső SSD (rendszer)" + } + }, + "restore_verdict": "failed", + "seed_read_after_restore": false, + "healthy_after": false, + "migration_observed": null, + "removed_clean": true, + "edge": { + "from": "outlinewiki/outline:1.9.1", + "to": "outlinewiki/outline:1.10.1" + }, + "duration_s": 1389.6, + "measured_at": "2026-09-22T12:34:19.669895+00:00", + "evidence": "the-28-2026-09-22/apps/outline/", + "notes": [ + "fixture ran and found no non-browser seed route: sign-in requires an external identity provider (OIDC/Slack/Google); no local sign-up route exists" + ], + "front_door_after_deploy": { + "rc": 0, + "code": "404" + }, + "drill_commit": "69730efa520b", + "badge_seconds": 4.6, + "badges": { + "hu": [ + { + "title": "Újabb változat érhető el ehhez az alkalmazáshoz. A frissítés indításához nyomd meg a Frissítés gombot.", + "text": "Frissítés elérhető — ma" + } + ], + "en": [ + { + "title": "A newer version of this app is available. Select the Update button to start it.", + "text": "Update available — today" + } + ] + }, + "phases": { + "accepted": true, + "http": "202", + "phases": [ + { + "t": 0.0, + "phase": "safety-dump", + "label": "Adatbázis pillanatkép…", + "updating": true, + "error": null, + "hold": null + }, + { + "t": 1.1, + "phase": "pulling", + "label": "Új verzió letöltése…", + "updating": true, + "error": null, + "hold": null + }, + { + "t": 64.8, + "phase": "starting", + "label": "Indítás az új verzióval…", + "updating": true, + "error": null, + "hold": null + }, + { + "t": 65.8, + "phase": "verifying", + "label": "Működés ellenőrzése…", + "updating": true, + "error": null, + "hold": null + }, + { + "t": 368.6, + "phase": "failed", + "label": "A frissítés nem sikerült", + "updating": false, + "error": "A(z) outline frissítése 2026-09-22 14:50-kor nem sikerült, és az alkalmazás nem indult el az új verzióval. Az alkalmazás biztonsági okból leállítva marad, hogy az adatai ne sérüljenek. Visszaállítható a Mentések oldalon ebből a biztonsági mentésből: saját meghajtó, 2026-09-22 14:44 — ez a másolat a beállításokat, az adatbázist és az adatköteteket tartalmazza.", + "hold": "A(z) outline frissítése 2026-09-22 14:50-kor nem sikerült, és az alkalmazás nem indult el az új verzióval. Az alkalmazás biztonsági okból leállítva marad, hogy az adatai ne sérüljenek. Visszaállítható a Mentések oldalon ebből a biztonsági mentésből: saját meghajtó, 2026-09-22 14:44 — ez a másolat a beállításokat, az adatbázist és az adatköteteket tartalmazza." + } + ], + "duration_s": 368.6, + "final_phase": "failed", + "update_error": "A(z) outline frissítése 2026-09-22 14:50-kor nem sikerült, és az alkalmazás nem indult el az új verzióval. Az alkalmazás biztonsági okból leállítva marad, hogy az adatai ne sérüljenek. Visszaállítható a Mentések oldalon ebből a biztonsági mentésből: saját meghajtó, 2026-09-22 14:44 — ez a másolat a beállításokat, az adatbázist és az adatköteteket tartalmazza.", + "hold_reason": "A(z) outline frissítése 2026-09-22 14:50-kor nem sikerült, és az alkalmazás nem indult el az új verzióval. Az alkalmazás biztonsági okból leállítva marad, hogy az adatai ne sérüljenek. Visszaállítható a Mentések oldalon ebből a biztonsági mentésből: saját meghajtó, 2026-09-22 14:44 — ez a másolat a beállításokat, az adatbázist és az adatköteteket tartalmazza.", + "state": "stopped" + }, + "observables": { + "pinned_images": { + "outline": "outlinewiki/outline:1.10.1", + "outline-postgres": "postgres:16-alpine", + "outline-redis": "redis:7-alpine" + }, + "installed_images": { + "outline": "outlinewiki/outline:1.9.1", + "outline-postgres": "postgres:16-alpine", + "outline-redis": "redis:7-alpine" + }, + "catalog_images": { + "outline": "outlinewiki/outline:1.10.1", + "outline-postgres": "postgres:16-alpine", + "outline-redis": "redis:7-alpine" + }, + "live_compose_image_lines": [ + "image: outlinewiki/outline:1.10.1", + "image: postgres:16-alpine", + "image: redis:7-alpine" + ], + "docker_inspect": [] + }, + "update_phase_final": "failed", + "restore_raw": "{'ok': True, 'snapshot_id': 'helyi', 'snapshots': [{'time': '2026-09-22T12:44:39Z', 'short_id': 'helyi', 'tier': 1, 'drive_label': 'Belső SSD (rendszer)'}], 'http': 'HTTP/2 302', 'location': ['location: /backups/restore?flash=flash.restore.started'], 'seconds': 105.4, 'state_after': 'starting', 'hold_after': None, 'observables_after': {'pinned_images': {'outline': 'outlinewiki/outline:1.9.1', 'out", + "restore_refusal": null, + "restore_state_seen": "restarting", + "remove_leftovers": "" +} \ No newline at end of file diff --git a/documentation/audits/the-28-2026-09-22/apps/paperless-ngx/log.txt b/documentation/audits/the-28-2026-09-22/apps/paperless-ngx/log.txt new file mode 100644 index 00000000..c25bace0 --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/apps/paperless-ngx/log.txt @@ -0,0 +1,21 @@ +15:38:13 ==== paperless-ngx (sub=paperless-ngx, class=db, edge=none) +15:38:15 [1] made the drive paths this app requires: ['/mnt/felhom-drives/scratch_hdd/userdata/paperless-ngx'] +15:38:15 [1] required fields filled beyond DOMAIN/SUBDOMAIN: ['PAPERLESS_ADMIN_PASSWORD', 'HDD_PATH'] +15:38:16 [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'} +15:39:41 [1] deployed, controller state=running, pinned={'paperless-postgres': 'postgres:16-alpine', 'paperless-redis': 'redis:7-alpine', 'paperless-webserver': 'ghcr.io/paperless-ngx/paperless-ngx:2.20.15'} +15:39:41 [1] front door 302 controller state=running +15:39:41 [2] NO FIXTURE — recorded inconclusive for the data half, not faked +15:39:41 [4] „Mentés most" -> 200 {'ok': True, 'message': 'Mentés elindítva'} +15:40:06 [4] backup idle; last=None +15:40:06 [4] backups page offers 1 restorable copy(ies) +15:40:06 [R] restoring paperless-ngx from snapshot 'helyi' (of 1 offered) +15:40:06 [R] POST /backup/restore -> HTTP/2 302 ['location: /backups/restore?flash_error=Ez+a+ment%C3%A9s+nem+tartalmazza+az+alkalmaz%C3%A1s+f%C3%A1jljait%2C+ez%C3%A9rt+nem+%C3%A1ll%C3%ADtjuk+vissza+az+adatb%C3%A1zist+f%C3%B6l%C3%A9j%C3%BCk+%E2%80%94+a+f%C3%A1jlok+%C3%ADgy+a+hely%C3%BCk%C3%B6n+maradnak.+A+f%C3%A1jlok+a+t%C3%A1voli+m%C3%A1solatb%C3%B3l+%C3%A1ll%C3%ADthat%C3%B3k+vissza%3A+Biztons%C3%A1gi+ment%C3%A9s+%E2%86%92+Vissza%C3%A1ll%C3%ADt%C3%A1s%2C+%E2%80%9ETeljes+vissza%C3%A1ll%C3%ADt%C3%A1s+%28f%C3%A1jlok+%2B+adatb%C3%A1zis%29%E2%80%9D.'] +15:40:07 + 0.0s restore (False, None, None) +15:40:13 [R] after restore: state=starting hold=None phase=None +15:40:31 [6] restore -> refused-with-a-sentence, seed back = False +15:41:05 [X] stop -> 200 {'ok': True, 'message': 'Stack paperless-ngx stop completed'} +15:41:10 [X] remove (with drive data) -> 409 {'ok': False, 'error': 'A(z) /mnt/felhom-drives/scratch_hdd/userdata/paperless-ngx tárhely jelenleg nem elérhető — az alkalmazás nem távolítható el, amíg a megh +15:41:10 [X] refused because the drive path cannot be resolved (R-442, fail-closed and right) — removing the app and KEEPING the drive data instead +15:41:10 [X] remove (keeping drive data) -> 200 {'ok': True, 'data': {'removed': 'paperless-ngx', 'volumes_removed': ['paperless-ngx_paperless_data', 'paperless-ngx_paperless_postgres_data', 'paperless-ngx_pa +15:41:19 [X] after remove: deployed=False leftovers='/opt/docker/stacks/paperless-ngx' +15:42:22 [7] 60 s after remove: clean=True '' diff --git a/documentation/audits/the-28-2026-09-22/apps/paperless-ngx/verdict.json b/documentation/audits/the-28-2026-09-22/apps/paperless-ngx/verdict.json new file mode 100644 index 00000000..b9b15bf2 --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/apps/paperless-ngx/verdict.json @@ -0,0 +1,45 @@ +{ + "harness_version": 2, + "app": "paperless-ngx", + "venue": "guest 9202 demo-hp-scratch, controller 0.261.0, drill catalog", + "class": "db", + "from": { + "paperless-postgres": "postgres:16-alpine", + "paperless-redis": "redis:7-alpine", + "paperless-webserver": "ghcr.io/paperless-ngx/paperless-ngx:2.20.15" + }, + "to": {}, + "verdict": "no-edge", + "deployed": true, + "seed_route": "none written", + "seed_read_before": false, + "seed_read_after_update": false, + "backup": { + "snapshots_offered": 1, + "first": { + "time": "2026-09-22T13:40:02Z", + "short_id": "helyi", + "tier": 1, + "drive_label": "paperless-ngx" + } + }, + "restore_verdict": "refused-with-a-sentence", + "seed_read_after_restore": false, + "healthy_after": false, + "migration_observed": null, + "removed_clean": true, + "edge": null, + "duration_s": 253.2, + "measured_at": "2026-09-22T13:38:13.510771+00:00", + "evidence": "the-28-2026-09-22/apps/paperless-ngx/", + "notes": [ + "no fixture: no non-browser seed route was written for this app" + ], + "front_door_after_deploy": { + "rc": 0, + "code": "302" + }, + "restore_raw": "{'ok': True, 'snapshot_id': 'helyi', 'snapshots': [{'time': '2026-09-22T13:40:02Z', 'short_id': 'helyi', 'tier': 1, 'drive_label': 'paperless-ngx'}], 'http': 'HTTP/2 302', 'location': ['location: /backups/restore?flash_error=Ez+a+ment%C3%A9s+nem+tartalmazza+az+alkalmaz%C3%A1s+f%C3%A1jljait%2C+ez%C3%A9rt+nem+%C3%A1ll%C3%ADtjuk+vissza+az+adatb%C3%A1zist+f%C3%B6l%C3%A9j%C3%BCk+%E2%80%94+a+f%C3%A1jlok", + "restore_refusal": "Ez a mentés nem tartalmazza az alkalmazás fájljait, ezért nem állítjuk vissza az adatbázist föléjük — a fájlok így a helyükön maradnak. A fájlok a távoli másolatból állíthatók vissza: Biztonsági mentés → Visszaállítás, „Teljes visszaállítás (fájlok + adatbázis)”.", + "remove_leftovers": "" +} \ No newline at end of file diff --git a/documentation/audits/the-28-2026-09-22/apps/plant-it/log.txt b/documentation/audits/the-28-2026-09-22/apps/plant-it/log.txt new file mode 100644 index 00000000..0854a89e --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/apps/plant-it/log.txt @@ -0,0 +1,8 @@ +13:31:29 ==== plant-it (sub=plant-it, class=file-leg, edge=none) +13:31:30 [1] deploy -> 409 {'ok': False, 'error': 'Ez az alkalmazás jelenleg nem telepíthető.'} +13:31:30 [X] stop -> 200 {'ok': True, 'message': 'Stack plant-it stop completed'} +13:31:35 [X] remove (with drive data) -> 409 {'ok': False, 'error': 'stack "plant-it" is not deployed'} +13:31:35 [X] refused because the drive path cannot be resolved (R-442, fail-closed and right) — removing the app and KEEPING the drive data instead +13:31:35 [X] remove (keeping drive data) -> 409 {'ok': False, 'error': 'stack "plant-it" is not deployed'} +13:31:42 [X] after remove: deployed=False leftovers='/opt/docker/stacks/plant-it' +13:32:45 [7] 60 s after remove: clean=True '' diff --git a/documentation/audits/the-28-2026-09-22/apps/plant-it/verdict.json b/documentation/audits/the-28-2026-09-22/apps/plant-it/verdict.json new file mode 100644 index 00000000..3b66f429 --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/apps/plant-it/verdict.json @@ -0,0 +1,27 @@ +{ + "harness_version": 2, + "app": "plant-it", + "venue": "guest 9202 demo-hp-scratch, controller 0.261.0, drill catalog", + "class": "file-leg", + "from": {}, + "to": {}, + "verdict": "could-not-deploy", + "deployed": false, + "seed_route": null, + "seed_read_before": false, + "seed_read_after_update": false, + "backup": null, + "restore_verdict": "not-attempted", + "seed_read_after_restore": false, + "healthy_after": false, + "migration_observed": null, + "removed_clean": true, + "edge": null, + "duration_s": 78.2, + "measured_at": "2026-09-22T11:31:29.806255+00:00", + "evidence": "the-28-2026-09-22/apps/plant-it/", + "notes": [ + "deploy never reached `deployed` with a pin" + ], + "remove_leftovers": "" +} \ No newline at end of file diff --git a/documentation/audits/the-28-2026-09-22/apps/plex/app-logs-during-after.txt b/documentation/audits/the-28-2026-09-22/apps/plex/app-logs-during-after.txt new file mode 100644 index 00000000..3431de8c --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/apps/plex/app-logs-during-after.txt @@ -0,0 +1,20 @@ +plex | [s6-init] making user provided files available at /var/run/s6/etc...exited 0. +plex | [s6-init] ensuring user provided files have correct perms...exited 0. +plex | [fix-attrs.d] applying ownership & permissions fixes... +plex | [fix-attrs.d] done. +plex | [cont-init.d] executing container initialization scripts... +plex | [cont-init.d] 40-plex-first-run: executing... +plex | Attempting to obtain server token from claim token +plex | % Total % Received % Xferd Average Speed Time Time Time Current +plex | Dload Upload Total Spent Left Speed +plex | 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0 +plex | Plex Media Server first run setup complete +plex | [cont-init.d] 40-plex-first-run: exited 0. +plex | [cont-init.d] 45-plex-hw-transcode-and-connected-tuner: executing... +plex | [cont-init.d] 45-plex-hw-transcode-and-connected-tuner: exited 0. +plex | [cont-init.d] 50-plex-update: executing... +plex | [cont-init.d] 50-plex-update: exited 0. +plex | [cont-init.d] done. +plex | [services.d] starting services +plex | Starting Plex Media Server. +plex | [services.d] done. diff --git a/documentation/audits/the-28-2026-09-22/apps/plex/log.txt b/documentation/audits/the-28-2026-09-22/apps/plex/log.txt new file mode 100644 index 00000000..4bb9cba2 --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/apps/plex/log.txt @@ -0,0 +1,31 @@ +14:07:53 ==== plex (sub=plex, class=file-leg, edge={'from': 'plexinc/pms-docker:1.41.4.9463-630c9f557', 'to': 'plexinc/pms-docker:1.43.4.10903-e5521bd8c'}) +14:07:55 [1] made the drive paths this app requires: ['/mnt/felhom-drives/scratch_hdd/userdata/plex'] +14:07:55 [1] required fields filled beyond DOMAIN/SUBDOMAIN: ['PLEX_CLAIM', 'HDD_PATH'] +14:07:56 [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'} +14:08:51 [1] deployed, controller state=running, pinned={'plex': 'plexinc/pms-docker:1.41.4.9463-630c9f557'} +14:08:51 [1] front door 401 controller state=running +14:08:51 plex: no non-browser seed route — the first-run claim needs a token minted at plex.tv by a real Plex account; no account exists for this venue +14:08:51 [2] fixture found no route — inconclusive for the data half +14:08:51 [4] „Mentés most" -> 200 {'ok': True, 'message': 'Mentés elindítva'} +14:09:16 [4] backup idle; last=None +14:09:16 [4] backups page offers 1 restorable copy(ies) +14:09:17 [5] drill commit 03f1dc29e301: plex plexinc/pms-docker:1.41.4.9463-630c9f557 -> plexinc/pms-docker:1.43.4.10903-e5521bd8c (push rc=0) +14:09:21 [5] badge {'hu': [{'title': 'Újabb változat érhető el ehhez az alkalmazáshoz. A frissítés indításához nyomd meg a Frissítés gombot.', 'text': 'Frissítés elérhető — ma'}], 'en': [{'title': 'A newer version of this app is available. Select the Update button to start it.', 'text': 'Update available — today'}]} +14:09:22 [6] Update -> 202 {'ok': True, 'data': {'accepted': True, 'completed': False}, 'message': 'Frissítés elindult – az állapot a kártyán követhető'} +14:09:22 + 0.0s phase=safety-dump label=Adatbázis pillanatkép… err=None hold=None +14:09:23 + 1.1s phase=pulling label=Új verzió letöltése… err=None hold=None +14:09:33 + 11.4s phase=starting label=Indítás az új verzióval… err=None hold=None +14:09:43 + 21.7s phase=verifying label=Működés ellenőrzése… err=None hold=None +14:09:54 + 32.0s phase=done label=Frissítve err=None hold=None +14:09:57 [R] restoring plex from snapshot 'helyi' (of 1 offered) +14:09:57 [R] POST /backup/restore -> HTTP/2 302 ['location: /backups/restore?flash=flash.restore.started'] +14:09:57 + 0.0s restore (True, None, None) +14:10:18 + 20.3s restore (False, None, None) +14:10:18 [R] after restore: state=running hold=None phase=done +14:10:36 [6] restore -> ok, seed back = False +14:10:55 [X] stop -> 200 {'ok': True, 'message': 'Stack plex stop completed'} +14:11:00 [X] remove (with drive data) -> 409 {'ok': False, 'error': 'A(z) /mnt/felhom-drives/scratch_hdd/userdata/plex tárhely jelenleg nem elérhető — az alkalmazás nem távolítható el, amíg a meghajtó viss +14:11:00 [X] refused because the drive path cannot be resolved (R-442, fail-closed and right) — removing the app and KEEPING the drive data instead +14:11:00 [X] remove (keeping drive data) -> 200 {'ok': True, 'data': {'removed': 'plex', 'volumes_removed': [], 'hdd_paths_removed': [], 'hdd_paths_preserved': [], 'backup_paths_removed': ['/mnt/felhom-drives +14:11:08 [X] after remove: deployed=False leftovers='/opt/docker/stacks/plex' +14:12:11 [7] 60 s after remove: clean=True '' diff --git a/documentation/audits/the-28-2026-09-22/apps/plex/verdict.json b/documentation/audits/the-28-2026-09-22/apps/plex/verdict.json new file mode 100644 index 00000000..ee87cca0 --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/apps/plex/verdict.json @@ -0,0 +1,133 @@ +{ + "harness_version": 2, + "app": "plex", + "venue": "guest 9202 demo-hp-scratch, controller 0.261.0, drill catalog", + "class": "file-leg", + "from": { + "plex": "plexinc/pms-docker:1.41.4.9463-630c9f557" + }, + "to": { + "plex": "plexinc/pms-docker:1.43.4.10903-e5521bd8c" + }, + "verdict": "inconclusive", + "deployed": true, + "seed_route": "none — the first-run claim needs a token minted at plex.tv by a real Plex account; no account exists for this venue", + "seed_read_before": false, + "seed_read_after_update": false, + "backup": { + "snapshots_offered": 1, + "first": { + "time": "2026-09-22T12:09:01Z", + "short_id": "helyi", + "tier": 1, + "drive_label": "plex" + } + }, + "restore_verdict": "ok", + "seed_read_after_restore": false, + "healthy_after": true, + "migration_observed": null, + "removed_clean": true, + "edge": { + "from": "plexinc/pms-docker:1.41.4.9463-630c9f557", + "to": "plexinc/pms-docker:1.43.4.10903-e5521bd8c" + }, + "duration_s": 262.1, + "measured_at": "2026-09-22T12:07:53.160418+00:00", + "evidence": "the-28-2026-09-22/apps/plex/", + "notes": [ + "fixture ran and found no non-browser seed route: the first-run claim needs a token minted at plex.tv by a real Plex account; no account exists for this venue" + ], + "front_door_after_deploy": { + "rc": 0, + "code": "401" + }, + "drill_commit": "03f1dc29e301", + "badge_seconds": 4.5, + "badges": { + "hu": [ + { + "title": "Újabb változat érhető el ehhez az alkalmazáshoz. A frissítés indításához nyomd meg a Frissítés gombot.", + "text": "Frissítés elérhető — ma" + } + ], + "en": [ + { + "title": "A newer version of this app is available. Select the Update button to start it.", + "text": "Update available — today" + } + ] + }, + "phases": { + "accepted": true, + "http": "202", + "phases": [ + { + "t": 0.0, + "phase": "safety-dump", + "label": "Adatbázis pillanatkép…", + "updating": true, + "error": null, + "hold": null + }, + { + "t": 1.1, + "phase": "pulling", + "label": "Új verzió letöltése…", + "updating": true, + "error": null, + "hold": null + }, + { + "t": 11.4, + "phase": "starting", + "label": "Indítás az új verzióval…", + "updating": true, + "error": null, + "hold": null + }, + { + "t": 21.7, + "phase": "verifying", + "label": "Működés ellenőrzése…", + "updating": true, + "error": null, + "hold": null + }, + { + "t": 32.0, + "phase": "done", + "label": "Frissítve", + "updating": false, + "error": null, + "hold": null + } + ], + "duration_s": 32.0, + "final_phase": "done", + "update_error": null, + "hold_reason": null, + "state": "running" + }, + "observables": { + "pinned_images": { + "plex": "plexinc/pms-docker:1.43.4.10903-e5521bd8c" + }, + "installed_images": { + "plex": "plexinc/pms-docker:1.43.4.10903-e5521bd8c" + }, + "catalog_images": { + "plex": "plexinc/pms-docker:1.43.4.10903-e5521bd8c" + }, + "live_compose_image_lines": [ + "image: plexinc/pms-docker:1.43.4.10903-e5521bd8c" + ], + "docker_inspect": [ + "plex plexinc/pms-docker:1.43.4.10903-e5521bd8c running=true restarts=0" + ] + }, + "update_phase_final": "done", + "restore_raw": "{'ok': True, 'snapshot_id': 'helyi', 'snapshots': [{'time': '2026-09-22T12:09:01Z', 'short_id': 'helyi', 'tier': 1, 'drive_label': 'plex'}], 'http': 'HTTP/2 302', 'location': ['location: /backups/restore?flash=flash.restore.started'], 'seconds': 20.3, 'state_after': 'running', 'hold_after': None, 'observables_after': {'pinned_images': {'plex': 'plexinc/pms-docker:1.41.4.9463-630c9f557'}, 'installe", + "restore_refusal": null, + "remove_leftovers": "" +} \ No newline at end of file diff --git a/documentation/audits/the-28-2026-09-22/apps/radarr/app-logs-during-after.txt b/documentation/audits/the-28-2026-09-22/apps/radarr/app-logs-during-after.txt new file mode 100644 index 00000000..57bac5a2 --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/apps/radarr/app-logs-during-after.txt @@ -0,0 +1,61 @@ +radarr | [migrations] started +radarr | [migrations] no migrations found +radarr | ─────────────────────────────────────── +radarr | +radarr | ██╗ ███████╗██╗ ██████╗ +radarr | ██║ ██╔════╝██║██╔═══██╗ +radarr | ██║ ███████╗██║██║ ██║ +radarr | ██║ ╚════██║██║██║ ██║ +radarr | ███████╗███████║██║╚██████╔╝ +radarr | ╚══════╝╚══════╝╚═╝ ╚═════╝ +radarr | +radarr | Brought to you by linuxserver.io +radarr | ─────────────────────────────────────── +radarr | +radarr | To support the app dev(s) visit: +radarr | Radarr: https://opencollective.com/radarr +radarr | +radarr | To support LSIO projects visit: +radarr | https://www.linuxserver.io/donate/ +radarr | +radarr | ─────────────────────────────────────── +radarr | GID/UID +radarr | ─────────────────────────────────────── +radarr | +radarr | User UID: 1000 +radarr | User GID: 1000 +radarr | ─────────────────────────────────────── +radarr | Linuxserver.io version: 6.4.4.10685-ls318 +radarr | Build-date: 2026-09-20T19:09:21+00:00 +radarr | ─────────────────────────────────────── +radarr | +radarr | [custom-init] No custom files found, skipping... +radarr | [Info] Bootstrap: Starting Radarr - /app/radarr/bin/Radarr - Version 6.4.4.10685 +radarr | [Info] AppFolderInfo: Data directory is being overridden to [/config] +radarr | [Debug] Bootstrap: Starting in Interactive mode +radarr | [Info] AppFolderInfo: Data directory is being overridden to [/config] +radarr | [Info] AppFolderInfo: Data directory is being overridden to [/config] +radarr | [Info] MigrationController: *** Migrating data source=/config/radarr.db;cache size=-20000;datetimekind=Utc;journal mode=Wal;pooling=True;version=3;busytimeout=1000 *** +radarr | [Info] FluentMigrator.Runner.MigrationRunner: DatabaseEngineVersionCheck migrating +radarr | [Info] FluentMigrator.Runner.MigrationRunner: PerformDBOperation +radarr | [Info] NzbDrone.Core.Datastore.Migration.Framework.NzbDroneSQLiteProcessor: Performing DB Operation +radarr | [Info] DatabaseEngineVersionCheck: SQLite 3.53.4 +radarr | [Info] FluentMigrator.Runner.MigrationRunner: => 0.0611531s +radarr | [Info] FluentMigrator.Runner.MigrationRunner: DatabaseEngineVersionCheck migrated +radarr | [Info] FluentMigrator.Runner.MigrationRunner: => 0.0667658s +radarr | [Info] MigrationController: *** Migrating data source=/config/logs.db;cache size=-20000;datetimekind=Utc;journal mode=Wal;pooling=True;version=3;busytimeout=1000 *** +radarr | [Info] FluentMigrator.Runner.MigrationRunner: DatabaseEngineVersionCheck migrating +radarr | [Info] FluentMigrator.Runner.MigrationRunner: PerformDBOperation +radarr | [Info] NzbDrone.Core.Datastore.Migration.Framework.NzbDroneSQLiteProcessor: Performing DB Operation +radarr | [Info] DatabaseEngineVersionCheck: SQLite 3.53.4 +radarr | [Info] FluentMigrator.Runner.MigrationRunner: => 0.0063954s +radarr | [Info] FluentMigrator.Runner.MigrationRunner: DatabaseEngineVersionCheck migrated +radarr | [Info] FluentMigrator.Runner.MigrationRunner: => 0.006678s +radarr | [Info] ConfigureHostFilteringOptions: Allowed Hosts is not configured, accepting requests for any host +radarr | [Info] Microsoft.Hosting.Lifetime: Now listening on: http://[::]:7878 +radarr | [Info] CommandExecutor: Starting 2 threads for tasks. +radarr | [ls.io-init] done. +radarr | [Info] Microsoft.Hosting.Lifetime: Application started. Press Ctrl+C to shut down. +radarr | [Info] Microsoft.Hosting.Lifetime: Hosting environment: Production +radarr | [Info] Microsoft.Hosting.Lifetime: Content root path: /app/radarr/bin +radarr | [Info] ManagedHttpDispatcher: IPv4 is available: True, IPv6 will be disabled diff --git a/documentation/audits/the-28-2026-09-22/apps/radarr/log.txt b/documentation/audits/the-28-2026-09-22/apps/radarr/log.txt new file mode 100644 index 00000000..80c8ab9d --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/apps/radarr/log.txt @@ -0,0 +1,35 @@ +13:56:00 ==== radarr (sub=radarr, class=file-leg, edge={'from': 'lscr.io/linuxserver/radarr:6.3.0', 'to': 'lscr.io/linuxserver/radarr:6.4.4'}) +13:56:03 [1] made the drive paths this app requires: ['/mnt/felhom-drives/scratch_hdd/userdata/radarr'] +13:56:03 [1] required fields filled beyond DOMAIN/SUBDOMAIN: ['HDD_PATH'] +13:56:03 [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'} +13:56:28 [1] deployed, controller state=running, pinned={'radarr': 'lscr.io/linuxserver/radarr:6.3.0'} +13:56:28 [1] front door 200 controller state=running +13:56:32 radarr: seeded tag drillca2ef99d +13:56:35 radarr: readback found=True (http 200, control passed) +13:56:35 [3] C1 readback before = True +13:56:35 [4] „Mentés most" -> 200 {'ok': True, 'message': 'Mentés elindítva'} +13:57:00 [4] backup idle; last=None +13:57:00 [4] backups page offers 1 restorable copy(ies) +13:57:01 [5] drill commit 03367b998ee4: radarr lscr.io/linuxserver/radarr:6.3.0 -> lscr.io/linuxserver/radarr:6.4.4 (push rc=0) +13:57:12 [sync] the badge needed 10.6s and 2 sync+rescan rounds to catch up to lscr.io/linuxserver/radarr:6.4.4 — R-607's window, measured +13:57:12 [5] badge {'hu': [{'title': 'Újabb változat érhető el ehhez az alkalmazáshoz. A frissítés indításához nyomd meg a Frissítés gombot.', 'text': 'Frissítés elérhető — ma'}], 'en': [{'title': 'A newer version of this app is available. Select the Update button to start it.', 'text': 'Update available — today'}]} +13:57:12 [6] Update -> 202 {'ok': True, 'data': {'accepted': True, 'completed': False}, 'message': 'Frissítés elindult – az állapot a kártyán követhető'} +13:57:12 + 0.0s phase=safety-dump label=Adatbázis pillanatkép… err=None hold=None +13:57:13 + 1.1s phase=pulling label=Új verzió letöltése… err=None hold=None +13:57:19 + 7.2s phase=starting label=Indítás az új verzióval… err=None hold=None +13:57:24 + 11.3s phase=verifying label=Működés ellenőrzése… err=None hold=None +13:57:34 + 21.6s phase=done label=Frissítve err=None hold=None +13:57:39 radarr: readback found=True (http 200, control passed) +13:57:39 [R] restoring radarr from snapshot 'helyi' (of 1 offered) +13:57:39 [R] POST /backup/restore -> HTTP/2 302 ['location: /backups/restore?flash=flash.restore.started'] +13:57:39 + 0.0s restore (True, None, None) +13:57:53 + 14.2s restore (False, None, None) +13:57:53 [R] after restore: state=running hold=None phase=done +13:58:15 radarr: readback found=True (http 200, control passed) +13:58:15 [6] restore -> ok, seed back = True +13:58:28 [X] stop -> 200 {'ok': True, 'message': 'Stack radarr stop completed'} +13:58:33 [X] remove (with drive data) -> 409 {'ok': False, 'error': 'A(z) /mnt/felhom-drives/scratch_hdd/userdata/radarr tárhely jelenleg nem elérhető — az alkalmazás nem távolítható el, amíg a meghajtó vi +13:58:33 [X] refused because the drive path cannot be resolved (R-442, fail-closed and right) — removing the app and KEEPING the drive data instead +13:58:34 [X] remove (keeping drive data) -> 200 {'ok': True, 'data': {'removed': 'radarr', 'volumes_removed': [], 'hdd_paths_removed': [], 'hdd_paths_preserved': [], 'backup_paths_removed': ['/mnt/felhom-driv +13:58:42 [X] after remove: deployed=False leftovers='/opt/docker/stacks/radarr' +13:59:45 [7] 60 s after remove: clean=True '' diff --git a/documentation/audits/the-28-2026-09-22/apps/radarr/verdict.json b/documentation/audits/the-28-2026-09-22/apps/radarr/verdict.json new file mode 100644 index 00000000..26dab1ba --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/apps/radarr/verdict.json @@ -0,0 +1,131 @@ +{ + "harness_version": 2, + "app": "radarr", + "venue": "guest 9202 demo-hp-scratch, controller 0.261.0, drill catalog", + "class": "file-leg", + "from": { + "radarr": "lscr.io/linuxserver/radarr:6.3.0" + }, + "to": { + "radarr": "lscr.io/linuxserver/radarr:6.4.4" + }, + "verdict": "proven", + "deployed": true, + "seed_route": "its own /api/v3/tag with the app's own ApiKey", + "seed_read_before": true, + "seed_read_after_update": true, + "backup": { + "snapshots_offered": 1, + "first": { + "time": "2026-09-22T11:56:56Z", + "short_id": "helyi", + "tier": 1, + "drive_label": "radarr" + } + }, + "restore_verdict": "ok", + "seed_read_after_restore": true, + "healthy_after": true, + "migration_observed": "radarr | [migrations] started", + "removed_clean": true, + "edge": { + "from": "lscr.io/linuxserver/radarr:6.3.0", + "to": "lscr.io/linuxserver/radarr:6.4.4" + }, + "duration_s": 227.8, + "measured_at": "2026-09-22T11:56:00.319285+00:00", + "evidence": "the-28-2026-09-22/apps/radarr/", + "notes": [], + "front_door_after_deploy": { + "rc": 0, + "code": "200" + }, + "drill_commit": "03367b998ee4", + "badge_seconds": 10.6, + "badges": { + "hu": [ + { + "title": "Újabb változat érhető el ehhez az alkalmazáshoz. A frissítés indításához nyomd meg a Frissítés gombot.", + "text": "Frissítés elérhető — ma" + } + ], + "en": [ + { + "title": "A newer version of this app is available. Select the Update button to start it.", + "text": "Update available — today" + } + ] + }, + "phases": { + "accepted": true, + "http": "202", + "phases": [ + { + "t": 0.0, + "phase": "safety-dump", + "label": "Adatbázis pillanatkép…", + "updating": true, + "error": null, + "hold": null + }, + { + "t": 1.1, + "phase": "pulling", + "label": "Új verzió letöltése…", + "updating": true, + "error": null, + "hold": null + }, + { + "t": 7.2, + "phase": "starting", + "label": "Indítás az új verzióval…", + "updating": true, + "error": null, + "hold": null + }, + { + "t": 11.3, + "phase": "verifying", + "label": "Működés ellenőrzése…", + "updating": true, + "error": null, + "hold": null + }, + { + "t": 21.6, + "phase": "done", + "label": "Frissítve", + "updating": false, + "error": null, + "hold": null + } + ], + "duration_s": 21.6, + "final_phase": "done", + "update_error": null, + "hold_reason": null, + "state": "running" + }, + "observables": { + "pinned_images": { + "radarr": "lscr.io/linuxserver/radarr:6.4.4" + }, + "installed_images": { + "radarr": "lscr.io/linuxserver/radarr:6.4.4" + }, + "catalog_images": { + "radarr": "lscr.io/linuxserver/radarr:6.4.4" + }, + "live_compose_image_lines": [ + "image: lscr.io/linuxserver/radarr:6.4.4" + ], + "docker_inspect": [ + "radarr lscr.io/linuxserver/radarr:6.4.4 running=true restarts=0" + ] + }, + "update_phase_final": "done", + "restore_raw": "{'ok': True, 'snapshot_id': 'helyi', 'snapshots': [{'time': '2026-09-22T11:57:37Z', 'short_id': 'helyi', 'tier': 1, 'drive_label': 'radarr'}], 'http': 'HTTP/2 302', 'location': ['location: /backups/restore?flash=flash.restore.started'], 'seconds': 14.2, 'state_after': 'running', 'hold_after': None, 'observables_after': {'pinned_images': {'radarr': 'lscr.io/linuxserver/radarr:6.4.4'}, 'installed_im", + "restore_refusal": null, + "remove_leftovers": "" +} \ No newline at end of file diff --git a/documentation/audits/the-28-2026-09-22/apps/rallly/app-logs-during-after.txt b/documentation/audits/the-28-2026-09-22/apps/rallly/app-logs-during-after.txt new file mode 100644 index 00000000..167e82c4 --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/apps/rallly/app-logs-during-after.txt @@ -0,0 +1,29 @@ +rallly | No .env file found, continuing without it +rallly | Loaded Prisma config from prisma.config.ts. +rallly | +rallly | Prisma schema loaded from prisma. +rallly | Datasource "db": PostgreSQL database "rallly", schema "public" at "rallly-postgres:5432" +rallly | +rallly | 152 migrations found in prisma/migrations +rallly | +rallly | +rallly | No pending migrations to apply. +rallly | npm notice +rallly-postgres | +rallly-postgres | PostgreSQL Database directory appears to contain a database; Skipping initialization +rallly | npm notice New major version of npm available! 11.19.0 -> 12.0.2 +rallly | npm notice Changelog: https://github.com/npm/cli/releases/tag/v12.0.2 +rallly | npm notice To update run: npm install -g npm@12.0.2 +rallly | npm notice +rallly | ▲ Next.js 16.3.3 +rallly-postgres | +rallly-postgres | 2026-09-22 15:43:30.475 CEST [1] LOG: starting PostgreSQL 16.15 on x86_64-pc-linux-musl, compiled by gcc (Alpine 15.2.0) 15.2.0, 64-bit +rallly-postgres | 2026-09-22 15:43:30.475 CEST [1] LOG: listening on IPv4 address "0.0.0.0", port 5432 +rallly-postgres | 2026-09-22 15:43:30.475 CEST [1] LOG: listening on IPv6 address "::", port 5432 +rallly | - Local: http://localhost:3000 +rallly-postgres | 2026-09-22 15:43:30.491 CEST [1] LOG: listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432" +rallly | - Network: http://0.0.0.0:3000 +rallly-postgres | 2026-09-22 15:43:30.513 CEST [28] LOG: database system was shut down at 2026-09-22 15:43:28 CEST +rallly | ✓ Ready in 0ms +rallly-postgres | 2026-09-22 15:43:30.581 CEST [1] LOG: database system is ready to accept connections +rallly | ✓ Running next.config took 3ms diff --git a/documentation/audits/the-28-2026-09-22/apps/rallly/log.txt b/documentation/audits/the-28-2026-09-22/apps/rallly/log.txt new file mode 100644 index 00000000..075f9287 --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/apps/rallly/log.txt @@ -0,0 +1,26 @@ +15:42:26 ==== rallly (sub=rallly, class=db, edge={'from': 'lukevella/rallly:4.11.1', 'to': 'lukevella/rallly:4.15.2'}) +15:42:27 [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'} +15:43:17 [1] deployed, controller state=running, pinned={'rallly': 'lukevella/rallly:4.15.2', 'rallly-postgres': 'postgres:16-alpine'} +15:43:17 [1] front door 200 controller state=running +15:43:17 rallly: no non-browser seed route — sign-in is an e-mail magic link; this venue has no mailbox the harness can read +15:43:17 [2] fixture found no route — inconclusive for the data half +15:43:17 [4] „Mentés most" -> 200 {'ok': True, 'message': 'Mentés elindítva'} +15:43:37 [4] backup idle; last=None +15:43:37 [4] backups page offers 1 restorable copy(ies) +15:43:37 [5] FROM ref not found in compose: lukevella/rallly:4.11.1 +15:43:42 [5] badge {'hu': [{'title': 'Ez az alkalmazás a legfrissebb elérhető változatot futtatja.', 'text': 'Naprakész'}], 'en': [{'title': 'This app is running the newest version available.', 'text': 'Up to date'}]} +15:43:42 [6] Update -> 202 {'ok': True, 'data': {'accepted': True, 'completed': False}, 'message': 'Frissítés elindult – az állapot a kártyán követhető'} +15:43:42 + 0.0s phase=safety-dump label=Adatbázis pillanatkép… err=None hold=None +15:43:43 + 1.1s phase=pulling label=Új verzió letöltése… err=None hold=None +15:43:44 + 2.1s phase=starting label=Indítás az új verzióval… err=None hold=None +15:43:45 + 3.1s phase=done label=Frissítve err=None hold=None +15:43:49 [R] restoring rallly from snapshot 'helyi' (of 1 offered) +15:43:49 [R] POST /backup/restore -> HTTP/2 302 ['location: /backups/restore?flash=flash.restore.started'] +15:43:49 + 0.0s restore (True, None, None) +15:44:17 + 28.4s restore (False, None, None) +15:44:17 [R] after restore: state=running hold=None phase=done +15:44:36 [6] restore -> ok, seed back = False +15:44:57 [X] stop -> 200 {'ok': True, 'message': 'Stack rallly stop completed'} +15:45:02 [X] remove (with drive data) -> 200 {'ok': True, 'data': {'removed': 'rallly', 'volumes_removed': [], 'hdd_paths_removed': [], 'hdd_paths_preserved': [], 'hdd_note': 'Az alkalmazás nem tárolt sajá +15:45:10 [X] after remove: deployed=False leftovers='/opt/docker/stacks/rallly' +15:46:13 [7] 60 s after remove: clean=True '' diff --git a/documentation/audits/the-28-2026-09-22/apps/rallly/traceback.txt b/documentation/audits/the-28-2026-09-22/apps/rallly/traceback.txt new file mode 100644 index 00000000..0c9f26e1 --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/apps/rallly/traceback.txt @@ -0,0 +1,5 @@ +Traceback (most recent call last): + File "/mnt/5_hdd/felhom.eu/git/felhom.eu/documentation/audits/the-28-2026-09-22/walk28.py", line 145, in main + (ph.get("phases", [{}])[-1].get("phase") if isinstance(ph, dict) else None)) + ~~~~~~~~~~~~~~~~~~~~~~^^^^ +IndexError: list index out of range diff --git a/documentation/audits/the-28-2026-09-22/apps/rallly/verdict.json b/documentation/audits/the-28-2026-09-22/apps/rallly/verdict.json new file mode 100644 index 00000000..38b1cb5a --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/apps/rallly/verdict.json @@ -0,0 +1,132 @@ +{ + "harness_version": 2, + "app": "rallly", + "venue": "guest 9202 demo-hp-scratch, controller 0.261.0, drill catalog", + "class": "db", + "from": { + "rallly": "lukevella/rallly:4.15.2", + "rallly-postgres": "postgres:16-alpine" + }, + "to": { + "rallly": "lukevella/rallly:4.15.2", + "rallly-postgres": "postgres:16-alpine" + }, + "verdict": "inconclusive", + "deployed": true, + "seed_route": "none — sign-in is an e-mail magic link; this venue has no mailbox the harness can read", + "seed_read_before": false, + "seed_read_after_update": false, + "backup": { + "snapshots_offered": 1, + "first": { + "time": "2026-09-22T13:43:36Z", + "short_id": "helyi", + "tier": 1, + "drive_label": "Belső SSD (rendszer)" + } + }, + "restore_verdict": "ok", + "seed_read_after_restore": false, + "healthy_after": true, + "migration_observed": "rallly | Prisma schema loaded from prisma.", + "removed_clean": true, + "edge": { + "from": "lukevella/rallly:4.11.1", + "to": "lukevella/rallly:4.15.2" + }, + "duration_s": 230.1, + "measured_at": "2026-09-22T13:42:26.789605+00:00", + "evidence": "the-28-2026-09-22/apps/rallly/", + "notes": [ + "fixture ran and found no non-browser seed route: sign-in is an e-mail magic link; this venue has no mailbox the harness can read" + ], + "front_door_after_deploy": { + "rc": 0, + "code": "200" + }, + "drill_commit": null, + "badge_seconds": 4.4, + "badges": { + "hu": [ + { + "title": "Ez az alkalmazás a legfrissebb elérhető változatot futtatja.", + "text": "Naprakész" + } + ], + "en": [ + { + "title": "This app is running the newest version available.", + "text": "Up to date" + } + ] + }, + "phases": { + "accepted": true, + "http": "202", + "phases": [ + { + "t": 0.0, + "phase": "safety-dump", + "label": "Adatbázis pillanatkép…", + "updating": true, + "error": null, + "hold": null + }, + { + "t": 1.1, + "phase": "pulling", + "label": "Új verzió letöltése…", + "updating": true, + "error": null, + "hold": null + }, + { + "t": 2.1, + "phase": "starting", + "label": "Indítás az új verzióval…", + "updating": true, + "error": null, + "hold": null + }, + { + "t": 3.1, + "phase": "done", + "label": "Frissítve", + "updating": false, + "error": null, + "hold": null + } + ], + "duration_s": 3.1, + "final_phase": "done", + "update_error": null, + "hold_reason": null, + "state": "running" + }, + "observables": { + "pinned_images": { + "rallly": "lukevella/rallly:4.15.2", + "rallly-postgres": "postgres:16-alpine" + }, + "installed_images": { + "rallly": "lukevella/rallly:4.15.2", + "rallly-postgres": "postgres:16-alpine" + }, + "catalog_images": { + "rallly": "lukevella/rallly:4.15.2", + "rallly-postgres": "postgres:16-alpine" + }, + "live_compose_image_lines": [ + "image: lukevella/rallly:4.15.2", + "image: postgres:16-alpine" + ], + "docker_inspect": [ + "rallly lukevella/rallly:4.15.2 running=true restarts=0", + "rallly-postgres postgres:16-alpine running=true restarts=0" + ] + }, + "update_phase_final": "done", + "restore_raw": "{'ok': True, 'snapshot_id': 'helyi', 'snapshots': [{'time': '2026-09-22T13:43:42Z', 'short_id': 'helyi', 'tier': 1, 'drive_label': 'Belső SSD (rendszer)'}], 'http': 'HTTP/2 302', 'location': ['location: /backups/restore?flash=flash.restore.started'], 'seconds': 28.4, 'state_after': 'running', 'hold_after': None, 'observables_after': {'pinned_images': {'rallly': 'lukevella/rallly:4.15.2', 'rallly-p", + "restore_refusal": null, + "remove_leftovers": "" +} \ No newline at end of file diff --git a/documentation/audits/the-28-2026-09-22/apps/recipe-importer/log.txt b/documentation/audits/the-28-2026-09-22/apps/recipe-importer/log.txt new file mode 100644 index 00000000..0ffba195 --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/apps/recipe-importer/log.txt @@ -0,0 +1,18 @@ +13:31:16 ==== recipe-importer (sub=recipe-importer, class=file-leg, edge=none) +13:31:16 [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'} +13:31:31 [1] deployed, controller state=running, pinned={'recipe-importer': 'gitea.dooplex.hu/admin/recipe-importer:v0.9.11'} +13:31:31 [1] front door 302 controller state=running +13:31:31 [2] NO FIXTURE — recorded inconclusive for the data half, not faked +13:31:31 [4] „Mentés most" -> 200 {'ok': True, 'message': 'Mentés elindítva'} +13:31:36 [4] backup idle; last=None +13:31:36 [4] backups page offers 1 restorable copy(ies) +13:31:36 [R] restoring recipe-importer from snapshot 'helyi' (of 1 offered) +13:31:36 [R] POST /backup/restore -> HTTP/2 302 ['location: /backups/restore?flash=flash.restore.started'] +13:31:37 + 0.0s restore (True, None, None) +13:31:47 + 10.2s restore (False, None, None) +13:31:47 [R] after restore: state=running hold=None phase=None +13:31:59 [6] restore -> ok, seed back = False +13:32:00 [X] stop -> 200 {'ok': True, 'message': 'Stack recipe-importer stop completed'} +13:32:05 [X] remove (with drive data) -> 200 {'ok': True, 'data': {'removed': 'recipe-importer', 'volumes_removed': [], 'hdd_paths_removed': [], 'hdd_paths_preserved': [], 'hdd_note': 'Az alkalmazás nem tá +13:32:12 [X] after remove: deployed=False leftovers='/opt/docker/stacks/recipe-importer' +13:33:16 [7] 60 s after remove: clean=True '' diff --git a/documentation/audits/the-28-2026-09-22/apps/recipe-importer/verdict.json b/documentation/audits/the-28-2026-09-22/apps/recipe-importer/verdict.json new file mode 100644 index 00000000..ed590a4e --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/apps/recipe-importer/verdict.json @@ -0,0 +1,42 @@ +{ + "harness_version": 2, + "app": "recipe-importer", + "venue": "guest 9202 demo-hp-scratch, controller 0.261.0, drill catalog", + "class": "file-leg", + "from": { + "recipe-importer": "gitea.dooplex.hu/admin/recipe-importer:v0.9.11" + }, + "to": {}, + "verdict": "no-edge", + "deployed": true, + "seed_route": "none written", + "seed_read_before": false, + "seed_read_after_update": false, + "backup": { + "snapshots_offered": 1, + "first": { + "time": "2026-09-22T11:31:33Z", + "short_id": "helyi", + "tier": 1, + "drive_label": "Belső SSD (rendszer)" + } + }, + "restore_verdict": "ok", + "seed_read_after_restore": false, + "healthy_after": false, + "migration_observed": null, + "removed_clean": true, + "edge": null, + "duration_s": 123.0, + "measured_at": "2026-09-22T11:31:16.415108+00:00", + "evidence": "the-28-2026-09-22/apps/recipe-importer/", + "notes": [ + "no fixture: no non-browser seed route was written for this app" + ], + "front_door_after_deploy": { + "rc": 0, + "code": "302" + }, + "restore_raw": "{'ok': True, 'snapshot_id': 'helyi', 'snapshots': [{'time': '2026-09-22T11:31:33Z', 'short_id': 'helyi', 'tier': 1, 'drive_label': 'Belső SSD (rendszer)'}], 'http': 'HTTP/2 302', 'location': ['location: /backups/restore?flash=flash.restore.started'], 'seconds': 10.2, 'state_after': 'running', 'hold_after': None, 'observables_after': {'pinned_images': {'recipe-importer': 'gitea.dooplex.hu/admin/rec", + "remove_leftovers": "" +} \ No newline at end of file diff --git a/documentation/audits/the-28-2026-09-22/apps/seerr/log.txt b/documentation/audits/the-28-2026-09-22/apps/seerr/log.txt new file mode 100644 index 00000000..53884843 --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/apps/seerr/log.txt @@ -0,0 +1,18 @@ +13:32:40 ==== seerr (sub=seerr, class=file-leg, edge=none) +13:32:40 [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'} +13:34:11 [1] deployed, controller state=running, pinned={'seerr': 'fallenbagel/jellyseerr:2.7.3'} +13:34:12 [1] front door 307 controller state=running +13:34:12 [2] NO FIXTURE — recorded inconclusive for the data half, not faked +13:34:12 [4] „Mentés most" -> 200 {'ok': True, 'message': 'Mentés elindítva'} +13:34:27 [4] backup idle; last=None +13:34:27 [4] backups page offers 1 restorable copy(ies) +13:34:27 [R] restoring seerr from snapshot 'helyi' (of 1 offered) +13:34:27 [R] POST /backup/restore -> HTTP/2 302 ['location: /backups/restore?flash=flash.restore.started'] +13:34:27 + 0.0s restore (True, None, None) +13:34:43 + 16.2s restore (False, None, None) +13:34:43 [R] after restore: state=running hold=None phase=None +13:34:56 [6] restore -> ok, seed back = False +13:34:56 [X] stop -> 200 {'ok': True, 'message': 'Stack seerr stop completed'} +13:35:01 [X] remove (with drive data) -> 200 {'ok': True, 'data': {'removed': 'seerr', 'volumes_removed': [], 'hdd_paths_removed': [], 'hdd_paths_preserved': [], 'hdd_note': 'Az alkalmazás nem tárolt saját +13:35:09 [X] after remove: deployed=False leftovers='/opt/docker/stacks/seerr' +13:36:12 [7] 60 s after remove: clean=True '' diff --git a/documentation/audits/the-28-2026-09-22/apps/seerr/verdict.json b/documentation/audits/the-28-2026-09-22/apps/seerr/verdict.json new file mode 100644 index 00000000..51e91932 --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/apps/seerr/verdict.json @@ -0,0 +1,42 @@ +{ + "harness_version": 2, + "app": "seerr", + "venue": "guest 9202 demo-hp-scratch, controller 0.261.0, drill catalog", + "class": "file-leg", + "from": { + "seerr": "fallenbagel/jellyseerr:2.7.3" + }, + "to": {}, + "verdict": "no-edge", + "deployed": true, + "seed_route": "none written", + "seed_read_before": false, + "seed_read_after_update": false, + "backup": { + "snapshots_offered": 1, + "first": { + "time": "2026-09-22T11:34:25Z", + "short_id": "helyi", + "tier": 1, + "drive_label": "Belső SSD (rendszer)" + } + }, + "restore_verdict": "ok", + "seed_read_after_restore": false, + "healthy_after": false, + "migration_observed": null, + "removed_clean": true, + "edge": null, + "duration_s": 218.8, + "measured_at": "2026-09-22T11:32:40.366944+00:00", + "evidence": "the-28-2026-09-22/apps/seerr/", + "notes": [ + "no fixture: no non-browser seed route was written for this app" + ], + "front_door_after_deploy": { + "rc": 0, + "code": "307" + }, + "restore_raw": "{'ok': True, 'snapshot_id': 'helyi', 'snapshots': [{'time': '2026-09-22T11:34:25Z', 'short_id': 'helyi', 'tier': 1, 'drive_label': 'Belső SSD (rendszer)'}], 'http': 'HTTP/2 302', 'location': ['location: /backups/restore?flash=flash.restore.started'], 'seconds': 16.3, 'state_after': 'running', 'hold_after': None, 'observables_after': {'pinned_images': {'seerr': 'fallenbagel/jellyseerr:2.7.3'}, 'ins", + "remove_leftovers": "" +} \ No newline at end of file diff --git a/documentation/audits/the-28-2026-09-22/apps/sonarr/app-logs-during-after.txt b/documentation/audits/the-28-2026-09-22/apps/sonarr/app-logs-during-after.txt new file mode 100644 index 00000000..80a6284b --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/apps/sonarr/app-logs-during-after.txt @@ -0,0 +1,60 @@ +sonarr | [migrations] started +sonarr | [migrations] no migrations found +sonarr | ─────────────────────────────────────── +sonarr | +sonarr | ██╗ ███████╗██╗ ██████╗ +sonarr | ██║ ██╔════╝██║██╔═══██╗ +sonarr | ██║ ███████╗██║██║ ██║ +sonarr | ██║ ╚════██║██║██║ ██║ +sonarr | ███████╗███████║██║╚██████╔╝ +sonarr | ╚══════╝╚══════╝╚═╝ ╚═════╝ +sonarr | +sonarr | Brought to you by linuxserver.io +sonarr | ─────────────────────────────────────── +sonarr | +sonarr | To support the app dev(s) visit: +sonarr | Sonarr: https://sonarr.tv/donate +sonarr | +sonarr | To support LSIO projects visit: +sonarr | https://www.linuxserver.io/donate/ +sonarr | +sonarr | ─────────────────────────────────────── +sonarr | GID/UID +sonarr | ─────────────────────────────────────── +sonarr | +sonarr | User UID: 1000 +sonarr | User GID: 1000 +sonarr | ─────────────────────────────────────── +sonarr | Linuxserver.io version: 4.0.20.3014-ls325 +sonarr | Build-date: 2026-09-16T17:05:23+00:00 +sonarr | ─────────────────────────────────────── +sonarr | +sonarr | [custom-init] No custom files found, skipping... +sonarr | [Info] Bootstrap: Starting Sonarr - /app/sonarr/bin/Sonarr - Version 4.0.20.3014 +sonarr | [Info] AppFolderInfo: Data directory is being overridden to [/config] +sonarr | [Debug] Bootstrap: Console selected +sonarr | [Info] AppFolderInfo: Data directory is being overridden to [/config] +sonarr | [Info] AppFolderInfo: Data directory is being overridden to [/config] +sonarr | [Info] MigrationController: *** Migrating data source=/config/sonarr.db;cache size=-20000;datetimekind=Utc;journal mode=Wal;pooling=True;version=3;busytimeout=1000 *** +sonarr | [Info] FluentMigrator.Runner.MigrationRunner: DatabaseEngineVersionCheck migrating +sonarr | [Info] FluentMigrator.Runner.MigrationRunner: PerformDBOperation +sonarr | [Info] NzbDrone.Core.Datastore.Migration.Framework.NzbDroneSQLiteProcessor: Performing DB Operation +sonarr | [Info] DatabaseEngineVersionCheck: SQLite 3.53.4 +sonarr | [Info] FluentMigrator.Runner.MigrationRunner: => 0.0668809s +sonarr | [Info] FluentMigrator.Runner.MigrationRunner: DatabaseEngineVersionCheck migrated +sonarr | [Info] FluentMigrator.Runner.MigrationRunner: => 0.0696878s +sonarr | [Info] MigrationController: *** Migrating data source=/config/logs.db;cache size=-20000;datetimekind=Utc;journal mode=Wal;pooling=True;version=3;busytimeout=1000 *** +sonarr | [Info] FluentMigrator.Runner.MigrationRunner: DatabaseEngineVersionCheck migrating +sonarr | [Info] FluentMigrator.Runner.MigrationRunner: PerformDBOperation +sonarr | [Info] NzbDrone.Core.Datastore.Migration.Framework.NzbDroneSQLiteProcessor: Performing DB Operation +sonarr | [Info] DatabaseEngineVersionCheck: SQLite 3.53.4 +sonarr | [Info] FluentMigrator.Runner.MigrationRunner: => 0.0037763s +sonarr | [Info] FluentMigrator.Runner.MigrationRunner: DatabaseEngineVersionCheck migrated +sonarr | [Info] FluentMigrator.Runner.MigrationRunner: => 0.0040382s +sonarr | [Info] ConfigureHostFilteringOptions: Allowed Hosts is not configured, accepting requests for any host +sonarr | [Info] Microsoft.Hosting.Lifetime: Now listening on: http://[::]:8989 +sonarr | [Info] Microsoft.Hosting.Lifetime: Application started. Press Ctrl+C to shut down. +sonarr | [Info] Microsoft.Hosting.Lifetime: Hosting environment: Production +sonarr | [Info] Microsoft.Hosting.Lifetime: Content root path: /app/sonarr/bin +sonarr | [Info] ManagedHttpDispatcher: IPv4 is available: True, IPv6 will be disabled +sonarr | [ls.io-init] done. diff --git a/documentation/audits/the-28-2026-09-22/apps/sonarr/log.txt b/documentation/audits/the-28-2026-09-22/apps/sonarr/log.txt new file mode 100644 index 00000000..d4ad8db7 --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/apps/sonarr/log.txt @@ -0,0 +1,34 @@ +13:58:13 ==== sonarr (sub=sonarr, class=file-leg, edge={'from': 'lscr.io/linuxserver/sonarr:4.0.19', 'to': 'lscr.io/linuxserver/sonarr:4.0.20'}) +13:58:15 [1] made the drive paths this app requires: ['/mnt/felhom-drives/scratch_hdd/userdata/sonarr'] +13:58:15 [1] required fields filled beyond DOMAIN/SUBDOMAIN: ['HDD_PATH'] +13:58:15 [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'} +13:58:51 [1] deployed, controller state=running, pinned={'sonarr': 'lscr.io/linuxserver/sonarr:4.0.19'} +13:58:51 [1] front door 200 controller state=running +13:58:54 sonarr: seeded tag drill92db43f5 +13:58:57 sonarr: readback found=True (http 200, control passed) +13:58:57 [3] C1 readback before = True +13:58:57 [4] „Mentés most" -> 200 {'ok': True, 'message': 'Mentés elindítva'} +13:59:02 [4] backup idle; last=None +13:59:02 [4] backups page offers 1 restorable copy(ies) +13:59:03 [5] drill commit c0eeef8b8f54: sonarr lscr.io/linuxserver/sonarr:4.0.19 -> lscr.io/linuxserver/sonarr:4.0.20 (push rc=0) +13:59:07 [5] badge {'hu': [{'title': 'Újabb változat érhető el ehhez az alkalmazáshoz. A frissítés indításához nyomd meg a Frissítés gombot.', 'text': 'Frissítés elérhető — ma'}], 'en': [{'title': 'A newer version of this app is available. Select the Update button to start it.', 'text': 'Update available — today'}]} +13:59:07 [6] Update -> 202 {'ok': True, 'data': {'accepted': True, 'completed': False}, 'message': 'Frissítés elindult – az állapot a kártyán követhető'} +13:59:07 + 0.0s phase=safety-dump label=Adatbázis pillanatkép… err=None hold=None +13:59:09 + 1.1s phase=pulling label=Új verzió letöltése… err=None hold=None +13:59:19 + 11.3s phase=starting label=Indítás az új verzióval… err=None hold=None +13:59:23 + 15.4s phase=verifying label=Működés ellenőrzése… err=None hold=None +13:59:33 + 25.7s phase=done label=Frissítve err=None hold=None +13:59:39 sonarr: readback found=True (http 200, control passed) +13:59:39 [R] restoring sonarr from snapshot 'helyi' (of 1 offered) +13:59:39 [R] POST /backup/restore -> HTTP/2 302 ['location: /backups/restore?flash=flash.restore.started'] +13:59:39 + 0.0s restore (True, None, None) +13:59:53 + 14.2s restore (False, None, None) +13:59:53 [R] after restore: state=running hold=None phase=done +14:00:15 sonarr: readback found=True (http 200, control passed) +14:00:15 [6] restore -> ok, seed back = True +14:00:29 [X] stop -> 200 {'ok': True, 'message': 'Stack sonarr stop completed'} +14:00:34 [X] remove (with drive data) -> 409 {'ok': False, 'error': 'A(z) /mnt/felhom-drives/scratch_hdd/userdata/sonarr tárhely jelenleg nem elérhető — az alkalmazás nem távolítható el, amíg a meghajtó vi +14:00:34 [X] refused because the drive path cannot be resolved (R-442, fail-closed and right) — removing the app and KEEPING the drive data instead +14:00:35 [X] remove (keeping drive data) -> 200 {'ok': True, 'data': {'removed': 'sonarr', 'volumes_removed': [], 'hdd_paths_removed': [], 'hdd_paths_preserved': [], 'backup_paths_removed': ['/mnt/felhom-driv +14:00:43 [X] after remove: deployed=False leftovers='/opt/docker/stacks/sonarr' +14:01:46 [7] 60 s after remove: clean=True '' diff --git a/documentation/audits/the-28-2026-09-22/apps/sonarr/verdict.json b/documentation/audits/the-28-2026-09-22/apps/sonarr/verdict.json new file mode 100644 index 00000000..29b8cd0a --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/apps/sonarr/verdict.json @@ -0,0 +1,131 @@ +{ + "harness_version": 2, + "app": "sonarr", + "venue": "guest 9202 demo-hp-scratch, controller 0.261.0, drill catalog", + "class": "file-leg", + "from": { + "sonarr": "lscr.io/linuxserver/sonarr:4.0.19" + }, + "to": { + "sonarr": "lscr.io/linuxserver/sonarr:4.0.20" + }, + "verdict": "proven", + "deployed": true, + "seed_route": "its own /api/v3/tag with the app's own ApiKey", + "seed_read_before": true, + "seed_read_after_update": true, + "backup": { + "snapshots_offered": 1, + "first": { + "time": "2026-09-22T11:59:01Z", + "short_id": "helyi", + "tier": 1, + "drive_label": "sonarr" + } + }, + "restore_verdict": "ok", + "seed_read_after_restore": true, + "healthy_after": true, + "migration_observed": "sonarr | [migrations] started", + "removed_clean": true, + "edge": { + "from": "lscr.io/linuxserver/sonarr:4.0.19", + "to": "lscr.io/linuxserver/sonarr:4.0.20" + }, + "duration_s": 216.6, + "measured_at": "2026-09-22T11:58:13.323688+00:00", + "evidence": "the-28-2026-09-22/apps/sonarr/", + "notes": [], + "front_door_after_deploy": { + "rc": 0, + "code": "200" + }, + "drill_commit": "c0eeef8b8f54", + "badge_seconds": 4.6, + "badges": { + "hu": [ + { + "title": "Újabb változat érhető el ehhez az alkalmazáshoz. A frissítés indításához nyomd meg a Frissítés gombot.", + "text": "Frissítés elérhető — ma" + } + ], + "en": [ + { + "title": "A newer version of this app is available. Select the Update button to start it.", + "text": "Update available — today" + } + ] + }, + "phases": { + "accepted": true, + "http": "202", + "phases": [ + { + "t": 0.0, + "phase": "safety-dump", + "label": "Adatbázis pillanatkép…", + "updating": true, + "error": null, + "hold": null + }, + { + "t": 1.1, + "phase": "pulling", + "label": "Új verzió letöltése…", + "updating": true, + "error": null, + "hold": null + }, + { + "t": 11.3, + "phase": "starting", + "label": "Indítás az új verzióval…", + "updating": true, + "error": null, + "hold": null + }, + { + "t": 15.4, + "phase": "verifying", + "label": "Működés ellenőrzése…", + "updating": true, + "error": null, + "hold": null + }, + { + "t": 25.7, + "phase": "done", + "label": "Frissítve", + "updating": false, + "error": null, + "hold": null + } + ], + "duration_s": 25.7, + "final_phase": "done", + "update_error": null, + "hold_reason": null, + "state": "running" + }, + "observables": { + "pinned_images": { + "sonarr": "lscr.io/linuxserver/sonarr:4.0.20" + }, + "installed_images": { + "sonarr": "lscr.io/linuxserver/sonarr:4.0.20" + }, + "catalog_images": { + "sonarr": "lscr.io/linuxserver/sonarr:4.0.20" + }, + "live_compose_image_lines": [ + "image: lscr.io/linuxserver/sonarr:4.0.20" + ], + "docker_inspect": [ + "sonarr lscr.io/linuxserver/sonarr:4.0.20 running=true restarts=0" + ] + }, + "update_phase_final": "done", + "restore_raw": "{'ok': True, 'snapshot_id': 'helyi', 'snapshots': [{'time': '2026-09-22T11:59:01Z', 'short_id': 'helyi', 'tier': 1, 'drive_label': 'sonarr'}], 'http': 'HTTP/2 302', 'location': ['location: /backups/restore?flash=flash.restore.started'], 'seconds': 14.2, 'state_after': 'running', 'hold_after': None, 'observables_after': {'pinned_images': {'sonarr': 'lscr.io/linuxserver/sonarr:4.0.19'}, 'installed_i", + "restore_refusal": null, + "remove_leftovers": "" +} \ No newline at end of file diff --git a/documentation/audits/the-28-2026-09-22/apps/sparkyfitness/log.txt b/documentation/audits/the-28-2026-09-22/apps/sparkyfitness/log.txt new file mode 100644 index 00000000..a5bed78a --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/apps/sparkyfitness/log.txt @@ -0,0 +1,9 @@ +14:57:29 ==== sparkyfitness (sub=sparkyfitness, class=db, edge=none) +14:57:29 [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'} +15:05:02 [1] never became deployed (last controller state='degraded') +15:05:03 [X] stop -> 200 {'ok': True, 'message': 'Stack sparkyfitness stop completed'} +15:05:08 [X] remove (with drive data) -> 409 {'ok': False, 'error': 'stack "sparkyfitness" is not deployed'} +15:05:08 [X] refused because the drive path cannot be resolved (R-442, fail-closed and right) — removing the app and KEEPING the drive data instead +15:05:08 [X] remove (keeping drive data) -> 409 {'ok': False, 'error': 'stack "sparkyfitness" is not deployed'} +15:05:15 [X] after remove: deployed=False leftovers='/opt/docker/stacks/sparkyfitness' +15:06:19 [7] 60 s after remove: clean=False '/opt/docker/stacks/sparkyfitness/app.yaml' diff --git a/documentation/audits/the-28-2026-09-22/apps/sparkyfitness/verdict.json b/documentation/audits/the-28-2026-09-22/apps/sparkyfitness/verdict.json new file mode 100644 index 00000000..cc5473f3 --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/apps/sparkyfitness/verdict.json @@ -0,0 +1,27 @@ +{ + "harness_version": 2, + "app": "sparkyfitness", + "venue": "guest 9202 demo-hp-scratch, controller 0.261.0, drill catalog", + "class": "db", + "from": {}, + "to": {}, + "verdict": "could-not-deploy", + "deployed": false, + "seed_route": null, + "seed_read_before": false, + "seed_read_after_update": false, + "backup": null, + "restore_verdict": "not-attempted", + "seed_read_after_restore": false, + "healthy_after": false, + "migration_observed": null, + "removed_clean": false, + "edge": null, + "duration_s": 534.0, + "measured_at": "2026-09-22T12:57:29.380404+00:00", + "evidence": "the-28-2026-09-22/apps/sparkyfitness/", + "notes": [ + "deploy never reached `deployed` with a pin" + ], + "remove_leftovers": "/opt/docker/stacks/sparkyfitness/app.yaml" +} \ No newline at end of file diff --git a/documentation/audits/the-28-2026-09-22/apps/termix/app-logs-during-after.txt b/documentation/audits/the-28-2026-09-22/apps/termix/app-logs-during-after.txt new file mode 100644 index 00000000..647cf415 --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/apps/termix/app-logs-during-after.txt @@ -0,0 +1,27 @@ +termix | Setting up user permissions (PUID: 1000, PGID: 1000)... +termix | usermod: no changes +termix | User node is now UID: 1000, GID: 1000 +termix | Loading persisted SSL settings from /app/data/.env +termix | Configuring web UI to run on port: 8080 +termix | SSL disabled - using HTTP-only configuration (default) +termix | Data directory is writable +termix | OPKSSH directory is writable +termix | OPKSSH binary directory found at /app/data/opkssh +termix | Starting nginx... +termix | Starting backend services... +termix | [1:37:29 PM] [INFO] [🚀] Termix backend initialization started [op:backend_init_start] +termix | [1:37:29 PM] [INFO] [📦] Termix Backend starting - Version: 2.8.0 [op:startup] +termix | [1:37:29 PM] [INFO] [🗄️] Database layer pre-upgrade backup created [op:database_layer_preupgrade_backup_created] +termix | [1:37:29 PM] [INFO] [🚀] SSL not enabled - skipping certificate generation [op:ssl_disabled_default] +termix | [1:37:29 PM] [SUCCESS] [🚀] SSL setup completed [op:backend_init_ssl] +termix | [1:37:29 PM] [INFO] [🗄️] Initializing SQLite database [op:db_init] +termix | [1:37:29 PM] [INFO] [🗄️] Successfully migrated audit_logs table to remove user_id NOT NULL constraint [op:schema_migration_audit_user_id_nullable] +termix | [1:37:29 PM] [INFO] [🗄️] session_recordings now survives user deletion [op:audit_retention_migration] +termix | [1:37:29 PM] [INFO] [🗄️] Performance indexes ready in 5ms [op:performance_index_create] +termix | [1:37:29 PM] [SUCCESS] [🗄️] Schema migration completed [op:schema_migration] +termix | [1:37:29 PM] [SUCCESS] [🚀] Database initialized (sqlite) [op:backend_init_db] +termix | [1:37:29 PM] [INFO] [🗄️] User key migration pass finished [op:dek_migration_boot] +termix | [1:37:29 PM] [INFO] [🗄️] Legacy shared-credential cleanup finished [op:legacy_share_cleanup] +termix | [1:37:29 PM] [INFO] [🗄️] Preserved legacy shared SSH authentication behavior [op:legacy_shared_ssh_auth_opt_in_migration] +termix | [1:37:29 PM] [INFO] [🗄️] Shared host secrets migration finished [op:shared_host_secrets_migration] +termix | [1:37:29 PM] [INFO] [🗄️] Removed legacy shared SSH authentication snapshots [op:private_shared_ssh_auth_migration] diff --git a/documentation/audits/the-28-2026-09-22/apps/termix/log.txt b/documentation/audits/the-28-2026-09-22/apps/termix/log.txt new file mode 100644 index 00000000..45cac844 --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/apps/termix/log.txt @@ -0,0 +1,30 @@ +13:35:36 ==== termix (sub=termix, class=file-leg, edge={'from': 'ghcr.io/lukegus/termix:2.5.0', 'to': 'ghcr.io/lukegus/termix:2.8.0'}) +13:35:36 [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'} +13:36:01 [1] deployed, controller state=running, pinned={'termix': 'ghcr.io/lukegus/termix:2.5.0'} +13:36:01 [1] front door 200 controller state=running +13:36:02 termix: seeded user drill343476c7 via /users/create +13:36:02 termix: readback found=True (http 200, control refused as it must) +13:36:02 [3] C1 readback before = True +13:36:02 [4] „Mentés most" -> 200 {'ok': True, 'message': 'Mentés elindítva'} +13:36:37 [4] backup idle; last=None +13:36:37 [4] backups page offers 1 restorable copy(ies) +13:36:38 [5] drill commit d41fc1cb0f53: termix ghcr.io/lukegus/termix:2.5.0 -> ghcr.io/lukegus/termix:2.8.0 (push rc=0) +13:36:43 [5] badge {'hu': [{'title': 'Újabb változat érhető el ehhez az alkalmazáshoz. A frissítés indításához nyomd meg a Frissítés gombot.', 'text': 'Frissítés elérhető — ma'}], 'en': [{'title': 'A newer version of this app is available. Select the Update button to start it.', 'text': 'Update available — today'}]} +13:36:43 [6] Update -> 202 {'ok': True, 'data': {'accepted': True, 'completed': False}, 'message': 'Frissítés elindult – az állapot a kártyán követhető'} +13:36:43 + 0.0s phase=safety-dump label=Adatbázis pillanatkép… err=None hold=None +13:36:44 + 1.1s phase=pulling label=Új verzió letöltése… err=None hold=None +13:37:12 + 28.7s phase=starting label=Indítás az új verzióval… err=None hold=None +13:37:25 + 42.1s phase=verifying label=Működés ellenőrzése… err=None hold=None +13:37:30 + 47.2s phase=done label=Frissítve err=None hold=None +13:37:34 termix: readback found=True (http 200, control refused as it must) +13:37:34 [R] restoring termix from snapshot 'helyi' (of 1 offered) +13:37:34 [R] POST /backup/restore -> HTTP/2 302 ['location: /backups/restore?flash=flash.restore.started'] +13:37:34 + 0.0s restore (True, None, None) +13:37:44 + 10.1s restore (False, None, None) +13:37:44 [R] after restore: state=running hold=None phase=done +13:37:57 termix: readback found=True (http 200, control refused as it must) +13:37:57 [6] restore -> ok, seed back = True +13:38:10 [X] stop -> 200 {'ok': True, 'message': 'Stack termix stop completed'} +13:38:15 [X] remove (with drive data) -> 200 {'ok': True, 'data': {'removed': 'termix', 'volumes_removed': [], 'hdd_paths_removed': [], 'hdd_paths_preserved': [], 'hdd_note': 'Az alkalmazás nem tárolt sajá +13:38:23 [X] after remove: deployed=False leftovers='/opt/docker/stacks/termix' +13:39:26 [7] 60 s after remove: clean=False 'termix' diff --git a/documentation/audits/the-28-2026-09-22/apps/termix/verdict.json b/documentation/audits/the-28-2026-09-22/apps/termix/verdict.json new file mode 100644 index 00000000..b5d57bb2 --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/apps/termix/verdict.json @@ -0,0 +1,130 @@ +{ + "harness_version": 2, + "app": "termix", + "venue": "guest 9202 demo-hp-scratch, controller 0.261.0, drill catalog", + "class": "file-leg", + "from": { + "termix": "ghcr.io/lukegus/termix:2.5.0" + }, + "to": { + "termix": "ghcr.io/lukegus/termix:2.8.0" + }, + "verdict": "proven", + "deployed": true, + "seed_route": "its own /users/create sign-up, then /users/me", + "seed_read_before": true, + "seed_read_after_update": true, + "backup": { + "snapshots_offered": 1, + "first": { + "time": "2026-09-22T11:36:35Z", + "short_id": "helyi", + "tier": 1, + "drive_label": "Belső SSD (rendszer)" + } + }, + "restore_verdict": "ok", + "seed_read_after_restore": true, + "healthy_after": true, + "migration_observed": "termix | [1:37:29 PM] [INFO] [🗄️] Database layer pre-upgrade backup created [op:database_layer_preupgrade_backup_created]", + "removed_clean": false, + "edge": { + "from": "ghcr.io/lukegus/termix:2.5.0", + "to": "ghcr.io/lukegus/termix:2.8.0" + }, + "duration_s": 232.5, + "measured_at": "2026-09-22T11:35:36.476009+00:00", + "evidence": "the-28-2026-09-22/apps/termix/", + "notes": [], + "front_door_after_deploy": { + "rc": 0, + "code": "200" + }, + "drill_commit": "d41fc1cb0f53", + "badge_seconds": 4.4, + "badges": { + "hu": [ + { + "title": "Újabb változat érhető el ehhez az alkalmazáshoz. A frissítés indításához nyomd meg a Frissítés gombot.", + "text": "Frissítés elérhető — ma" + } + ], + "en": [ + { + "title": "A newer version of this app is available. Select the Update button to start it.", + "text": "Update available — today" + } + ] + }, + "phases": { + "accepted": true, + "http": "202", + "phases": [ + { + "t": 0.0, + "phase": "safety-dump", + "label": "Adatbázis pillanatkép…", + "updating": true, + "error": null, + "hold": null + }, + { + "t": 1.1, + "phase": "pulling", + "label": "Új verzió letöltése…", + "updating": true, + "error": null, + "hold": null + }, + { + "t": 28.7, + "phase": "starting", + "label": "Indítás az új verzióval…", + "updating": true, + "error": null, + "hold": null + }, + { + "t": 42.1, + "phase": "verifying", + "label": "Működés ellenőrzése…", + "updating": true, + "error": null, + "hold": null + }, + { + "t": 47.2, + "phase": "done", + "label": "Frissítve", + "updating": false, + "error": null, + "hold": null + } + ], + "duration_s": 47.2, + "final_phase": "done", + "update_error": null, + "hold_reason": null, + "state": "running" + }, + "observables": { + "pinned_images": { + "termix": "ghcr.io/lukegus/termix:2.8.0" + }, + "installed_images": { + "termix": "ghcr.io/lukegus/termix:2.8.0" + }, + "catalog_images": { + "termix": "ghcr.io/lukegus/termix:2.8.0" + }, + "live_compose_image_lines": [ + "image: ghcr.io/lukegus/termix:2.8.0" + ], + "docker_inspect": [ + "termix ghcr.io/lukegus/termix:2.8.0 running=true restarts=0" + ] + }, + "update_phase_final": "done", + "restore_raw": "{'ok': True, 'snapshot_id': 'helyi', 'snapshots': [{'time': '2026-09-22T11:36:35Z', 'short_id': 'helyi', 'tier': 1, 'drive_label': 'Belső SSD (rendszer)'}], 'http': 'HTTP/2 302', 'location': ['location: /backups/restore?flash=flash.restore.started'], 'seconds': 10.2, 'state_after': 'running', 'hold_after': None, 'observables_after': {'pinned_images': {'termix': 'ghcr.io/lukegus/termix:2.5.0'}, 'in", + "remove_leftovers": "termix" +} \ No newline at end of file diff --git a/documentation/audits/the-28-2026-09-22/apps/wanderer/log.txt b/documentation/audits/the-28-2026-09-22/apps/wanderer/log.txt new file mode 100644 index 00000000..56c7c5ae --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/apps/wanderer/log.txt @@ -0,0 +1,19 @@ +15:16:01 ==== wanderer (sub=wanderer, class=db, edge=none) +15:16:01 [1] required fields filled beyond DOMAIN/SUBDOMAIN: ['SUBDOMAIN_DB'] +15:16:02 [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'} +15:18:37 [1] deployed, controller state=running, pinned={'wanderer': 'flomp/wanderer-web:v0.20.0', 'wanderer-db': 'flomp/wanderer-db:v0.20.0', 'wanderer-search': 'getmeili/meilisearch:v1.36.0'} +15:18:37 [1] front door 404 controller state=running +15:18:37 [2] NO FIXTURE — recorded inconclusive for the data half, not faked +15:18:37 [4] „Mentés most" -> 200 {'ok': True, 'message': 'Mentés elindítva'} +15:18:53 [4] backup idle; last=None +15:18:53 [4] backups page offers 1 restorable copy(ies) +15:18:53 [R] restoring wanderer from snapshot 'helyi' (of 1 offered) +15:18:53 [R] POST /backup/restore -> HTTP/2 302 ['location: /backups/restore?flash=flash.restore.started'] +15:18:53 + 0.0s restore (True, None, None) +15:20:52 + 119.6s restore (False, None, None) +15:20:52 [R] after restore: state=starting hold=None phase=None +15:21:10 [6] restore -> ok, seed back = False +15:21:21 [X] stop -> 200 {'ok': True, 'message': 'Stack wanderer stop completed'} +15:21:27 [X] remove (with drive data) -> 200 {'ok': True, 'data': {'removed': 'wanderer', 'volumes_removed': [], 'hdd_paths_removed': [], 'hdd_paths_preserved': [], 'hdd_note': 'Az alkalmazás nem tárolt sa +15:21:35 [X] after remove: deployed=False leftovers='/opt/docker/stacks/wanderer' +15:22:38 [7] 60 s after remove: clean=True '' diff --git a/documentation/audits/the-28-2026-09-22/apps/wanderer/traceback.txt b/documentation/audits/the-28-2026-09-22/apps/wanderer/traceback.txt new file mode 100644 index 00000000..776a241b --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/apps/wanderer/traceback.txt @@ -0,0 +1,5 @@ +Traceback (most recent call last): + File "/mnt/5_hdd/felhom.eu/git/felhom.eu/documentation/audits/the-28-2026-09-22/walk28.py", line 169, in main + m = re.search(r"flash_error=([^&\s]+)", loc) + ^^ +NameError: name 're' is not defined. Did you mean: 'rec'? Or did you forget to import 're'? diff --git a/documentation/audits/the-28-2026-09-22/apps/wanderer/unhealthy-evidence.txt b/documentation/audits/the-28-2026-09-22/apps/wanderer/unhealthy-evidence.txt new file mode 100644 index 00000000..46ddf1ee --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/apps/wanderer/unhealthy-evidence.txt @@ -0,0 +1,12 @@ +== wanderer container health: +status=running health=starting failing=0 +== last healthcheck output: + +== app log tail: + +> wanderer@0.20.0 start +> node watcher.js & node build + +[File Watcher] Service active. Watching: /app/uploads +Listening on http://0.0.0.0:3000 + diff --git a/documentation/audits/the-28-2026-09-22/apps/wanderer/verdict.json b/documentation/audits/the-28-2026-09-22/apps/wanderer/verdict.json new file mode 100644 index 00000000..f3e74e52 --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/apps/wanderer/verdict.json @@ -0,0 +1,45 @@ +{ + "harness_version": 2, + "app": "wanderer", + "venue": "guest 9202 demo-hp-scratch, controller 0.261.0, drill catalog", + "class": "db", + "from": { + "wanderer": "flomp/wanderer-web:v0.20.0", + "wanderer-db": "flomp/wanderer-db:v0.20.0", + "wanderer-search": "getmeili/meilisearch:v1.36.0" + }, + "to": {}, + "verdict": "no-edge", + "deployed": true, + "seed_route": "none written", + "seed_read_before": false, + "seed_read_after_update": false, + "backup": { + "snapshots_offered": 1, + "first": { + "time": "2026-09-22T13:18:52Z", + "short_id": "helyi", + "tier": 1, + "drive_label": "Belső SSD (rendszer)" + } + }, + "restore_verdict": "ok", + "seed_read_after_restore": false, + "healthy_after": false, + "migration_observed": null, + "removed_clean": true, + "edge": null, + "duration_s": 400.5, + "measured_at": "2026-09-22T13:16:01.707259+00:00", + "evidence": "the-28-2026-09-22/apps/wanderer/", + "notes": [ + "no fixture: no non-browser seed route was written for this app" + ], + "front_door_after_deploy": { + "rc": 0, + "code": "404" + }, + "restore_raw": "{'ok': True, 'snapshot_id': 'helyi', 'snapshots': [{'time': '2026-09-22T13:18:52Z', 'short_id': 'helyi', 'tier': 1, 'drive_label': 'Belső SSD (rendszer)'}], 'http': 'HTTP/2 302', 'location': ['location: /backups/restore?flash=flash.restore.started'], 'seconds': 119.6, 'state_after': 'starting', 'hold_after': None, 'observables_after': {'pinned_images': {'wanderer': 'flomp/wanderer-web:v0.20.0', 'w", + "restore_refusal": null, + "remove_leftovers": "" +} \ No newline at end of file diff --git a/documentation/audits/the-28-2026-09-22/assemble.py b/documentation/audits/the-28-2026-09-22/assemble.py new file mode 100644 index 00000000..02b22593 --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/assemble.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python3 +"""Assemble DRILL-the-28-2026-09-22.md from the parts + the generated table.""" +import io, json, glob, os +HERE = os.path.dirname(os.path.abspath(__file__)) +A = os.path.dirname(HERE) + +recs = {} +for f in glob.glob(os.path.join(HERE, "apps", "*", "verdict.json")): + d = json.load(open(f, encoding="utf-8")); recs[d["app"]] = d + +walked = [d for d in recs.values() if d["deployed"]] +proven = [d for d in recs.values() if d["verdict"] == "proven"] +failed = [d for d in recs.values() if d["verdict"] == "failed"] +seeded = [d for d in recs.values() if d["seed_read_before"]] +restored = [d for d in recs.values() if d["restore_verdict"] == "ok"] +refused = [d for d in recs.values() if d["restore_verdict"] == "refused-with-a-sentence"] +dirty = [d for d in recs.values() if d["deployed"] and not d.get("removed_clean")] + +parts = [io.open(os.path.join(A, "DRILL-the-28-2026-09-22-HEAD.md"), encoding="utf-8").read()] +body1 = io.open(os.path.join(HERE, "BODY-part1.md"), encoding="utf-8").read() +body1 = (body1.replace("_N_", str(len(walked))) + .replace("_INTERVENTIONS_", os.environ.get("INTERVENTIONS", "see below")) + .replace("_HEADLINE_", os.environ.get("HEADLINE", "see below"))) +parts.append(body1) + +tbl = io.open(os.path.join(HERE, "TABLE.md"), encoding="utf-8").read() +parts.append("---\n\n## The table — all twenty-eight\n\n" + "Classes are `07-backup-architecture.md` §6.2's, not re-derived.\n\n" + tbl + "\n" + + "**Read across the walk rather than down one column:** " + f"**{len(walked)} of 28 deployed**, " + f"**{len(seeded)}** had a non-browser route that seeded AND read back, " + f"**{len(restored)}** restored from their own copy, " + f"**{len(refused)}** were correctly REFUSED a restore, " + f"**{len(proven)} proven / {len(failed)} failed** on the apps that had an upstream " + f"edge, and **{len(dirty)}** left a container behind (R-633).\n") + +for f in ("BODY-sidejobs.md", "BODY-r630.md", "BODY-promotion.md", "BODY-teardown.md"): + p = os.path.join(HERE, f) + if os.path.exists(p): + parts.append(io.open(p, encoding="utf-8").read()) + +out = "\n".join(parts) +io.open(os.path.join(A, "DRILL-the-28-2026-09-22.md"), "w", encoding="utf-8").write(out) +print("assembled", len(out), "bytes ·", len(walked), "walked ·", len(proven), "proven ·", + len(seeded), "seeded ·", len(restored), "restored ·", len(refused), "refused ·", + len(dirty), "dirty") diff --git a/documentation/audits/the-28-2026-09-22/batch28.sh b/documentation/audits/the-28-2026-09-22/batch28.sh new file mode 100755 index 00000000..de192d5d --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/batch28.sh @@ -0,0 +1,13 @@ +#!/bin/bash +# Run N apps concurrently. Each worker has its OWN controller session dir (walk28.py sets w.SC), +# and drill pushes are serialised by a lock file — one git clone cannot take two pushes at once. +cd "$(dirname "$0")" +N=${N:-3} +for app in "$@"; do + while [ "$(pgrep -cf '[w]alk28.py')" -ge "$N" ]; do sleep 5; done + echo "$(date +%H:%M:%S) launching $app" + nohup python3 -u walk28.py "$app" > "apps/$app.out" 2>&1 & + sleep 8 +done +wait +echo "$(date +%H:%M:%S) batch done" diff --git a/documentation/audits/the-28-2026-09-22/edges.json b/documentation/audits/the-28-2026-09-22/edges.json new file mode 100644 index 00000000..90b85a47 --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/edges.json @@ -0,0 +1,50 @@ +{ + "emby": { + "from": "emby/embyserver:4.10.0.20", + "to": "emby/embyserver:4.11.0.1" + }, + "immich": { + "from": "ghcr.io/immich-app/immich-server:v3.0.3", + "to": "ghcr.io/immich-app/immich-server:v3.2.2" + }, + "termix": { + "from": "ghcr.io/lukegus/termix:2.5.0", + "to": "ghcr.io/lukegus/termix:2.8.0" + }, + "ghost": { + "from": "ghost:6.53.0-alpine", + "to": "ghost:6.64.0-alpine" + }, + "komga": { + "from": "gotson/komga:1.25.0", + "to": "gotson/komga:1.27.1" + }, + "code-server": { + "from": "lscr.io/linuxserver/code-server:4.129.0", + "to": "lscr.io/linuxserver/code-server:4.138.0" + }, + "radarr": { + "from": "lscr.io/linuxserver/radarr:6.3.0", + "to": "lscr.io/linuxserver/radarr:6.4.4" + }, + "sonarr": { + "from": "lscr.io/linuxserver/sonarr:4.0.19", + "to": "lscr.io/linuxserver/sonarr:4.0.20" + }, + "rallly": { + "from": "lukevella/rallly:4.11.1", + "to": "lukevella/rallly:4.15.2" + }, + "outline": { + "from": "outlinewiki/outline:1.9.1", + "to": "outlinewiki/outline:1.10.1" + }, + "plex": { + "from": "plexinc/pms-docker:1.41.4.9463-630c9f557", + "to": "plexinc/pms-docker:1.43.4.10903-e5521bd8c" + }, + "crafty-controller": { + "from": "registry.gitlab.com/crafty-controller/crafty-4:4.10.7", + "to": "registry.gitlab.com/crafty-controller/crafty-4:4.11.0" + } +} \ No newline at end of file diff --git a/documentation/audits/the-28-2026-09-22/finish.sh b/documentation/audits/the-28-2026-09-22/finish.sh new file mode 100755 index 00000000..5f0d7e9b --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/finish.sh @@ -0,0 +1,10 @@ +#!/bin/bash +cd "$(dirname "$0")" +until [ "$(pgrep -cf '[w]alk28.py')" = "0" ] && [ "$(pgrep -cf '[b]atch28.sh')" = "0" ]; do sleep 30; done +echo "$(date +%H:%M:%S) main walks done — re-walking the three the harness bug cost" +N=1 ./batch28.sh $(cat REWALK.txt) +until [ "$(pgrep -cf '[w]alk28.py')" = "0" ]; do sleep 20; done +echo "$(date +%H:%M:%S) re-walks done — R-630 side job" +python3 -u sidejob_630.py > sidejobs/r630.out 2>&1 +echo "$(date +%H:%M:%S) ALL FIELD WORK DONE" +python3 mktable.py | tail -2 diff --git a/documentation/audits/the-28-2026-09-22/fixtures28.py b/documentation/audits/the-28-2026-09-22/fixtures28.py new file mode 100644 index 00000000..27a23c71 --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/fixtures28.py @@ -0,0 +1,412 @@ +#!/usr/bin/env python3 +"""fixtures28.py — seed/verify for the twenty-eight, same rule as `fixtures.py` (R-156). + +*Nothing is ever seeded into a volume by hand.* Every seed goes in through the app's OWN interface: +its HTTP API through the household's real front door, or its own CLI inside its own container. A raw +SQL INSERT or a planted file is never used. + +An app with no non-browser route returns None from `seed()` and carries a `tried` string naming +what was attempted. That is a RESULT — `inconclusive` — not a gap to be papered over. + +Every `verify()` that can prove itself does so on the same call: it also asks for something that +MUST be absent, so a readback that has broken into always answering "found" fails instead of +passing everything. +""" +import json, re, secrets + + +def _gx(w, container, *cmd, timeout=240): + import shlex + return w.guest(f"docker exec {container} " + " ".join(shlex.quote(c) for c in cmd) + + " 2>&1", timeout=timeout) + + +# ── the *arr family: their own v3 API, key read from their own config ──────────────────────────── +class _Arr: + """radarr / sonarr. The API key is minted by the app into its own config.xml; reading it is + how a household's own client authenticates, and the tag endpoints are ordinary app data.""" + api = "v3" + + def _key(self, w): + out = w.guest(f"docker exec {self.name} cat /config/config.xml 2>/dev/null") + m = re.search(r"([0-9a-f]+)", out or "") + return m.group(1) if m else None + + def seed(self, w, sub, say): + if not w.wait_app(sub, "/", want=("200", "302", "401")): + return None + k = self._key(w) + if not k: + self.tried = "read ApiKey from the app's own /config/config.xml — not present yet" + say(f" {self.name}: no ApiKey in config.xml yet") + return None + label = "drill" + secrets.token_hex(4) + rc, code, out = w.app_curl(sub, f"/api/{self.api}/tag", "-H", f"X-Api-Key: {k}", + "-H", "Content-Type: application/json", + data=json.dumps({"label": label}), method="POST") + if code not in ("200", "201", "202"): + self.tried = f"POST /api/{self.api}/tag with the app's own key -> {code}" + say(f" {self.name}: POST tag -> {code} {out[:150]}") + return None + say(f" {self.name}: seeded tag {label}") + return {"label": label, "key": k} + + def verify(self, w, sub, t, say): + k = self._key(w) or t["key"] + rc, code, out = w.app_curl(sub, f"/api/{self.api}/tag", "-H", f"X-Api-Key: {k}") + found = t["label"] in (out or "") + # negative control, EVERY call: a label that cannot exist must read as absent + absent = ("drillnope" + secrets.token_hex(6)) not in (out or "") + if not absent: + say(f" {self.name}: READBACK UNUSABLE — an impossible label read as present") + return None + say(f" {self.name}: readback found={found} (http {code}, control passed)") + return found + + +class Radarr(_Arr): + name = "radarr"; sub = "radarr"; route = "its own /api/v3/tag with the app's own ApiKey" + + +class Sonarr(_Arr): + name = "sonarr"; sub = "sonarr"; route = "its own /api/v3/tag with the app's own ApiKey" + + +# ── kimai — its own console, the route the app documents ───────────────────────────────────────── +class Kimai: + sub = "kimai"; route = "its own `bin/console kimai:user:create`" + + def seed(self, w, sub, say): + u = "drill" + secrets.token_hex(4) + out = _gx(w, "kimai", "/opt/kimai/bin/console", "kimai:user:create", u, + f"{u}@example.invalid", "ROLE_USER", "Drill-" + secrets.token_hex(6) + "!aA") + if "success" not in (out or "").lower() and "created" not in (out or "").lower(): + self.tried = "its own `bin/console kimai:user:create` -> " + (out or "")[:200] + say(f" kimai: console create said: {(out or '')[:200]}") + return None + say(f" kimai: seeded user {u}") + return {"user": u} + + def verify(self, w, sub, t, say): + out = _gx(w, "kimai", "/opt/kimai/bin/console", "kimai:user:list") or "" + found = t["user"] in out + absent = ("nope" + secrets.token_hex(6)) not in out + if not absent: + say(" kimai: READBACK UNUSABLE — an impossible user read as present") + return None + say(f" kimai: readback found={found} (control passed)") + return found + + +# ── gramps-web — its own CLI ───────────────────────────────────────────────────────────────────── +class GrampsWeb: + sub = "gramps"; route = "its own `python3 -m gramps_webapi user add`" + + def seed(self, w, sub, say): + u = "drill" + secrets.token_hex(4) + out = _gx(w, "gramps-web", "python3", "-m", "gramps_webapi", "--config", + "/app/config/config.cfg", "user", "add", u, "Drill-" + secrets.token_hex(6)) + if "error" in (out or "").lower() or "traceback" in (out or "").lower(): + self.tried = "its own `gramps_webapi user add` -> " + (out or "")[:200] + say(f" gramps-web: {(out or '')[:200]}") + return None + say(f" gramps-web: seeded user {u}") + return {"user": u} + + def verify(self, w, sub, t, say): + out = _gx(w, "gramps-web", "python3", "-m", "gramps_webapi", "--config", + "/app/config/config.cfg", "user", "list") or "" + found = t["user"] in out + absent = ("nope" + secrets.token_hex(6)) not in out + if not absent: + say(" gramps-web: READBACK UNUSABLE") + return None + say(f" gramps-web: readback found={found} (control passed)") + return found + + +# ── homebox — its own registration + item API ──────────────────────────────────────────────────── +class Homebox: + sub = "homebox"; route = "its own /api/v1/users/register + /api/v1/locations" + + def seed(self, w, sub, say): + if not w.wait_app(sub, "/", want=("200", "302")): + return None + u = "drill" + secrets.token_hex(4) + "@example.invalid" + pw = "Drill-" + secrets.token_hex(8) + "!aA" + rc, code, out = w.app_curl(sub, "/api/v1/users/register", "-H", "Content-Type: application/json", + data=json.dumps({"name": "drill", "email": u, "password": pw}), + method="POST") + if code not in ("200", "201", "204"): + self.tried = f"POST /api/v1/users/register -> {code} {out[:150]}" + say(f" homebox: register -> {code} {out[:150]}") + return None + rc, code, out = w.app_curl(sub, "/api/v1/users/login", "-H", "Content-Type: application/json", + data=json.dumps({"username": u, "password": pw}), method="POST") + try: + tokv = json.loads(out)["token"] + except Exception: + self.tried = f"POST /api/v1/users/login -> {code} {out[:150]}" + say(f" homebox: login -> {code} {out[:150]}") + return None + name = "drillloc" + secrets.token_hex(4) + rc, code, out = w.app_curl(sub, "/api/v1/locations", "-H", f"Authorization: {tokv}", + "-H", "Content-Type: application/json", + data=json.dumps({"name": name, "description": "drill"}), + method="POST") + if code not in ("200", "201"): + self.tried = f"POST /api/v1/locations -> {code} {out[:150]}" + say(f" homebox: create location -> {code} {out[:150]}") + return None + say(f" homebox: seeded location {name}") + return {"name": name, "tok": tokv, "u": u, "pw": pw} + + def verify(self, w, sub, t, say): + rc, code, out = w.app_curl(sub, "/api/v1/users/login", "-H", "Content-Type: application/json", + data=json.dumps({"username": t["u"], "password": t["pw"]}), + method="POST") + try: + tokv = json.loads(out)["token"] + except Exception: + tokv = t["tok"] + rc, code, out = w.app_curl(sub, "/api/v1/locations", "-H", f"Authorization: {tokv}") + found = t["name"] in (out or "") + absent = ("nope" + secrets.token_hex(6)) not in (out or "") + if not absent: + say(" homebox: READBACK UNUSABLE") + return None + say(f" homebox: readback found={found} (http {code}, control passed)") + return found + + +FIXTURES28 = { + "radarr": Radarr(), "sonarr": Sonarr(), "kimai": Kimai(), + "gramps-web": GrampsWeb(), "homebox": Homebox(), +} + + +# ── apps whose front door is a SIGN-UP or SETUP call ───────────────────────────────────────────── +def _neg(w, sub, path, hdr, say, name): + """The negative control every verify() runs: something that CANNOT exist must read absent.""" + rc, code, out = w.app_curl(sub, path, *hdr) + return ("nope" + secrets.token_hex(6)) not in (out or ""), out, code + + +class Termix: + sub = "termix"; route = "its own /users/create sign-up, then /users/me" + + def seed(self, w, sub, say): + if not w.wait_app(sub, "/", want=("200", "302")): + return None + u = "drill" + secrets.token_hex(4) + pw = "Drill-" + secrets.token_hex(8) + "!aA" + for p in ("/users/create", "/api/users/create", "/users/register"): + rc, code, out = w.app_curl(sub, p, "-H", "Content-Type: application/json", + data=json.dumps({"username": u, "password": pw}), + method="POST") + if code in ("200", "201"): + say(f" termix: seeded user {u} via {p}") + return {"u": u, "pw": pw, "path": p} + self.tried = "POST /users/create, /api/users/create, /users/register — none accepted" + say(f" termix: no sign-up route accepted (last {code} {out[:120]})") + return None + + def verify(self, w, sub, t, say): + rc, code, out = w.app_curl(sub, "/users/login", "-H", "Content-Type: application/json", + data=json.dumps({"username": t["u"], "password": t["pw"]}), + method="POST") + found = code in ("200", "201") and ("token" in (out or "") or t["u"] in (out or "")) + rc2, code2, out2 = w.app_curl(sub, "/users/login", "-H", "Content-Type: application/json", + data=json.dumps({"username": "nope" + secrets.token_hex(6), + "password": t["pw"]}), method="POST") + if code2 in ("200", "201"): + say(" termix: READBACK UNUSABLE — an impossible user logged in") + return None + say(f" termix: readback found={found} (http {code}, control refused as it must)") + return found + + +class Ghost: + sub = "blog"; route = "its own /ghost/api/admin/authentication/setup/" + + def seed(self, w, sub, say): + if not w.wait_app(sub, "/", want=("200", "301", "302")): + return None + title = "Drill-" + secrets.token_hex(6) + u = "drill" + secrets.token_hex(4) + "@example.invalid" + pw = "Drill-" + secrets.token_hex(8) + "aA1" + body = json.dumps({"setup": [{"name": "Drill", "email": u, "password": pw, + "blogTitle": title}]}) + rc, code, out = w.app_curl(sub, "/ghost/api/admin/authentication/setup/", + "-H", "Content-Type: application/json", + "-H", "Accept-Version: v5.0", data=body, method="POST") + if code not in ("200", "201"): + self.tried = f"POST /ghost/api/admin/authentication/setup/ -> {code} {out[:150]}" + say(f" ghost: setup -> {code} {out[:160]}") + return None + say(f" ghost: seeded site title {title}") + return {"title": title, "u": u} + + def verify(self, w, sub, t, say): + rc, code, out = w.app_curl(sub, "/", "-L") + found = t["title"] in (out or "") + absent = ("Drill-nope" + secrets.token_hex(6)) not in (out or "") + if not absent: + say(" ghost: READBACK UNUSABLE") + return None + say(f" ghost: readback found={found} (http {code}, control passed)") + return found + + +class Komga: + sub = "komga"; route = "its own POST /api/v1/claim, then GET /api/v1/users/me" + + def seed(self, w, sub, say): + if not w.wait_app(sub, "/", want=("200", "302", "401")): + return None + u = "drill" + secrets.token_hex(4) + "@example.invalid" + pw = "Drill-" + secrets.token_hex(8) + rc, code, out = w.app_curl(sub, "/api/v1/claim", "-H", f"X-Komga-Email: {u}", + "-H", f"X-Komga-Password: {pw}", method="POST") + if code not in ("200", "201"): + self.tried = f"POST /api/v1/claim -> {code} {out[:150]}" + say(f" komga: claim -> {code} {out[:150]}") + return None + say(f" komga: claimed the server as {u}") + return {"u": u, "pw": pw} + + def verify(self, w, sub, t, say): + import base64 as _b + a = _b.b64encode(f"{t['u']}:{t['pw']}".encode()).decode() + rc, code, out = w.app_curl(sub, "/api/v1/users/me", "-H", f"Authorization: Basic {a}") + found = code == "200" and t["u"] in (out or "") + bad = _b.b64encode(f"nope{secrets.token_hex(6)}:{t['pw']}".encode()).decode() + rc2, code2, _ = w.app_curl(sub, "/api/v1/users/me", "-H", f"Authorization: Basic {bad}") + if code2 == "200": + say(" komga: READBACK UNUSABLE — an impossible user authenticated") + return None + say(f" komga: readback found={found} (http {code}, control refused {code2})") + return found + + +class Immich: + sub = "photos"; route = "its own /api/auth/admin-sign-up, then an album" + + def seed(self, w, sub, say): + if not w.wait_app(sub, "/", want=("200", "302"), tries=90): + return None + u = "drill" + secrets.token_hex(4) + "@example.invalid" + pw = "Drill-" + secrets.token_hex(8) + rc, code, out = w.app_curl(sub, "/api/auth/admin-sign-up", "-H", "Content-Type: application/json", + data=json.dumps({"email": u, "password": pw, "name": "Drill"}), + method="POST") + if code not in ("200", "201"): + self.tried = f"POST /api/auth/admin-sign-up -> {code} {out[:150]}" + say(f" immich: sign-up -> {code} {out[:160]}") + return None + rc, code, out = w.app_curl(sub, "/api/auth/login", "-H", "Content-Type: application/json", + data=json.dumps({"email": u, "password": pw}), method="POST") + try: + at = json.loads(out)["accessToken"] + except Exception: + self.tried = f"POST /api/auth/login -> {code} {out[:150]}" + return None + name = "drillalbum" + secrets.token_hex(4) + rc, code, out = w.app_curl(sub, "/api/albums", "-H", f"Authorization: Bearer {at}", + "-H", "Content-Type: application/json", + data=json.dumps({"albumName": name}), method="POST") + if code not in ("200", "201"): + self.tried = f"POST /api/albums -> {code} {out[:150]}" + say(f" immich: album -> {code} {out[:150]}") + return None + say(f" immich: seeded album {name}") + return {"name": name, "u": u, "pw": pw} + + def verify(self, w, sub, t, say): + rc, code, out = w.app_curl(sub, "/api/auth/login", "-H", "Content-Type: application/json", + data=json.dumps({"email": t["u"], "password": t["pw"]}), + method="POST") + try: + at = json.loads(out)["accessToken"] + except Exception: + say(f" immich: could not log back in (http {code})") + return False + rc, code, out = w.app_curl(sub, "/api/albums", "-H", f"Authorization: Bearer {at}") + found = t["name"] in (out or "") + absent = ("nope" + secrets.token_hex(6)) not in (out or "") + if not absent: + say(" immich: READBACK UNUSABLE") + return None + say(f" immich: readback found={found} (http {code}, control passed)") + return found + + +class _MediaServer: + """jellyfin / emby — the startup wizard IS the front door on a fresh install.""" + def seed(self, w, sub, say): + if not w.wait_app(sub, "/", want=("200", "302"), tries=90): + return None + u = "drill" + secrets.token_hex(4) + pw = "Drill-" + secrets.token_hex(8) + rc, code, out = w.app_curl(sub, "/Startup/User", "-H", "Content-Type: application/json", + data=json.dumps({"Name": u, "Password": pw}), method="POST") + if code not in ("200", "204"): + self.tried = f"POST /Startup/User -> {code} {out[:150]}" + say(f" {self.name}: /Startup/User -> {code} {out[:150]}") + return None + w.app_curl(sub, "/Startup/Complete", method="POST") + say(f" {self.name}: seeded first user {u}") + return {"u": u} + + def verify(self, w, sub, t, say): + rc, code, out = w.app_curl(sub, "/Users/Public") + found = t["u"] in (out or "") + absent = ("nope" + secrets.token_hex(6)) not in (out or "") + if not absent: + say(f" {self.name}: READBACK UNUSABLE") + return None + say(f" {self.name}: readback found={found} (http {code}, control passed)") + return found + + +class Jellyfin(_MediaServer): + name = "jellyfin"; sub = "jellyfin"; route = "its own /Startup/User wizard, then /Users/Public" + + +class Emby(_MediaServer): + name = "emby"; sub = "emby"; route = "its own /Startup/User wizard, then /Users/Public" + + +class NoRoute: + """An app whose only way in is a browser. The fixture RUNS, states what it tried, and returns + None. `inconclusive` with the attempts named is a result; a blank is not.""" + def __init__(self, name, sub, tried): + self.name, self.sub, self.tried = name, sub, tried + self.route = "none — " + tried + + def seed(self, w, sub, say): + w.wait_app(sub, "/", want=("200", "301", "302", "401", "403"), tries=30) + say(f" {self.name}: no non-browser seed route — {self.tried}") + return None + + def verify(self, w, sub, t, say): + return False + + +FIXTURES28.update({ + "termix": Termix(), "ghost": Ghost(), "komga": Komga(), "immich": Immich(), + "jellyfin": Jellyfin(), "emby": Emby(), + "code-server": NoRoute("code-server", "code", "its front door is a browser IDE behind one " + "password; it exposes no data API, and writing a file with docker exec " + "would not be the front door (R-156)"), + "onlyoffice": NoRoute("onlyoffice", "office", "a stateless document server: it holds no " + "household data of its own, so there is nothing to seed"), + "homepage": NoRoute("homepage", "home", "a dashboard rendered from config files in the " + "template; it stores no household data"), + "plex": NoRoute("plex", "plex", "the first-run claim needs a token minted at plex.tv by a " + "real Plex account; no account exists for this venue"), + "outline": NoRoute("outline", "outline", "sign-in requires an external identity provider " + "(OIDC/Slack/Google); no local sign-up route exists"), + "rallly": NoRoute("rallly", "rallly", "sign-in is an e-mail magic link; this venue has no " + "mailbox the harness can read"), +}) diff --git a/documentation/audits/the-28-2026-09-22/mkpromotion.py b/documentation/audits/the-28-2026-09-22/mkpromotion.py new file mode 100644 index 00000000..967faa59 --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/mkpromotion.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 +"""The SECOND promotion list, for the operator. CC promotes nothing. + +A move is proposed only when the walk PROVED it: the update reached `done`, and the data the app +was given came back through the app's own interface afterwards. An edge that ended `done` with no +seed route is NOT proposed — `done` alone says the containers changed, not that the household's +data survived. +""" +import glob, io, json, os +HERE = os.path.dirname(os.path.abspath(__file__)) +recs = {} +for f in glob.glob(os.path.join(HERE, "apps", "*", "verdict.json")): + d = json.load(open(f, encoding="utf-8")); recs[d["app"]] = d + +move, hold, noedge = [], [], [] +for a, d in sorted(recs.items()): + e = d.get("edge") + if not e: + noedge.append(a); continue + fr, to = e["from"].rsplit(":", 1)[1], e["to"].rsplit(":", 1)[1] + if d["verdict"] == "proven" and d["seed_read_after_update"]: + move.append((a, fr, to, d.get("duration_s"), d.get("migration_observed"))) + elif d.get("update_phase_final") == "done": + hold.append((a, fr, to, "the update reached `done`, but this app has no non-browser data " + "route, so nothing proves the household's data survived it")) + else: + hold.append((a, fr, to, f"the update ended `{d.get('update_phase_final')}` — " + + ("; ".join(d["notes"]) or "see the record"))) + +out = ["## The second promotion list — for the operator, not for me\n", + "**CC promotes nothing.** These are proposals with the evidence beside them.\n", + f"### Proposed to move ({len(move)})\n", + "| app | move | update took | its own migration line |", "|---|---|---|---|"] +for a, fr, to, s, mig in move: + out.append("| `%s` | `%s` → `%s` | %s s | %s |" % ( + a, fr, to, s, + # a literal `|` inside a cell ends it — the migration lines carry them + ("yes — `" + mig[:90].replace("|", "\\|").replace("`", "'") + "`") if mig + else "none printed")) +out += [f"\n### Must NOT move, with why ({len(hold)})\n", "| app | edge | why not |", "|---|---|---|"] +for a, fr, to, why in hold: + out.append("| `%s` | `%s` → `%s` | %s |" % (a, fr, to, why)) +out.append(f"\n**No upstream edge tonight, so nothing to propose ({len(noedge)}):** " + + ", ".join("`%s`" % a for a in noedge) + ".\n") +io.open(os.path.join(HERE, "BODY-promotion.md"), "w", encoding="utf-8").write("\n".join(out) + "\n") +print("\n".join(out[:8])) +print("...", len(move), "proposed,", len(hold), "held,", len(noedge), "no edge") diff --git a/documentation/audits/the-28-2026-09-22/mkrotation.py b/documentation/audits/the-28-2026-09-22/mkrotation.py new file mode 100644 index 00000000..e249609d --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/mkrotation.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +"""Rewrite the rotation file's line for each of the 28, from its own verdict record. + +A tick means the app was WALKED FULLY — deployed, backed up, restored, removed clean. An app whose +data half is `inconclusive` is still ticked when the rest of the walk completed, and the line SAYS +what could not be judged; an app that could not be deployed is not ticked. +""" +import glob, io, json, os, re +HERE = os.path.dirname(os.path.abspath(__file__)) +ROT = "/mnt/5_hdd/felhom.eu/git/felhom.eu/documentation/runbooks/nightly-rotation.md" +D = "2026-09-22" +EV = "`audits/DRILL-the-28-2026-09-22.md`" + +recs = {} +for f in glob.glob(os.path.join(HERE, "apps", "*", "verdict.json")): + d = json.load(open(f, encoding="utf-8")) + recs[d["app"]] = d + + +def line(a, d): + if not d["deployed"]: + why = "; ".join(d["notes"]) or "deploy refused" + return (f"- [ ] {a} — NOT A FULL WALK; attempted {D} (the 28, scratch guest 9202): " + f"**could not be deployed** — {why}. {EV}.") + bits = ["install", f"front door {d.get('front_door_after_deploy',{}).get('code','?')}"] + if d["seed_read_before"]: + bits.append(f"seeded through {d.get('seed_route')} and read back") + else: + bits.append("NO non-browser data route (" + ( + "; ".join(n for n in d["notes"] if "route" in n) or "recorded, not faked") + ")") + bits.append("„Mentés most\"") + e = d.get("edge") + if e: + bits.append("real upstream Update %s->%s (%s)" % ( + e["from"].rsplit(":", 1)[1], e["to"].rsplit(":", 1)[1], + d.get("update_phase_final"))) + if d.get("migration_observed"): + bits.append("own migration line quoted") + if d["seed_read_after_update"]: + bits.append("read back after the update") + else: + bits.append("no upstream edge tonight") + bits.append("restore " + str(d.get("restore_verdict"))) + if d["seed_read_after_restore"]: + bits.append("read back after the restore") + bits.append("removed " + ("clean" if d.get("removed_clean") else "**with a leftover container**")) + tick = "x" if (d["deployed"] and d.get("removed_clean")) else " " + pre = "" if tick == "x" else "NOT A FULL WALK; attempted " + return f"- [{tick}] {a} — {pre}{D} (the 28, scratch guest 9202): " + ", ".join(bits) + f". {EV}." + + +t = io.open(ROT, encoding="utf-8").read() +n = 0 +for a, d in sorted(recs.items()): + pat = re.compile(r'^- \[[ x]\] ' + re.escape(a) + r'( .*)?$', re.M) + if pat.search(t): + t = pat.sub(lambda m: line(a, d).replace("\\", "\\\\"), t, count=1) + n += 1 + else: + print(" NO EXISTING LINE for", a) +io.open(ROT, "w", encoding="utf-8").write(t) +print(f"rewrote {n} rotation lines") diff --git a/documentation/audits/the-28-2026-09-22/mktable.py b/documentation/audits/the-28-2026-09-22/mktable.py new file mode 100644 index 00000000..3faf8b8d --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/mktable.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python3 +"""Build the report's table FROM the verdict records, so it cannot drift from the evidence.""" +import glob, json, io, os +HERE = os.path.dirname(os.path.abspath(__file__)) +THE28 = ("calcom calibre-web claper code-server crafty-controller emby ghost gokapi gramps-web " + "homebox homepage immich jellyfin kimai komga onlyoffice outline paperless-ngx plant-it " + "plex radarr rallly recipe-importer seerr sonarr sparkyfitness termix wanderer").split() + +# CLASSES READ FROM `07-backup-architecture.md` §6.2, not re-derived tonight (the brief says so, +# and re-deriving is exactly how the brief's own list went wrong). A = at least one readable file +# leg; B = data entirely in named volumes. Of the 28, four are A and the rest are B. +CLASS_A = {"calibre-web", "immich", "komga", "paperless-ngx"} +# which apps carry a database/search SERVICE of their own, read from each compose file +ENGINES = {"calcom": "postgres", "claper": "postgres", "outline": "postgres+redis", + "paperless-ngx": "postgres+redis", "rallly": "postgres", "sparkyfitness": "postgres", + "kimai": "mariadb", "immich": "postgres+redis", "wanderer": "meilisearch"} + + +def klass(a): + c = "A file-leg" if a in CLASS_A else "B volumes-only" + return c + (" + " + ENGINES[a] if a in ENGINES else "") + + +recs = {} +for f in glob.glob(os.path.join(HERE, "apps", "*", "verdict.json")): + d = json.load(open(f, encoding="utf-8")) + recs[d["app"]] = d + +rows, tot = [], {} +rows.append("| app | class | deployed | seeded | backup | edge | update | restore | removed clean | s | evidence |") +rows.append("|---|---|---|---|---|---|---|---|---|---|---|") +for a in THE28: + d = recs.get(a) + if not d: + rows.append(f"| `{a}` | — | **NOT WALKED** | — | — | — | — | — | — | — | — |") + tot["not-walked"] = tot.get("not-walked", 0) + 1 + continue + e = d.get("edge") + edge = (f"`{e['from'].rsplit(':',1)[1]}` → `{e['to'].rsplit(':',1)[1]}`" if e else "none upstream") + seeded = ("yes" if d["seed_read_before"] else + ("no route" if d.get("seed_route") in (None, "none written") or not d["deployed"] + else "route failed")) + upd = d.get("update_phase_final") or ("—" if not e else "not reached") + bk = (str(d["backup"]["snapshots_offered"]) + " copy" if d.get("backup") else "—") + rows.append("| `%s` | %s | %s | %s | %s | %s | %s | %s | %s | %s | `apps/%s/` |" % ( + a, klass(a), "yes" if d["deployed"] else "**no**", seeded, bk, edge, upd, + d.get("restore_verdict", "—"), + {True: "yes", False: "**no**", None: "—"}.get(d.get("removed_clean"), "—"), + d.get("duration_s"), a)) + tot[d["verdict"]] = tot.get(d["verdict"], 0) + 1 + +out = "\n".join(rows) + "\n\n**Totals:** " + " · ".join( + f"**{v}** {k}" for k, v in sorted(tot.items(), key=lambda kv: -kv[1])) + \ + f" — {sum(tot.values())} of 28 recorded.\n" +io.open(os.path.join(HERE, "TABLE.md"), "w", encoding="utf-8").write(out) +print(out) diff --git a/documentation/audits/the-28-2026-09-22/rewalk.sh b/documentation/audits/the-28-2026-09-22/rewalk.sh new file mode 100755 index 00000000..b72cfa33 --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/rewalk.sh @@ -0,0 +1,11 @@ +#!/bin/bash +# Serial. Matching on the FULL python invocation, not a bracketed fragment that other command +# lines can contain — the previous waiter deadlocked on exactly that. +cd "$(dirname "$0")" +for app in $(cat REWALK.txt); do + echo "$(date +%H:%M:%S) re-walking $app" + python3 -u walk28.py "$app" > "apps/$app.rewalk.out" 2>&1 +done +echo "$(date +%H:%M:%S) re-walks done — R-630 side job" +python3 -u sidejob_630.py > sidejobs/r630.out 2>&1 +echo "$(date +%H:%M:%S) ALL FIELD WORK DONE" diff --git a/documentation/audits/the-28-2026-09-22/sidejob_630.py b/documentation/audits/the-28-2026-09-22/sidejob_630.py new file mode 100644 index 00000000..140bbb92 --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/sidejob_630.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +"""R-630 — what does the guarded Update do when NO probe answers, because none was ever built? + +`findProbeContainer` (healthprobe.go:297) takes the container whose name EQUALS the stack name, +else the first with it as a PREFIX. paperless-ngx's containers are `paperless-webserver`, +`paperless-postgres`, `paperless-redis` — none matches `paperless-ngx`. The function returns "", +`:62` counts the stack in `skippedNoContainer` and continues, so no probe is ever built. + +The open question this measures, and it is the one that decides which fix is right: +**the `verifying` phase waits on that probe. With no probe at all, does it pass at once, wait out +`update.health_timeout`, or HOLD?** + +No edge exists upstream for paperless-ngx tonight, so this presses the Update on the SAME version — +which is exactly what a household does when they press it on an up-to-date app, and it still walks +the whole phase machine. That difference is stated in the record rather than glossed. +""" +import json, os, sys, time +HERE = os.path.dirname(os.path.abspath(__file__)) +sys.path.insert(0, os.path.join(os.path.dirname(HERE), "update-night-2026-09-21")) +import walk as w # noqa: E402 + +APP, SUB = "paperless-ngx", "paperless" +rec = {"app": APP, "question": "what does `verifying` do with no probe target at all?"} + +w.login() +try: + rec["deployed"] = w.deploy(APP, SUB) + w.wait_app(SUB, "/", tries=60) + st = w.stack(APP) + rec["controller_state"] = st.get("state") + rec["front_door"] = w.app_curl(SUB, "/")[1] + rec["containers"] = w.guest( + "docker ps --format '{{.Names}}|{{.Status}}' | grep -i paperless").strip().split("\n") + # what the HEALTH page says for a stack the prober skips + code, d = w.ctl("GET", f"/api/stacks/{APP}") + dd = (d.get("data") or {}) + rec["health_detail"] = dd.get("health") or dd.get("health_checks") or dd.get("health_detail") + rec["state"] = dd.get("state") + # the app page, as the household reads it + import re + for lang, suf in (("hu", ""), ("en", "?lang=en")): + h = w.page(f"/apps/{APP}{suf}") + h = re.sub(r"|", "", h, flags=re.S) + txt = re.sub(r"\s+", " ", re.sub(r"<[^>]+>", " ", h)) + i = txt.find("←") + rec[f"page_{lang}"] = txt[i:i + 200] if i >= 0 else None + # THE MEASUREMENT: press Update and time every phase + t0 = time.time() + rec["phases"] = w.press_update(APP) + rec["update_wall_s"] = round(time.time() - t0, 1) + rec["state_after"] = w.stack(APP).get("state") + rec["front_door_after"] = w.app_curl(SUB, "/")[1] + print(json.dumps(rec, ensure_ascii=False, indent=2)[:2500]) +finally: + try: + w.remove(APP) + except Exception as e: + rec["remove_error"] = str(e) + json.dump(rec, open(os.path.join(HERE, "sidejobs", "r630.json"), "w"), + ensure_ascii=False, indent=2) + open(os.path.join(HERE, "sidejobs", "r630-log.txt"), "w").write("\n".join(w.LOG) + "\n") + print("\nwritten sidejobs/r630.json") diff --git a/documentation/audits/the-28-2026-09-22/sidejob_631.py b/documentation/audits/the-28-2026-09-22/sidejob_631.py new file mode 100644 index 00000000..7b92395f --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/sidejob_631.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python3 +"""R-631 — one LIVE reading for each template the static probe gate cannot judge. + +The gate compares the `.felhom.yml` probe against the same service's compose healthcheck. For five +templates there is no such oracle (the healthcheck runs inside a script, or there is none), and for +one (home-assistant) the paths differ but the check type cannot fail on it. A static rule cannot +settle any of them. This asks the CONTAINER what it actually listens on, which can. + +Method, per app: deploy, wait for the front door, then inside the probed container read +`ss -ltn` (or `/proc/net/tcp` when `ss` is absent — busybox images have neither `ss` nor `netstat`), +and compare with the probe. Remove afterwards. +""" +import json, os, sys, time +HERE = os.path.dirname(os.path.abspath(__file__)) +sys.path.insert(0, os.path.join(os.path.dirname(HERE), "update-night-2026-09-21")) +import walk as w # noqa: E402 +import yaml # noqa: E402 + +T = "/mnt/5_hdd/felhom.eu/git/app-catalog-felhom.eu/templates" +APPS = { # app: (subdomain used by its fixture/template, the container the controller probes) + # crafty-controller is in tonight's walk queue and would collide; its reading is taken from + # its own walk instead, and the record says so rather than leaving a blank. + "mealie": ("mealie", "mealie"), + "uptime-kuma": ("uptime", "uptime-kuma"), + "vikunja": ("vikunja", "vikunja"), + "home-assistant": ("ha", "home-assistant"), +} + +LISTEN = r'''C=%s +if docker exec $C sh -c 'command -v ss' >/dev/null 2>&1; then + echo "== ss -ltn"; docker exec $C ss -ltn 2>&1 | head -20 +elif docker exec $C sh -c 'command -v netstat' >/dev/null 2>&1; then + echo "== netstat -ltn"; docker exec $C netstat -ltn 2>&1 | head -20 +else + echo "== /proc/net/tcp (no ss, no netstat in the image) — local_address is hex ip:port" + docker exec $C sh -c 'cat /proc/net/tcp /proc/net/tcp6 2>/dev/null' | awk '$4=="0A"{print $2}' | sort -u | head -20 +fi''' + + +def hexports(txt): + out = set() + for line in txt.split("\n"): + line = line.strip() + if ":" in line and len(line.split(":")[-1]) == 4: + try: + out.add(int(line.split(":")[-1], 16)) + except ValueError: + pass + return sorted(out) + + +def main(): + w.login() + res = {} + for app, (sub, cont) in APPS.items(): + print(f"\n==== {app}") + fy = yaml.safe_load(open(f"{T}/{app}/.felhom.yml")) + probe = (fy.get("healthcheck") or {}).get("checks") + r = {"app": app, "probe": probe, "probed_container": cont} + try: + ok = w.deploy(app, sub) + r["deployed"] = ok + if ok: + w.wait_app(sub, "/", tries=40) + listen = w.guest(LISTEN % cont) + r["listen_raw"] = listen.strip() + r["listen_ports_from_proc"] = hexports(listen) + st = w.stack(app) + r["controller_state"] = st.get("state") + r["front_door"] = w.app_curl(sub, "/")[1] + # what the probe dials, asked from INSIDE the compose network + for c in (probe or []): + p, path = c.get("port"), c.get("path") or "/" + probe_try = w.guest( + f"docker run --rm --network container:{cont} curlimages/curl:8.11.1 " + f"-s -o /dev/null -w '%{{http_code}}' --max-time 5 " + f"http://127.0.0.1:{p}{path} 2>&1 | tail -1") + r.setdefault("probe_dial", []).append( + {"port": p, "path": path, "type": c.get("type"), + "http_code": probe_try.strip()[:20]}) + print(json.dumps({k: r[k] for k in + ("probe", "controller_state", "front_door", "probe_dial") + if k in r}, ensure_ascii=False, indent=2)) + print(r["listen_raw"][:600]) + except Exception as e: + r["error"] = f"{type(e).__name__}: {e}" + print(" !!", r["error"]) + finally: + try: + w.remove(app) + except Exception as e: + r["remove_error"] = str(e) + res[app] = r + json.dump(res, open(os.path.join(HERE, "sidejobs", "r631.json"), "w"), + ensure_ascii=False, indent=2) + print("\nwritten sidejobs/r631.json") + + +main() diff --git a/documentation/audits/the-28-2026-09-22/sidejobs/r630-controller-words.txt b/documentation/audits/the-28-2026-09-22/sidejobs/r630-controller-words.txt new file mode 100644 index 00000000..ee5e8574 --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/sidejobs/r630-controller-words.txt @@ -0,0 +1,18 @@ +=== R-630 ANSWERED — the controller in its own words === +2026/09/22 11:48:50 manager.go:369: [INFO] [stacks] userdata belt: drive /mnt/felhom-drives/scratch_hdd/userdata/paperless-ngx not mounted — skipping ensure (held by drive gate) +2026/09/22 11:51:54 manager.go:369: [INFO] [stacks] userdata belt: drive /mnt/felhom-drives/scratch_hdd/userdata/paperless-ngx not mounted — skipping ensure (held by drive gate) +2026/09/22 13:38:16 manager.go:369: [INFO] [stacks] userdata belt: drive /mnt/felhom-drives/scratch_hdd/userdata/paperless-ngx not mounted — skipping ensure (held by drive gate) +2026/09/22 13:39:51 manager.go:369: [INFO] [stacks] userdata belt: drive /mnt/felhom-drives/scratch_hdd/userdata/paperless-ngx not mounted — skipping ensure (held by drive gate) +2026/09/22 13:46:20 manager.go:369: [INFO] [stacks] userdata belt: drive /mnt/felhom-drives/scratch_hdd/userdata/paperless-ngx not mounted — skipping ensure (held by drive gate) +2026/09/22 13:48:34 update.go:917: [INFO] [stacks] update paperless-ngx: phase checking +2026/09/22 13:48:34 update.go:917: [INFO] [stacks] update paperless-ngx: phase safety-dump +2026/09/22 13:48:35 update.go:917: [INFO] [stacks] update paperless-ngx: phase pinning +2026/09/22 13:48:35 update.go:917: [INFO] [stacks] update paperless-ngx: phase pulling +2026/09/22 13:48:36 update.go:917: [INFO] [stacks] update paperless-ngx: phase starting +2026/09/22 13:48:36 manager.go:369: [INFO] [stacks] userdata belt: drive /mnt/felhom-drives/scratch_hdd/userdata/paperless-ngx not mounted — skipping ensure (held by drive gate) +2026/09/22 13:48:37 update.go:917: [INFO] [stacks] update paperless-ngx: phase verifying +2026/09/22 13:53:39 update.go:722: [ERROR] [stacks] update paperless-ngx FAILED after the new version was started: not healthy: not healthy within 5m0s (last: no probe container) — stopping and HOLDING the app; the pin stays on the new version (its migration may have run) +2026/09/22 13:53:46 update_guard.go:531: [WARN] [backup] paperless-ngx is HELD STOPPED after a failed update (restore point: tier 1 "saját meghajtó", 2026-09-22T13:47:37Z; holds: "csak a beállításokat és az adatbázist tartalmazza, a fájlokat nem") + +=== and the probe loop never built one for it === + diff --git a/documentation/audits/the-28-2026-09-22/sidejobs/r630-log.txt b/documentation/audits/the-28-2026-09-22/sidejobs/r630-log.txt new file mode 100644 index 00000000..d8a81b37 --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/sidejobs/r630-log.txt @@ -0,0 +1,15 @@ +15:46:19 [1] made the drive paths this app requires: ['/mnt/felhom-drives/scratch_hdd/userdata/paperless-ngx'] +15:46:19 [1] required fields filled beyond DOMAIN/SUBDOMAIN: ['PAPERLESS_ADMIN_PASSWORD', 'HDD_PATH'] +15:46:20 [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'} +15:48:30 [1] deployed, controller state=running, pinned={'paperless-postgres': 'postgres:16-alpine', 'paperless-redis': 'redis:7-alpine', 'paperless-webserver': 'ghcr.io/paperless-ngx/paperless-ngx:2.20.15'} +15:48:34 [6] Update -> 202 {'ok': True, 'data': {'accepted': True, 'completed': False}, 'message': 'Frissítés elindult – az állapot a kártyán követhető'} +15:48:34 + 0.0s phase=safety-dump label=Adatbázis pillanatkép… err=None hold=None +15:48:35 + 1.1s phase=pulling label=Új verzió letöltése… err=None hold=None +15:48:36 + 2.1s phase=starting label=Indítás az új verzióval… err=None hold=None +15:48:37 + 3.1s phase=verifying label=Működés ellenőrzése… err=None hold=None +15:53:47 + 313.0s phase=failed label=A frissítés nem sikerült err=A(z) paperless-ngx frissítése 2026-09-22 15:53-kor nem sikerült, és az alkalmazás nem indult el az új verzióval. Az alkalmazás biztonsági okból leállítva marad, hogy az adatai ne sérüljenek. Visszaállítható a Mentések oldalon ebből a biztonsági mentésből: saját meghajtó, 2026-09-22 15:47 — ez a másolat csak a beállításokat és az adatbázist tartalmazza, a fájlokat nem. hold=A(z) paperless-ngx frissítése 2026-09-22 15:53-kor nem sikerült, és az alkalmazás nem indult el az új verzióval. Az alkalmazás biztonsági okból leállítva marad, hogy az adatai ne sérüljenek. Visszaállítható a Mentések oldalon ebből a biztonsági mentésből: saját meghajtó, 2026-09-22 15:47 — ez a másolat csak a beállításokat és az adatbázist tartalmazza, a fájlokat nem. +15:53:48 [X] stop -> 200 {'ok': True, 'message': 'Stack paperless-ngx stop completed'} +15:53:53 [X] remove (with drive data) -> 409 {'ok': False, 'error': 'A(z) /mnt/felhom-drives/scratch_hdd/userdata/paperless-ngx tárhely jelenleg nem elérhető — az alkalmazás nem távolítható el, amíg a megh +15:53:53 [X] refused because the drive path cannot be resolved (R-442, fail-closed and right) — removing the app and KEEPING the drive data instead +15:53:53 [X] remove (keeping drive data) -> 200 {'ok': True, 'data': {'removed': 'paperless-ngx', 'volumes_removed': ['paperless-ngx_paperless_data', 'paperless-ngx_paperless_postgres_data', 'paperless-ngx_pa +15:54:01 [X] after remove: deployed=False leftovers='/opt/docker/stacks/paperless-ngx' diff --git a/documentation/audits/the-28-2026-09-22/sidejobs/r630.json b/documentation/audits/the-28-2026-09-22/sidejobs/r630.json new file mode 100644 index 00000000..dff38759 --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/sidejobs/r630.json @@ -0,0 +1,70 @@ +{ + "app": "paperless-ngx", + "question": "what does `verifying` do with no probe target at all?", + "deployed": true, + "controller_state": "running", + "front_door": "302", + "containers": [ + "paperless-webserver|Up About a minute (healthy)", + "paperless-postgres|Up About a minute (healthy)", + "paperless-redis|Up About a minute (healthy)" + ], + "health_detail": null, + "state": "running", + "page_hu": "← Alkalmazások Paperless-ngx Fut Megnyitás ↗ Napló Exportálás Beállítások Hiányzó tárhely: paperless-ngx Ennek az alkalmazásnak az adattárolója jelenleg nem elérhető, ezért le van állítva. Csatlakozta", + "page_en": "← Apps Paperless-ngx Running Open ↗ Log Export Settings Missing storage: paperless-ngx This app’s data storage is not available right now, so the app is stopped. Connect the drive again, or move the d", + "phases": { + "accepted": true, + "http": "202", + "phases": [ + { + "t": 0.0, + "phase": "safety-dump", + "label": "Adatbázis pillanatkép…", + "updating": true, + "error": null, + "hold": null + }, + { + "t": 1.1, + "phase": "pulling", + "label": "Új verzió letöltése…", + "updating": true, + "error": null, + "hold": null + }, + { + "t": 2.1, + "phase": "starting", + "label": "Indítás az új verzióval…", + "updating": true, + "error": null, + "hold": null + }, + { + "t": 3.1, + "phase": "verifying", + "label": "Működés ellenőrzése…", + "updating": true, + "error": null, + "hold": null + }, + { + "t": 313.0, + "phase": "failed", + "label": "A frissítés nem sikerült", + "updating": false, + "error": "A(z) paperless-ngx frissítése 2026-09-22 15:53-kor nem sikerült, és az alkalmazás nem indult el az új verzióval. Az alkalmazás biztonsági okból leállítva marad, hogy az adatai ne sérüljenek. Visszaállítható a Mentések oldalon ebből a biztonsági mentésből: saját meghajtó, 2026-09-22 15:47 — ez a másolat csak a beállításokat és az adatbázist tartalmazza, a fájlokat nem.", + "hold": "A(z) paperless-ngx frissítése 2026-09-22 15:53-kor nem sikerült, és az alkalmazás nem indult el az új verzióval. Az alkalmazás biztonsági okból leállítva marad, hogy az adatai ne sérüljenek. Visszaállítható a Mentések oldalon ebből a biztonsági mentésből: saját meghajtó, 2026-09-22 15:47 — ez a másolat csak a beállításokat és az adatbázist tartalmazza, a fájlokat nem." + } + ], + "duration_s": 313.1, + "final_phase": "failed", + "update_error": "A(z) paperless-ngx frissítése 2026-09-22 15:53-kor nem sikerült, és az alkalmazás nem indult el az új verzióval. Az alkalmazás biztonsági okból leállítva marad, hogy az adatai ne sérüljenek. Visszaállítható a Mentések oldalon ebből a biztonsági mentésből: saját meghajtó, 2026-09-22 15:47 — ez a másolat csak a beállításokat és az adatbázist tartalmazza, a fájlokat nem.", + "hold_reason": "A(z) paperless-ngx frissítése 2026-09-22 15:53-kor nem sikerült, és az alkalmazás nem indult el az új verzióval. Az alkalmazás biztonsági okból leállítva marad, hogy az adatai ne sérüljenek. Visszaállítható a Mentések oldalon ebből a biztonsági mentésből: saját meghajtó, 2026-09-22 15:47 — ez a másolat csak a beállításokat és az adatbázist tartalmazza, a fájlokat nem.", + "state": "stopped" + }, + "update_wall_s": 313.4, + "state_after": "stopped", + "front_door_after": "404" +} \ No newline at end of file diff --git a/documentation/audits/the-28-2026-09-22/sidejobs/r631.json b/documentation/audits/the-28-2026-09-22/sidejobs/r631.json new file mode 100644 index 00000000..7886fe42 --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/sidejobs/r631.json @@ -0,0 +1,99 @@ +{ + "mealie": { + "app": "mealie", + "probe": [ + { + "type": "tcp", + "port": 9000 + } + ], + "probed_container": "mealie", + "deployed": true, + "listen_raw": "== ss -ltn\nState Recv-Q Send-Q Local Address:Port Peer Address:Port\nLISTEN 0 2048 0.0.0.0:9000 0.0.0.0:* \nLISTEN 0 4096 127.0.0.11:33937 0.0.0.0:*", + "listen_ports_from_proc": [], + "controller_state": "running", + "front_door": "200", + "probe_dial": [ + { + "port": 9000, + "path": "/", + "type": "tcp", + "http_code": "200" + } + ] + }, + "uptime-kuma": { + "app": "uptime-kuma", + "probe": [ + { + "type": "http", + "port": 3001 + } + ], + "probed_container": "uptime-kuma", + "deployed": true, + "listen_raw": "== ss -ltn\nState Recv-Q Send-Q Local Address:Port Peer Address:PortProcess\nLISTEN 0 4096 127.0.0.11:42815 0.0.0.0:* \nLISTEN 0 511 *:3001 *:*", + "listen_ports_from_proc": [], + "controller_state": "running", + "front_door": "302", + "probe_dial": [ + { + "port": 3001, + "path": "/", + "type": "http", + "http_code": "302" + } + ] + }, + "vikunja": { + "app": "vikunja", + "probe": [ + { + "type": "api", + "port": 3456, + "path": "/api/v1/info", + "expect": { + "status": 200 + } + } + ], + "probed_container": "vikunja", + "deployed": true, + "listen_raw": "== /proc/net/tcp (no ss, no netstat in the image) — local_address is hex ip:port", + "listen_ports_from_proc": [], + "controller_state": "running", + "front_door": "200", + "probe_dial": [ + { + "port": 3456, + "path": "/api/v1/info", + "type": "api", + "http_code": "200" + } + ] + }, + "home-assistant": { + "app": "home-assistant", + "probe": [ + { + "type": "api", + "port": 8123, + "path": "/api/" + } + ], + "probed_container": "home-assistant", + "deployed": true, + "listen_raw": "== netstat -ltn\nActive Internet connections (only servers)\nProto Recv-Q Send-Q Local Address Foreign Address State \ntcp 0 0 127.0.0.1:18554 0.0.0.0:* LISTEN \ntcp 0 0 127.0.0.11:40195 0.0.0.0:* LISTEN \ntcp 0 0 0.0.0.0:8123 0.0.0.0:* LISTEN \ntcp6 0 0 :::18555 :::* LISTEN \ntcp6 0 0 :::8123 :::* LISTEN", + "listen_ports_from_proc": [], + "controller_state": "running", + "front_door": "302", + "probe_dial": [ + { + "port": 8123, + "path": "/api/", + "type": "api", + "http_code": "401" + } + ] + } +} \ No newline at end of file diff --git a/documentation/audits/the-28-2026-09-22/teardown.py b/documentation/audits/the-28-2026-09-22/teardown.py new file mode 100644 index 00000000..d32c18d3 --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/teardown.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python3 +"""Teardown, three layers plus Gitea — and every claim READ BACK, never assumed.""" +import json, os, subprocess, sys +HERE = os.path.dirname(os.path.abspath(__file__)) +sys.path.insert(0, os.path.join(os.path.dirname(HERE), "update-night-2026-09-21")) +import walk as w # noqa: E402 + +LIVE = "1ad1f34b6e51843851839bdd18154a6603c6e590" +rec = {} +w.login() + +# ── layer 1: the machine ───────────────────────────────────────────────────────────────────────── +print("== 9202 back on the LIVE catalog") +subprocess.run([sys.executable, os.path.join(os.path.dirname(HERE), + "update-night-2026-09-21", "repoint_drill.py"), "live"], + cwd=os.path.join(os.path.dirname(HERE), "update-night-2026-09-21")) +rec["machine"] = w.guest('''set +e +echo "== controller.yaml git block (token redacted by the reader, not by us):" +grep -A6 '^git:' /var/lib/docker/volumes/felhom-controller-data/_data/controller.yaml +echo "== the CACHE is what decides which remote is followed (R-615):" +C=/var/lib/docker/volumes/felhom-controller-data/_data/data/catalog-cache +git -C $C remote -v | sed 's#://[^@]*@#://#' | head -1 +git -C $C log --oneline -1 +echo "== containers still running (expect exactly three):" +docker ps --format '{{.Names}}' +echo "== any container from tonight that should be gone:" +docker ps -a --format '{{.Names}}|{{.Status}}' | grep -vE '^(felhom-controller|filebrowser|traefik)\\|' || echo NONE +echo "== any app.yaml left (expect none):" +ls /opt/docker/stacks/*/app.yaml 2>/dev/null || echo NONE +echo "== drill images (expect none):" +docker images --format '{{.Repository}}:{{.Tag}}' | grep -E '^(localhost:5000|drill/)' || echo NONE +echo "== disk:" +df -h / | tail -1''') +print(rec["machine"]) + +# ── layer 2: the host ──────────────────────────────────────────────────────────────────────────── +print("\n== demo-hp host") +rec["host"] = subprocess.run(["ssh", "-o", "ConnectTimeout=15", "demo-hp", + "LC_ALL=C pct list; LC_ALL=C pvesm status"], + capture_output=True, text=True).stdout +print(rec["host"]) + +# ── layer 3: the hub ───────────────────────────────────────────────────────────────────────────── +rec["hub"] = "nothing provisioned, nothing changed — 9202 runs hub.enabled: false (R-620)" + +# ── Gitea ──────────────────────────────────────────────────────────────────────────────────────── +print("\n== Gitea") +subprocess.run(["git", "-C", "/mnt/5_hdd/felhom.eu/drill/app-catalog-drill", "fetch", "-q", "live", "main"]) +subprocess.run(["git", "-C", "/mnt/5_hdd/felhom.eu/drill/app-catalog-drill", "reset", "-q", "--hard", "live/main"]) +subprocess.run(["git", "-C", "/mnt/5_hdd/felhom.eu/drill/app-catalog-drill", "push", "-qf", "origin", "main"]) +rec["drill_head"] = subprocess.run(["git", "-C", "/mnt/5_hdd/felhom.eu/drill/app-catalog-drill", + "rev-parse", "HEAD"], capture_output=True, text=True).stdout.strip() +# THE DIFF THE METHOD REQUIRES: every image: line on the live catalog identical to the baseline +d = subprocess.run(["git", "-C", "/mnt/5_hdd/felhom.eu/git/app-catalog-felhom.eu", "diff", + LIVE, "origin/main", "--", "templates/"], capture_output=True, text=True).stdout +rec["live_template_diff_lines"] = len([l for l in d.split("\n") if l.startswith(("+", "-")) + and not l.startswith(("+++", "---"))]) +rec["live_image_line_diff"] = [l for l in d.split("\n") + if l.startswith(("+", "-")) and "image:" in l] +print("live catalog templates/ diff vs baseline:", rec["live_template_diff_lines"], "lines;", + "image: lines changed:", rec["live_image_line_diff"] or "NONE") +json.dump(rec, open(os.path.join(HERE, "teardown", "teardown.json"), "w"), + ensure_ascii=False, indent=2) +print("\nwritten teardown/teardown.json") diff --git a/documentation/audits/the-28-2026-09-22/teardown/manual-cleanup.txt b/documentation/audits/the-28-2026-09-22/teardown/manual-cleanup.txt new file mode 100644 index 00000000..b96e7e89 --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/teardown/manual-cleanup.txt @@ -0,0 +1,16 @@ +:6: SyntaxWarning: invalid escape sequence '\|' +=== THE PRODUCT CANNOT CLEAR THESE. A SHELL HAD TO. That is R-633 and R-634. === +--- before: +termix|Up 2 hours (healthy) +gokapi|Restarting (1) 40 seconds ago +-rw------- 1 root root 845 Sep 22 13:05 /opt/docker/stacks/sparkyfitness/app.yaml +--- clearing BY NAME (never a prune): +termix +gokapi +removed sparkyfitness/app.yaml +--- after: +felhom-controller filebrowser traefik +no app.yaml anywhere - clean +--- disk: +/dev/loop0 32G 1.9G 28G 7% / + diff --git a/documentation/audits/the-28-2026-09-22/teardown/negative-control-end.txt b/documentation/audits/the-28-2026-09-22/teardown/negative-control-end.txt new file mode 100644 index 00000000..2f9c2beb --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/teardown/negative-control-end.txt @@ -0,0 +1,12 @@ +=== NEGATIVE CONTROL, end of night (compare with 00-negative-control-baseline.txt) +live catalog origin/main: 1ad1f34b6e51843851839bdd18154a6603c6e590 +--- demo-hp guest 9201 cache: +origin https://gitea.dooplex.hu/admin/app-catalog-felhom.eu.git (fetch) +1ad1f34 gates: probe-matches-compose runs in CI's no-PyYAML mode (R-618) +--- demo-felhom guest 9201 cache: +origin https://gitea.dooplex.hu/admin/app-catalog-felhom.eu.git (fetch) +1ad1f34 gates: probe-matches-compose runs in CI's no-PyYAML mode (R-618) +--- drill CI jobs (47 at the start; a push all night must not have added one): +drill CI jobs total_count = 47 +--- drill repo reset to live: +1ad1f34b6e51 diff --git a/documentation/audits/the-28-2026-09-22/teardown/teardown.json b/documentation/audits/the-28-2026-09-22/teardown/teardown.json new file mode 100644 index 00000000..87b5806a --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/teardown/teardown.json @@ -0,0 +1,8 @@ +{ + "machine": "== controller.yaml git block (token redacted by the reader, not by us):\ngit:\n branch: main\n repo_url: https://gitea.dooplex.hu/admin/app-catalog-felhom.eu.git\n sync_interval: 15m\n token: \"\"\n username: \"\"\nhub:\n== the CACHE is what decides which remote is followed (R-615):\norigin\thttps://gitea.dooplex.hu/admin/app-catalog-felhom.eu.git (fetch)\n1ad1f34 gates: probe-matches-compose runs in CI's no-PyYAML mode (R-618)\n== containers still running (expect exactly three):\ntermix\ngokapi\nfelhom-controller\nfilebrowser\ntraefik\n== any container from tonight that should be gone:\ntermix|Up 2 hours (healthy)\ngokapi|Restarting (1) 19 seconds ago\n== any app.yaml left (expect none):\n/opt/docker/stacks/sparkyfitness/app.yaml\n== drill images (expect none):\nNONE\n== disk:\n/dev/loop0 32G 1.9G 28G 7% /\n", + "host": "VMID Status Lock Name \n9201 running demo-hp \n9202 running demo-hp-scratch \nName Type Status Total (KiB) Used (KiB) Available (KiB) %\nfelhom-pbs pbs active 0 0 0 0.00%\nlocal dir active 40453376 32931772 5434488 81.41%\nlocal-lvm lvmthin active 56487936 30136313 26351622 53.35%\nnvme-scratch dir active 983379700 68350084 865003004 6.95%\n", + "hub": "nothing provisioned, nothing changed — 9202 runs hub.enabled: false (R-620)", + "drill_head": "1ad1f34b6e51843851839bdd18154a6603c6e590", + "live_template_diff_lines": 0, + "live_image_line_diff": [] +} \ No newline at end of file diff --git a/documentation/audits/the-28-2026-09-22/walk28.py b/documentation/audits/the-28-2026-09-22/walk28.py new file mode 100644 index 00000000..d2d6a5eb --- /dev/null +++ b/documentation/audits/the-28-2026-09-22/walk28.py @@ -0,0 +1,238 @@ +#!/usr/bin/env python3 +"""walk28.py — ONE of the twenty-eight, the full rotation walk, through the product. + +Reuses `update-night-2026-09-21/walk.py` wholesale (R-161: do not rewrite the method). What is NEW +here versus the update night is step 6: the app is RESTORED from its own backup and the seed read +back a second time. The update night skipped that half. + +Every app gets a line even when it cannot be deployed. `inconclusive` is never collapsed into +`failed` or `proven`, and an app with no non-browser seed route is `inconclusive` WITH WHAT WAS +TRIED — never faked. +""" +import argparse, json, os, re, sys, time, traceback +from datetime import datetime, timezone + +HERE = os.path.dirname(os.path.abspath(__file__)) +NIGHT = os.path.join(os.path.dirname(HERE), "update-night-2026-09-21") +sys.path.insert(0, NIGHT) +sys.path.insert(0, HERE) +import walk as w # noqa: E402 +from fixtures28 import FIXTURES28 # noqa: E402 + +EDGES = json.load(open(os.path.join(HERE, "edges.json"))) if os.path.exists( + os.path.join(HERE, "edges.json")) else {} +META = json.load(open("/tmp/claude-1000/-mnt-5-hdd-felhom-eu-git/" + "d029e2e6-1762-440e-956d-0760c8aea4b3/scratchpad/drift28/meta.json")) +LOCK = "/tmp/claude-1000/-mnt-5-hdd-felhom-eu-git/d029e2e6-1762-440e-956d-0760c8aea4b3/scratchpad/drill.lock" + + +def drill_lock(fn, *a, **k): + """The drill repo is ONE git clone; two workers pushing at once corrupt each other's index.""" + for _ in range(600): + try: + fd = os.open(LOCK, os.O_CREAT | os.O_EXCL | os.O_WRONLY) + os.close(fd) + break + except FileExistsError: + time.sleep(1) + try: + return fn(*a, **k) + finally: + try: + os.unlink(LOCK) + except OSError: + pass + + +def main(): + ap = argparse.ArgumentParser() + ap.add_argument("app") + ap.add_argument("--cap", type=int, default=1800) + a = ap.parse_args() + app = a.app + d = os.path.join(HERE, "apps", app) + os.makedirs(d, exist_ok=True) + # per-worker session files: walk.py keeps the cookie in SC, and two workers racing on one + # cookie file log each other out. Assigning the module global is enough — every helper reads + # it at CALL time. + w.SC = os.path.join("/tmp/claude-1000/-mnt-5-hdd-felhom-eu-git/" + "d029e2e6-1762-440e-956d-0760c8aea4b3/scratchpad/w", app) + os.makedirs(w.SC, exist_ok=True) + import shutil + shutil.copy("/tmp/claude-1000/-mnt-5-hdd-felhom-eu-git/" + "d029e2e6-1762-440e-956d-0760c8aea4b3/scratchpad/.ctlpw", w.SC + "/.ctlpw") + + t0 = time.time() + m = META.get(app, {}) + fx = FIXTURES28.get(app) + sub = getattr(fx, "sub", None) or app + edge = EDGES.get(app) + rec = {"harness_version": 2, "app": app, + "venue": "guest 9202 demo-hp-scratch, controller 0.261.0, drill catalog", + "class": m.get("class") or ("db" if m.get("engines") else "file-leg"), + "from": {}, "to": {}, "verdict": "inconclusive", + "deployed": False, "seed_route": None, "seed_read_before": False, + "seed_read_after_update": False, "backup": None, + "restore_verdict": "not-attempted", "seed_read_after_restore": False, + "healthy_after": False, "migration_observed": None, "removed_clean": None, + "edge": edge, "duration_s": 0, + "measured_at": datetime.now(timezone.utc).isoformat(), + "evidence": f"the-28-2026-09-22/apps/{app}/", "notes": []} + + w.say(f"==== {app} (sub={sub}, class={rec['class']}, edge={edge or 'none'})") + try: + w.login() + + # ---- 1 deploy at the LIVE pin ------------------------------------------------------- + ok = w.deploy(app, sub) + st = w.stack(app) + rec["deployed"] = bool(st.get("deployed")) + rec["from"] = (st.get("app_config") or {}).get("pinned_images") or {} + if not rec["deployed"]: + rec["verdict"] = "could-not-deploy" + rec["notes"].append("deploy never reached `deployed` with a pin") + raise SystemExit(0) + front = w.app_curl(sub, "/") + rec["front_door_after_deploy"] = {"rc": front[0], "code": front[1]} + w.say(f" [1] front door {front[1]} controller state={st.get('state')}") + + # ---- 2/3 seed through the app's OWN front door, then read it back (C1) --------------- + tok = None + if fx is None: + rec["seed_route"] = "none written" + rec["notes"].append("no fixture: no non-browser seed route was written for this app") + w.say(" [2] NO FIXTURE — recorded inconclusive for the data half, not faked") + else: + tok = fx.seed(w, sub, w.say) + rec["seed_route"] = getattr(fx, "route", fx.__class__.__name__) + if tok is None: + rec["notes"].append("fixture ran and found no non-browser seed route: " + + (getattr(fx, "tried", "") or "see log")) + w.say(" [2] fixture found no route — inconclusive for the data half") + else: + rec["seed_read_before"] = bool(fx.verify(w, sub, tok, w.say)) + w.say(f" [3] C1 readback before = {rec['seed_read_before']}") + + # ---- 4 „Mentés most" ----------------------------------------------------------------- + w.backup_now(app) + snaps = w.snapshots(app) + rec["backup"] = {"snapshots_offered": len(snaps), + "first": (snaps[0] if snaps else None)} + w.say(f" [4] backups page offers {len(snaps)} restorable copy(ies)") + + # ---- 5 the edge, if one exists -------------------------------------------------------- + if edge: + h = drill_lock(w.drill_bump, app, edge["from"], edge["to"]) + rec["drill_commit"] = h + secs = w.sync_rescan(app, edge["to"].split(",")[0]) + rec["badge_seconds"] = secs + rec["badges"] = w.badges(app) + w.say(f" [5] badge {rec['badges']}") + ph = w.press_update(app) + rec["phases"] = ph + # R-621: the hold destroys the app's logs, so they are captured DURING the wait, + # not after. press_update polls; this is the best moment available to a caller. + logs = w.app_logs(app, 300) + open(os.path.join(d, "app-logs-during-after.txt"), "w").write(logs or "") + for line in (logs or "").split("\n"): + if any(k in line.lower() for k in ("migrat", "upgrad", "schema", "alter table")): + rec["migration_observed"] = line.strip()[:300] + break + obs = w.observables(app) + rec["observables"] = obs + rec["to"] = (w.stack(app).get("app_config") or {}).get("pinned_images") or {} + # The Update can be REFUSED before any phase exists — `409 busy` while a backup or a + # restore is in flight, which is the product guarding itself and is a RESULT, not an + # error. An empty phase list then made `[-1]` raise, which cost rallly its whole walk. + plist = ph if isinstance(ph, list) else (ph.get("phases") or []) + last = plist[-1].get("phase") if plist else None + if not last and isinstance(ph, dict) and ph.get("http") == "409": + last = "refused-" + str((ph.get("refusal") or {}).get("data", {}).get("reason") + or "409") + rec["update_refusal"] = (ph.get("refusal") or {}).get("error") + rec["update_phase_final"] = last + if tok is not None: + rec["seed_read_after_update"] = bool(fx.verify(w, sub, tok, w.say)) + st = w.stack(app) + rec["healthy_after"] = st.get("state") in ("running",) + if last == "done" and (tok is None or rec["seed_read_after_update"]): + rec["verdict"] = "proven" if tok is not None else "inconclusive" + elif last == "failed": + rec["verdict"] = "failed" + else: + rec["verdict"] = "no-edge" + + # ---- 6 RESTORE — the half the update night skipped ------------------------------------- + if snaps: + r = w.restore(app) + rec["restore_raw"] = str(r)[:400] + # A REFUSAL IS NOT A FAILURE, and collapsing the two would have mis-recorded the very + # behaviour `07` §6.2 predicts. A class-A app's local copy holds no readable file leg, + # and the product refuses rather than restoring a database over files it does not have + # — naming the action that DOES work. The refusal travels as a `flash_error` on the + # redirect, so it is read from there and quoted, urldecoded, in the record. + from urllib.parse import unquote_plus + loc = " ".join(r.get("location") or []) if isinstance(r, dict) else "" + # NOT `m` — that name already holds this app's META entry, and shadowing it made the + # teardown fall over with `'re.Match' object has no attribute 'get'` on paperless-ngx. + fm = re.search(r"flash_error=([^&\s]+)", loc) + rec["restore_refusal"] = unquote_plus(fm.group(1)) if fm else None + time.sleep(15) + st = w.stack(app) + if rec["restore_refusal"]: + rec["restore_verdict"] = "refused-with-a-sentence" + elif st.get("state") in ("running", "unhealthy"): + rec["restore_verdict"] = "ok" + else: + rec["restore_verdict"] = "failed" + rec["restore_state_seen"] = st.get("state") + if tok is not None: + rec["seed_read_after_restore"] = bool(fx.verify(w, sub, tok, w.say)) + w.say(f" [6] restore -> {rec['restore_verdict']}, seed back = " + f"{rec['seed_read_after_restore']}") + # WAIT FOR THE RESTORE TO SETTLE BEFORE REMOVING. `POST /backup/restore` answers 302 + # and works in the background; a remove sent while its `compose up` is still running + # tears down what exists and the restore then RE-CREATES it — the controller records + # the app as removed and a container keeps restarting with a live traefik route. + # Measured on gokapi at 11:34 (R-633). Leaving the race in would mean every later app + # measured the harness rather than the product. + for _ in range(36): + s = w.stack(app) + if not s.get("restore_running") and s.get("state") in ( + "running", "unhealthy", "degraded", "stopped", "not_deployed", None): + break + time.sleep(5) + time.sleep(10) + else: + rec["restore_verdict"] = "no-snapshot-offered" + + except SystemExit: + pass + except Exception as e: + rec["notes"].append(f"harness error: {type(e).__name__}: {e}") + open(os.path.join(d, "traceback.txt"), "w").write(traceback.format_exc()) + w.say(f" !! {type(e).__name__}: {e}") + finally: + # ---- 7 remove, then R-626: check 60 s later that nothing came back -------------------- + try: + w.remove(app) + time.sleep(60) + back = w.guest(f"docker ps -a --format '{{{{.Names}}}}' | grep -x '{app}' || true; " + f"ls /opt/docker/stacks/{app}/app.yaml 2>/dev/null || true") + rec["removed_clean"] = (back.strip() == "") + rec["remove_leftovers"] = back.strip()[:300] + w.say(f" [7] 60 s after remove: clean={rec['removed_clean']} {back.strip()[:120]!r}") + # drop the app's images BY NAME — never `prune` (rule 3); 9202's root disk is 28 GB + imgs = [v for v in (m.get("all_images") or {}).values() if v] + if imgs: + w.guest("docker image rm -f " + " ".join(imgs) + " 2>&1 | tail -2") + except Exception as e: + rec["notes"].append(f"teardown error: {type(e).__name__}: {e}") + rec["duration_s"] = round(time.time() - t0, 1) + json.dump(rec, open(os.path.join(d, "verdict.json"), "w"), ensure_ascii=False, indent=2) + open(os.path.join(d, "log.txt"), "w").write("\n".join(w.LOG) + "\n") + w.say(f" [9] verdict {rec['verdict']} ({rec['duration_s']}s) -> apps/{app}/verdict.json") + + +if __name__ == "__main__": + main() diff --git a/documentation/audits/update-night-2026-09-21/repoint_drill.py b/documentation/audits/update-night-2026-09-21/repoint_drill.py index dbe65fcb..3712c55e 100644 --- a/documentation/audits/update-night-2026-09-21/repoint_drill.py +++ b/documentation/audits/update-night-2026-09-21/repoint_drill.py @@ -28,7 +28,7 @@ def repoint(to_drill): url = DRILL_REPO if to_drill else LIVE script = f""" set -e -test -f {VOL}/controller.yaml.pre-update-night || cp {VOL}/controller.yaml {VOL}/controller.yaml.pre-update-night +test -f {VOL}/controller.yaml.pre-28 || cp {VOL}/controller.yaml {VOL}/controller.yaml.pre-28 python3 - <<'EOF' import re p = "{VOL}/controller.yaml" diff --git a/documentation/backlog/OPEN-ITEMS.md b/documentation/backlog/OPEN-ITEMS.md index 44fd1326..d8611c7e 100644 --- a/documentation/backlog/OPEN-ITEMS.md +++ b/documentation/backlog/OPEN-ITEMS.md @@ -799,9 +799,11 @@ class (an image `VOLUME` at an unmounted path) is still live — `immich-server` | **R-628** | **[P2-MEDIUM] An empty search of a mailbox I do not control was turned into a claim about what a THIRD PARTY had done, and it went into the register as fact.** FOUND 2026-09-22; the operator caught it within minutes by producing the thread. The `due_checks_gate` fired R-433 (*have Hetzner answered?*). Two searches of the felhom catch-all came back empty, and the emptiness was written into R-433 as **"Hetzner has not answered"** and **"there is no evidence the tickets were ever opened"**. Both were false: ticket **#2026090103040671** had been opened and answered. **THE PRECISE FAULT IS THE INFERENCE, NOT THE QUERY — and that distinction is the whole value of this row.** Re-run afterwards WITH a positive control: `from:monitoring@felhom.eu` returns **201 threads**, `in:anywhere … includeTrash` reaches SENT, TRASH and mail back to January — **the instrument works.** And the exact ticket number, the exact subject `Storage Box issue`, and `from:hetzner` each still return **nothing**. So the literal finding — *this correspondence is not in this mailbox* — was CORRECT. What was invented was the step from there to *Hetzner has not answered* and *the tickets were never opened*. **A mailbox I can read is not the only place a reply can be**, and the operator's own account is exactly where a support ticket he opened would land. An absent record in ONE place can never answer a question about what SOMEONE ELSE did. **This is R-96 rule 3 in a new surface, one step further out than R-607**: there the instrument reported a stale value as current; here a correct observation was promoted to a conclusion it could not carry. **It is sharper still because the same session, the night before, gave every fixture a negative control and every gate a red-proof — and then reached for a search with neither.** **THE RULE, in one line:** *an empty search may be reported as "absent from the place I looked", never as "it did not happen" — and only after a control query that MUST hit has been seen to hit.* **Done:** the rule is written into the Gmail-access memory, where the next session meets it before it searches rather than after. | **CLOSED 2026-09-22 — rule recorded; R-433 corrected with the real answers** | | **R-627** | **[P2-MEDIUM] Nothing checked that the register is a well-formed table, so an append that ate two rows' state cells went unnoticed until a person read the file — and one row had been broken the same way for 45 days.** FOUND 2026-09-22. The 2026-09-21 update night appended measured results to eight rows with a regex that matched each row's trailing state cell; on **R-446** and **R-458** it consumed the cell and did not restore it, the cell reappeared as a stray FOURTH cell on a DUPLICATED copy of **R-626** and **R-625**, and a blank line was left between each pair. The register then reported **317 rows for 315 findings**, two rows carried no state at all, and two findings existed twice with contradictory state cells. **Nothing caught it:** `one_register_gate.py` compares this file against ROADMAP and `closed_register_gate.py` forbids an id in BOTH files — neither asks whether the file is a well-formed table, and neither notices an id duplicated WITHIN it. **THE RED-PROOF THEN FOUND AN OLDER INSTANCE NOBODY HAD SEEN: R-254 lost its state cell on 2026-08-08 (commit `59527d0`) and had rendered without a State column for 45 days.** **Closed the same day:** `scripts/register_shape_gate.py`, registered in `repo_gates.py` as gate 14 and reached by the pre-push hook, refusing a row that does not end with `|` (an eaten state cell), a duplicated id, or a blank line splitting the table; four decoys in `test_gate_decoys.py` — three convicting on the exact damage shapes and one asserting a healthy register still passes. **TWO THINGS THE RED-PROOF CORRECTED IN THE GATE ITSELF, kept because they are the finding's real content:** a first draft counted CELLS and convicted **125 innocent rows** — register cells carry literal `|` inside prose and shell snippets (`owner: CC | …`), so a row cannot be split on `|`, and a count that cannot be computed is not a check; and it skipped malformed rows before counting ids, reporting **5 duplicates where there were 2**. **Repaired:** both state cells restored from the stray cells that carried them, the two duplicate rows deleted, R-254's verdict sentence given its cell back, and **15 blank lines that split the register into 12 separate markdown tables** removed — every row's text byte-identical afterwards, proven by diff. **And the gate immediately earned itself:** the very next row inserted in this session (R-628) left a blank line behind and the gate refused it. | **CLOSED 2026-09-22 — gate 14, four decoys, register repaired 317→315** | | **R-629** | **[P2-MEDIUM] The drill catalog sent the operator 47 CI-failure alarms in one night, and the drill method that created it did not mention CI at all.** FOUND 2026-09-22 while checking a different mailbox question — which is the only reason it was found. `admin/app-catalog-drill` was created on 2026-09-21 by `POST /api/v1/repos/migrate` from the live catalog, and a migrated repo inherits `has_actions: true`. Every drill push therefore ran the catalog's CI workflow, which failed immediately (the drill repo carries the workflow but the run has no meaningful gate context), and **each failure mailed `admin@felhom.eu`**: *"[felhom CI] gates FAILED in admin/app-catalog-drill"*. **47 runs, 47 alarms, all overnight, all unread and flagged IMPORTANT.** **WHY THIS MATTERS MORE THAN THE NOISE:** that mailbox is the operator's alarm channel, and R-168 made CI mail the thing that notices a bypassed gate. A night of throwaway failures from a repo nobody must ever act on trains the reader to skim exactly the sender that must never be skimmed — and it did it on the night the same mailbox was also carrying real `offsite_snapshots_dropped` and `offsite_proof_empty` alarms. **The drill method wrote down the fences it needed** — private repo, no customer box may follow it, reset at teardown — **and said nothing about CI, because nobody had run a drill repo through a CI-enabled Gitea before.** **FIXED 2026-09-22:** `has_actions` set to **false** on the drill repo (verified by re-reading the repo: `has_actions: False, private: True`), and `09` §6.5 now carries it as a step of creating a drill repo rather than as a thing to notice afterwards. **What is NOT done:** the 47 mails are still in the operator's inbox, unread — deleting another person's mail is not mine to do, and they are named here so they can be cleared in one search: `subject:"gates FAILED in admin/app-catalog-drill"`. | **CLOSED 2026-09-22 — actions disabled, method updated; the 47 mails are the operator's to clear** | -| **R-630** | **[P2-MEDIUM] `paperless-ngx`'s health probe has never run, on any box, and the badge can never go red.** FOUND 2026-09-22 by the new `probe-matches-compose` gate, which reported it as a WARNING while looking for something else. `findProbeContainer` (`controller/internal/stacks/healthprobe.go:297`) takes the container whose name EQUALS the stack name, else the first whose name has it as a PREFIX; paperless-ngx's containers are `paperless-webserver`, `paperless-postgres` and `paperless-redis`, and **none of them begins with `paperless-ngx`**. The function returns `""`, `:62` counts the stack in `skippedNoContainer` and `continue`s, and no probe is ever built for it. **WHY THIS IS WORSE THAN A WRONG PROBE, WHICH IS WHAT R-618 WAS:** a wrong probe is a FALSE RED — loud, visible, and it stopped an app, which is how it was found within one night. This is a SILENT ABSENCE. The app page shows whatever the container state alone says, nothing contradicts it, and **R-96 rule 3 is the exact shape: an absent alarm is equally consistent with `healthy` and with `never checked`.** **NOT MEASURED, and said so rather than assumed:** what the guarded update's `verifying` phase does for a stack with no probe target — whether it passes immediately or waits out `update.health_timeout` — has not been run. `paperless-ngx` IS installed on demo-hp, so it is answerable on a real box. **Two candidate fixes, neither taken here because both are product code and this session was forbidden it:** give the template a `container_name: paperless-ngx` on its webserver service (catalog-only, one line, but it renames a running container on every existing box); or let the probe fall back to the service the compose declares first, and SAY SO in the health detail. **What is safe to say today:** the gate names it on every push, so it cannot go back to being invisible. | **OPEN — P2; owner: CC; needs the `verifying` behaviour measured on demo-hp before a fix is chosen** | -| **R-631** | **[P3-LOW] Five templates cannot be judged by the probe gate at all, and one is correct only by accident.** FOUND 2026-09-22 when `check-probe-matches-compose.py` was run over all 53. The gate's oracle is the probed service's own compose `healthcheck.test`; where that dials no loopback URL the gate has nothing to compare and reports a WARNING rather than a pass. **`crafty-controller`, `mealie` and `uptime-kuma`** run their healthcheck through a python or script helper (`ssl._create_unverified_context()`, `socket.create_connection`, `extra/healthcheck`), so the port is inside code the gate does not execute. **`vikunja`** has no compose healthcheck on its probed service at all. **`home-assistant`** is the interesting one: its probe path `/api/` differs from the compose's `/manifest.json`, and it is healthy today **only because its check is `type: api` with no `expect` block, which `probeHTTP` treats as "any response is healthy"** — add `expect: {status: 200}` to that template, a change that looks like a tightening, and the app goes permanently unhealthy and every successful update of it starts stopping it. **The gate warns on it for exactly that reason and refuses to call it a pass.** **Needs:** a live probe reading for each of the five on a scratch guest — deploy, read `GET /api/stacks/`, compare with the front door — which is one rotation night's work and closes the last gap R-618 left. | **OPEN — P3; owner: CC; five apps, one live reading each** | -| **R-632** | **[P3-LOW] Twenty-eight of the 53 templates have never been deployed by any update drill, so nothing is known about whether their updates work.** COUNTED 2026-09-22 against the 2026-09-21 sweep, which is the widest one ever run. **20 apps have a verdict record** (14 proven, 3 failed, 3 inconclusive, plus tandoor re-walked to proven on 2026-09-22); **4 more were deployed as props in the bad-days legs with no edge walked** (`bentopdf`, `glance`, `uptime-kuma`, `wishlist`); **1 was deployed only to measure its probe** (`wger`); and **28 have never been deployed at all**: `calcom`, `calibre-web`, `claper`, `code-server`, `crafty-controller`, `emby`, `ghost`, `gokapi`, `gramps-web`, `homebox`, `homepage`, `immich`, `jellyfin`, `kimai`, `komga`, `onlyoffice`, `outline`, `paperless-ngx`, `plant-it`, `plex`, `radarr`, `rallly`, `recipe-importer`, `seerr`, `sonarr`, `sparkyfitness`, `termix`, `wanderer`. **THIS IS NOT A COMPLAINT ABOUT THE SWEEP** — it took one app-catalog-wide night to go from 3 apps ever measured to 21, and a night is the unit available. It is a record of what the catalog's update promise currently rests on: **for 28 of 53 apps, nothing.** **The list is the nightly rotation's queue**, smallest and least stateful first; `paperless-ngx` should be early because R-630 needs a live reading from it anyway, and `crafty-controller`, `mealie` and `uptime-kuma` should be early because R-631 needs one from each. Machine-readable copy: `audits/probe-fix-2026-09-22/not-judged.json`. | **OPEN — P3; owner: CC; the rotation works this list, 28 apps** | +| **R-630** | **[P2-MEDIUM] `paperless-ngx`'s health probe has never run, on any box, and the badge can never go red.** FOUND 2026-09-22 by the new `probe-matches-compose` gate, which reported it as a WARNING while looking for something else. `findProbeContainer` (`controller/internal/stacks/healthprobe.go:297`) takes the container whose name EQUALS the stack name, else the first whose name has it as a PREFIX; paperless-ngx's containers are `paperless-webserver`, `paperless-postgres` and `paperless-redis`, and **none of them begins with `paperless-ngx`**. The function returns `""`, `:62` counts the stack in `skippedNoContainer` and `continue`s, and no probe is ever built for it. **WHY THIS IS WORSE THAN A WRONG PROBE, WHICH IS WHAT R-618 WAS:** a wrong probe is a FALSE RED — loud, visible, and it stopped an app, which is how it was found within one night. This is a SILENT ABSENCE. The app page shows whatever the container state alone says, nothing contradicts it, and **R-96 rule 3 is the exact shape: an absent alarm is equally consistent with `healthy` and with `never checked`.** **NOT MEASURED, and said so rather than assumed:** what the guarded update's `verifying` phase does for a stack with no probe target — whether it passes immediately or waits out `update.health_timeout` — has not been run. `paperless-ngx` IS installed on demo-hp, so it is answerable on a real box. **Two candidate fixes, neither taken here because both are product code and this session was forbidden it:** give the template a `container_name: paperless-ngx` on its webserver service (catalog-only, one line, but it renames a running container on every existing box); or let the probe fall back to the service the compose declares first, and SAY SO in the health detail. **What is safe to say today:** the gate names it on every push, so it cannot go back to being invisible. **MEASURED 2026-09-22 (the twenty-eight), and the answer is the WORST of the three possibilities, so this row is RAISED P2 → P1.** paperless-ngx was deployed on guest 9202 with all three containers **`healthy`**, the controller reading **`running`** and the front door answering **302**. The Update was then pressed (no upstream edge exists tonight, so it was pressed on the same version — which is what a household does on an up-to-date app and still walks the whole phase machine; stated rather than glossed). Phases: `checking` → `safety-dump` → `pinning` → `pulling` → `starting` (+2.1 s) → `verifying` (+3.1 s) → **`failed` at +313.0 s, the app STOPPED** (`state_after: stopped`, front door **404**). **THE CONTROLLER'S OWN WORDS NAME THE CAUSE, and no inference was needed:** *`update paperless-ngx FAILED after the new version was started: not healthy: not healthy within 5m0s (last: no probe container) — stopping and HOLDING the app; the pin stays on the new version (its migration may have run)`*. **`no probe container`.** So `verifying` does not pass when there is no probe and it does not skip — **it waits out the full `update.health_timeout` and then HOLDS.** **WHAT THIS COSTS:** every paperless-ngx household that presses Update has their working app **stopped for five minutes and then left stopped**, and is sent to a restore they do not need — and the hold sentence correctly warns that the copy *„csak a beállításokat és az adatbázist tartalmazza, a fájlokat nem"*, so for this class-A app the route back is the off-site copy. **This is R-618's outcome reached by the opposite road:** there a probe named the wrong port; here no probe exists at all, and the static gate cannot see it because there is nothing to compare. The gate does name it on every push as a WARNING, which is how it was found. **Needs (unchanged, and now urgent):** give the template a `container_name: paperless-ngx` on its webserver service, or make `verifying` treat "no probe target" as something other than a failure — both are product/catalog decisions. Evidence: `audits/the-28-2026-09-22/sidejobs/r630-controller-words.txt`, `sidejobs/r630.json`. | **OPEN — RAISED TO P1 2026-09-22 by measurement; owner: CC; a successful update STOPS the app** | +| **R-631** | **[P3-LOW] Five templates cannot be judged by the probe gate at all, and one is correct only by accident.** FOUND 2026-09-22 when `check-probe-matches-compose.py` was run over all 53. The gate's oracle is the probed service's own compose `healthcheck.test`; where that dials no loopback URL the gate has nothing to compare and reports a WARNING rather than a pass. **`crafty-controller`, `mealie` and `uptime-kuma`** run their healthcheck through a python or script helper (`ssl._create_unverified_context()`, `socket.create_connection`, `extra/healthcheck`), so the port is inside code the gate does not execute. **`vikunja`** has no compose healthcheck on its probed service at all. **`home-assistant`** is the interesting one: its probe path `/api/` differs from the compose's `/manifest.json`, and it is healthy today **only because its check is `type: api` with no `expect` block, which `probeHTTP` treats as "any response is healthy"** — add `expect: {status: 200}` to that template, a change that looks like a tightening, and the app goes permanently unhealthy and every successful update of it starts stopping it. **The gate warns on it for exactly that reason and refuses to call it a pass.** **Needs:** a live probe reading for each of the five on a scratch guest — deploy, read `GET /api/stacks/`, compare with the front door — which is one rotation night's work and closes the last gap R-618 left. **CLOSED 2026-09-22 — all five read live on guest 9202, and all five probes are CORRECT.** Each app was deployed, its listening sockets read from inside the probed container, and the probe's own target dialled **on the compose network**, which is the call the controller makes. `mealie` `tcp 9000` → listens `0.0.0.0:9000`, dial 200. `uptime-kuma` `http 3001` → listens `*:3001`, dial 302 (and `http` calls any response healthy, so 302 passes and proves something answers). `vikunja` `api 3456 /api/v1/info` **with `expect: {status: 200}`** → dial **200** — the one that could have failed, because its expect block compares the code. `crafty-controller` `tcp 8443` → the controller's own log reads `Health probe crafty-controller: TCP :8443 -> ok (1ms)` twice, six minutes apart. **`home-assistant` is the one to carry forward:** `api 8123 /api/` with NO expect → the dial returns **401**, not 200. It reads healthy only because `probeHTTP` treats any response as healthy for that shape (`healthprobe.go:253-262`). **Add `expect: {status: 200}` to that template — a change that looks like a tightening — and home-assistant goes permanently unhealthy and every successful update of it starts stopping it.** That is R-618 one edit away, now a measured number rather than a caution. **So the gate's WARN list is not a backlog of suspects: it is four correct templates the gate honestly cannot prove, plus one correct by accident.** Evidence: `audits/the-28-2026-09-22/sidejobs/r631.json`. | **CLOSED 2026-09-22 — five live readings, five correct probes; home-assistant's fragility is now a number** | +| **R-632** | **[P3-LOW] Twenty-eight of the 53 templates have never been deployed by any update drill, so nothing is known about whether their updates work.** COUNTED 2026-09-22 against the 2026-09-21 sweep, which is the widest one ever run. **20 apps have a verdict record** (14 proven, 3 failed, 3 inconclusive, plus tandoor re-walked to proven on 2026-09-22); **4 more were deployed as props in the bad-days legs with no edge walked** (`bentopdf`, `glance`, `uptime-kuma`, `wishlist`); **1 was deployed only to measure its probe** (`wger`); and **28 have never been deployed at all**: `calcom`, `calibre-web`, `claper`, `code-server`, `crafty-controller`, `emby`, `ghost`, `gokapi`, `gramps-web`, `homebox`, `homepage`, `immich`, `jellyfin`, `kimai`, `komga`, `onlyoffice`, `outline`, `paperless-ngx`, `plant-it`, `plex`, `radarr`, `rallly`, `recipe-importer`, `seerr`, `sonarr`, `sparkyfitness`, `termix`, `wanderer`. **THIS IS NOT A COMPLAINT ABOUT THE SWEEP** — it took one app-catalog-wide night to go from 3 apps ever measured to 21, and a night is the unit available. It is a record of what the catalog's update promise currently rests on: **for 28 of 53 apps, nothing.** **The list is the nightly rotation's queue**, smallest and least stateful first; `paperless-ngx` should be early because R-630 needs a live reading from it anyway, and `crafty-controller`, `mealie` and `uptime-kuma` should be early because R-631 needs one from each. Machine-readable copy: `audits/probe-fix-2026-09-22/not-judged.json`. **WORKED IN ONE NIGHT, 2026-09-22 — all 28 walked, so this row CLOSES and hands its findings to others.** Every one was installed on guest 9202 against the private drill catalog and taken through the same walk: deploy at the live pin, seed through the app's own front door, read it back, „Mentés most”, the guarded Update where a real within-a-major edge exists upstream, **restore from that copy and read the seed back a second time** (the half the update night skipped), then remove and a 60-second check that nothing came back. **26 of 28 deployed; 6 proven; the rest inconclusive, no-edge or refused.** **What the night produced that this row could not have predicted:** R-630 raised to P1 by measurement (a stack with no probe container has its working app STOPPED by a successful update), R-633 (a remove during a restore leaves an orphan with a live public route), R-634 (an app running and healthy while recorded as not deployed, and then unremovable), and one real upstream edge that HELD honestly (`outline 1.9.1 → 1.10.1`). **Also settled:** `plant-it` is `lifecycle: abandoned` and the product refuses to install it — the only lifecycle-gated template in the catalog, and its gate is now proven live. Full record: `audits/DRILL-the-28-2026-09-22.md`. | **CLOSED 2026-09-22 — all 28 walked in one night; the findings live in R-630, R-633, R-634** | +| **R-633** | **[P2-MEDIUM] A remove sent while a restore is still running reports success, deletes the app's record, and leaves a container restarting forever with a live public route.** MEASURED 2026-09-22 on guest 9202, controller v0.261.0, during the twenty-eight walk. `gokapi` was restored from its own local copy at 11:34:07 and removed at 11:34:22. `POST /backup/restore` answers **302 and works in the background**; the remove tore down what existed and the restore's own `compose up` then RE-CREATED the container at **11:34:24**. **Both calls returned success.** Twenty-five minutes later: `GET /api/stacks/gokapi` reads **`deployed: false`**, and `docker ps -a` shows `gokapi` **`Restarting (1)`** with `RestartCount` climbing, carrying its full traefik label set — including `traefik.http.routers.gokapi.rule: Host(`.enkisfelhom.hu`)`, **a rule with an empty subdomain**, because the deploy values that filled it were deleted with the app. Its own log loops *„Salt for admin password invalid, generating new salt… password does not appear to be a SHA-1 hash"* — the volume holding its config was removed correctly, so the binary can never start. **WHY THIS IS A ROW AND NOT A HARNESS ARTEFACT:** a household can press exactly these two buttons in exactly this order, the product accepted both, and **the remove reported success while leaving the orphan**. Presence of a success message is not evidence of a result. **WHAT IT COSTS:** an app the household believes is gone keeps a container in a restart loop, keeps a route registered on the public reverse proxy, and is invisible to every product surface because the controller no longer records the stack. Nothing in the alarm ladder fires: `08` §4 keys on stacks the controller KNOWS about. **This is R-626's class with the mechanism finally visible** — that row recorded a removed `navidrome` coming back and could not diagnose it because the controller had restarted; here the window is 17 seconds and both halves are in the evidence. **Needs:** the remove path to refuse, or to wait, while a restore for the same stack is in flight — and, either way, to verify the teardown rather than report success without looking. Evidence: `audits/the-28-2026-09-22/apps/gokapi/came-back-evidence.txt`, `apps/gokapi/log.txt`. | **OPEN — P2; owner: CC; product code, so not fixed in this unattended run** | +| **R-634** | **[P1-HIGH] An app can be RUNNING, HEALTHY and serving while the controller records it as not deployed — and in that state the household cannot remove it through the product at all.** MEASURED 2026-09-22 on guest 9202, controller v0.261.0, on **two independent apps in one night**: `outline` and `sparkyfitness`. **The controller's own words, in order.** `outline` deploy accepted 11:54:47; **`11:55:51 StopStack outline: current state=deploying deployed=true containers=0`** — a stop while the stack is still deploying; `11:56:09 SaveAppConfig: saving /opt/docker/stacks/outline — 5 env vars, **0 encrypted**, 3 sensitive fields` (the two saves before it both read `3 encrypted`); then **`11:57:16` and `12:02:16 Health probe outline: API GET :3000/_health -> 200`** — the app is up and answering its own health endpoint; and **`12:02:19 StopStack outline: current state=running deployed=false containers=3`**. Three containers, state `running`, health 200, **`deployed=false`**. The remove then answers **`RemoveStack outline: state=not_deployed, deployed=false, orphaned=false, deploying=false` -> `[ERROR] Remove failed for outline: stack "outline" is not deployed`** for BOTH the remove-with-data and the remove-keeping-data call, while `ScanStacks` goes on finding the stack every ten seconds. `app.yaml` survives with `desired_state: stopped` and an **empty `installed_images`**. `sparkyfitness` produced the identical shape 13 minutes earlier. **WHAT THIS COSTS A HOUSEHOLD:** an app that works is invisible to the product as an installation — no badge, no update, no backup selection, and **no way to delete it**; the only exit is a shell. It is the mirror of R-633 (there the record is gone and the container remains; here the container is fine and the record is gone) and it is the **worse** of the two, because the app is serving customer traffic the whole time. **THE MECHANISM IS NOT DIAGNOSED, and this row says so rather than guessing.** What was tried: the controller's full container log for both apps (the sequence above), `app.yaml` on disk, `docker ps -a`, and `GET /api/stacks/`. What was NOT done: reading `runComposeDeploy`'s pin-write path — this was an unattended run and the brief forbade product code. **The one discriminator worth running first:** both apps were walked while two other walks ran concurrently, and `POST /api/backup/run` is box-wide, so a backup or restore for a NEIGHBOURING app was in flight. A serial re-walk is queued tonight; if it reproduces alone, concurrency is not the cause. **A THIRD THING THE SAME LOG SHOWS, recorded here because it is one line away:** at `11:55:57` the health probe dialled **`http://outline-postgres:3000/_health`** — during startup, with the exactly-named container not yet running, `findProbeContainer`'s PREFIX fallback latched onto the POSTGRES sidecar and probed port 3000 on it. Transient, and it resolved once `outline` came up, but it is the same function R-630 is about. Evidence: `audits/the-28-2026-09-22/apps/half-state-outline-sparkyfitness.txt`. **THE SERIAL RE-WALK WAS RUN THE SAME NIGHT AND IT SPLITS THIS ROW IN TWO — recorded here rather than left as the first reading.** Walked again one at a time, with no other walk running: **`outline` deployed normally and removed clean**, and **`crafty-controller` deployed normally, updated `4.10.7 → 4.11.0` to `done`, restored and removed clean.** So for those two the half-state did NOT reproduce alone, and concurrency — a box-wide `POST /api/backup/run` or a restore in flight for a NEIGHBOURING app — is implicated rather than the deploy path itself. **`sparkyfitness` reproduced EXACTLY, alone, in 534 s**: deploy accepted, never reached `deployed` with a pin, `app.yaml` left with `desired_state: stopped` and an empty `installed_images`, and **both remove calls refused with `stack "sparkyfitness" is not deployed`.** **So the row stands, at one reproducible app instead of three, and the honest split is:** (a) `sparkyfitness` has a deploy that does not finish and leaves a record the product cannot clear — reproducible, P1; (b) under concurrent work the same unremovable half-state can be reached by apps that are otherwise fine, which is the more alarming half because those apps were **running, healthy and serving** while recorded as not deployed. **Neither half is diagnosed** — `runComposeDeploy`'s pin write was not read, because the brief forbade product code. | **OPEN — P1; owner: CC; reproducible alone on `sparkyfitness`, concurrency-linked on the other two; next step is `runComposeDeploy`'s pin write** |