Files
Varaverk/Arrs_Stack/README-Arrs_Stack.md
Gmer4Lfe 8a2707ee37 Correct and extend folder docs for Docker Essentials, Watchdogs, Media and Arrs Stack
The docs had drifted from the scripts — a script that no longer exists, three wrong variable
names, a reversed run order, and seven scheduled scripts that were never documented at all.
2026-08-01 22:59:07 -04:00

17 KiB
Raw Permalink Blame History

━━━━━ ARRS STACK ━━━━━

Lifecycle management for the arr suite (Lidarr, Sonarr, Radarr) across a multi-server ecosystem. Library sync so every node tracks the same content. Orphan cleanup against live arr APIs so deleted content actually leaves disk. Release correction so wrong MusicBrainz editions don't silently block Lidarr imports. Emby notified automatically after every deletion. Failed downloads recovered overnight. Artwork fetched continuously. Database hygiene for dropped upstream entries. Quality upgrades propagate to all nodes immediately via webhook. Weekly discovery adds new content based on what you actually play and watch.

These scripts permanently delete files and modify arr databases. Orphan cleanup is protected by multiple safety layers that must all pass before anything is touched — but dry runs and log review are still the right first step on any new system or after any configuration change. See Manual-Arrs_Stack.md for the safe testing procedure.


━━━ THE PROBLEMS THAT BUILT THIS ━━━

Deleted Shows and Removed Albums Still on Disk When you remove a series from Sonarr and the delete command fails — permission issue, container wasn't running, path mismatch — the files stay permanently. Over years on an active library this accumulates significantly. The fix: arr cleanup scripts query the live API for every tracked file path, walk the disk, and delete anything absent from the API response that's old enough to be past the import window.

Wrong MusicBrainz Release Edition Silently Blocking Lidarr Imports Lidarr selects one specific release edition per album using a MusicBrainz release ID. When it picks the wrong edition (Brazil CD instead of US CD, Japan Digital instead of standard), the track IDs don't match what's embedded in the files. RescanFolders reports "Importing 0 tracks" even with perfectly tagged, complete files present. No error — just silence. The fix: lidarr_release_fixer.sh reads the MUSICBRAINZ_ALBUMID tag from each file, finds the matching release in Lidarr's known releases, switches the selection, and queues a RefreshArtist. Runs before cleanup so corrected albums are imported before the orphan scan ever sees them.

Emby Showing Ghost Entries After Cleanup After arr cleanup deletes files, Emby still shows them until its next scheduled scan — potentially hours later. Users see broken entries that produce "file not found" errors. The fix: notify_emby_scan() is called automatically after every deletion. Triggers Emby's "Clean Missing Files" task immediately.

No Safety Net on Deletion Size A misconfigured root path — pointing cleanup at the wrong directory — means the API returns zero tracked files for a root that actually contains thousands. Every file walks as an orphan. Everything gets deleted. This is the catastrophic failure mode. The fix: LIDARR/SONARR/RADARR_MAX_DELETE_GB — if total deletion size exceeds the limit, the script stops and requires --i-know-what-im-doing to proceed. The flag name is long and annoying by design. It cannot be added by accident.

Failed Downloads Accumulating Silently Import failures and stalled downloads sit in arr queues indefinitely. Without intervention they occupy queue slots, block new searches, and the item never gets downloaded. Checking queues manually across three arrs is tedious. The fix: arrs_failed_stalled_recovery.sh inspects all arr queues, blocklists the bad release, removes it, and triggers a re-search. Runs daily.


━━━ WHAT THIS FOLDER DOES ━━━

Library Sync arr_sync.sh — full-mesh arr library sync across all nodes. Every node syncs with every other — union model, no hierarchy. Once arrs agree on what to track, rsync spreads the actual files.

Release Correction lidarr_release_fixer.sh — reads MUSICBRAINZ_ALBUMID from FLAC and MP3 files, matches against Lidarr's known releases per album, switches monitored=true to the correct edition, queues RefreshArtist. Runs before lidarr_cleanup.sh daily. Handles both FLAC (vorbis comment block) and MP3 (ID3v2 TXXX frame).

