45 lines
4.4 KiB
Markdown
45 lines
4.4 KiB
Markdown
# 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).
|