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.
120 lines
6.1 KiB
Markdown
120 lines
6.1 KiB
Markdown
---
|
|
type: reference
|
|
title: "*arr Stack"
|
|
description: "The *arr automation stack on FrankBook-Pro, every container on the VPN netns: topology, wiring, and gotchas."
|
|
tags: [arr-stack, prowlarr, sonarr, radarr, lidarr, readarr, qbittorrent, frankbook-pro, dankserver]
|
|
timestamp: 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/profile` endpoint at all.
|
|
- `PUT /downloadclient` and `PUT /indexer/{id}` take a SINGLE resource object
|
|
(GET it, patch `settings`, PUT the whole object back). An array body fails
|
|
with `Path: $ | 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, not `fields:[]`.
|
|
- `'Base Url' must not be empty` when keys were camelCase → **PascalCase is
|
|
required** (`BaseUrl`, `ApiKey`) — schema keys, not the JSON wire keys.
|
|
- `DefaultMetadataProfileId must be > 0` → fetch real ids from
|
|
`GET /api/v1/metadataprofile` (rows like `(1,'Standard')`)
|
|
`GET /api/v1/qualitydefinition` returns 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).
|
|
- `testingPackageId` in a PUT body downgrades validation failures to
|
|
warnings.
|
|
- `syncLevel` is enum `ApplicationSyncLevel` — 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.
|
|
|
|
1. **Confirm which host runs the service before probing** (`podman ps` on
|
|
the target, not the box you ssh'd into first — the arrs live on
|
|
FrankBook-Pro).
|
|
2. 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).
|
|
3. For config truth, dump the whole file
|
|
(`podman exec <svc> cat /config/config.xml`), not a piped
|
|
`grep | sed -n 2p`: **`sed -n Np` exits 0 on zero matches**, so the
|
|
pipeline hides a miss and you conclude "empty field" when the grep itself
|
|
failed.
|
|
4. 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
|