From 2f1f6c8052fc80eafa0e56fe87cbe5e461b6c2cf Mon Sep 17 00:00:00 2001 From: kisfenyo Date: Thu, 15 Oct 2026 12:58:18 +0200 Subject: [PATCH] Prowlarr evidence (R-1048) + its apps-page card; the stated app count 62 -> 63; R-1057 filed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - documentation/audits/prowlarr-2026-10-15/: bench (update test proven, C3 failed as it must, soak 7.6 %, volume-persistence CLEAN, official-image check), 9202 runs 1-3 (run 1's bump emptied the drill .felhom.yml — a test-tool bug, fixed; run 3 re-proved the step and backup -> remove -> restore with the full template), tools. - website: a Prowlarr card on both apps pages; every stated count 62 -> 63, the media group 19 -> 20; sizing.json + sizing-data.json carry Prowlarr (41/44 MB, onboarding), the sizing test and SIZING.md table moved to 63 apps. - OPEN-ITEMS: R-1057 (a restore of a removed gated app returns without the gate, even from a backup made before the setup — measured on 9202); R-1036 notes the count and that wger/plant-it are hidden/abandoned. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS --- .../audits/prowlarr-2026-10-15/README.md | 33 + .../prowlarr-2026-10-15/bench/01-licence.txt | 3 + .../bench/01-official-image.txt | 8 + .../prowlarr-2026-10-15/bench/01-tags.txt | 9 + .../bench/01-version-tags.txt | 3 + .../bench/02-first-start-2.6.5.txt | 156 ++++ .../bench/03-upgrade-test.txt | 47 ++ .../bench/04-c3-and-teardown.txt | 5 + .../prowlarr-2026-10-15/bench/C3-verdict.json | 31 + .../bench/MV-prowlarr/abort-states.json | 8 + .../bench/MV-prowlarr/compose-final.log | 59 ++ .../bench/MV-prowlarr/engine-state.json | 1 + .../bench/MV-prowlarr/files-after-detail.json | 1 + .../bench/MV-prowlarr/files-after.json | 1 + .../MV-prowlarr/files-before-detail.json | 1 + .../bench/MV-prowlarr/files-before.json | 1 + .../bench/MV-prowlarr/memory-samples.json | 762 ++++++++++++++++++ .../bench/MV-prowlarr/migration-lines.txt | 6 + .../bench/MV-prowlarr/run.log | 59 ++ .../bench/MV-prowlarr/to-full.log | 60 ++ .../bench/MV-prowlarr/to-states.json | 8 + .../bench/MV-prowlarr/verdict.json | 56 ++ .../bench/VP-prowlarr/prowlarr/probe.json | 157 ++++ .../prowlarr-2026-10-15/bench/vp-prowlarr.txt | 9 + .../box/00-attempt1-removed.txt | 6 + .../prowlarr-2026-10-15/box/00-repoint.txt | 17 + .../prowlarr-2026-10-15/box/99-restore.txt | 15 + .../box-verdict-prowlarr-run1-empty-meta.json | 18 + .../box/prowlarr/box-verdict-prowlarr.json | 18 + .../10-repoint-restore-walk.txt | 9 + .../box/run2-empty-meta/README.md | 14 + .../box/run2-empty-meta/restore-console.txt | 123 +++ .../box/run2-empty-meta/restore-results.json | 18 + .../box/run2-empty-meta/restore.txt | 117 +++ .../box/run3/restore-console.txt | 52 ++ .../box/run3/restore-results.json | 18 + .../prowlarr-2026-10-15/box/run3/restore.txt | 46 ++ .../box/walk-attempt1-aborted.txt | 0 .../prowlarr-2026-10-15/box/walk-console.txt | 57 ++ .../prowlarr-2026-10-15/box/walk-results.json | 41 + .../audits/prowlarr-2026-10-15/box/walk.txt | 53 ++ .../tools/bench_first_start.sh | 25 + .../prowlarr-2026-10-15/tools/repoint9202.py | 86 ++ .../tools/walk_9201_link.py | 179 ++++ .../tools/walk_prowlarr.py | 207 +++++ .../prowlarr-2026-10-15/tools/walk_restore.py | 136 ++++ .../audits/sizing-2026-10-13/SIZING.md | 4 +- .../audits/sizing-2026-10-13/sizing.json | 11 + documentation/backlog/OPEN-ITEMS.md | 5 +- scripts/test_build_sizing_web.py | 12 +- website/CHANGELOG.md | 9 +- website/alkalmazasok.html | 27 +- website/assets/sizing-data.json | 16 +- website/en/apps.html | 27 +- website/en/faq.html | 8 +- website/en/index.html | 6 +- website/en/services.html | 4 +- website/en/technology.html | 2 +- website/gyik.html | 8 +- website/index.html | 6 +- website/szolgaltatasok.html | 4 +- website/technologiak.html | 2 +- 62 files changed, 2846 insertions(+), 44 deletions(-) create mode 100644 documentation/audits/prowlarr-2026-10-15/README.md create mode 100644 documentation/audits/prowlarr-2026-10-15/bench/01-licence.txt create mode 100644 documentation/audits/prowlarr-2026-10-15/bench/01-official-image.txt create mode 100644 documentation/audits/prowlarr-2026-10-15/bench/01-tags.txt create mode 100644 documentation/audits/prowlarr-2026-10-15/bench/01-version-tags.txt create mode 100644 documentation/audits/prowlarr-2026-10-15/bench/02-first-start-2.6.5.txt create mode 100644 documentation/audits/prowlarr-2026-10-15/bench/03-upgrade-test.txt create mode 100644 documentation/audits/prowlarr-2026-10-15/bench/04-c3-and-teardown.txt create mode 100644 documentation/audits/prowlarr-2026-10-15/bench/C3-verdict.json create mode 100644 documentation/audits/prowlarr-2026-10-15/bench/MV-prowlarr/abort-states.json create mode 100644 documentation/audits/prowlarr-2026-10-15/bench/MV-prowlarr/compose-final.log create mode 100644 documentation/audits/prowlarr-2026-10-15/bench/MV-prowlarr/engine-state.json create mode 100644 documentation/audits/prowlarr-2026-10-15/bench/MV-prowlarr/files-after-detail.json create mode 100644 documentation/audits/prowlarr-2026-10-15/bench/MV-prowlarr/files-after.json create mode 100644 documentation/audits/prowlarr-2026-10-15/bench/MV-prowlarr/files-before-detail.json create mode 100644 documentation/audits/prowlarr-2026-10-15/bench/MV-prowlarr/files-before.json create mode 100644 documentation/audits/prowlarr-2026-10-15/bench/MV-prowlarr/memory-samples.json create mode 100644 documentation/audits/prowlarr-2026-10-15/bench/MV-prowlarr/migration-lines.txt create mode 100644 documentation/audits/prowlarr-2026-10-15/bench/MV-prowlarr/run.log create mode 100644 documentation/audits/prowlarr-2026-10-15/bench/MV-prowlarr/to-full.log create mode 100644 documentation/audits/prowlarr-2026-10-15/bench/MV-prowlarr/to-states.json create mode 100644 documentation/audits/prowlarr-2026-10-15/bench/MV-prowlarr/verdict.json create mode 100644 documentation/audits/prowlarr-2026-10-15/bench/VP-prowlarr/prowlarr/probe.json create mode 100644 documentation/audits/prowlarr-2026-10-15/bench/vp-prowlarr.txt create mode 100644 documentation/audits/prowlarr-2026-10-15/box/00-attempt1-removed.txt create mode 100644 documentation/audits/prowlarr-2026-10-15/box/00-repoint.txt create mode 100644 documentation/audits/prowlarr-2026-10-15/box/99-restore.txt create mode 100644 documentation/audits/prowlarr-2026-10-15/box/prowlarr/box-verdict-prowlarr-run1-empty-meta.json create mode 100644 documentation/audits/prowlarr-2026-10-15/box/prowlarr/box-verdict-prowlarr.json create mode 100644 documentation/audits/prowlarr-2026-10-15/box/run2-empty-meta/10-repoint-restore-walk.txt create mode 100644 documentation/audits/prowlarr-2026-10-15/box/run2-empty-meta/README.md create mode 100644 documentation/audits/prowlarr-2026-10-15/box/run2-empty-meta/restore-console.txt create mode 100644 documentation/audits/prowlarr-2026-10-15/box/run2-empty-meta/restore-results.json create mode 100644 documentation/audits/prowlarr-2026-10-15/box/run2-empty-meta/restore.txt create mode 100644 documentation/audits/prowlarr-2026-10-15/box/run3/restore-console.txt create mode 100644 documentation/audits/prowlarr-2026-10-15/box/run3/restore-results.json create mode 100644 documentation/audits/prowlarr-2026-10-15/box/run3/restore.txt create mode 100644 documentation/audits/prowlarr-2026-10-15/box/walk-attempt1-aborted.txt create mode 100644 documentation/audits/prowlarr-2026-10-15/box/walk-console.txt create mode 100644 documentation/audits/prowlarr-2026-10-15/box/walk-results.json create mode 100644 documentation/audits/prowlarr-2026-10-15/box/walk.txt create mode 100644 documentation/audits/prowlarr-2026-10-15/tools/bench_first_start.sh create mode 100644 documentation/audits/prowlarr-2026-10-15/tools/repoint9202.py create mode 100644 documentation/audits/prowlarr-2026-10-15/tools/walk_9201_link.py create mode 100644 documentation/audits/prowlarr-2026-10-15/tools/walk_prowlarr.py create mode 100644 documentation/audits/prowlarr-2026-10-15/tools/walk_restore.py diff --git a/documentation/audits/prowlarr-2026-10-15/README.md b/documentation/audits/prowlarr-2026-10-15/README.md new file mode 100644 index 00000000..96c66d2c --- /dev/null +++ b/documentation/audits/prowlarr-2026-10-15/README.md @@ -0,0 +1,33 @@ +# Prowlarr into the live catalog — evidence (R-1048, 2026-10-15) + +The record that cites this folder: `app-catalog-felhom.eu/onboarding/prowlarr.md` (61 checks). The design it follows: +`documentation/architecture/15-media-library.md` §6 (the links) and `09-update-architecture.md` §6.4 (the ladder). + +## The venues and what each run proves + +| folder | venue | what it proves | +|---|---|---| +| `bench/` | LXC 9401 on demo-hp (`/opt/upg`) | licence, tags, the official-image check, first start from birth (memory, tools, files, analytics on by default), the update test `MV-prowlarr` (proven; abort starts-and-serves; 10-minute soak 7.6 %), C3 negative control (failed, as it must), volume-persistence CLEAN | +| `box/walk.txt`, `walk-results.json` | 9202, drill catalog, run 1 | install window as a stranger, the setup gate, the first account, sign-in right and wrong, „Kész", the stranger after it, six wrong passwords, the address the app logs, remove with data. **Its Update ran with an EMPTY `.felhom.yml`** — see run 2 | +| `box/run2-empty-meta/` | 9202, run 2 | **not a measurement of the template** — the test tool emptied the drill `.felhom.yml` (README there). One product fact: a restore brings the app back at the image in the backup | +| `box/run3/` | 9202, drill catalog with the full template | the step version-2.5.2.5491 -> version-2.6.5.5623 through the guarded Update (the box verdict `box/prowlarr/box-verdict-prowlarr.json`), seed -> the Update's per-app backup -> remove keeping backups -> restore -> seed read back; probe negative control; volume size | +| `box9201/` | demo-hp 9201, LIVE catalog, after the publish | the link read-back: Sonarr then Prowlarr, „Bekötés most", the link and its test, the sync command; remove both keeping data | +| `tools/` | DooPlex | the scripts that ran (passwords and keys never printed) | + +## The test-tool bug (fixed) + +`tools/walk_prowlarr.py` bumped the drill template with `open(fy, "w").write(re.sub(…, open(fy).read(), …))`: the write +truncates before the read, so drill commit f2b0cc1 emptied `templates/prowlarr/.felhom.yml`. The product then ran the +app with no deploy fields (`env: {}`, traefik rule `Host(\`.enkisfelhom.hu\`)`). Found in run 2 by the gate's `not +routed (404)` lines and the 0-byte file in the stack folder. Fixed in the tool (read first, then write); drill commit +e917a58 restored the file; run 3 repeated the step. Run 1's checks before the bump stand. + +## Findings and where they went + +- Prowlarr sends anonymous usage data by default → the template seeds it off (template; no row, fixed). +- `/initialize.json` gives the API key to anyone before the first account → the setup gate hides it; after the setup + it redirects to the login (measured; FIRST-ADMIN row). +- No lock-out after wrong passwords → a stranger cannot lock the household out (the property `09` §3 decisions 57–60 + protect); no row. +- The box links Prowlarr only when Sonarr/Radarr come up, or on „Bekötés most" → the copy says so; the register row is + in `OPEN-ITEMS.md` (filed in this session, see `box9201/`). diff --git a/documentation/audits/prowlarr-2026-10-15/bench/01-licence.txt b/documentation/audits/prowlarr-2026-10-15/bench/01-licence.txt new file mode 100644 index 00000000..b8a130e8 --- /dev/null +++ b/documentation/audits/prowlarr-2026-10-15/bench/01-licence.txt @@ -0,0 +1,3 @@ +404: Not Found GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + diff --git a/documentation/audits/prowlarr-2026-10-15/bench/01-official-image.txt b/documentation/audits/prowlarr-2026-10-15/bench/01-official-image.txt new file mode 100644 index 00000000..5af66584 --- /dev/null +++ b/documentation/audits/prowlarr-2026-10-15/bench/01-official-image.txt @@ -0,0 +1,8 @@ +== 2026-10-15: does the Prowlarr project publish an image of its own? +hub.docker.com/v2/repositories/prowlarr/prowlarr/ -> 404 +hub.docker.com/v2/repositories/servarr/prowlarr/ -> 404 +hub.docker.com/v2/repositories/linuxserver/prowlarr/ -> 200 +ghcr.io anonymous pull token for prowlarr/prowlarr -> 403 +ghcr.io anonymous pull token for linuxserver/prowlarr -> 200 +== the project's README, docker lines: +[![Docker Pulls](https://img.shields.io/docker/pulls/hotio/prowlarr.svg)](https://wiki.servarr.com/prowlarr/installation/docker) diff --git a/documentation/audits/prowlarr-2026-10-15/bench/01-tags.txt b/documentation/audits/prowlarr-2026-10-15/bench/01-tags.txt new file mode 100644 index 00000000..8fafbaa7 --- /dev/null +++ b/documentation/audits/prowlarr-2026-10-15/bench/01-tags.txt @@ -0,0 +1,9 @@ +nightly 2026-10-14T01:17:17 ['amd64', 'arm64'] sha256:5944ab837fcc +version-2.6.5.5623 2026-10-07T10:52:59 ['amd64', 'arm64'] sha256:f9151e5bc102 +latest 2026-10-07T10:52:51 ['amd64', 'arm64'] sha256:f9151e5bc102 +develop 2026-10-07T10:52:25 ['amd64', 'arm64'] sha256:e7a05286500b +v2.6.5.5623 2026-09-16T18:13:53Z False +v2.6.4.5611 2026-09-06T11:41:32Z True +v2.6.3.5592 2026-08-31T18:00:16Z True +v2.6.2.5562 2026-08-23T08:12:32Z True +v2.6.1.5509 2026-08-02T10:12:35Z True diff --git a/documentation/audits/prowlarr-2026-10-15/bench/01-version-tags.txt b/documentation/audits/prowlarr-2026-10-15/bench/01-version-tags.txt new file mode 100644 index 00000000..13ea0d66 --- /dev/null +++ b/documentation/audits/prowlarr-2026-10-15/bench/01-version-tags.txt @@ -0,0 +1,3 @@ +version-2.4.0.5397 2026-07-15 +version-2.5.2.5491 2026-09-16 +version-2.6.5.5623 2026-10-07 diff --git a/documentation/audits/prowlarr-2026-10-15/bench/02-first-start-2.6.5.txt b/documentation/audits/prowlarr-2026-10-15/bench/02-first-start-2.6.5.txt new file mode 100644 index 00000000..d2487f74 --- /dev/null +++ b/documentation/audits/prowlarr-2026-10-15/bench/02-first-start-2.6.5.txt @@ -0,0 +1,156 @@ +== image +lscr.io/linuxserver/prowlarr:version-2.6.5.5623 +id=sha256:cc10ec0691c72ac0282a72a61032afa138a3ee4f1c6297c66e60036b737ce4eb digest=lscr.io/linuxserver/prowlarr@sha256:f9151e5bc1025c6d0a630d503210cdcb6bb55a7cc098562609d96a408d838902 size=185390329 arch=amd64 +== tools in the image (one per run) +curl: /usr/bin/curl +wget: /usr/bin/wget +sh: /bin/sh +bash: /bin/bash +python3: +node: +== the service's run script (linuxserver s6) +#!/usr/bin/with-contenv bash +# shellcheck shell=bash + +if [[ -z ${LSIO_NON_ROOT_USER} ]]; then + exec \ + s6-notifyoncheck -d -n 300 -w 1000 \ + cd /app/prowlarr/bin s6-setuidgid abc /app/prowlarr/bin/Prowlarr \ + -nobrowser -data=/config +else + exec \ + s6-notifyoncheck -d -n 300 -w 1000 \ + cd /app/prowlarr/bin /app/prowlarr/bin/Prowlarr \ + -nobrowser -data=/config +fi +init-prowlarr-config +svc-prowlarr +== memory from birth (cgroup /sys/fs/cgroup/system.slice/docker-cd60831859f2819d7d5312216a6815836726cb4934aeb7b89fa35f7de0bb33ec.scope), every 2 s for 150 s +peak anon 44 MiB; oom_kill 0; restarts 0; health-like: 200 +== process list + PID USER COMMAND + 1 root /package/admin/s6/command/s6-svscan -d4 -- /run/service + 17 root s6-supervise s6-linux-init-shutdownd + 20 root /package/admin/s6-linux-init/command/s6-linux-init-shutdownd -d3 -c /run/s6/basedir -g 3000 -C -B + 36 root s6-supervise svc-cron + 37 root s6-supervise s6rc-oneshot-runner + 38 root s6-supervise s6rc-fdholder + 39 root s6-supervise svc-prowlarr + 47 root /package/admin/s6/command/s6-ipcserverd -1 -- /package/admin/s6/command/s6-ipcserver-access -v0 -E -l0 -i data/rules -- /package/admin/s6/command/s6-sudod -t 30000 -- /package/admin/s6-rc/command/s6-rc-oneshot-run -l ../.. -- + 177 root busybox crond -f -S -l 5 + 270 root ps -o pid,user,args +== config.xml after the first start (ApiKey redacted) + + * + 9696 + 6969 + False + True + + None + Enabled + + master + debug + + + + + Prowlarr + Docker +== host config through the app's own API (analytics, update, auth) +{'analyticsEnabled': True, 'updateAutomatically': False, 'updateMechanism': 'docker', 'authenticationMethod': 'none', 'authenticationRequired': 'enabled', 'bindAddress': '*', 'port': 9696, 'proxyEnabled': False, 'logLevel': 'debug'} +== open TCP connections from the container (after 150 s) +== first-start log (60 lines) +[migrations] started +[migrations] no migrations found +─────────────────────────────────────── + + ██╗ ███████╗██╗ ██████╗ + ██║ ██╔════╝██║██╔═══██╗ + ██║ ███████╗██║██║ ██║ + ██║ ╚════██║██║██║ ██║ + ███████╗███████║██║╚██████╔╝ + ╚══════╝╚══════╝╚═╝ ╚═════╝ + + Brought to you by linuxserver.io +─────────────────────────────────────── + +To support the app dev(s) visit: +Prowlarr: https://opencollective.com/prowlarr + +To support LSIO projects visit: +https://www.linuxserver.io/donate/ + +─────────────────────────────────────── +GID/UID +─────────────────────────────────────── + +User UID: 1000 +User GID: 1000 +─────────────────────────────────────── +Linuxserver.io version: 2.6.5.5623-ls163 +Build-date: 2026-10-07T10:41:33+00:00 +─────────────────────────────────────── + +[custom-init] No custom files found, skipping... +[Info] Bootstrap: Starting Prowlarr - /app/prowlarr/bin/Prowlarr - Version 2.6.5.5623 +[Info] AppFolderInfo: Data directory is being overridden to [/config] +[Debug] Bootstrap: Starting in Interactive mode +[Info] AppFolderInfo: Data directory is being overridden to [/config] +[Info] AppFolderInfo: Data directory is being overridden to [/config] +[Warn] Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager: No XML encryptor configured. Key {47654387-96ce-48e3-975d-a24d2f52e390} may be persisted to storage in unencrypted form. +[Trace] EventAggregator: Publishing ConfigFileSavedEvent +[Info] MigrationController: *** Migrating data source=/config/prowlarr.db;cache size=-20000;datetimekind=Utc;journal mode=Wal;pooling=True;version=3;busytimeout=1000 *** +[Info] FluentMigrator.Runner.MigrationRunner: DatabaseEngineVersionCheck migrating +[Info] FluentMigrator.Runner.MigrationRunner: PerformDBOperation +[Info] NzbDrone.Core.Datastore.Migration.Framework.NzbDroneSQLiteProcessor: Performing DB Operation +[Info] DatabaseEngineVersionCheck: SQLite 3.53.4 +[Info] FluentMigrator.Runner.MigrationRunner: => 0.0873453s +[Info] FluentMigrator.Runner.MigrationRunner: DatabaseEngineVersionCheck migrated +[Info] FluentMigrator.Runner.MigrationRunner: => 0.0924168s +[Info] FluentMigrator.Runner.MigrationRunner: VersionMigration migrating +[Info] NzbDrone.Core.Datastore.Migration.Framework.NzbDroneSQLiteProcessor: Beginning Transaction +[Info] FluentMigrator.Runner.MigrationRunner: CreateTable VersionInfo +[Info] NzbDrone.Core.Datastore.Migration.Framework.NzbDroneSQLiteProcessor: CREATE TABLE "VersionInfo" ("Version" INTEGER NOT NULL) +[Info] FluentMigrator.Runner.MigrationRunner: => 0.0269859s +[Info] NzbDrone.Core.Datastore.Migration.Framework.NzbDroneSQLiteProcessor: Committing Transaction +[Info] FluentMigrator.Runner.MigrationRunner: VersionMigration migrated +[Info] FluentMigrator.Runner.MigrationRunner: => 0.0354473s +[Info] FluentMigrator.Runner.MigrationRunner: VersionUniqueMigration migrating +[Info] NzbDrone.Core.Datastore.Migration.Framework.NzbDroneSQLiteProcessor: Beginning Transaction +[Info] FluentMigrator.Runner.MigrationRunner: CreateIndex VersionInfo (Version) +[Info] NzbDrone.Core.Datastore.Migration.Framework.NzbDroneSQLiteProcessor: CREATE UNIQUE INDEX "UC_Version" ON "VersionInfo" ("Version" ASC) +[Info] FluentMigrator.Runner.MigrationRunner: => 0.0022584s +== files the app wrote under /config +/config +/config/config.xml +/config/prowlarr.db-wal +/config/logs +/config/logs/prowlarr.debug.txt +/config/logs/prowlarr.txt +/config/prowlarr.pid +/config/asp +/config/asp/key-47654387-96ce-48e3-975d-a24d2f52e390.xml +/config/prowlarr.db +/config/prowlarr.db-shm +/config/Sentry +/config/Sentry/FDD003821460B16D4E0D4235129BD6BFAF29B751 +/config/logs.db +/config/Definitions +/config/Definitions/hdonly-api.yml +/config/Definitions/snowpt.yml +/config/Definitions/tribalmixes.yml +/config/Definitions/huntorrent.yml +/config/Definitions/eztvl.yml +/config/Definitions/fappaizuri.yml +/config/Definitions/racing4everyone-api.yml +/config/Definitions/crazyspirits-api.yml +/config/Definitions/animetorrentsro.yml +/config/Definitions/leech24.yml +/config/Definitions/fenyarnyek-tracker.yml +/config/Definitions/kinorun.yml +/config/Definitions/zenith.yml +/config/Definitions/dimeadozen.yml +/config/Definitions/pussytorrents.yml +== removed: 0 containers, 0 volumes diff --git a/documentation/audits/prowlarr-2026-10-15/bench/03-upgrade-test.txt b/documentation/audits/prowlarr-2026-10-15/bench/03-upgrade-test.txt new file mode 100644 index 00000000..a66a5228 --- /dev/null +++ b/documentation/audits/prowlarr-2026-10-15/bench/03-upgrade-test.txt @@ -0,0 +1,47 @@ + "verdict": "proven", + "seed_read_before": true, + "seed_read_after": true, + "healthy_after": true, + "migration_observed": "\u001b[2Kprowlarr | [migrations] started", + "abort": "starts-and-serves", + "abort_detail": null, + "engine_state_after": null, + "memory": { + "soak_s": 605.5, + "requested_s": 600, + "requests": 11961, + "codes": { + "200": 11961 + }, + "first_kill": null, + "containers": { + "prowlarr": { + "limit": 536870912, + "peak": 113446912, + "peak_pct": 0.211, + "anon_peak_sampled": 41000960, + "anon_peak_pct": 0.076, + "swap_peak": 0, + "oom_kills": 0, + "restarts": 0, + "oomkilled_flag": false, + "measured": true + } + }, + "unmeasured": [], + "venue_swap_bytes": 0, + "load": "reached" + }, + "marks": [], + "bench_overrides": null, + "duration_s": 30.9, + "measured_at": "2026-10-15T10:10:50Z", + "evidence": "evidence/MV-prowlarr", + "scratch_cleared": [], + "files_changed": [], + "files_changed_detail": [], + "files_ignored": [], + "total_s": 724.3 +} + +[exited with code 0] diff --git a/documentation/audits/prowlarr-2026-10-15/bench/04-c3-and-teardown.txt b/documentation/audits/prowlarr-2026-10-15/bench/04-c3-and-teardown.txt new file mode 100644 index 00000000..2fb3650f --- /dev/null +++ b/documentation/audits/prowlarr-2026-10-15/bench/04-c3-and-teardown.txt @@ -0,0 +1,5 @@ +/opt/upg/evidence/C3 +/opt/upg/evidence/C3/verdict.json failed NEGATIVE control: the TO image starts and exits immediately +removed 3 volumes of this run +1 +status: stopped diff --git a/documentation/audits/prowlarr-2026-10-15/bench/C3-verdict.json b/documentation/audits/prowlarr-2026-10-15/bench/C3-verdict.json new file mode 100644 index 00000000..d2b773f6 --- /dev/null +++ b/documentation/audits/prowlarr-2026-10-15/bench/C3-verdict.json @@ -0,0 +1,31 @@ +{ + "harness_version": 5, + "edge": "C3", + "app": "privatebin", + "note": "NEGATIVE control: the TO image starts and exits immediately", + "from": { + "privatebin": "privatebin/pdo:2.0.5" + }, + "to": { + "privatebin": "alpine:3.20" + }, + "verdict": "failed", + "seed_read_before": true, + "seed_read_after": false, + "healthy_after": false, + "migration_observed": null, + "abort": "starts-and-serves", + "abort_detail": null, + "engine_state_after": null, + "memory": null, + "marks": [], + "bench_overrides": null, + "duration_s": 420.8, + "measured_at": "2026-10-15T09:58:46Z", + "evidence": "evidence/C3", + "scratch_cleared": [], + "files_changed": [], + "files_changed_detail": [], + "files_ignored": [], + "total_s": 485.2 +} \ No newline at end of file diff --git a/documentation/audits/prowlarr-2026-10-15/bench/MV-prowlarr/abort-states.json b/documentation/audits/prowlarr-2026-10-15/bench/MV-prowlarr/abort-states.json new file mode 100644 index 00000000..bd4e5ba0 --- /dev/null +++ b/documentation/audits/prowlarr-2026-10-15/bench/MV-prowlarr/abort-states.json @@ -0,0 +1,8 @@ +{ + "prowlarr": { + "status": "running", + "health": "healthy", + "restarts": 0, + "exit": 0 + } +} \ No newline at end of file diff --git a/documentation/audits/prowlarr-2026-10-15/bench/MV-prowlarr/compose-final.log b/documentation/audits/prowlarr-2026-10-15/bench/MV-prowlarr/compose-final.log new file mode 100644 index 00000000..44c13c76 --- /dev/null +++ b/documentation/audits/prowlarr-2026-10-15/bench/MV-prowlarr/compose-final.log @@ -0,0 +1,59 @@ +prowlarr | [migrations] started +prowlarr | [migrations] no migrations found +prowlarr | ─────────────────────────────────────── +prowlarr | +prowlarr | ██╗ ███████╗██╗ ██████╗ +prowlarr | ██║ ██╔════╝██║██╔═══██╗ +prowlarr | ██║ ███████╗██║██║ ██║ +prowlarr | ██║ ╚════██║██║██║ ██║ +prowlarr | ███████╗███████║██║╚██████╔╝ +prowlarr | ╚══════╝╚══════╝╚═╝ ╚═════╝ +prowlarr | +prowlarr | Brought to you by linuxserver.io +prowlarr | ─────────────────────────────────────── +prowlarr | +prowlarr | To support the app dev(s) visit: +prowlarr | Prowlarr: https://opencollective.com/prowlarr +prowlarr | +prowlarr | To support LSIO projects visit: +prowlarr | https://www.linuxserver.io/donate/ +prowlarr | +prowlarr | ─────────────────────────────────────── +prowlarr | GID/UID +prowlarr | ─────────────────────────────────────── +prowlarr | +prowlarr | User UID: 1000 +prowlarr | User GID: 1000 +prowlarr | ─────────────────────────────────────── +prowlarr | Linuxserver.io version: 2.5.2.5491-ls160 +prowlarr | Build-date: 2026-09-16T08:53:07+00:00 +prowlarr | ─────────────────────────────────────── +prowlarr | +prowlarr | [custom-init] No custom files found, skipping... +prowlarr | [Info] Bootstrap: Starting Prowlarr - /app/prowlarr/bin/Prowlarr - Version 2.5.2.5491 +prowlarr | [Info] AppFolderInfo: Data directory is being overridden to [/config] +prowlarr | [Debug] Bootstrap: Console selected +prowlarr | [Info] AppFolderInfo: Data directory is being overridden to [/config] +prowlarr | [Info] AppFolderInfo: Data directory is being overridden to [/config] +prowlarr | [Info] MigrationController: *** Migrating data source=/config/prowlarr.db;cache size=-20000;datetimekind=Utc;journal mode=Wal;pooling=True;version=3;busytimeout=1000 *** +prowlarr | [Info] FluentMigrator.Runner.MigrationRunner: DatabaseEngineVersionCheck migrating +prowlarr | [Info] FluentMigrator.Runner.MigrationRunner: PerformDBOperation +prowlarr | [Info] NzbDrone.Core.Datastore.Migration.Framework.NzbDroneSQLiteProcessor: Performing DB Operation +prowlarr | [Info] DatabaseEngineVersionCheck: SQLite 3.50.4 +prowlarr | [Info] FluentMigrator.Runner.MigrationRunner: => 0.0542648s +prowlarr | [Info] FluentMigrator.Runner.MigrationRunner: DatabaseEngineVersionCheck migrated +prowlarr | [Info] FluentMigrator.Runner.MigrationRunner: => 0.0584528s +prowlarr | [Info] MigrationController: *** Migrating data source=/config/logs.db;cache size=-20000;datetimekind=Utc;journal mode=Wal;pooling=True;version=3;busytimeout=1000 *** +prowlarr | [Info] FluentMigrator.Runner.MigrationRunner: DatabaseEngineVersionCheck migrating +prowlarr | [Info] FluentMigrator.Runner.MigrationRunner: PerformDBOperation +prowlarr | [Info] NzbDrone.Core.Datastore.Migration.Framework.NzbDroneSQLiteProcessor: Performing DB Operation +prowlarr | [Info] DatabaseEngineVersionCheck: SQLite 3.50.4 +prowlarr | [Info] FluentMigrator.Runner.MigrationRunner: => 0.005874s +prowlarr | [Info] FluentMigrator.Runner.MigrationRunner: DatabaseEngineVersionCheck migrated +prowlarr | [Info] FluentMigrator.Runner.MigrationRunner: => 0.0061016s +prowlarr | [Info] Microsoft.Hosting.Lifetime: Now listening on: http://[::]:9696 +prowlarr | [Info] CommandExecutor: Starting 3 threads for tasks. +prowlarr | [ls.io-init] done. +prowlarr | [Info] Microsoft.Hosting.Lifetime: Application started. Press Ctrl+C to shut down. +prowlarr | [Info] Microsoft.Hosting.Lifetime: Hosting environment: Production +prowlarr | [Info] Microsoft.Hosting.Lifetime: Content root path: /app/prowlarr/bin diff --git a/documentation/audits/prowlarr-2026-10-15/bench/MV-prowlarr/engine-state.json b/documentation/audits/prowlarr-2026-10-15/bench/MV-prowlarr/engine-state.json new file mode 100644 index 00000000..ec747fa4 --- /dev/null +++ b/documentation/audits/prowlarr-2026-10-15/bench/MV-prowlarr/engine-state.json @@ -0,0 +1 @@ +null \ No newline at end of file diff --git a/documentation/audits/prowlarr-2026-10-15/bench/MV-prowlarr/files-after-detail.json b/documentation/audits/prowlarr-2026-10-15/bench/MV-prowlarr/files-after-detail.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/documentation/audits/prowlarr-2026-10-15/bench/MV-prowlarr/files-after-detail.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/documentation/audits/prowlarr-2026-10-15/bench/MV-prowlarr/files-after.json b/documentation/audits/prowlarr-2026-10-15/bench/MV-prowlarr/files-after.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/documentation/audits/prowlarr-2026-10-15/bench/MV-prowlarr/files-after.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/documentation/audits/prowlarr-2026-10-15/bench/MV-prowlarr/files-before-detail.json b/documentation/audits/prowlarr-2026-10-15/bench/MV-prowlarr/files-before-detail.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/documentation/audits/prowlarr-2026-10-15/bench/MV-prowlarr/files-before-detail.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/documentation/audits/prowlarr-2026-10-15/bench/MV-prowlarr/files-before.json b/documentation/audits/prowlarr-2026-10-15/bench/MV-prowlarr/files-before.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/documentation/audits/prowlarr-2026-10-15/bench/MV-prowlarr/files-before.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/documentation/audits/prowlarr-2026-10-15/bench/MV-prowlarr/memory-samples.json b/documentation/audits/prowlarr-2026-10-15/bench/MV-prowlarr/memory-samples.json new file mode 100644 index 00000000..f6d4d547 --- /dev/null +++ b/documentation/audits/prowlarr-2026-10-15/bench/MV-prowlarr/memory-samples.json @@ -0,0 +1,762 @@ +[ + { + "t": 15.1, + "containers": { + "prowlarr": { + "limit": 536870912, + "current": 100339712, + "peak": 113446912, + "anon": 41000960, + "swap": 0, + "swap_peak": 0, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 300 + }, + { + "t": 30.2, + "containers": { + "prowlarr": { + "limit": 536870912, + "current": 99377152, + "peak": 113446912, + "anon": 39952384, + "swap": 0, + "swap_peak": 0, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 596 + }, + { + "t": 45.4, + "containers": { + "prowlarr": { + "limit": 536870912, + "current": 97824768, + "peak": 113446912, + "anon": 38797312, + "swap": 0, + "swap_peak": 0, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 896 + }, + { + "t": 60.5, + "containers": { + "prowlarr": { + "limit": 536870912, + "current": 96886784, + "peak": 113446912, + "anon": 36749312, + "swap": 0, + "swap_peak": 0, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 1196 + }, + { + "t": 75.7, + "containers": { + "prowlarr": { + "limit": 536870912, + "current": 95993856, + "peak": 113446912, + "anon": 36790272, + "swap": 0, + "swap_peak": 0, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 1493 + }, + { + "t": 90.8, + "containers": { + "prowlarr": { + "limit": 536870912, + "current": 97419264, + "peak": 113446912, + "anon": 38031360, + "swap": 0, + "swap_peak": 0, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 1793 + }, + { + "t": 105.9, + "containers": { + "prowlarr": { + "limit": 536870912, + "current": 97075200, + "peak": 113446912, + "anon": 36839424, + "swap": 0, + "swap_peak": 0, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 2093 + }, + { + "t": 121.0, + "containers": { + "prowlarr": { + "limit": 536870912, + "current": 96071680, + "peak": 113446912, + "anon": 36798464, + "swap": 0, + "swap_peak": 0, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 2389 + }, + { + "t": 136.2, + "containers": { + "prowlarr": { + "limit": 536870912, + "current": 96092160, + "peak": 113446912, + "anon": 36769792, + "swap": 0, + "swap_peak": 0, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 2689 + }, + { + "t": 151.3, + "containers": { + "prowlarr": { + "limit": 536870912, + "current": 97271808, + "peak": 113446912, + "anon": 36802560, + "swap": 0, + "swap_peak": 0, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 2989 + }, + { + "t": 166.5, + "containers": { + "prowlarr": { + "limit": 536870912, + "current": 96870400, + "peak": 113446912, + "anon": 36802560, + "swap": 0, + "swap_peak": 0, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 3287 + }, + { + "t": 181.6, + "containers": { + "prowlarr": { + "limit": 536870912, + "current": 97411072, + "peak": 113446912, + "anon": 36765696, + "swap": 0, + "swap_peak": 0, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 3586 + }, + { + "t": 196.7, + "containers": { + "prowlarr": { + "limit": 536870912, + "current": 96997376, + "peak": 113446912, + "anon": 36737024, + "swap": 0, + "swap_peak": 0, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 3886 + }, + { + "t": 211.9, + "containers": { + "prowlarr": { + "limit": 536870912, + "current": 97185792, + "peak": 113446912, + "anon": 36786176, + "swap": 0, + "swap_peak": 0, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 4186 + }, + { + "t": 227.0, + "containers": { + "prowlarr": { + "limit": 536870912, + "current": 97894400, + "peak": 113446912, + "anon": 36818944, + "swap": 0, + "swap_peak": 0, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 4486 + }, + { + "t": 242.1, + "containers": { + "prowlarr": { + "limit": 536870912, + "current": 101642240, + "peak": 113446912, + "anon": 39890944, + "swap": 0, + "swap_peak": 0, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 4783 + }, + { + "t": 257.3, + "containers": { + "prowlarr": { + "limit": 536870912, + "current": 98832384, + "peak": 113446912, + "anon": 37060608, + "swap": 0, + "swap_peak": 0, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 5083 + }, + { + "t": 272.4, + "containers": { + "prowlarr": { + "limit": 536870912, + "current": 99069952, + "peak": 113446912, + "anon": 37146624, + "swap": 0, + "swap_peak": 0, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 5383 + }, + { + "t": 287.5, + "containers": { + "prowlarr": { + "limit": 536870912, + "current": 98840576, + "peak": 113446912, + "anon": 37117952, + "swap": 0, + "swap_peak": 0, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 5679 + }, + { + "t": 302.7, + "containers": { + "prowlarr": { + "limit": 536870912, + "current": 98979840, + "peak": 113446912, + "anon": 37146624, + "swap": 0, + "swap_peak": 0, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 5979 + }, + { + "t": 317.8, + "containers": { + "prowlarr": { + "limit": 536870912, + "current": 99758080, + "peak": 113446912, + "anon": 37097472, + "swap": 0, + "swap_peak": 0, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 6279 + }, + { + "t": 332.9, + "containers": { + "prowlarr": { + "limit": 536870912, + "current": 99631104, + "peak": 113446912, + "anon": 37072896, + "swap": 0, + "swap_peak": 0, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 6579 + }, + { + "t": 348.1, + "containers": { + "prowlarr": { + "limit": 536870912, + "current": 99631104, + "peak": 113446912, + "anon": 37072896, + "swap": 0, + "swap_peak": 0, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 6876 + }, + { + "t": 363.2, + "containers": { + "prowlarr": { + "limit": 536870912, + "current": 100147200, + "peak": 113446912, + "anon": 37019648, + "swap": 0, + "swap_peak": 0, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 7176 + }, + { + "t": 378.3, + "containers": { + "prowlarr": { + "limit": 536870912, + "current": 99917824, + "peak": 113446912, + "anon": 36990976, + "swap": 0, + "swap_peak": 0, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 7475 + }, + { + "t": 393.5, + "containers": { + "prowlarr": { + "limit": 536870912, + "current": 100225024, + "peak": 113446912, + "anon": 36990976, + "swap": 0, + "swap_peak": 0, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 7775 + }, + { + "t": 408.6, + "containers": { + "prowlarr": { + "limit": 536870912, + "current": 100548608, + "peak": 113446912, + "anon": 36990976, + "swap": 0, + "swap_peak": 0, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 8073 + }, + { + "t": 423.7, + "containers": { + "prowlarr": { + "limit": 536870912, + "current": 100618240, + "peak": 113446912, + "anon": 37019648, + "swap": 0, + "swap_peak": 0, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 8373 + }, + { + "t": 438.8, + "containers": { + "prowlarr": { + "limit": 536870912, + "current": 100392960, + "peak": 113446912, + "anon": 36990976, + "swap": 0, + "swap_peak": 0, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 8672 + }, + { + "t": 454.0, + "containers": { + "prowlarr": { + "limit": 536870912, + "current": 101130240, + "peak": 113446912, + "anon": 36626432, + "swap": 0, + "swap_peak": 0, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 8972 + }, + { + "t": 469.1, + "containers": { + "prowlarr": { + "limit": 536870912, + "current": 100212736, + "peak": 113446912, + "anon": 36691968, + "swap": 0, + "swap_peak": 0, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 9269 + }, + { + "t": 484.3, + "containers": { + "prowlarr": { + "limit": 536870912, + "current": 100794368, + "peak": 113446912, + "anon": 36712448, + "swap": 0, + "swap_peak": 0, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 9569 + }, + { + "t": 499.4, + "containers": { + "prowlarr": { + "limit": 536870912, + "current": 101376000, + "peak": 113446912, + "anon": 36872192, + "swap": 0, + "swap_peak": 0, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 9869 + }, + { + "t": 514.5, + "containers": { + "prowlarr": { + "limit": 536870912, + "current": 101093376, + "peak": 113446912, + "anon": 36835328, + "swap": 0, + "swap_peak": 0, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 10169 + }, + { + "t": 529.6, + "containers": { + "prowlarr": { + "limit": 536870912, + "current": 102236160, + "peak": 113446912, + "anon": 36786176, + "swap": 0, + "swap_peak": 0, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 10467 + }, + { + "t": 544.8, + "containers": { + "prowlarr": { + "limit": 536870912, + "current": 101814272, + "peak": 113446912, + "anon": 36835328, + "swap": 0, + "swap_peak": 0, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 10765 + }, + { + "t": 559.9, + "containers": { + "prowlarr": { + "limit": 536870912, + "current": 101646336, + "peak": 113446912, + "anon": 36814848, + "swap": 0, + "swap_peak": 0, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 11065 + }, + { + "t": 575.0, + "containers": { + "prowlarr": { + "limit": 536870912, + "current": 102891520, + "peak": 113446912, + "anon": 37228544, + "swap": 0, + "swap_peak": 0, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 11365 + }, + { + "t": 590.2, + "containers": { + "prowlarr": { + "limit": 536870912, + "current": 102367232, + "peak": 113446912, + "anon": 36925440, + "swap": 0, + "swap_peak": 0, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 11665 + }, + { + "t": 605.3, + "containers": { + "prowlarr": { + "limit": 536870912, + "current": 102789120, + "peak": 113446912, + "anon": 36958208, + "swap": 0, + "swap_peak": 0, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 11961 + } +] \ No newline at end of file diff --git a/documentation/audits/prowlarr-2026-10-15/bench/MV-prowlarr/migration-lines.txt b/documentation/audits/prowlarr-2026-10-15/bench/MV-prowlarr/migration-lines.txt new file mode 100644 index 00000000..ddf9c3cd --- /dev/null +++ b/documentation/audits/prowlarr-2026-10-15/bench/MV-prowlarr/migration-lines.txt @@ -0,0 +1,6 @@ +prowlarr | [migrations] started +prowlarr | [migrations] no migrations found +prowlarr | [Info] MigrationController: *** Migrating data source=/config/prowlarr.db;cache size=-20000;datetimekind=Utc;journal mode=Wal;pooling=True;version=3;busytimeout=1000 *** +prowlarr | [Info] FluentMigrator.Runner.MigrationRunner: DatabaseEngineVersionCheck migrating +prowlarr | [Info] FluentMigrator.Runner.MigrationRunner: PerformDBOperation +prowlarr | [Info] NzbDrone.Core.Datastore.Migration.Framework.NzbDroneSQLiteProcessor: Performing DB Operation \ No newline at end of file diff --git a/documentation/audits/prowlarr-2026-10-15/bench/MV-prowlarr/run.log b/documentation/audits/prowlarr-2026-10-15/bench/MV-prowlarr/run.log new file mode 100644 index 00000000..6c10ede5 --- /dev/null +++ b/documentation/audits/prowlarr-2026-10-15/bench/MV-prowlarr/run.log @@ -0,0 +1,59 @@ +[09:58:46] scratch drive folders cleared before FROM (R-656): none existed +[09:58:46] MV-prowlarr: deploying prowlarr at FROM {'prowlarr': 'lscr.io/linuxserver/prowlarr:version-2.5.2.5491'} +[09:59:33] FROM settled=True in 30.9s :: {"prowlarr": {"status": "running", "health": "healthy", "restarts": 0, "exit": 0}} +[09:59:33] fixture: the BOX walk's own (ProwlarrBox), through upgrade_boxport +[09:59:33] prowlarr: POST /tag felhom68458ecd http=201 +[09:59:33] prowlarr: POST /appprofile felhom-profile-3a9231 http=201 +[09:59:34] prowlarr: tag kept, app profile present=True +[09:59:34] C1 (seed reads back BEFORE): True +[09:59:34] MV-prowlarr: swapping to TO {'prowlarr': 'lscr.io/linuxserver/prowlarr:version-2.6.5.5623'} +[09:59:38] TO up -d rc=0 +[10:00:09] TO settled=True in 30.9s :: {"prowlarr": {"status": "running", "health": "healthy", "restarts": 0, "exit": 0}} +[10:00:09] migration lines observed: 6 +[10:00:09] prowlarr: tag kept, app profile present=True +[10:00:09] RESULT (seed reads back AFTER): True +[10:00:10] memory watch: 600s, 4 callers on 1 path(s) at 172.18.0.2:9696 +[10:00:25] + 15s prowlarr=95M/512M peak=108M kills=0 rs=0 reqs=300 +[10:00:40] + 30s prowlarr=94M/512M peak=108M kills=0 rs=0 reqs=596 +[10:00:55] + 45s prowlarr=93M/512M peak=108M kills=0 rs=0 reqs=896 +[10:01:10] + 60s prowlarr=92M/512M peak=108M kills=0 rs=0 reqs=1196 +[10:01:25] + 76s prowlarr=91M/512M peak=108M kills=0 rs=0 reqs=1493 +[10:01:41] + 91s prowlarr=92M/512M peak=108M kills=0 rs=0 reqs=1793 +[10:01:56] + 106s prowlarr=92M/512M peak=108M kills=0 rs=0 reqs=2093 +[10:02:11] + 121s prowlarr=91M/512M peak=108M kills=0 rs=0 reqs=2389 +[10:02:26] + 136s prowlarr=91M/512M peak=108M kills=0 rs=0 reqs=2689 +[10:02:41] + 151s prowlarr=92M/512M peak=108M kills=0 rs=0 reqs=2989 +[10:02:56] + 166s prowlarr=92M/512M peak=108M kills=0 rs=0 reqs=3287 +[10:03:11] + 182s prowlarr=92M/512M peak=108M kills=0 rs=0 reqs=3586 +[10:03:26] + 197s prowlarr=92M/512M peak=108M kills=0 rs=0 reqs=3886 +[10:03:42] + 212s prowlarr=92M/512M peak=108M kills=0 rs=0 reqs=4186 +[10:03:57] + 227s prowlarr=93M/512M peak=108M kills=0 rs=0 reqs=4486 +[10:04:12] + 242s prowlarr=96M/512M peak=108M kills=0 rs=0 reqs=4783 +[10:04:27] + 257s prowlarr=94M/512M peak=108M kills=0 rs=0 reqs=5083 +[10:04:42] + 272s prowlarr=94M/512M peak=108M kills=0 rs=0 reqs=5383 +[10:04:57] + 288s prowlarr=94M/512M peak=108M kills=0 rs=0 reqs=5679 +[10:05:12] + 303s prowlarr=94M/512M peak=108M kills=0 rs=0 reqs=5979 +[10:05:28] + 318s prowlarr=95M/512M peak=108M kills=0 rs=0 reqs=6279 +[10:05:43] + 333s prowlarr=95M/512M peak=108M kills=0 rs=0 reqs=6579 +[10:05:58] + 348s prowlarr=95M/512M peak=108M kills=0 rs=0 reqs=6876 +[10:06:13] + 363s prowlarr=95M/512M peak=108M kills=0 rs=0 reqs=7176 +[10:06:28] + 378s prowlarr=95M/512M peak=108M kills=0 rs=0 reqs=7475 +[10:06:43] + 394s prowlarr=95M/512M peak=108M kills=0 rs=0 reqs=7775 +[10:06:58] + 409s prowlarr=95M/512M peak=108M kills=0 rs=0 reqs=8073 +[10:07:13] + 424s prowlarr=95M/512M peak=108M kills=0 rs=0 reqs=8373 +[10:07:29] + 439s prowlarr=95M/512M peak=108M kills=0 rs=0 reqs=8672 +[10:07:44] + 454s prowlarr=96M/512M peak=108M kills=0 rs=0 reqs=8972 +[10:07:59] + 469s prowlarr=95M/512M peak=108M kills=0 rs=0 reqs=9269 +[10:08:14] + 484s prowlarr=96M/512M peak=108M kills=0 rs=0 reqs=9569 +[10:08:29] + 499s prowlarr=96M/512M peak=108M kills=0 rs=0 reqs=9869 +[10:08:44] + 514s prowlarr=96M/512M peak=108M kills=0 rs=0 reqs=10169 +[10:08:59] + 530s prowlarr=97M/512M peak=108M kills=0 rs=0 reqs=10467 +[10:09:15] + 545s prowlarr=97M/512M peak=108M kills=0 rs=0 reqs=10765 +[10:09:30] + 560s prowlarr=96M/512M peak=108M kills=0 rs=0 reqs=11065 +[10:09:45] + 575s prowlarr=98M/512M peak=108M kills=0 rs=0 reqs=11365 +[10:10:00] + 590s prowlarr=97M/512M peak=108M kills=0 rs=0 reqs=11665 +[10:10:15] + 605s prowlarr=98M/512M peak=108M kills=0 rs=0 reqs=11961 +[10:10:15] memory watch: killed=False tight=[] requests=11961 codes={'200': 11961} +[10:10:15] MV-prowlarr: ABORT — putting the FROM images back +[10:10:50] prowlarr: tag kept, app profile present=True +[10:10:50] ABORT: app came back in 30.9s; data present=True \ No newline at end of file diff --git a/documentation/audits/prowlarr-2026-10-15/bench/MV-prowlarr/to-full.log b/documentation/audits/prowlarr-2026-10-15/bench/MV-prowlarr/to-full.log new file mode 100644 index 00000000..6904edee --- /dev/null +++ b/documentation/audits/prowlarr-2026-10-15/bench/MV-prowlarr/to-full.log @@ -0,0 +1,60 @@ +prowlarr | [migrations] started +prowlarr | [migrations] no migrations found +prowlarr | ─────────────────────────────────────── +prowlarr | +prowlarr | ██╗ ███████╗██╗ ██████╗ +prowlarr | ██║ ██╔════╝██║██╔═══██╗ +prowlarr | ██║ ███████╗██║██║ ██║ +prowlarr | ██║ ╚════██║██║██║ ██║ +prowlarr | ███████╗███████║██║╚██████╔╝ +prowlarr | ╚══════╝╚══════╝╚═╝ ╚═════╝ +prowlarr | +prowlarr | Brought to you by linuxserver.io +prowlarr | ─────────────────────────────────────── +prowlarr | +prowlarr | To support the app dev(s) visit: +prowlarr | Prowlarr: https://opencollective.com/prowlarr +prowlarr | +prowlarr | To support LSIO projects visit: +prowlarr | https://www.linuxserver.io/donate/ +prowlarr | +prowlarr | ─────────────────────────────────────── +prowlarr | GID/UID +prowlarr | ─────────────────────────────────────── +prowlarr | +prowlarr | User UID: 1000 +prowlarr | User GID: 1000 +prowlarr | ─────────────────────────────────────── +prowlarr | Linuxserver.io version: 2.6.5.5623-ls163 +prowlarr | Build-date: 2026-10-07T10:41:33+00:00 +prowlarr | ─────────────────────────────────────── +prowlarr | +prowlarr | [custom-init] No custom files found, skipping... +prowlarr | [Info] Bootstrap: Starting Prowlarr - /app/prowlarr/bin/Prowlarr - Version 2.6.5.5623 +prowlarr | [Info] AppFolderInfo: Data directory is being overridden to [/config] +prowlarr | [Debug] Bootstrap: Starting in Interactive mode +prowlarr | [Info] AppFolderInfo: Data directory is being overridden to [/config] +prowlarr | [Info] AppFolderInfo: Data directory is being overridden to [/config] +prowlarr | [Info] MigrationController: *** Migrating data source=/config/prowlarr.db;cache size=-20000;datetimekind=Utc;journal mode=Wal;pooling=True;version=3;busytimeout=1000 *** +prowlarr | [Info] FluentMigrator.Runner.MigrationRunner: DatabaseEngineVersionCheck migrating +prowlarr | [Info] FluentMigrator.Runner.MigrationRunner: PerformDBOperation +prowlarr | [Info] NzbDrone.Core.Datastore.Migration.Framework.NzbDroneSQLiteProcessor: Performing DB Operation +prowlarr | [Info] DatabaseEngineVersionCheck: SQLite 3.53.4 +prowlarr | [Info] FluentMigrator.Runner.MigrationRunner: => 0.0508233s +prowlarr | [Info] FluentMigrator.Runner.MigrationRunner: DatabaseEngineVersionCheck migrated +prowlarr | [Info] FluentMigrator.Runner.MigrationRunner: => 0.0550323s +prowlarr | [Info] MigrationController: *** Migrating data source=/config/logs.db;cache size=-20000;datetimekind=Utc;journal mode=Wal;pooling=True;version=3;busytimeout=1000 *** +prowlarr | [Info] FluentMigrator.Runner.MigrationRunner: DatabaseEngineVersionCheck migrating +prowlarr | [Info] FluentMigrator.Runner.MigrationRunner: PerformDBOperation +prowlarr | [Info] NzbDrone.Core.Datastore.Migration.Framework.NzbDroneSQLiteProcessor: Performing DB Operation +prowlarr | [Info] DatabaseEngineVersionCheck: SQLite 3.53.4 +prowlarr | [Info] FluentMigrator.Runner.MigrationRunner: => 0.0062184s +prowlarr | [Info] FluentMigrator.Runner.MigrationRunner: DatabaseEngineVersionCheck migrated +prowlarr | [Info] FluentMigrator.Runner.MigrationRunner: => 0.0064463s +prowlarr | [Info] ConfigureHostFilteringOptions: Allowed Hosts is not configured, accepting requests for any host +prowlarr | [Info] Microsoft.Hosting.Lifetime: Now listening on: http://[::]:9696 +prowlarr | [Info] CommandExecutor: Starting 3 threads for tasks. +prowlarr | [ls.io-init] done. +prowlarr | [Info] Microsoft.Hosting.Lifetime: Application started. Press Ctrl+C to shut down. +prowlarr | [Info] Microsoft.Hosting.Lifetime: Hosting environment: Production +prowlarr | [Info] Microsoft.Hosting.Lifetime: Content root path: /app/prowlarr/bin diff --git a/documentation/audits/prowlarr-2026-10-15/bench/MV-prowlarr/to-states.json b/documentation/audits/prowlarr-2026-10-15/bench/MV-prowlarr/to-states.json new file mode 100644 index 00000000..bd4e5ba0 --- /dev/null +++ b/documentation/audits/prowlarr-2026-10-15/bench/MV-prowlarr/to-states.json @@ -0,0 +1,8 @@ +{ + "prowlarr": { + "status": "running", + "health": "healthy", + "restarts": 0, + "exit": 0 + } +} \ No newline at end of file diff --git a/documentation/audits/prowlarr-2026-10-15/bench/MV-prowlarr/verdict.json b/documentation/audits/prowlarr-2026-10-15/bench/MV-prowlarr/verdict.json new file mode 100644 index 00000000..e48d6769 --- /dev/null +++ b/documentation/audits/prowlarr-2026-10-15/bench/MV-prowlarr/verdict.json @@ -0,0 +1,56 @@ +{ + "harness_version": 5, + "edge": "MV-prowlarr", + "app": "prowlarr", + "note": "night 2026-09-23 within-a-major move: prowlarr=lscr.io/linuxserver/prowlarr:version-2.6.5.5623", + "from": { + "prowlarr": "lscr.io/linuxserver/prowlarr:version-2.5.2.5491" + }, + "to": { + "prowlarr": "lscr.io/linuxserver/prowlarr:version-2.6.5.5623" + }, + "verdict": "proven", + "seed_read_before": true, + "seed_read_after": true, + "healthy_after": true, + "migration_observed": "\u001b[2Kprowlarr | [migrations] started", + "abort": "starts-and-serves", + "abort_detail": null, + "engine_state_after": null, + "memory": { + "soak_s": 605.5, + "requested_s": 600, + "requests": 11961, + "codes": { + "200": 11961 + }, + "first_kill": null, + "containers": { + "prowlarr": { + "limit": 536870912, + "peak": 113446912, + "peak_pct": 0.211, + "anon_peak_sampled": 41000960, + "anon_peak_pct": 0.076, + "swap_peak": 0, + "oom_kills": 0, + "restarts": 0, + "oomkilled_flag": false, + "measured": true + } + }, + "unmeasured": [], + "venue_swap_bytes": 0, + "load": "reached" + }, + "marks": [], + "bench_overrides": null, + "duration_s": 30.9, + "measured_at": "2026-10-15T10:10:50Z", + "evidence": "evidence/MV-prowlarr", + "scratch_cleared": [], + "files_changed": [], + "files_changed_detail": [], + "files_ignored": [], + "total_s": 724.3 +} \ No newline at end of file diff --git a/documentation/audits/prowlarr-2026-10-15/bench/VP-prowlarr/prowlarr/probe.json b/documentation/audits/prowlarr-2026-10-15/bench/VP-prowlarr/prowlarr/probe.json new file mode 100644 index 00000000..cfca50d0 --- /dev/null +++ b/documentation/audits/prowlarr-2026-10-15/bench/VP-prowlarr/prowlarr/probe.json @@ -0,0 +1,157 @@ +{ + "probe": { + "app": "prowlarr", + "containers": [ + { + "diff_added": 205, + "diff_benign_db_touches": [], + "diff_data_dirs": [], + "diff_other_dirs": [ + { + "added": [], + "changed_count": 4, + "db_signature": false, + "dir": "/", + "files": [ + "etc", + "defaults", + "run", + "app" + ] + }, + { + "added": [], + "changed_count": 4, + "db_signature": false, + "dir": "/etc", + "files": [ + "crontabs", + "passwd-", + "s6-overlay", + "group-" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/etc/crontabs", + "files": [ + "root" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/etc/s6-overlay", + "files": [ + "s6-rc.d" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/etc/s6-overlay/s6-rc.d", + "files": [ + "init-adduser" + ] + }, + { + "added": [], + "changed_count": 1, + "db_signature": false, + "dir": "/etc/s6-overlay/s6-rc.d/init-adduser", + "files": [ + "branding" + ] + } + ], + "diff_token_dirs": [], + "diff_total": 219, + "diff_unresolved": [], + "exit": 0, + "gid": 0, + "health": "healthy", + "image": "lscr.io/linuxserver/prowlarr:version-2.6.5.5623", + "mounts": [ + { + "class": "named-declared", + "files": 591, + "name": "volgate-prowlarr_prowlarr_config", + "sample": [ + "config.xml", + "prowlarr.db-wal", + "prowlarr.pid", + "prowlarr.db", + "prowlarr.db-shm", + "logs.db", + "logs/prowlarr.debug.txt", + "logs/prowlarr.txt", + "asp/key-676faa09-8e2e-44e6-b101-6bb715cf2305.xml", + "Definitions/hdonly-api.yml", + "Definitions/snowpt.yml", + "Definitions/tribalmixes.yml", + "Definitions/huntorrent.yml", + "Definitions/eztvl.yml", + "Definitions/fappaizuri.yml", + "Definitions/racing4everyone-api.yml", + "Definitions/crazyspirits-api.yml", + "Definitions/animetorrentsro.yml", + "Definitions/leech24.yml", + "Definitions/fenyarnyek-tracker.yml", + "Definitions/kinorun.yml", + "Definitions/zenith.yml", + "Definitions/dimeadozen.yml", + "Definitions/pussytorrents.yml", + "Definitions/whiteangel.yml", + "Definitions/hdbao.yml", + "Definitions/borgzelle.yml", + "Definitions/bitagent.yml", + "Definitions/midnightscene.yml", + "Definitions/asiancinema.yml", + "Definitions/thorhub-api.yml", + "Definitions/hdolimpo-api.yml", + "Definitions/yuscene-api.yml", + "Definitions/ultrahd.yml", + "Definitions/blueroms.yml", + "Definitions/bwtorrents.yml", + "Definitions/torrentdownload.yml", + "Definitions/hddolby.yml", + "Definitions/totheglory2fa.yml", + "Definitions/selezen.yml" + ], + "source": "/var/lib/docker/volumes/volgate-prowlarr_prowlarr_config/_data", + "target": "/config", + "writable_by_app": "yes" + } + ], + "name": "prowlarr", + "restarts": null, + "status": "running", + "uid": 0 + } + ], + "declared_volumes": [ + "prowlarr_config" + ], + "env_keys": [ + "DOMAIN", + "HDD_PATH", + "IMPORT_PATH", + "SUBDOMAIN", + "TZ", + "USERDATA_PATH" + ], + "exercise": [ + "http://172.18.0.2:9696/ -> 200" + ], + "fixture": null, + "ports": [ + 9696 + ] + }, + "reasons": [], + "verdict": "CLEAN" +} \ No newline at end of file diff --git a/documentation/audits/prowlarr-2026-10-15/bench/vp-prowlarr.txt b/documentation/audits/prowlarr-2026-10-15/bench/vp-prowlarr.txt new file mode 100644 index 00000000..702db3a2 --- /dev/null +++ b/documentation/audits/prowlarr-2026-10-15/bench/vp-prowlarr.txt @@ -0,0 +1,9 @@ +/opt/upg/scripts/upgrade_fixtures.py:643: SyntaxWarning: invalid escape sequence '\$' + '| awk "/Session-Id/ {print \$2}"); ' +self-testing the prober (both directions)… + prober flags the R-156 signature and clears a correct template — trustworthy +prowlarr CLEAN + +1 clean · 0 broken · 0 undetermined (of 1 in scope) +volume-persistence gate OK +rc=0 diff --git a/documentation/audits/prowlarr-2026-10-15/box/00-attempt1-removed.txt b/documentation/audits/prowlarr-2026-10-15/box/00-attempt1-removed.txt new file mode 100644 index 00000000..6aa8bbc7 --- /dev/null +++ b/documentation/audits/prowlarr-2026-10-15/box/00-attempt1-removed.txt @@ -0,0 +1,6 @@ +11:54:45 attempt 1 (aborted by the walk script's own bug, after the deploy press): running +11:54:48 [X] stop -> 200 {'ok': True, 'message': 'Stack prowlarr stop completed'} +11:55:20 [X] remove (with drive data) -> 200 {'ok': True, 'data': {'removed': 'prowlarr', 'volumes_removed': ['prowlarr_prowlarr_config'], 'hdd_paths_removed': [], 'hdd_paths_preserved': [], 'hdd_note': 'A +11:55:28 [X] after remove: deployed=False leftovers='/opt/docker/stacks/prowlarr' +containers=0 volumes=0 stackdir=0 + diff --git a/documentation/audits/prowlarr-2026-10-15/box/00-repoint.txt b/documentation/audits/prowlarr-2026-10-15/box/00-repoint.txt new file mode 100644 index 00000000..6e37d751 --- /dev/null +++ b/documentation/audits/prowlarr-2026-10-15/box/00-repoint.txt @@ -0,0 +1,17 @@ +mirror main b5bb89c DRILL prowlarr: the live candidate (R-1048) at the PREVIOUS image version-2.5.2.5491 — the box walk's starting point on 9202 + +drill repo main: b5bb89c +2026/10/15 09:53:53 sync.go:498: [INFO] [sync] Updated prowlarr/.felhom.yml +2026/10/15 09:53:53 sync.go:245: [INFO] [sync] catalog moved 0f1e78b73101 -> b5bb89c2616e (1 new, 1 stack dir(s) changed) +2026/10/15 09:53:53 sync.go:283: [INFO] [sync] Catalog sync complete +2026/10/15 09:53:53 sync.go:126: [INFO] [sync] Initial sync: Sablonok frissítve — új: prowlarr; frissítve: immich + +26: repo_url: file:///opt/docker/felhom-controller/drill-catalog.git +27: sync_interval: 15m +-rw------- 1 root root 1944 Oct 12 20:52 /var/lib/docker/volumes/felhom-controller-data/_data/controller.yaml.pre-prowlarr1015 +mirror main b5bb89c DRILL prowlarr: the live candidate (R-1048) at the PREVIOUS image version-2.5.2.5491 — the box walk's starting point on 9202 +cache HEAD b5bb89c DRILL prowlarr: the live candidate (R-1048) at the PREVIOUS image version-2.5.2.5491 — the box walk's starting point on 9202 +file:///opt/docker/felhom-controller/drill-catalog.git + +16: image: lscr.io/linuxserver/prowlarr:version-2.5.2.5491 + diff --git a/documentation/audits/prowlarr-2026-10-15/box/99-restore.txt b/documentation/audits/prowlarr-2026-10-15/box/99-restore.txt new file mode 100644 index 00000000..81621d2b --- /dev/null +++ b/documentation/audits/prowlarr-2026-10-15/box/99-restore.txt @@ -0,0 +1,15 @@ +2026/10/15 09:59:58 sync.go:283: [INFO] [sync] Catalog sync complete +2026/10/15 09:59:58 sync.go:126: [INFO] [sync] Initial sync: Sablonok frissítve — frissítve: immich + +26: repo_url: https://gitea.dooplex.hu/admin/app-catalog-felhom.eu.git +27: sync_interval: 15m +-rw------- 1 root root 1944 Oct 12 20:52 /var/lib/docker/volumes/felhom-controller-data/_data/controller.yaml.pre-prowlarr1015 +mirror main f2b0cc1 DRILL prowlarr: lscr.io/linuxserver/prowlarr:version-2.5.2.5491 -> lscr.io/linuxserver/prowlarr:version-2.6.5.5623 (R-1048 box proof) +cache HEAD 3ff0cf3 Rule file: a release needs no golden; the floor carries it after the three test boxes; golden weekly (09 §3 decision 250, operator 2026-10-15) +https://gitea.dooplex.hu/admin/app-catalog-felhom.eu.git + +immich: box .felhom.yml == live origin/main: True +paperless-ngx: box .felhom.yml == live origin/main: True +sonarr: box .felhom.yml == live origin/main: True +decoy (prowlarr exists only in the drill): box has prowlarr template: 1 +ls: cannot access '/opt/docker/felhom-controller/drill-catalog.git': No such file or directory diff --git a/documentation/audits/prowlarr-2026-10-15/box/prowlarr/box-verdict-prowlarr-run1-empty-meta.json b/documentation/audits/prowlarr-2026-10-15/box/prowlarr/box-verdict-prowlarr-run1-empty-meta.json new file mode 100644 index 00000000..116541b1 --- /dev/null +++ b/documentation/audits/prowlarr-2026-10-15/box/prowlarr/box-verdict-prowlarr-run1-empty-meta.json @@ -0,0 +1,18 @@ +{ + "app": "prowlarr", + "venue": "box 9202 (drill catalog, controller 0.304.0), the product's guarded Update", + "from": { + "prowlarr": "lscr.io/linuxserver/prowlarr:version-2.5.2.5491" + }, + "to": { + "prowlarr": "lscr.io/linuxserver/prowlarr:version-2.6.5.5623" + }, + "verdict": "proven", + "seed_read_before": true, + "seed_read_after": true, + "healthy_after": true, + "duration_s": 84.1, + "final_phase": "done", + "measured_at": "2026-10-15T09:58:47Z", + "evidence": "felhom.eu/documentation/audits/prowlarr-2026-10-15/box/walk.txt" +} \ No newline at end of file diff --git a/documentation/audits/prowlarr-2026-10-15/box/prowlarr/box-verdict-prowlarr.json b/documentation/audits/prowlarr-2026-10-15/box/prowlarr/box-verdict-prowlarr.json new file mode 100644 index 00000000..f33358c7 --- /dev/null +++ b/documentation/audits/prowlarr-2026-10-15/box/prowlarr/box-verdict-prowlarr.json @@ -0,0 +1,18 @@ +{ + "app": "prowlarr", + "venue": "box 9202 (drill catalog), the product's guarded Update \u2014 run 3, the full .felhom.yml", + "from": { + "prowlarr": "lscr.io/linuxserver/prowlarr:version-2.5.2.5491" + }, + "to": { + "prowlarr": "lscr.io/linuxserver/prowlarr:version-2.6.5.5623" + }, + "verdict": "proven", + "seed_read_before": true, + "seed_read_after": true, + "healthy_after": true, + "duration_s": 26.7, + "final_phase": "done", + "measured_at": "2026-10-15T10:39:09Z", + "evidence": "felhom.eu/documentation/audits/prowlarr-2026-10-15/box/run3/restore.txt" +} \ No newline at end of file diff --git a/documentation/audits/prowlarr-2026-10-15/box/run2-empty-meta/10-repoint-restore-walk.txt b/documentation/audits/prowlarr-2026-10-15/box/run2-empty-meta/10-repoint-restore-walk.txt new file mode 100644 index 00000000..9699c671 --- /dev/null +++ b/documentation/audits/prowlarr-2026-10-15/box/run2-empty-meta/10-repoint-restore-walk.txt @@ -0,0 +1,9 @@ +mirror main e985077 DRILL prowlarr: back to version-2.5.2.5491 for the restore walk (R-1048, checklist 2.5) + +drill repo main: e985077 +27: sync_interval: 15m +-rw------- 1 root root 1944 Oct 12 20:52 /var/lib/docker/volumes/felhom-controller-data/_data/controller.yaml.pre-prowlarr1015 +mirror main e985077 DRILL prowlarr: back to version-2.5.2.5491 for the restore walk (R-1048, checklist 2.5) +cache HEAD e985077 DRILL prowlarr: back to version-2.5.2.5491 for the restore walk (R-1048, checklist 2.5) +file:///opt/docker/felhom-controller/drill-catalog.git + diff --git a/documentation/audits/prowlarr-2026-10-15/box/run2-empty-meta/README.md b/documentation/audits/prowlarr-2026-10-15/box/run2-empty-meta/README.md new file mode 100644 index 00000000..f618bffc --- /dev/null +++ b/documentation/audits/prowlarr-2026-10-15/box/run2-empty-meta/README.md @@ -0,0 +1,14 @@ +# Run 2 — NOT a measurement of the template (kept as a record) + +Run 1's drill bump (`walk_prowlarr.py`, drill commit f2b0cc1) opened the drill's `.felhom.yml` for writing BEFORE it read +it, so the file became EMPTY. Run 1's install, gate, first-admin, stranger and lock-out checks happened before that +commit and stand. Run 1's Update and run 2 as a whole ran with an EMPTY `.felhom.yml` on the box: no deploy fields, so +`app.yaml` `env: {}`, the traefik rule `Host(\`.enkisfelhom.hu\`)` and the app unreachable from outside (every +`gate: … not routed (404)` line). The seed still read back because the fixture talks to the app inside its container. + +The defect was in the test tool, not in the product. Drill commit e917a58 put the full file back; run 3 (`../run3/`) +re-proves the step and the restore with it, and writes `../prowlarr/box-verdict-prowlarr.json`. Run 1's verdict is kept +as `../prowlarr/box-verdict-prowlarr-run1-empty-meta.json`. + +One true fact from run 2, product behaviour: a restore after „remove, keep backups" brings the app back at the image +recorded in the backup (version-2.5.2.5491 here, the image before the Update), not at the catalog's newest. diff --git a/documentation/audits/prowlarr-2026-10-15/box/run2-empty-meta/restore-console.txt b/documentation/audits/prowlarr-2026-10-15/box/run2-empty-meta/restore-console.txt new file mode 100644 index 00000000..a5cdedad --- /dev/null +++ b/documentation/audits/prowlarr-2026-10-15/box/run2-empty-meta/restore-console.txt @@ -0,0 +1,123 @@ +mirror main e985077 DRILL prowlarr: back to version-2.5.2.5491 for the restore walk (R-1048, checklist 2.5) + +drill repo main: e985077 +12:23:13 [sync] the badge NEVER caught up to lscr.io/linuxserver/prowlarr:version-2.5.2.5491 in 78.3s — catalog_images = None +12:23:14 [1] deploy -> 202 {"ok": true, "message": "Telepítés elindítva – az állapot a kártyán követhető"} +12:23:38 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:23:43 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:23:48 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:23:53 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:23:58 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:24:03 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:24:08 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:24:13 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:24:18 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:24:23 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:24:28 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:24:33 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:24:38 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:24:43 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:24:49 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:24:54 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:24:59 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:25:04 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:25:09 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:25:14 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:25:19 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:25:24 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:25:29 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:25:34 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:25:39 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:25:44 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:25:49 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:25:54 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:25:59 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:26:04 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:26:09 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:26:14 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:26:19 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:26:24 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:26:29 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:26:34 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:26:40 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:26:45 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:26:50 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:26:55 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:27:00 app never answered on prowlarr/ping (last rc=0 code=404) +12:27:03 [1] running: lscr.io/linuxserver/prowlarr:version-2.5.2.5491 health=healthy +12:27:13 prowlarr: POST /tag felhom6a4ef5fa http=201 +12:27:16 prowlarr: POST /appprofile felhom-profile-16d260 http=201 +12:27:29 prowlarr: tag kept, app profile present=True +12:27:29 [2] seed {'tag': 'felhom6a4ef5fa', 'profile': 'felhom-profile-16d260'} read back: True +12:27:30 drill commit 03b0c0d343d8: -> lscr.io/linuxserver/prowlarr:version-2.6.5.5623 (push rc=0) +mirror main 03b0c0d DRILL prowlarr: the step -> lscr.io/linuxserver/prowlarr:version-2.6.5.5623 (R-1048, checklist 2.5) + +drill repo main: 03b0c0d +12:27:40 [6] Update -> 202 {'ok': True, 'data': {'accepted': True, 'completed': False}, 'message': 'Frissítés elindult – az állapot a kártyán követhető'} +12:27:40 + 0.0s phase=backing-up label=Biztonsági mentés készül a frissítés előtt… err=None hold=None +12:27:45 + 5.1s phase=safety-dump label=Adatbázis pillanatkép… err=None hold=None +12:27:46 + 6.2s phase=pulling label=Új verzió letöltése… err=None hold=None +12:27:56 + 16.4s phase=copying label=Az adatok másolása a frissítés előtt… err=None hold=None +12:28:02 + 22.6s phase=verifying label=Működés ellenőrzése… err=None hold=None +12:29:09 + 89.2s phase=done label=Frissítve err=None hold=None +12:29:09 [3] guarded Update (its backing-up phase is the per-app backup): final=done phases=['backing-up', 'safety-dump', 'pulling', 'copying', 'verifying', 'done'] +12:29:22 prowlarr: tag kept, app profile present=True +12:29:22 [3] seed read back after the Update: True +12:29:22 [4] restore points offered BEFORE the remove: [{"time": "2026-10-15T10:27:44Z", "short_id": "helyi", "tier": 1, "drive_label": "Belső SSD (rendszer)"}] +12:29:26 [4] stop -> 200 +12:29:57 [4] remove KEEPING the backups -> 200 {"ok": true, "data": {"removed": "prowlarr", "volumes_removed": ["prowlarr_prowlarr_config"], "hdd_paths_removed": [], "hdd_paths_preserved": [], "userdata_kept": [], "verified": true}, "message": "Stack prowlarr removed"} +12:30:06 [4] left after remove: containers=0 volumes=0 +12:30:06 [R] restoring prowlarr from snapshot 'helyi' (of 1 offered) +12:30:06 [R] POST /backup/restore -> HTTP/2 302 ['location: /backups/restore?flash=flash.restore.started'] +12:30:06 + 0.0s restore (True, None, None) +12:30:28 + 22.3s restore (False, None, None) +12:30:28 [R] after restore: state=running hold=None phase=None +12:30:32 [5] restore: {"ok": true, "snapshot_id": "helyi", "http": "HTTP/2 302", "seconds": 22.3, "state_after": "running", "hold_after": null, "why": null} +12:30:32 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:30:37 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:30:42 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:30:47 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:30:52 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:30:57 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:31:02 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:31:07 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:31:12 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:31:17 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:31:22 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:31:28 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:31:33 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:31:38 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:31:43 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:31:48 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:31:53 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:31:58 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:32:03 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:32:08 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:32:13 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:32:18 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:32:23 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:32:28 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:32:33 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:32:38 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:32:43 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:32:48 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:32:53 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:32:58 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:33:03 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:33:08 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:33:13 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:33:19 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:33:24 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:33:29 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:33:34 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:33:39 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:33:44 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:33:49 gate: prowlarr not routed right now (404) — not cached, asked again next call +12:33:54 app never answered on prowlarr/ping (last rc=0 code=404) +12:34:07 prowlarr: tag kept, app profile present=True +12:34:07 [5] 2.5 the seed reads back after remove + restore: True +12:34:10 [5] image after restore: lscr.io/linuxserver/prowlarr:version-2.5.2.5491 health=healthy +12:34:14 [X] stop -> 200 {'ok': True, 'message': 'Stack prowlarr stop completed'} +12:34:46 [X] remove (with drive data) -> 200 {'ok': True, 'data': {'removed': 'prowlarr', 'volumes_removed': ['prowlarr_prowlarr_config'], 'hdd_paths_removed': [], 'hdd_paths_preserved': [], 'hdd_note': 'A +12:34:54 [X] after remove: deployed=False leftovers='/opt/docker/stacks/prowlarr' +12:34:58 [9] after remove: containers=0 volumes=0 diff --git a/documentation/audits/prowlarr-2026-10-15/box/run2-empty-meta/restore-results.json b/documentation/audits/prowlarr-2026-10-15/box/run2-empty-meta/restore-results.json new file mode 100644 index 00000000..11e94351 --- /dev/null +++ b/documentation/audits/prowlarr-2026-10-15/box/run2-empty-meta/restore-results.json @@ -0,0 +1,18 @@ +{ + "seed": { + "tag": "felhom6a4ef5fa", + "profile": "felhom-profile-16d260" + }, + "seed_before": true, + "after_update": true, + "after_restore": true, + "update_final": "done", + "restore": { + "ok": true, + "snapshot_id": "helyi", + "http": "HTTP/2 302", + "seconds": 22.3, + "state_after": "running" + }, + "measured_at": "2026-10-15T10:34:58Z" +} \ No newline at end of file diff --git a/documentation/audits/prowlarr-2026-10-15/box/run2-empty-meta/restore.txt b/documentation/audits/prowlarr-2026-10-15/box/run2-empty-meta/restore.txt new file mode 100644 index 00000000..6115b05e --- /dev/null +++ b/documentation/audits/prowlarr-2026-10-15/box/run2-empty-meta/restore.txt @@ -0,0 +1,117 @@ + [sync] the badge NEVER caught up to lscr.io/linuxserver/prowlarr:version-2.5.2.5491 in 78.3s — catalog_images = None +[1] deploy -> 202 {"ok": true, "message": "Telepítés elindítva – az állapot a kártyán követhető"} + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + app never answered on prowlarr/ping (last rc=0 code=404) +[1] running: lscr.io/linuxserver/prowlarr:version-2.5.2.5491 health=healthy + prowlarr: POST /tag felhom6a4ef5fa http=201 + prowlarr: POST /appprofile felhom-profile-16d260 http=201 + prowlarr: tag kept, app profile present=True +[2] seed {'tag': 'felhom6a4ef5fa', 'profile': 'felhom-profile-16d260'} read back: True +drill commit 03b0c0d343d8: -> lscr.io/linuxserver/prowlarr:version-2.6.5.5623 (push rc=0) + [6] Update -> 202 {'ok': True, 'data': {'accepted': True, 'completed': False}, 'message': 'Frissítés elindult – az állapot a kártyán követhető'} + + 0.0s phase=backing-up label=Biztonsági mentés készül a frissítés előtt… err=None hold=None + + 5.1s phase=safety-dump label=Adatbázis pillanatkép… err=None hold=None + + 6.2s phase=pulling label=Új verzió letöltése… err=None hold=None + + 16.4s phase=copying label=Az adatok másolása a frissítés előtt… err=None hold=None + + 22.6s phase=verifying label=Működés ellenőrzése… err=None hold=None + + 89.2s phase=done label=Frissítve err=None hold=None +[3] guarded Update (its backing-up phase is the per-app backup): final=done phases=['backing-up', 'safety-dump', 'pulling', 'copying', 'verifying', 'done'] + prowlarr: tag kept, app profile present=True +[3] seed read back after the Update: True +[4] restore points offered BEFORE the remove: [{"time": "2026-10-15T10:27:44Z", "short_id": "helyi", "tier": 1, "drive_label": "Belső SSD (rendszer)"}] +[4] stop -> 200 +[4] remove KEEPING the backups -> 200 {"ok": true, "data": {"removed": "prowlarr", "volumes_removed": ["prowlarr_prowlarr_config"], "hdd_paths_removed": [], "hdd_paths_preserved": [], "userdata_kept": [], "verified": true}, "message": "Stack prowlarr removed"} +[4] left after remove: containers=0 volumes=0 + [R] restoring prowlarr from snapshot 'helyi' (of 1 offered) + [R] POST /backup/restore -> HTTP/2 302 ['location: /backups/restore?flash=flash.restore.started'] + + 0.0s restore (True, None, None) + + 22.3s restore (False, None, None) + [R] after restore: state=running hold=None phase=None +[5] restore: {"ok": true, "snapshot_id": "helyi", "http": "HTTP/2 302", "seconds": 22.3, "state_after": "running", "hold_after": null, "why": null} + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + gate: prowlarr not routed right now (404) — not cached, asked again next call + app never answered on prowlarr/ping (last rc=0 code=404) + prowlarr: tag kept, app profile present=True +[5] 2.5 the seed reads back after remove + restore: True +[5] image after restore: lscr.io/linuxserver/prowlarr:version-2.5.2.5491 health=healthy + [X] stop -> 200 {'ok': True, 'message': 'Stack prowlarr stop completed'} + [X] remove (with drive data) -> 200 {'ok': True, 'data': {'removed': 'prowlarr', 'volumes_removed': ['prowlarr_prowlarr_config'], 'hdd_paths_removed': [], 'hdd_paths_preserved': [], 'hdd_note': 'A + [X] after remove: deployed=False leftovers='/opt/docker/stacks/prowlarr' +[9] after remove: containers=0 volumes=0 diff --git a/documentation/audits/prowlarr-2026-10-15/box/run3/restore-console.txt b/documentation/audits/prowlarr-2026-10-15/box/run3/restore-console.txt new file mode 100644 index 00000000..b7dbcfee --- /dev/null +++ b/documentation/audits/prowlarr-2026-10-15/box/run3/restore-console.txt @@ -0,0 +1,52 @@ +mirror main e917a58 DRILL prowlarr: restore the .felhom.yml an earlier walk emptied (open-for-write before read), compose at version-2.5.2.5491 (R-1048 run 3) + +drill repo main: e917a58 +12:36:56 [sync] the badge NEVER caught up to lscr.io/linuxserver/prowlarr:version-2.5.2.5491 in 78.4s — catalog_images = None +12:36:56 [1] deploy -> 202 {"ok": true, "message": "Telepítés elindítva – az állapot a kártyán követhető"} +12:37:27 gate: prowlarr is gated — passed as the household (cookie set) +12:37:30 [1] through traefik: 401 (gated: 302 to the gate is the healthy answer for a stranger) +12:37:33 [1] stack .felhom.yml bytes: 3948 +12:37:37 [1] running: lscr.io/linuxserver/prowlarr:version-2.5.2.5491 health=healthy +12:37:47 prowlarr: POST /tag felhom794ac402 http=201 +12:37:50 prowlarr: POST /appprofile felhom-profile-0eee01 http=201 +12:38:02 prowlarr: tag kept, app profile present=True +12:38:02 [2] seed {'tag': 'felhom794ac402', 'profile': 'felhom-profile-0eee01'} read back: True +12:38:06 [2] 2.7 config volume size after the seed: 5.5M +12:38:12 [2] 4.4 negative control, the same probe on a port nothing serves: rc=7 ; the real probe: rc=0 +12:38:15 [2] 1.8 the app's own start lines: [Info] Microsoft.Hosting.Lifetime: Application started. Press Ctrl+C to shut down. | [Info] Microsoft.Hosting.Lifetime: Hosting environment: Production +12:38:16 drill commit ebf3668f589c: -> lscr.io/linuxserver/prowlarr:version-2.6.5.5623 (push rc=0) +mirror main ebf3668f DRILL prowlarr: the step -> lscr.io/linuxserver/prowlarr:version-2.6.5.5623 (R-1048, checklist 2.5) + +drill repo main: ebf3668 +12:38:26 [6] Update -> 202 {'ok': True, 'data': {'accepted': True, 'completed': False}, 'message': 'Frissítés elindult – az állapot a kártyán követhető'} +12:38:26 + 0.0s phase=backing-up label=Biztonsági mentés készül a frissítés előtt… err=None hold=None +12:38:31 + 5.1s phase=safety-dump label=Adatbázis pillanatkép… err=None hold=None +12:38:32 + 6.2s phase=pulling label=Új verzió letöltése… err=None hold=None +12:38:39 + 12.3s phase=copying label=Az adatok másolása a frissítés előtt… err=None hold=None +12:38:47 + 20.5s phase=starting label=Indítás az új verzióval… err=None hold=None +12:38:48 + 21.5s phase=verifying label=Működés ellenőrzése… err=None hold=None +12:38:53 + 26.6s phase=done label=Frissítve err=None hold=None +12:38:53 [3] guarded Update (its backing-up phase is the per-app backup): final=done phases=['backing-up', 'safety-dump', 'pulling', 'copying', 'starting', 'verifying', 'done'] +12:39:06 prowlarr: tag kept, app profile present=True +12:39:06 [3] seed read back after the Update: True +12:39:09 [3] after: {"pinned_images": {"prowlarr": "lscr.io/linuxserver/prowlarr:version-2.6.5.5623"}, "installed_images": {"prowlarr": "lscr.io/linuxserver/prowlarr:version-2.6.5.5623"}, "catalog_images": {"prowlarr": "lscr.io/linuxserver/prowlarr:version-2.6.5.5623"}, "live_compose_image_lines": ["image: lscr.io/linuxserver/prowlarr:version-2.6.5.5623"], "docker_inspect": ["prowlarr lscr.io/linuxserver/prowlarr:ve +12:39:09 [3] box verdict proven +12:39:09 [4] restore points offered BEFORE the remove: [{"time": "2026-10-15T10:38:30Z", "short_id": "helyi", "tier": 1, "drive_label": "Belső SSD (rendszer)"}] +12:39:13 [4] stop -> 200 +12:39:45 [4] remove KEEPING the backups -> 200 {"ok": true, "data": {"removed": "prowlarr", "volumes_removed": ["prowlarr_prowlarr_config"], "hdd_paths_removed": [], "hdd_paths_preserved": [], "userdata_kept": [], "verified": true}, "message": "Stack prowlarr removed"} +12:39:53 [4] left after remove: containers=0 volumes=0 +12:39:53 [R] restoring prowlarr from snapshot 'helyi' (of 1 offered) +12:39:53 [R] POST /backup/restore -> HTTP/2 302 ['location: /backups/restore?flash=flash.restore.started'] +12:39:53 + 0.0s restore (True, None, None) +12:40:20 + 26.3s restore (False, None, None) +12:40:20 [R] after restore: state=running hold=None phase=None +12:40:23 [5] restore: {"ok": true, "snapshot_id": "helyi", "http": "HTTP/2 302", "seconds": 26.4, "state_after": "running", "hold_after": null, "why": null} +12:40:37 prowlarr: tag kept, app profile present=True +12:40:37 [5] 2.5 the seed reads back after remove + restore: True +12:40:40 [5] through traefik after restore: 200 +12:40:43 [5] app.yaml env keys after restore: DOMAIN SUBDOMAIN +12:40:46 [5] image after restore: lscr.io/linuxserver/prowlarr:version-2.5.2.5491 health=healthy +12:40:50 [X] stop -> 200 {'ok': True, 'message': 'Stack prowlarr stop completed'} +12:41:21 [X] remove (with drive data) -> 200 {'ok': True, 'data': {'removed': 'prowlarr', 'volumes_removed': ['prowlarr_prowlarr_config'], 'hdd_paths_removed': [], 'hdd_paths_preserved': [], 'hdd_note': 'A +12:41:30 [X] after remove: deployed=False leftovers='/opt/docker/stacks/prowlarr' +12:41:33 [9] after remove: containers=0 volumes=0 diff --git a/documentation/audits/prowlarr-2026-10-15/box/run3/restore-results.json b/documentation/audits/prowlarr-2026-10-15/box/run3/restore-results.json new file mode 100644 index 00000000..c5ebf581 --- /dev/null +++ b/documentation/audits/prowlarr-2026-10-15/box/run3/restore-results.json @@ -0,0 +1,18 @@ +{ + "seed": { + "tag": "felhom794ac402", + "profile": "felhom-profile-0eee01" + }, + "seed_before": true, + "after_update": true, + "after_restore": true, + "update_final": "done", + "restore": { + "ok": true, + "snapshot_id": "helyi", + "http": "HTTP/2 302", + "seconds": 26.4, + "state_after": "running" + }, + "measured_at": "2026-10-15T10:41:33Z" +} \ No newline at end of file diff --git a/documentation/audits/prowlarr-2026-10-15/box/run3/restore.txt b/documentation/audits/prowlarr-2026-10-15/box/run3/restore.txt new file mode 100644 index 00000000..79b420bc --- /dev/null +++ b/documentation/audits/prowlarr-2026-10-15/box/run3/restore.txt @@ -0,0 +1,46 @@ + [sync] the badge NEVER caught up to lscr.io/linuxserver/prowlarr:version-2.5.2.5491 in 78.4s — catalog_images = None +[1] deploy -> 202 {"ok": true, "message": "Telepítés elindítva – az állapot a kártyán követhető"} + gate: prowlarr is gated — passed as the household (cookie set) +[1] through traefik: 401 (gated: 302 to the gate is the healthy answer for a stranger) +[1] stack .felhom.yml bytes: 3948 +[1] running: lscr.io/linuxserver/prowlarr:version-2.5.2.5491 health=healthy + prowlarr: POST /tag felhom794ac402 http=201 + prowlarr: POST /appprofile felhom-profile-0eee01 http=201 + prowlarr: tag kept, app profile present=True +[2] seed {'tag': 'felhom794ac402', 'profile': 'felhom-profile-0eee01'} read back: True +[2] 2.7 config volume size after the seed: 5.5M +[2] 4.4 negative control, the same probe on a port nothing serves: rc=7 ; the real probe: rc=0 +[2] 1.8 the app's own start lines: [Info] Microsoft.Hosting.Lifetime: Application started. Press Ctrl+C to shut down. | [Info] Microsoft.Hosting.Lifetime: Hosting environment: Production +drill commit ebf3668f589c: -> lscr.io/linuxserver/prowlarr:version-2.6.5.5623 (push rc=0) + [6] Update -> 202 {'ok': True, 'data': {'accepted': True, 'completed': False}, 'message': 'Frissítés elindult – az állapot a kártyán követhető'} + + 0.0s phase=backing-up label=Biztonsági mentés készül a frissítés előtt… err=None hold=None + + 5.1s phase=safety-dump label=Adatbázis pillanatkép… err=None hold=None + + 6.2s phase=pulling label=Új verzió letöltése… err=None hold=None + + 12.3s phase=copying label=Az adatok másolása a frissítés előtt… err=None hold=None + + 20.5s phase=starting label=Indítás az új verzióval… err=None hold=None + + 21.5s phase=verifying label=Működés ellenőrzése… err=None hold=None + + 26.6s phase=done label=Frissítve err=None hold=None +[3] guarded Update (its backing-up phase is the per-app backup): final=done phases=['backing-up', 'safety-dump', 'pulling', 'copying', 'starting', 'verifying', 'done'] + prowlarr: tag kept, app profile present=True +[3] seed read back after the Update: True +[3] after: {"pinned_images": {"prowlarr": "lscr.io/linuxserver/prowlarr:version-2.6.5.5623"}, "installed_images": {"prowlarr": "lscr.io/linuxserver/prowlarr:version-2.6.5.5623"}, "catalog_images": {"prowlarr": "lscr.io/linuxserver/prowlarr:version-2.6.5.5623"}, "live_compose_image_lines": ["image: lscr.io/linuxserver/prowlarr:version-2.6.5.5623"], "docker_inspect": ["prowlarr lscr.io/linuxserver/prowlarr:ve +[3] box verdict proven +[4] restore points offered BEFORE the remove: [{"time": "2026-10-15T10:38:30Z", "short_id": "helyi", "tier": 1, "drive_label": "Belső SSD (rendszer)"}] +[4] stop -> 200 +[4] remove KEEPING the backups -> 200 {"ok": true, "data": {"removed": "prowlarr", "volumes_removed": ["prowlarr_prowlarr_config"], "hdd_paths_removed": [], "hdd_paths_preserved": [], "userdata_kept": [], "verified": true}, "message": "Stack prowlarr removed"} +[4] left after remove: containers=0 volumes=0 + [R] restoring prowlarr from snapshot 'helyi' (of 1 offered) + [R] POST /backup/restore -> HTTP/2 302 ['location: /backups/restore?flash=flash.restore.started'] + + 0.0s restore (True, None, None) + + 26.3s restore (False, None, None) + [R] after restore: state=running hold=None phase=None +[5] restore: {"ok": true, "snapshot_id": "helyi", "http": "HTTP/2 302", "seconds": 26.4, "state_after": "running", "hold_after": null, "why": null} + prowlarr: tag kept, app profile present=True +[5] 2.5 the seed reads back after remove + restore: True +[5] through traefik after restore: 200 +[5] app.yaml env keys after restore: DOMAIN SUBDOMAIN +[5] image after restore: lscr.io/linuxserver/prowlarr:version-2.5.2.5491 health=healthy + [X] stop -> 200 {'ok': True, 'message': 'Stack prowlarr stop completed'} + [X] remove (with drive data) -> 200 {'ok': True, 'data': {'removed': 'prowlarr', 'volumes_removed': ['prowlarr_prowlarr_config'], 'hdd_paths_removed': [], 'hdd_paths_preserved': [], 'hdd_note': 'A + [X] after remove: deployed=False leftovers='/opt/docker/stacks/prowlarr' +[9] after remove: containers=0 volumes=0 diff --git a/documentation/audits/prowlarr-2026-10-15/box/walk-attempt1-aborted.txt b/documentation/audits/prowlarr-2026-10-15/box/walk-attempt1-aborted.txt new file mode 100644 index 00000000..e69de29b diff --git a/documentation/audits/prowlarr-2026-10-15/box/walk-console.txt b/documentation/audits/prowlarr-2026-10-15/box/walk-console.txt new file mode 100644 index 00000000..ee015c2d --- /dev/null +++ b/documentation/audits/prowlarr-2026-10-15/box/walk-console.txt @@ -0,0 +1,57 @@ +11:55:37 [1] POST /api/stacks/prowlarr/deploy -> 202 {"ok": true, "message": "Telepítés elindítva – az állapot a kártyán követhető"} +11:56:00 [1] install window, a stranger on the public name (23 s, distinct answers): +11:56:00 8x / -> 302 loc=https://felhom.enkisfelhom.hu/__gate/start?rd=https%3A%2F%2F +11:56:00 14x / -> 404 +11:56:00 8x /api/v1/system/status -> 401 +11:56:00 14x /api/v1/system/status -> 404 +11:56:00 8x /initialize.json -> 401 +11:56:00 14x /initialize.json -> 404 +11:56:03 [1] state=running health=None restarts: 0 started=2026-10-15T09:55:47.047772324Z health=healthy +11:56:07 [1] the first-start seed line: [felhom] first start - AuthenticationRequired=Enabled, AnalyticsEnabled=False seeded +11:56:10 [1] config.xml (key redacted): Enabled False debug * 9696 6969 False True None master Prowlarr Docker +11:56:10 gate: prowlarr is gated — passed as the household (cookie set) +11:56:10 [2] household GET /initialize.json through the gate -> 200, carries the API key: True +11:56:10 [2] GET /api/v1/config/host -> 200: authenticationMethod=none authenticationRequired=enabled analyticsEnabled=False +11:56:10 [2] the household makes its first account (PUT /api/v1/config/host, forms) -> 202 +11:56:18 [3] sign in (right password) POST /login -> 302 loc=/ auth-cookie=ProwlarrAuth +11:56:18 [3] sign in (wrong password) POST /login -> 302 loc=/login?returnUrl=/&loginFailed=true auth-cookie=none +11:56:29 prowlarr: POST /tag felhom7b8df208 http=201 +11:56:32 prowlarr: POST /appprofile felhom-profile-11b5ec http=201 +11:56:45 prowlarr: tag kept, app profile present=True +11:56:45 [4] seed {'tag': 'felhom7b8df208', 'profile': 'felhom-profile-11b5ec'} read back before the update: True +11:56:45 [4] backup press SKIPPED for prowlarr (R-648: whole-box only; the update's backing-up phase backs up prowlarr alone) +11:56:45 [4] „Mentés most” -> null +11:56:45 [5] the household's „Kész, beállítottam” -> 200 {"data": {"opened": true}, "error": "", "ok": true} +11:56:50 [5] stranger after the gate opened: GET / -> 302 loc=https://prowlarr.enkisfelhom.hu/login?returnUrl=%2F apiKey-in-body=False +11:56:50 [5] stranger after the gate opened: GET /initialize.json -> 302 loc=https://prowlarr.enkisfelhom.hu/login?returnUrl=%2Finitialize.json apiKey-in-body=False +11:56:50 [5] stranger after the gate opened: GET /api/v1/system/status -> 401 apiKey-in-body=False +11:56:50 [5] stranger after the gate opened: GET /api/v1/indexer -> 401 apiKey-in-body=False +11:56:50 [6] stranger wrong password #1 -> 302 loc=/login?returnUrl=/&loginFailed=true +11:56:50 [6] stranger wrong password #2 -> 302 loc=/login?returnUrl=/&loginFailed=true +11:56:50 [6] stranger wrong password #3 -> 302 loc=/login?returnUrl=/&loginFailed=true +11:56:50 [6] stranger wrong password #4 -> 302 loc=/login?returnUrl=/&loginFailed=true +11:56:50 [6] stranger wrong password #5 -> 302 loc=/login?returnUrl=/&loginFailed=true +11:56:50 [6] stranger wrong password #6 -> 302 loc=/login?returnUrl=/&loginFailed=true +11:56:50 [6] the household's right password right after (same address) -> 302 loc=/ auth-cookie=yes +11:56:53 [6] what the app logged for the failures (address): 2026-10-15 11:56:50.4|Warn|Auth|Auth-Failure ip 192.168.0.180 username 'felhom-test' | 2026-10-15 11:56:50.5|Warn|Auth|Auth-Failure ip 192.168.0.180 username 'felhom-test' | 2026-10-15 11:56:50.5|Warn|Auth|Auth-Failure ip 192.168.0.180 username 'felhom-test' +11:56:57 [7] drill commit f2b0cc15090d: lscr.io/linuxserver/prowlarr:version-2.5.2.5491 -> lscr.io/linuxserver/prowlarr:version-2.6.5.5623 (push rc=0) +mirror main f2b0cc1 DRILL prowlarr: lscr.io/linuxserver/prowlarr:version-2.5.2.5491 -> lscr.io/linuxserver/prowlarr:version-2.6.5.5623 (R-1048 box proof) + +drill repo main: f2b0cc1 +11:57:07 [7] 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ő"}], "en": [{"title": "A newer version of this app is available. Select the Update button to start it.", "text": "Update available"}]} +11:57:07 [6] Update -> 202 {'ok': True, 'data': {'accepted': True, 'completed': False}, 'message': 'Frissítés elindult – az állapot a kártyán követhető'} +11:57:07 + 0.0s phase=backing-up label=Biztonsági mentés készül a frissítés előtt… err=None hold=None +11:57:12 + 5.1s phase=safety-dump label=Adatbázis pillanatkép… err=None hold=None +11:57:13 + 6.2s phase=pulling label=Új verzió letöltése… err=None hold=None +11:57:18 + 11.3s phase=copying label=Az adatok másolása a frissítés előtt… err=None hold=None +11:57:24 + 17.4s phase=verifying label=Működés ellenőrzése… err=None hold=None +11:58:31 + 84.1s phase=done label=Frissítve err=None hold=None +11:58:31 [7] guarded Update: {"accepted": true, "http": "202", "phases": [{"t": 0.0, "phase": "backing-up", "label": "Biztonsági mentés készül a frissítés előtt…", "updating": true, "error": null, "hold": null}, {"t": 5.1, "phase": "safety-dump", "label": "Adatbázis pillanatkép…", "updating": true, "error": null, "hold": null}, {"t": 6.2, "phase": "pulling", "label": "Új verzió letöltése…", "updating": true, "error": null, "h +11:58:44 prowlarr: tag kept, app profile present=True +11:58:47 [7] after: {"pinned_images": {"prowlarr": "lscr.io/linuxserver/prowlarr:version-2.6.5.5623"}, "installed_images": {"prowlarr": "lscr.io/linuxserver/prowlarr:version-2.6.5.5623"}, "catalog_images": {"prowlarr": "lscr.io/linuxserver/prowlarr:version-2.6.5.5623"}, "live_compose_image_lines": ["image: lscr.io/linuxserver/prowlarr:version-2.6.5.5623"], "docker_inspect": ["prowlarr lscr.io/linuxserver/prowlarr:ve +11:58:47 [8] box verdict proven +11:58:51 [X] stop -> 200 {'ok': True, 'message': 'Stack prowlarr stop completed'} +11:59:23 [X] remove (with drive data) -> 200 {'ok': True, 'data': {'removed': 'prowlarr', 'volumes_removed': ['prowlarr_prowlarr_config'], 'hdd_paths_removed': [], 'hdd_paths_preserved': [], 'hdd_note': 'A +11:59:31 [X] after remove: deployed=False leftovers='/opt/docker/stacks/prowlarr' +11:59:34 [9] after remove: containers=0 volumes=0 stackdir=1 +exit 0 diff --git a/documentation/audits/prowlarr-2026-10-15/box/walk-results.json b/documentation/audits/prowlarr-2026-10-15/box/walk-results.json new file mode 100644 index 00000000..b7c940e1 --- /dev/null +++ b/documentation/audits/prowlarr-2026-10-15/box/walk-results.json @@ -0,0 +1,41 @@ +{ + "install_window": { + "/ -> 404": 14, + "/initialize.json -> 404": 14, + "/api/v1/system/status -> 404": 14, + "/ -> 302 loc=https://felhom.enkisfelhom.hu/__gate/start?rd=https%3A%2F%2F": 8, + "/initialize.json -> 401": 8, + "/api/v1/system/status -> 401": 8 + }, + "first_admin": "202", + "signin_right": [ + "302", + "/", + true + ], + "signin_wrong": [ + "302", + "/login?returnUrl=/&loginFailed=true", + false + ], + "stranger_open_/": [ + "302", + "https://prowlarr.enkisfelhom.hu/login?returnUrl=%2F", + false + ], + "stranger_open_/initialize.json": [ + "302", + "https://prowlarr.enkisfelhom.hu/login?returnUrl=%2Finitialize.json", + false + ], + "stranger_open_/api/v1/system/status": [ + "401", + "", + false + ], + "stranger_open_/api/v1/indexer": [ + "401", + "", + false + ] +} \ No newline at end of file diff --git a/documentation/audits/prowlarr-2026-10-15/box/walk.txt b/documentation/audits/prowlarr-2026-10-15/box/walk.txt new file mode 100644 index 00000000..91f10320 --- /dev/null +++ b/documentation/audits/prowlarr-2026-10-15/box/walk.txt @@ -0,0 +1,53 @@ +[1] POST /api/stacks/prowlarr/deploy -> 202 {"ok": true, "message": "Telepítés elindítva – az állapot a kártyán követhető"} +[1] install window, a stranger on the public name (23 s, distinct answers): + 8x / -> 302 loc=https://felhom.enkisfelhom.hu/__gate/start?rd=https%3A%2F%2F + 14x / -> 404 + 8x /api/v1/system/status -> 401 + 14x /api/v1/system/status -> 404 + 8x /initialize.json -> 401 + 14x /initialize.json -> 404 +[1] state=running health=None restarts: 0 started=2026-10-15T09:55:47.047772324Z health=healthy +[1] the first-start seed line: [felhom] first start - AuthenticationRequired=Enabled, AnalyticsEnabled=False seeded +[1] config.xml (key redacted): Enabled False debug * 9696 6969 False True None master Prowlarr Docker + gate: prowlarr is gated — passed as the household (cookie set) +[2] household GET /initialize.json through the gate -> 200, carries the API key: True +[2] GET /api/v1/config/host -> 200: authenticationMethod=none authenticationRequired=enabled analyticsEnabled=False +[2] the household makes its first account (PUT /api/v1/config/host, forms) -> 202 +[3] sign in (right password) POST /login -> 302 loc=/ auth-cookie=ProwlarrAuth +[3] sign in (wrong password) POST /login -> 302 loc=/login?returnUrl=/&loginFailed=true auth-cookie=none + prowlarr: POST /tag felhom7b8df208 http=201 + prowlarr: POST /appprofile felhom-profile-11b5ec http=201 + prowlarr: tag kept, app profile present=True +[4] seed {'tag': 'felhom7b8df208', 'profile': 'felhom-profile-11b5ec'} read back before the update: True + [4] backup press SKIPPED for prowlarr (R-648: whole-box only; the update's backing-up phase backs up prowlarr alone) +[4] „Mentés most” -> null +[5] the household's „Kész, beállítottam” -> 200 {"data": {"opened": true}, "error": "", "ok": true} +[5] stranger after the gate opened: GET / -> 302 loc=https://prowlarr.enkisfelhom.hu/login?returnUrl=%2F apiKey-in-body=False +[5] stranger after the gate opened: GET /initialize.json -> 302 loc=https://prowlarr.enkisfelhom.hu/login?returnUrl=%2Finitialize.json apiKey-in-body=False +[5] stranger after the gate opened: GET /api/v1/system/status -> 401 apiKey-in-body=False +[5] stranger after the gate opened: GET /api/v1/indexer -> 401 apiKey-in-body=False +[6] stranger wrong password #1 -> 302 loc=/login?returnUrl=/&loginFailed=true +[6] stranger wrong password #2 -> 302 loc=/login?returnUrl=/&loginFailed=true +[6] stranger wrong password #3 -> 302 loc=/login?returnUrl=/&loginFailed=true +[6] stranger wrong password #4 -> 302 loc=/login?returnUrl=/&loginFailed=true +[6] stranger wrong password #5 -> 302 loc=/login?returnUrl=/&loginFailed=true +[6] stranger wrong password #6 -> 302 loc=/login?returnUrl=/&loginFailed=true +[6] the household's right password right after (same address) -> 302 loc=/ auth-cookie=yes +[6] what the app logged for the failures (address): 2026-10-15 11:56:50.4|Warn|Auth|Auth-Failure ip 192.168.0.180 username 'felhom-test' | 2026-10-15 11:56:50.5|Warn|Auth|Auth-Failure ip 192.168.0.180 username 'felhom-test' | 2026-10-15 11:56:50.5|Warn|Auth|Auth-Failure ip 192.168.0.180 username 'felhom-test' +[7] drill commit f2b0cc15090d: lscr.io/linuxserver/prowlarr:version-2.5.2.5491 -> lscr.io/linuxserver/prowlarr:version-2.6.5.5623 (push rc=0) +[7] 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ő"}], "en": [{"title": "A newer version of this app is available. Select the Update button to start it.", "text": "Update available"}]} + [6] Update -> 202 {'ok': True, 'data': {'accepted': True, 'completed': False}, 'message': 'Frissítés elindult – az állapot a kártyán követhető'} + + 0.0s phase=backing-up label=Biztonsági mentés készül a frissítés előtt… err=None hold=None + + 5.1s phase=safety-dump label=Adatbázis pillanatkép… err=None hold=None + + 6.2s phase=pulling label=Új verzió letöltése… err=None hold=None + + 11.3s phase=copying label=Az adatok másolása a frissítés előtt… err=None hold=None + + 17.4s phase=verifying label=Működés ellenőrzése… err=None hold=None + + 84.1s phase=done label=Frissítve err=None hold=None +[7] guarded Update: {"accepted": true, "http": "202", "phases": [{"t": 0.0, "phase": "backing-up", "label": "Biztonsági mentés készül a frissítés előtt…", "updating": true, "error": null, "hold": null}, {"t": 5.1, "phase": "safety-dump", "label": "Adatbázis pillanatkép…", "updating": true, "error": null, "hold": null}, {"t": 6.2, "phase": "pulling", "label": "Új verzió letöltése…", "updating": true, "error": null, "h + prowlarr: tag kept, app profile present=True +[7] after: {"pinned_images": {"prowlarr": "lscr.io/linuxserver/prowlarr:version-2.6.5.5623"}, "installed_images": {"prowlarr": "lscr.io/linuxserver/prowlarr:version-2.6.5.5623"}, "catalog_images": {"prowlarr": "lscr.io/linuxserver/prowlarr:version-2.6.5.5623"}, "live_compose_image_lines": ["image: lscr.io/linuxserver/prowlarr:version-2.6.5.5623"], "docker_inspect": ["prowlarr lscr.io/linuxserver/prowlarr:ve +[8] box verdict proven + [X] stop -> 200 {'ok': True, 'message': 'Stack prowlarr stop completed'} + [X] remove (with drive data) -> 200 {'ok': True, 'data': {'removed': 'prowlarr', 'volumes_removed': ['prowlarr_prowlarr_config'], 'hdd_paths_removed': [], 'hdd_paths_preserved': [], 'hdd_note': 'A + [X] after remove: deployed=False leftovers='/opt/docker/stacks/prowlarr' +[9] after remove: containers=0 volumes=0 stackdir=1 diff --git a/documentation/audits/prowlarr-2026-10-15/tools/bench_first_start.sh b/documentation/audits/prowlarr-2026-10-15/tools/bench_first_start.sh new file mode 100644 index 00000000..50c558fd --- /dev/null +++ b/documentation/audits/prowlarr-2026-10-15/tools/bench_first_start.sh @@ -0,0 +1,25 @@ +#!/bin/bash +# bench_first_start.sh — Prowlarr's first start on the bench (LXC 9401), measured. A throwaway container +# `prowlarr-probe` with a throwaway volume `prowlarr-probe-config`; both removed at the end. Swap is off on the bench. +set -u +IMG=$1; C=prowlarr-probe; V=prowlarr-probe-config +docker rm -f $C >/dev/null 2>&1; docker volume rm $V >/dev/null 2>&1 +echo "== image"; docker pull -q $IMG; docker image inspect $IMG -f 'id={{.Id}} digest={{index .RepoDigests 0}} size={{.Size}} arch={{.Architecture}}' +echo "== tools in the image (one per run)" +for t in curl wget sh bash python3 node; do echo "$t: $(docker run --rm --entrypoint sh $IMG -c "command -v $t" 2>/dev/null)"; done +echo "== the service's run script (linuxserver s6)"; docker run --rm --entrypoint sh $IMG -c 'cat /etc/s6-overlay/s6-rc.d/svc-prowlarr/run 2>/dev/null; ls /etc/s6-overlay/s6-rc.d/ | grep -i prowlarr' +docker volume create $V >/dev/null +CID=$(docker create --name $C -e TZ=Europe/Budapest -e PUID=1000 -e PGID=1000 -e UMASK=002 --memory 512m -v $V:/config $IMG) +docker start $C >/dev/null; T0=$(date +%s) +CG=$(find /sys/fs/cgroup -type d -name "*$CID*" 2>/dev/null | head -1) +echo "== memory from birth (cgroup $CG), every 2 s for 150 s" +peak=0; for i in $(seq 1 75); do a=$(awk '$1=="anon"{print $2}' $CG/memory.stat 2>/dev/null); [ -n "$a" ] && [ "$a" -gt "$peak" ] && peak=$a; sleep 2; done +echo "peak anon $((peak/1048576)) MiB; oom_kill $(awk '$1=="oom_kill"{print $2}' $CG/memory.events); restarts $(docker inspect $C -f '{{.RestartCount}}'); health-like: $(docker exec $C curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:9696/ping)" +echo "== process list"; docker exec $C ps -o pid,user,args | head -12 +echo "== config.xml after the first start (ApiKey redacted)"; docker exec $C cat /config/config.xml | sed -E 's#[^<]+##' +K=$(docker exec $C sed -n 's#.*\([^<]*\).*#\1#p' /config/config.xml) +echo "== host config through the app's own API (analytics, update, auth)"; docker exec $C curl -s -H "X-Api-Key: $K" http://127.0.0.1:9696/api/v1/config/host | python3 -c "import json,sys; d=json.load(sys.stdin); print({k:d.get(k) for k in ('analyticsEnabled','updateAutomatically','updateMechanism','authenticationMethod','authenticationRequired','bindAddress','port','proxyEnabled','logLevel')})" 2>/dev/null || echo "(no python3 in the bench shell? raw:)" +echo "== open TCP connections from the container (after 150 s)"; docker exec $C sh -c 'cat /proc/net/tcp /proc/net/tcp6 2>/dev/null | awk "NR>1 && \$4==\"01\" {print \$3}" | sort -u' +echo "== first-start log (60 lines)"; docker logs $C 2>&1 | head -60 | sed -E 's#[A-Fa-f0-9]{32}##g' +echo "== files the app wrote under /config"; docker exec $C sh -c 'find /config -maxdepth 2 | head -30' +docker rm -f $C >/dev/null; docker volume rm $V >/dev/null; echo "== removed: $(docker ps -aq -f name=$C | wc -l) containers, $(docker volume ls -q -f name=$V | wc -l) volumes" diff --git a/documentation/audits/prowlarr-2026-10-15/tools/repoint9202.py b/documentation/audits/prowlarr-2026-10-15/tools/repoint9202.py new file mode 100644 index 00000000..2083eac5 --- /dev/null +++ b/documentation/audits/prowlarr-2026-10-15/tools/repoint9202.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python3 +"""repoint.py mirror|drill|restore|status — point the Tester 1 box at the drill catalog WITHOUT a credential (R-992). + +WHY A LOCAL MIRROR. `09` §6.5's route needs a catalog credential on the box: the drill repo is private (R-992). This +session could not mint a READ-ONLY one — the Gitea credential on DooPlex is itself a token, and Gitea answers 403 to a +token asking to list or create tokens (measured 2026-10-14) — and the night of 2026-10-06 put the admin WRITE token +into the box's controller.yaml, which this task forbids. The cache-staging workaround of 2026-10-12 does not hold for a +test that lasts days: every sync (and every controller start) re-applies the live catalog over the staged templates +and over the installed apps' compose files (Syncer.copyTemplates, no deployed check). + +So the drill catalog is MIRRORED onto the box: a bare git repository inside the controller's own data volume, and +`git.repo_url: file:///opt/docker/felhom-controller/drill-catalog.git`. The controller's sync runs exactly as for a +remote (clone/fetch/reset, the R-615 re-clone on a changed URL); no secret is on the box. DooPlex updates the mirror +from the drill repo with a git bundle (`mirror`). The drill repo on Gitea stays the record. + + SC=~/.felhom-tester1 TARGET=tester-1 python3 repoint.py mirror # (re)load the mirror from the drill repo main + ... repoint.py drill # save controller.yaml (once), set repo_url to the mirror, restart, sync, rescan + ... repoint.py restore # put the saved controller.yaml back (byte-identical), restart, sync, rescan + ... repoint.py status +""" +import os, subprocess, sys, time +sys.path.insert(0, "/mnt/5_hdd/felhom.eu/git/app-catalog-felhom.eu/scripts") +import box_walk as w + +DRILL = "/mnt/5_hdd/felhom.eu/drill/app-catalog-drill" +VOL = "/var/lib/docker/volumes/felhom-controller-data/_data" # guest path of the controller's data volume +CVOL = "/opt/docker/felhom-controller" # the same, inside the controller container +SAVE = f"{VOL}/controller.yaml.pre-prowlarr1015" +MIRROR_URL = f"file://{CVOL}/drill-catalog.git" + + +def status(): + print(w.guest(f"""grep -nE 'repo_url|sync_interval' {VOL}/controller.yaml; ls -l {SAVE} 2>&1 | tail -1 +docker exec felhom-controller sh -c 'git -C {CVOL}/drill-catalog.git log -1 --format="mirror main %h %s" main 2>&1; + git -C {CVOL}/data/catalog-cache log -1 --format="cache HEAD %h %s" 2>&1; git -C {CVOL}/data/catalog-cache remote get-url origin'""")) + + +def mirror(): + b = "/tmp/drill-prowlarr.bundle" + subprocess.run(["git", "-C", DRILL, "bundle", "create", b, "main"], check=True, capture_output=True) + head = subprocess.run(["git", "-C", DRILL, "rev-parse", "--short", "main"], capture_output=True, text=True).stdout.strip() + ssh = w.ssh_args() + # bundle -> PVE host -> guest -> controller volume; the bare repo is made and fetched BY the controller's own user + subprocess.run(["scp", "-q", "-o", "StrictHostKeyChecking=accept-new"] + (["-J", w._T["jump"]] if w._T["jump"] else []) + + [b, f"{w._T['ssh']}:{b}"], check=True) + subprocess.run(ssh + [f"pct push {w.GUEST} {b} {VOL}/drill-prowlarr.bundle && rm -f {b}"], check=True) + print(w.guest(f"""set -e +docker exec felhom-controller sh -c 'test -d {CVOL}/drill-catalog.git || git init -q --bare -b main {CVOL}/drill-catalog.git +git -C {CVOL}/drill-catalog.git fetch -q {CVOL}/drill-prowlarr.bundle +main:main && rm -f {CVOL}/drill-prowlarr.bundle +git -C {CVOL}/drill-catalog.git log -1 --format="mirror main %h %s" main'""")) + print("drill repo main:", head) + os.remove(b) + + +def restart_sync(): + w.guest("docker restart felhom-controller >/dev/null") + for _ in range(60): + time.sleep(3) + try: + w.login() + break + except SystemExit: + continue + w.sync_rescan() + time.sleep(3) + print(w.guest("docker logs --since 3m felhom-controller 2>&1 | grep -E '\\[sync\\]' | grep -v DEBUG | tail -8")) + + +def drill(): + print(w.guest(f"""set -e +test -f {SAVE} || cp -p {VOL}/controller.yaml {SAVE} +sed -i -E 's#^(\\s+repo_url: ).*$#\\1{MIRROR_URL}#' {VOL}/controller.yaml +grep -n 'repo_url' {VOL}/controller.yaml; diff {SAVE} {VOL}/controller.yaml || true""")) + restart_sync() + status() + + +def restore(): + print(w.guest(f"""set -e +cp -p {SAVE} {VOL}/controller.yaml +cmp {SAVE} {VOL}/controller.yaml && echo RESTORED-IDENTICAL; grep -n repo_url {VOL}/controller.yaml""")) + restart_sync() + status() + + +{"mirror": mirror, "drill": drill, "restore": restore, "status": status}[sys.argv[1]]() diff --git a/documentation/audits/prowlarr-2026-10-15/tools/walk_9201_link.py b/documentation/audits/prowlarr-2026-10-15/tools/walk_9201_link.py new file mode 100644 index 00000000..6cbc3be1 --- /dev/null +++ b/documentation/audits/prowlarr-2026-10-15/tools/walk_9201_link.py @@ -0,0 +1,179 @@ +#!/usr/bin/env python3 +"""walk_9201_link.py — the read-back on demo-hp 9201 from the LIVE catalog after the publish (R-1048, brief Part D3). + +Runs ON DOOPLEX with TARGET=9201, SC=<0600 dir holding .ctlpw>, EV=. Presses only what a person presses: +POST /api/sync, the deploy wizard's POST /api/stacks//deploy, the dashboard's „Bekötés most" (POST +/api/storage/media/wire), stop + remove KEEPING data. Reads Prowlarr's own API inside its container with the key from +its own config.xml (never printed), exactly as the box's wiring does. + +The two orders the controller's code implies (mediawire runs when a media_library app comes up; Prowlarr has no marker): + B Sonarr first, Prowlarr after -> expected NO link by itself; „Bekötés most" makes it + A Radarr after Prowlarr -> expected the link by itself, ~60 s after Radarr comes up +Then: Prowlarr's own connection test for each link, and its sync command to completion. +""" +import json +import os +import re +import sys +import time + +sys.path.insert(0, "/mnt/5_hdd/felhom.eu/git/app-catalog-felhom.eu/scripts") +import box_walk as w # noqa: E402 + +EV = os.environ["EV"] +os.makedirs(EV, exist_ok=True) +LOG = open(os.path.join(EV, "link.txt"), "a", buffering=1) +TO = "lscr.io/linuxserver/prowlarr:version-2.6.5.5623" +HDD = "/mnt/felhom-drives/hdd_1" +RES = {} +_lib_say = w.say + + +def say(*a): + _lib_say(*a) + LOG.write(" ".join(str(x) for x in a) + "\n") + + +w.say = say + + +def papi(path, body=None, method=None): + """Prowlarr's own API from inside its container (the key read from its config.xml at use, never printed).""" + m = method or ("POST" if body is not None else "GET") + data = json.dumps(body) if body is not None else "" + script = ("K=$(sed -n 's:.*\\(.*\\).*:\\1:p' /config/config.xml); " + f"curl -s -o /tmp/pb.json -w '%{{http_code}}' -X {m} -H \"X-Api-Key: $K\" -H 'Content-Type: application/json' " + f"--data-binary @- http://127.0.0.1:9696/api/v1{path}; echo; cat /tmp/pb.json") + out = w.guest(f"docker exec -i prowlarr sh -c {json.dumps(script)} <<'EOF'\n{data}\nEOF") + code, _, body_ = out.partition("\n") + try: + return code.strip(), json.loads(body_) if body_.strip() else None + except Exception: + return code.strip(), body_[:300] + + +def apps(): + c, d = papi("/applications") + return [(a.get("name"), a.get("syncLevel"), next((f.get("value") for f in a.get("fields", []) if f.get("name") == "baseUrl"), None)) + for a in (d or [])] if c == "200" else c + + +def wiring_lines(since): + return w.guest(f"docker logs --since {since} felhom-controller 2>&1 | grep -E '\\[media\\] wiring|\\[mediawire\\]' | cut -c1-400").strip() + + +def install(name, values): + if w.stack(name).get("deployed"): + sys.exit(say(f"{name} is already installed on 9201 — STOP, a household's app is never reused") or 1) + code, d = w.ctl("POST", f"/api/stacks/{name}/deploy", {"values": values}) + say(f" deploy {name} {json.dumps(values)} -> {code} {json.dumps(d, ensure_ascii=False)[:120]}") + w.DEPLOYED_THIS_RUN.add(name) + for _ in range(90): + time.sleep(5) + st = w.stack(name) + if st.get("deployed") and (st.get("app_config") or {}).get("pinned_images") and st.get("state") == "running": + say(f" {name}: deployed, running, pinned={(st.get('app_config') or {}).get('pinned_images')}") + return True + sys.exit(say(f" {name} never became running — STOP") or 1) + + +def wait_healthy(c, tries=60): + for _ in range(tries): + if w.guest(f"docker inspect {c} -f '{{{{.State.Health.Status}}}}'").strip() == "healthy": + return True + time.sleep(5) + return False + + +def main(): + w.login() + t0 = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()) + for a in ("prowlarr", "sonarr", "radarr"): + if w.stack(a).get("deployed"): + sys.exit(say(f"{a} is installed on 9201 — STOP") or 1) + say("[0] controller: " + w.guest("cat /etc/felhom-controller-image").strip()) + w.sync_rescan(expect_app="prowlarr", expect_ref=TO) + say("[0] live catalog on the box: " + w.guest("docker exec felhom-controller sh -c 'git -C /opt/docker/felhom-controller/data/catalog-cache log -1 --format=\"%h %s\"; git -C /opt/docker/felhom-controller/data/catalog-cache remote get-url origin'").strip().replace("\n", " | ")) + say("[0] prowlarr template on the box: " + w.guest("wc -c /opt/docker/stacks/prowlarr/.felhom.yml; grep -E 'image:' /opt/docker/stacks/prowlarr/docker-compose.yml; grep -E '^setup_gate' /opt/docker/stacks/prowlarr/.felhom.yml").strip().replace("\n", " | ")) + + # ── order B: Sonarr first ── + install("sonarr", {"DOMAIN": w.DOMAIN, "SUBDOMAIN": "sonarr", "HDD_PATH": HDD}) + wait_healthy("sonarr") + time.sleep(75) # the wiring runs 60 s after the last media app came up + say("[B1] wiring after Sonarr came up:\n" + wiring_lines(t0)) + tP = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()) + install("prowlarr", {"DOMAIN": w.DOMAIN, "SUBDOMAIN": "prowlarr"}) + wait_healthy("prowlarr") + say("[B2] prowlarr first-start line: " + w.guest("docker logs prowlarr 2>&1 | grep -m1 felhom").strip()) + time.sleep(150) + b_apps = apps() + b_wire = wiring_lines(tP) + say(f"[B3] 150 s after Prowlarr came up (Sonarr was there first): Prowlarr's Apps = {b_apps}") + say(f"[B3] wiring runs since Prowlarr's install: {b_wire or '(none)'}") + RES["order_B_apps_without_press"] = b_apps + RES["order_B_wiring_runs"] = b_wire + + # ── the household's „Bekötés most" ── + code, d = w.ctl("POST", "/api/storage/media/wire", {}) + pr = [r for r in ((d or {}).get("data") or []) if isinstance(r, dict) and r.get("app") == "prowlarr"] + say(f"[C1] „Bekötés most” (POST /api/storage/media/wire) -> {code}; prowlarr result: {json.dumps(pr, ensure_ascii=False)}") + c_apps = apps() + say(f"[C2] Prowlarr's Apps after the press: {c_apps}") + RES["after_press_apps"] = c_apps + + # ── order A: Radarr after Prowlarr ── + tR = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()) + install("radarr", {"DOMAIN": w.DOMAIN, "SUBDOMAIN": "radarr", "HDD_PATH": HDD}) + wait_healthy("radarr") + a_apps = None + for i in range(30): + time.sleep(10) + a_apps = apps() + if isinstance(a_apps, list) and any(n == "Radarr" for n, _, _ in a_apps): + say(f"[A1] Radarr appeared in Prowlarr's Apps by itself, ~{(i + 1) * 10} s after Radarr ran") + break + say(f"[A2] Prowlarr's Apps: {a_apps}") + say("[A2] wiring runs since Radarr's install:\n" + wiring_lines(tR)) + RES["order_A_apps"] = a_apps + + # ── the links work: Prowlarr's own test per app, then its sync command to completion ── + c, full = papi("/applications") + for a in (full or []): + tc, td = papi("/applications/test", a) + say(f"[D1] Prowlarr's connection test for {a.get('name')} ({a.get('implementation')}, syncLevel {a.get('syncLevel')}) -> {tc} {str(td)[:160] if td else ''}") + RES[f"test_{a.get('name')}"] = tc + c, cmd = papi("/command", {"name": "ApplicationIndexerSync"}) + cid = (cmd or {}).get("id") if isinstance(cmd, dict) else None + say(f"[D2] sync command ApplicationIndexerSync -> {c} id={cid}") + st = None + for _ in range(30): + time.sleep(2) + c, cd = papi(f"/command/{cid}") + st = (cd or {}).get("status") if isinstance(cd, dict) else cd + if st in ("completed", "failed", "aborted"): + break + say(f"[D2] sync command status: {st}") + RES["sync_status"] = st + say("[D3] Prowlarr's own log of the sync: " + w.guest( + "docker exec prowlarr sh -c \"grep -h -iE 'ApplicationIndexerSync|Syncing|sonarr:8989|radarr:7878' /config/logs/prowlarr.txt | tail -6\"").strip().replace("\n", " | ")[:900]) + say("[D4] Sonarr's and Radarr's indexer lists (Prowlarr has no indexer yet, so both empty is the expected answer): " + + w.guest("for a in sonarr:8989:v3 radarr:7878:v3; do n=${a%%:*}; r=${a#*:}; p=${r%%:*}; v=${r#*:}; " + "docker exec $n sh -c \"K=\\$(sed -n 's:.*\\(.*\\).*:\\1:p' /config/config.xml); " + "curl -s -H \\\"X-Api-Key: \\$K\\\" http://127.0.0.1:$p/api/$v/indexer\" | head -c 200; echo \" <- $n\"; done").strip().replace("\n", " | ")) + + # ── remove all three, KEEPING data (the brief: without „delete data") ── + for a in ("radarr", "prowlarr", "sonarr"): + c1, _ = w.ctl("POST", f"/api/stacks/{a}/stop") + for _ in range(24): + time.sleep(5) + if w.stack(a).get("state") != "running": + break + code, d = w.ctl("POST", f"/api/stacks/{a}/remove", {"remove_hdd_data": False, "remove_backups": False}) + say(f"[X] {a}: stop {c1}, remove keeping data -> {code} {json.dumps(d, ensure_ascii=False)[:260]}") + time.sleep(5) + say("[X] left: " + w.guest("echo containers=$(docker ps -aq -f name='^(prowlarr|sonarr|radarr)$' | wc -l) " + "volumes=$(docker volume ls -q | grep -cE '^(prowlarr|sonarr|radarr)_')").strip()) + json.dump(RES, open(os.path.join(EV, "link-results.json"), "w"), indent=2, ensure_ascii=False) + + +main() diff --git a/documentation/audits/prowlarr-2026-10-15/tools/walk_prowlarr.py b/documentation/audits/prowlarr-2026-10-15/tools/walk_prowlarr.py new file mode 100644 index 00000000..62300b06 --- /dev/null +++ b/documentation/audits/prowlarr-2026-10-15/tools/walk_prowlarr.py @@ -0,0 +1,207 @@ +#!/usr/bin/env python3 +"""walk_prowlarr.py — Prowlarr on scratch guest 9202 THROUGH THE PRODUCT, from the drill catalog (R-1048, 2026-10-15). + +Runs ON DOOPLEX with TARGET=9202 and SC=<0600 dir holding .ctlpw>. 9202 must already be pointed at the drill mirror, +whose prowlarr template is the live candidate at the PREVIOUS image (version-2.5.2.5491). Presses only what a person +presses (box_walk.py): deploy, the setup gate as household and stranger, Prowlarr's own first-admin and sign-in routes +through traefik over https, „Kész", „Mentés most", the drill bump + the guarded Update, remove. + +Closes NEW-APP-CHECKLIST 3.1 3.3 3.5 3.6 3.9 3.10 2.5 2.6 4.3 6.2(box) 6.3 9.2. Writes the evidence to EV and the box +verdict to EV/prowlarr/box-verdict-prowlarr.json (the ladder writer's input). The household password it creates is kept +only in SC (0600) for the life of the run; the app's API key is never printed. +""" +import json +import os +import re +import secrets +import sys +import time + +sys.path.insert(0, "/mnt/5_hdd/felhom.eu/git/app-catalog-felhom.eu/scripts") +import box_walk as w # noqa: E402 +import upgrade_fixtures_box as fx # noqa: E402 + +EV = os.environ["EV"] +APP, SUB = "prowlarr", "prowlarr" +FROM = "lscr.io/linuxserver/prowlarr:version-2.5.2.5491" +TO = "lscr.io/linuxserver/prowlarr:version-2.6.5.5623" +os.makedirs(os.path.join(EV, APP), exist_ok=True) +LOG = open(os.path.join(EV, "walk.txt"), "a", buffering=1) +RES = {} + + +_lib_say = w.say + + +def say(*a): + _lib_say(*a) + LOG.write(" ".join(str(x) for x in a) + "\n") + + +w.say = say # the library's own lines land in the same record (the original is kept above — no recursion) + + +def stranger(path, method="GET", data=None, accept="application/json", extra=()): + """A visitor with NO dashboard session and NO gate cookie, on the app's public name over https.""" + args = ["curl", "-sSk", "--max-time", "20", "-o", "/tmp/pw-body.txt", "-D", "-", "-H", f"Host: {SUB}.{w.DOMAIN}", + "-H", f"Accept: {accept}"] + list(extra) + if method != "GET": + args += ["-X", method] + if data is not None: + args += ["--data-binary", "@-"] + r = w.sh(args + [f"{w.BASE}{path}"], inp=data) + head = r.stdout or "" + st = re.match(r"HTTP/\S+ (\d+)", head) + loc = re.search(r"(?im)^location:\s*(\S+)", head) + body = w.sh(["head", "-c", "200", "/tmp/pw-body.txt"]).stdout.replace("\n", " ") + return (st.group(1) if st else "000"), (loc.group(1) if loc else ""), body, head + + +def household(path, method="GET", body=None, headers=()): + ext = [] + for h in headers: + ext += ["-H", h] + if body is not None: + ext += ["-H", "Content-Type: application/json"] + rc, code, out = w.app_curl(SUB, path, *ext, method=method, data=json.dumps(body) if body is not None else None) + return code, out + + +def main(): + w.login() + st = w.stack(APP) + if st.get("deployed"): + sys.exit(say("prowlarr is installed on 9202 — STOP, not a fresh install") or 1) + + # ── 3.5 + 4.3: install, and a stranger polls the public name once a second through the install window ── + t0 = time.time() + code, d = w.ctl("POST", f"/api/stacks/{APP}/deploy", {"values": {"DOMAIN": w.DOMAIN, "SUBDOMAIN": SUB}}) + say(f"[1] POST /api/stacks/{APP}/deploy -> {code} {json.dumps(d, ensure_ascii=False)[:160]}") + w.DEPLOYED_THIS_RUN.add(APP) + seen = {} + for i in range(90): + for p in ("/", "/initialize.json", "/api/v1/system/status"): + c, loc, body, _ = stranger(p, accept="text/html" if p == "/" else "application/json") + key = f"{p} -> {c} {('loc=' + loc[:60]) if loc else ''} {'API-KEY-IN-BODY' if 'apiKey' in body else ''}".strip() + seen[key] = seen.get(key, 0) + 1 + s = w.stack(APP) + if s.get("deployed") and s.get("state") == "running" and i > 20: + break + time.sleep(1) + say(f"[1] install window, a stranger on the public name ({round(time.time() - t0)} s, distinct answers):") + for k, n in sorted(seen.items()): + say(f" {n:3d}x {k}") + RES["install_window"] = seen + s = w.stack(APP) + say(f"[1] state={s.get('state')} health={s.get('health')} restarts: " + + w.guest("docker inspect prowlarr -f '{{.RestartCount}} started={{.State.StartedAt}} health={{.State.Health.Status}}'").strip()) + say("[1] the first-start seed line: " + w.guest("docker logs prowlarr 2>&1 | grep -m1 felhom").strip()) + say("[1] config.xml (key redacted): " + w.guest("docker exec prowlarr cat /config/config.xml | sed -E 's#[^<]+##' | tr '\\n' ' '").strip()) + + # ── 3.1: the household reaches the first-run screen through the gate; its key comes from the app's own page ── + code, out = household("/initialize.json") + m = re.search(r'"?apiKey"?\s*:\s*"([0-9a-fA-F]{20,})"', out) + say(f"[2] household GET /initialize.json through the gate -> {code}, carries the API key: {bool(m)}") + key = m.group(1) if m else "" + code, out = household("/api/v1/config/host", headers=(f"X-Api-Key: {key}",)) + hc = json.loads(out) if code == "200" else {} + say(f"[2] GET /api/v1/config/host -> {code}: authenticationMethod={hc.get('authenticationMethod')} " + f"authenticationRequired={hc.get('authenticationRequired')} analyticsEnabled={hc.get('analyticsEnabled')}") + user, pw = "felhom-test", "Pw-" + secrets.token_urlsafe(14) + open(os.path.join(w.SC, "prowlarr-household.json"), "w").write(json.dumps({"user": user, "password": pw})) + os.chmod(os.path.join(w.SC, "prowlarr-household.json"), 0o600) + hc.update({"authenticationMethod": "forms", "username": user, "password": pw, "passwordConfirmation": pw}) + code, out = household("/api/v1/config/host", method="PUT", body=hc, headers=(f"X-Api-Key: {key}",)) + say(f"[2] the household makes its first account (PUT /api/v1/config/host, forms) -> {code}") + RES["first_admin"] = code + time.sleep(8) + w.wait_app(SUB, "/ping", want=("200",), tries=24) + + # ── 3.9: sign in the way the browser does, through traefik over https (still behind the gate) ── + for label, p in (("right", pw), ("wrong", "nope-" + secrets.token_hex(4))): + rc, code, out = w.app_curl(SUB, "/login?returnUrl=%2F", "-H", "Content-Type: application/x-www-form-urlencoded", + "-D", "-", method="POST", data=f"username={user}&password={p}&rememberMe=on") + loc = re.search(r"(?im)^location:\s*(\S+)", out) + ck = re.search(r"(?im)^set-cookie:\s*([A-Za-z]+Auth)=", out) + say(f"[3] sign in ({label} password) POST /login -> {code} loc={loc.group(1) if loc else '-'} auth-cookie={ck.group(1) if ck else 'none'}") + RES[f"signin_{label}"] = [code, loc.group(1) if loc else "", bool(ck)] + + # ── 6.1 C1 + 2.5: seed through the app's own API, read back, back it up through the product ── + fxp = fx.FIXTURES[APP] + tok = fxp.seed(w, SUB, say) + ok1 = bool(tok) and fxp.verify(w, SUB, tok, say) + say(f"[4] seed {tok} read back before the update: {ok1}") + bk = w.backup_now(APP) + say(f"[4] „Mentés most” -> {json.dumps(bk, ensure_ascii=False)[:200]}") + + # ── 3.3: „Kész" opens the gate; then a stranger meets the APP's own sign-in, never the app ── + code, d = w.ctl("POST", f"/apps/{APP}/setup-gate/open") + say(f"[5] the household's „Kész, beállítottam” -> {code} {json.dumps(d, ensure_ascii=False)[:120]}") + time.sleep(5) + for p, acc in (("/", "text/html"), ("/initialize.json", "application/json"), ("/api/v1/system/status", "application/json"), + ("/api/v1/indexer", "application/json")): + c, loc, body, _ = stranger(p, accept=acc) + say(f"[5] stranger after the gate opened: GET {p} -> {c} {('loc=' + loc[:70]) if loc else ''} apiKey-in-body={'apiKey' in body}") + RES[f"stranger_open_{p}"] = [c, loc, "apiKey" in body] + + # ── 3.6 + 3.10: six wrong passwords from a stranger; then the household; and what address the app logged ── + for i in range(6): + c, loc, _b, _ = stranger("/login?returnUrl=%2F", method="POST", data=f"username={user}&password=wrong{i}", + accept="text/html", extra=("-H", "Content-Type: application/x-www-form-urlencoded")) + say(f"[6] stranger wrong password #{i + 1} -> {c} loc={loc[:60]}") + c, loc, _b, head = stranger("/login?returnUrl=%2F", method="POST", data=f"username={user}&password={pw}", + accept="text/html", extra=("-H", "Content-Type: application/x-www-form-urlencoded")) + say(f"[6] the household's right password right after (same address) -> {c} loc={loc[:60]} " + f"auth-cookie={'yes' if re.search(r'(?im)^set-cookie:.*Auth=', head) else 'no'}") + say("[6] what the app logged for the failures (address): " + + w.guest("docker exec prowlarr sh -c \"grep -h -i 'auth-failure\\|Auth-' /config/logs/prowlarr*.txt | tail -3\"").strip().replace("\n", " | ")[:600]) + + # ── 6.2/6.3 box: the drill bump and the product's guarded Update; the seed read back after ── + # the drill bump, staged by explicit path (box_walk.drill_bump runs `git add -A`, which this task forbids) + D = w.DRILL + comp, fy = f"{D}/templates/{APP}/docker-compose.yml", f"{D}/templates/{APP}/.felhom.yml" + w.sh(["git", "-C", D, "pull", "-q", "--rebase", "origin", "main"], timeout=120) + c_ = open(comp).read() + if f"image: {FROM}" not in c_: + sys.exit(say(f"[7] FROM {FROM} not in the drill compose — STOP") or 1) + open(comp, "w").write(c_.replace(f"image: {FROM}", f"image: {TO}")) + # FIXED after run 1: this line used to be `open(fy, "w").write(re.sub(..., open(fy).read(), ...))`, which truncates the + # file BEFORE it is read — run 1's bump commit (drill f2b0cc1) emptied the drill's .felhom.yml, so run 1's Update ran + # without the app's metadata. Run 3 (walk_restore.py, box/run3/) re-proves the step with the full file. + fy_text = open(fy).read() + open(fy, "w").write(re.sub(r'^catalog_since:.*$', 'catalog_since: "' + time.strftime("%Y-%m-%d") + '"', fy_text, count=1, flags=re.M)) + w.sh(["git", "-C", D, "add", f"templates/{APP}/docker-compose.yml", f"templates/{APP}/.felhom.yml"]) + w.sh(["git", "-C", D, "commit", "-q", "-m", f"DRILL {APP}: {FROM} -> {TO} (R-1048 box proof)"]) + r_ = w.sh(["git", "-C", D, "push", "-q", "origin", "main"], timeout=120) + say(f"[7] drill commit {w.sh(['git', '-C', D, 'rev-parse', '--short=12', 'HEAD']).stdout.strip()}: {FROM} -> {TO} (push rc={r_.returncode})") + # the mirror on 9202 is what the box reads: reload it from the drill repo + import subprocess + subprocess.run([sys.executable, os.path.join(os.path.dirname(os.path.abspath(__file__)), "repoint9202.py"), "mirror"], check=True, + env=dict(os.environ)) + w.sync_rescan(expect_app=APP, expect_ref=TO) + say(f"[7] badges: {json.dumps(w.badges(APP), ensure_ascii=False)[:300]}") + up = w.press_update(APP) + say(f"[7] guarded Update: {json.dumps(up, ensure_ascii=False)[:400]}") + ok2 = bool(tok) and fxp.verify(w, SUB, tok, say) + obs = w.observables(APP) + say(f"[7] after: {json.dumps(obs, ensure_ascii=False)[:400]}") + s = w.stack(APP) + healthy = s.get("state") == "running" + verdict = {"app": APP, "venue": "box 9202 (drill catalog, controller 0.304.0), the product's guarded Update", + "from": {APP: FROM}, "to": {APP: TO}, + "verdict": "proven" if (ok1 and ok2 and up.get("final_phase") == "done" and healthy) else "failed", + "seed_read_before": ok1, "seed_read_after": ok2, "healthy_after": healthy, + "duration_s": up.get("duration_s"), "final_phase": up.get("final_phase"), + "measured_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()), + "evidence": "felhom.eu/documentation/audits/prowlarr-2026-10-15/box/walk.txt"} + json.dump(verdict, open(os.path.join(EV, APP, "box-verdict-prowlarr.json"), "w"), indent=2) + say(f"[8] box verdict {verdict['verdict']}") + + # ── 2.6: remove through the product; nothing left ── + if os.environ.get("KEEP") != "1": + w.remove(APP) + say("[9] after remove: " + w.guest("echo containers=$(docker ps -aq -f name=^prowlarr$ | wc -l) volumes=$(docker volume ls -q | grep -c prowlarr) stackdir=$(ls -d /opt/docker/stacks/prowlarr 2>/dev/null | wc -l)").strip()) + json.dump(RES, open(os.path.join(EV, "walk-results.json"), "w"), indent=2, ensure_ascii=False) + + +main() diff --git a/documentation/audits/prowlarr-2026-10-15/tools/walk_restore.py b/documentation/audits/prowlarr-2026-10-15/tools/walk_restore.py new file mode 100644 index 00000000..b8f6b339 --- /dev/null +++ b/documentation/audits/prowlarr-2026-10-15/tools/walk_restore.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python3 +"""walk_restore.py — NEW-APP-CHECKLIST 2.5 for Prowlarr on scratch guest 9202: seed -> backup -> remove -> restore -> +read back, THROUGH THE PRODUCT (R-1048, 2026-10-15, second 9202 run). + +The box has no per-app „Mentés most" (R-648: the only backup press is whole-box and is never pressed from a drill), so +the per-app backup is the one the guarded Update takes in its `backing-up` phase — the same route as grocy's and +lubelogger's restore records. Runs ON DOOPLEX with TARGET=9202, SC=<0600 dir holding .ctlpw>, EV=; 9202 +must already read the drill mirror, whose prowlarr template is at FROM. Presses: deploy, the drill bump + Update, +remove KEEPING the backups, the household's restore button (POST /backup/restore), remove with the backups. +""" +import json +import os +import subprocess +import sys +import time + +sys.path.insert(0, "/mnt/5_hdd/felhom.eu/git/app-catalog-felhom.eu/scripts") +import box_walk as w # noqa: E402 +import upgrade_fixtures_box as fx # noqa: E402 + +EV = os.environ["EV"] +APP, SUB = "prowlarr", "prowlarr" +FROM = "lscr.io/linuxserver/prowlarr:version-2.5.2.5491" +TO = "lscr.io/linuxserver/prowlarr:version-2.6.5.5623" +TOOLS = os.path.dirname(os.path.abspath(__file__)) +LOG = open(os.path.join(EV, "restore.txt"), "a", buffering=1) +_lib_say = w.say + + +def say(*a): + _lib_say(*a) + LOG.write(" ".join(str(x) for x in a) + "\n") + + +w.say = say + + +def drill_set(ref, why): + """Point the drill template at `ref`, staged by explicit path, then reload the 9202 mirror.""" + D = w.DRILL + comp, fy = f"{D}/templates/{APP}/docker-compose.yml", f"{D}/templates/{APP}/.felhom.yml" + w.sh(["git", "-C", D, "pull", "-q", "--rebase", "origin", "main"], timeout=120) + c_ = open(comp).read() + other = TO if ref == FROM else FROM + if f"image: {ref}" not in c_: + open(comp, "w").write(c_.replace(f"image: {other}", f"image: {ref}")) + w.sh(["git", "-C", D, "add", f"templates/{APP}/docker-compose.yml", f"templates/{APP}/.felhom.yml"]) + w.sh(["git", "-C", D, "commit", "-q", "-m", f"DRILL {APP}: {why} -> {ref} (R-1048, checklist 2.5)"]) + r_ = w.sh(["git", "-C", D, "push", "-q", "origin", "main"], timeout=120) + say(f"drill commit {w.sh(['git', '-C', D, 'rev-parse', '--short=12', 'HEAD']).stdout.strip()}: -> {ref} (push rc={r_.returncode})") + subprocess.run([sys.executable, os.path.join(TOOLS, "repoint9202.py"), "mirror"], check=True, env=dict(os.environ)) + + +def main(): + w.login() + if w.stack(APP).get("deployed"): + sys.exit(say("prowlarr is installed on 9202 — STOP, not a fresh install") or 1) + drill_set(FROM, "back to the previous image for a step") + w.sync_rescan(expect_app=APP, expect_ref=FROM) + code, d = w.ctl("POST", f"/api/stacks/{APP}/deploy", {"values": {"DOMAIN": w.DOMAIN, "SUBDOMAIN": SUB}}) + say(f"[1] deploy -> {code} {json.dumps(d, ensure_ascii=False)[:120]}") + w.DEPLOYED_THIS_RUN.add(APP) + for _ in range(60): + s = w.stack(APP) + if s.get("deployed") and s.get("state") == "running": + break + time.sleep(3) + w.wait_app(SUB, "/ping", want=("200",), tries=40) + say("[1] through traefik: " + w.guest(f"curl -sk -o /dev/null -w '%{{http_code}}' -H 'Host: {SUB}.{w.DOMAIN}' https://127.0.0.1/ping").strip() + + " (gated: 302 to the gate is the healthy answer for a stranger)") + say("[1] stack .felhom.yml bytes: " + w.guest(f"wc -c < /opt/docker/stacks/{APP}/.felhom.yml").strip()) + say("[1] running: " + w.guest("docker inspect prowlarr -f '{{.Config.Image}} health={{.State.Health.Status}}'").strip()) + + fxp = fx.FIXTURES[APP] + tok = fxp.seed(w, SUB, say) + ok1 = bool(tok) and fxp.verify(w, SUB, tok, say) + say(f"[2] seed {tok} read back: {ok1}") + say("[2] 2.7 config volume size after the seed: " + w.guest("du -sh /var/lib/docker/volumes/prowlarr_prowlarr_config/_data | cut -f1").strip()) + say("[2] 4.4 negative control, the same probe on a port nothing serves: rc=" + w.guest("docker exec prowlarr curl -sf -o /dev/null http://127.0.0.1:9697/ping; echo $?").strip() + + " ; the real probe: rc=" + w.guest("docker exec prowlarr curl -sf -o /dev/null http://127.0.0.1:9696/ping; echo $?").strip()) + say("[2] 1.8 the app's own start lines: " + w.guest("docker logs prowlarr 2>&1 | grep -m2 -E 'Hosting environment|Application started'").strip().replace("\n", " | ")) + + drill_set(TO, "the step") + w.sync_rescan(expect_app=APP, expect_ref=TO) + up = w.press_update(APP) + say(f"[3] guarded Update (its backing-up phase is the per-app backup): final={up.get('final_phase')} " + f"phases={[p.get('phase') for p in up.get('phases', [])]}") + ok2 = bool(tok) and fxp.verify(w, SUB, tok, say) + say(f"[3] seed read back after the Update: {ok2}") + obs = w.observables(APP) + say(f"[3] after: {json.dumps(obs, ensure_ascii=False)[:400]}") + s = w.stack(APP) + if os.environ.get("VERDICT") == "1": + verdict = {"app": APP, "venue": "box 9202 (drill catalog), the product's guarded Update — run 3, the full .felhom.yml", + "from": {APP: FROM}, "to": {APP: TO}, + "verdict": "proven" if (ok1 and ok2 and up.get("final_phase") == "done" and s.get("state") == "running") else "failed", + "seed_read_before": ok1, "seed_read_after": ok2, "healthy_after": s.get("state") == "running", + "duration_s": up.get("duration_s"), "final_phase": up.get("final_phase"), + "measured_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()), + "evidence": "felhom.eu/documentation/audits/prowlarr-2026-10-15/box/run3/restore.txt"} + os.makedirs(os.path.join(EV, "..", APP), exist_ok=True) + json.dump(verdict, open(os.path.join(EV, "..", APP, "box-verdict-prowlarr.json"), "w"), indent=2) + say(f"[3] box verdict {verdict['verdict']}") + + snaps = w.snapshots(APP) + say(f"[4] restore points offered BEFORE the remove: {json.dumps(snaps, ensure_ascii=False)[:400]}") + c1, d1 = w.ctl("POST", f"/api/stacks/{APP}/stop") + say(f"[4] stop -> {c1}") + for _ in range(24): + time.sleep(5) + if w.stack(APP).get("state") != "running": + break + code, d = w.ctl("POST", f"/api/stacks/{APP}/remove", {"remove_hdd_data": False, "remove_backups": False}) + say(f"[4] remove KEEPING the backups -> {code} {json.dumps(d, ensure_ascii=False)[:300]}") + time.sleep(5) + say("[4] left after remove: " + w.guest("echo containers=$(docker ps -aq -f name=^prowlarr$ | wc -l) volumes=$(docker volume ls -q | grep -c prowlarr)").strip()) + + rr = w.restore(APP) + say(f"[5] restore: {json.dumps({k: rr.get(k) for k in ('ok', 'snapshot_id', 'http', 'seconds', 'state_after', 'hold_after', 'why')}, ensure_ascii=False)}") + w.wait_app(SUB, "/ping", want=("200",), tries=40) + ok3 = bool(tok) and fxp.verify(w, SUB, tok, say) + say(f"[5] 2.5 the seed reads back after remove + restore: {ok3}") + say("[5] through traefik after restore: " + w.guest(f"curl -sk -o /dev/null -w '%{{http_code}}' -H 'Host: {SUB}.{w.DOMAIN}' https://127.0.0.1/ping").strip()) + say("[5] app.yaml env keys after restore: " + w.guest(f"sed -n '/^env:/,/^[a-z]/p' /opt/docker/stacks/{APP}/app.yaml | grep -oE '^ +[A-Z_]+' | paste -sd' '").strip()) + say("[5] image after restore: " + w.guest("docker inspect prowlarr -f '{{.Config.Image}} health={{.State.Health.Status}}'").strip()) + + # teardown through the product, backups too (this is a scratch install) + w.remove(APP) + say("[9] after remove: " + w.guest("echo containers=$(docker ps -aq -f name=^prowlarr$ | wc -l) volumes=$(docker volume ls -q | grep -c prowlarr)").strip()) + json.dump({"seed": tok, "seed_before": ok1, "after_update": ok2, "after_restore": ok3, + "update_final": up.get("final_phase"), "restore": {k: rr.get(k) for k in ("ok", "snapshot_id", "http", "seconds", "state_after")}, + "measured_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())}, + open(os.path.join(EV, "restore-results.json"), "w"), indent=2, ensure_ascii=False) + + +main() diff --git a/documentation/audits/sizing-2026-10-13/SIZING.md b/documentation/audits/sizing-2026-10-13/SIZING.md index 4dd6078d..ca87745f 100644 --- a/documentation/audits/sizing-2026-10-13/SIZING.md +++ b/documentation/audits/sizing-2026-10-13/SIZING.md @@ -187,11 +187,11 @@ a backup at the same time can slow it down · **does not fit:** guest < peak. Pe | Household set | 5.9 GB | does not fit | fits well | fits well | fits well | | Household set without Immich | 2.7 GB | fits, but tight | fits well | fits well | fits well | | Immich alone | 3.7 GB | fits, but tight | fits well | fits well | fits well | -| All 62 apps | 21.8 GB | does not fit | does not fit | does not fit | fits, but tight | +| All 63 apps | 21.9 GB | does not fit | does not fit | does not fit | fits, but tight | - **8 GB:** the household set without Immich fits, but tight; Immich alone fits, but tight; the whole household set does not fit. - **16 GB:** the whole household set fits well. -- **32 GB:** the household set fits well; all 62 apps at once fit, but tight. +- **32 GB:** the household set fits well; all 63 apps at once fit, but tight. 2026-10-14: raised with the HP check (operator ruling A); one verdict became stricter — household set without Immich on 8 GB, by 0.5 MB. diff --git a/documentation/audits/sizing-2026-10-13/sizing.json b/documentation/audits/sizing-2026-10-13/sizing.json index 8d42174e..2e2e3328 100644 --- a/documentation/audits/sizing-2026-10-13/sizing.json +++ b/documentation/audits/sizing-2026-10-13/sizing.json @@ -682,6 +682,17 @@ "measured_on": "demo-felhom (N100)", "date": "2026-10-13" }, + { + "slug": "prowlarr", + "display_name": "Prowlarr", + "mem_typical_mb": 41, + "mem_peak_mb": 44, + "cpu_class": "light", + "cpu_note": "", + "source": "onboarding", + "measured_on": null, + "date": "2026-10-15" + }, { "slug": "radicale", "display_name": "Radicale", diff --git a/documentation/backlog/OPEN-ITEMS.md b/documentation/backlog/OPEN-ITEMS.md index 3c48dd23..ec684001 100644 --- a/documentation/backlog/OPEN-ITEMS.md +++ b/documentation/backlog/OPEN-ITEMS.md @@ -135,7 +135,7 @@ stopping line that lies. | **R-1018** | Apps & catalog | P3 | **The OnlyOffice links (FileBrowser, Nextcloud) have no recorded live proof since the move to Proxmox.** `controller/internal/integrations/` holds two handlers; no audit after the Proxmox move shows a document opened from FileBrowser or Nextcloud in OnlyOffice (searched 2026-10-14). The media-app links of `15` §6 will copy this code, including its revoke-on-stop rule (`lifecycle.go:10-13`). | **OPEN** — filed 2026-10-14 | — | One live walk on a Tier-0 box: install OnlyOffice + FileBrowser, open a document, save, read it back; then stop one app and record what the link does | CC | | **R-1028** | Apps & catalog | P3 | **Plex's claim code can run out during the install, and the box cannot recover it.** plex.tv's code lives 4 minutes from the moment the household copies it; the clock covers the wizard, the image pull (`plexinc/pms-docker` is 351 MB, measured) and the first start. Plex reads `PLEX_CLAIM` only on its first run, and the field is `locked_after_deploy` — so a late code leaves an unclaimed server and the only way back is remove + install again. Reported by the operator during the 2026-10-14 test; on Tester 1's line the pull+start took 8.5 s and the claim worked (`audits/media-all-drives-2026-10-14/``G-auth/G3-plex-claim.txt`), so it bites on a slow line or a slow hand. **K6, 2026-10-14 night (`audits/media-drive-events-2026-10-14/``K6-1-unclaimed-plex.txt`):** an unclaimed Plex starts with no code at all, and `POST /myplex/claim?token=` on the RUNNING server reaches plex.tv (a wrong code → 500 after plex.tv's 401). So the box can pull + start first and ask for the code after: no race. A real claim needs the household's code — the morning step on the throwaway `plex-k6` on Tester 1. Narrowed 2026-10-14 night. **PROVEN 2026-10-15 morning (`K6-2-claim-after-start.txt`):** the operator's real code, given to the RUNNING unclaimed `plex-k6` → 200, `claimed` 0→1, `PlexOnlineToken` written, and the open door closed (no token → 401). `ADVERTISE_IP` was unset and the claim did not need it; whether clients find the server without it was not measurable (the throwaway had no published port). **BUILT 2026-10-15 (v0.313.0):** `after_start` field — not asked at install; the Plex page asks for the code once Plex runs and claims the running server; the gate opens only when Plex says claimed. Proven up to the code on 9201 (G5). | **NARROWED** — the dashboard claim with a real code is the last step | a household Plex code | Claim through the Plex page with a real code (G5c); then close | CC | | **R-1029** | Apps & catalog | P3 | **Plex's folder picker shows the drives as identical „media” folders.** With one bind per drive at the same path (`/mnt/felhom-drives//userdata/media`), the leaf of every library folder is `media`; the drive's name sits two levels up, so in Plex's picker the household sees several folders all called „media” (reported by the operator during the 2026-10-14 test on Tester 1). It conflicts with `15` §2 reason 2 (the same path everywhere). | **READY** — filed 2026-10-14 | the layout decision (STATUS D9) | Decide in `15` §2: the players (`:ro`, never linked) may get a readable path per drive (e.g. `/media/`) without breaking the hardlink rule, which binds only the download/library apps | CC | -| **R-1036** | Apps & catalog | P3 | **The apps page lags the catalog by two apps.** `app-catalog-felhom.eu/templates/` holds **64** apps; `/alkalmazasok` and `/en/apps` render **63** cards, one of which (`filebrowser`) is built in and not a catalog template — so **62 catalog apps are listed**. Missing: **`plant-it`** and **`wger`**, both in the catalog since 2026-10-06/08. Their website assets are ALREADY present (`plant-it-logo.svg` + 3 screenshots, `wger-logo.png` + 3 screenshots, renamed to the catalog slug by `a3507761`, 2026-10-08) — only the cards were never written, so this is not a new-asset job. Found 2026-10-14 while reconciling the app count for the „felhő tárhely” page, where four places said **60** and the rest said **62**. **Done in that session:** every stated count on the site set to **62** (`index.html`, `szolgaltatasok.html`, `en/services.html` were 60), which is what the apps page actually shows — so no visitor can count the cards and find the number wrong. **62 is still not the catalog's number.** **To close:** write the two cards on both apps pages (gate 15 keeps the two pages' app sets equal, so they move together), then raise every stated count 62 → 64 in the same commit. **The operator chose this split at the 2026-10-14 STOP** over doing it inside the SEO change. | **OPEN** | — | write the `plant-it` + `wger` cards on both apps pages, then 62 → 64 everywhere, one commit | CC | +| **R-1036** | Apps & catalog | P3 | **The apps page lags the catalog by two apps.** `app-catalog-felhom.eu/templates/` holds **64** apps; `/alkalmazasok` and `/en/apps` render **63** cards, one of which (`filebrowser`) is built in and not a catalog template — so **62 catalog apps are listed**. Missing: **`plant-it`** and **`wger`**, both in the catalog since 2026-10-06/08. Their website assets are ALREADY present (`plant-it-logo.svg` + 3 screenshots, `wger-logo.png` + 3 screenshots, renamed to the catalog slug by `a3507761`, 2026-10-08) — only the cards were never written, so this is not a new-asset job. Found 2026-10-14 while reconciling the app count for the „felhő tárhely” page, where four places said **60** and the rest said **62**. **Done in that session:** every stated count on the site set to **62** (`index.html`, `szolgaltatasok.html`, `en/services.html` were 60), which is what the apps page actually shows — so no visitor can count the cards and find the number wrong. **62 is still not the catalog's number.** **To close:** write the two cards on both apps pages (gate 15 keeps the two pages' app sets equal, so they move together), then raise every stated count 62 → 64 in the same commit. **The operator chose this split at the 2026-10-14 STOP** over doing it inside the SEO change. **2026-10-15 (R-1048):** Prowlarr's card added on both pages and every stated count 62 → 63 (the media group 19 → 20), so the site still states what it shows; the two owed cards would make it 65. **Note before writing them:** `wger` is `lifecycle: hidden` and `plant-it` `lifecycle: abandoned` — neither is offered for a new install, so a card would advertise an app the wizard refuses. | **OPEN** | — | write the `plant-it` + `wger` cards on both apps pages, then 62 → 64 everywhere, one commit | CC | | **R-76** | Apps & catalog | P4 | **FileBrowser-created folders break the setgid chain, and a drop-zone's mode is not stable** **MIGRATED FROM `ROADMAP.md` 2026-08-22 (R-369) — originally filed 2026-07-26, size S, roadmap state `idea (surfaced by the R-75 spike, 2026-07-26)`.** Moved verbatim; nothing added or reinterpreted. The roadmap keeps its copy as history, marked moved. | **OPEN — migrated from ROADMAP 2026-08-22, rank unchanged** **2026-10-06 night: not a catalog fix** — the setgid chain is set by the controller's FileBrowser setup; a controller row. | — | Two related findings from `audits/SPIKE-catalog-data-paths-2026-07-26.md` P3/P5, both **pre-existing** and deliberately left alone by that spike. **(a)** FileBrowser Quantum 1.3.3 creates files `0644` and folders `0755` and does **not** propagate the setgid bit — even though the entrypoint wrapper's `umask 002` really is in effect (`/proc/1/status` `Umask: 0002`). Group inheritance itself works (a file uploaded into a 2775 group-100 dir landed group 100, not the process gid 1000), so the convention's *group* half holds and only its *mode* half is lost. The consequence is proven with a control: inside a UI-created `0755` folder a gid-1000 process's file landed group **1000**, while the identical write into the 2775 parent landed group **100**. So **any folder a customer creates through FileBrowser breaks the shared-group chain one level down.** Latent today — every userdata-touching catalog app that declares an identity declares uid/gid **1000**, the same uid FileBrowser runs as, so owner permissions mask it; it bites the day a content app runs as a different non-root uid with gid 1000. The comment at `infra/infra.go:156` is right that the image ignores `-e UMASK` but does not say t | CC | | **R-707** | Apps & catalog | P4 | **[P2] 37 apps still start with a login a stranger can take (`09` §3 decision 45).** Audit of all 53 apps: `app-catalog-felhom.eu/FIRST-ADMIN.md` (class, fix route, status, measured or read). Open: **3 hard-coded defaults** — calibre-web (`admin / admin123`, measured working on demo-hp and 9202; its own `cps.py -s` route needs a generated password WITH a special character — our generator is letters+digits, a controller change), mealie (`changeme@example.com / MyPassword`), wger (`admin / adminadmin`); **34 open first-run screens** (the first visitor creates the admin: actualbudget, adventurelog, audiobookshelf, calcom, docmost, emby, ghost, gitea, gramps-web, home-assistant, homebox, immich, jellyfin, komga, n8n, navidrome, opengist, outline, papra, plant-it, radarr, rallly, recipe-importer, romm, seerr, sonarr, sparkyfitness, tandoor, termix, uptime-kuma, vikunja, wanderer, wishlist, zipline). **Stale notes:** romm's `default_creds` `admin / admin` answers 401 on demo-hp (like a wrong password) — the page now warns with a login that does not exist; zipline's looks stale too. **Measured on demo-hp 2026-09-28 (read-only):** bookstack's default still logs in on the INSTALLED app (the fix is for new installs; the page now warns). Each fix: route (a) env or (b) the app's own CLI/API via `after_install:`, proven on 9202 with the default failing and the generated password working; route (c) a page sentence. Several sessions (operator, 2026-09-28). **2026-09-29 (controller v0.280.0, catalog `d0e7e2e`):** every class-3 app fixed — mealie, wger, calibre-web by `after_install` (calibre-web with the new `password:24:special`), proven on 9202 fresh installs (`audits/login-gate-2026-09-29/D/`); the setup gate (decision 46, spike PASSED) built and live on immich, n8n, audiobookshelf (probes measured) and uptime-kuma (button) (`…/C/`); romm's and zipline's stale notes removed. **Left: 30 class-4 apps** — gate each (probe measured on 9202 where one exists — 11 upstream candidates listed in `…/B/B-VERDICT.md` §3; the button otherwise). **2026-09-29 afternoon (controller v0.281.0, catalog `6faf432`):** 28 more class-4 apps gated — 32 of 34 — each proven on 9202 (`audits/gate-rollout-2026-09-29/`B): stranger → gate page / 401, household reached the first-setup screen, the gate opened (9 by a measured probe, the rest by the press), the app answered after. seerr, outline, rallly: gated, their opening needs a media server / e-mail (not proven). **Left:** wanderer (R-714); plant-it is not installable. | **NARROWED** (2026-10-03 triage: the row's verdict was finished, but it names open work no other row carries — seerr, outline and rallly are gated, but the gate OPENING is not proven (needs a media server / e-mail)) — **CLOSED — 2026-09-29 (the rest → R-714)** | — | — | CC | | **R-905** | Apps & catalog | P4 | **wger's collected style files (283 MB) go into every wger backup, though the app rebuilds them at every start.** Since R-762's fix (`DJANGO_DEBUG=False` + `collectstatic`, catalog `cf1ed43`) the static files sit in a named volume that the backup legs copy like data (measured 283 MB, `audits/design-build-2026-10-06/`); `collectstatic` regenerates them, so they are rebuildable bytes in every Tier-1 unit, Tier-2 copy and off-site snapshot. wger is `lifecycle: hidden` and no box runs it (hub read 2026-10-08). Options to weigh: a non-backed-up volume class for regenerable data, or an anonymous volume. Found by the R-762 helpers 2026-10-08. | **OPEN — needs a design before wger is shown** | — | Design the „regenerable volume" exclusion (catalog + controller backup legs) | CC | @@ -197,7 +197,7 @@ stopping line that lies. | **R-332** | Storage & devices | P3 | **The new Hiba-from-counters path has never fired on real hardware.** v0.215.0's whole point is a verdict the product could not previously reach, and it is proven only against the committed fixture's values in unit tests (12 scenario groups, 11 of 12 red-proofs failing as required). The live validation on demo-hp proved the **negative** — three healthy disks still read Rendben across the deploy, no false alert — and the **severity wire** end to end, but no live disk has actually reached Hiba. **This is the honest gap and it must not be closed by pointing at the fixture tests**: the drive that produced the fixture is in DooPlex, which is Tier 2 and never a drill target, and the demo boxes are all-flash and healthy | **WATCHING — NEW 2026-08-14, NARROWED same day.** One item originally in this gap is now PROVEN LIVE: the **persisted state surviving a controller restart**. The v0.215.0→v0.216.0 redeploy destroyed and rebuilt the container, and the new one read back a `changed_at` written by the PREVIOUS version (`2026-08-14T07:23:14.640216851Z`, still intact at 09:31:35Z) instead of re-baselining — Scenario L on real hardware, not just the production-path unit test. **What remains unproven is the verdict itself, plus the stronger restart half: an already-ALERTED disk not re-alerting** | a real degrading disk, or an injection harness | **Closing condition:** a live disk reaching Hiba from counters, OR a deliberate injection through the REAL pipeline (agent `/disks` → controller check → hub event), not a hand-set verdict | CC | | **R-331** | Storage & devices | P4 | **Disk health Phase 3 — growth-rate detection, and retiring the static 64.** The v0.215.0 count backstop (64 unreadable sectors → Hiba) is **a judgement from ONE drive**: the observed benign excursion peaked at 16 and cleared inside an hour, and the terminal run passed 64 at 13 Aug 11:28 and never came back. It is deliberately a backstop BEHIND the sustain rule, not the primary signal, but it is still a magic number tuned on a single sample and it will be wrong for some drive. With Phase 2's history the box can ask the question that actually matters — *is this count climbing, and how fast* — which distinguishes a drive with eight stable aging sectors from one adding forty a day, something no static threshold can do. Revisit 64 when that exists | **READY (M) — NEW 2026-08-14** | R-330 | Growth-rate rule over persisted samples; re-derive or delete the static 64 | CC | -## Security & access — 22 rows (P1 2, P2 3, P3 11, P4 6) +## Security & access — 23 rows (P1 2, P2 3, P3 12, P4 6) | ID | Category | Sev | What | State | Blocked on | Next action | Owner | |---|---|---|---|---|---|---|---| @@ -223,6 +223,7 @@ stopping line that lies. | **R-1050** | Security & access | P4 | **Raise HSTS to one year on or after 2026-11-15, if no https trouble was reported.** `09` §3 decision 249 (D2 = A, operator 2026-10-15): HSTS stays `max-age=86400` now; after a month with no https trouble it becomes one year (`31536000`); never preload, never `includeSubDomains`. The value is `HSTSMaxAgeSeconds` in `felhom-controller/controller/internal/infra/infra.go`, pinned by `TestRenderForwardedHeaders_HTTPSOnly`. „Trouble” = any report of a name that will not open, or a box answering http after the floor. | **WATCHING** — filed 2026-10-15 | the date | On or after 2026-11-15: read the register and the operator's mail for https trouble since 2026-10-15; none → a controller release with one year (test updated), bake → vouch → floor | CC | | **R-1054** | Security & access | P3 | **The hub's operator/global API key is still one bearer that can do everything on `/api/v1`, including reading every box's break-glass root password (`GET /admin/hosts/{id}/recovery-credential`).** Hub v0.152.0 gave the web surface scoped tokens (`05` §17) but tokens are refused on `/api/v1` by design. Who uses the global key today is not listed in one place. Fix: list its consumers; move each operator use to a scoped token on the web side (or a token class on the API), and keep the global key only where a box path needs it — or retire it. | OPEN — filed 2026-10-15 by the token build | — | List the global key's consumers (pod env, scripts, runbooks) | CC | | **R-1055** | Security & access | P3 | **A stolen `customers:write` token can take over a household's dashboard: edit the household's e-mail, then „resend claim" — the claim code goes to the new address.** The operator table (`09` §3 decision 251) put „resend claim" in Customers write; the self-bind link, which mints a capability, was made session only. Nobody holds a `customers:write` token today. Fix (operator decision): keep it, or make `/configs/{id}/claim-resend` session only (one line in `hub/internal/web/routes.go`). | WAITING-ON-OPERATOR — filed 2026-10-15 by the token build | operator decision | Decide: claim resend stays in Customers write, or session only | operator | +| **R-1057** | Security & access | P3 | **A restore of a REMOVED gated app comes back without its setup gate, even when the backup was taken before the household's setup — so the app returns open, with no account.** Measured 2026-10-15 on 9202 with Prowlarr (`audits/prowlarr-2026-10-15/box/run3/restore.txt`): installed (gated, no account made), the guarded Update's per-app backup, remove keeping the backups, the household's restore button — afterwards a stranger's `/ping` through traefik answered 200, where before the remove it answered 401 (the gate). This follows the documented rule (`felhom-controller` `internal/stacks/life_records.go` `carryLifeRecords`: no prior record = no gate, „the data comes back with its admin"); the rule's assumption fails for a backup made before the setup. For a first-visitor app (Prowlarr, n8n, …) the first stranger sets the sign-in; for Prowlarr `/initialize.json` also hands out the API key (seen before the setup in the install window — NOT measured after this restore). Needs: the household to install, let a backup run before finishing the setup, remove, restore. | **OPEN** — filed 2026-10-15 by the Prowlarr walk | — | Record the gate's state in the per-app backup and re-close it on restore when it was closed then; red-proof with this walk (`tools/walk_restore.py`) | CC | ## Box system & updates — 13 rows (P2 1, P3 9, P4 3) diff --git a/scripts/test_build_sizing_web.py b/scripts/test_build_sizing_web.py index fcccb95a..f61c8a3e 100644 --- a/scripts/test_build_sizing_web.py +++ b/scripts/test_build_sizing_web.py @@ -92,8 +92,8 @@ class SizingWeb(unittest.TestCase): self.assertEqual(io.open(DATA, encoding="utf-8").read(), B.render(B.build()), "sizing-data.json differs from a fresh build — run scripts/build_sizing_web.py") - def test_offers_the_62_listed_apps_plus_builtin(self): - self.assertEqual(len(self.all), 62) + def test_offers_the_63_listed_apps_plus_builtin(self): + self.assertEqual(len(self.all), 63) # 62 + Prowlarr (2026-10-15, R-1048) self.assertEqual([a["slug"] for a in self.d["apps"] if a.get("builtin")], ["filebrowser"]) def test_guest_sizes_follow_the_installer(self): @@ -109,7 +109,7 @@ class SizingWeb(unittest.TestCase): "household": (HOUSEHOLD, [N, T, W, W, W, W, W], 16, 12), "household without immich": ([s for s in HOUSEHOLD if s != "immich"], [T, W, W, W, W, W, W], 12, 8), # 2026-10-14 HP raise: 2,731 x 1.5 = 4,096.5 > 4,096 "immich alone": (["immich"], [T, W, W, W, W, W, W], 12, 8), - "all 62": (self.all, [N, N, N, N, T, W, W], 48, 32), # 24 GB: guest 19,868 < peak 22,333 + "all 63": (self.all, [N, N, N, N, T, W, W], 48, 32), # 24 GB: guest 19,868 < peak 22,377 (22,333 before Prowlarr) "nothing": ([], [W] * 7, 8, None), } for name, (sel, want, rec, tight) in cases.items(): @@ -133,7 +133,7 @@ class SizingWeb(unittest.TestCase): def test_segments_add_up_to_the_bar(self): for name, sel in {"household": HOUSEHOLD, "household without immich": [x for x in HOUSEHOLD if x != "immich"], - "immich alone": ["immich"], "all 62": self.all, "nothing": []}.items(): + "immich alone": ["immich"], "all 63": self.all, "nothing": []}.items(): well, segs = segments(self.d, sel) self.assertAlmostEqual(sum(mb for _, mb in segs), well * 1024, delta=1, msg=name) self.assertGreaterEqual(dict(segs)["base"], 0, name + ": base system negative") @@ -148,7 +148,7 @@ class SizingWeb(unittest.TestCase): base8 = small * 1024 - guest_mib(self.d, small) tiny = ["glance"] # 6 MB: its fits-well size is far below 8 GB self.assertLess(min_box_gb(self.d, (self.d["platform_mb"] + 7) * self.d["headroom"]), small) - for name, sel in {"nothing": [], "tiny": tiny, "household": HOUSEHOLD, "all 62": self.all}.items(): + for name, sel in {"nothing": [], "tiny": tiny, "household": HOUSEHOLD, "all 63": self.all}.items(): bar, segs = segments(self.d, sel) self.assertGreaterEqual(bar, small, name) self.assertAlmostEqual(sum(mb for _, mb in segs), bar * 1024, delta=1, msg=name) @@ -217,7 +217,7 @@ class SizingWeb(unittest.TestCase): elif len(cells) > 2 and not cells[0].startswith("---"): rows[cells[0]] = cells sel = {"Household set": HOUSEHOLD, "Household set without Immich": [s for s in HOUSEHOLD if s != "immich"], - "Immich alone": ["immich"], "All 62 apps": self.all} + "Immich alone": ["immich"], "All 63 apps": self.all} self.assertEqual(set(rows), set(sel)) for name, slugs in sel.items(): peak, verdicts = estimate(self.d, slugs) diff --git a/website/CHANGELOG.md b/website/CHANGELOG.md index 98426373..7a329151 100644 --- a/website/CHANGELOG.md +++ b/website/CHANGELOG.md @@ -1,4 +1,11 @@ -## 2026-10-15 — the Prowlarr logo (unblocks the site gate) +## 2026-10-15 — Prowlarr on the apps pages; the count 62 → 63 (R-1048) + +- A Prowlarr card after Radarr on `alkalmazasok.html` and `en/apps.html` (the logo arrived in the commit before). The + card says the box links it to Sonarr and Radarr, with at most one button press (the controller links it by itself only + when Sonarr/Radarr come up after it). +- Every stated app count 62 → 63 on both languages; the media group 19 → 20. R-1036's two owed cards are unchanged. + +## 2026-10-15 — the Prowlarr logo (unblocks the site gate) - `website/assets/prowlarr-logo.svg` added — byte-identical to the uncommitted copy in the media work folder (`felhom.eu-media`, untouched since 12:18). The catalog lists Prowlarr, so `site_gates.py` failed „app logos" and the diff --git a/website/alkalmazasok.html b/website/alkalmazasok.html index 7719812a..6d94b1bd 100644 --- a/website/alkalmazasok.html +++ b/website/alkalmazasok.html @@ -4,8 +4,8 @@ - Telepíthető alkalmazások — 62 app otthoni szerverre | Felhom.eu - + Telepíthető alkalmazások — 63 app otthoni szerverre | Felhom.eu + @@ -16,14 +16,14 @@ - + - + @@ -102,7 +102,7 @@ -

Válassz 62 alkalmazás közül. Szinte mind ingyenes, nyílt forráskódú szoftver, és mind a saját szervereden fut.

+

Válassz 63 alkalmazás közül. Szinte mind ingyenes, nyílt forráskódú szoftver, és mind a saját szervereden fut.

Mekkora gép kell ezekhez? →

Fontos tudnivalók

@@ -266,7 +266,7 @@

Média & Szórakozás

- 19 alkalmazás + 20 alkalmazás
@@ -540,6 +540,21 @@ Open Source
+ +
+
+ +

Prowlarr

Letöltési források egy helyen
+
+
+

A letöltési forrásaidat (indexereket) egyszer adod meg, és a Sonarr meg a Radarr magától megkapja őket. A gép köti össze őket; legfeljebb egy gombot nyomsz meg hozzá. Saját meghajtót nem használ.

+
Egyszer kell megadniSonarr és RadarrA gép köti össze
+
+ +
diff --git a/website/assets/sizing-data.json b/website/assets/sizing-data.json index fa69bd7f..41218372 100644 --- a/website/assets/sizing-data.json +++ b/website/assets/sizing-data.json @@ -5,7 +5,7 @@ "source": { "path": "documentation/audits/sizing-2026-10-13/sizing.json", "commit": "c885b5ac", - "sha256": "56e12018217fe3e05c096569321b106a73fc0a9f762480745b32340ecdbb458e" + "sha256": "d289766cc93cf5d488eeab7567bcbda29297cb673621897bb851cd0b49613b9b" }, "platform_mb": 470, "headroom": 1.5, @@ -391,6 +391,20 @@ "cpu_note_en": "", "measured": true }, + { + "slug": "prowlarr", + "name_hu": "Prowlarr", + "name_en": "Prowlarr", + "tagline_hu": "Letöltési források egy helyen", + "tagline_en": "Download sources in one place", + "category": "media", + "typical_mb": 41, + "peak_mb": 44, + "cpu_class": "light", + "cpu_note_hu": "", + "cpu_note_en": "", + "measured": true + }, { "slug": "seerr", "name_hu": "Seerr", diff --git a/website/en/apps.html b/website/en/apps.html index ca90606c..d81ec295 100644 --- a/website/en/apps.html +++ b/website/en/apps.html @@ -4,8 +4,8 @@ - Apps to install — 62 apps for a home server | Felhom.eu - + Apps to install — 63 apps for a home server | Felhom.eu + @@ -16,14 +16,14 @@ - + - + @@ -102,7 +102,7 @@ -

Choose from 62 apps. Almost all of them are free, open-source software, and every one runs on your own server.

+

Choose from 63 apps. Almost all of them are free, open-source software, and every one runs on your own server.

How big a machine do these need? →

Good to know

@@ -266,7 +266,7 @@

Media & Entertainment

- 19 apps + 20 apps
@@ -540,6 +540,21 @@ Open Source
+ +
+
+ +

Prowlarr

Download sources in one place
+
+
+

Enter your download sources (indexers) once, and Sonarr and Radarr get them by themselves. The box links them; at most you press one button. It uses no drive of its own.

+
Enter onceSonarr and RadarrThe box links them
+
+ +
diff --git a/website/en/faq.html b/website/en/faq.html index e953898b..c5a9ee00 100644 --- a/website/en/faq.html +++ b/website/en/faq.html @@ -61,7 +61,7 @@ "name": "How is Felhom.eu different from a Synology or QNAP NAS?", "acceptedAnswer": { "@type": "Answer", - "text": "Synology and QNAP make NAS (network storage) devices built mainly for storing files, with their own closed software. I offer an open, Docker-based system instead, where the catalog's apps install in a few clicks – from a photo manager to a media server to a password manager. A NAS offers a limited choice of apps and depends on the maker's software updates. With Felhom you choose from 62 apps, and if something is missing, I look at whether it can be added." + "text": "Synology and QNAP make NAS (network storage) devices built mainly for storing files, with their own closed software. I offer an open, Docker-based system instead, where the catalog's apps install in a few clicks – from a photo manager to a media server to a password manager. A NAS offers a limited choice of apps and depends on the maker's software updates. With Felhom you choose from 63 apps, and if something is missing, I look at whether it can be added." } }, { @@ -141,7 +141,7 @@ "name": "Which apps can I run?", "acceptedAnswer": { "@type": "Answer", - "text": "I offer 62 apps – the full list is on the Apps page. A few examples: Immich – a Google Photos alternative that backs up photos and videos automatically. Jellyfin/Plex – your own Netflix for films and series. Nextcloud – store and share files (instead of Google Drive). Vaultwarden – a password manager for the whole family. Paperless-ngx – scan documents and make them searchable. If something is not on the list, ask – I will look at whether it can join the catalog." + "text": "I offer 63 apps – the full list is on the Apps page. A few examples: Immich – a Google Photos alternative that backs up photos and videos automatically. Jellyfin/Plex – your own Netflix for films and series. Nextcloud – store and share files (instead of Google Drive). Vaultwarden – a password manager for the whole family. Paperless-ngx – scan documents and make them searchable. If something is not on the list, ask – I will look at whether it can join the catalog." } }, { @@ -500,7 +500,7 @@

Synology and QNAP make NAS (network storage) devices built mainly for storing files, with their own closed software. I offer an open, Docker-based system instead, where the catalog's apps install in a few clicks – from a photo manager to a media server to a password manager.

-

A NAS offers a limited choice of apps and depends on the maker's software updates. With Felhom you choose from 62 apps, and if something is missing, I look at whether it can be added.

+

A NAS offers a limited choice of apps and depends on the maker's software updates. With Felhom you choose from 63 apps, and if something is missing, I look at whether it can be added.

@@ -628,7 +628,7 @@
-

I offer 62 apps – the full list is on the Apps page. A few examples:

+

I offer 63 apps – the full list is on the Apps page. A few examples:

Immich – a Google Photos alternative that backs up photos and videos automatically. Jellyfin/Plex – your own Netflix for films and series. Nextcloud – store and share files (instead of Google Drive). Vaultwarden – a password manager for the whole family. Paperless-ngx – scan documents and make them searchable.

If something is not on the list, ask – I will look at whether it can join the catalog.

diff --git a/website/en/index.html b/website/en/index.html index f71b9d40..e878e474 100644 --- a/website/en/index.html +++ b/website/en/index.html @@ -5,7 +5,7 @@ - + Felhom.eu — Your own cloud, your own rules | A home server service in Hungary @@ -140,7 +140,7 @@
  • Photos, documents, media
  • Network shares
  • -
  • 62 apps to install
  • +
  • 63 apps to install
  • @@ -246,7 +246,7 @@

    What you can install

    -

    62 apps — almost all of them open source

    +

    63 apps — almost all of them open source

    See all applications →
    diff --git a/website/en/services.html b/website/en/services.html index 93f89114..c34dae4f 100644 --- a/website/en/services.html +++ b/website/en/services.html @@ -5,7 +5,7 @@ Services — what a Felhom box does today | Felhom.eu - + @@ -111,7 +111,7 @@

    The box's folders appear in Windows File Explorer and the Mac Finder — you drag files onto them like onto any other folder. Each share can be set to read-only.

  • -

    62 apps to install

    +

    63 apps to install

    You install them from the Dashboard in a few clicks — no command line. Almost all of them are open source (the exceptions are marked on the apps page), and every one runs on your machine.

  • diff --git a/website/en/technology.html b/website/en/technology.html index ffea624b..682c74a7 100644 --- a/website/en/technology.html +++ b/website/en/technology.html @@ -179,7 +179,7 @@
    • A web interface in English or Hungarian – the household chooses, and no command-line knowledge is needed
    • -
    • App catalog – 62 apps to install in a few clicks
    • +
    • App catalog – 63 apps to install in a few clicks
    • Resource view – memory and storage use as it happens
    • Automatic backups – databases and files, with their status watched
    • App updates – only versions we have tried, a fresh backup first, and if the new version does not start, the box goes back to the previous one by itself
    • diff --git a/website/gyik.html b/website/gyik.html index d500b0fc..41229a30 100644 --- a/website/gyik.html +++ b/website/gyik.html @@ -61,7 +61,7 @@ "name": "Mi a különbség a Felhom.eu és a Synology/QNAP NAS szolgáltatásai között?", "acceptedAnswer": { "@type": "Answer", - "text": "A Synology és QNAP elsősorban fájltárolásra készült NAS (hálózati adattároló) eszközök, saját zárt szoftverrel. Én ezzel szemben egy nyílt, Docker-alapú rendszert kínálok, amelyre a katalógus alkalmazásai pár kattintással telepíthetők – fotókezelőtől a média szerveren át a jelszókezelőig. A NAS-ok korlátozott alkalmazás-választékot kínálnak és a gyártó szoftverfrissítéseitől függenek. A Felhomnál 62 alkalmazásból válogathatsz, és ha valami hiányzik, megnézem, felvehető-e." + "text": "A Synology és QNAP elsősorban fájltárolásra készült NAS (hálózati adattároló) eszközök, saját zárt szoftverrel. Én ezzel szemben egy nyílt, Docker-alapú rendszert kínálok, amelyre a katalógus alkalmazásai pár kattintással telepíthetők – fotókezelőtől a média szerveren át a jelszókezelőig. A NAS-ok korlátozott alkalmazás-választékot kínálnak és a gyártó szoftverfrissítéseitől függenek. A Felhomnál 63 alkalmazásból válogathatsz, és ha valami hiányzik, megnézem, felvehető-e." } }, { @@ -141,7 +141,7 @@ "name": "Milyen alkalmazásokat tudok futtatni?", "acceptedAnswer": { "@type": "Answer", - "text": "62 alkalmazást kínálok – a teljes listát az Alkalmazások oldalon találod. Néhány példa: Immich – Google Photos alternatíva fotók és videók automatikus mentésével. Jellyfin/Plex – saját Netflix filmekhez és sorozatokhoz. Nextcloud – fájlok tárolása és megosztása (Google Drive helyett). Vaultwarden – jelszókezelő az egész családnak. Paperless-ngx – dokumentumok digitalizálása és kereshetővé tétele. Ha valami nincs a listán, kérdezz – megnézem, felvehető-e a katalógusba." + "text": "63 alkalmazást kínálok – a teljes listát az Alkalmazások oldalon találod. Néhány példa: Immich – Google Photos alternatíva fotók és videók automatikus mentésével. Jellyfin/Plex – saját Netflix filmekhez és sorozatokhoz. Nextcloud – fájlok tárolása és megosztása (Google Drive helyett). Vaultwarden – jelszókezelő az egész családnak. Paperless-ngx – dokumentumok digitalizálása és kereshetővé tétele. Ha valami nincs a listán, kérdezz – megnézem, felvehető-e a katalógusba." } }, { @@ -500,7 +500,7 @@

      A Synology és QNAP elsősorban fájltárolásra készült NAS (hálózati adattároló) eszközök, saját zárt szoftverrel. Én ezzel szemben egy nyílt, Docker-alapú rendszert kínálok, amelyre a katalógus alkalmazásai pár kattintással telepíthetők – fotókezelőtől a média szerveren át a jelszókezelőig.

      -

      A NAS-ok korlátozott alkalmazás-választékot kínálnak és a gyártó szoftverfrissítéseitől függenek. A Felhomnál 62 alkalmazásból válogathatsz, és ha valami hiányzik, megnézem, felvehető-e.

      +

      A NAS-ok korlátozott alkalmazás-választékot kínálnak és a gyártó szoftverfrissítéseitől függenek. A Felhomnál 63 alkalmazásból válogathatsz, és ha valami hiányzik, megnézem, felvehető-e.

  • @@ -628,7 +628,7 @@
    -

    62 alkalmazást kínálok – a teljes listát az Alkalmazások oldalon találod. Néhány példa:

    +

    63 alkalmazást kínálok – a teljes listát az Alkalmazások oldalon találod. Néhány példa:

    Immich – Google Photos alternatíva fotók és videók automatikus mentésével. Jellyfin/Plex – saját Netflix filmekhez és sorozatokhoz. Nextcloud – fájlok tárolása és megosztása (Google Drive helyett). Vaultwarden – jelszókezelő az egész családnak. Paperless-ngx – dokumentumok digitalizálása és kereshetővé tétele.

    Ha valami nincs a listán, kérdezz – megnézem, felvehető-e a katalógusba.

    diff --git a/website/index.html b/website/index.html index 03f5595e..115baf5a 100644 --- a/website/index.html +++ b/website/index.html @@ -5,7 +5,7 @@ - + Felhom.eu — Saját felhőd, saját szabályaid | Otthoni szerver szolgáltatás @@ -140,7 +140,7 @@
    • Fényképek, dokumentumok, média
    • Hálózati megosztás
    • -
    • 62 telepíthető alkalmazás
    • +
    • 63 telepíthető alkalmazás
  • @@ -246,7 +246,7 @@

    Amit telepíthetsz

    -

    Fájlkezelés, fotók és média, dokumentumok, jelszókezelés, okosotthon, fejlesztői eszközök. Nextcloud, Immich, Jellyfin, Paperless-ngx, Vaultwarden, Home Assistant és még 54 másik: 62 alkalmazás, szinte mind nyílt forráskódú.

    +

    Fájlkezelés, fotók és média, dokumentumok, jelszókezelés, okosotthon, fejlesztői eszközök. Nextcloud, Immich, Jellyfin, Paperless-ngx, Vaultwarden, Home Assistant és még 55 másik: 63 alkalmazás, szinte mind nyílt forráskódú.

    Összes alkalmazás megtekintése →
    diff --git a/website/szolgaltatasok.html b/website/szolgaltatasok.html index 08400a27..133af98c 100644 --- a/website/szolgaltatasok.html +++ b/website/szolgaltatasok.html @@ -5,7 +5,7 @@ Szolgáltatások — mit tud ma a Felhom doboz | Felhom.eu - + @@ -111,7 +111,7 @@

    A doboz mappái megjelennek a Windows Intézőben és a Mac Finderben: úgy húzod rájuk a fájlokat, mint bármelyik másik mappára. Megosztásonként beállítható, hogy csak olvasható legyen.

  • -

    62 telepíthető alkalmazás

    +

    63 telepíthető alkalmazás

    A vezérlőpultból telepíthetők, pár kattintással, parancssor nélkül. Szinte mind nyílt forráskódú, és mind a te gépeden fut.

  • diff --git a/website/technologiak.html b/website/technologiak.html index b3869bb7..26cd45b3 100644 --- a/website/technologiak.html +++ b/website/technologiak.html @@ -179,7 +179,7 @@
    • Magyar vagy angol webes felület – a háztartás választ, és nincs szükség parancssor ismeretre
    • -
    • Alkalmazás katalógus – 62 app telepíthető pár kattintással
    • +
    • Alkalmazás katalógus – 63 app telepíthető pár kattintással
    • Erőforrás figyelés – memória és tárhely használat valós időben
    • Automatikus mentések – adatbázis és fájl backup, állapotfigyeléssel
    • Alkalmazás frissítések – csak kipróbált verzió, előtte friss mentés, és ha az új verzió nem indul, a doboz magától visszaáll az előzőre