Files
Varaverk/Media/sonarr_tvdb_removed.sh
T
Gmer4Lfe 95151c2278 Watchdogs/ folder + host conf rename
Move all watchdog scripts to a dedicated Watchdogs/ folder:
  Docker_Essentials/docker_watchdog.sh   → Watchdogs/
  unRAID_Essentials/system_watchdog.sh   → Watchdogs/
  unRAID_Essentials/resource_watchdog.sh → Watchdogs/
  Orchestrators/watchdog_orchestrator.sh → Watchdogs/
  Tools/watchdog_skip_list_manager.sh    → Watchdogs/

Rename host config files:
  master_host1.conf → host1.conf
  master_host2.conf → host2.conf

Update all references across the ecosystem:
  master.conf: WATCHDOG_ORCHESTRATOR_SCRIPTS paths → Watchdogs/
  load_config.sh: host*.conf glob + all comments
  git_pull_execute.sh: sparse checkout glob + all comments
  Partnership/ssh_setup.sh: HOST_CONF path construction
  user_script_plug-in.sh: all script paths + per-host conf path
  common.sh, README.md, README-User_Script_Plug-in.md: comment refs
  All Partnership, Fallback, Monitors, Transcodes, Tools scripts: comment refs
2026-05-22 17:08:36 -04:00

252 lines
10 KiB
Bash

