Prowlarr evidence (R-1048) + its apps-page card; the stated app count 62 -> 63; R-1057 filed
gates / gates (push) Successful in 6m34s

- 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) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
2026-10-15 12:58:18 +02:00
parent 994c75c0a8
commit 2f1f6c8052
62 changed files with 2846 additions and 44 deletions
@@ -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/`).
@@ -0,0 +1,3 @@
404: Not Found GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
@@ -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)
@@ -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
@@ -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
@@ -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)
<Config>
<BindAddress>*</BindAddress>
<Port>9696</Port>
<SslPort>6969</SslPort>
<EnableSsl>False</EnableSsl>
<LaunchBrowser>True</LaunchBrowser>
<ApiKey><redacted></ApiKey>
<AuthenticationMethod>None</AuthenticationMethod>
<AuthenticationRequired>Enabled</AuthenticationRequired>
<AllowedHosts></AllowedHosts>
<Branch>master</Branch>
<LogLevel>debug</LogLevel>
<SslCertPath></SslCertPath>
<SslCertPassword></SslCertPassword>
<UrlBase></UrlBase>
<TrustedNetworks></TrustedNetworks>
<InstanceName>Prowlarr</InstanceName>
<UpdateMechanism>Docker</UpdateMechanism>
</Config>== 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
@@ -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]
@@ -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
@@ -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
}
@@ -0,0 +1,8 @@
{
"prowlarr": {
"status": "running",
"health": "healthy",
"restarts": 0,
"exit": 0
}
}
@@ -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
@@ -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
}
]
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,8 @@
{
"prowlarr": {
"status": "running",
"health": "healthy",
"restarts": 0,
"exit": 0
}
}
@@ -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
}
@@ -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"
}
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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"
}
@@ -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"
}
@@ -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
@@ -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.
@@ -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
@@ -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"
}
@@ -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
@@ -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
@@ -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"
}
@@ -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
@@ -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): <Config> <AuthenticationRequired>Enabled</AuthenticationRequired> <AnalyticsEnabled>False</AnalyticsEnabled> <LogLevel>debug</LogLevel> <UrlBase></UrlBase> <BindAddress>*</BindAddress> <Port>9696</Port> <SslPort>6969</SslPort> <EnableSsl>False</EnableSsl> <LaunchBrowser>True</LaunchBrowser> <ApiKey><redacted></ApiKey> <AuthenticationMethod>None</AuthenticationMethod> <Branch>master</Branch> <SslCertPath></SslCertPath> <SslCertPassword></SslCertPassword> <InstanceName>Prowlarr</InstanceName> <UpdateMechanism>Docker</UpdateMechanism> </Config>
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
@@ -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
]
}
@@ -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): <Config> <AuthenticationRequired>Enabled</AuthenticationRequired> <AnalyticsEnabled>False</AnalyticsEnabled> <LogLevel>debug</LogLevel> <UrlBase></UrlBase> <BindAddress>*</BindAddress> <Port>9696</Port> <SslPort>6969</SslPort> <EnableSsl>False</EnableSsl> <LaunchBrowser>True</LaunchBrowser> <ApiKey><redacted></ApiKey> <AuthenticationMethod>None</AuthenticationMethod> <Branch>master</Branch> <SslCertPath></SslCertPath> <SslCertPassword></SslCertPassword> <InstanceName>Prowlarr</InstanceName> <UpdateMechanism>Docker</UpdateMechanism> </Config>
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
@@ -0,0 +1,25 @@
#!/bin/bash
# bench_first_start.sh <image> — 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#<ApiKey>[^<]+#<ApiKey><redacted>#'
K=$(docker exec $C sed -n 's#.*<ApiKey>\([^<]*\)</ApiKey>.*#\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}#<hex32>#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"
@@ -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]]()
@@ -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=<evidence dir>. Presses only what a person presses:
POST /api/sync, the deploy wizard's POST /api/stacks/<n>/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:.*<ApiKey>\\(.*\\)</ApiKey>.*:\\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:.*<ApiKey>\\(.*\\)</ApiKey>.*:\\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()
@@ -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#<ApiKey>[^<]+#<ApiKey><redacted>#' | 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()
@@ -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=<evidence dir>; 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()
@@ -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 | 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 | | 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 | | 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 |
<!-- /sizing-verdicts --> <!-- /sizing-verdicts -->
- **8 GB:** the household set without Immich fits, but tight; Immich alone fits, but tight; the whole household set does not fit. - **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. - **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. 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.
@@ -682,6 +682,17 @@
"measured_on": "demo-felhom (N100)", "measured_on": "demo-felhom (N100)",
"date": "2026-10-13" "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", "slug": "radicale",
"display_name": "Radicale", "display_name": "Radicale",
+3 -2
View File
@@ -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-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-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/<drive>/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/<drive label>`) without breaking the hardlink rule, which binds only the download/library apps | 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/<drive>/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/<drive label>`) 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-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-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 | | **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-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 | | **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 | | 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-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-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-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) ## Box system & updates — 13 rows (P2 1, P3 9, P4 3)
+6 -6
View File
@@ -92,8 +92,8 @@ class SizingWeb(unittest.TestCase):
self.assertEqual(io.open(DATA, encoding="utf-8").read(), B.render(B.build()), 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") "sizing-data.json differs from a fresh build — run scripts/build_sizing_web.py")
def test_offers_the_62_listed_apps_plus_builtin(self): def test_offers_the_63_listed_apps_plus_builtin(self):
self.assertEqual(len(self.all), 62) 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"]) self.assertEqual([a["slug"] for a in self.d["apps"] if a.get("builtin")], ["filebrowser"])
def test_guest_sizes_follow_the_installer(self): 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": (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 "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), "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), "nothing": ([], [W] * 7, 8, None),
} }
for name, (sel, want, rec, tight) in cases.items(): 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): 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"], 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) well, segs = segments(self.d, sel)
self.assertAlmostEqual(sum(mb for _, mb in segs), well * 1024, delta=1, msg=name) self.assertAlmostEqual(sum(mb for _, mb in segs), well * 1024, delta=1, msg=name)
self.assertGreaterEqual(dict(segs)["base"], 0, name + ": base system negative") 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) base8 = small * 1024 - guest_mib(self.d, small)
tiny = ["glance"] # 6 MB: its fits-well size is far below 8 GB 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) 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) bar, segs = segments(self.d, sel)
self.assertGreaterEqual(bar, small, name) self.assertGreaterEqual(bar, small, name)
self.assertAlmostEqual(sum(mb for _, mb in segs), bar * 1024, delta=1, msg=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("---"): elif len(cells) > 2 and not cells[0].startswith("---"):
rows[cells[0]] = cells rows[cells[0]] = cells
sel = {"Household set": HOUSEHOLD, "Household set without Immich": [s for s in HOUSEHOLD if s != "immich"], 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)) self.assertEqual(set(rows), set(sel))
for name, slugs in sel.items(): for name, slugs in sel.items():
peak, verdicts = estimate(self.d, slugs) peak, verdicts = estimate(self.d, slugs)
+8 -1
View File
@@ -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 - `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 (`felhom.eu-media`, untouched since 12:18). The catalog lists Prowlarr, so `site_gates.py` failed „app logos" and the
+21 -6
View File
@@ -4,8 +4,8 @@
<script>document.documentElement.className=document.documentElement.className.replace('no-js','js');</script> <script>document.documentElement.className=document.documentElement.className.replace('no-js','js');</script>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Telepíthető alkalmazások — 62 app otthoni szerverre | Felhom.eu</title> <title>Telepíthető alkalmazások — 63 app otthoni szerverre | Felhom.eu</title>
<meta name="description" content="Nextcloud, Immich, Jellyfin, Vaultwarden — összesen 62 alkalmazás otthoni szerveredre, szinte mind nyílt forráskódú. Google Drive, iCloud Photos és Spotify alternatívák."> <meta name="description" content="Nextcloud, Immich, Jellyfin, Vaultwarden — összesen 63 alkalmazás otthoni szerveredre, szinte mind nyílt forráskódú. Google Drive, iCloud Photos és Spotify alternatívák.">
<meta name="keywords" content="Nextcloud, Immich, Jellyfin, Plex, Sonarr, Radarr, Vaultwarden, nyílt forráskódú alkalmazások, self-hosted apps, otthoni szerver alkalmazások, Google Photos alternatíva, Dropbox alternatíva"> <meta name="keywords" content="Nextcloud, Immich, Jellyfin, Plex, Sonarr, Radarr, Vaultwarden, nyílt forráskódú alkalmazások, self-hosted apps, otthoni szerver alkalmazások, Google Photos alternatíva, Dropbox alternatíva">
<meta name="robots" content="index, follow"> <meta name="robots" content="index, follow">
@@ -16,14 +16,14 @@
<meta property="og:type" content="website"> <meta property="og:type" content="website">
<meta property="og:url" content="https://felhom.eu/alkalmazasok"> <meta property="og:url" content="https://felhom.eu/alkalmazasok">
<meta property="og:title" content="62 alkalmazás otthoni szerverre"> <meta property="og:title" content="63 alkalmazás otthoni szerverre">
<meta property="og:description" content="Nextcloud, Immich, Jellyfin, Vaultwarden és még sok más. Fájlkezelés, fotók, média, dokumentumok — mind a saját szerveredről."> <meta property="og:description" content="Nextcloud, Immich, Jellyfin, Vaultwarden és még sok más. Fájlkezelés, fotók, média, dokumentumok — mind a saját szerveredről.">
<meta property="og:image" content="https://felhom.eu/assets/og-image.png?v=3"> <meta property="og:image" content="https://felhom.eu/assets/og-image.png?v=3">
<meta property="og:locale" content="hu_HU"> <meta property="og:locale" content="hu_HU">
<meta property="og:site_name" content="Felhom.eu"> <meta property="og:site_name" content="Felhom.eu">
<meta name="twitter:card" content="summary_large_image"> <meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="62 alkalmazás otthoni szerverre"> <meta name="twitter:title" content="63 alkalmazás otthoni szerverre">
<meta name="twitter:description" content="Nextcloud, Immich, Jellyfin és még sok más — a te szerveredről."> <meta name="twitter:description" content="Nextcloud, Immich, Jellyfin és még sok más — a te szerveredről.">
<meta name="twitter:image" content="https://felhom.eu/assets/og-image.png?v=3"> <meta name="twitter:image" content="https://felhom.eu/assets/og-image.png?v=3">
@@ -102,7 +102,7 @@
<div class="page-header"> <div class="page-header">
<h1>Telepíthető <span>Alkalmazások</span></h1> <h1>Telepíthető <span>Alkalmazások</span></h1>
</div> </div>
<p class="page-lede">Válassz 62 alkalmazás közül. Szinte mind ingyenes, nyílt forráskódú szoftver, és mind a saját szervereden fut.</p> <p class="page-lede">Válassz 63 alkalmazás közül. Szinte mind ingyenes, nyílt forráskódú szoftver, és mind a saját szervereden fut.</p>
<p class="page-lede"><a href="/gepigeny">Mekkora gép kell ezekhez? →</a></p> <p class="page-lede"><a href="/gepigeny">Mekkora gép kell ezekhez? →</a></p>
<div class="disclaimer-banner"> <div class="disclaimer-banner">
<h3><svg class="ico-lg" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-info"/></svg> Fontos tudnivalók</h3> <h3><svg class="ico-lg" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-info"/></svg> Fontos tudnivalók</h3>
@@ -266,7 +266,7 @@
<div class="category-header"> <div class="category-header">
<span class="category-icon"><svg class="ico-lg" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-tv"/></svg></span> <span class="category-icon"><svg class="ico-lg" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-tv"/></svg></span>
<h2>Média & <span>Szórakozás</span></h2> <h2>Média & <span>Szórakozás</span></h2>
<span class="app-count">19 alkalmazás</span> <span class="app-count">20 alkalmazás</span>
</div> </div>
<div class="app-grid"> <div class="app-grid">
<!-- Immich --> <!-- Immich -->
@@ -540,6 +540,21 @@
<span class="open-source-badge"><svg class="ico ico-yes" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-check"/></svg>Open Source</span> <span class="open-source-badge"><svg class="ico ico-yes" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-check"/></svg>Open Source</span>
</div> </div>
</div> </div>
<!-- Prowlarr -->
<div class="app-card" id="prowlarr">
<div class="app-card-header">
<img src="/assets/prowlarr-logo.svg" alt="Prowlarr" class="app-logo">
<div class="app-title-area"><h3>Prowlarr</h3><span class="app-tagline">Letöltési források egy helyen</span></div>
</div>
<div class="app-card-body">
<p class="app-description">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.</p>
<div class="app-features"><span class="app-feature">Egyszer kell megadni</span><span class="app-feature">Sonarr és Radarr</span><span class="app-feature">A gép köti össze</span></div>
</div>
<div class="app-card-footer">
<a href="https://github.com/Prowlarr/Prowlarr" target="_blank" rel="noopener" class="github-link"><img src="/assets/github.svg" alt="GitHub">Forráskód</a>
<span class="open-source-badge"><svg class="ico ico-yes" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-check"/></svg>Open Source</span>
</div>
</div>
<!-- Seerr --> <!-- Seerr -->
<div class="app-card" id="seerr"> <div class="app-card" id="seerr">
<div class="app-card-header"> <div class="app-card-header">
+15 -1
View File
@@ -5,7 +5,7 @@
"source": { "source": {
"path": "documentation/audits/sizing-2026-10-13/sizing.json", "path": "documentation/audits/sizing-2026-10-13/sizing.json",
"commit": "c885b5ac", "commit": "c885b5ac",
"sha256": "56e12018217fe3e05c096569321b106a73fc0a9f762480745b32340ecdbb458e" "sha256": "d289766cc93cf5d488eeab7567bcbda29297cb673621897bb851cd0b49613b9b"
}, },
"platform_mb": 470, "platform_mb": 470,
"headroom": 1.5, "headroom": 1.5,
@@ -391,6 +391,20 @@
"cpu_note_en": "", "cpu_note_en": "",
"measured": true "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", "slug": "seerr",
"name_hu": "Seerr", "name_hu": "Seerr",
+21 -6
View File
@@ -4,8 +4,8 @@
<script>document.documentElement.className=document.documentElement.className.replace('no-js','js');</script> <script>document.documentElement.className=document.documentElement.className.replace('no-js','js');</script>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Apps to install — 62 apps for a home server | Felhom.eu</title> <title>Apps to install — 63 apps for a home server | Felhom.eu</title>
<meta name="description" content="Nextcloud, Immich, Jellyfin, Vaultwarden — 62 apps for your home server in Hungary, almost all of them open source. Alternatives to Google Drive, iCloud Photos and Spotify."> <meta name="description" content="Nextcloud, Immich, Jellyfin, Vaultwarden — 63 apps for your home server in Hungary, almost all of them open source. Alternatives to Google Drive, iCloud Photos and Spotify.">
<meta name="keywords" content="Nextcloud, Immich, Jellyfin, Plex, Sonarr, Radarr, Vaultwarden, open-source apps, self-hosted apps, home server apps, Google Photos alternative, Dropbox alternative"> <meta name="keywords" content="Nextcloud, Immich, Jellyfin, Plex, Sonarr, Radarr, Vaultwarden, open-source apps, self-hosted apps, home server apps, Google Photos alternative, Dropbox alternative">
<meta name="robots" content="index, follow"> <meta name="robots" content="index, follow">
@@ -16,14 +16,14 @@
<meta property="og:type" content="website"> <meta property="og:type" content="website">
<meta property="og:url" content="https://felhom.eu/en/apps"> <meta property="og:url" content="https://felhom.eu/en/apps">
<meta property="og:title" content="62 apps for a home server"> <meta property="og:title" content="63 apps for a home server">
<meta property="og:description" content="Nextcloud, Immich, Jellyfin, Vaultwarden and many more. Files, photos, media, documents — all from your own server."> <meta property="og:description" content="Nextcloud, Immich, Jellyfin, Vaultwarden and many more. Files, photos, media, documents — all from your own server.">
<meta property="og:image" content="https://felhom.eu/assets/og-image.png?v=3"> <meta property="og:image" content="https://felhom.eu/assets/og-image.png?v=3">
<meta property="og:locale" content="en_GB"> <meta property="og:locale" content="en_GB">
<meta property="og:site_name" content="Felhom.eu"> <meta property="og:site_name" content="Felhom.eu">
<meta name="twitter:card" content="summary_large_image"> <meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="62 apps for a home server"> <meta name="twitter:title" content="63 apps for a home server">
<meta name="twitter:description" content="Nextcloud, Immich, Jellyfin and many more — from your own server."> <meta name="twitter:description" content="Nextcloud, Immich, Jellyfin and many more — from your own server.">
<meta name="twitter:image" content="https://felhom.eu/assets/og-image.png?v=3"> <meta name="twitter:image" content="https://felhom.eu/assets/og-image.png?v=3">
@@ -102,7 +102,7 @@
<div class="page-header"> <div class="page-header">
<h1>Apps to <span>install</span></h1> <h1>Apps to <span>install</span></h1>
</div> </div>
<p class="page-lede">Choose from 62 apps. Almost all of them are free, open-source software, and every one runs on your own server.</p> <p class="page-lede">Choose from 63 apps. Almost all of them are free, open-source software, and every one runs on your own server.</p>
<p class="page-lede"><a href="/en/sizing">How big a machine do these need? →</a></p> <p class="page-lede"><a href="/en/sizing">How big a machine do these need? →</a></p>
<div class="disclaimer-banner"> <div class="disclaimer-banner">
<h3><svg class="ico-lg" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-info"/></svg> Good to know</h3> <h3><svg class="ico-lg" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-info"/></svg> Good to know</h3>
@@ -266,7 +266,7 @@
<div class="category-header"> <div class="category-header">
<span class="category-icon"><svg class="ico-lg" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-tv"/></svg></span> <span class="category-icon"><svg class="ico-lg" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-tv"/></svg></span>
<h2>Media & <span>Entertainment</span></h2> <h2>Media & <span>Entertainment</span></h2>
<span class="app-count">19 apps</span> <span class="app-count">20 apps</span>
</div> </div>
<div class="app-grid"> <div class="app-grid">
<!-- Immich --> <!-- Immich -->
@@ -540,6 +540,21 @@
<span class="open-source-badge"><svg class="ico ico-yes" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-check"/></svg>Open Source</span> <span class="open-source-badge"><svg class="ico ico-yes" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-check"/></svg>Open Source</span>
</div> </div>
</div> </div>
<!-- Prowlarr -->
<div class="app-card" id="prowlarr">
<div class="app-card-header">
<img src="/assets/prowlarr-logo.svg" alt="Prowlarr" class="app-logo">
<div class="app-title-area"><h3>Prowlarr</h3><span class="app-tagline">Download sources in one place</span></div>
</div>
<div class="app-card-body">
<p class="app-description">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.</p>
<div class="app-features"><span class="app-feature">Enter once</span><span class="app-feature">Sonarr and Radarr</span><span class="app-feature">The box links them</span></div>
</div>
<div class="app-card-footer">
<a href="https://github.com/Prowlarr/Prowlarr" target="_blank" rel="noopener" class="github-link"><img src="/assets/github.svg" alt="GitHub">Source code</a>
<span class="open-source-badge"><svg class="ico ico-yes" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-check"/></svg>Open Source</span>
</div>
</div>
<!-- Seerr --> <!-- Seerr -->
<div class="app-card" id="seerr"> <div class="app-card" id="seerr">
<div class="app-card-header"> <div class="app-card-header">
+4 -4
View File
@@ -61,7 +61,7 @@
"name": "How is Felhom.eu different from a Synology or QNAP NAS?", "name": "How is Felhom.eu different from a Synology or QNAP NAS?",
"acceptedAnswer": { "acceptedAnswer": {
"@type": "Answer", "@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?", "name": "Which apps can I run?",
"acceptedAnswer": { "acceptedAnswer": {
"@type": "Answer", "@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 @@
</button> </button>
<div class="faq-answer"><div class="faq-answer-inner"> <div class="faq-answer"><div class="faq-answer-inner">
<p>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.</p> <p>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.</p>
<p>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.</p> <p>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.</p>
</div></div> </div></div>
</div> </div>
@@ -628,7 +628,7 @@
<svg class="faq-chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 12 15 18 9"></polyline></svg> <svg class="faq-chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 12 15 18 9"></polyline></svg>
</button> </button>
<div class="faq-answer"><div class="faq-answer-inner"> <div class="faq-answer"><div class="faq-answer-inner">
<p>I offer <strong>62 apps</strong> – the full list is on the <a href="/en/apps">Apps</a> page. A few examples:</p> <p>I offer <strong>63 apps</strong> – the full list is on the <a href="/en/apps">Apps</a> page. A few examples:</p>
<p><strong>Immich</strong> – a Google Photos alternative that backs up photos and videos automatically. <strong>Jellyfin/Plex</strong> – your own Netflix for films and series. <strong>Nextcloud</strong> – store and share files (instead of Google Drive). <strong>Vaultwarden</strong> – a password manager for the whole family. <strong>Paperless-ngx</strong> – scan documents and make them searchable.</p> <p><strong>Immich</strong> – a Google Photos alternative that backs up photos and videos automatically. <strong>Jellyfin/Plex</strong> – your own Netflix for films and series. <strong>Nextcloud</strong> – store and share files (instead of Google Drive). <strong>Vaultwarden</strong> – a password manager for the whole family. <strong>Paperless-ngx</strong> – scan documents and make them searchable.</p>
<p>If something is not on the list, ask – I will look at whether it can join the catalog.</p> <p>If something is not on the list, ask – I will look at whether it can join the catalog.</p>
</div></div> </div></div>
+3 -3
View File
@@ -5,7 +5,7 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="A home server in your own home in Hungary: photos, documents, media. 62 apps to install, automatic backups on three levels, a dashboard in English or Hungarian. A closed test is starting now."> <meta name="description" content="A home server in your own home in Hungary: photos, documents, media. 63 apps to install, automatic backups on three levels, a dashboard in English or Hungarian. A closed test is starting now.">
<title>Felhom.eu — Your own cloud, your own rules | A home server service in Hungary</title> <title>Felhom.eu — Your own cloud, your own rules | A home server service in Hungary</title>
<!-- SEO meta --> <!-- SEO meta -->
@@ -140,7 +140,7 @@
<ul> <ul>
<li>Photos, documents, media</li> <li>Photos, documents, media</li>
<li>Network shares</li> <li>Network shares</li>
<li>62 apps to install</li> <li>63 apps to install</li>
</ul> </ul>
</li> </li>
<li> <li>
@@ -246,7 +246,7 @@
<div class="band"> <div class="band">
<div class="band-item"> <div class="band-item">
<h3>What you can install</h3> <h3>What you can install</h3>
<p>62 apps — almost all of them open source</p> <p>63 apps — almost all of them open source</p>
<a href="/en/apps" class="btn btn-outline">See all applications →</a> <a href="/en/apps" class="btn btn-outline">See all applications →</a>
</div> </div>
<div class="band-item"> <div class="band-item">
+2 -2
View File
@@ -5,7 +5,7 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Services — what a Felhom box does today | Felhom.eu</title> <title>Services — what a Felhom box does today | Felhom.eu</title>
<meta name="description" content="What a Felhom box does today: photos and documents, network shares, 62 installable applications, three-tier automatic backup and remote monitoring."> <meta name="description" content="What a Felhom box does today: photos and documents, network shares, 63 installable applications, three-tier automatic backup and remote monitoring.">
<meta name="keywords" content="home server services, features, network shares, automatic backup, installable applications, remote monitoring"> <meta name="keywords" content="home server services, features, network shares, automatic backup, installable applications, remote monitoring">
<meta name="robots" content="index, follow"> <meta name="robots" content="index, follow">
<link rel="canonical" href="https://felhom.eu/en/services"> <link rel="canonical" href="https://felhom.eu/en/services">
@@ -111,7 +111,7 @@
<p>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.</p> <p>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.</p>
</li> </li>
<li class="caps-item"> <li class="caps-item">
<h4><svg class="ico-lg" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-package"/></svg> 62 apps to install</h4> <h4><svg class="ico-lg" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-package"/></svg> 63 apps to install</h4>
<p>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.</p> <p>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.</p>
</li> </li>
<li class="caps-item"> <li class="caps-item">
+1 -1
View File
@@ -179,7 +179,7 @@
<ul class="feature-list"> <ul class="feature-list">
<li>A web interface in English or Hungarian – the household chooses, and no command-line knowledge is needed</li> <li>A web interface in English or Hungarian – the household chooses, and no command-line knowledge is needed</li>
<li>App catalog – 62 apps to install in a few clicks</li> <li>App catalog – 63 apps to install in a few clicks</li>
<li>Resource view – memory and storage use as it happens</li> <li>Resource view – memory and storage use as it happens</li>
<li>Automatic backups – databases and files, with their status watched</li> <li>Automatic backups – databases and files, with their status watched</li>
<li>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</li> <li>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</li>
+4 -4
View File
@@ -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?", "name": "Mi a különbség a Felhom.eu és a Synology/QNAP NAS szolgáltatásai között?",
"acceptedAnswer": { "acceptedAnswer": {
"@type": "Answer", "@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?", "name": "Milyen alkalmazásokat tudok futtatni?",
"acceptedAnswer": { "acceptedAnswer": {
"@type": "Answer", "@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 @@
</button> </button>
<div class="faq-answer"><div class="faq-answer-inner"> <div class="faq-answer"><div class="faq-answer-inner">
<p>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.</p> <p>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.</p>
<p>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.</p> <p>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.</p>
</div></div> </div></div>
</div> </div>
@@ -628,7 +628,7 @@
<svg class="faq-chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 12 15 18 9"></polyline></svg> <svg class="faq-chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 12 15 18 9"></polyline></svg>
</button> </button>
<div class="faq-answer"><div class="faq-answer-inner"> <div class="faq-answer"><div class="faq-answer-inner">
<p><strong>62 alkalmazást</strong> kínálok – a teljes listát az <a href="/alkalmazasok">Alkalmazások</a> oldalon találod. Néhány példa:</p> <p><strong>63 alkalmazást</strong> kínálok – a teljes listát az <a href="/alkalmazasok">Alkalmazások</a> oldalon találod. Néhány példa:</p>
<p><strong>Immich</strong> – Google Photos alternatíva fotók és videók automatikus mentésével. <strong>Jellyfin/Plex</strong> – saját Netflix filmekhez és sorozatokhoz. <strong>Nextcloud</strong> – fájlok tárolása és megosztása (Google Drive helyett). <strong>Vaultwarden</strong> – jelszókezelő az egész családnak. <strong>Paperless-ngx</strong> – dokumentumok digitalizálása és kereshetővé tétele.</p> <p><strong>Immich</strong> – Google Photos alternatíva fotók és videók automatikus mentésével. <strong>Jellyfin/Plex</strong> – saját Netflix filmekhez és sorozatokhoz. <strong>Nextcloud</strong> – fájlok tárolása és megosztása (Google Drive helyett). <strong>Vaultwarden</strong> – jelszókezelő az egész családnak. <strong>Paperless-ngx</strong> – dokumentumok digitalizálása és kereshetővé tétele.</p>
<p>Ha valami nincs a listán, kérdezz – megnézem, felvehető-e a katalógusba.</p> <p>Ha valami nincs a listán, kérdezz – megnézem, felvehető-e a katalógusba.</p>
</div></div> </div></div>
+3 -3
View File
@@ -5,7 +5,7 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Otthoni szerver a saját otthonodban: fényképek, dokumentumok, médiatár. 62 telepíthető alkalmazás, háromszintű automatikus mentés, magyar vagy angol nyelvű vezérlőpult. Most zárt teszt indul."> <meta name="description" content="Otthoni szerver a saját otthonodban: fényképek, dokumentumok, médiatár. 63 telepíthető alkalmazás, háromszintű automatikus mentés, magyar vagy angol nyelvű vezérlőpult. Most zárt teszt indul.">
<title>Felhom.eu — Saját felhőd, saját szabályaid | Otthoni szerver szolgáltatás</title> <title>Felhom.eu — Saját felhőd, saját szabályaid | Otthoni szerver szolgáltatás</title>
<!-- SEO meta --> <!-- SEO meta -->
@@ -140,7 +140,7 @@
<ul> <ul>
<li>Fényképek, dokumentumok, média</li> <li>Fényképek, dokumentumok, média</li>
<li>Hálózati megosztás</li> <li>Hálózati megosztás</li>
<li>62 telepíthető alkalmazás</li> <li>63 telepíthető alkalmazás</li>
</ul> </ul>
</li> </li>
<li> <li>
@@ -246,7 +246,7 @@
<div class="band"> <div class="band">
<div class="band-item"> <div class="band-item">
<h3>Amit telepíthetsz</h3> <h3>Amit telepíthetsz</h3>
<p>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: <strong>62 alkalmazás</strong>, szinte mind nyílt forráskódú.</p> <p>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: <strong>63 alkalmazás</strong>, szinte mind nyílt forráskódú.</p>
<a href="/alkalmazasok" class="btn btn-outline">Összes alkalmazás megtekintése →</a> <a href="/alkalmazasok" class="btn btn-outline">Összes alkalmazás megtekintése →</a>
</div> </div>
<div class="band-item"> <div class="band-item">
+2 -2
View File
@@ -5,7 +5,7 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Szolgáltatások — mit tud ma a Felhom doboz | Felhom.eu</title> <title>Szolgáltatások — mit tud ma a Felhom doboz | Felhom.eu</title>
<meta name="description" content="Mit tud ma egy Felhom doboz: fényképek és dokumentumok, hálózati megosztás, 62 telepíthető alkalmazás, háromszintű automatikus mentés és távoli felügyelet."> <meta name="description" content="Mit tud ma egy Felhom doboz: fényképek és dokumentumok, hálózati megosztás, 63 telepíthető alkalmazás, háromszintű automatikus mentés és távoli felügyelet.">
<meta name="keywords" content="otthoni szerver szolgáltatások, funkciók, hálózati megosztás, automatikus mentés, telepíthető alkalmazások, távoli felügyelet"> <meta name="keywords" content="otthoni szerver szolgáltatások, funkciók, hálózati megosztás, automatikus mentés, telepíthető alkalmazások, távoli felügyelet">
<meta name="robots" content="index, follow"> <meta name="robots" content="index, follow">
<link rel="canonical" href="https://felhom.eu/szolgaltatasok"> <link rel="canonical" href="https://felhom.eu/szolgaltatasok">
@@ -111,7 +111,7 @@
<p>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.</p> <p>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.</p>
</li> </li>
<li class="caps-item"> <li class="caps-item">
<h4><svg class="ico-lg" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-package"/></svg> 62 telepíthető alkalmazás</h4> <h4><svg class="ico-lg" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-package"/></svg> 63 telepíthető alkalmazás</h4>
<p>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.</p> <p>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.</p>
</li> </li>
<li class="caps-item"> <li class="caps-item">
+1 -1
View File
@@ -179,7 +179,7 @@
<ul class="feature-list"> <ul class="feature-list">
<li>Magyar vagy angol webes felület – a háztartás választ, és nincs szükség parancssor ismeretre</li> <li>Magyar vagy angol webes felület – a háztartás választ, és nincs szükség parancssor ismeretre</li>
<li>Alkalmazás katalógus – 62 app telepíthető pár kattintással</li> <li>Alkalmazás katalógus – 63 app telepíthető pár kattintással</li>
<li>Erőforrás figyelés – memória és tárhely használat valós időben</li> <li>Erőforrás figyelés – memória és tárhely használat valós időben</li>
<li>Automatikus mentések – adatbázis és fájl backup, állapotfigyeléssel</li> <li>Automatikus mentések – adatbázis és fájl backup, állapotfigyeléssel</li>
<li>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</li> <li>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</li>