diff --git a/jellyfin/qbittorrent/docker-compose.yml b/jellyfin/qbittorrent/docker-compose.yml index e69de29..a307503 100644 --- a/jellyfin/qbittorrent/docker-compose.yml +++ b/jellyfin/qbittorrent/docker-compose.yml @@ -0,0 +1,84 @@ +services: + gluetun: + image: qmcgaw/gluetun:latest + container_name: gluetun + cap_add: + - NET_ADMIN + devices: + - /dev/net/tun:/dev/net/tun + environment: + - VPN_SERVICE_PROVIDER=protonvpn + - VPN_TYPE=openvpn + - OPENVPN_USER=${OPENVPN_USER} + - OPENVPN_PASSWORD=${OPENVPN_PASSWORD} + - FIREWALL_OUTBOUND_SUBNETS=192.168.88.0/24 + - FIREWALL_INPUT_PORTS=8080 + - TZ=America/Chicago + ports: + - 8080:8080 + - 6881:6881 + - 6881:6881/udp + volumes: + - /srv/docker/gluetun:/gluetun + restart: unless-stopped + healthcheck: + test: ["CMD", "sh", "-c", "wget -qO- https://ipinfo.io/ip || exit 1"] + interval: 60s + timeout: 20s + retries: 3 + start_period: 30s + labels: + - "com.centurylinklabs.watchtower.monitor-only=true" + + qbittorrent: + image: lscr.io/linuxserver/qbittorrent:latest + container_name: qbittorrent + network_mode: "service:gluetun" + depends_on: + gluetun: + condition: service_healthy + environment: + - PUID=1000 + - PGID=1000 + - TZ=America/Chicago + - WEBUI_PORT=8080 + volumes: + - /srv/docker/qbittorrent/config:/config + - /mnt/media:/mnt/media + restart: unless-stopped + labels: + - "com.centurylinklabs.watchtower.monitor-only=true" + +# NOTE (2026-08-07): Replaces the native qbittorrent-nox systemd service on the +# jellyfin host, which was bound to a desktop ProtonVPN app's tunnel interface +# (proton0). When that GUI-managed tunnel dropped, there was no way to reconnect +# without an interactive desktop session, silently killing all downloads. +# +# qbittorrent now runs with network_mode: service:gluetun, so it shares gluetun's +# network namespace entirely. If the VPN drops, qbittorrent has zero network +# access (fails closed, never falls back to the real IP) until gluetun +# auto-reconnects on its own -- no GUI or manual intervention needed. +# +# OPENVPN_USER / OPENVPN_PASSWORD are the ProtonVPN OpenVPN/IKEv2 credentials +# (see API Codes.md), NOT the regular Proton account login. Set them in a .env +# file alongside this compose file (not committed) or export them before +# `docker compose up -d`. +# +# qbittorrent's media volume is mounted at /mnt/media (matching the original +# native install), NOT /data, so it lines up with Sonarr/Radarr's existing +# Remote Path Mapping (host 192.168.88.10: /mnt/media/ -> /data/) and the +# pre-existing categories (tv-sonarr, movies-radarr) needed zero path rewriting +# when migrating resume data. +# +# NOTE (2026-09-03): Added com.centurylinklabs.watchtower.monitor-only=true to +# both services. A Watchtower instance was newly deployed on the jellyfin host +# on 2026-09-01 (previously only siklos had one), and its first successful +# gluetun update on 2026-09-02 broke qbittorrent: Watchtower recreates the +# dependent container (qbittorrent) BEFORE recreating the linked target +# (gluetun), so qbittorrent got wired to gluetun's old, about-to-be-removed +# container ID and lost all network access until manually fixed. monitor-only +# keeps the nightly email notification (so Joe still hears about new images) +# but stops Watchtower from auto-restarting this pair. Update manually with +# `cd /srv/docker/qbittorrent && docker compose pull && docker compose up -d` +# (compose handles the gluetun-then-qbittorrent order correctly; watchtower +# does not).