The vault was 155 pages / 31,210 lines, most of it measurement ledgers for payloads that no longer run. A throughput figure for a runtime nobody operates is not a record: AI moves faster than the vault, so the number is wrong before anyone reads it, and a wrong number in a runbook gets believed. Per the new purpose test in `AGENTS.md` → *Documentation Policy*, it is destroyed. Gone (`.AGENTTRASH/20261019-destruction/`, KILL-LIST.txt is the manifest): - the `frank-medium` endpoint and its five era pages, `frank-npu`, FastFlowLM, the ikllama/kingjones/dealign/frank-redo eras, the ling/swift15/freetoken/ubatch campaign pages, qwen3.8 spec-matrix and strix-optimizations, the two "Running Qwen3.6 …" pages; - the whole `Pipeline Parallelism/` folder (no live script passes a pipeline flag) and the multi-device/x4-pair/hybrid/offload experiment pages; - every vLLM page — no vLLM endpoint exists on this fleet; - `LLM/deployment.md` (`Architecture/Deployment Flow.md` is the test-referenced owner) and the external surveys (SGLang, Lemonade, llama.cpp forks/diff, self-hosted app census, the TTS survey) that were obsolete by construction; - `log.md`, 2,380 lines. Git holds the chronology; a causal law belongs to the page that owns the thing it governs, never to a diary. Survivors: 101 pages, 17,542 lines, 0 OKF violations, 0 unresolved links. Eras tables keep the window, payload, runtime, reason and rollback pointer; the rate columns are blanked. The live shapes stay verbatim — those numbers are what the deployer and `models.yml` consume.
6.1 KiB
type, title, description, tags, timestamp
| type | title | description | tags | timestamp | |||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| reference | *arr Stack | The *arr automation stack on FrankBook-Pro, every container on the VPN netns: topology, wiring, and gotchas. |
|
2026-09-04T00:00:00Z |
*arr Stack
The *arr automation stack runs on FrankBook-Pro (the Mac mini) as quadlets inside its podman machine; all egress rides the gluetun WireGuard netns. The per-service pages (Prowlarr, Sonarr, Radarr, Lidarr, Readarr, qBittorrent) carry deployment facts; this page holds the cross-cutting API and auth-posture procedures.
API version split (memorize before any *arr HTTP call)
- Sonarr/Radarr:
/api/v3/…; Lidarr/Readarr/Prowlarr:/api/v1/…. - Prowlarr is port 9696 — 8090 is Jackett.
- Empty body = endpoint absent, not a failure: probe endpoints by status
code first; v1 has no
/api/v1/profileendpoint at all. PUT /downloadclientandPUT /indexer/{id}take a SINGLE resource object (GET it, patchsettings, PUT the whole object back). An array body fails withPath: $ | BytePositionInLine: 1.- API keys live in each container's config:
podman exec <svc> grep -o "<ApiKey>[^<]*" /config/config.xml | cut -d">" -f2. They are local-only secrets — they stop being secret the moment a vhost serves the API unauthenticated.
Schema-first probing (validators are free documentation)
Send the FULL schema template object with fields filled; v1 validators reject partials, and each error names the next required field — peel validators one live call at a time instead of guessing payload shapes:
'Fields' must not be empty→ send a full schema copy, notfields:[].'Base Url' must not be emptywhen keys were camelCase → PascalCase is required (BaseUrl,ApiKey) — schema keys, not the JSON wire keys.DefaultMetadataProfileId must be > 0→ fetch real ids fromGET /api/v1/metadataprofile(rows like(1,'Standard'))GET /api/v1/qualitydefinitionreturns the quality CATALOG — ids ≠ profiles.targetType null→ configContract not registered in that build (this Prowlarr build has no Prowlarr-plugin indexer; integration is Torznab-family only).testingPackageIdin a PUT body downgrades validation failures to warnings.syncLevelis enumApplicationSyncLevel— omit it or use the int; a string is rejected.
Verification rules: POST /downloadclient returns the COLLECTION, not
the created object — verify via GET. Test endpoints return verdicts in the
BODY at HTTP 200 ({} = pass, a list = fail) — judge from bodies, not
status codes. Command POSTs (MoviesSearch, ApplicationSync) run
server-side and respond only when the work finishes: curl -m timeouts
(HTTP 000) during a search are NOT failures — poll queue/history afterward
instead of re-firing; warm small commands (RefreshMonitoredDownloads)
answer in ~20 ms, so a hang means live work in progress, not a dead API.
DB as arbiter: when endpoint shapes are opaque, stop the container and
edit /opt/prowlarr/config/prowlarr.db with sudo python3 sqlite directly,
then start. The indexers table columns are
Id/Name/Implementation/Settings/ConfigContract/Enable/Priority/Added — the
enable flag column is Enable (not EnableRss). Prowlarr's real application
settings values live in prowlarr.db → Applications.Settings JSON (the
/api/v1/applications GET responses carry fields: []).
Auth posture probe (ground truth is behavior, not config flags)
AuthenticationMethod=None + AuthenticationRequired=Enabled in
config.xml is ambiguous on paper; on the wire it means a login screen with
zero users — first-account-wins.
- Confirm which host runs the service before probing (
podman pson the target, not the box you ssh'd into first — the arrs live on FrankBook-Pro). - Probe an API endpoint that bypasses the SPA shell, locally and through
any vhost:
curl -s -o /dev/null -w "%{http_code}" http://<host>:<port>/api/v3/system/status. SPA roots (/) always serve a 200 shell — never conclude "open" or "gated" from/. 401 = gated; 200 + JSON = API fully open (anyone on the net can drive it with curl). - For config truth, dump the whole file
(
podman exec <svc> cat /config/config.xml), not a pipedgrep | sed -n 2p:sed -n Npexits 0 on zero matches, so the pipeline hides a miss and you conclude "empty field" when the grep itself failed. - Decision framing: when posture = gated-with-zero-users, present it as a decision menu (None behind the front-door wall vs External/authentikarr) not a silent change — first-account-wins means whoever logs in first owns the instance.
The None + Enabled auth-config trap (v1 .NET apps)
<AuthenticationMethod>None</AuthenticationMethod> +
<AuthenticationRequired>Enabled</AuthenticationRequired> is
self-contradictory: the UI Save is permanently disabled, and any save throws
DryIoc.ScopedItemException in AuthorizationMiddleware → 500 on every
page after restart. The valid no-auth representation is absence of the
<AuthenticationRequired> key, not the literal string Disabled. Fix =
delete the key from /config/config.xml
(sed -i "/<AuthenticationRequired>/d"), restart the container. Tell-tale:
one v1 app boots clean while siblings 500-sprawl → diff their config.xml
auth blocks; the healthy one shows the correct shape. (Prowlarr runs
Forms + Enabled healthily — the crash combo is None+Enabled only.)
General rules that saved the session these came from: container
State=running ≠ app serving — read podman logs --tail 12 <c> before
theorizing; when a UI gate blocks saving, go under it (these apps keep
plaintext XML config on disk — read/edit via podman exec).
See Also
- Prowlarr — indexer feeds and the v2 sync model
- Sonarr · Radarr · Lidarr · Readarr — the managers
- qBittorrent — download client, 4.6.7 pin, E2E verification
- FrankBook-Pro — the host and its podman machine
- Homepage Status Probes — how the e2e gate treats this sleeping host