Structural repair: - 13 pages were the same document twice (each exactly 2x its HEAD size), a second frontmatter block whose closing fence had been glued onto a body line by a frontmatter rewrite that re-read its own output. Un-doubled by cutting at the second frontmatter and keeping the copy whose body is a superset; the orphan census reports 0 lost tokens against HEAD. - log.md: one entry per date, bare `## YYYY-MM-DD` headings, newest first — 66 dated headings merged into 34, titles demoted to bold leads, every content line verified present. The file had been descending at the head and ascending at the tail. Ownership moves: - New `LLM/frank-medium.md` is the locator for the retired Dankserver endpoint: era windows quoted from each era page's own banner, no numbers restated, and the two unresolved date conflicts recorded instead of silently resolved. `frank-archon.md` links to it instead of restating the pre-rename window. - The four Pipeline-Parallelism stubs (frontmatter only, no body) are gone; `Multi-Node Runtimes.md` owns runtime construction, launch, mixed-vendor verdicts and cross-split spec decode, and the four links that pointed at the stubs were repointed at it or at the drafter page. - Authentik's three stub pages fold into `Authentik.md`, with the process detail and the outpost reference split out so the parent stays under the length hint. Verified corrections (host census + live probe, 2026-10-19): - The MXFP4 checkpoint two pages called the "on-disk rollback" is gone: /home/dank/models/ holds StillDeadcode, rad and tpl.jinja. Stated once, on the era page that owns that payload; the swap page now links to it. - frank-medium-radiance-mxfp4: the 4.24/7 accept rate belongs to the DFlash2 arm measured on the FP8 target, not to the MXFP4 era; the 3-family soak run census is kept once (155 requests) instead of twice (~120 and 155). - Fleet shapes re-confirmed against the live endpoints before touching any page: 3x200000 fp8 KV, 2x200704, 2x262144. Index rows are regenerated, not hand-carved. Net -1,849 vault lines.
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