Files
Frank 33eae71d33 docs: distill the managed skill library into the vault (waves 1-7)
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.
2026-10-05 21:19:15 -07:00

9.2 KiB
Raw Permalink Blame History

type, title, description, tags, timestamp
type title description tags timestamp
reference qBittorrent qBittorrent download client for the *arr stack — pinned 4.6.7 on FrankBook-Pro, shares the Gluetun WireGuard netns, PBKDF2 WebUI credential recipe.
qbittorrent
arr-stack
frankbook-pro
gluetun
2026-09-03T00:00:00Z

qBittorrent

qBittorrent is the torrent download client for the *arr stack. The stack runs on FrankBook-Pro (the Mac mini) as quadlet units inside its podman machine — not on Dankserver. It receives download requests from Sonarr, Radarr, Lidarr, and Readarr via category tagging.

Host: FrankBook-Pro (podman machine) — Access: WebUI on port 8080, published by the Gluetun unit because qBittorrent shares that container's network namespace (a netns-sharer publishes no ports of its own).

Facts

Item Value
Image lscr.io/linuxserver/qbittorrent:4.6.7 — pinned, see the pin rationale below
Network Network=gluetun.container (torrent egress exits via the WireGuard/ProtonVPN tunnel)
Ports 8080 (WebUI) and 6881 TCP+UDP published on the gluetun unit
Env PUID=0, PGID=0, TZ=America/Los_Angeles, UMASK_SET=022
Volumes /Volumes/COMPLETE/qbittorrent-data:/config, plus /Volumes/COMPLETE and /Volumes/INCOMPLETE mounted at identical paths
Auth Web UI username/password (admin + generated password); LAN bypass not used

Why the 4.6.7 pin exists (2026-09-01)

Readarr's abandoned qBittorrent client rejects qB ≥ 5's 204 empty-body login success as Authentication failure — a runtime break, not just an API-create issue. The fleet pins lscr.io/linuxserver/qbittorrent:4.6.7 while Readarr is in the stack. Never unpin to latest while Readarr is in the fleet.

Second trap on any version flip: qB 5.x's shutdown conf rewrite leaves a WebUI\Password_PBKDF2=@ByteArray(...) line that 4.6.7 rejects — every login then returns "invalid credentials" (the arrs included). Fix = rewrite the same conf line with the same password while qB is stopped.

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):

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 chosen value lives host-local in /home/dank/.qbpass (mode 600, owner dank) — never in git; every arr download client and Prowlarr reference it. (The classic @ByteArray(salt+hash) form is WRONG for qb5: login 401s on it; verify() base64-decodes the two halves separately.)

Deployment (current)

Quadlet unit qbittorrent.container from this repo (frankbook-pro/qbittorrent/quadlet/), installed inside the podman machine at /home/core/.config/containers/systemd/qbittorrent.service by the frankbook-pro handler in scripts/deploy-remote.sh:

[Container]
ContainerName=qbittorrent
Image=lscr.io/linuxserver/qbittorrent:4.6.7
Network=gluetun.container
Environment=PUID=0
Environment=PGID=0
Environment=TZ=America/Los_Angeles
Environment=UMASK_SET=022
Volume=/Volumes/COMPLETE/qbittorrent-data:/config
Volume=/Volumes/COMPLETE:/Volumes/COMPLETE
Volume=/Volumes/INCOMPLETE:/Volumes/INCOMPLETE

[Service]
TimeoutStartSec=900
Restart=always

