# qBittorrent Dedupe & Hardlink Manager
Script Bash intelligent pour **qBittorrent** qui analyse automatiquement les torrents, détecte leurs liens avec les bibliothèques **Radarr/Sonarr**, gère le **cross-seed** et applique une classification par **tags** directement dans l'interface qBittorrent.
## Fonctionnalites
- **Multi-instance** : gere simultanement plusieurs instances qBittorrent (ex: VPN + Direct)
- **Detection automatique des liens** : repere les torrents deja presents dans les bibliotheques Arr via les inodes (hardlinks)
- **Cross-seed** : identifie les torrents lies a un meme fichier media via plusieurs sources
- **Classification par tags** : `linked`, `cross-linked`, `partial`, `no-media`, `orphan`, `a effacer`
- **Reparation automatique** (optionnel) : cree des hardlinks manuels pour les torrents orphelins quand un fichier identique existe dans les medias
- **Duree de seed minimale par tracker** : verifie si la duree de seed requise est atteinte avant de marquer un torrent a supprimer
- **Orphelins de disque** : scanne les repertoires medias pour detecter les fichiers qui ne sont ni geres par les Arr, ni lies a un torrent
- **Caches resilients** : systeme de cache disque pour les inodes, hashes et statuts, avec reprise sur interruption (Ctrl+C)
## Prerequis
- **Bash** >= 4.0 (tableaux associatifs)
- **curl** — API qBittorrent / Radarr / Sonarr
- **python3** — parsing JSON
- **xxhash** ou **md5sum** — comparaison de contenu pour la reparation
- Acces reseau aux instances qBittorrent et aux APIs Arr
## Installation
```bash
# 1. Creer le dossier de travail
mkdir -p ~/qb-dedupe && cd ~/qb-dedupe
# 2. Creer le dossier de configuration
mkdir -p cleanup
# 3. Copier le script
chmod +x check_hardlinks.sh
# 4. Editer la configuration
nano cleanup/cleanup.conf# Liste des instances a surveiller
INSTANCES=("VPN" "DIRECT")
# Instance VPN (ex: container derriere un VPN)
QBIT_VPN_URL="http://192.168.1.50:8080"
QBIT_VPN_USER="admin"
QBIT_VPN_PASS="votre_mot_de_passe"
# Instance Direct (ex: container direct)
QBIT_DIRECT_URL="http://192.168.1.51:8080"
QBIT_DIRECT_USER="admin"
QBIT_DIRECT_PASS="votre_mot_de_passe"Chemins sur l'hote (pas les chemins Docker internes) :
MEDIA_DIRS=(
"/mnt/tank/medias/films"
"/mnt/tank/medias/series"
)Si qBittorrent tourne dans un container et voit /data/completed alors que l'hote voit /mnt/tank/torrents :
declare -A PATH_MAP
PATH_MAP["/data/completed"]="/mnt/tank/torrents"
PATH_MAP["/downloads"]="/mnt/tank/downloads"Syntaxe obligatoire : cles composites
INSTANCE|apppour eviter l'ecrasement des valeurs dans le tableau associatif bash.
# INCORRECT (la derniere ligne ecrase la premiere)
# ARR_CONFIG["VPN"]="radarr|..."
# ARR_CONFIG["VPN"]="sonarr|..."
# CORRECT
declare -A ARR_CONFIG
ARR_CONFIG["VPN|radarr"]="https://radarr.example.com|VOTRE_CLE_API"
ARR_CONFIG["VPN|sonarr"]="https://sonarr.example.com|VOTRE_CLE_API"
ARR_CONFIG["DIRECT|radarr"]="https://radarr.example.com|VOTRE_CLE_API"
ARR_CONFIG["DIRECT|sonarr"]="https://sonarr.example.com|VOTRE_CLE_API"Les noms doivent correspondre exactement aux tags crees dans l'UI qBittorrent.
TAG_LINKED="linked"
TAG_CROSS_LINKED="cross-linked"
TAG_PARTIAL="partial"
TAG_NO_MEDIA="no-media"
TAG_ORPHAN="orphan"
TAG_DELETE="a effacer"
# Tags a supprimer AVANT d'appliquer les nouveaux (nettoyage)
DELETE_TAGS=("linked" "cross-linked" "partial" "no-media" "orphan" "a effacer")| Variable | Description | Defaut |
|---|---|---|
AUTO_REPAIR |
Active la Phase 5 (hardlinks manuels) | false |
ARR_CACHE_DURATION |
Duree de vie du cache Arr (secondes). 0 = desactive |
3600 |
SCAN_DISK_ORPHANS |
Active le scan des fichiers non references | false |
DISK_ORPHAN_LOG |
Chemin du rapport des orphelins de disque | disk_orphans.log |
DISK_ORPHAN_MIN_SIZE |
Taille minimale (octets) pour les orphelins de disque | 0 |
CHOWN_FILES |
Applique un chown sur les nouveaux hardlinks | false |
CHOWN_USER |
Utilisateur:groupe pour le chown | mich:users |
Le script s'execute en 6 phases :
Connexion aux instances qBittorrent via l'API et recuperation de la liste des torrents.
Interrogation des APIs Radarr (/api/v3/movie) et Sonarr (/api/v3/series puis /api/v3/episodefile?seriesId=).
Les fichiers connus sont indexes par inode (identifiant filesystem) pour une comparaison instantanee, sans dependre des noms de fichiers.
Si un torrent a deja ete traite et qu'il est dans le cache, on l'identifie directement comme linked.
Pour chaque fichier de chaque torrent, on determine sa position :
- Dans les repertoires medias (
MEDIA_DIRS) - Dans le dossier cross-seed (
CROSS_SEED_DIR) - Ailleurs (inconnu)
Chaque torrent recoit un tag selon la situation de ses fichiers :
| Tag | Condition |
|---|---|
linked |
Tous les fichiers sont dans les medias, via un inode connu des Arr |
cross-linked |
Tous les fichiers sont dans les medias, mais un au moins est aussi dans CROSS_SEED_DIR |
orphan |
Aucun fichier n'est lie aux medias (hardlink manquant ou torrent orphelin) |
Phase 4.5 — pour les torrents orphan, on verifie la duree de seed minimale par tracker. Si elle est atteinte, le tag devient a effacer. Sinon, il reste orphan.
La duree minimale par tracker est demandee une seule fois interactivement, puis stockee dans cleanup/tracker_secrets.conf.
Si AUTO_REPAIR=true, le script tente de creer un hardlink manuel pour chaque fichier orphelin :
- Recherche d'un candidat de meme taille dans les medias
- Filtre par nom similaire (normalisation des stopwords)
- Comparaison par hash (xxhash/md5) pour garantir l'identite binaire
- Si match ->
rmdu fichier orphelin +lnvers le fichier media - Retagage en
linked
Scan des repertoires MEDIA_DIRS pour lister les fichiers qui ne sont :
- Ni references par Radarr/Sonarr
- Ni lies a un torrent via hardlink
Rapport genere dans DISK_ORPHAN_LOG.
./check_hardlinks.shExecution toutes les 6 heures :
0 */6 * * * /home/user/qb-dedupe/check_hardlinks.sh >> /home/user/qb-dedupe/lastrun.log 2>&1Le script est resilient : si vous l'interrompez (Ctrl+C), les caches sont sauvegardes immediatement.
- Lancement initial — tous les torrents se retrouvent tagues.
- Filtrage UI — dans qBittorrent, filtrer par
orphan. - Reparation — si
AUTO_REPAIR=true, les orphelins reparables deviennentlinked. - Revue — examiner les torrents restant
orphan(non reparables). - Suppression — les torrents
a effaceront depasse la duree de seed requise. Vous pouvez les supprimer en toute securite.
| Fichier | Description |
|---|---|
cleanup/hash_cache.txt |
Cache des hashes de fichiers (accelere la reparation) |
cleanup/inode_status.txt |
Position des inodes (media / cross / inconnu) |
cleanup/torrent_status.txt |
Dernier statut connu de chaque torrent |
cleanup/arr_inodes.txt |
Cache des inodes connus des Arr |
cleanup/tracker_secrets.conf |
Durees minimales de seed par tracker (chmod 600) |
cleanup/disk_orphans.log |
Liste des fichiers medias non references |
Normal, le script bascule automatiquement sur le fallback series -> episodefile?seriesId=. Si le message persiste, verifiez votre cle API et l'URL Sonarr.
Verifiez imperativement la syntaxe des cles composites :
# Mauvais
ARR_CONFIG["VPN"]="radarr|..."
ARR_CONFIG["VPN"]="sonarr|..." # ecrase la ligne precedente
# Correct
ARR_CONFIG["VPN|radarr"]="url|key"
ARR_CONFIG["VPN|sonarr"]="url|key"Supprimez les fichiers dans cleanup/ pour forcer une reinterrogation complete :
rm -f cleanup/*.txtLe hardlink (naturel ou repare) necessite que les torrents et les medias soient sur le meme volume physique (meme device). Verifiez avec df -h que les chemins partagent le meme point de montage.
MIT — Utilisation et modification libre. Aucune garantie fournie.