ansible-playbook site.yml -K # -K only if sudo still asks once; playbook installs NOPASSWD for tim
```
What it does: OS updates, tools (incl. Microsoft `edit`), Docker, AMD/Vulkan, UFW (LAN only), `/opt/stack` + `/storage`, `.env`, `arr-stack.service`, and **tim** as local admin (passwordless sudo, docker/video/render/…, owns stack + storage, permissive GPU nodes).
Depends on **gluetun** healthy. If gluetun is unhealthy with `lookup … i/o timeout`, WireGuard is not passing traffic — usually a **stale Endpoint IP** in `wireguard/wg0.conf`.
**Auto-heal:** `scripts/gluetun-endpoint-watch.sh` + systemd timer every **5 minutes** (installed by Ansible). When gluetun is not healthy it: digs `WIREGUARD_ENDPOINT_HOST` → updates `Endpoint` in `wg0.conf` → recreates gluetun → recreates qbittorrent → tests exit IP.
- Keep “Keep incomplete torrents in” off (or a subfolder under `/data/torrents` if you prefer).
2.**Categories** (same Options → Downloads page, or right‑click empty area in the left “Categories” panel → **Add category**):
| Category name | Save path |
|---------------|-----------|
| `movies` | `/data/torrents/movies` |
| `tv` | `/data/torrents/tv` |
| `music` | `/data/torrents/music` |
For each: **Add category** → name exactly `movies` / `tv` / `music` (lowercase, matches Sonarr/Radarr/Lidarr) → set **Save path** to the path above → OK.
3. Sonarr/Radarr/Lidarr download-client “Category” fields must match those names so torrents land in the right folder for hardlinks.
- Then enable FlareSolverr on indexers that show Cloudflare errors.
Does **not** solve every captcha (Turnstile/hCaptcha image puzzles, etc.). Prefer indexers without CF; paid APIs (2captcha / CapSolver) are optional external services, not a free self-hosted magic box.
**Bazarr** — Sonarr/Radarr URLs + API keys; paths `/data/media/tv`, `/data/media/movies`.
**Jellyfin** — Libraries: Movies `/data/media/movies`, Shows `/data/media/tv`, Music `/data/media/music`.
**Open WebUI** — Create admin on first visit. Model connection is pre-set to Bonsai (`http://bonsai:8080/v1`). Enable web search in chat if desired. RAG: Workspace → Knowledge (uploads) or files under `/workspace`.
**Bonsai** — No UI login. Smoke test: `curl -s http://192.168.8.123:8081/v1/models`. Needs GGUF in `models/bonsai/` and `docker compose build bonsai` once.
**OpenHands** — On the server (`/opt/stack`): `docker compose --profile coding up -d openhands`.
Settings → Advanced: base URL `http://bonsai:8080/v1`, API key `sk-local-bonsai`, model `openai/<id from /v1/models>`.
**Connect to Forgejo (pick A and/or B)**
**A — Provider (browse / open repos in OpenHands)**
1. Forgejo → avatar → **Settings → Applications** → generate a token (scopes: `read:repository`, `write:repository`, `read:user` — or full access on a trusted LAN).