Gotchas (verified live)

  • 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.
  • Cookie-name quirk by version: qB 5.x names its WebUI session cookie QBT_SID_<port>; 4.6.7 uses plain SID. Any automation must parse the cookie generically (e.g. grep -oE '[A-Za-z_]*SID[A-Za-z0-9_]*=[^;[:space:]]*') instead of hard-coding a port-suffixed name, or it fails with 403 cascades that never reach qB as failed logins.
  • Egress verification for this stack must run inside the gluetun container (podman exec gluetun wget -qO- https://ipinfo.io/json); running the same probe one ssh-context out returns the residential IP — a false leak alarm.
  • Ensure umask is set to 022 so downloaded files are readable by arr apps.

E2E verification after any pipeline change

Run after ANY change to the torrent pipeline — WireGuard credential rotation, qB version change, gluetun reconfig, category re-seeding. Proves the full grab path (tunnel egress → qB download → category save path → clean removal). All commands from a dev box via ssh frankbook-pro; inside the Mac shell export PATH="/usr/local/bin:/opt/homebrew/bin:$PATH" for podman machine ssh.

Access note (2026-09-21): the Mac's login user is dank@192.168.0.112 (NOT core — core is the podman-machine VM user, reached only via podman machine ssh). blankpad has key auth for dank@ (bootstrapped 2026-09-21) and a frankbook-pro alias in ~/.ssh/config. macOS sshd has a low MaxAuthTries: when password-bootstrapping use -o PreferredAuthentications=password -o PubkeyAuthentication=no -o IdentitiesOnly=yes or the key offers exhaust the attempts before the password is tried.

1. Egress gate — MUST run inside gluetun:

podman machine ssh 'podman exec gluetun wget -qO- https://ipinfo.io/json'

Require org = AS9009 (M247 — ProtonVPN Panama PA-12 config); judge by org, never country alone. NEVER run this one ssh-context out (bare wget on the Mac host): it silently returns the residential Comcast IP and reads as a hard-gate leak that does not exist. A residential reading ⇒ re-run inside the container before diagnosing.

2. Login — generic cookie parse. The QPW fetch is flaky (~1 in N returns empty); assert length before use — an empty-password run once produced a Forbidden loop that cost hours:

QPW=$(podman machine ssh 'cat /home/core/Containers/qbittorrent/env' | grep QBIT_WEBUI_PASSWORD | cut -d= -f2)
[ ${#QPW} -eq 20 ] || exit 1
SID=$(curl -si --data-urlencode "username=admin" --data-urlencode "password=$QPW" \
  http://127.0.0.1:8080/api/v2/auth/login \
  | grep -oE '[A-Za-z_]*SID[A-Za-z0-9_]*=[^;[:space:]]*' | head -1)
curl -b "$SID" …   # pass the WHOLE match

WebUI from the LAN: http://192.168.0.112:8080 (published on the gluetun unit). Username admin; password = the machine-local QBIT_WEBUI_PASSWORD (20 chars, generated once at provisioning by the deploy handler — never in the repo). Parse-bug 403s never reach qB as failed logins — no ban accrues; server-side truth is /Volumes/COMPLETE/qbittorrent-data/qBittorrent/config/qBittorrent/logs/qbittorrent.log (WebAPI login success/failure + source IP). Repeated REAL failures accrue toward qB's 5-failures/3600 s IP ban.

3. Add a legal test torrent. Blender open-movie torrents (~300 MB, e.g. Big Buck Bunny) via POST /torrents/add with the raw .torrent file (-F "file=@…") and category=Finished Torrents. Never leave a real-looking grab behind.

4. Poll and verify. Poll GET /torrents/info (~20 s): state downloading → moving (INCOMPLETE→COMPLETE move) → stalledUP/uploading at progress=1. save_path must be /Volumes/COMPLETE/Finished Torrents (qB 4.x auto-creates unknown categories at DefaultSavePath; 5.x did NOT inherit category paths — one more reason for the 4.6.7 pin). Confirm on disk: ls "/Volumes/COMPLETE/Finished Torrents/". Throughput ~17 MB/s is normal for the tunnel.

5. Cleanup — delete only your artifact:

curl -s -o /dev/null -w "%{http_code}" -b "$SID" \
  --data-urlencode "hashes=<literal-hash>" \
  --data-urlencode "deleteFiles=true" \
  http://127.0.0.1:8080/api/v2/torrents/delete   # expect 200

Target the known hash literally — the user's real grabs share the directory and MUST be untouched. Verify: hash absent from /torrents/info, your dir gone, all other entries intact.

Version-coupling constraints: qB is pinned to 4.6.7 (Readarr's EOL client rejects qB ≥ 5's 204 login at runtime — see the pin rationale above). Do not unpin while Readarr exists. After ANY qB version flip, regenerate the WebUI\Password_PBKDF2 line fresh (same password, 100k iterations) — a 5.x-written hash line makes 4.6.7 reject every login. Rewrite while qB is stopped.

See Also

  • ARR-Stack — full *arr stack documentation
  • Gluetun — the WireGuard tunnel this container shares