Orphan Cleanup lidarr_cleanup.sh, sonarr_cleanup.sh, radarr_cleanup.sh — API-verified orphan removal. Five classification categories (TRACKED/PROTECTED/ORPHAN/JUNK/RECENT), seven safety layers, automatic Emby notification after deletion.

Database Hygiene radarr_tmdb_removed.sh, sonarr_tvdb_removed.sh — remove entries that upstream databases have dropped (TMDb/TVDB status="deleted"). These generate health warnings in arrs and can never be monitored or downloaded. Most are announced-but-never-released entries. Files are kept by default — most have none.

Library Recovery arrs_failed_stalled_recovery.sh — detect and recover failed imports and stalled downloads across all arrs. Blocklists the bad release and triggers a re-search — hands- free overnight recovery.

Library Enrichment lidarr_missing_art.sh — fetch missing album and artist artwork from fanart.tv and fallback sources. Never overwrites existing files.

Upgrade Propagation start_webhook_listener.sh — Node.js HTTP server that receives Sonarr/Radarr/Lidarr OnUpgrade webhooks. Continuous; started at array start. Writes to /var/log/varaverk/upgrade_webhook.log. upgrade_webhook_handler.sh — triggered by the webhook listener. Pushes the upgraded item folder to every remote node immediately, then triggers an arr library rescan on each remote so the upgraded file is accepted without triggering a redundant quality search.

Discovery playback_aware_lidarr_discovery.sh — behavior-driven music discovery. Scores your Emby play history, runs Last.fm getSimilar on top artists, adds the best matches to Lidarr. 05 meaningful adds per week. playback_aware_radarr_discovery.sh — behavior-driven movie discovery. Scores recently watched movies, runs TMDB recommendations on seeds, adds top candidates to Radarr. playback_aware_sonarr_discovery.sh — behavior-driven TV discovery. Scores recently watched series weighted by user diversity, runs TMDB TV recommendations on seeds, adds top shows to Sonarr. Multi-user design: one person binge-watching does not dominate seeds.


━━━ EXECUTION ORDER ━━━

Daily via daily_sync_maintenance.sh (DAILY_MAINTENANCE_SCRIPTS):

1. lidarr_release_fixer.sh   — correct wrong release editions before cleanup sees them
2. lidarr_cleanup.sh         — orphan removal (music)
3. sonarr_cleanup.sh         — orphan removal (TV)
4. radarr_cleanup.sh         — orphan removal (movies)
5. lidarr_missing_art.sh     — fetch missing artwork (HOST1 only)
6. radarr_tmdb_removed.sh    — remove TMDb-dropped movies
7. sonarr_tvdb_removed.sh    — remove TVDB-dropped series

Note: media_shares_permissions.sh and media_cleaner.sh run before these from Media/ — permissions and junk removal must complete first.

Every 30 min + 4hr via orchestrators (CRITICAL/INTERMEDIATE_MAINTENANCE_SCRIPTS):

arrs_failed_stalled_recovery.sh   — failed/stalled queue recovery
arr_sync.sh                       — library sync across all nodes

Weekly via weekly_sync_maintenance.sh (WEEKLY_MAINTENANCE_SCRIPTS):

playback_aware_lidarr_discovery.sh   — score play history → Last.fm similar → Lidarr
playback_aware_radarr_discovery.sh   — score watch history → TMDB recommendations → Radarr
playback_aware_sonarr_discovery.sh   — score episode history → TMDB TV → Sonarr

Continuous (started by array_started.sh):

start_webhook_listener.sh   — Node.js webhook server; dispatches upgrade_webhook_handler.sh

On every arr upgrade (triggered by webhook):

upgrade_webhook_handler.sh  — push upgraded folder to all remote nodes + trigger arr rescan

Why release fixer before cleanup: the fixer corrects Lidarr's release selection so files get imported. If cleanup ran first, a correctable album could accumulate age toward the orphan threshold before the fixer had a chance to fix it.

Why arr_sync before rsync: once arrs agree on what to track, rsync spreads the actual files. An upgrade on one node — new tracked path, old path no longer in API — gets cleaned by arr_cleanup on all nodes after the next sync cycle.


━━━ HOST AWARENESS ━━━

