docs(arr-chain): Prowlarr feed contract cracked, qB PBKDF2 persistence recipe, chain proof
This commit is contained in:
1 parent
a04c9d7d02
commit
3626c7f40d
3 files changed
+134
-17
No files matched your search
@@ -1,6 +1,8 @@
|
|||||||
# Prowlarr
|
# Prowlarr
|
||||||
|
|
||||||
Prowlarr is the indexer management layer for the *arr stack. It aggregates indexers (torrent and Usenet) and exposes a unified API that Sonarr, Radarr, Lidarr, and Readarr consume.
|
Prowlarr is the indexer management layer for the *arr stack. It hosts the
|
||||||
|
indexer definitions and exposes them as Torznab feeds and downloads;
|
||||||
|
Sonarr, Radarr, Lidarr, and Readarr query those feeds and receive results.
|
||||||
|
|
||||||
**Host:** Dankserver — **Hostname:** `prowlarr.dankserver.local` — **Docs:** https://wiki.servarr.com/prowlarr
|
**Host:** Dankserver — **Hostname:** `prowlarr.dankserver.local` — **Docs:** https://wiki.servarr.com/prowlarr
|
||||||
|
|
||||||
@@ -11,14 +13,35 @@ Prowlarr is the indexer management layer for the *arr stack. It aggregates index
|
|||||||
| Image | `lscr.io/linuxserver/prowlarr:latest` |
|
| Image | `lscr.io/linuxserver/prowlarr:latest` |
|
||||||
| Port | 9696 (host) → 9696 (container) |
|
| Port | 9696 (host) → 9696 (container) |
|
||||||
| Min RAM | 256 MB |
|
| Min RAM | 256 MB |
|
||||||
| Auth | API key (Settings → General) |
|
| Auth | Forms (web UI) + API key (`/api/v1/...` and feed routes) |
|
||||||
|
|
||||||
## Configuration
|
## Live wiring (verified end-to-end 2026-08-30)
|
||||||
|
|
||||||
1. Add indexers: Settings → Indexers → Add.
|
Indexer queries flow **through** Prowlarr; a grab hands off to the querying
|
||||||
2. Test each indexer with the **Test** button.
|
app's own qBittorrent download client. Feeds are per-indexer:
|
||||||
3. Assign tags (e.g., `movies`, `tv`, `music`) to indexers for routing.
|
|
||||||
4. Add download client: Settings → Download Clients → Add qBittorrent.
|
```
|
||||||
|
http://prowlarr:9696/{indexerId}/api?apikey={GLOBAL_KEY}&t=caps|search|tvsearch|movie|music|book&...
|
||||||
|
http://prowlarr:9696/{indexerId}/download?link=...&file=... (torrent handoff)
|
||||||
|
```
|
||||||
|
|
||||||
|
| App | Indexer row | Prowlarr id | Categories | Status |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| Radarr | Prowlarr-YTS | 7 | [2000] | ✅ grabbed + imported 2160p Dark Knight 2026-08-30 |
|
||||||
|
| Sonarr | Prowlarr-Nyaa | 4 | [5000] | ✅ connection validated (202) |
|
||||||
|
| Lidarr | Prowlarr-Nyaa | 4 | [3000] | ✅ connection validated (202) |
|
||||||
|
| Readarr | Prowlarr-Byrutor | 3 | [7000] | indexer valid; download client parked |
|
||||||
|
|
||||||
|
The global API key is in `config.xml` (`<ApiKey>`); per-application keys are
|
||||||
|
only used by Prowlarr's own sync calls, NOT by the feeds. The arr download
|
||||||
|
clients authenticate to qBittorrent with the permanent WebUI password stored
|
||||||
|
in `/home/dank/.qbpass` (mode 600) on Dankserver.
|
||||||
|
|
||||||
|
**Readarr caveat:** Readarr is abandoned upstream (last release 0.4.x); its
|
||||||
|
qBittorrent client cannot authenticate against qB 5 (`Authentication
|
||||||
|
failure` on the connection test while Sonarr/Lidarr/Radarr pass with the
|
||||||
|
same credentials). The books pipeline is parked pending a Readarr successor;
|
||||||
|
the indexer row itself is healthy.
|
||||||
|
|
||||||
## Docker Compose
|
## Docker Compose
|
||||||
|
|
||||||
@@ -40,11 +63,38 @@ prowlarr:
|
|||||||
networks: [appnet]
|
networks: [appnet]
|
||||||
```
|
```
|
||||||
|
|
||||||
## Gotchas
|
## Gotchas (all verified live 2026-08-30)
|
||||||
|
|
||||||
- Prowlarr database grows with indexer data. Set up periodic vacuum/backup.
|
- **The feed route is `/{indexerId}/api`.** There is no `/torznab/{id}/feed`
|
||||||
- Indexer sync has a 15–60 minute delay. Search results may lag.
|
— that path (and any other unmatched path) hits the Forms-auth catch-all
|
||||||
- Rotate API key: update it in every arr app.
|
and gets `302 → /login?returnUrl=…`. An arr following the redirect parses
|
||||||
|
the login HTML and fails with a cryptic
|
||||||
|
`'/' is an unexpected token … Line 54` XML error that looks like an auth
|
||||||
|
problem. It is a path typo. Source: `NewznabController`
|
||||||
|
(`[HttpGet("{id:int}/api")]` + absolute
|
||||||
|
`/api/v1/indexer/{id}/newznab`).
|
||||||
|
- **The arr's Torznab joiner is `TrimEnd(baseUrl,'/') + apiPath` with NO
|
||||||
|
separator.** `baseUrl="…/7/" + apiPath="api"` silently becomes `…/7api`.
|
||||||
|
Either keep the leading slash on `apiPath` (`"/api"`) or end the baseUrl
|
||||||
|
with `/api`.
|
||||||
|
- Categories in the indexer row must be a subset of what the feed's `caps`
|
||||||
|
advertises; extras log "does not support the following categories" and
|
||||||
|
some feeds reject them.
|
||||||
|
- The v1 API collection is `/api/v1/applications` (not `/api/v1/app`);
|
||||||
|
GET responses carry `fields: []` — real settings values live in
|
||||||
|
`prowlarr.db` → `Applications.Settings` JSON (read-only inspection).
|
||||||
|
- Instance name/`InstanceName=Prowlarr` in `config.xml` does NOT prefix the
|
||||||
|
API/feed routes on this build (they answer at root; the `/Prowlarr/...`
|
||||||
|
prefix 302s to the SPA).
|
||||||
|
- **Application auto-sync cannot manage these rows** (they carry
|
||||||
|
`SyncLevel=2`): `POST /api/v1/applications/test` fails with
|
||||||
|
`Unable to cast object of type 'NzbDrone.Core.Thing…'` — Prowlarr v1
|
||||||
|
cannot deserialize Radarr 6.3's newer response types. The manual Torznab
|
||||||
|
rows above are therefore the real integration; sync neither duplicates
|
||||||
|
nor clobbers them.
|
||||||
|
- Prowlarr runs `AuthenticationMethod=Forms` + `AuthenticationRequired=Enabled`
|
||||||
|
healthily — the DryIoc crash combo was `None`+`Enabled` only (see
|
||||||
|
[[radarr]] auth notes).
|
||||||
|
|
||||||
## See Also
|
## See Also
|
||||||
|
|
||||||
|
|||||||
@@ -15,10 +15,39 @@ qBittorrent is the torrent download client for the *arr stack on Dankserver. It
|
|||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
1. Change the default Web UI password (Settings → Web UI).
|
1. Keep Web UI authentication ON (LAN bypass not needed — the arr apps
|
||||||
2. Enable "No auth mode for LAN" for arr app connectivity (Settings → Web UI → uncheck "HTTP authentication required for clients").
|
authenticate with the API) and give them a permanent password.
|
||||||
3. Create download categories: `movies`, `tv`, `music`, `books`.
|
2. Create download categories per app (`radarr`, `sonarr`, `lidarr`,
|
||||||
4. Set max active downloads: Settings → Downloads (recommended: 5 global, 3 per category).
|
`readarr`): the arr download clients route category-per-app, and Prowlarr
|
||||||
|
feeds hand grabs to each app's own client.
|
||||||
|
3. Set max active downloads: Settings → Downloads (recommended: 5 global,
|
||||||
|
3 per category).
|
||||||
|
|
||||||
|
## Persistent WebUI credentials (verified 2026-08-30)
|
||||||
|
|
||||||
|
qBittorrent 5 mints a **new temporary password at every start** unless a
|
||||||
|
password is stored in `qBittorrent.conf`. That rotation silently breaks every
|
||||||
|
arr download client after each reboot. The Web-API has no password endpoint
|
||||||
|
any more (`/api/v2/app/setPassword` was removed), and the linuxserver image
|
||||||
|
has no `WEBUI_AUTH` handling — so persistence must be written to the conf
|
||||||
|
directly, in qb's own `salt:hash` format (`src/base/utils/password.cpp`:
|
||||||
|
PBKDF2-HMAC-SHA512, 100 000 iterations, 16-byte salt = 4×uint32, then
|
||||||
|
`base64(salt) + ":" + base64(hash)` as the raw value):
|
||||||
|
|
||||||
|
```python
|
||||||
|
import base64, hashlib, os
|
||||||
|
salt = os.urandom(16)
|
||||||
|
h = hashlib.pbkdf2_hmac("sha512", password.encode(), salt, 100000, 64)
|
||||||
|
val = (base64.b64encode(salt) + b":" + base64.b64encode(h)).decode()
|
||||||
|
# with the container STOPPED, in [Preferences]:
|
||||||
|
# WebUI\Username=admin
|
||||||
|
# WebUI\Password_PBKDF2=<val>
|
||||||
|
```
|
||||||
|
|
||||||
|
Then `podman start qbittorrent` and confirm login survives a
|
||||||
|
`podman restart` — the temp-password rotation stops the moment stored
|
||||||
|
credentials verify. (The classic `@ByteArray(salt+hash)` form is WRONG for
|
||||||
|
qb5: login 401s on it; verify() base64-decodes the two halves separately.)
|
||||||
|
|
||||||
## Docker Compose
|
## Docker Compose
|
||||||
|
|
||||||
@@ -41,9 +70,14 @@ qbittorrent:
|
|||||||
networks: [appnet]
|
networks: [appnet]
|
||||||
```
|
```
|
||||||
|
|
||||||
## Gotchas
|
## Gotchas (verified live)
|
||||||
|
|
||||||
- qBittorrent Web UI password changes on first run (check `config/qBittorrent.conf`).
|
- Temporary-password rotation on every boot is the default state of an
|
||||||
|
untouched qb5 — see the persistence recipe above; without it, all arr
|
||||||
|
download clients silently fail queue adds ("Couldn't add release … to
|
||||||
|
download queue" in the arr logs) after any restart.
|
||||||
|
- API login from `curl` needs a `Referer: http://<host>:<port>/` header
|
||||||
|
(CSRF protection) or POSTs get rejected even with a valid SID.
|
||||||
- Ensure umask is set to `022` so downloaded files are readable by arr apps.
|
- Ensure umask is set to `022` so downloaded files are readable by arr apps.
|
||||||
- Monitor bandwidth limits to avoid saturating the network.
|
- Monitor bandwidth limits to avoid saturating the network.
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,39 @@ timestamp: 2026-09-14
|
|||||||
|
|
||||||
# Bundle Change Log
|
# Bundle Change Log
|
||||||
|
|
||||||
|
## 2026-08-30 (late) — download chain proven end to end; qB credentials persisted
|
||||||
|
- **Cracked the arr↔Prowlarr feed contract**: route is `/{indexerId}/api`
|
||||||
|
(Prowlarr `NewznabController`; absolute `/api/v1/indexer/{id}/newznab`),
|
||||||
|
auth = the Prowlarr **global** key. The previously hand-rolled
|
||||||
|
`/torznab/{id}/feed` paths were Forms-auth catch-alls returning
|
||||||
|
`302 → /login` — which the arrs XML-parsed as "unexpected token '/'".
|
||||||
|
A source-verified librarian answer settled it in one shot.
|
||||||
|
- **Re-pointed all four arr indexer rows** (Radarr→YTS id7,
|
||||||
|
Sonarr/Lidarr→Nyaa id4, Readarr→Byrutor id3) via each app's own API;
|
||||||
|
every PUT passed the app's live connection validator (202); RSS enabled.
|
||||||
|
- **First real grab**: `MoviesSearch` → 3 YTS releases → *The Dark Knight
|
||||||
|
(2008) 2160p* (7.5 GiB) → qBittorrent 100 % → **imported**
|
||||||
|
(`hasFile: true`, 8,078,103,833 B at `/movies/The Dark Knight (2008)`,
|
||||||
|
hardlinked from the shared `/mnt/entropy` filesystem), queue empty.
|
||||||
|
- **Prowlarr Application auto-sync found broken by version skew**
|
||||||
|
(`Unable to cast object of type 'NzbDrone.Core.Thing…'` against Radarr
|
||||||
|
6.3) — the manual rows are the integration; documented.
|
||||||
|
- **qBittorrent permanent credentials installed**: the WebUI temp password
|
||||||
|
rotated on every boot (it had silently broken the arr clients once this
|
||||||
|
session). qb5 removed the API password endpoint and the lscr image has
|
||||||
|
no env support — wrote `WebUI\Password_PBKDF2` (source-verified
|
||||||
|
`base64(salt):base64(PBKDF2-HMAC-SHA512/100k)` form) into the conf with
|
||||||
|
the container stopped; login survives `podman restart` (204). All arr
|
||||||
|
download clients updated to it; value kept host-local in
|
||||||
|
`/home/dank/.qbpass` (0600), never in git.
|
||||||
|
- **Readarr parked with reason**: abandoned upstream; its qB client cannot
|
||||||
|
authenticate against qB 5 while three sibling apps pass with identical
|
||||||
|
credentials. Indexer row healthy; books pipeline awaits a successor.
|
||||||
|
- **Docs**: rewrote `Dankserver/prowlarr.md` (live wiring + six verified
|
||||||
|
gotchas incl. the joiner bug and the 302-masquerading-as-auth failure)
|
||||||
|
and `Dankserver/qbittorrent.md` (persistent-credential recipe, Referer
|
||||||
|
CSRF note).
|
||||||
|
|
||||||
## 2026-08-29 (late) — guarded-host sign-in fixed end to end
|
## 2026-08-29 (late) — guarded-host sign-in fixed end to end
|
||||||
- **Diagnosed** both user symptoms: `Invalid next URL` = the flow being entered with an absolute cross-host `next=` (open-redirect guard rejects at completion); phone "hostname could not be found" = the public DNS zone had **no `auth` A record** (added via Porkbun API, `auth A 71.227.227.172 TTL 300`).
|
- **Diagnosed** both user symptoms: `Invalid next URL` = the flow being entered with an absolute cross-host `next=` (open-redirect guard rejects at completion); phone "hostname could not be found" = the public DNS zone had **no `auth` A record** (added via Porkbun API, `auth A 71.227.227.172 TTL 300`).
|
||||||
- **Disproved meta-header routing** live (full meta-header set still 404s; matching `Host` alone answers). The 2026.8 embedded outpost routes solely on the original `Host` vs each proxy provider's `external_host`.
|
- **Disproved meta-header routing** live (full meta-header set still 404s; matching `Host` alone answers). The 2026.8 embedded outpost routes solely on the original `Host` vs each proxy provider's `external_host`.
|
||||||
|
|||||||
Reference in new issue
Block a user