diff --git a/Docker_Essentials/docker_watchdog.sh b/Docker_Essentials/docker_watchdog.sh index 6e30075..b5acb52 100644 --- a/Docker_Essentials/docker_watchdog.sh +++ b/Docker_Essentials/docker_watchdog.sh @@ -53,6 +53,24 @@ fi success "Running as root" +# Select correct per-host watchdog lists +detect_hosts + +if [[ "$LOCAL_SERVER_NAME" == "$HOST1" ]]; then + WATCHDOG_REQUIRED_CONTAINERS=("${HOST1_WATCHDOG_REQUIRED_CONTAINERS[@]}") + declare -A WATCHDOG_CONTAINER_URLS + for key in "${!HOST1_WATCHDOG_CONTAINER_URLS[@]}"; do + WATCHDOG_CONTAINER_URLS["$key"]="${HOST1_WATCHDOG_CONTAINER_URLS[$key]}" + done +else + WATCHDOG_REQUIRED_CONTAINERS=("${HOST2_WATCHDOG_REQUIRED_CONTAINERS[@]}") + declare -A WATCHDOG_CONTAINER_URLS + for key in "${!HOST2_WATCHDOG_CONTAINER_URLS[@]}"; do + WATCHDOG_CONTAINER_URLS["$key"]="${HOST2_WATCHDOG_CONTAINER_URLS[$key]}" + done +fi + +info "Watchdog running as: $LOCAL_SERVER_NAME" if ! command -v docker >/dev/null 2>&1; then error "Docker not found" exit 1 diff --git a/Master.conf b/Master.conf index b688f5c..8aca32a 100644 --- a/Master.conf +++ b/Master.conf @@ -679,14 +679,19 @@ declare -A WATCHDOG_CONTAINERS=( ["Code-Server"]=1024 ) -declare -A WATCHDOG_CONTAINER_URLS=( +# Per-host HTTP health check URLs — docker_watchdog.sh picks correct list via detect_hosts() +declare -A HOST1_WATCHDOG_CONTAINER_URLS=( ["Emby"]="http://localhost:8096" ) -# Containers that must always be running — strike system, persistent skip list on /boot/ -# Skip list auto-clears when container recovers — no manual intervention for normal recovery -# These are your core auth and proxy stack — everything depends on them being up -WATCHDOG_REQUIRED_CONTAINERS=( +declare -A HOST2_WATCHDOG_CONTAINER_URLS=( + ["Emby"]="http://localhost:8096" # his Emby — same port, different server +) + +# Per-host required containers — core stack that must always be running +# docker_watchdog.sh picks correct list via detect_hosts() +# Strike system — persistent skip list on /boot/, auto-clears on recovery +HOST1_WATCHDOG_REQUIRED_CONTAINERS=( "NginxProxyManager" "Lldap-Gmer4Lfe" "Authelia" @@ -696,6 +701,11 @@ WATCHDOG_REQUIRED_CONTAINERS=( "Redis-Authelia-Secondary" ) +HOST2_WATCHDOG_REQUIRED_CONTAINERS=( + "NginxProxyManager" + # add HOST2's required containers here +) + # Strike state file — /tmp resets on reboot which is correct for strike tracking WATCHDOG_STATE_FILE="/tmp/container_watchdog_state.db" @@ -909,6 +919,13 @@ MEDIA_MAINTENANCE_JOBS=( # Each arr script queries its API to get all tracked file paths, then compares against # what exists on disk. Files not tracked by the arr and older than ORPHAN_AGE days are deleted. # +# Each server runs different arrs managing different shares: +# HOST1: Sonarr (Tv_Shows), Radarr (Movies), Lidarr (Music) +# HOST2: Sonarr (Anime_Shows), Radarr (Anime_Movies) +# +# detect_hosts() selects the correct URL, API key, and root path at runtime. +# Scripts run identically on both servers — configuration drives behavior. +# # Why the age threshold matters: # The arr downloads a file then processes it — there is a window where the file exists # on disk but the arr hasn't imported it yet. ORPHAN_AGE prevents deleting files that @@ -917,33 +934,52 @@ MEDIA_MAINTENANCE_JOBS=( # Protected patterns are NEVER deleted regardless of tracking status or age. # These protect arr-generated metadata (cover art, .nfo files, subtitles) that the arr # depends on but does not include in its tracked file API response. -# Add new patterns here if arr metadata formats change in future versions. -# Lidarr — music library - LIDARR_URL="http://192.168.50.2:8686" - LIDARR_API_KEY="b2977e71ef074bc0a0529d9fcce3b2dc" - LIDARR_MUSIC_ROOT="/mnt/user/Music-New" # must match the root path set in Lidarr - LIDARR_ORPHAN_AGE=7 # days before untracked file is eligible for deletion - LIDARR_EXTENSIONS=("flac" "mp3" "m4a" "wav" "aac" "ogg" "opus" "wma") - LIDARR_PROTECTED_PATTERNS=("*.jpg" "*.jpeg" "*.png" "*.nfo" "*.lrc") - # never deleted — cover art, metadata, lyrics +# ── Lidarr ──────────────────────────────────────────────────────────────────────────────────── +# Lidarr runs on HOST1 only — music library management +# HOST2 does not run Lidarr — no HOST2 Lidarr config needed -# Sonarr — TV library - SONARR_URL="http://192.168.50.2:8989" - SONARR_API_KEY="130decd3db5b4c25afad64864cd03f9f" - SONARR_TV_ROOT="/mnt/user/Tv_Shows" # must match the root path set in Sonarr - SONARR_ORPHAN_AGE=7 - SONARR_EXTENSIONS=("mkv" "mp4" "avi" "m4v" "ts" "wmv" "mov") - SONARR_PROTECTED_PATTERNS=("*.jpg" "*.jpeg" "*.png" "*.nfo" "*.srt" "*.sub" "*.ass" "*.ssa") - # never deleted — artwork, metadata, subtitles +HOST1_LIDARR_URL="http://192.168.50.2:8686" +HOST1_LIDARR_API_KEY="b2977e71ef074bc0a0529d9fcce3b2dc" +HOST1_LIDARR_MUSIC_ROOT="/mnt/user/Music-New" # must match root path set in Lidarr exactly -# Radarr — movie library - RADARR_URL="http://192.168.50.2:7878" - RADARR_API_KEY="d43a3ec6cf1549edb4af0cc63f98b2a9" - RADARR_MOVIES_ROOT="/mnt/user/Movies" # must match the root path set in Radarr - RADARR_ORPHAN_AGE=7 - RADARR_EXTENSIONS=("mkv" "mp4" "avi" "m4v" "wmv" "mov") - RADARR_PROTECTED_PATTERNS=("*.jpg" "*.jpeg" "*.png" "*.nfo" "*.srt" "*.sub" "*.ass" "*.ssa") +LIDARR_ORPHAN_AGE=7 # days before untracked file eligible for deletion +LIDARR_EXTENSIONS=("flac" "mp3" "m4a" "wav" "aac" "ogg" "opus" "wma") +LIDARR_PROTECTED_PATTERNS=("*.jpg" "*.jpeg" "*.png" "*.nfo" "*.lrc") + # never deleted — cover art, metadata, lyrics + +# ── Sonarr ──────────────────────────────────────────────────────────────────────────────────── +# HOST1 Sonarr manages Tv_Shows — HOST2 Sonarr manages Anime_Shows +# Scripts run on each server and hit their own local Sonarr instance + +HOST1_SONARR_URL="http://192.168.50.2:8989" +HOST1_SONARR_API_KEY="130decd3db5b4c25afad64864cd03f9f" +HOST1_SONARR_TV_ROOT="/mnt/user/Tv_Shows" # must match root path set in HOST1 Sonarr exactly + +HOST2_SONARR_URL="http://localhost:8989" # HOST2 local Sonarr — update with actual port +HOST2_SONARR_API_KEY="your-host2-sonarr-api-key" +HOST2_SONARR_TV_ROOT="/mnt/user/Anime_Shows" # must match root path set in HOST2 Sonarr exactly + +SONARR_ORPHAN_AGE=7 +SONARR_EXTENSIONS=("mkv" "mp4" "avi" "m4v" "ts" "wmv" "mov") +SONARR_PROTECTED_PATTERNS=("*.jpg" "*.jpeg" "*.png" "*.nfo" "*.srt" "*.sub" "*.ass" "*.ssa") + # never deleted — artwork, metadata, subtitles + +# ── Radarr ──────────────────────────────────────────────────────────────────────────────────── +# HOST1 Radarr manages Movies — HOST2 Radarr manages Anime_Movies +# Scripts run on each server and hit their own local Radarr instance + +HOST1_RADARR_URL="http://192.168.50.2:7878" +HOST1_RADARR_API_KEY="d43a3ec6cf1549edb4af0cc63f98b2a9" +HOST1_RADARR_MOVIES_ROOT="/mnt/user/Movies" # must match root path set in HOST1 Radarr exactly + +HOST2_RADARR_URL="http://localhost:7878" # HOST2 local Radarr — update with actual port +HOST2_RADARR_API_KEY="your-host2-radarr-api-key" +HOST2_RADARR_MOVIES_ROOT="/mnt/user/Anime_Movies" # must match root path set in HOST2 Radarr exactly + +RADARR_ORPHAN_AGE=7 +RADARR_EXTENSIONS=("mkv" "mp4" "avi" "m4v" "wmv" "mov") +RADARR_PROTECTED_PATTERNS=("*.jpg" "*.jpeg" "*.png" "*.nfo" "*.srt" "*.sub" "*.ass" "*.ssa") # ============================================================================================== # ── TRANSCODES ──────────────────────────────────────────────────────────────────────────────── @@ -1090,8 +1126,15 @@ ZFS_REPORT_IGNORE_POOLS=( # ━━━ Emby Session Report ━━━ # Requires API key from Emby Settings → API Keys in the Emby WebUI. # No persistent writes — queries fresh each run. - EMBY_URL="http://localhost:8096" - EMBY_API_KEY="0c27448d93a7431f9ac63569f7655829" +# Each server has its own Emby instance with its own API key. +# detect_hosts() selects the correct URL and key at runtime. + +HOST1_EMBY_URL="http://localhost:8096" +HOST1_EMBY_API_KEY="0c27448d93a7431f9ac63569f7655829" + +HOST2_EMBY_URL="http://localhost:8096" # same port — different server, different key +HOST2_EMBY_API_KEY="your-host2-emby-api-key" + EMBY_REPORT_DAYS=7 # number of days to include in the report period EMBY_REPORT_TOP_N=10 # number of top content items to show in report diff --git a/Media/lidarr_cleanup.sh b/Media/lidarr_cleanup.sh index 9466428..6c5b851 100644 --- a/Media/lidarr_cleanup.sh +++ b/Media/lidarr_cleanup.sh @@ -19,6 +19,8 @@ # not include these in its tracked file API response. Without protection these would # be classified as orphans and deleted — breaking Lidarr and Emby metadata display. # +# Lidarr runs on HOST1 only — music library is HOST1's source of truth. +# If run on HOST2 this script exits cleanly with no action. # All configuration in Master.conf under Arr Cleanup section. # Supports --dry-run to preview what would be deleted without making changes. # ----------------------------------------------------------------------------------------------- @@ -54,6 +56,21 @@ if ! command -v jq >/dev/null 2>&1; then exit 1 fi +# Lidarr runs on HOST1 only — exit cleanly if running on HOST2 +detect_hosts + +if [[ "$LOCAL_SERVER_NAME" != "$HOST1" ]]; then + info "Lidarr runs on $HOST1 only — skipping on $LOCAL_SERVER_NAME" + exit 0 +fi + +LIDARR_URL="$HOST1_LIDARR_URL" +LIDARR_API_KEY="$HOST1_LIDARR_API_KEY" +LIDARR_MUSIC_ROOT="$HOST1_LIDARR_MUSIC_ROOT" + +info "Lidarr instance: $LOCAL_SERVER_NAME → $LIDARR_URL" +info "Music root: $LIDARR_MUSIC_ROOT" + require_var LIDARR_URL require_var LIDARR_API_KEY require_var LIDARR_MUSIC_ROOT diff --git a/Media/radarr_cleanup.sh b/Media/radarr_cleanup.sh index 8fa84ec..323cf79 100644 --- a/Media/radarr_cleanup.sh +++ b/Media/radarr_cleanup.sh @@ -19,6 +19,8 @@ # (*.srt, *.sub, *.ass) but does not include these in its tracked file API response. # Without protection these would be classified as orphans and deleted. # +# HOST1 Radarr manages Movies. HOST2 Radarr manages Anime_Movies. +# detect_hosts() selects the correct URL, API key, and root path at runtime. # All configuration in Master.conf under Arr Cleanup section. # Supports --dry-run to preview what would be deleted without making changes. # ----------------------------------------------------------------------------------------------- @@ -54,6 +56,22 @@ if ! command -v jq >/dev/null 2>&1; then exit 1 fi +# Select correct Radarr instance based on which server is running this script +detect_hosts + +if [[ "$LOCAL_SERVER_NAME" == "$HOST1" ]]; then + RADARR_URL="$HOST1_RADARR_URL" + RADARR_API_KEY="$HOST1_RADARR_API_KEY" + RADARR_MOVIES_ROOT="$HOST1_RADARR_MOVIES_ROOT" +else + RADARR_URL="$HOST2_RADARR_URL" + RADARR_API_KEY="$HOST2_RADARR_API_KEY" + RADARR_MOVIES_ROOT="$HOST2_RADARR_MOVIES_ROOT" +fi + +info "Radarr instance: $LOCAL_SERVER_NAME → $RADARR_URL" +info "Movies root: $RADARR_MOVIES_ROOT" + require_var RADARR_URL require_var RADARR_API_KEY require_var RADARR_MOVIES_ROOT diff --git a/Media/sonarr_cleanup.sh b/Media/sonarr_cleanup.sh index 2a4a8db..eb48a9f 100644 --- a/Media/sonarr_cleanup.sh +++ b/Media/sonarr_cleanup.sh @@ -19,6 +19,8 @@ # (*.srt, *.sub, *.ass) but does not include these in its tracked file API response. # Without protection these would be classified as orphans and deleted. # +# HOST1 Sonarr manages Tv_Shows. HOST2 Sonarr manages Anime_Shows. +# detect_hosts() selects the correct URL, API key, and root path at runtime. # All configuration in Master.conf under Arr Cleanup section. # Supports --dry-run to preview what would be deleted without making changes. # ----------------------------------------------------------------------------------------------- @@ -54,6 +56,22 @@ if ! command -v jq >/dev/null 2>&1; then exit 1 fi +# Select correct Sonarr instance based on which server is running this script +detect_hosts + +if [[ "$LOCAL_SERVER_NAME" == "$HOST1" ]]; then + SONARR_URL="$HOST1_SONARR_URL" + SONARR_API_KEY="$HOST1_SONARR_API_KEY" + SONARR_TV_ROOT="$HOST1_SONARR_TV_ROOT" +else + SONARR_URL="$HOST2_SONARR_URL" + SONARR_API_KEY="$HOST2_SONARR_API_KEY" + SONARR_TV_ROOT="$HOST2_SONARR_TV_ROOT" +fi + +info "Sonarr instance: $LOCAL_SERVER_NAME → $SONARR_URL" +info "TV root: $SONARR_TV_ROOT" + require_var SONARR_URL require_var SONARR_API_KEY require_var SONARR_TV_ROOT diff --git a/user_script_plug-in.sh b/user_script_plug-in.sh index 6612aff..afcd857 100644 --- a/user_script_plug-in.sh +++ b/user_script_plug-in.sh @@ -159,6 +159,9 @@ #/mnt/user/appdata/unraid_scripts/Monitors/zfs_memory_snapshot.sh # # ━━━ Orchestrators ━━━ +# Orchestrators run their child scripts in the correct order. +# Individual scripts below are still accessible for manual runs or testing. +# #/mnt/user/appdata/unraid_scripts/Orchestrators/daily_sync.sh #/mnt/user/appdata/unraid_scripts/Orchestrators/media_management.sh #/mnt/user/appdata/unraid_scripts/Orchestrators/transcode_management.sh @@ -173,6 +176,8 @@ # ━━━ Rsync — Individual Media Shares (ad hoc) ━━━ #/mnt/user/appdata/unraid_scripts/Rsync/rsync.sh /mnt/user/Movies #/mnt/user/appdata/unraid_scripts/Rsync/rsync.sh /mnt/user/Tv_Shows +#/mnt/user/appdata/unraid_scripts/Rsync/rsync.sh /mnt/user/Music +#/mnt/user/appdata/unraid_scripts/Rsync/rsync.sh /mnt/user/Music_Videos #/mnt/user/appdata/unraid_scripts/Rsync/rsync.sh /mnt/user/Anime_Shows #/mnt/user/appdata/unraid_scripts/Rsync/rsync.sh /mnt/user/Anime_Shows-Old #/mnt/user/appdata/unraid_scripts/Rsync/rsync.sh /mnt/user/Anime_Movies @@ -181,8 +186,8 @@ #/mnt/user/appdata/unraid_scripts/Rsync/rsync.sh /mnt/user/Intros #/mnt/user/appdata/unraid_scripts/Rsync/rsync.sh /mnt/user/Kids_Movies #/mnt/user/appdata/unraid_scripts/Rsync/rsync.sh /mnt/user/Kids_Tv_Shows -#/mnt/user/appdata/unraid_scripts/Rsync/rsync.sh /mnt/user/Music_Videos #/mnt/user/appdata/unraid_scripts/Rsync/rsync.sh /mnt/user/Nextcloud +#/mnt/user/appdata/unraid_scripts/Rsync/rsync.sh /mnt/user/Sports #/mnt/user/appdata/unraid_scripts/Rsync/rsync.sh /mnt/user/stand-up_comedy # # ━━━ Docker Essentials ━━━ @@ -191,14 +196,32 @@ #/mnt/user/appdata/unraid_scripts/Docker_Essentials/docker_weekly_restart.sh #/mnt/user/appdata/unraid_scripts/Docker_Essentials/docker_network_connect.sh # +# ━━━ Media ━━━ +# media_management.sh runs all of these in order — individual entries for manual use. +# Always --dry-run --log first on arr cleanup scripts before running live. +# +#/mnt/user/appdata/unraid_scripts/Media/media_shares_permissions.sh +#/mnt/user/appdata/unraid_scripts/Media/media_cleaner.sh anime --dry-run +#/mnt/user/appdata/unraid_scripts/Media/media_cleaner.sh anime +#/mnt/user/appdata/unraid_scripts/Media/media_cleaner.sh media --dry-run +#/mnt/user/appdata/unraid_scripts/Media/media_cleaner.sh media +#/mnt/user/appdata/unraid_scripts/Media/lidarr_cleanup.sh --dry-run --log +#/mnt/user/appdata/unraid_scripts/Media/lidarr_cleanup.sh +#/mnt/user/appdata/unraid_scripts/Media/sonarr_cleanup.sh --dry-run --log +#/mnt/user/appdata/unraid_scripts/Media/sonarr_cleanup.sh +#/mnt/user/appdata/unraid_scripts/Media/radarr_cleanup.sh --dry-run --log +#/mnt/user/appdata/unraid_scripts/Media/radarr_cleanup.sh +# # ━━━ Transcodes ━━━ -# transcode_management.sh runs cleanup then manager every 3min — schedule this. -# ramdisk_setup.sh runs once at array start — separate User Script entry. -# transcode_manager.sh and transcode_cleanup.sh can be run individually if needed. +# transcode_management.sh runs cleanup then manager — schedule that, not the individuals. +# ramdisk_setup.sh runs once at array start. +# transcode_manager.sh and transcode_cleanup.sh available for individual manual runs. # #/mnt/user/appdata/unraid_scripts/Transcodes/ramdisk_setup.sh #/mnt/user/appdata/unraid_scripts/Orchestrators/transcode_management.sh +#/mnt/user/appdata/unraid_scripts/Transcodes/transcode_manager.sh --dry-run #/mnt/user/appdata/unraid_scripts/Transcodes/transcode_manager.sh +#/mnt/user/appdata/unraid_scripts/Transcodes/transcode_cleanup.sh --dry-run #/mnt/user/appdata/unraid_scripts/Transcodes/transcode_cleanup.sh # # ━━━ Tools ━━━