Scripts run on both servers via detect_hosts(), which aliases all HOST*_ prefixed vars to their unprefixed names at runtime. No manual HOST1/HOST2 comparisons exist in any script.

arr_sync.sh keeps all arr databases in bidirectional union — either server can download to any share. Arr cleanup uses the union model: a file is only an orphan if the arr on this host doesn't have it indexed. Arr scripts check the aliased URL — if empty (arr not configured on this host), they exit cleanly with no action.

lidarr_release_fixer.sh and lidarr_missing_art.sh exit cleanly on hosts without Lidarr configured — no HOST1_LIDARR_URL means nothing runs.


━━━ SCRIPTS IN THIS FOLDER ━━━

Script Role When It Runs
arr_sync.sh Full-mesh arr library sync — all nodes track the same content Every 4hr + weekly before rsync
lidarr_release_fixer.sh Fix wrong MusicBrainz release editions so files get imported Daily before lidarr_cleanup
lidarr_cleanup.sh Delete orphaned music files not tracked by Lidarr Daily
sonarr_cleanup.sh Delete orphaned TV files not tracked by Sonarr Daily
radarr_cleanup.sh Delete orphaned movie files not tracked by Radarr Daily
arrs_failed_stalled_recovery.sh Auto-recover failed imports and stalled downloads Every 30 min / daily
lidarr_missing_art.sh Fetch missing album and artist artwork Daily (HOST1 only)
radarr_tmdb_removed.sh Remove movies dropped from TMDb Daily
sonarr_tvdb_removed.sh Remove series dropped from TVDB Daily
start_webhook_listener.sh Node.js webhook server — receive arr OnUpgrade and dispatch handler Continuous
upgrade_webhook_handler.sh Push upgraded item folder to remote nodes + trigger arr rescan On each arr upgrade
playback_aware_lidarr_discovery.sh Behavior-driven music discovery — Emby plays → Last.fm similar → Lidarr Weekly
playback_aware_radarr_discovery.sh Behavior-driven movie discovery — Emby watches → TMDB recommendations → Radarr Weekly
playback_aware_sonarr_discovery.sh Behavior-driven TV discovery — Emby episodes → TMDB TV recommendations → Sonarr Weekly
arr_download_orphan_cleaner.sh Clear orphaned completed downloads out of the SABnzbd Completed folders Daily
sonarr_classification_scan.sh Detect series sitting in the wrong root (anime / kids / general); --move acts Daily
radarr_classification_scan.sh Same for movies, plus junk-metadata detection via --remove-junk Daily
lidarr_duplicate_artist_cleanup.sh Remove phantom zero-file duplicate artists; flag real ones for review Daily
arr_cache_prefill.sh Warm the shared tracked-data cache so consumers never read cold Array start + every 4hr
arr_corruption_scan.sh ffprobe every tracked video for corrupt headers; --remediate deletes + re-searches Weekly
arr_full_rescan.sh Force a real disk↔database reconciliation on all three arrs Weekly

━━━ THE NEWER LAYERS ━━━

The original folder was "delete what the arrs no longer track". These were added as distinct failure modes surfaced — each exists because something went wrong that the cleanups could not have caught.

🗑️ Download-Side Orphans — arr_download_orphan_cleaner.sh

Every cleanup script here walks the library side. Nothing walked the download side — so completed downloads the arrs had stopped tracking accumulated in SABnzbd's Completed folders indefinitely. Discovered as 755 GB of orphaned TV downloads, oldest from 2022, filling the cache pool to 89%.

Classifies every entry as TRACKED / RECENT / JUNK / REDUNDANT / IMPORTABLE / UNMATCHED and acts only on the ones it can justify. The queue is a hard gate: if it cannot be read, the arr is skipped entirely, because without it there is no way to tell an active import from an orphan. A run total over DOWNLOAD_ORPHAN_MAX_DELETE_GB aborts — an abnormally large delete is the visible symptom of a partial queue fetch.

🎭 Wrong-Root Detection — sonarr_classification_scan.sh + radarr_classification_scan.sh

Overseerr lets any user request content into the wrong root folder — kids shows into general TV, anime into Kids_Tv_Shows. These classify every item from metadata alone (genre, certification, network/studio, original language) and report where the computed classification disagrees with the folder the item actually sits in.

