From 06c61fddc493607f6c1dbe800988386a28f59b15 Mon Sep 17 00:00:00 2001 From: bolbol Date: Sun, 12 Jul 2026 16:12:23 +0000 Subject: [PATCH] runbook: VPS qBittorrent Docker + VPN WireGuard kill-switch (migration 12/07) --- common/vps-qbittorrent-docker.md | 44 ++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 common/vps-qbittorrent-docker.md diff --git a/common/vps-qbittorrent-docker.md b/common/vps-qbittorrent-docker.md new file mode 100644 index 0000000..f9f687d --- /dev/null +++ b/common/vps-qbittorrent-docker.md @@ -0,0 +1,44 @@ +# 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).