Files
oikos/knowledge/wiki/containers/122-arriman.md
dtoro 8a6422bd7d docs: move narrative wiki under knowledge/wiki/ (phase 3)
Problem: node and cross-cutting narratives lived at the repo root
(containers/, vms/, infrastructure/, host .md files), interleaved with the
machine-readable substrate.

Change:
- Move containers/ -> knowledge/wiki/containers/, vms/ -> knowledge/wiki/vms/,
  infrastructure/ -> knowledge/wiki/infrastructure/, hosts/{hubris,strong}.md ->
  knowledge/wiki/hosts/, infrastructure/references/ -> knowledge/sources/references/,
  GLOSSARY.md -> knowledge/GLOSSARY.md.
- Add knowledge/{index.md,log.md,sources/index.md} scaffolding.
- Rewrite all relative links repo-wide via a path-resolving mapper (inbound +
  outbound + between-moved-files), including .hermes/, runbooks, operations,
  investigations, plans, README, AGENTS.
- Repoint inventory.yaml doc_page fields and regenerate hosts/*.yaml (which
  embed doc_page); update oikos/gen-topology.py output path, candidate doc
  paths, and footer links; update code-comment doc paths.

Substrate untouched in place: inventory.yaml, hosts/*.yaml (regenerated,
idempotent), oikos/ code, mcp/, secrets/, bin/.

Verification:
- Logical broken-link set identical to pre-move baseline (net 128 -> 127; the
  topology regen fixed one, introduced none). Remaining are pre-existing refs
  to destroyed/archived nodes, out of scope for this move.
- gen-topology.py --check exit 0 (in sync); cards carry knowledge/wiki/ doc paths.
- build_host_files.py idempotent; all inventory doc_page targets resolve.
- MCP contract verified: get_page/search_docs/get_changelog resolve moved pages.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 14:35:23 +02:00

159 lines
10 KiB
Markdown

# 122 — `arriman`
Docker host running the \*arr stack via [`ezarr`](https://github.com/ezarr/ezarr) compose. Replaced the old yunohost-based [LXC 100](index.md#recently-destroyed-kept-for-archaeology) on 2026-04-21.
## At a glance
- **Hostname:** `arriman`
- **IP:** `192.168.8.245`
- **Host:** **strong** (migrated from hubris 2026-07-05)
- **Privilege:** privileged
- **Resources:** 4 cores / 8 GiB RAM / 24 GiB rootfs
- **Mounts:** `/mnt/media_local``/mnt/library`
- **Public hostnames:** `jellyseerr` / `qbit` / `sab` (see below)
## Compose
`/home/arr/ezarr/docker-compose.yml`. Network `ezarr_default` `172.18.0.0/16`. Service users all run with primary GID 10000 (host `media` group) inside their containers — see [media permissions](../infrastructure/media-permissions.md). Per-app `/config/<app>-config/` dirs bind to `/config` inside.
`.env` contents:
- `DOWNLOADS_DIR=/mnt/library/downloads/`
- `MEDIA_DIR=/mnt/library/`
- `MEDIACENTER_GID=10000`**must be 10000, not 13000.** s6-setuidgid only honors the primary PGID; `group_add:` doesn't propagate. Without this, qBit errors every torrent with "Permission denied".
- `SONARR_UID=13001`, `RADARR_UID=13002`, `LIDARR_UID=13003`, `PROWLARR_UID=13006`, `JELLYSEERR_UID=13012`, `BAZARR_UID=13013`, `QBITTORRENT_UID`, `SABNZBD_UID`
## Service / port map
All services route through gluetun's network namespace. Ports are exposed via
the gluetun container:
| Service | Host:Container | Public hostname |
| ------------- | -------------- | ------------------------------------ |
| gluetun (VPN) | — | — |
| sonarr | `8989:8989` | direct only (via gluetun) |
| radarr | `7878:7878` | direct only (via gluetun) |
| lidarr | `8686:8686` | direct only (via gluetun) |
| prowlarr | `9696:9696` | direct only (via gluetun) |
| bazarr | `6767:6767` | direct only (via gluetun) |
| jellyseerr | `5056:5055` | [`jellyseerr.hubris.network`](../infrastructure/dns.md) |
| qbittorrent | `8080:8080` | [`qbit.hubris.network`](../infrastructure/dns.md) |
| sabnzbd | `8082:8082` HTTP, `9090:9090` HTTPS | [`sab.hubris.network`](../infrastructure/dns.md) |
| flaresolverr | `8191:8191` | internal only |
| homarr | `7575:7575` | internal only |
Internal *arr ↔ *arr / *arr ↔ qBit/SAB/flaresolverr comms run on `localhost:<port>`
(services share gluetun's shared network namespace). External services reach them
via `gluetun:<port>` (e.g. Sonarr → qBittorrent at `localhost:8080` or
`gluetun:8080`).
## Categories (qBit + SAB + *arr)
| Category | qBit savePath | SAB dir | Consumer |
| -------- | --------------------------- | ------- | --------------------------------- |
| tv | `/data/torrents/tv` | tv | Sonarr |
| movies | `/data/torrents/movies` | movies | Radarr |
| music | `/data/torrents/music` | music | Lidarr |
| anime | `/data/torrents/anime` | anime | Sonarr (2nd download client, root `/data/media/anime`) |
| books / audiobooks / comics / podcasts | `/data/torrents/<cat>` | `<cat>` | (reserved, none in use yet) |
Path mapping: host `/mnt/library/<cat>` ↔ container `/data/media/<cat>`. Downloads: host `/mnt/library/downloads/<torrents|usenet>/<cat>` ↔ container `/data/torrents/<cat>` and `/data/usenet/<cat>`.
## Auth (reverse-proxy + Authentik forward-auth)
### qBit
Auto-login behind forward-auth via IP whitelist. `qBittorrent.conf` lines:
- `WebUI\\AuthSubnetWhitelist=172.18.0.0/16, 172.17.0.0/16, 192.168.8.175/32`
- `WebUI\\ReverseProxySupportEnabled=true`
- `WebUI\\TrustedReverseProxiesList=192.168.8.175, 172.18.0.0/16`
> **Stop the container before editing `qBittorrent.conf`.** qBit writes its in-memory config on graceful shutdown and clobbers any live edits. Recipe: `docker stop qbittorrent && sed -i ... && docker start qbittorrent`.
Mobile/desktop clients keep working via `/api/v2/*` path bypass on Caddy.
### SABnzbd
Gated with Authentik forward-auth (applied 2026-06-13). Caddy `sab.hubris.network` block uses `import authentik` inside a `handle` block. SABnzbd's own web auth is disabled:
- `html_login = 0` → no HTML login form
- `username` / `password` cleared → CherryPy Basic Auth not activated
- `local_ranges = 172.18.0.0/16, 192.168.8.0/24, 127.0.0.0/8` → proxied requests from Caddy (192.168.8.x) and Docker-proxy (172.18.x) pass without auth
**API key** (`67ef5a45e4e04157994e977005a33878`) still works for internal service-to-service calls (Sonarr/Radarr/Lidarr via Docker internal networking — they talk to SAB at `localhost:8082`, not through Caddy).
`host_whitelist`: `sabnzbd, localhost, 127.0.0.1, 192.168.8.132, sab.hubris.network` — extend before accessing SAB from a new host.
## Credentials
Backups of all migration-time secrets at `/mnt/library/backup/arr-migration-2026-04-21/state/`:
- `lxc100-apikey-{sonarr,radarr,prowlarr,lidarr}.txt`
- `qbit-creds.txt`, `sab-creds.txt`
- `lxc100-jellyseerr-settings.json`
\*arr API keys were preserved from the yunohost DB — don't rotate unless needed.
qBittorrent: `admin` / `CDyjxMKvjfGupCKzXFkjsKw4nRrXMRL`.
## Migration gotchas (apply if ever re-importing)
- Imported \*arr `config.xml`: must have `<BindAddress>*</BindAddress>`, `<Port>{ezarr-port}</Port>`, `<UrlBase></UrlBase>`. Otherwise app listens only on 127.0.0.1, wrong port, wrong path.
- Imported DB paths must be rewritten `/mnt/library/``/data/media/` (RootFolders / Series / Movies / Artists tables). Without this, scans and imports fail silently.
- Bazarr `config/config.yaml` (yaml, not ini) — `ip: 0.0.0.0`, `port: 6767`, empty top-level `base_url`; sonarr/radarr connection sections normalized to docker service names.
- Staging dir for migrations: `/mnt/library/backup/stage/<app>/` (visible to both old LXC 100 and arriman because both bind-mount the same host path).
## Permissions
Member of [media GID 10000](../infrastructure/media-permissions.md). The LXC has `www-data, audiobookshelf, radarr, sonarr, lidarr, prowlarr, qbittorrent, bazarr, jellyseerr, mylar, jackett, overseerr, plex, arr` in the `media` group. Each docker container also receives GID 10000 as its primary group via `MEDIACENTER_GID=10000`.
## Rollback material (kept until 2026-04-28+)
`/mnt/library/backup/arr-migration-2026-04-21/`:
- `tars/` — per-subtree tars of yunohost app data (granular restore)
- `snapshots/` — vzdumps of LXC 100 (pre-migration) and LXC 122 (pre-import), plus post-stop vzdumps of LXC 100 + 106
- `stage/<app>-preimport.tar.gz` — original empty ezarr `/config/<app>-config/` dirs
- `state/` — service dumps, listening ports, API keys, jellyseerr settings at migration time
- `DESTROY_AFTER_2026-04-28.md` — destroy checklist + post-destroy restore commands
## Related
- [Caddy (121)](121-caddy.md)
- [Authentik (124)](124-authentik.md) — forward-auth wiring + per-app `/api/*` bypass
- [DNS](../infrastructure/dns.md)
- [Media permissions](../infrastructure/media-permissions.md)
- [Hubris host](../hosts/hubris.md)
## Changelog
### 2026-06-13 — SABnzbd gated with Authentik forward-auth
SABnzbd now uses Authentik forward-auth (same `import authentik` Caddy pattern as qBit). SABnzbd's own web auth disabled: `html_login=0`, credentials cleared, `local_ranges` extended to cover Docker bridge + homelab LAN. API key still works for internal *arr service calls. See [Auth section](#auth-reverse-proxy--authentik-forward-auth) above.
### 2026-06-04 — all arr services moved behind gluetun VPN; SAB port conflict fixed
- All services (sonarr, radarr, lidarr, bazarr, prowlarr, jellyseerr, homarr,
flaresolverr) now use `network_mode: service:gluetun` — whole stack routes
through the VPN
- Port mappings moved from individual services to gluetun container
- **Fixed SABnzbd port conflict**: was crashing in a restart loop because
qBittorrent held port 8080 inside the shared gluetun namespace. Changed
SAB internal port to 8082 (config at `/config/sabnzbd-config/sabnzbd.ini`)
- Caddy `sab.hubris.network` updated to point to `:8082`
- Jellyseerr's `extra_hosts` (auth.hubris.network) moved to gluetun since
`extra_hosts` conflicts with `network_mode`
### 2026-06-02 — ProtonVPN added (gluetun); LXC IP set static
- Added `gluetun` container to compose as a WireGuard VPN sidecar (ProtonVPN, server AL#57, located in Tirana, Albania)
- **qbittorrent** and **sabnzbd** now use `network_mode: service:gluetun` — all traffic routes through the VPN
- Ports 8080 (qBit WebUI), 6881 tcp/udp (qBit BT), 8081 (SAB WebUI) exposed through gluetun
- gluetun config at `gluetun-config/wireguard/wg0.conf` (read-only mount)
- Healthcheck on gluetun; qBit/SAB wait for `service_healthy` before starting
- LXC IP changed from DHCP to static (`192.168.8.132`) via `pct set` + `/etc/network/interfaces`
- **After first start:** Sonarr/Radarr/Lidarr download client host needs updating from `qbittorrent``gluetun` (SAB similarly `sabnzbd``gluetun`)
- **Also fixed:** 7 other DHCP LXCs (101 jellyfin, 103 paperless, 104 gitea, 105 apps, 114 nextcloud, 118 elementsynapse, 120 mule-images, 121 caddy) set to static IPs to prevent floating on reboot. See infrastructure/dns.md.
### 2026-04-28 — wiki entry created
Initial documentation.
### 2026-04-26 — `MEDIACENTER_GID` flipped 13000 → 10000
qBit was erroring every torrent with "Permission denied" because s6-setuidgid only honors the primary PGID and `group_add:` doesn't propagate. Setting the primary GID to 10000 (the host `media` group) fixed it.
### 2026-04-21 — \*arr stack migrated yunohost → docker-compose
Old LXC 100 stopped; ezarr stack stood up here; data and DBs imported with path rewrites. flaresolverr folded in from old LXC 106. Three Caddy sites added (jellyseerr, qbit, sab) → see [caddy](121-caddy.md). Rollback artifacts under `/mnt/library/backup/arr-migration-2026-04-21/`.