4.4 KiB
4.4 KiB
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 wg0dans 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 viavps.internal:8080(host-gateway). Ingressqb.yesminedor.tn → http://vps.internal:8080dé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 avecServerDomains=qb.yesminedor.tn;localhost. - Downloads :
/opt/qbittorrent-docker/data(chown 1000:1000 — hotio ne prend possession que de /config, pas de /data → sinon torrent enerrormkdir permission denied).
Pièges rencontrés
VPN_LAN_NETWORK=172.16.0.0/12refusé : 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).- 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). - HostHeaderValidation : une fois
ServerDomainsdéfini,Host: localhost:8080est rejeté même aveclocalhostdans la liste (le test local doit envoyer-H "Host: qb.yesminedor.tn"). - É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)". - 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.
- 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 CONNMARK0xff0000dans 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@wg0système : stop, disable,/etc/wireguard/wg0.confshreddé (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.internaldepuis conteneurs MCP). deluser qbtuser+rm -rf /home/qbtuser→ 49 Go libérés (disque 28% → 3%).
Vérifs de routine
# 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).