massive update. Master conf split, now modular with a load sceriprt to drive all configs to scripts. with unraid scpecific safeguard tests , and improved standardized ux. including dynamic host detect, who am i who else it there. EVERY SINGLE SCRIPT UPDATED. DEBATING THAT THIS IS ACUALLY V2

This commit is contained in:
2026-05-03 17:16:49 -04:00
parent 2691a35e80
commit ec7de648dc
72 changed files with 25640 additions and 14629 deletions
+157 -92
View File
@@ -1,40 +1,70 @@
#!/bin/bash
# -----------------------------------------------------------------------------------------------
# --------------------------------- Downloaders Reset ------------------------------------------
# -----------------------------------------------------------------------------------------------
# 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_sync_maintenance.sh via MEDIA_MANAGEMENT_JOBS.
# ==============================================================================================
# ================================= Downloaders Reset ==========================================
# ==============================================================================================
# Maintenance reset for all download clients on this server.
# Called every 15 minutes by critical_sync_maintenance.sh via CRITICAL_MAINTENANCE_SCRIPTS.
# Can also be run manually for ad hoc cleanup.
#
# Downloaders covered:
# slskd — clears stuck/errored searches, dead transfer records,
# purges expired failed imports (albums Lidarr rejected)
# 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
# ── DOWNLOADERS COVERED ───────────────────────────────────────────────────────────────────────
# slskd
# Stuck searches — clears Completed/Errored searches left by Soularr crashes
# prevents 409 Conflict on next Soularr startup
# Dead transfers — removes completed/errored/aborted transfer records per user
# prevents Soularr 404 loop when polling a user whose transfer is gone
# NEVER removes InProgress or Queued transfers
# Failed imports — purges albums Soularr downloaded but Lidarr rejected
# Soularr moves these to failed_imports/ and never cleans them up
#
# 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 DOWNLOADER_RETENTION_DAYS
# qBittorrent deleteFiles=false — removes from qBit, leaves files for arrs to manage
# SABnzbd
# Completed history — removes completed download records older than DOWNLOADER_RETENTION_DAYS
# Failed history — removes failed download records older than DOWNLOADER_RETENTION_DAYS
# Stalled queue — removes Paused or Stuck queue items no longer progressing
# active downloading items are never touched
#
# Configuration in Master.conf under Docker Essentials — Downloaders Reset.
# Supports --dry-run to preview without making changes.
# -----------------------------------------------------------------------------------------------
# qBittorrent
# Age failsafe — removes torrents older than QBIT_FAILSAFE_MIN_DAYS
# deleteFiles=false — removes from qBit, leaves files for arrs to manage
# optional ratio requirement via QBIT_FAILSAFE_MIN_RATIO
#
# ── HOST AWARENESS ────────────────────────────────────────────────────────────────────────────
# detect_hosts() sets MY_ID and aliases all HOST*_SLSKD_*, HOST*_SABNZBD_*, HOST*_QBIT_* vars.
# If a downloader URL is empty for this host — that section is skipped with a clear message.
# HOST2 currently has no downloaders configured — all sections skip cleanly on HOST2.
#
# ── SAFEGUARDS ────────────────────────────────────────────────────────────────────────────────
# slskd — skips users with InProgress or Queued transfers — never interrupts active downloads
# SABnzbd — age check before deletion — only removes items past retention threshold
# qBittorrent — age + optional ratio check — failsafe only removes old completed torrents
# All sections — skip gracefully if downloader is unreachable, no fatal exit
# acquire_lock "wait" — if previous run still active, waits briefly then exits cleanly
#
# ── CONFIGURATION (master_host*.conf) ─────────────────────────────────────────────────────────
# HOST*_SLSKD_URL / HOST*_SLSKD_API_KEY / HOST*_SLSKD_FAILED_IMPORTS_DIR
# HOST*_SABNZBD_URL / HOST*_SABNZBD_API_KEY
# HOST*_QBIT_URL / HOST*_QBIT_USERNAME / HOST*_QBIT_PASSWORD
#
# ── CONFIGURATION (master.conf) ───────────────────────────────────────────────────────────────
# DOWNLOADER_RETENTION_DAYS — days before history entries are purged
# QBIT_FAILSAFE_MIN_DAYS — minimum torrent age before failsafe deletion
# QBIT_FAILSAFE_MIN_RATIO — minimum ratio requirement (0 = age only)
#
# ── USAGE ─────────────────────────────────────────────────────────────────────────────────────
# downloaders_reset.sh — normal reset
# downloaders_reset.sh --dry-run — preview without making changes
# downloaders_reset.sh --log — verbose output
# downloaders_reset.sh --status — show config and exit
# ==============================================================================================
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/../Master.conf"
source "$SCRIPT_DIR/../common.sh"
source "$SCRIPT_DIR/../load_config.sh"
parse_args "$@"
# -----------------------------------------------------------------------------------------------
# ━━━ $ICON_GEAR Setup ━━━
# -----------------------------------------------------------------------------------------------
# ==============================================================================================
# ━━━ Setup ━━━
# ==============================================================================================
echo ""
echo "━━━ $ICON_GEAR Downloaders Reset ━━━"
@@ -43,45 +73,54 @@ if [[ "$EUID" -ne 0 ]]; then
exit 1
fi
detect_hosts
# Lock first — wait mode since this runs every 15min and previous may still be finishing
acquire_lock "wait"
# detect_hosts() sets MY_ID and aliases all HOST*_SLSKD_*, HOST*_SABNZBD_*, HOST*_QBIT_* vars
detect_hosts
START_TIME=$(date +%s)
# Select host-specific config
if [[ "$LOCAL_SERVER_NAME" == "$HOST1" ]]; then
SLSKD_URL="$HOST1_SLSKD_URL"
SLSKD_API_KEY="$HOST1_SLSKD_API_KEY"
SLSKD_FAILED_IMPORTS_DIR="$HOST1_SLSKD_FAILED_IMPORTS_DIR"
SABNZBD_URL="$HOST1_SABNZBD_URL"
SABNZBD_API_KEY="$HOST1_SABNZBD_API_KEY"
QBIT_URL="$HOST1_QBIT_URL"
QBIT_USERNAME="$HOST1_QBIT_USERNAME"
QBIT_PASSWORD="$HOST1_QBIT_PASSWORD"
else
# HOST2 placeholders — fill in when HOST2 is back online
SLSKD_URL="${HOST2_SLSKD_URL:-}"
SLSKD_API_KEY="${HOST2_SLSKD_API_KEY:-}"
SLSKD_FAILED_IMPORTS_DIR="${HOST2_SLSKD_FAILED_IMPORTS_DIR:-}"
SABNZBD_URL="${HOST2_SABNZBD_URL:-}"
SABNZBD_API_KEY="${HOST2_SABNZBD_API_KEY:-}"
QBIT_URL="${HOST2_QBIT_URL:-}"
QBIT_USERNAME="${HOST2_QBIT_USERNAME:-}"
QBIT_PASSWORD="${HOST2_QBIT_PASSWORD:-}"
fi
[[ "$DRY_RUN" == true ]] && warn "DRY RUN — no changes will be made"
CUTOFF=$(( $(date +%s) - (DOWNLOADER_RETENTION_DAYS * 86400) ))
TOTAL_PASS=0
TOTAL_FAIL=0
# -----------------------------------------------------------------------------------------------
# ==============================================================================================
# ━━━ Status ━━━
# ==============================================================================================
if [[ "$SHOW_STATUS" == true ]]; then
echo ""
echo "━━━━━ $ICON_SUMMARY STATUS ━━━━━"
echo "$ICON_HOST Identity: $MY_ID ($LOCAL_SERVER_NAME)"
echo "$ICON_GEAR slskd: ${SLSKD_URL:-not configured}"
echo "$ICON_GEAR SABnzbd: ${SABNZBD_URL:-not configured}"
echo "$ICON_GEAR qBittorrent: ${QBIT_URL:-not configured}"
echo "$ICON_TIME Retention: ${DOWNLOADER_RETENTION_DAYS} days"
echo "$ICON_GEAR qBit age: ${QBIT_FAILSAFE_MIN_DAYS} days"
echo "$ICON_GEAR qBit ratio: ${QBIT_FAILSAFE_MIN_RATIO} (0=age only)"
echo "$ICON_NOTIFY Notify: unRAID=${NOTIFY_UNRAID:-false} Discord=$([[ -n "${MY_DISCORD_WEBHOOK:-}" ]] && echo enabled || echo disabled)"
echo "$ICON_GEAR Dry Run: $DRY_RUN"
echo "━━━━━━━━━━━━━━━━━━━━━━━"
exit 0
fi
[[ "$DRY_RUN" == true ]] && warn "DRY RUN — no changes will be made"
# Log which downloaders are active on this host
if [[ -z "$SLSKD_URL" ]] && [[ -z "$SABNZBD_URL" ]] && [[ -z "$QBIT_URL" ]]; then
warn "No downloaders configured for $MY_ID — nothing to reset"
exit 0
fi
[[ -n "$SLSKD_URL" ]] && log "slskd active on $MY_ID"
[[ -n "$SABNZBD_URL" ]] && log "SABnzbd active on $MY_ID"
[[ -n "$QBIT_URL" ]] && log "qBittorrent active on $MY_ID"
# ==============================================================================================
# ━━━ slskd — Stuck Searches ━━━
# Clears searches in Completed/Errored state left by Soularr crashes
# Prevents 409 Conflict error on next Soularr startup
# -----------------------------------------------------------------------------------------------
# ==============================================================================================
# Clears searches in Completed/Errored state left by Soularr crashes.
# Prevents 409 Conflict error on next Soularr startup when it tries to
# create a search with the same ID that already exists in a terminal state.
if [[ -n "$SLSKD_URL" ]] && [[ -n "$SLSKD_API_KEY" ]]; then
echo ""
echo "━━━ 🔍 slskd — Stuck Searches ━━━"
@@ -127,12 +166,13 @@ if [[ -n "$SLSKD_URL" ]] && [[ -n "$SLSKD_API_KEY" ]]; then
fi
fi
# -----------------------------------------------------------------------------------------------
# ==============================================================================================
# ━━━ slskd — Dead Transfer Records ━━━
# Removes completed/errored/aborted transfer records per user
# Prevents Soularr 404 loop when polling a user whose transfer no longer exists
# NEVER removes transfers that are InProgress or Queued
# -----------------------------------------------------------------------------------------------
# ==============================================================================================
# Removes completed/errored/aborted transfer records per user.
# Prevents Soularr 404 loop when polling a user whose transfer no longer exists.
# Safety: NEVER removes transfers that are InProgress or Queued — active downloads protected.
if [[ -n "$SLSKD_URL" ]] && [[ -n "$SLSKD_API_KEY" ]]; then
echo ""
echo "━━━ 🔍 slskd — Dead Transfer Records ━━━"
@@ -152,6 +192,7 @@ if [[ -n "$SLSKD_URL" ]] && [[ -n "$SLSKD_API_KEY" ]]; then
SUCCESS=0; SKIPPED=0; FAIL=0
while IFS= read -r USER; do
[[ -z "$USER" ]] && continue
# Skip users with any active or queued transfers — never interrupt downloads
ACTIVE=$(echo "$TRANSFERS" | grep -o "\"username\":\"$USER\"[^}]*\"state\":\"[^\"]*\"" | \
grep -c "InProgress\|Queued")
if [[ "$ACTIVE" -gt 0 ]]; then
@@ -182,11 +223,13 @@ if [[ -n "$SLSKD_URL" ]] && [[ -n "$SLSKD_API_KEY" ]]; then
fi
fi
# -----------------------------------------------------------------------------------------------
# ==============================================================================================
# ━━━ slskd — Purge Expired Failed Imports ━━━
# Removes albums Soularr downloaded but Lidarr rejected
# Soularr moves these to failed_imports/ and never cleans them up
# -----------------------------------------------------------------------------------------------
# ==============================================================================================
# Removes albums Soularr downloaded but Lidarr rejected.
# Soularr moves rejected albums to failed_imports/ and never cleans them up.
# Purges directories older than DOWNLOADER_RETENTION_DAYS to prevent unbounded growth.
if [[ -n "$SLSKD_FAILED_IMPORTS_DIR" ]]; then
echo ""
echo "━━━ 🔍 slskd — Failed Imports (older than ${DOWNLOADER_RETENTION_DAYS} days) ━━━"
@@ -217,10 +260,12 @@ if [[ -n "$SLSKD_FAILED_IMPORTS_DIR" ]]; then
fi
fi
# -----------------------------------------------------------------------------------------------
# ==============================================================================================
# ━━━ SABnzbd — Clear Completed History ━━━
# Removes completed download history older than DOWNLOADER_RETENTION_DAYS
# -----------------------------------------------------------------------------------------------
# ==============================================================================================
# Removes completed download history older than DOWNLOADER_RETENTION_DAYS.
# Keeps recent history for reference — only purges what's past the retention window.
if [[ -n "$SABNZBD_URL" ]] && [[ -n "$SABNZBD_API_KEY" ]]; then
echo ""
echo "━━━ 🔍 SABnzbd — Completed History (older than ${DOWNLOADER_RETENTION_DAYS} days) ━━━"
@@ -261,10 +306,12 @@ if [[ -n "$SABNZBD_URL" ]] && [[ -n "$SABNZBD_API_KEY" ]]; then
fi
fi
# -----------------------------------------------------------------------------------------------
# ==============================================================================================
# ━━━ SABnzbd — Clear Failed History ━━━
# Removes failed download history older than DOWNLOADER_RETENTION_DAYS
# -----------------------------------------------------------------------------------------------
# ==============================================================================================
# Removes failed download history older than DOWNLOADER_RETENTION_DAYS.
# Failed history is kept briefly for diagnosis but purged after the retention window.
if [[ -n "$SABNZBD_URL" ]] && [[ -n "$SABNZBD_API_KEY" ]]; then
echo ""
echo "━━━ 🔍 SABnzbd — Failed History (older than ${DOWNLOADER_RETENTION_DAYS} days) ━━━"
@@ -305,11 +352,14 @@ if [[ -n "$SABNZBD_URL" ]] && [[ -n "$SABNZBD_API_KEY" ]]; then
fi
fi
# -----------------------------------------------------------------------------------------------
# ==============================================================================================
# ━━━ SABnzbd — Remove Stalled Queue Items ━━━
# Removes paused queue items no longer progressing
# Active downloading items are never touched
# -----------------------------------------------------------------------------------------------
# ==============================================================================================
# Removes queue items in Paused or Stuck state that are no longer progressing.
# Active downloading items (Downloading, Grabbing) are never touched.
# Paused items may be intentional pauses — but in an automated environment
# a Paused item sitting in the queue indefinitely is effectively stalled.
if [[ -n "$SABNZBD_URL" ]] && [[ -n "$SABNZBD_API_KEY" ]]; then
echo ""
echo "━━━ 🔍 SABnzbd — Stalled Queue Items ━━━"
@@ -331,15 +381,19 @@ if [[ -n "$SABNZBD_URL" ]] && [[ -n "$SABNZBD_API_KEY" ]]; then
[[ -z "$NZO_ID" ]] && continue
STATUS=$(echo "$QUEUE" | grep -A10 "$NZO_ID" | \
grep -o '"status":"[^"]*"' | sed 's/"status":"//;s/"//')
[[ "$STATUS" != "Paused" ]] && ((SKIPPED++)) && continue
# Only remove Paused or Stuck items — Downloading/Grabbing are active
if [[ "$STATUS" != "Paused" ]] && [[ "$STATUS" != "Stuck" ]]; then
((SKIPPED++))
continue
fi
if [[ "$DRY_RUN" == true ]]; then
warn "DRY RUN — would remove stalled item: $NZO_ID"
warn "DRY RUN — would remove stalled item: $NZO_ID ($STATUS)"
((DELETED++))
else
curl -sf --max-time 10 \
"$SABNZBD_URL/api?mode=queue&name=delete&value=$NZO_ID&apikey=$SABNZBD_API_KEY" \
>/dev/null
info "$ICON_TRASH Removed stalled: $NZO_ID"
info "$ICON_TRASH Removed stalled ($STATUS): $NZO_ID"
((DELETED++))
fi
done <<< "$STALLED_IDS"
@@ -349,11 +403,17 @@ if [[ -n "$SABNZBD_URL" ]] && [[ -n "$SABNZBD_API_KEY" ]]; then
fi
fi
# -----------------------------------------------------------------------------------------------
# ━━━ qBittorrent — Last Chance Failsafe Cleanup ━━━
# Deletes torrents older than QBIT_FAILSAFE_MIN_DAYS
# deleteFiles=false — removes from qBit, leaves files for arrs to manage
# -----------------------------------------------------------------------------------------------
# ==============================================================================================
# ━━━ qBittorrent — Age Failsafe Cleanup ━━━
# ==============================================================================================
# Last-chance cleanup for torrents that have been sitting in qBit past their useful life.
# deleteFiles=false — removes the torrent record from qBit but leaves files on disk.
# Radarr/Sonarr manage actual files independently — this only cleans up the qBit entry.
#
# Safety checks before deletion:
# Age must exceed QBIT_FAILSAFE_MIN_DAYS
# Ratio must meet QBIT_FAILSAFE_MIN_RATIO (0 = age only, no ratio requirement)
if [[ -n "$QBIT_URL" ]] && [[ -n "$QBIT_USERNAME" ]]; then
echo ""
echo "━━━ 🔍 qBittorrent — Failsafe (older than ${QBIT_FAILSAFE_MIN_DAYS} days) ━━━"
@@ -366,7 +426,8 @@ if [[ -n "$QBIT_URL" ]] && [[ -n "$QBIT_USERNAME" ]]; then
grep SID | awk '{print "SID="$NF}')
if [[ -z "$QBIT_COOKIE" ]]; then
error "Failed to authenticate with qBittorrent"
error "Failed to authenticate with qBittorrent — check QBIT_USERNAME/PASSWORD"
notify "qBittorrent auth failed on $(hostname) — check credentials in master_host*.conf" "Downloaders Reset" "warning"
((TOTAL_FAIL++))
else
TORRENTS=$(curl -sf --max-time 15 \
@@ -385,8 +446,11 @@ if [[ -n "$QBIT_URL" ]] && [[ -n "$QBIT_USERNAME" ]]; then
[[ -z "$HASH" || -z "$ADDED" ]] && continue
AGE_DAYS=$(( (NOW - ADDED) / 86400 ))
# Age check — must be old enough
[[ "$AGE_DAYS" -lt "$QBIT_FAILSAFE_MIN_DAYS" ]] && ((SKIPPED++)) && continue
# Ratio check — if configured
if [[ "$QBIT_FAILSAFE_MIN_RATIO" != "0" ]]; then
RATIO_INT="${RATIO%.*}"
MIN_RATIO_INT="${QBIT_FAILSAFE_MIN_RATIO%.*}"
@@ -411,14 +475,15 @@ if [[ -n "$QBIT_URL" ]] && [[ -n "$QBIT_USERNAME" ]]; then
fi
fi
# -----------------------------------------------------------------------------------------------
# ━━━ $ICON_SUMMARY Summary ━━━
# -----------------------------------------------------------------------------------------------
# ==============================================================================================
# ━━━ Summary ━━━
# ==============================================================================================
echo ""
echo "━━━━━ $ICON_SUMMARY DOWNLOADERS RESET SUMMARY ━━━━━"
echo "$ICON_TIME Duration: $(format_duration $(( $(date +%s) - START_TIME )))"
echo "$ICON_SUCCESS Actions: $TOTAL_PASS"
echo "$ICON_ERROR Failures: $TOTAL_FAIL"
echo "$ICON_HOST Identity: $MY_ID ($LOCAL_SERVER_NAME)"
echo "$ICON_TIME Duration: $(format_duration $(( $(date +%s) - START_TIME )))"
echo "$ICON_SUCCESS Actions: $TOTAL_PASS"
echo "$ICON_ERROR Failures: $TOTAL_FAIL"
echo ""
if [[ "$DRY_RUN" == true ]]; then