# VPS — qBittorrent Docker + VPN WireGuard intégré (kill-switch) **Date** : 2026-07-12 **Contexte** : remplacement de qbittorrent-nox natif (systemd + policy routing UID vers wg0 KeepSolid) par un conteneur `hotio/qbittorrent` avec VPN interne. L'ancienne archi rendait le WebUI inexposable (routing asymétrique : requêtes entrantes par eth0/tunnel CF, réponses sortantes par wg0). ## Architecture finale - **VPS** : vmi3418972 (Contabo), Tailscale 100.94.90.119, hostname contabo-vps-qbt. - **Conteneur** : `ghcr.io/hotio/qbittorrent:latest` (qBittorrent 5.2.3), compose dans `/opt/qbittorrent-docker/`. - **VPN** : WireGuard KeepSolid **dans le conteneur** (`VPN_ENABLED=true`, `VPN_CONF=wg0`, provider generic). Conf : `/opt/qbittorrent-docker/config/wireguard/wg0.conf` (chmod 600, survit aux recreate via le volume `./config:/config`). Clé = peer régénéré 12/07 (l'ancienne était grillée), stockée dans Vaultwarden org claude. - **Kill-switch** : nftables généré par hotio, policy drop sur input/output. Seuls autorisés : endpoint WireGuard (UDP), WebUI 8080 depuis `VPN_LAN_NETWORK`, wg0, lo. Testé : `ip link del wg0` dans le conteneur → curl et DNS totalement bloqués, zéro fuite. - **WebUI** : port publié `8080:8080` → cloudflared (`cloudflared-mcp-vps`, réseau mcp-vps) le joint via `vps.internal:8080` (host-gateway). Ingress `qb.yesminedor.tn → http://vps.internal:8080` déjà en place côté dashboard Zero Trust (tunnel mcp-vps réutilisé). - **Sécurité WebUI** : user dédié (pas admin), mot de passe fort (Vaultwarden org claude), `LocalHostAuth=true` (PAS de bypass localhost — critique : le trafic CF arrive vu comme IP docker locale), CSRF actif, HostHeaderValidation avec `ServerDomains=qb.yesminedor.tn;localhost`. - **Downloads** : `/opt/qbittorrent-docker/data` (chown **1000:1000** — hotio ne prend possession que de /config, pas de /data → sinon torrent en `error` mkdir permission denied). ## Pièges rencontrés 1. **`VPN_LAN_NETWORK=172.16.0.0/12` refusé** : le bloc englobe le sous-réseau du conteneur lui-même (172.19.0.0/16) → hotio exit 1 « Route conflicts with address on interface eth0 ». Énumérer les sous-réseaux précis : `172.17.0.0/16,172.18.0.0/16` (docker0 + réseau mcp-vps de cloudflared). 2. **qBittorrent 5.x API** : login OK = HTTP **204** (pas 200) + cookie SID. Extraction cookie par sed fragile → utiliser `curl -c cookiejar`. Trop d'échecs login = ban IP temporaire (401 sur tout). 3. **HostHeaderValidation** : une fois `ServerDomains` défini, `Host: localhost:8080` est rejeté même avec `localhost` dans la liste (le test local doit envoyer `-H "Host: qb.yesminedor.tn"`). 4. **Éditer qBittorrent.conf UNIQUEMENT conteneur arrêté** (qbt réécrit le fichier à l'arrêt). Hash mot de passe : PBKDF2-HMAC-SHA512, 100 000 itérations, dklen 64, format `WebUI\Password_PBKDF2="@ByteArray(b64salt:b64hash)"`. 5. **Ne JAMAIS refaire le policy routing UID système** (ip rule uidrange → table dédiée) : c'était la cause du WebUI inexposable. Le VPN dans le conteneur isole tout. 6. **Règles Tailscale à ne pas toucher** lors des nettoyages ip rule/mangle : fwmark `0x80000/0xff0000` (prio 5210/5230/5250), `lookup 52` (5270), et les 2 CONNMARK `0xff0000` dans mangle sont à **Tailscale**, pas au VPN torrent. ## Démolition de l'ancienne instance (faite le 12/07) - `qbittorrent-nox.service` : stop, disable, fichier supprimé, daemon-reload. - `wg-quick@wg0` système : stop, disable, `/etc/wireguard/wg0.conf` shreddé (clé grillée). Vérifié avant : seul uid 998 routait dessus, memory-node passe par Tailscale. - ip rules 5208 (`to 172.16.0.0/12`) et 5209 (uidrange 998) supprimées. - ufw : règles 8080 docker0 + 8080 from 172.16.0.0/12 supprimées. **Conservé** : 22+8080 sur tailscale0, 22+8080 fallback IP Nabil, 22 from 172.16.0.0/12 (SSH `vps.internal` depuis conteneurs MCP). - `deluser qbtuser` + `rm -rf /home/qbtuser` → **49 Go libérés** (disque 28% → 3%). ## Vérifs de routine ```bash # IP de sortie = IP KeepSolid, jamais l'IP Contabo (194.163.159.151) docker exec qbittorrent curl -s ifconfig.me # handshake WG docker exec qbittorrent wg show # test kill-switch (puis docker restart qbittorrent pour rétablir) docker exec qbittorrent ip link del wg0 && docker exec qbittorrent curl -s --max-time 10 ifconfig.me || echo "BLOQUÉ = OK" ``` Persistance : `restart: unless-stopped` + docker.service enabled ; la conf WG et le mot de passe WebUI vivent dans `./config` (volume disque).