Report-only by default. --move acts on forward misplacements and adult-content-in-kids-root leaks. It deliberately does not move non-anime content out of the anime root — deliberate style placements (Western animation grouped with anime by choice) are genuine judgment calls.

Both poll the arr's async move command to completion before verifying, because moveFiles=true flips the database instantly while the physical move is still queued behind others.

🎨 Phantom Artists — lidarr_duplicate_artist_cleanup.sh

MusicBrainz duplicates leave two Lidarr entries for one artist, one holding the files and one holding nothing. Removes only the zero-file side, with deleteFiles=false so nothing on disk is touched. Pairs where both sides hold files are flagged for review, never auto-resolved.

Gated on the tracked-count floor shared with lidarr_cleanup.sh — during a library-wide desync both sides of a real duplicate can read as zero-file phantoms.

🩺 Corruption + Reconciliation — arr_corruption_scan.sh + arr_full_rescan.sh

arr_corruption_scan.sh ffprobes tracked video files for corrupt headers. Report-only unless --remediate, which deletes the file record and triggers an explicit re-search. Requires repeat detections across separate runs before acting, so a transient probe failure cannot delete a healthy file.

arr_full_rescan.sh forces a genuine disk↔database reconciliation. Organic scans only touch files involved in an import, so an untouched library silently drifts — confirmed when Lidarr reported ~23% of its true track count for 1,004 of 1,357 artists with no scan running and every file present on disk.

Cache Warmth — arr_cache_prefill.sh

Populates the shared tracked-data cache at array start and every 4 hours, so consumers never pay a cold fetch. Pure enhancement: nothing depends on it having run, and every consumer still writes through on a cold cache.


━━━ HOW THE SCRIPTS RELATE ━━━

Every 4hr / weekly (arr sync before rsync):
  arr_sync.sh ──────────────── syncs tracked IDs across all nodes
        │                      union model: any node adds → all nodes get it
        │
        └── then rsync spreads the actual files to all nodes
        └── then arr_cleanup removes orphans on all nodes (old paths, removed content)

Daily maintenance window:
  [Media/media_shares_permissions.sh + media_cleaner.sh run first — from Media/]
        │
        ▼
  lidarr_release_fixer.sh ────── reads MBID tag → switches release in Lidarr → RefreshArtist
        │ (corrected albums get imported before cleanup scans for orphans)
        ▼
  lidarr_cleanup.sh ──────────── queries Lidarr API → walks /Music → deletes orphans
  sonarr_cleanup.sh ──────────── queries Sonarr API → walks /Tv_Shows → deletes orphans
  radarr_cleanup.sh ──────────── queries Radarr API → walks /Movies → deletes orphans
        │
        └── each cleanup → notify_emby_scan() → Emby removes ghost entries

Daily recovery:
  arrs_failed_stalled_recovery.sh ── importFailed/stalled → blocklist → re-search

Weekly discovery (WEEKLY_MAINTENANCE_SCRIPTS):
  playback_aware_lidarr_discovery.sh ─ Emby plays → Last.fm similar → top candidates → Lidarr
  playback_aware_radarr_discovery.sh ─ Emby watches → TMDB recommendations → top candidates → Radarr
  playback_aware_sonarr_discovery.sh ─ Emby episodes → TMDB TV recommendations → top candidates → Sonarr
        │
        └── each discovery script fires arr search immediately after successful add

Continuous (started by array_started.sh):
  start_webhook_listener.sh ── Node.js HTTP server listens on WEBHOOK_PORT
        │ arr OnUpgrade fires webhook → POST to http://HOST_LAN_IP:WEBHOOK_PORT/webhook?key=SECRET
        └── upgrade_webhook_handler.sh
              ├── rsync upgraded folder → all remote nodes immediately
              └── trigger arr library rescan on each remote (accept new file, no quality search)

Ad-hoc enrichment:
  lidarr_missing_art.sh ─────── discovers missing artwork → fetches from fanart.tv
  radarr_tmdb_removed.sh ────── status="deleted" → remove from Radarr + add exclusion
  sonarr_tvdb_removed.sh ────── status="deleted" → remove from Sonarr + add exclusion