#!/bin/bash
# ==============================================================================================
# ============================= Sonarr — TVDB Removed ==========================================
# ==============================================================================================
#
# PURPOSE
# ─────────────────────────────────────────────────────────────────────────────
# Remove series from Sonarr that TVDB has dropped. Sonarr marks these with
# status="deleted" — they generate health errors and can never be monitored
# or downloaded.
#
# ==============================================================================================
# OPERATIONAL MODEL
# ==============================================================================================
#
# Queries Sonarr API for series with status="deleted" (TVDB removal marker)
# Reports each entry with file count and total size
# Removes the series record from Sonarr
# Optionally deletes associated files (disabled by default)
# Optionally adds to Sonarr's import exclusion list (default: true — prevents re-add)
#
# Files are NOT deleted by default — use --delete-files to also remove from disk.
# Per-deletion output is always visible — deletions are never silently swallowed.
#
# ==============================================================================================
# OPERATIONAL SAFEGUARDS
# ==============================================================================================
#
# detect_hosts() — exits cleanly if SONARR_URL empty (Sonarr not on this host)
# API pre-flight — verifies Sonarr reachable before querying
# Dry-run mode — full preview without removing anything
#
# ==============================================================================================
# CONFIGURATION
# ==============================================================================================
#
# master.conf
#
# SONARR_DROPPED_ADD_EXCLUSION — add removed series to import exclusion (default: true)
#
# host*.conf
#
# HOST1_SONARR_URL / HOST1_SONARR_API_KEY
# Aliased by detect_hosts() — script uses SONARR_URL / SONARR_API_KEY
#
# ==============================================================================================
# RUNTIME MODES
# ==============================================================================================
#
# sonarr_tvdb_removed.sh — remove records, keep files, add exclusion
# sonarr_tvdb_removed.sh --delete-files — also delete files from disk
# sonarr_tvdb_removed.sh --dry-run — preview without removing anything
# sonarr_tvdb_removed.sh --log — verbose output
# sonarr_tvdb_removed.sh --status — show config and exit
#
# ==============================================================================================
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/../load_config.sh"
# ── Parse --delete-files before standard parse_args ───────────────────────────────────────────
DELETE_FILES=false
FILTERED_ARGS=()
for arg in "$@"; do
if [[ "$arg" == "--delete-files" ]]; then
DELETE_FILES=true
else
FILTERED_ARGS+=("$arg")
fi
done
parse_args "${FILTERED_ARGS[@]}"
# ==============================================================================================
# ━━━ Setup ━━━
# ==============================================================================================
if [[ "$EUID" -ne 0 ]]; then
error "Must be run as root"
exit 1
fi
if ! command -v curl >/dev/null 2>&1; then
error "curl not found"
exit 1
fi
if ! command -v jq >/dev/null 2>&1; then
error "jq not found"
exit 1
fi
acquire_lock
detect_hosts
if [[ -z "$SONARR_URL" ]]; then
log "Sonarr not configured for $MY_ID — nothing to do"
exit 0
fi
ADD_EXCLUSION="${SONARR_DROPPED_ADD_EXCLUSION:-true}"
echo " $MY_ID ($LOCAL_SERVER_NAME) — $SONARR_URL"
[[ "$DELETE_FILES" == true ]] && warn "DELETE FILES MODE — files will be removed from disk"
[[ "$DELETE_FILES" == false ]] && echo " Files: records only (use --delete-files to also remove from disk)"
[[ "$DRY_RUN" == true ]] && warn "DRY RUN — no changes will be made"
# ==============================================================================================
# ━━━ Status ━━━
# ==============================================================================================
if [[ "$SHOW_STATUS" == true ]]; then
echo ""
echo "━━━━━ $ICON_SUMMARY STATUS ━━━━━"
echo "$ICON_HOST Identity: $MY_ID ($LOCAL_SERVER_NAME)"
echo "$ICON_GEAR Sonarr URL: $SONARR_URL"
echo "$ICON_GEAR Add exclusion: $ADD_EXCLUSION"
echo "$ICON_GEAR Delete files: $DELETE_FILES"
echo "$ICON_GEAR Dry Run: $DRY_RUN"
echo "━━━━━━━━━━━━━━━━━━━━━━━"
exit 0
fi
# ==============================================================================================
# ━━━ Query Sonarr ━━━
# ==============================================================================================
echo ""
echo "━━━ $ICON_SYNC Querying Sonarr ━━━"
if ! curl -sf --connect-timeout 5 --max-time 10 \
"$SONARR_URL/api/v3/system/status?apikey=$SONARR_API_KEY" | jq -e '.version' >/dev/null 2>&1; then
error "Sonarr API unreachable at $SONARR_URL"
exit 1
fi
SERIES=$(curl -sf --connect-timeout 5 --max-time 30 \
"$SONARR_URL/api/v3/series?apikey=$SONARR_API_KEY" 2>/dev/null)
if [[ -z "$SERIES" || "$SERIES" == "null" ]]; then
error "Sonarr series API returned empty"
exit 1
fi
TOTAL=$(echo "$SERIES" | jq '. | length')
DROPPED=$(echo "$SERIES" | jq '[.[] | select(.status == "deleted")] | length')
echo " $TOTAL series total — $DROPPED dropped from TVDB"
if [[ "$DROPPED" -eq 0 ]]; then
log "No TVDB-removed series found — nothing to do"
echo ""
echo "━━━━━ $ICON_SUMMARY SONARR TVDB REMOVED SUMMARY ━━━━━"
echo "$ICON_HOST Identity: $MY_ID ($LOCAL_SERVER_NAME)"
echo "$ICON_DONE Status: nothing to remove"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
exit 0
fi
# ==============================================================================================
# ━━━ Remove Dropped Series ━━━
# ==============================================================================================
echo ""
echo "━━━ $ICON_TRASH Remove TVDB-Dropped Series ━━━"
START=$(date +%s)
REMOVED=()
FAILED=()
FILES_DELETED=0
FILES_SKIPPED=0
while IFS=$'\t' read -r id title year tvdb_id episode_file_count size_on_disk; do
[[ -z "$id" ]] && continue
SIZE_HUMAN=""
if [[ "$size_on_disk" -gt 0 ]]; then
SIZE_HUMAN=$(awk "BEGIN {printf \"%.1fGB\", $size_on_disk / 1073741824}")
fi
if [[ "$episode_file_count" -gt 0 ]]; then
log "$ICON_WARN $title ($year) [tvdbid $tvdb_id] — $episode_file_count episode files${SIZE_HUMAN:+, $SIZE_HUMAN}"
else
log "$ICON_TRASH $title ($year) [tvdbid $tvdb_id] — no files"
fi
if [[ "$DRY_RUN" == true ]]; then
warn "DRY RUN — would remove: $title"
[[ "$DELETE_FILES" == true && "$episode_file_count" -gt 0 ]] && \
warn "DRY RUN — would delete $episode_file_count file(s)${SIZE_HUMAN:+, $SIZE_HUMAN}"
REMOVED+=("$title")
continue
fi
DELETE_PARAM="false"
if [[ "$DELETE_FILES" == true && "$episode_file_count" -gt 0 ]]; then
DELETE_PARAM="true"
fi
RESP=$(curl -sf -X DELETE --connect-timeout 5 --max-time 15 \
"$SONARR_URL/api/v3/series/${id}?deleteFiles=${DELETE_PARAM}&addImportListExclusion=${ADD_EXCLUSION}&apikey=$SONARR_API_KEY" \
2>/dev/null)
CURL_EXIT=$?
if [[ "$CURL_EXIT" -eq 0 ]]; then
log " Removed from Sonarr ✅"
REMOVED+=("$title")
if [[ "$DELETE_PARAM" == "true" ]]; then
(( FILES_DELETED++ ))
elif [[ "$episode_file_count" -gt 0 ]]; then
(( FILES_SKIPPED++ ))
fi
else
warn " Failed to remove $title (curl exit $CURL_EXIT)"
FAILED+=("$title")
fi
done < <(echo "$SERIES" | jq -r '
.[] | select(.status == "deleted") |
[
(.id | tostring),
.title,
(.year | tostring),
(.tvdbId | tostring),
(if .episodeFileCount? then (.episodeFileCount | tostring) else "0" end),
(if .sizeOnDisk? then (.sizeOnDisk | tostring) else "0" end)
] | @tsv
')
END=$(date +%s)
# ==============================================================================================
# ━━━ Summary ━━━
# ==============================================================================================
echo ""
echo "━━━━━ $ICON_SUMMARY SONARR TVDB REMOVED SUMMARY ━━━━━"
echo "$ICON_HOST Identity: $MY_ID ($LOCAL_SERVER_NAME)"
echo "$ICON_TIME Duration: $(format_duration $(( END - START )))"
echo "$ICON_TRASH Removed: ${#REMOVED[@]} of $DROPPED"
[[ ${#FAILED[@]} -gt 0 ]] && echo "$ICON_ERROR Failed: ${FAILED[*]}"
[[ "$FILES_DELETED" -gt 0 ]] && echo "$ICON_TRASH Files deleted: $FILES_DELETED series worth"
[[ "$FILES_SKIPPED" -gt 0 ]] && echo "$ICON_WARN Files kept: $FILES_SKIPPED series (had files — use --delete-files to remove)"
[[ "$ADD_EXCLUSION" == "true" ]] && echo "$ICON_GEAR Import exclusion added for removed entries"
if [[ "$DRY_RUN" == true ]]; then
warn "DRY RUN — no changes made"
elif [[ ${#FAILED[@]} -eq 0 ]]; then
echo "$ICON_DONE Status: done ✅"
else
warn "Status: ${#FAILED[@]} removal(s) failed"
fi
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
[[ ${#FAILED[@]} -gt 0 ]] && exit 1
exit 0