All ~173 managed skills read in full and merged, one polished reference page per skill-cluster: - LLM cross-cutting (17 pages): GGUF Compatibility, Llama Cpp Serving Sizing, Llama Cpp Multi-Device Splits, PCIe Link and Atomics, RDNA4 Container Serving, RDNA4 MoE Expert Offload VRAM Budget, vLLM Boot Forensics, vLLM Porting and Compile Triage, LLM Bench Windows, LLM Performance Diagnosis, Model Swap and Rollback, Tcclaviger Fork Ops, Heterogeneous Split Triage, FreeToken Builds + deltas folded into the retired runbooks (GLM DFlash2 port laws, Lucebox dual-R9700, R9V qualification pipeline, FLM pack registration, ik hybrid envelope, frank-medium split rebalance, llama.cpp GGUF preflight, batch-ubatch split section, bench-window D-state/stale-read notes, second-endpoint bring-up). - Architecture (3): Stale Signal Audit, Quadlet Lifecycle, Deploy State Triage. - Operations (6 + index): Remote Job Runner, Payload and Disk Reclaim, Container Host Reachability, Fleet Ops Playbook, Fleet Audit and Posture, SSH and Remote Access. - Infrastructure: Observability Provisioning + Homepage Status Probes; updates to Authentik, Anubis, LAN-DNS (per-client DoH bypass, Porkbun zone ops), Swap Configuration, OpenWebUI, Hermes (pre-existing page preserved and extended). - FrankBook-Pro: ARR-Stack filled (API split, schema-first probing, auth-posture, auth-config trap); qBittorrent E2E verification. - Desktop/Agent/Research: Noctalia and Niri, OMP Harness, Agent Docs and Edits, Command Center Dotfiles, PICO-8 Asset Standards. - Indexes: LLM Cross-Cutting References section, Operations/ and Agent/ index pages, root-index Skill Distillation section, AGENTS.md pointer. - log.md carries the wave plan. check-vault-links.py 0 unresolved (185 pages); ./tests/run.sh 366/366.
6.2 KiB
type, title, description, tags, timestamp
| type | title | description | tags | timestamp | |||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| reference | *arr Stack | The *arr automation stack (Prowlarr, Sonarr, Radarr, Lidarr, Readarr, qBittorrent) — FrankBook-Pro only, every container on the gluetun WireGuard netns (all egress VPN): topology, wiring, auth posture, 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