Media/ now holds only media-level scripts (cleaner, permissions, play_state_sync). All arr management scripts (cleanup, discovery, sync, webhooks, release fixer) live in Arrs_Stack/.
27 KiB
━━━━━ MEDIA — Manual ━━━━━
Config reference, procedures, operational workflows. For overview see README-Media.md. For per-script detail see script headers.
━━━ PERMISSIONS MODEL ━━━
Directories: 755 nobody:users
Owner (nobody) — rwx enter, list, create files
Group (users) — r-x enter and list
Others — r-x Samba guests can browse
No world-write — prevents accidental deletion by unauthenticated access
Files: 664 nobody:users
Owner (nobody) — rw read + write
Group (users) — rw arrs can import, rename, delete
Others — r Samba guests can read
No execute bit — media files are never executable
Two separate passes — not a single recursive chmod. Directories need the execute bit
to enter. Files must never have the execute bit. A single chmod -R 664 would break
directory entry. The script runs find -type d and find -type f separately.
If this script corrects many files on every run, a container has wrong PUID/PGID.
Correct values on unRAID: PUID=99 (nobody) PGID=100 (users). Add to each container's
environment in its Docker template. Common culprits: SABnzbd, qBittorrent, slskd.
Once fixed, this script corrects 0 files per run — it becomes a pure daily failsafe.
━━━ ARR CLEANUP — FILE CLASSIFICATION ━━━
Every file found on disk during an arr cleanup run falls into exactly one category:
TRACKED → arr API returned this exact path → leave it alone
PROTECTED → matches ARR_PROTECTED_PATTERNS → never delete
ORPHAN → media extension, not tracked, old enough → delete
JUNK → not a media extension, not protected → delete (any age)
RECENT → not tracked, under ARR_ORPHAN_AGE days → skip (may be mid-import)
Why protected patterns are critical: arrs generate artwork (*.jpg), metadata
(*.nfo), and subtitles/lyrics that do NOT appear in the tracked file API response.
Without protection, these would be classified as orphans and deleted — removing cover art
from every album, every movie poster, every TV show thumbnail. Requires a full rescan
to recover. Never remove artwork extensions from protected patterns.
━━━ ARR CLEANUP — SAFETY LAYERS ━━━
All 7 layers must pass before any file is touched. There is no way to push through a failed safety check without the explicit override flag.
1. Container running + healthy — a stopped container has an empty API
2. API reachable — no API = no tracked file list = everything looks orphaned
3. API version matches — major version must match tested version in master.conf
4. Item count > 0 — no artists/series/movies = something is wrong with DB
5. Tracked file count > 0 — empty response = everything would be deleted
6. Tracked count >= MIN_TRACKED_PCT — dramatic drop from last run = abort and alert
7. Deletion size < MAX_DELETE_GB — last line of defense against misconfigured root path
Layer 7 is the catastrophic failure prevention. A misconfigured root path — pointing
cleanup at the wrong directory — means the API returns zero tracked files for a root
that actually contains thousands. Everything walks as an orphan. Everything gets deleted.
LIDARR/SONARR/RADARR_MAX_DELETE_GB requires --i-know-what-im-doing to proceed past it.
━━━ CONFIGURATION — master.conf ━━━
Permissions
PERMISSIONS_DIR_MODE="755"
PERMISSIONS_FILE_MODE="664"
PERMISSIONS_OWNER="nobody:users"
Media Cleaner — File Patterns
ANIME_FILE_PATTERNS=(
"*.sfv" # checksum verification — useless after download verified
"*.md5" "*.sha1" # other checksum formats
"*.nfo" # scene info file — not library metadata
"*.url" "*.lnk" # website shortcuts
"*.rar" "*.zip" # source archives kept by some clients after extraction
"*.info" # tool output files
"*.torrent" # torrent descriptor left by some clients
"*.sample*" # scene preview clip
"*.proof*" # screenshot proving encode quality
"*sync-conflict*" # Syncthing conflict copies
"*.scr" "*.exe" # executables — should never be in a media folder
"*.srr" # scene recovery record
"*.log" # tool/client logs
"*.json" # metadata or tool output
)
MEDIA_FILE_PATTERNS=(
"${ANIME_FILE_PATTERNS[@]}" # all anime patterns plus:
"*.iso" # disc images after ripping
"*.lrc" # lyric files in media folders
)
DO NOT add patterns that match media you want to keep:
*.mkv *.mp4 *.avi *.m4v— video files
*.flac *.mp3 *.m4a— audio files
*.srt *.sub *.ass— subtitle files (Bazarr managed)
*.jpg *.png— artwork
Always use--dry-runwhen adding new patterns.
Lidarr Cleanup Thresholds
LIDARR_ORPHAN_AGE=7 # days — files newer than this are RECENT (mid-import window)
LIDARR_MIN_TRACKED_PCT=80 # abort if API returns < 80% of last known count
LIDARR_MAX_DELETE_GB=50 # require --i-know-what-im-doing above this
LIDARR_IMPORT_SCAN_TIMEOUT=600 # seconds to wait for pre-flight import scan
LIDARR_VERSION_MAJOR=3 # expected Lidarr major version (API safety check)
LIDARR_EXTENSIONS=("flac" "mp3" "m4a" "wav" "aac" "ogg" "opus" "wma")
LIDARR_PROTECTED_PATTERNS=("*.jpg" "*.jpeg" "*.png" "*.nfo" "*.lrc")
LIDARR_TRACKED_COUNT_FILE="$DATA_DIR/lidarr_tracked.count" # persistent baseline
ARR_CLEANUP_STATS="$DATA_DIR/arr_cleanup_stats.db" # read by coffee report
Sonarr Cleanup Thresholds
SONARR_ORPHAN_AGE=7
SONARR_MAX_DELETE_GB=50
SONARR_IMPORT_SCAN_TIMEOUT=600
SONARR_VERSION_MAJOR=4
SONARR_EXTENSIONS=("mkv" "mp4" "avi" "m4v" "ts" "wmv" "mov")
SONARR_PROTECTED_PATTERNS=("*.jpg" "*.jpeg" "*.png" "*.nfo" "*.srt" "*.sub" "*.ass" "*.ssa")
Note: *.ts IS in extensions — transport stream is used for Live TV recordings tracked
by Sonarr. Orphaned .ts recordings should be cleaned like any other orphaned episode.
Radarr Cleanup Thresholds
RADARR_ORPHAN_AGE=7
RADARR_MAX_DELETE_GB=50
RADARR_IMPORT_SCAN_TIMEOUT=600
RADARR_VERSION_MAJOR=6
RADARR_EXTENSIONS=("mkv" "mp4" "avi" "m4v" "wmv" "mov")
RADARR_PROTECTED_PATTERNS=("*.jpg" "*.jpeg" "*.png" "*.nfo" "*.srt" "*.sub" "*.ass" "*.ssa")
Arr Sync
ARR_SYNC_ENABLED=true
ARR_SYNC_BLOCKLIST="$DATA_DIR/arr_sync_blocklist.tsv" # tombstone file
ARR_SYNC_CONNECT_TIMEOUT=10 # SSH connect timeout in seconds
ARR_SYNC_API_TIMEOUT=60 # curl API call timeout in seconds
DOCKER_APPDATA_BASE=/mnt/user/appdata
ARR_SYNC_LIDARR_PORT=8686
ARR_SYNC_SONARR_PORT=8989
ARR_SYNC_RADARR_PORT=7878
Arr Recovery
ARR_IMPORT_RECOVERY_AGE=6 # hours — items newer than this are skipped
SONARR_VERSION_MAJOR=4
RADARR_VERSION_MAJOR=6
LIDARR_VERSION_MAJOR=3
ARR_RECOVERY_STATS="$DATA_DIR/arr_recovery_stats.db" # read by coffee report
TMDb / TVDB Removed
RADARR_DROPPED_ADD_EXCLUSION=true # add removed movies to Radarr import exclusion
SONARR_DROPPED_ADD_EXCLUSION=true # add removed series to Sonarr import exclusion
Lidarr Missing Art
FANART_API_KEY="your-fanart-tv-api-key"
LASTFM_API_KEY="your-lastfm-api-key"
LIDARR_ART_MIN_SIZE=5000 # minimum valid download size in bytes
LIDARR_ART_MAX_PARALLEL=4 # concurrent background download jobs
LIDARR_ART_RETRIES=2 # download retry attempts per image
LIDARR_ART_SLEEP_BETWEEN=1 # seconds between fanart.tv API calls (rate limit)
Lidarr Discovery
LIDARR_DISCOVERY_THRESHOLD=70 # minimum score for Stage 1 seeds and Stage 2 adds
LIDARR_DISCOVERY_LOOKBACK_DAYS=7 # Emby play history window in days
LIDARR_DISCOVERY_MIN_PLAYS=3 # min plays before an artist is evaluated as a seed
LIDARR_DISCOVERY_MAX_ADDS=5 # max seeds (Stage 1) and max adds (Stage 2) per run
LIDARR_DISCOVERY_USER_CAP_PCT=35 # max % any one user contributes to play weight
LIDARR_DISCOVERY_REJECT_COOLDOWN=30 # days before re-evaluating a Stage 2 reject
LIDARR_DISCOVERY_HISTORY="$DATA_DIR/lidarr_discovery_history.db"
Requires HOST*_LASTFM_API_KEY in host*.conf.
Radarr Discovery
RADARR_DISCOVERY_THRESHOLD=52 # minimum score to add a candidate
RADARR_DISCOVERY_LOOKBACK_DAYS=30 # Emby watch history window in days
RADARR_DISCOVERY_MAX_SEEDS=5 # max seed movies from Stage 1
RADARR_DISCOVERY_MAX_ADDS=5 # max movies to add per run
RADARR_DISCOVERY_MIN_VOTE_COUNT=100 # min TMDB votes for a candidate
RADARR_DISCOVERY_MIN_RATING=60 # min TMDB vote_average × 10 (60 = 6.0/10)
RADARR_DISCOVERY_REJECT_COOLDOWN=60 # days before re-evaluating a rejected movie
RADARR_DISCOVERY_SEED_LIBRARIES=("Movies") # Emby libraries to draw seeds from
RADARR_DISCOVERY_HISTORY="$DATA_DIR/radarr_discovery_history.db"
Requires HOST*_TMDB_API_KEY in host*.conf.
Sonarr Discovery
SONARR_DISCOVERY_THRESHOLD=52 # minimum score to add a candidate
SONARR_DISCOVERY_LOOKBACK_DAYS=14 # Emby episode history window in days
SONARR_DISCOVERY_MAX_SEEDS=5 # max seed series from Stage 1
SONARR_DISCOVERY_MAX_ADDS=3 # max shows to add per run (TV is a larger commitment)
SONARR_DISCOVERY_MIN_VOTE_COUNT=50 # min TMDB votes for a candidate
SONARR_DISCOVERY_MIN_RATING=65 # min TMDB vote_average × 10 (65 = 6.5/10)
SONARR_DISCOVERY_REJECT_COOLDOWN=60 # days before re-evaluating a rejected show
SONARR_DISCOVERY_USER_EPISODE_CAP=8 # max episodes per user in seed scoring
SONARR_DISCOVERY_MONITOR_MODE="all" # "all" = all seasons monitored; "future" = upcoming only
SONARR_DISCOVERY_HISTORY="$DATA_DIR/sonarr_discovery_history.db"
# SONARR_EMBY_LIBRARIES is shared with emby_to_sonarr_sync — see Orchestrator Job Order
Requires HOST*_TMDB_API_KEY in host*.conf.
MONITOR_MODE note: Use
"all"(default) to have Sonarr search all existing seasons after adding a show."future"only marks upcoming seasons as monitored — shows where all seasons have already aired will appear unmonitored and Sonarr will not search for them.
Play State Sync
PLAY_STATE_SYNC_ENABLED=true # toggle entire sync
PLAY_STATE_SYNC_LOOKBACK_DAYS=30 # history window for played items
Emby and Jellyfin servers configured per-host:
# host*.conf
HOST1_EMBY_URL="http://192.168.50.2:8096"
HOST1_EMBY_API_KEY="..."
HOST1_JELLYFIN_URL="" # empty = skip Jellyfin on this host
HOST1_JELLYFIN_API_KEY=""
Upgrade Webhook
# master.conf
WEBHOOK_PORT=7821 # 0 = disable listener
WEBHOOK_SECRET="" # auto-generated on first start if empty
When WEBHOOK_PORT=0, start_webhook_listener.sh exits cleanly and no listener starts.
When WEBHOOK_SECRET is empty, a 32-byte hex secret is generated on first start and
written back to master.conf. Run Tools/webhook_setup.sh to register the URL in arrs.
Webhook log: /var/log/varaverk/upgrade_webhook.log
Orchestrator Job Order
MEDIA_MAINTENANCE_JOBS=(
"Media/media_shares_permissions.sh" # 1. permissions — always first
"Media/media_cleaner.sh anime" # 2. junk removal — before orphan scan
"Media/media_cleaner.sh media" # 3.
"Arrs_Stack/lidarr_cleanup.sh" # 4. arr cleanup — after permissions + clean
"Arrs_Stack/sonarr_cleanup.sh" # 5.
"Arrs_Stack/radarr_cleanup.sh" # 6.
)
━━━ CONFIGURATION — host*.conf ━━━
host1.conf
# Shares this server applies permissions to
HOST1_MEDIA_PERMISSION_SHARES=(
"/mnt/user/Movies"
"/mnt/user/Tv_Shows"
"/mnt/user/Music"
"/mnt/user/Kids_Movies"
"/mnt/user/Kids_Tv_Shows"
"/mnt/user/Sports"
"/mnt/user/stand-up_comedy"
)
# Folders cleaned by each profile
HOST1_ANIME_CLEAN_FOLDERS=(
"/mnt/user/Anime_Movies"
"/mnt/user/Anime_Movies-Old"
"/mnt/user/Anime_Shows"
"/mnt/user/Anime_Shows-Old"
)
HOST1_MEDIA_CLEAN_FOLDERS=(
"/mnt/user/Kids_Movies"
"/mnt/user/Kids_Tv_Shows"
"/mnt/user/Movies"
"/mnt/user/Music"
"/mnt/user/Sports"
"/mnt/user/stand-up_comedy"
"/mnt/user/Tv_Shows"
)
# Arr connection details — must match arr settings exactly
HOST1_LIDARR_URL="http://192.168.50.2:8686"
HOST1_LIDARR_API_KEY="..."
HOST1_LIDARR_MUSIC_ROOT="/mnt/user/Music"
HOST1_LIDARR_PATH_MAP="" # container→host path translation if needed
HOST1_SONARR_URL="http://192.168.50.2:8989"
HOST1_SONARR_API_KEY="..."
HOST1_SONARR_TV_ROOT="/mnt/user/Tv_Shows"
HOST1_SONARR_PATH_MAP=""
HOST1_RADARR_URL="http://192.168.50.2:7878"
HOST1_RADARR_API_KEY="..."
HOST1_RADARR_MOVIES_ROOT="/mnt/user/Movies"
HOST1_RADARR_PATH_MAP=""
HOST1_EMBY_URL="http://192.168.50.2:8096"
HOST1_EMBY_API_KEY="..."
LIDARR/SONARR/RADARR_MUSIC/TV/MOVIES_ROOT must exactly match the Root Folder path in the arr's own settings. Arr UI → Settings → Media Management → Root Folders. A mismatch means every file on disk looks untracked — all appear as orphans. MAX_DELETE_GB is the only thing standing between a path mismatch and losing your library.
━━━ SAFE TESTING PROCEDURE ━━━
The arr cleanup scripts permanently delete files. There is no recycle bin, no undo. Follow this procedure on first use, after any root path change, after any API key change, and after any significant arr library change.
Step 1 — Dry Run With Full Logging
lidarr_cleanup.sh --dry-run --log
sonarr_cleanup.sh --dry-run --log
radarr_cleanup.sh --dry-run --log
Step 2 — Review the Output
Are TRACKED files the ones you expect?
→ Known arr-managed files should show as TRACKED
→ If they show as ORPHAN, the root path is wrong — STOP
Is the ORPHAN count reasonable?
→ Healthy cleanup removes dozens to hundreds, not tens of thousands
→ Large count = stop, investigate root path before proceeding
Are PROTECTED patterns working?
→ Artwork (*.jpg) and subtitles (*.srt) must show as PROTECTED
→ If they show as ORPHAN, check PROTECTED_PATTERNS config
Are RECENT files being correctly skipped?
→ Files downloaded in the last 7 days should show as RECENT, not ORPHAN
Step 3 — Check Numbers if Something Looks Wrong
# Root path mismatch? Compare these:
# Lidarr UI: Settings → Media Management → Root Folders
# Sonarr UI: Settings → Media Management → Root Folders
# Radarr UI: Settings → Media Management → Root Folders
# Must exactly match LIDARR_MUSIC_ROOT / SONARR_TV_ROOT / RADARR_MOVIES_ROOT
# Is the arr running?
docker ps | grep -E "Lidarr|Sonarr|Radarr"
# Library scan not complete?
# Trigger manual scan in arr UI and wait for completion
Step 4 — Run Live
# Only after dry run review passes.
lidarr_cleanup.sh
sonarr_cleanup.sh
radarr_cleanup.sh
Step 5 — Verify in Arr UI
Library count — should not have dropped significantly
healthy cleanup removes a small number, not a large percentage
Missing files — check if any monitored content shows as missing
Emby library — should show no ghost entries (notify_emby_scan handles this automatically)
━━━ PROCEDURES ━━━
Adding a New Arr
# 1. Copy radarr_cleanup.sh as template
cp radarr_cleanup.sh readarr_cleanup.sh
# 2. Replace RADARR_ prefix with READARR_ throughout
# Update API endpoint, tracked file API path, extension list, protected patterns
# 3. Add to host*.conf
HOST1_READARR_URL="http://192.168.50.2:8787"
HOST1_READARR_API_KEY="your-api-key"
HOST1_READARR_BOOKS_ROOT="/mnt/user/Books"
# 4. Add thresholds to master.conf
READARR_ORPHAN_AGE=7
READARR_MAX_DELETE_GB=50
READARR_EXTENSIONS=("epub" "pdf" "mobi" "azw3" "cbz" "cbr")
READARR_PROTECTED_PATTERNS=("*.jpg" "*.jpeg" "*.png" "*.nfo")
# 5. Add to MEDIA_MAINTENANCE_JOBS in master.conf
MEDIA_MAINTENANCE_JOBS=(
...existing jobs...
"Media/readarr_cleanup.sh"
)
media_management.sh picks it up automatically. No orchestrator changes needed.
Run --dry-run --log before scheduling.
Managing the Arr Sync Blocklist
# Add item to blocklist (removes from all arrs + tombstones the ID)
arr_sync.sh --blocklist-add lidarr <musicbrainz-artist-id> "reason"
arr_sync.sh --blocklist-add sonarr <tvdb-series-id> "reason"
arr_sync.sh --blocklist-add radarr <tmdb-movie-id> "reason"
# Remove from blocklist (un-tombstones the ID — does NOT re-add to arrs)
arr_sync.sh --blocklist-remove lidarr <id>
# View all blocklisted IDs
arr_sync.sh --blocklist-list
--blocklist-add is the only destructive operation — it simultaneously:
- Writes the tombstone entry to the blocklist TSV file
- Deletes the item from the local arr API (no file deletion)
- SSHes each remote node and deletes from their arr API
Files become orphans on all nodes — arr_cleanup removes them on the next run.
━━━ TROUBLESHOOTING ━━━
Arr Cleanup Deleting Files It Shouldn't
1. Check the protected patterns — artwork and subtitles must be listed
LIDARR_PROTECTED_PATTERNS / SONARR_PROTECTED_PATTERNS / RADARR_PROTECTED_PATTERNS
2. Check the root path matches arr settings exactly
Run: lidarr_cleanup.sh --status (shows configured root path)
Compare: Lidarr UI → Settings → Media Management → Root Folders
3. Check if files are truly orphaned
Run: lidarr_cleanup.sh --dry-run --log
Look for the specific file — verify it shows ORPHAN, not PROTECTED or TRACKED
Arr Cleanup Aborting at Safety Layer 6 (Tracked Count Drop)
API returned far fewer tracked files than last run.
Possible causes:
- Arr database was recently rebuilt from scratch
- Large manual library removal
- Path map mismatch after arr migration
If intentional (library intentionally reduced):
Delete LIDARR_TRACKED_COUNT_FILE to reset the baseline
Run cleanup once — it will establish a new baseline
If unintentional:
Investigate before proceeding — the arr may have a problem
Arr Sync Not Picking Up New Content
Is ARR_SYNC_ENABLED=true in master.conf?
Can this host SSH to the remote without password?
→ ssh -i [SSH_KEY] root@[remote-tailscale-ip] "hostname"
Is the arr accessible on the remote?
→ arr_sync.sh --status (shows each node's arr reachability)
→ arr_sync.sh --log (verbose output per-node, per-arr)
Is the item in the blocklist?
→ arr_sync.sh --blocklist-list
Emby Still Showing Ghost Entries After Cleanup
notify_emby_scan() is called automatically after every arr cleanup deletion.
If ghosts persist:
1. Is Emby's API responding?
curl -s "http://[emby-ip]:8096/System/Info/Public"
2. Is EMBY_URL / EMBY_API_KEY correct in host*.conf?
Run: sonarr_cleanup.sh --status (shows Emby config)
3. Trigger manually in Emby:
Library → Manage Library → Clean Missing Files
━━━ OUTPUT TIERS ━━━
All scripts have two output levels controlled by --log.
Without --log, each script processes silently and always concludes with a summary
block: identity, duration, counts (files removed, items added, arrs cleaned), and a
status line. Warnings and errors are always visible.
With --log, per-item detail appears: individual titles being processed, API query
progress, per-node sync results, and per-file examination output. Use this when
debugging unexpected results or validating configuration before the first scheduled run.
Dry-run output follows the same tiers — --dry-run alone shows the summary of what
would happen; --dry-run --log shows the full per-item preview list.
━━━ FLAG REFERENCE ━━━
media_shares_permissions.sh
media_shares_permissions.sh
Apply correct ownership and permissions to all configured media shares. Safe to run
manually at any time — idempotent, only changes what's wrong.
media_shares_permissions.sh --dry-run
Show how many files and directories would be corrected per share. If unexpectedly large,
check container PUID/PGID settings first (PUID=99 PGID=100).
media_shares_permissions.sh --status
Show configured share list and ownership of the share roots.
media_shares_permissions.sh --log
Show ownership correction count per share and per file (verbose).
On large libraries this runs 20-30 minutes. This is expected — millions of files with recursive walk takes time. Designed to run overnight in the maintenance window.
media_cleaner.sh
media_cleaner.sh anime
Remove junk files from anime share folders using ANIME_FILE_PATTERNS.
media_cleaner.sh media
Remove junk files from media share folders using MEDIA_FILE_PATTERNS.
media_cleaner.sh [profile] --dry-run
Show what would be deleted without removing anything. Always run first when adding new
patterns or folders.
media_cleaner.sh [profile] --status
Show folder list and file patterns for the profile.
media_cleaner.sh [profile] --log
Show every file examined, not just those removed.
lidarr_cleanup.sh / sonarr_cleanup.sh / radarr_cleanup.sh
[script] --dry-run --log
Preview every classification decision. Always run this first. See Safe Testing Procedure.
[script]
Live run — deletes confirmed orphans and junk, triggers Emby clean.
[script] --log
Live run with verbose per-file output.
[script] --status
Show configuration, API status, tracked file count, and last run stats.
[script] --i-know-what-im-doing
Bypass the MAX_DELETE_GB size threshold. Required when deletion exceeds the configured
limit. Long flag name is intentional — cannot be added accidentally.
[script] --skip-strike-list
Bypass the ORPHAN_AGE age check. Deletes RECENT files too — files that are under the
age threshold. Use when you know recent downloads are actually orphans.
[script] --i-know-what-im-doing --skip-strike-list
NUCLEAR MODE — age check and size threshold both bypassed. Deletes on first pass.
Use when you want a clean one-pass wipe of everything the arr doesn't track.
No recovery possible after deletion.
play_state_sync.sh
play_state_sync.sh
Sync played/unplayed state and resume positions from local Emby to the remote Emby.
Only items played within PLAY_SYNC_DAYS are synced.
play_state_sync.sh --full
Ignore PLAY_SYNC_DAYS — sync all played items regardless of age. May be slow on large
libraries. Use after a new Emby install or database restore to rebuild full play history.
play_state_sync.sh --dry-run
Show what would be synced without writing any state.
play_state_sync.sh --status
Show configured servers, reachability, and user counts.
play_state_sync.sh --log
Verbose output — show each item comparison.
arrs_failed_stalled_recovery.sh
arrs_failed_stalled_recovery.sh
Check all configured arrs for failed imports and stalled downloads. Blocklist + remove +
re-search for each problem item.
arrs_failed_stalled_recovery.sh --dry-run
Show what would be actioned per arr without making any changes.
arrs_failed_stalled_recovery.sh --status
Show configuration, arr reachability, and last recovery stats.
arrs_failed_stalled_recovery.sh --log
Verbose output per item per arr.
arr_sync.sh
arr_sync.sh
Sync all arr types across all configured nodes.
arr_sync.sh --dry-run
Show what would be added/removed on each node without making changes.
arr_sync.sh --status
Show node configuration, arr reachability, and blocklist count.
arr_sync.sh --log
Verbose per-node, per-arr output.
arr_sync.sh --blocklist-add [arr] [id] "[reason]"
Remove item from all arrs and tombstone the ID. See Procedures above.
arr_sync.sh --blocklist-remove [arr] [id]
Remove tombstone — does NOT re-add item to arrs.
arr_sync.sh --blocklist-list
Show all tombstoned IDs.
radarr_tmdb_removed.sh / sonarr_tvdb_removed.sh
[script]
Remove records for entries with status="deleted" (dropped from upstream database).
Files are kept. Import exclusion is added.
[script] --delete-files
Also delete associated files from disk. Most dropped entries have no files — they were
announced movies/series that were never downloaded.
[script] --dry-run
Preview what would be removed without making changes.
[script] --status
Show arr connection status and current count of dropped entries.
[script] --log
Verbose per-entry output.
lidarr_missing_art.sh
lidarr_missing_art.sh
Fetch all missing album and artist artwork from fanart.tv and fallback sources.
Never overwrites existing files.
lidarr_missing_art.sh --dry-run
Show what would be downloaded without writing any files.
lidarr_missing_art.sh --status
Show configuration and API key status.
lidarr_missing_art.sh --log
Verbose per-album, per-artist output.
playback_aware_lidarr_discovery.sh
playback_aware_lidarr_discovery.sh
Score Emby play history, run Last.fm getSimilar on top artists, add candidates above
threshold to Lidarr. Triggers ArtistSearch immediately after each successful add.
playback_aware_lidarr_discovery.sh --dry-run
Score and rank all Stage 1 seeds and Stage 2 candidates. No Lidarr API calls. No writes
to history file. Shows exactly what would be added and at what score.
playback_aware_lidarr_discovery.sh --status
Show config values, history file path and size, and API key status.
playback_aware_lidarr_discovery.sh --log
Verbose per-artist scoring output for both stages.
playback_aware_radarr_discovery.sh
playback_aware_radarr_discovery.sh
Score recently watched Emby movies, run TMDB recommendations on seeds, add candidates
above threshold to Radarr. Triggers MoviesSearch immediately after each successful add.
playback_aware_radarr_discovery.sh --dry-run
Score and rank all Stage 1 seeds and Stage 2 candidates. No Radarr API calls. No writes
to history file.
playback_aware_radarr_discovery.sh --status
Show config values, history file path and size, and API key status.
playback_aware_radarr_discovery.sh --log
Verbose per-movie scoring output for both stages.
playback_aware_sonarr_discovery.sh
playback_aware_sonarr_discovery.sh
Score recently watched Emby series (weighted by user diversity), run TMDB TV
recommendations on seeds, add candidates above threshold to Sonarr. Triggers SeriesSearch
immediately after each successful add.
playback_aware_sonarr_discovery.sh --dry-run
Score and rank all Stage 1 seeds and Stage 2 candidates. No Sonarr API calls. No writes
to history file.
playback_aware_sonarr_discovery.sh --status
Show config values, history file path and size, and API key status.
playback_aware_sonarr_discovery.sh --log
Verbose per-series scoring output — shows user diversity, recency, and volume scores per
seed; breadth, rating, and votes scores per candidate.