421 lines
19 KiB
Bash
421 lines
19 KiB
Bash
#!/bin/bash
|
|
# ==============================================================================================
|
|
# downloaders_reset.sh
|
|
# ==============================================================================================
|
|
# PURPOSE: Daily maintenance reset for all download clients.
|
|
# Clears stuck states, purges old history, and prepares downloaders for a
|
|
# clean daily cycle. Run before container restarts in daily maintenance.
|
|
#
|
|
# DOWNLOADERS COVERED:
|
|
# slskd — clears stuck/errored searches, dead transfer records,
|
|
# purges expired failed imports
|
|
# SABnzbd — clears completed and failed history older than retention period,
|
|
# removes stalled/paused queue items
|
|
# qBittorrent — last chance failsafe delete for torrents older than
|
|
# QBIT_FAILSAFE_MIN_DAYS regardless of ratio
|
|
#
|
|
# USAGE:
|
|
# bash downloaders_reset.sh # live run
|
|
# bash downloaders_reset.sh --dry-run # preview only, no changes made
|
|
#
|
|
# SCHEDULE: Run before downloader restarts in daily_sync_maintenance.sh
|
|
# Ensures all downloaders start each day in a clean known state
|
|
#
|
|
# SAFETY:
|
|
# Always --dry-run first before scheduling
|
|
# slskd transfer cleanup skips any user with InProgress or Queued transfers
|
|
# qBittorrent only deletes if torrent age exceeds QBIT_FAILSAFE_MIN_DAYS
|
|
# SABnzbd only deletes history older than RETENTION_DAYS
|
|
#
|
|
# TO ADD A NEW DOWNLOADER:
|
|
# Add URL, API key, and any specific variables in Configuration section
|
|
# Add a new section block following the same pattern as existing downloaders
|
|
# ==============================================================================================
|
|
|
|
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
# ━━━ Configuration ━━━
|
|
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
|
|
# Retention period in days — applies to ALL cleanup sections:
|
|
# slskd failed imports, SABnzbd completed history, SABnzbd failed history
|
|
RETENTION_DAYS=7
|
|
|
|
# ━━━ slskd ━━━
|
|
SLSKD_URL="http://localhost:8980"
|
|
SLSKD_API_KEY="4bF9kL2mNpQrT7vWxYz1A3dEgHjKoRsU"
|
|
|
|
# Path to Soularr failed imports folder
|
|
# Created by Soularr when Lidarr rejects a downloaded album
|
|
SLSKD_FAILED_IMPORTS_DIR="/mnt/user/Temp_Storage/Slskd/completed/failed_imports"
|
|
|
|
# ━━━ SABnzbd ━━━
|
|
SABNZBD_URL="http://localhost:8180"
|
|
SABNZBD_API_KEY="8bfefe41d83b4d50883e32859b55ca9a"
|
|
|
|
# ━━━ qBittorrent ━━━
|
|
QBIT_URL="http://localhost:8080"
|
|
QBIT_USERNAME="admin"
|
|
QBIT_PASSWORD="changeme"
|
|
|
|
# Last chance failsafe — deletes torrents older than this many days.
|
|
# qBittorrent's own rules handle normal cleanup (ratio >= 1.25 OR 45 days
|
|
# inactive). This catches anything missed after an extended period.
|
|
# QBIT_FAILSAFE_MIN_RATIO=0 disables ratio gate — age is the only condition.
|
|
QBIT_FAILSAFE_MIN_DAYS=180
|
|
QBIT_FAILSAFE_MIN_RATIO=0
|
|
|
|
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
|
|
DRY_RUN=false
|
|
[[ "$1" == "--dry-run" ]] && DRY_RUN=true
|
|
|
|
$DRY_RUN && echo "🧪 DRY RUN MODE — no changes will be made"
|
|
echo ""
|
|
|
|
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
# ━━━ slskd — Clear Stuck Searches ━━━
|
|
# Clears searches in Completed/Errored state left behind by Soularr crashes.
|
|
# Prevents 409 Conflict error on next Soularr startup.
|
|
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
echo "🔍 slskd — Stuck Searches"
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
|
|
SEARCHES=$(curl -s -X GET "$SLSKD_URL/api/v0/searches" \
|
|
-H "X-Api-Key: $SLSKD_API_KEY")
|
|
|
|
IDS=$(echo "$SEARCHES" | grep -o '"id":"[^"]*","isComplete":true' | \
|
|
grep -o '"id":"[^"]*"' | sed 's/"id":"//;s/"//')
|
|
COUNT=$(echo "$IDS" | grep -c . 2>/dev/null || echo 0)
|
|
|
|
if [[ -z "$IDS" || "$COUNT" -eq 0 ]]; then
|
|
echo "✅ No stuck searches found"
|
|
else
|
|
echo "📋 Found $COUNT stuck search(es)"
|
|
if $DRY_RUN; then
|
|
echo "🧪 Would delete search IDs:"
|
|
echo "$IDS"
|
|
else
|
|
SUCCESS=0
|
|
FAIL=0
|
|
while IFS= read -r ID; do
|
|
[[ -z "$ID" ]] && continue
|
|
RESULT=$(curl -s -o /dev/null -w "%{http_code}" -X DELETE \
|
|
"$SLSKD_URL/api/v0/searches/$ID" \
|
|
-H "X-Api-Key: $SLSKD_API_KEY")
|
|
if [[ "$RESULT" == "200" || "$RESULT" == "204" ]]; then
|
|
echo " 🗑️ Cleared search: $ID"
|
|
((SUCCESS++))
|
|
else
|
|
echo " ❌ Failed: $ID (HTTP $RESULT)"
|
|
((FAIL++))
|
|
fi
|
|
done <<< "$IDS"
|
|
echo "✅ Searches: $SUCCESS cleared, $FAIL failed"
|
|
fi
|
|
fi
|
|
|
|
echo ""
|
|
|
|
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
# ━━━ slskd — Clear Dead Transfer Records ━━━
|
|
# Removes completed/errored/aborted transfer records per user.
|
|
# Prevents Soularr 404 loop when polling a user whose transfer
|
|
# record no longer exists after a crash or disconnect.
|
|
# NEVER removes transfers that are InProgress or Queued.
|
|
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
echo "🔍 slskd — Dead Transfer Records"
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
|
|
TRANSFERS=$(curl -s -X GET "$SLSKD_URL/api/v0/transfers/downloads" \
|
|
-H "X-Api-Key: $SLSKD_API_KEY")
|
|
|
|
USERNAMES=$(echo "$TRANSFERS" | grep -o '"username":"[^"]*"' | \
|
|
sed 's/"username":"//;s/"//' | sort -u)
|
|
|
|
if [[ -z "$USERNAMES" ]]; then
|
|
echo "✅ No transfer records found"
|
|
else
|
|
SUCCESS=0
|
|
SKIPPED=0
|
|
FAIL=0
|
|
|
|
while IFS= read -r USER; do
|
|
[[ -z "$USER" ]] && continue
|
|
|
|
USER_STATES=$(echo "$TRANSFERS" | grep -o \
|
|
"\"username\":\"$USER\"[^}]*\"state\":\"[^\"]*\"" | \
|
|
grep -o '"state":"[^"]*"')
|
|
|
|
ACTIVE=$(echo "$USER_STATES" | grep -c "InProgress\|Queued")
|
|
|
|
if [[ "$ACTIVE" -gt 0 ]]; then
|
|
echo " ⏳ Skipping $USER — $ACTIVE active/queued transfer(s)"
|
|
((SKIPPED++))
|
|
continue
|
|
fi
|
|
|
|
if $DRY_RUN; then
|
|
echo "🧪 Would clear transfers for: $USER"
|
|
((SUCCESS++))
|
|
continue
|
|
fi
|
|
|
|
RESULT=$(curl -s -o /dev/null -w "%{http_code}" -X DELETE \
|
|
"$SLSKD_URL/api/v0/transfers/downloads/$USER" \
|
|
-H "X-Api-Key: $SLSKD_API_KEY")
|
|
|
|
if [[ "$RESULT" == "200" || "$RESULT" == "204" ]]; then
|
|
echo " 🗑️ Cleared transfers for: $USER"
|
|
((SUCCESS++))
|
|
else
|
|
echo " ❌ Failed to clear: $USER (HTTP $RESULT)"
|
|
((FAIL++))
|
|
fi
|
|
done <<< "$USERNAMES"
|
|
|
|
echo "✅ Transfers: $SUCCESS cleared, $SKIPPED skipped (active), $FAIL failed"
|
|
fi
|
|
|
|
echo ""
|
|
|
|
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
# ━━━ slskd — Purge Expired Failed Imports ━━━
|
|
# Removes albums Soularr downloaded but Lidarr rejected.
|
|
# Soularr moves these to failed_imports/ and never cleans them up.
|
|
# Retention controlled by RETENTION_DAYS variable.
|
|
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
echo "🔍 slskd — Failed Imports (older than ${RETENTION_DAYS} days)"
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
|
|
if [[ ! -d "$SLSKD_FAILED_IMPORTS_DIR" ]]; then
|
|
echo "⚠️ Directory not found: $SLSKD_FAILED_IMPORTS_DIR"
|
|
else
|
|
OLD_IMPORTS=$(find "$SLSKD_FAILED_IMPORTS_DIR" \
|
|
-mindepth 1 -maxdepth 1 -mtime +${RETENTION_DAYS})
|
|
IMPORT_COUNT=$(echo "$OLD_IMPORTS" | grep -c . 2>/dev/null || echo 0)
|
|
|
|
if [[ -z "$OLD_IMPORTS" || "$IMPORT_COUNT" -eq 0 ]]; then
|
|
echo "✅ No expired failed imports found"
|
|
else
|
|
echo "📋 Found $IMPORT_COUNT expired failed import(s)"
|
|
if $DRY_RUN; then
|
|
echo "🧪 Would delete:"
|
|
echo "$OLD_IMPORTS"
|
|
else
|
|
find "$SLSKD_FAILED_IMPORTS_DIR" \
|
|
-mindepth 1 -maxdepth 1 -mtime +${RETENTION_DAYS} \
|
|
-exec rm -rf {} \;
|
|
echo "🗑️ Purged $IMPORT_COUNT expired failed import(s)"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
echo ""
|
|
|
|
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
# ━━━ SABnzbd — Clear Completed History ━━━
|
|
# Removes completed download history older than RETENTION_DAYS.
|
|
# Keeps history clean without losing recent job records.
|
|
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
echo "🔍 SABnzbd — Completed History (older than ${RETENTION_DAYS} days)"
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
|
|
CUTOFF=$(( $(date +%s) - (RETENTION_DAYS * 86400) ))
|
|
|
|
HISTORY=$(curl -s \
|
|
"$SABNZBD_URL/api?mode=history&output=json&limit=1000&apikey=$SABNZBD_API_KEY")
|
|
|
|
COMPLETED_IDS=$(echo "$HISTORY" | grep -o '"nzo_id":"[^"]*"' | \
|
|
sed 's/"nzo_id":"//;s/"//')
|
|
|
|
if [[ -z "$COMPLETED_IDS" ]]; then
|
|
echo "✅ No completed history found"
|
|
else
|
|
DELETED=0
|
|
SKIPPED=0
|
|
while IFS= read -r NZO_ID; do
|
|
[[ -z "$NZO_ID" ]] && continue
|
|
JOB_TIME=$(echo "$HISTORY" | grep -A5 "$NZO_ID" | \
|
|
grep -o '"completed":[0-9]*' | grep -o '[0-9]*')
|
|
[[ -z "$JOB_TIME" ]] && continue
|
|
[[ "$JOB_TIME" -gt "$CUTOFF" ]] && ((SKIPPED++)) && continue
|
|
if $DRY_RUN; then
|
|
echo "🧪 Would delete completed job: $NZO_ID"
|
|
((DELETED++))
|
|
else
|
|
curl -s \
|
|
"$SABNZBD_URL/api?mode=history&name=delete&value=$NZO_ID&apikey=$SABNZBD_API_KEY" \
|
|
> /dev/null
|
|
echo " 🗑️ Deleted: $NZO_ID"
|
|
((DELETED++))
|
|
fi
|
|
done <<< "$COMPLETED_IDS"
|
|
echo "✅ Completed: $DELETED deleted, $SKIPPED within retention period"
|
|
fi
|
|
|
|
echo ""
|
|
|
|
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
# ━━━ SABnzbd — Clear Failed History ━━━
|
|
# Removes failed download history older than RETENTION_DAYS.
|
|
# Failed jobs older than retention period serve no purpose.
|
|
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
echo "🔍 SABnzbd — Failed History (older than ${RETENTION_DAYS} days)"
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
|
|
FAILED=$(curl -s \
|
|
"$SABNZBD_URL/api?mode=history&output=json&limit=1000&failed_only=1&apikey=$SABNZBD_API_KEY")
|
|
|
|
FAILED_IDS=$(echo "$FAILED" | grep -o '"nzo_id":"[^"]*"' | \
|
|
sed 's/"nzo_id":"//;s/"//')
|
|
|
|
if [[ -z "$FAILED_IDS" ]]; then
|
|
echo "✅ No failed history found"
|
|
else
|
|
DELETED=0
|
|
SKIPPED=0
|
|
while IFS= read -r NZO_ID; do
|
|
[[ -z "$NZO_ID" ]] && continue
|
|
JOB_TIME=$(echo "$FAILED" | grep -A5 "$NZO_ID" | \
|
|
grep -o '"completed":[0-9]*' | grep -o '[0-9]*')
|
|
[[ -z "$JOB_TIME" ]] && continue
|
|
[[ "$JOB_TIME" -gt "$CUTOFF" ]] && ((SKIPPED++)) && continue
|
|
if $DRY_RUN; then
|
|
echo "🧪 Would delete failed job: $NZO_ID"
|
|
((DELETED++))
|
|
else
|
|
curl -s \
|
|
"$SABNZBD_URL/api?mode=history&name=delete&value=$NZO_ID&apikey=$SABNZBD_API_KEY" \
|
|
> /dev/null
|
|
echo " 🗑️ Deleted: $NZO_ID"
|
|
((DELETED++))
|
|
fi
|
|
done <<< "$FAILED_IDS"
|
|
echo "✅ Failed: $DELETED deleted, $SKIPPED within retention period"
|
|
fi
|
|
|
|
echo ""
|
|
|
|
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
# ━━━ SABnzbd — Remove Stalled Queue Items ━━━
|
|
# Removes paused queue items that are no longer progressing.
|
|
# Active downloading items are never touched.
|
|
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
echo "🔍 SABnzbd — Stalled Queue Items"
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
|
|
QUEUE=$(curl -s \
|
|
"$SABNZBD_URL/api?mode=queue&output=json&apikey=$SABNZBD_API_KEY")
|
|
|
|
STALLED_IDS=$(echo "$QUEUE" | grep -o '"nzo_id":"[^"]*"' | \
|
|
sed 's/"nzo_id":"//;s/"//')
|
|
|
|
if [[ -z "$STALLED_IDS" ]]; then
|
|
echo "✅ No stalled queue items found"
|
|
else
|
|
DELETED=0
|
|
SKIPPED=0
|
|
while IFS= read -r NZO_ID; do
|
|
[[ -z "$NZO_ID" ]] && continue
|
|
STATUS=$(echo "$QUEUE" | grep -A10 "$NZO_ID" | \
|
|
grep -o '"status":"[^"]*"' | sed 's/"status":"//;s/"//')
|
|
[[ "$STATUS" != "Paused" ]] && ((SKIPPED++)) && continue
|
|
if $DRY_RUN; then
|
|
echo "🧪 Would remove stalled item: $NZO_ID"
|
|
((DELETED++))
|
|
else
|
|
curl -s \
|
|
"$SABNZBD_URL/api?mode=queue&name=delete&value=$NZO_ID&apikey=$SABNZBD_API_KEY" \
|
|
> /dev/null
|
|
echo " 🗑️ Removed: $NZO_ID"
|
|
((DELETED++))
|
|
fi
|
|
done <<< "$STALLED_IDS"
|
|
echo "✅ Queue: $DELETED removed, $SKIPPED active (skipped)"
|
|
fi
|
|
|
|
echo ""
|
|
|
|
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
# ━━━ qBittorrent — Last Chance Failsafe Cleanup ━━━
|
|
# Deletes torrents older than QBIT_FAILSAFE_MIN_DAYS.
|
|
# qBittorrent's own rules handle normal cleanup (ratio >= 1.25 OR 45 days
|
|
# inactive). This catches anything missed after extended time.
|
|
# QBIT_FAILSAFE_MIN_RATIO=0 disables ratio gate — age is the only condition.
|
|
# deleteFiles=false — removes torrent from qBit but leaves files on disk.
|
|
# Radarr/Sonarr manage the actual files independently.
|
|
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
echo "🔍 qBittorrent — Failsafe Cleanup"
|
|
echo " Age > ${QBIT_FAILSAFE_MIN_DAYS} days"
|
|
[[ "$QBIT_FAILSAFE_MIN_RATIO" != "0" ]] && \
|
|
echo " Ratio >= ${QBIT_FAILSAFE_MIN_RATIO}"
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
|
|
QBIT_COOKIE=$(curl -s -c - \
|
|
"$QBIT_URL/api/v2/auth/login" \
|
|
--data "username=$QBIT_USERNAME&password=$QBIT_PASSWORD" | \
|
|
grep SID | awk '{print "SID="$NF}')
|
|
|
|
if [[ -z "$QBIT_COOKIE" ]]; then
|
|
echo "❌ Failed to authenticate with qBittorrent — check URL, username, password"
|
|
else
|
|
TORRENTS=$(curl -s \
|
|
"$QBIT_URL/api/v2/torrents/info" \
|
|
-H "Cookie: $QBIT_COOKIE")
|
|
|
|
HASHES=$(echo "$TORRENTS" | grep -o '"hash":"[^"]*"' | \
|
|
sed 's/"hash":"//;s/"//')
|
|
|
|
NOW=$(date +%s)
|
|
DELETED=0
|
|
SKIPPED=0
|
|
|
|
while IFS= read -r HASH; do
|
|
[[ -z "$HASH" ]] && continue
|
|
|
|
ADDED=$(echo "$TORRENTS" | grep -A30 "\"hash\":\"$HASH\"" | \
|
|
grep -o '"added_on":[0-9]*' | grep -o '[0-9]*' | head -1)
|
|
RATIO=$(echo "$TORRENTS" | grep -A30 "\"hash\":\"$HASH\"" | \
|
|
grep -o '"ratio":[0-9.]*' | grep -o '[0-9.]*' | head -1)
|
|
NAME=$(echo "$TORRENTS" | grep -A30 "\"hash\":\"$HASH\"" | \
|
|
grep -o '"name":"[^"]*"' | sed 's/"name":"//;s/"//' | head -1)
|
|
|
|
[[ -z "$ADDED" ]] && continue
|
|
|
|
# Age in days — skip if under threshold
|
|
AGE_DAYS=$(( (NOW - ADDED) / 86400 ))
|
|
[[ "$AGE_DAYS" -lt "$QBIT_FAILSAFE_MIN_DAYS" ]] && ((SKIPPED++)) && continue
|
|
|
|
# Ratio gate — skip if ratio below minimum (disabled when set to 0)
|
|
if [[ "$QBIT_FAILSAFE_MIN_RATIO" != "0" ]]; then
|
|
RATIO_OK=$(echo "$RATIO >= $QBIT_FAILSAFE_MIN_RATIO" | bc -l 2>/dev/null)
|
|
[[ "$RATIO_OK" != "1" ]] && ((SKIPPED++)) && continue
|
|
fi
|
|
|
|
if $DRY_RUN; then
|
|
echo "🧪 Would delete: $NAME (${AGE_DAYS} days old, ratio: $RATIO)"
|
|
((DELETED++))
|
|
else
|
|
curl -s -X POST \
|
|
"$QBIT_URL/api/v2/torrents/delete" \
|
|
-H "Cookie: $QBIT_COOKIE" \
|
|
--data "hashes=$HASH&deleteFiles=false"
|
|
echo " 🗑️ Deleted: $NAME (${AGE_DAYS} days old, ratio: $RATIO)"
|
|
((DELETED++))
|
|
fi
|
|
done <<< "$HASHES"
|
|
|
|
echo "✅ qBittorrent: $DELETED deleted, $SKIPPED skipped"
|
|
fi
|
|
|
|
echo ""
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
echo "✅ Downloaders reset complete"
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" |