--- type: reference description: FrankBook-Pro — the Mac mini running the *arr stack + qBittorrent inside a podman machine VM, gluetun-gated tags: [frankbook-pro, mac, arr, qbittorrent, gluetun, wireguard, podman-machine] timestamp: 2026-09-01 --- # FrankBook-Pro FrankBook-Pro (192.168.0.112, macOS x86_64, Intel) runs the *arr automation stack and qBittorrent inside a **podman machine** VM (Fedora CoreOS-style `core` user). Seven quadlet units live in `frankbook-pro//quadlet/`: `gluetun`, `qbittorrent`, `prowlarr`, `sonarr`, `radarr`, `lidarr`, `readarr`. ## Why a VM, and why inside it - macOS podman runs containers inside a VM; quadlet units are installed in the VM (`/home/core/.config/containers/systemd/`) via `podman machine ssh`. - `/Volumes/COMPLETE` (review disk) and `/Volumes/INCOMPLETE` (in-progress disk) are virtiofs-mounted into the VM, so qBittorrent writes straight to the same disks the human review workflow uses. - Grab-only stack: the arrs never auto-import; nothing writes to the entropy volume. Completed files land in `/Volumes/COMPLETE/Finished Torrents`. ## WireGuard gate (gluetun) — ACTIVE qBittorrent downloads through gluetun (netns share); the arrs run without it. Provisioned 2026-09-01 with a **second Proton WireGuard config** (server `PA-12`; the downloaded `.conf` lives in `~/Downloads/FrankBook-Pro-PA-12.conf` on Dankware). One config = one keypair — reusing Dankclaw's would bounce the server-side endpoint and kill SearXNG. The Seattle server the plan originally assumed is equally valid; only endpoint + server pubkey must match the chosen config. Egress verified from inside the VM: `curl ifconfig.me` shows the Proton PA exit (never the residential IP), ~19 MB/s sustained on a live torrent. `/home/core/Containers/gluetun/env` (0600, inside the VM): VPN_SERVICE_PROVIDER=custom VPN_TYPE=wireguard WIREGUARD_PRIVATE_KEY= WIREGUARD_ADDRESSES= WIREGUARD_PUBLIC_KEY= WIREGUARD_ENDPOINT_IP=51.179.177.2 WIREGUARD_ENDPOINT_PORT=51820 WIREGUARD_ALLOWED_IPS=0.0.0.0/0,::/0 WIREGUARD_PERSISTENT_KEEPALIVE_INTERVAL=25s The private key is hashed into the VM env file and never enters the repo. NAT-PMP inbound forwarding is available on Proton (gluetun supports it `FIREWALL_INPUT_PORTS` + `NATPMP=true`) — deliberately deferred: the e2e gate passes outbound-only, and swarm peers needing an inbound port is a seeding-era concern, not a grab-only one. ## Deploy flow Same deployer as every host: `./scripts/deploy.sh frankbook-pro --service ` from Dankware. The Mac-side handler (`deploy_frankbook_stack` in `scripts/deploy-remote.sh`) pipes unit files + a seed script through `podman machine ssh bash -s`, seeds qBittorrent's first-boot config once (marker file `qbit-seeded`), restarts the selected unit inside the VM, and health-checks its published port on Mac loopback. ## LAN reachability gvproxy forwards published ports to Mac loopback only. LaunchAgents (`local.fwd.*` in `~/Library/LaunchAgents`) run socat forwarders binding the LAN IP (192.168.0.112) for arr UIs (9696/8989/7878/8686/8787) and the qB WebUI (8080). The two **6881 tcp+udp agents were retired**: gvproxy binds a wildcard `*:6881/udp` listener (dual-stack; Go sets `SO_REUSEADDR` on TCP but not UDP), so the socat-specific bind collides at every boot — delete the agents; swarm inbound (if ever needed) is the Proton NAT-PMP path, not the LAN one. Note macOS's application firewall still filters gvproxy inbound from non-local sources (stealth mode drops unsolicited packets), so cross-host reachability of 8080 from other LAN boxes may need `socketfilterfw` allowlisting of gvproxy — Mac-local use and swarm egress are unaffected. ## qBittorrent runtime + wiring (2026-09-01) - `WebUI\Password_PBKDF2` is a **PBKDF2** `@ByteArray(salt:hash)` (PBKDF2-HMAC-SHA512, 100k iterations, 64 bytes) — qB ≥ 4.2 ignores plaintext. The plaintext lives in `/home/core/Containers/qbittorrent/env` (mode 600, VM-local) for the arr wiring. The first seed wrote the hash under the legacy `WebUI\Password` key (silently ignored → every client got `Unauthenticated`); the live conf was patched and `scripts/deploy-remote.sh` fixed to write the `_PBKDF2` key. Gotcha (live, 2026-09-01): after qB 5.x ran, its conf rewrite left a hash line 4.6.7 rejects (`invalid credentials` for every login, including the arrs) rewriting the same `@ByteArray` line fresh (same password, 100k iterations) restored login. Any future version flip: regenerate the line. - `HostHeaderValidation=false` + `ServerDomains=*`: the socat forwarder preserves the LAN `Host` header, which would otherwise be rejected. - **Save-path convention**: `Session\DefaultSavePath=/Volumes/COMPLETE/ Finished Torrents`, `Session\TempPath=/Volumes/INCOMPLETE/incomplete` (two-disk layout: partials on INCOMPLETE, qB moves on completion). qB 5.2 API adds with a category do **not** inherit the category save path (no auto-TMM), so the default itself points at `Finished Torrents`: every grab lands there regardless of client behavior; human review moves files onward; nothing writes to the entropy volume. - Categories seeded: `Finished Torrents`, `lidarr`, `radarr`, `readarr` `tv-sonarr` (all → `Finished Torrents`). - **Download clients** (gateway `10.88.0.1:8080`, user `admin`): all five test green against live qB — sonarr (`tv-sonarr`), radarr, lidarr, readarr (rows pre-created in the prior session; passwords re-pushed via API), and prowlarr (created from the `/downloadclient/schema` template — a hand-minimal body 400s in Prowlarr's create-time test). Readarr's EOL validator (last release 2023) rejects qB ≥ 5's `204` empty login success as "Authentication failure" — that kills runtime auth too, not just API creates; the fix was pinning qB itself (below), after which readarr tests 200 like the rest. - **qB pinned to 4.6.7** (`Image=lscr.io/linuxserver/qbittorrent:4.6.7` not `latest`): `latest` resolved to 5.x, whose `204` login breaks Readarr's frozen client; 4.6.7 answers the legacy `200` + `"Ok."` that every client accepts. Pinned in `frankbook-pro/qbittorrent/quadlet/qbittorrent.container`. - **Prowlarr Applications** push to sonarr/radarr/lidarr (`fullSync` gateway ports). Readarr deliberately omitted: prowlarr's indexer-push test to readarr's `/api/v1/indexer/test` 500s (EOL schema drift). Legacy `Prowlarr-*` indexer rows carried in the migrated dankserver DBs pointed at the unresolvable hostname `prowlarr:9696` and were deleted; the current `*(Prowlarr)` entries test OK. - **E2E proven (2026-09-01, on 4.6.7)**: added a legal test torrent via the API with category `Finished Torrents` — downloaded through the tunnel (~17 MB/s), qB moved it INCOMPLETE → COMPLETE into `/Volumes/COMPLETE/Finished Torrents`, then deleted with data. # Citations - Plan: `local://podman-standardization-arr-migration-plan.md` (session 2026-09-01) - Units: `frankbook-pro/*/quadlet/*.container`, seam `tests/frankbook_quadlet.bats` - Handler: `deploy_frankbook_stack`, `scripts/deploy-remote.sh`