feat: slskd reconnect guard in downloaders_reset, mass v2 sync
- downloaders_reset: connection check block before slskd API sections; triggers PUT /api/v0/server reconnect if disconnected, polls 60s, gates Stuck Searches and Dead Transfer Records on SLSKD_CONNECTED - Sync all modified/new/deleted files from v2 refactor across Docker_Essentials, Media, Monitors, Partnership, Rsync, Tools, Transcodes, unRAID_Essentials, common.sh, master confs, and new Manual/README docs
This commit is contained in:
@@ -0,0 +1,636 @@
|
||||
# ━━━━━ 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
|
||||
|
||||
```bash
|
||||
PERMISSIONS_DIR_MODE="755"
|
||||
PERMISSIONS_FILE_MODE="664"
|
||||
PERMISSIONS_OWNER="nobody:users"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Media Cleaner — File Patterns
|
||||
|
||||
```bash
|
||||
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-run` when adding new patterns.
|
||||
|
||||
---
|
||||
|
||||
### Lidarr Cleanup Thresholds
|
||||
|
||||
```bash
|
||||
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=/boot/config/lidarr_tracked_count # persistent baseline
|
||||
ARR_CLEANUP_STATS=/boot/config/arr_cleanup_stats.db # read by coffee report
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Sonarr Cleanup Thresholds
|
||||
|
||||
```bash
|
||||
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
|
||||
|
||||
```bash
|
||||
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
|
||||
|
||||
```bash
|
||||
ARR_SYNC_ENABLED=true
|
||||
ARR_SYNC_BLOCKLIST=/boot/config/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
|
||||
|
||||
```bash
|
||||
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=/boot/config/arr_recovery_stats.db # read by coffee report
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### TMDb / TVDB Removed
|
||||
|
||||
```bash
|
||||
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
|
||||
|
||||
```bash
|
||||
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)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Orchestrator Job Order
|
||||
|
||||
```bash
|
||||
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.
|
||||
"Media/lidarr_cleanup.sh" # 4. arr cleanup — after permissions + clean
|
||||
"Media/sonarr_cleanup.sh" # 5.
|
||||
"Media/radarr_cleanup.sh" # 6.
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ━━━ CONFIGURATION — master_host*.conf ━━━
|
||||
|
||||
### master_host1.conf
|
||||
|
||||
```bash
|
||||
# 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
|
||||
|
||||
```bash
|
||||
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
|
||||
|
||||
```bash
|
||||
# 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
|
||||
|
||||
```bash
|
||||
# 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
|
||||
|
||||
```bash
|
||||
# 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 master_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
|
||||
|
||||
```bash
|
||||
# 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:
|
||||
1. Writes the tombstone entry to the blocklist TSV file
|
||||
2. Deletes the item from the local arr API (no file deletion)
|
||||
3. 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 master_host*.conf?
|
||||
Run: sonarr_cleanup.sh --status (shows Emby config)
|
||||
|
||||
3. Trigger manually in Emby:
|
||||
Library → Manage Library → Clean Missing Files
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ━━━ 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.
|
||||
|
||||
---
|
||||
|
||||
### 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.
|
||||
+168
-925
File diff suppressed because it is too large
Load Diff
+92
-54
@@ -1,78 +1,116 @@
|
||||
#!/bin/bash
|
||||
# ==============================================================================================
|
||||
# ================================= ARR SYNC ===================================================
|
||||
# ================================= ARR Sync ===================================================
|
||||
# ==============================================================================================
|
||||
# Bidirectional arr library sync across all nodes in the ecosystem.
|
||||
# Syncs Lidarr, Sonarr, and Radarr libraries so every node tracks the same content.
|
||||
# Run before rsync — once arrs agree on library, rsync spreads the files.
|
||||
#
|
||||
# ── DESIGN ────────────────────────────────────────────────────────────────────────────────────
|
||||
# Full mesh: every node syncs with every other node — no primary, no hierarchy.
|
||||
# PURPOSE
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Full-mesh arr library sync across all nodes — Lidarr, Sonarr, and Radarr.
|
||||
# Every node syncs with every other, union model, no hierarchy. Run before
|
||||
# rsync in the weekly sync window: once arrs agree on what to track, rsync
|
||||
# spreads the actual files.
|
||||
#
|
||||
# ==============================================================================================
|
||||
# OPERATIONAL MODEL
|
||||
# ==============================================================================================
|
||||
#
|
||||
# Full mesh: every node syncs with every other — no primary, no hierarchy.
|
||||
# Union model: if any node tracks an item, all nodes get it (unless blocklisted).
|
||||
# Convergence: any node can add content; after one full cycle all nodes agree.
|
||||
# Upgrade-aware: server1 upgrades a file → arr tracks new path → rsync spreads it →
|
||||
# arr_cleanup removes old file on all nodes because arr no longer tracks it.
|
||||
# Upgrade-aware: a file upgrade on one node → arr tracks new path → rsync spreads
|
||||
# it → arr_cleanup removes old path on all nodes (arr no longer tracks it).
|
||||
#
|
||||
# ── NODE DISCOVERY ────────────────────────────────────────────────────────────────────────────
|
||||
# Reads HOST* vars from master.conf. Add HOST3= and it joins the sync automatically.
|
||||
# No scripts change when adding a new node.
|
||||
# Node discovery: reads HOST* vars from master.conf. Add HOST3= and it joins the
|
||||
# sync automatically — no script changes needed for a new node.
|
||||
#
|
||||
# ── REMOTE API KEY ACCESS ─────────────────────────────────────────────────────────────────────
|
||||
# Remote API keys are not stored anywhere. Script SSHes to each remote node and reads
|
||||
# the key directly from that arr's config.xml in its appdata directory.
|
||||
# Only the API response (JSON) is returned — key never leaves the remote node.
|
||||
# Self-maintaining: remote key regeneration is picked up automatically.
|
||||
# Graceful skip: arr not configured locally → skip cleanly. Arr not reachable on
|
||||
# a remote → skip that node for that arr type, continue with others.
|
||||
#
|
||||
# ── BLOCKLIST ─────────────────────────────────────────────────────────────────────────────────
|
||||
# ARR_SYNC_BLOCKLIST in DATA_DIR tombstones IDs that must never be re-added anywhere.
|
||||
# Read from ALL nodes via SSH at start of each run — immediate effect across all nodes.
|
||||
# (Each node reads every other node's blocklist file via SSH — no rsync delay.)
|
||||
# Manage via --blocklist-add / --blocklist-remove / --blocklist-list.
|
||||
#
|
||||
# --blocklist-add does three things atomically:
|
||||
# 1. Writes the TSV tombstone entry (prevents future re-adds by arr_sync)
|
||||
# 2. Deletes the item from the local arr API (deleteFiles=false)
|
||||
# 3. SSHes each remote node and deletes from their arr API (deleteFiles=false)
|
||||
# Files become orphans on all nodes — arr_cleanup.sh removes them on next run.
|
||||
#
|
||||
# ── GRACEFUL SKIP ─────────────────────────────────────────────────────────────────────────────
|
||||
# Arr not configured locally → skip cleanly, no error.
|
||||
# Arr not reachable on a remote → skip that node for that arr type, continue with others.
|
||||
# Partial mesh works — nodes that share an arr type sync with each other.
|
||||
#
|
||||
# ── WHAT GETS SYNCED ──────────────────────────────────────────────────────────────────────────
|
||||
# Library items (tracked artists/series/movies) keyed on stable IDs:
|
||||
# What gets synced — library items keyed on stable external IDs:
|
||||
# Lidarr — MusicBrainz artist ID (foreignArtistId)
|
||||
# Sonarr — TVDB series ID (tvdbId)
|
||||
# Radarr — TMDB movie ID (tmdbId)
|
||||
# When adding to a remote node, that node's own quality profile, metadata profile,
|
||||
# and root folder path are used — settings are never copied from the source node.
|
||||
# When adding to a remote, that node's own quality profile, metadata profile,
|
||||
# and root folder path are used — settings are never copied from source.
|
||||
#
|
||||
# ==============================================================================================
|
||||
# DESIGN PRINCIPLES
|
||||
# ==============================================================================================
|
||||
#
|
||||
# Remote API Keys Never Stored
|
||||
# SSHes to each remote node and reads the key directly from that arr's
|
||||
# config.xml in its appdata directory. Only the API response (JSON) is
|
||||
# returned — the key never leaves the remote node. Self-maintaining: key
|
||||
# regeneration on the remote is picked up automatically next run.
|
||||
#
|
||||
# Blocklist TSV
|
||||
# ARR_SYNC_BLOCKLIST in DATA_DIR tombstones IDs that must never be re-added
|
||||
# anywhere. Read from ALL nodes via SSH at run start — immediate effect with
|
||||
# no rsync delay.
|
||||
#
|
||||
# --blocklist-add does three things atomically:
|
||||
# 1. Writes the TSV tombstone entry (prevents future re-adds by arr_sync)
|
||||
# 2. Deletes the item from the local arr API (deleteFiles=false)
|
||||
# 3. SSHes each remote node and deletes from their arr API (deleteFiles=false)
|
||||
# Files become orphans on all nodes — arr_cleanup removes them on next run.
|
||||
#
|
||||
# ==============================================================================================
|
||||
# OPERATIONAL SAFEGUARDS
|
||||
# ==============================================================================================
|
||||
#
|
||||
# acquire_lock — prevents two sync instances running simultaneously
|
||||
# ARR_SYNC_ENABLED — global gate, exits cleanly when false
|
||||
# SSH connect timeout — ARR_SYNC_CONNECT_TIMEOUT — does not hang on unreachable node
|
||||
# API call timeout — ARR_SYNC_API_TIMEOUT — does not hang on slow arr
|
||||
# Graceful skip — unreachable node/arr → skip and continue, never abort
|
||||
# Blocklist gate — item in blocklist → never added to any node
|
||||
# Silent by default — only additions produce output, clean runs stay silent
|
||||
#
|
||||
# ==============================================================================================
|
||||
# STATE FILES
|
||||
# ==============================================================================================
|
||||
#
|
||||
# ARR_SYNC_BLOCKLIST — TSV file in DATA_DIR (default: DATA_DIR/arr_sync_blocklist.tsv)
|
||||
# Columns: arr_type, id, reason, date_added
|
||||
# Read from all nodes via SSH at the start of each run.
|
||||
#
|
||||
# ==============================================================================================
|
||||
# CONFIGURATION
|
||||
# ==============================================================================================
|
||||
#
|
||||
# master_host*.conf
|
||||
#
|
||||
# HOST*_LIDARR_URL / HOST*_LIDARR_API_KEY — local Lidarr (aliased by detect_hosts)
|
||||
# HOST*_SONARR_URL / HOST*_SONARR_API_KEY — local Sonarr
|
||||
# HOST*_RADARR_URL / HOST*_RADARR_API_KEY — local Radarr
|
||||
#
|
||||
# master.conf
|
||||
#
|
||||
# ARR_SYNC_ENABLED — global on/off toggle (default: true)
|
||||
# ARR_SYNC_BLOCKLIST — path to TSV blocklist file
|
||||
# ARR_SYNC_CONNECT_TIMEOUT — SSH connect timeout in seconds (default: 10)
|
||||
# ARR_SYNC_API_TIMEOUT — curl API call timeout in seconds (default: 60)
|
||||
# DOCKER_APPDATA_BASE — base path for arr appdata dirs (default: /mnt/user/appdata)
|
||||
# ARR_SYNC_LIDARR_PORT — Lidarr port on all nodes (default: 8686)
|
||||
# ARR_SYNC_SONARR_PORT — Sonarr port on all nodes (default: 8989)
|
||||
# ARR_SYNC_RADARR_PORT — Radarr port on all nodes (default: 7878)
|
||||
#
|
||||
# ==============================================================================================
|
||||
# RUNTIME MODES
|
||||
# ==============================================================================================
|
||||
#
|
||||
# ── USAGE ─────────────────────────────────────────────────────────────────────────────────────
|
||||
# arr_sync.sh — sync all arr types, all nodes
|
||||
# arr_sync.sh --dry-run — preview only, no changes
|
||||
# arr_sync.sh --log — verbose output
|
||||
# arr_sync.sh --status — show config and exit
|
||||
# arr_sync.sh --blocklist-add lidarr <mbid> "reason" — remove from all arrs + tombstone
|
||||
#
|
||||
# Blocklist management:
|
||||
# arr_sync.sh --blocklist-add lidarr <mbid> "reason" — remove from all arrs + tombstone
|
||||
# arr_sync.sh --blocklist-add sonarr <tvdbId> "reason" — remove from all arrs + tombstone
|
||||
# arr_sync.sh --blocklist-add radarr <tmdbId> "reason" — remove from all arrs + tombstone
|
||||
# arr_sync.sh --blocklist-remove lidarr <id> — un-tombstone (does NOT re-add)
|
||||
# arr_sync.sh --blocklist-list — show all blocklisted IDs
|
||||
# arr_sync.sh --blocklist-list — show all blocklisted IDs
|
||||
#
|
||||
# ── CONFIGURATION (master.conf) ───────────────────────────────────────────────────────────────
|
||||
# ARR_SYNC_ENABLED — global on/off toggle (default true)
|
||||
# ARR_SYNC_BLOCKLIST — path to TSV blocklist (default: DATA_DIR/arr_sync_blocklist.tsv)
|
||||
# ARR_SYNC_CONNECT_TIMEOUT — SSH connect timeout in seconds (default 10)
|
||||
# ARR_SYNC_API_TIMEOUT — curl API call timeout in seconds (default 60)
|
||||
# DOCKER_APPDATA_BASE — base path for arr appdata dirs (default /mnt/user/appdata)
|
||||
# ARR_SYNC_LIDARR_PORT — Lidarr port on all nodes (default 8686)
|
||||
# ARR_SYNC_SONARR_PORT — Sonarr port on all nodes (default 8989)
|
||||
# ARR_SYNC_RADARR_PORT — Radarr port on all nodes (default 7878)
|
||||
#
|
||||
# ── CONFIGURATION (master_host*.conf) ─────────────────────────────────────────────────────────
|
||||
# HOST*_LIDARR_URL / HOST*_LIDARR_API_KEY — local Lidarr (aliased by detect_hosts)
|
||||
# HOST*_SONARR_URL / HOST*_SONARR_API_KEY — local Sonarr
|
||||
# HOST*_RADARR_URL / HOST*_RADARR_API_KEY — local Radarr
|
||||
# ==============================================================================================
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
@@ -2,13 +2,19 @@
|
||||
# ==============================================================================================
|
||||
# ========================= Arrs Failed / Stalled Recovery =====================================
|
||||
# ==============================================================================================
|
||||
# Automatically detects and recovers from failed imports and stalled downloads
|
||||
# across Sonarr, Radarr, and Lidarr. Blocklists the bad release and triggers
|
||||
# a new search — hands-free recovery while you sleep.
|
||||
#
|
||||
# ── WHAT IT CHECKS ────────────────────────────────────────────────────────────────────────────
|
||||
# Four problem types from the arr queue API:
|
||||
# importFailed — downloaded successfully but arr couldn't import the file
|
||||
# PURPOSE
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Detect and recover failed imports and stalled downloads across Sonarr, Radarr,
|
||||
# and Lidarr. Blocklists the bad release and triggers a re-search — hands-free
|
||||
# overnight recovery.
|
||||
#
|
||||
# ==============================================================================================
|
||||
# OPERATIONAL MODEL
|
||||
# ==============================================================================================
|
||||
#
|
||||
# Four problem types detected from the arr queue API:
|
||||
# importFailed — downloaded but arr couldn't import the file
|
||||
# importPending — downloaded, stuck waiting to import (will not self-resolve)
|
||||
# error status — serious failure not covered by the above two states
|
||||
# stalled — download stuck with no connections or no progress
|
||||
@@ -16,55 +22,65 @@
|
||||
# Never touches items with state "downloading" or "imported" — safe to run anytime.
|
||||
# Items newer than ARR_IMPORT_RECOVERY_AGE are skipped — gives arr time to retry first.
|
||||
#
|
||||
# ── WHAT IT DOES PER PROBLEM ITEM ─────────────────────────────────────────────────────────────
|
||||
# Per problem item (3-step response):
|
||||
# 1. Blocklist the release — prevents re-grabbing the same bad release
|
||||
# 2. Remove from queue — cleans up the failed item
|
||||
# 3. Trigger new search — finds a different release automatically
|
||||
#
|
||||
# ── HOST AWARENESS ────────────────────────────────────────────────────────────────────────────
|
||||
# detect_hosts() sets MY_ID and aliases all arr vars:
|
||||
# SONARR_URL / SONARR_API_KEY / SONARR_RECOVERY
|
||||
# RADARR_URL / RADARR_API_KEY / RADARR_RECOVERY
|
||||
# LIDARR_URL / LIDARR_API_KEY / LIDARR_RECOVERY (HOST1 only — exits cleanly on HOST2)
|
||||
# No manual HOST1/HOST2 comparisons needed — MY_ID routes automatically.
|
||||
# ==============================================================================================
|
||||
# OPERATIONAL SAFEGUARDS
|
||||
# ==============================================================================================
|
||||
#
|
||||
# ── API VERSION SAFETY ────────────────────────────────────────────────────────────────────────
|
||||
# check_arr_version() verifies the running arr matches the tested major version in master.conf.
|
||||
# If the API structure changed after an upgrade — exits rather than silently misoperating.
|
||||
# acquire_lock — prevents concurrent runs overlapping
|
||||
# jq validation — exits if jq not installed (required for JSON parsing)
|
||||
# API pre-flight — checks each arr is reachable before querying queue
|
||||
# Version check — check_arr_version() verifies running arr matches master.conf major
|
||||
# version; exits rather than silently misoperating after upgrade
|
||||
# Age threshold — skips items newer than ARR_IMPORT_RECOVERY_AGE (default 6hr)
|
||||
# Silent by default — only problems produce output, clean arrs stay silent
|
||||
#
|
||||
# API version mapping (endpoint paths differ from major version labels):
|
||||
# Sonarr v4 → /api/v3/ (v3 endpoint retained in v4)
|
||||
# Radarr v6 → /api/v3/ (v3 endpoint retained in v6)
|
||||
# Lidarr v3 → /api/v1/ (different from Sonarr/Radarr)
|
||||
#
|
||||
# ── SAFEGUARDS ────────────────────────────────────────────────────────────────────────────────
|
||||
# acquire_lock — prevents concurrent runs overlapping
|
||||
# jq validation — exits if jq not installed (required for JSON parsing)
|
||||
# API pre-flight — checks each arr is reachable before querying queue
|
||||
# Version check — verifies arr major version matches tested version in master.conf
|
||||
# Age threshold — skips items newer than ARR_IMPORT_RECOVERY_AGE (default 6hr)
|
||||
# Silent by default — only problems produce output, clean arrs stay silent
|
||||
# ==============================================================================================
|
||||
# STATE FILES
|
||||
# ==============================================================================================
|
||||
#
|
||||
# ARR_RECOVERY_STATS — stats file written after each run (read by coffee report)
|
||||
#
|
||||
# ==============================================================================================
|
||||
# CONFIGURATION
|
||||
# ==============================================================================================
|
||||
#
|
||||
# master_host*.conf
|
||||
#
|
||||
# ── CONFIGURATION (master_host*.conf) ─────────────────────────────────────────────────────────
|
||||
# HOST*_SONARR_URL / HOST*_SONARR_API_KEY / HOST*_SONARR_RECOVERY
|
||||
# HOST*_RADARR_URL / HOST*_RADARR_API_KEY / HOST*_RADARR_RECOVERY
|
||||
# HOST1_LIDARR_URL / HOST1_LIDARR_API_KEY / HOST1_LIDARR_RECOVERY
|
||||
# All aliased by detect_hosts() — script uses unprefixed names
|
||||
#
|
||||
# ── CONFIGURATION (master.conf) ───────────────────────────────────────────────────────────────
|
||||
# ARR_IMPORT_RECOVERY_AGE — hours before item is eligible (default 6)
|
||||
# SONARR_VERSION_MAJOR — expected Sonarr major version (e.g. 4)
|
||||
# RADARR_VERSION_MAJOR — expected Radarr major version (e.g. 6)
|
||||
# LIDARR_VERSION_MAJOR — expected Lidarr major version (e.g. 3)
|
||||
# ARR_RECOVERY_STATS — stats file path (read by coffee report)
|
||||
# master.conf
|
||||
#
|
||||
# ARR_IMPORT_RECOVERY_AGE — hours before item is eligible for recovery (default: 6)
|
||||
# SONARR_VERSION_MAJOR — expected Sonarr major version (e.g. 4)
|
||||
# RADARR_VERSION_MAJOR — expected Radarr major version (e.g. 6)
|
||||
# LIDARR_VERSION_MAJOR — expected Lidarr major version (e.g. 3)
|
||||
# ARR_RECOVERY_STATS — stats file path (read by coffee report)
|
||||
#
|
||||
# ==============================================================================================
|
||||
# RUNTIME MODES
|
||||
# ==============================================================================================
|
||||
#
|
||||
# ── USAGE ─────────────────────────────────────────────────────────────────────────────────────
|
||||
# arrs_failed_stalled_recovery.sh — normal run
|
||||
# arrs_failed_stalled_recovery.sh --dry-run — show what would be actioned, no changes
|
||||
# arrs_failed_stalled_recovery.sh --log — verbose output
|
||||
# arrs_failed_stalled_recovery.sh --status — show config and exit
|
||||
#
|
||||
# ── SCHEDULE ──────────────────────────────────────────────────────────────────────────────────
|
||||
# Recommended: 0 5 * * * (5am daily)
|
||||
# Or every 6hr: 0 */6 * * * (matches ARR_IMPORT_RECOVERY_AGE default)
|
||||
# Recommended schedule: 0 5 * * * (5am daily)
|
||||
# Or every 6hr: 0 */6 * * * (matches ARR_IMPORT_RECOVERY_AGE default)
|
||||
#
|
||||
# ==============================================================================================
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
+71
-58
@@ -2,83 +2,96 @@
|
||||
# ==============================================================================================
|
||||
# ================================= Lidarr Cleanup =============================================
|
||||
# ==============================================================================================
|
||||
# Removes orphaned music files from the library that Lidarr no longer tracks.
|
||||
# Uses the Lidarr API to build a complete list of tracked file paths then compares
|
||||
# against what exists on disk — anything untracked and older than LIDARR_ORPHAN_AGE
|
||||
# days is considered an orphan and deleted.
|
||||
#
|
||||
# ── FILE CLASSIFICATION ───────────────────────────────────────────────────────────────────────
|
||||
# TRACKED — Lidarr API knows about this exact file path → leave it alone
|
||||
# PROTECTED — matches LIDARR_PROTECTED_PATTERNS → never delete (cover art, .nfo, .lrc etc.)
|
||||
# ORPHAN — music file, not tracked, older than LIDARR_ORPHAN_AGE days → delete
|
||||
# PURPOSE
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Delete orphaned music files not tracked by Lidarr. Queries the API for all
|
||||
# tracked file paths, walks the library on disk, and removes anything untracked
|
||||
# that is old enough to be past the import window. Triggers an Emby library
|
||||
# clean after each deletion run so ghost entries disappear immediately.
|
||||
#
|
||||
# ==============================================================================================
|
||||
# OPERATIONAL MODEL
|
||||
# ==============================================================================================
|
||||
#
|
||||
# Every file encountered on disk is classified into one of five categories:
|
||||
#
|
||||
# TRACKED — Lidarr API knows this exact path → leave it alone
|
||||
# PROTECTED — matches LIDARR_PROTECTED_PATTERNS → never delete
|
||||
# ORPHAN — music file, not tracked, older than LIDARR_ORPHAN_AGE → delete
|
||||
# JUNK — not a music extension, not protected → delete regardless of age
|
||||
# RECENT — not tracked, under LIDARR_ORPHAN_AGE days old → skip (may be mid-import)
|
||||
# RECENT — not tracked, under LIDARR_ORPHAN_AGE → skip (may be mid-import)
|
||||
#
|
||||
# ── WHY PROTECTED PATTERNS MATTER ─────────────────────────────────────────────────────────────
|
||||
# Lidarr generates cover art (*.jpg), metadata (*.nfo) and lyrics (*.lrc) but does 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 generates cover art (*.jpg), metadata (*.nfo), and lyrics (*.lrc) but
|
||||
# does NOT include these in its tracked file API response. Without PROTECTED
|
||||
# classification these would be deleted — breaking Lidarr and Emby display.
|
||||
#
|
||||
# ── SAFETY LAYERS — ALL MUST PASS BEFORE ANY FILE IS TOUCHED ─────────────────────────────────
|
||||
# 1. Container must be running and not starting/unhealthy
|
||||
# 2. API must be reachable
|
||||
# 3. API version must match tested major version in master.conf
|
||||
# 4. Artist count must be > 0
|
||||
# 5. Tracked file count must be > 0
|
||||
# 6. Tracked count must be >= LIDARR_MIN_TRACKED_PCT % of last known count
|
||||
# 7. Deletion size must be < LIDARR_MAX_DELETE_GB — or --i-know-what-im-doing required
|
||||
# ==============================================================================================
|
||||
# OPERATIONAL SAFEGUARDS
|
||||
# ==============================================================================================
|
||||
#
|
||||
# ── OVERRIDE FLAGS ────────────────────────────────────────────────────────────────────────────
|
||||
# --i-know-what-im-doing
|
||||
# Required when deletion would exceed LIDARR_MAX_DELETE_GB.
|
||||
# Long and annoying by design — cannot be added accidentally.
|
||||
# Seven gates — ALL must pass before any file is touched:
|
||||
# 1. Container running and not starting/unhealthy
|
||||
# 2. API reachable
|
||||
# 3. API version matches LIDARR_VERSION_MAJOR in master.conf
|
||||
# 4. Artist count > 0
|
||||
# 5. Tracked file count > 0
|
||||
# 6. Tracked count >= LIDARR_MIN_TRACKED_PCT % of last known count
|
||||
# 7. Deletion size < LIDARR_MAX_DELETE_GB — or --i-know-what-im-doing required
|
||||
#
|
||||
# --skip-strike-list
|
||||
# Bypasses the LIDARR_ORPHAN_AGE age check — deletes recent files too.
|
||||
#
|
||||
# NUCLEAR MODE — both flags active together:
|
||||
# Age check bypassed, size threshold bypassed, deletes on first pass.
|
||||
# Use when Soularr has filled the gaps and you want a clean one-pass wipe.
|
||||
# ⚠️ Script author takes NO responsibility for data loss with both flags active.
|
||||
# The user accepts full responsibility — this is 100% intentional by design.
|
||||
#
|
||||
# ── HOST AWARENESS ────────────────────────────────────────────────────────────────────────────
|
||||
# Runs on any node where Lidarr is configured — skips cleanly if LIDARR_URL/API_KEY not set.
|
||||
# detect_hosts() aliases LIDARR_URL, LIDARR_API_KEY, LIDARR_MUSIC_ROOT from HOST*_LIDARR_*.
|
||||
#
|
||||
# ── SAFEGUARDS ────────────────────────────────────────────────────────────────────────────────
|
||||
# acquire_lock "wait" — large scans take time, wait for previous run to finish
|
||||
# jq + curl validation — exits if either tool missing
|
||||
# DOCKER_TIMEOUT — container checks protected against daemon hangs
|
||||
# 7 safety layers — all must pass before any file is touched
|
||||
# Duplicate detection — temp file of tracked paths, grep before delete
|
||||
# validate_unraid_cmd — notify script validated before use
|
||||
# Silent by default — orphans/junk warn(), clean library logs silently
|
||||
#
|
||||
# ── CONFIGURATION (master_host*.conf) ─────────────────────────────────────────────────────────
|
||||
# ==============================================================================================
|
||||
# STATE FILES
|
||||
# ==============================================================================================
|
||||
#
|
||||
# LIDARR_TRACKED_COUNT_FILE — persistent baseline for the tracked % safety check (gate 6)
|
||||
# Updated after each successful run. Protects against misconfigured root path
|
||||
# returning an empty API response and deleting the entire library.
|
||||
#
|
||||
# ==============================================================================================
|
||||
# CONFIGURATION
|
||||
# ==============================================================================================
|
||||
#
|
||||
# master_host*.conf
|
||||
#
|
||||
# HOST1_LIDARR_URL / HOST1_LIDARR_API_KEY / HOST1_LIDARR_MUSIC_ROOT
|
||||
# HOST1_LIDARR_PATH_MAP — container path → host path translation
|
||||
# All aliased by detect_hosts() — script uses unprefixed names
|
||||
#
|
||||
# ── CONFIGURATION (master.conf) ───────────────────────────────────────────────────────────────
|
||||
# LIDARR_IMPORT_SCAN_TIMEOUT — seconds to wait for pre-flight import scan (default 600)
|
||||
# LIDARR_LOCK_WARN_AGE — override default lock warning age (large libraries)
|
||||
# LIDARR_ORPHAN_AGE — days before untracked file is eligible for deletion
|
||||
# LIDARR_MAX_DELETE_GB — require --i-know-what-im-doing above this
|
||||
# LIDARR_MIN_TRACKED_PCT — abort if tracked count drops below this % of last run
|
||||
# LIDARR_TRACKED_COUNT_FILE — persistent baseline file path
|
||||
# LIDARR_EXTENSIONS — music file extensions to consider for orphan classification
|
||||
# LIDARR_PROTECTED_PATTERNS — file patterns that are never deleted
|
||||
# LIDARR_VERSION_MAJOR — expected Lidarr major version for API safety check
|
||||
# ARR_CLEANUP_STATS — stats file path (read by coffee report)
|
||||
# master.conf
|
||||
#
|
||||
# ── USAGE ─────────────────────────────────────────────────────────────────────────────────────
|
||||
# lidarr_cleanup.sh — normal run
|
||||
# lidarr_cleanup.sh --dry-run — preview, no deletions
|
||||
# lidarr_cleanup.sh --log — verbose output
|
||||
# lidarr_cleanup.sh --status — show config and exit
|
||||
# lidarr_cleanup.sh --i-know-what-im-doing — bypass size threshold
|
||||
# LIDARR_ORPHAN_AGE — days before untracked file is eligible for deletion
|
||||
# LIDARR_MAX_DELETE_GB — require --i-know-what-im-doing above this
|
||||
# LIDARR_MIN_TRACKED_PCT — abort if tracked count drops below this % of last run
|
||||
# LIDARR_TRACKED_COUNT_FILE — persistent baseline file path
|
||||
# LIDARR_EXTENSIONS — music file extensions for orphan classification
|
||||
# LIDARR_PROTECTED_PATTERNS — file patterns that are never deleted
|
||||
# LIDARR_VERSION_MAJOR — expected Lidarr major version for API safety check
|
||||
# LIDARR_IMPORT_SCAN_TIMEOUT — seconds to wait for pre-flight import scan (default 600)
|
||||
# LIDARR_LOCK_WARN_AGE — override default lock warning age (large libraries)
|
||||
# ARR_CLEANUP_STATS — stats file path (read by coffee report)
|
||||
#
|
||||
# ==============================================================================================
|
||||
# RUNTIME MODES
|
||||
# ==============================================================================================
|
||||
#
|
||||
# lidarr_cleanup.sh — normal run
|
||||
# lidarr_cleanup.sh --dry-run — preview, no deletions
|
||||
# lidarr_cleanup.sh --log — verbose output
|
||||
# lidarr_cleanup.sh --status — show config and exit
|
||||
# lidarr_cleanup.sh --i-know-what-im-doing — bypass size threshold
|
||||
# lidarr_cleanup.sh --i-know-what-im-doing --skip-strike-list — NUCLEAR MODE
|
||||
#
|
||||
# NUCLEAR MODE: both flags bypass age check AND size threshold. Use when Soularr
|
||||
# has filled the gaps and you want a clean one-pass wipe. User accepts full
|
||||
# responsibility — the flag name is long and annoying by design.
|
||||
#
|
||||
# ==============================================================================================
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
+47
-38
@@ -2,60 +2,69 @@
|
||||
# ==============================================================================================
|
||||
# ================================= Lidarr Missing Art =========================================
|
||||
# ==============================================================================================
|
||||
# Fetches missing album and artist artwork for the Lidarr music library.
|
||||
# Reads from Lidarr API to discover album/artist paths, then downloads only
|
||||
# what is missing — never overwrites existing files.
|
||||
#
|
||||
# ── SAFE DESIGN ───────────────────────────────────────────────────────────────────────────────
|
||||
# READS from Lidarr only — no writes back to Lidarr
|
||||
# NEVER modifies audio tags or renames media files
|
||||
# NEVER overwrites existing artwork
|
||||
# ONLY writes missing artwork files to existing album/artist directories
|
||||
# PURPOSE
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Fetch missing album and artist artwork for the Lidarr music library. Downloads
|
||||
# only what is absent — never overwrites existing files. Idempotent re-runs are
|
||||
# safe.
|
||||
#
|
||||
# ── ARTWORK TARGETS ───────────────────────────────────────────────────────────────────────────
|
||||
# Album folder: cover.jpg cdart.png back.jpg
|
||||
# Artist folder: folder.jpg fanart.jpg logo.png banner.jpg
|
||||
# ==============================================================================================
|
||||
# OPERATIONAL MODEL
|
||||
# ==============================================================================================
|
||||
#
|
||||
# ── SOURCES ───────────────────────────────────────────────────────────────────────────────────
|
||||
# Artwork targets per album folder: cover.jpg cdart.png back.jpg
|
||||
# Artwork targets per artist folder: folder.jpg fanart.jpg logo.png banner.jpg
|
||||
#
|
||||
# Sources (tried in order, first success wins):
|
||||
# Album covers: fanart.tv → iTunes fallback
|
||||
# Artist art: fanart.tv → Deezer fallback → Last.fm fallback
|
||||
#
|
||||
# ── HOST AWARENESS ────────────────────────────────────────────────────────────────────────────
|
||||
# Lidarr runs on HOST1 only. detect_hosts() sets LIDARR_URL — if empty (HOST2) the
|
||||
# script exits cleanly with no action rather than failing.
|
||||
# Reads from Lidarr API only — no writes back to Lidarr. Never modifies audio
|
||||
# tags or renames media files. Only writes missing artwork files to existing
|
||||
# album/artist directories.
|
||||
#
|
||||
# ── OUTPUT ────────────────────────────────────────────────────────────────────────────────────
|
||||
# Minimal by default — section headers + per-section summary always visible.
|
||||
# --log shows per-item detail (each album, each artist, each file fetched).
|
||||
# ==============================================================================================
|
||||
# OPERATIONAL SAFEGUARDS
|
||||
# ==============================================================================================
|
||||
#
|
||||
# ── SAFEGUARDS ────────────────────────────────────────────────────────────────────────────────
|
||||
# acquire_lock — prevents concurrent runs during large library scans
|
||||
# curl + jq check — fail fast if tools missing
|
||||
# API reachability — verified before processing begins
|
||||
# Skip existing — never overwrites, idempotent re-runs are safe
|
||||
# Min file size — rejects corrupt/placeholder downloads (LIDARR_ART_MIN_SIZE)
|
||||
# Parallel jobs — capped at LIDARR_ART_MAX_PARALLEL to avoid hammering APIs
|
||||
# Download retries — LIDARR_ART_RETRIES attempts per image before giving up
|
||||
# Dry-run mode — logs what would be downloaded without writing anything
|
||||
# acquire_lock — prevents concurrent runs during large library scans
|
||||
# curl + jq check — fail fast if tools missing
|
||||
# API reachability — verified before processing begins
|
||||
# detect_hosts() — exits cleanly if LIDARR_URL empty (HOST2, no Lidarr)
|
||||
# Skip existing — never overwrites, idempotent re-runs are safe
|
||||
# Min file size check — rejects corrupt/placeholder downloads (LIDARR_ART_MIN_SIZE)
|
||||
# Parallel job cap — LIDARR_ART_MAX_PARALLEL — avoids hammering external APIs
|
||||
# Download retries — LIDARR_ART_RETRIES attempts per image before giving up
|
||||
# Rate limiting — LIDARR_ART_SLEEP_BETWEEN between fanart.tv API calls
|
||||
#
|
||||
# ── CONFIGURATION (master.conf) ───────────────────────────────────────────────────────────────
|
||||
# FANART_API_KEY — fanart.tv API key
|
||||
# LASTFM_API_KEY — last.fm API key
|
||||
# LIDARR_ART_MIN_SIZE — minimum valid download size in bytes
|
||||
# LIDARR_ART_MAX_PARALLEL — concurrent background download jobs
|
||||
# LIDARR_ART_RETRIES — download retry attempts per image
|
||||
# ==============================================================================================
|
||||
# CONFIGURATION
|
||||
# ==============================================================================================
|
||||
#
|
||||
# master_host*.conf
|
||||
#
|
||||
# HOST1_LIDARR_URL / HOST1_LIDARR_API_KEY
|
||||
# Aliased by detect_hosts() — script uses LIDARR_URL / LIDARR_API_KEY
|
||||
#
|
||||
# master.conf
|
||||
#
|
||||
# FANART_API_KEY — fanart.tv API key
|
||||
# LASTFM_API_KEY — last.fm API key
|
||||
# LIDARR_ART_MIN_SIZE — minimum valid download size in bytes
|
||||
# LIDARR_ART_MAX_PARALLEL — concurrent background download jobs
|
||||
# LIDARR_ART_RETRIES — download retry attempts per image
|
||||
# LIDARR_ART_SLEEP_BETWEEN — seconds between fanart.tv API calls
|
||||
#
|
||||
# ── CONFIGURATION (master_host*.conf) ─────────────────────────────────────────────────────────
|
||||
# HOST1_LIDARR_URL — Lidarr base URL
|
||||
# HOST1_LIDARR_API_KEY — Lidarr API key
|
||||
# All aliased by detect_hosts() — script uses unprefixed LIDARR_URL / LIDARR_API_KEY
|
||||
# ==============================================================================================
|
||||
# RUNTIME MODES
|
||||
# ==============================================================================================
|
||||
#
|
||||
# ── USAGE ─────────────────────────────────────────────────────────────────────────────────────
|
||||
# lidarr_missing_art.sh — fetch all missing artwork
|
||||
# lidarr_missing_art.sh --dry-run — preview without downloading
|
||||
# lidarr_missing_art.sh --log — verbose per-item output
|
||||
# lidarr_missing_art.sh --status — show config and exit
|
||||
#
|
||||
# ==============================================================================================
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
+31
-19
@@ -2,20 +2,24 @@
|
||||
# ==============================================================================================
|
||||
# ================================= Media Cleaner ==============================================
|
||||
# ==============================================================================================
|
||||
# Removes unwanted junk files from media share folders using configurable file patterns.
|
||||
# Two profiles — anime and media — each with their own folder list and file patterns.
|
||||
# Runs daily via DAILY_MAINTENANCE_SCRIPTS after media_shares_permissions.sh.
|
||||
#
|
||||
# ── PROFILES ──────────────────────────────────────────────────────────────────────────────────
|
||||
# PURPOSE
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Remove junk files from media shares using configurable file patterns. Two
|
||||
# profiles — anime and media — each with their own folder list and patterns.
|
||||
# Runs second in the daily maintenance window, after permissions and before
|
||||
# arr cleanup, so orphan detection only encounters actual media files.
|
||||
#
|
||||
# ==============================================================================================
|
||||
# OPERATIONAL MODEL
|
||||
# ==============================================================================================
|
||||
#
|
||||
# Profiles:
|
||||
# anime — cleans ANIME_CLEAN_FOLDERS using ANIME_FILE_PATTERNS
|
||||
# typical targets: *.sfv *.nfo *.url *.rar *.zip *.sample* etc.
|
||||
# media — cleans MEDIA_CLEAN_FOLDERS using MEDIA_FILE_PATTERNS (adds *.iso *.lrc)
|
||||
#
|
||||
# media — cleans MEDIA_CLEAN_FOLDERS using MEDIA_FILE_PATTERNS
|
||||
# same patterns plus *.iso *.lrc (media-specific extras)
|
||||
#
|
||||
# ── WHAT IT REMOVES ───────────────────────────────────────────────────────────────────────────
|
||||
# Junk files left behind by download clients, scene releases, and various tools:
|
||||
# *.sfv *.md5 *.sha1 — checksum verification files — useless post-download
|
||||
# Removes junk left by download clients, scene releases, and tools:
|
||||
# *.sfv *.md5 *.sha1 — checksum files — useless post-download
|
||||
# *.nfo *.url *.lnk — scene info files — not needed in media library
|
||||
# *.rar *.zip — archives — source files not needed after extraction
|
||||
# *.sample* *.proof* — scene samples — never needed
|
||||
@@ -24,12 +28,10 @@
|
||||
# *.torrent — torrent files left by download clients
|
||||
# *.log *.json — tool output files
|
||||
#
|
||||
# ── HOST AWARENESS ────────────────────────────────────────────────────────────────────────────
|
||||
# detect_hosts() sets MY_ID and aliases HOST*_ANIME_CLEAN_FOLDERS → ANIME_CLEAN_FOLDERS
|
||||
# and HOST*_MEDIA_CLEAN_FOLDERS → MEDIA_CLEAN_FOLDERS.
|
||||
# Each server only cleans the shares it owns — correct folders per host automatically.
|
||||
# ==============================================================================================
|
||||
# OPERATIONAL SAFEGUARDS
|
||||
# ==============================================================================================
|
||||
#
|
||||
# ── SAFEGUARDS ────────────────────────────────────────────────────────────────────────────────
|
||||
# acquire_lock "wait" — wait if previous run still active
|
||||
# detect_hosts() — correct folder lists per host via MY_ID aliases
|
||||
# Empty array guards — warns and exits cleanly if no folders or patterns configured
|
||||
@@ -37,22 +39,32 @@
|
||||
# validate_unraid_cmd — notify script validated before use
|
||||
# Silent by default — only problems and removals produce output
|
||||
#
|
||||
# ── CONFIGURATION (master_host*.conf) ─────────────────────────────────────────────────────────
|
||||
# ==============================================================================================
|
||||
# CONFIGURATION
|
||||
# ==============================================================================================
|
||||
#
|
||||
# master_host*.conf
|
||||
#
|
||||
# HOST*_ANIME_CLEAN_FOLDERS — folders cleaned by the anime profile on this host
|
||||
# HOST*_MEDIA_CLEAN_FOLDERS — folders cleaned by the media profile on this host
|
||||
# Aliased by detect_hosts() — script uses ANIME_CLEAN_FOLDERS / MEDIA_CLEAN_FOLDERS
|
||||
#
|
||||
# ── CONFIGURATION (master.conf) ───────────────────────────────────────────────────────────────
|
||||
# master.conf
|
||||
#
|
||||
# ANIME_FILE_PATTERNS — file patterns removed by the anime profile
|
||||
# MEDIA_FILE_PATTERNS — file patterns removed by the media profile
|
||||
#
|
||||
# ── USAGE ─────────────────────────────────────────────────────────────────────────────────────
|
||||
# ==============================================================================================
|
||||
# RUNTIME MODES
|
||||
# ==============================================================================================
|
||||
#
|
||||
# media_cleaner.sh anime — clean anime shares
|
||||
# media_cleaner.sh media — clean media shares
|
||||
# media_cleaner.sh anime --dry-run — preview anime clean, no deletions
|
||||
# media_cleaner.sh media --dry-run — preview media clean, no deletions
|
||||
# media_cleaner.sh anime --log — verbose output
|
||||
# media_cleaner.sh anime --status — show config and exit
|
||||
#
|
||||
# ==============================================================================================
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
@@ -2,45 +2,22 @@
|
||||
# ==============================================================================================
|
||||
# ============================= Media Shares Permissions =======================================
|
||||
# ==============================================================================================
|
||||
# Applies correct ownership and permissions to all configured media shares.
|
||||
# Runs daily via DAILY_MAINTENANCE_SCRIPTS — first job before arr cleanup scripts.
|
||||
# Arr cleanup depends on correct ownership to rename and delete files safely.
|
||||
#
|
||||
# ── WHY THIS EXISTS ───────────────────────────────────────────────────────────────────────────
|
||||
# Originally a band-aid for 777 permissions caused by containers running as root.
|
||||
# Now a proper daily failsafe — even with correct container config, files can arrive
|
||||
# with wrong ownership from:
|
||||
# - rsync without --chown (brings source server's ownership)
|
||||
# - Manual admin copies (creates root:root files)
|
||||
# - New containers not yet configured with correct PUID/PGID
|
||||
# - unRAID updates that reset container environments
|
||||
# PURPOSE
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Apply nobody:users ownership and correct permissions to all media shares.
|
||||
# Runs daily as the first job in the maintenance window — arr cleanup depends
|
||||
# on correct ownership to rename and delete files.
|
||||
#
|
||||
# ── 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
|
||||
# Now a proper daily failsafe: files arrive with wrong ownership from rsync
|
||||
# without --chown, manual admin copies, containers with unconfigured PUID/PGID,
|
||||
# or unRAID environment resets after updates.
|
||||
#
|
||||
# Files: 664 nobody:users
|
||||
# Owner (nobody) — rw read + write ✅
|
||||
# Group (users) — rw arrs can import/rename ✅
|
||||
# Others — r Samba guests can read ✅
|
||||
# No execute bit — media files are never executable ✅
|
||||
# ==============================================================================================
|
||||
# OPERATIONAL SAFEGUARDS
|
||||
# ==============================================================================================
|
||||
#
|
||||
# ── DIAGNOSTIC — HIGH CORRECTED COUNT ─────────────────────────────────────────────────────────
|
||||
# If this script corrects many files 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 — check these first
|
||||
# Once fixed, this script should correct 0 files per run (pure failsafe)
|
||||
#
|
||||
# ── HOST AWARENESS ────────────────────────────────────────────────────────────────────────────
|
||||
# detect_hosts() sets MY_ID and aliases HOST*_MEDIA_PERMISSION_SHARES → MEDIA_PERMISSION_SHARES
|
||||
# Each server only applies permissions to the shares it owns.
|
||||
#
|
||||
# ── SAFEGUARDS ────────────────────────────────────────────────────────────────────────────────
|
||||
# acquire_lock "wait" — wait if previous run still active (large share scans take time)
|
||||
# acquire_lock "wait" — wait if previous run still active (large share scans)
|
||||
# detect_hosts() — correct share list per host via MY_ID aliases
|
||||
# Empty array guard — warns and exits cleanly if no shares configured
|
||||
# Folder existence — skips missing shares with warning, continues others
|
||||
@@ -48,20 +25,35 @@
|
||||
# validate_unraid_cmd — notify script validated before use
|
||||
# Silent by default — only failures produce output, success is silent
|
||||
#
|
||||
# ── CONFIGURATION (master_host*.conf) ─────────────────────────────────────────────────────────
|
||||
# Diagnostic — high corrected count on every run means a container has wrong PUID/PGID:
|
||||
# Correct values on unRAID: PUID=99 (nobody) PGID=100 (users)
|
||||
# Common culprits: SABnzbd, qBittorrent, slskd — check these first
|
||||
# Once fixed, this script should correct 0 files per run (pure failsafe)
|
||||
#
|
||||
# ==============================================================================================
|
||||
# CONFIGURATION
|
||||
# ==============================================================================================
|
||||
#
|
||||
# master_host*.conf
|
||||
#
|
||||
# HOST*_MEDIA_PERMISSION_SHARES — shares this host applies permissions to
|
||||
# Aliased by detect_hosts() — script uses MEDIA_PERMISSION_SHARES
|
||||
#
|
||||
# ── CONFIGURATION (master.conf) ───────────────────────────────────────────────────────────────
|
||||
# master.conf
|
||||
#
|
||||
# PERMISSIONS_DIR_MODE — directory permissions (default 755)
|
||||
# PERMISSIONS_FILE_MODE — file permissions (default 664)
|
||||
# PERMISSIONS_OWNER — ownership applied to all files (default nobody:users)
|
||||
#
|
||||
# ── USAGE ─────────────────────────────────────────────────────────────────────────────────────
|
||||
# ==============================================================================================
|
||||
# RUNTIME MODES
|
||||
# ==============================================================================================
|
||||
#
|
||||
# media_shares_permissions.sh — normal run
|
||||
# media_shares_permissions.sh --dry-run — preview without making changes
|
||||
# media_shares_permissions.sh --log — verbose output
|
||||
# media_shares_permissions.sh --status — show config and exit
|
||||
#
|
||||
# ==============================================================================================
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
@@ -1,24 +1,17 @@
|
||||
#!/bin/bash
|
||||
# ==============================================================================================
|
||||
# =============================== LIDARR DISCOVERY =============================================
|
||||
# =========================== Playback-Aware Lidarr Discovery ==================================
|
||||
# ==============================================================================================
|
||||
# Lidarr discovery orchestrator.
|
||||
#
|
||||
# Consumes:
|
||||
# Kernel/decision_engine.sh
|
||||
# PURPOSE
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Behavior-driven music discovery scoring prototype. Consumes
|
||||
# Kernel/decision_engine.sh to score artist/album candidates for acquisition
|
||||
# using family-aware and playback-weighted logic.
|
||||
#
|
||||
# Purpose:
|
||||
# Discover high-quality artists/albums for acquisition using
|
||||
# family-aware and behavior-driven scoring logic.
|
||||
#
|
||||
# This script is intentionally selective.
|
||||
#
|
||||
# Music discovery is treated differently than TV/movies:
|
||||
#
|
||||
# Higher strictness
|
||||
# Stronger quality bias
|
||||
# Lower tolerance for trend chasing
|
||||
# Longer behavioral memory
|
||||
# WIP — not yet connected to a live data source or scheduled. Discovery logic
|
||||
# is intentionally selective: higher strictness, stronger quality bias, lower
|
||||
# tolerance for trend-chasing, longer behavioral memory than TV/movies.
|
||||
#
|
||||
# ==============================================================================================
|
||||
|
||||
|
||||
+61
-51
@@ -2,77 +2,87 @@
|
||||
# ==============================================================================================
|
||||
# ================================= Radarr Cleanup =============================================
|
||||
# ==============================================================================================
|
||||
# Removes orphaned movie files from the library that Radarr no longer tracks.
|
||||
# Uses the Radarr API to build a complete list of tracked movie file paths then compares
|
||||
# against what exists on disk — anything untracked and older than RADARR_ORPHAN_AGE
|
||||
# days is considered an orphan and deleted.
|
||||
#
|
||||
# ── FILE CLASSIFICATION ───────────────────────────────────────────────────────────────────────
|
||||
# TRACKED — Radarr API knows about this exact file path → leave it alone
|
||||
# PROTECTED — matches RADARR_PROTECTED_PATTERNS → never delete (artwork, subtitles, .nfo)
|
||||
# ORPHAN — video file, not tracked, older than RADARR_ORPHAN_AGE days → delete
|
||||
# PURPOSE
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Delete orphaned movie files not tracked by Radarr. Queries the API for all
|
||||
# tracked movie file paths, walks the library on disk, and removes anything
|
||||
# untracked that is old enough to be past the import window. Triggers an Emby
|
||||
# library clean after each deletion run so ghost entries disappear immediately.
|
||||
#
|
||||
# ==============================================================================================
|
||||
# OPERATIONAL MODEL
|
||||
# ==============================================================================================
|
||||
#
|
||||
# Every file encountered on disk is classified into one of five categories:
|
||||
#
|
||||
# TRACKED — Radarr API knows this exact path → leave it alone
|
||||
# PROTECTED — matches RADARR_PROTECTED_PATTERNS → never delete
|
||||
# ORPHAN — video file, not tracked, older than RADARR_ORPHAN_AGE → delete
|
||||
# JUNK — not a video extension, not protected → delete regardless of age
|
||||
# RECENT — not tracked, under RADARR_ORPHAN_AGE days old → skip (may be mid-import)
|
||||
# RECENT — not tracked, under RADARR_ORPHAN_AGE → skip (may be mid-import)
|
||||
#
|
||||
# ── WHY PROTECTED PATTERNS MATTER ─────────────────────────────────────────────────────────────
|
||||
# Radarr generates movie artwork (*.jpg), metadata (*.nfo) and manages subtitles (*.srt,
|
||||
# *.sub, *.ass) but does NOT include these in its tracked file API response.
|
||||
# Without protection these would be classified as orphans and deleted — breaking
|
||||
# Radarr and Emby metadata display.
|
||||
# Radarr generates movie artwork (*.jpg), metadata (*.nfo), and manages subtitles
|
||||
# (*.srt, *.sub, *.ass) but does NOT include these in its tracked file API response.
|
||||
# Without PROTECTED classification these would be deleted — breaking Radarr and
|
||||
# Emby metadata display.
|
||||
#
|
||||
# ── SAFETY LAYERS — ALL MUST PASS BEFORE ANY FILE IS TOUCHED ─────────────────────────────────
|
||||
# 1. Container must be running and not starting/unhealthy
|
||||
# 2. API must be reachable
|
||||
# 3. API version must match tested major version in master.conf
|
||||
# 4. Movie count must be > 0
|
||||
# 5. Tracked file count must be > 0
|
||||
# 6. Deletion size must be < RADARR_MAX_DELETE_GB — or --i-know-what-im-doing required
|
||||
# After deletions: notify_emby_scan() triggers Emby "Clean Missing Files" task.
|
||||
# Emby removes ghost entries immediately — no user-facing file-not-found errors.
|
||||
#
|
||||
# ── POST-DELETION ─────────────────────────────────────────────────────────────────────────────
|
||||
# After files are deleted notify_emby_scan() triggers Emby "Clean Missing Files" task.
|
||||
# Emby immediately removes ghost entries — no user-facing file-not-found errors.
|
||||
# ==============================================================================================
|
||||
# OPERATIONAL SAFEGUARDS
|
||||
# ==============================================================================================
|
||||
#
|
||||
# ── OVERRIDE FLAGS ────────────────────────────────────────────────────────────────────────────
|
||||
# --i-know-what-im-doing required when deletion exceeds RADARR_MAX_DELETE_GB
|
||||
# --skip-strike-list bypasses RADARR_ORPHAN_AGE age check
|
||||
# NUCLEAR MODE — both active: age + size bypass, deletes on first pass
|
||||
# ⚠️ User accepts full responsibility — no recovery possible after deletion
|
||||
# Six gates — ALL must pass before any file is touched:
|
||||
# 1. Container running and not starting/unhealthy
|
||||
# 2. API reachable
|
||||
# 3. API version matches RADARR_VERSION_MAJOR in master.conf
|
||||
# 4. Movie count > 0
|
||||
# 5. Tracked file count > 0
|
||||
# 6. Deletion size < RADARR_MAX_DELETE_GB — or --i-know-what-im-doing required
|
||||
#
|
||||
# ── HOST AWARENESS ────────────────────────────────────────────────────────────────────────────
|
||||
# HOST1 Radarr manages Movies. HOST2 Radarr manages Anime_Movies.
|
||||
# detect_hosts() sets MY_ID and aliases RADARR_URL, RADARR_API_KEY, RADARR_MOVIES_ROOT.
|
||||
# No manual HOST1/HOST2 comparisons — MY_ID routes correctly on any server.
|
||||
#
|
||||
# ── SAFEGUARDS ────────────────────────────────────────────────────────────────────────────────
|
||||
# acquire_lock "wait" — large scans take time, wait for previous run to finish
|
||||
# jq + curl validation — exits if either tool missing
|
||||
# DOCKER_TIMEOUT — container checks protected against daemon hangs
|
||||
# 6 safety layers — all must pass before any file is touched
|
||||
# notify_emby_scan() — triggers Emby clean after deletion
|
||||
# validate_unraid_cmd — notify script validated before use
|
||||
# Silent by default — orphans/junk warn(), clean library logs silently
|
||||
#
|
||||
# ── CONFIGURATION (master_host*.conf) ─────────────────────────────────────────────────────────
|
||||
# ==============================================================================================
|
||||
# CONFIGURATION
|
||||
# ==============================================================================================
|
||||
#
|
||||
# master_host*.conf
|
||||
#
|
||||
# HOST*_RADARR_URL / HOST*_RADARR_API_KEY / HOST*_RADARR_MOVIES_ROOT
|
||||
# HOST*_RADARR_PATH_MAP — container path → host path translation
|
||||
# All aliased by detect_hosts() — script uses unprefixed names
|
||||
#
|
||||
# ── CONFIGURATION (master.conf) ───────────────────────────────────────────────────────────────
|
||||
# RADARR_ORPHAN_AGE — days before untracked file eligible for deletion
|
||||
# RADARR_MAX_DELETE_GB — require --i-know-what-im-doing above this
|
||||
# RADARR_EXTENSIONS — video file extensions considered for orphan classification
|
||||
# RADARR_PROTECTED_PATTERNS — file patterns never deleted
|
||||
# RADARR_VERSION_MAJOR — expected Radarr major version for API safety check
|
||||
# RADARR_IMPORT_SCAN_TIMEOUT — seconds to wait for pre-flight import scan (default 600)
|
||||
# ARR_CLEANUP_STATS — stats file path (read by coffee report)
|
||||
# master.conf
|
||||
#
|
||||
# ── USAGE ─────────────────────────────────────────────────────────────────────────────────────
|
||||
# radarr_cleanup.sh — normal run
|
||||
# radarr_cleanup.sh --dry-run — preview, no deletions
|
||||
# radarr_cleanup.sh --log — verbose output
|
||||
# radarr_cleanup.sh --status — show config and exit
|
||||
# radarr_cleanup.sh --i-know-what-im-doing — bypass size threshold
|
||||
# RADARR_ORPHAN_AGE — days before untracked file eligible for deletion
|
||||
# RADARR_MAX_DELETE_GB — require --i-know-what-im-doing above this
|
||||
# RADARR_EXTENSIONS — video file extensions for orphan classification
|
||||
# RADARR_PROTECTED_PATTERNS — file patterns never deleted
|
||||
# RADARR_VERSION_MAJOR — expected Radarr major version for API safety check
|
||||
# RADARR_IMPORT_SCAN_TIMEOUT — seconds to wait for pre-flight import scan (default 600)
|
||||
# ARR_CLEANUP_STATS — stats file path (read by coffee report)
|
||||
#
|
||||
# ==============================================================================================
|
||||
# RUNTIME MODES
|
||||
# ==============================================================================================
|
||||
#
|
||||
# radarr_cleanup.sh — normal run
|
||||
# radarr_cleanup.sh --dry-run — preview, no deletions
|
||||
# radarr_cleanup.sh --log — verbose output
|
||||
# radarr_cleanup.sh --status — show config and exit
|
||||
# radarr_cleanup.sh --i-know-what-im-doing — bypass size threshold
|
||||
# radarr_cleanup.sh --i-know-what-im-doing --skip-strike-list — NUCLEAR MODE
|
||||
#
|
||||
# NUCLEAR MODE: both flags bypass age check AND size threshold. User accepts full
|
||||
# responsibility — the flag name is long and annoying by design.
|
||||
#
|
||||
# ==============================================================================================
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
@@ -2,36 +2,58 @@
|
||||
# ==============================================================================================
|
||||
# ============================= Radarr — TMDb Removed ==========================================
|
||||
# ==============================================================================================
|
||||
# Removes movies from Radarr that have been dropped from TMDb.
|
||||
# Radarr marks these with status="deleted" — they generate system health errors and
|
||||
# can never be monitored or downloaded. 99% are future/announced movies that were
|
||||
# delisted before release.
|
||||
#
|
||||
# ── WHAT IT DOES ──────────────────────────────────────────────────────────────────────────────
|
||||
# PURPOSE
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Remove movies from Radarr that TMDb has dropped. Radarr marks these with
|
||||
# status="deleted" — they generate health errors and can never be monitored
|
||||
# or downloaded. Most are announced-but-never-released films delisted before
|
||||
# release.
|
||||
#
|
||||
# ==============================================================================================
|
||||
# OPERATIONAL MODEL
|
||||
# ==============================================================================================
|
||||
#
|
||||
# Queries Radarr API for movies with status="deleted" (TMDb removal marker)
|
||||
# Reports each found entry with file status and size
|
||||
# Reports each entry with file status and size
|
||||
# Removes the movie record from Radarr
|
||||
# Optionally deletes associated files (disabled by default — most have none)
|
||||
# Optionally adds to Radarr's import exclusion list (default: true)
|
||||
# Optionally adds to Radarr's import exclusion list (default: true — prevents re-add)
|
||||
#
|
||||
# ── SAFE DEFAULTS ─────────────────────────────────────────────────────────────────────────────
|
||||
# Files are NOT deleted by default — use --delete-files to also remove from disk
|
||||
# Import exclusion added by default — prevents Radarr re-adding dropped movies
|
||||
# Per-deletion output always visible — deletions are never silently swallowed
|
||||
# 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.
|
||||
#
|
||||
# ── HOST AWARENESS ────────────────────────────────────────────────────────────────────────────
|
||||
# Radarr runs on HOST1 only. detect_hosts() sets RADARR_URL — if empty (HOST2)
|
||||
# the script exits cleanly.
|
||||
# ==============================================================================================
|
||||
# OPERATIONAL SAFEGUARDS
|
||||
# ==============================================================================================
|
||||
#
|
||||
# ── CONFIGURATION (master.conf) ───────────────────────────────────────────────────────────────
|
||||
# RADARR_DROPPED_ADD_EXCLUSION — add removed movies to import exclusion (default: true)
|
||||
# detect_hosts() — exits cleanly if RADARR_URL empty (Radarr not on this host)
|
||||
# API pre-flight — verifies Radarr reachable before querying
|
||||
# Dry-run mode — full preview without removing anything
|
||||
#
|
||||
# ==============================================================================================
|
||||
# CONFIGURATION
|
||||
# ==============================================================================================
|
||||
#
|
||||
# master.conf
|
||||
#
|
||||
# RADARR_DROPPED_ADD_EXCLUSION — add removed movies to import exclusion (default: true)
|
||||
#
|
||||
# master_host*.conf
|
||||
#
|
||||
# HOST1_RADARR_URL / HOST1_RADARR_API_KEY
|
||||
# Aliased by detect_hosts() — script uses RADARR_URL / RADARR_API_KEY
|
||||
#
|
||||
# ==============================================================================================
|
||||
# RUNTIME MODES
|
||||
# ==============================================================================================
|
||||
#
|
||||
# ── USAGE ─────────────────────────────────────────────────────────────────────────────────────
|
||||
# radarr_tmdb_removed.sh — remove records, keep files, add exclusion
|
||||
# radarr_tmdb_removed.sh --delete-files — also delete files from disk
|
||||
# radarr_tmdb_removed.sh --dry-run — preview without removing anything
|
||||
# radarr_tmdb_removed.sh --log — verbose output
|
||||
# radarr_tmdb_removed.sh --status — show config and exit
|
||||
#
|
||||
# ==============================================================================================
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
+61
-51
@@ -2,77 +2,87 @@
|
||||
# ==============================================================================================
|
||||
# ================================= Sonarr Cleanup =============================================
|
||||
# ==============================================================================================
|
||||
# Removes orphaned TV episode files from the library that Sonarr no longer tracks.
|
||||
# Uses the Sonarr API to build a complete list of tracked episode file paths then compares
|
||||
# against what exists on disk — anything untracked and older than SONARR_ORPHAN_AGE
|
||||
# days is considered an orphan and deleted.
|
||||
#
|
||||
# ── FILE CLASSIFICATION ───────────────────────────────────────────────────────────────────────
|
||||
# TRACKED — Sonarr API knows about this exact file path → leave it alone
|
||||
# PROTECTED — matches SONARR_PROTECTED_PATTERNS → never delete (artwork, subtitles, .nfo)
|
||||
# ORPHAN — video file, not tracked, older than SONARR_ORPHAN_AGE days → delete
|
||||
# PURPOSE
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Delete orphaned TV episode files not tracked by Sonarr. Queries the API for
|
||||
# all tracked episode file paths, walks the library on disk, and removes anything
|
||||
# untracked that is old enough to be past the import window. Triggers an Emby
|
||||
# library clean after each deletion run so ghost entries disappear immediately.
|
||||
#
|
||||
# ==============================================================================================
|
||||
# OPERATIONAL MODEL
|
||||
# ==============================================================================================
|
||||
#
|
||||
# Every file encountered on disk is classified into one of five categories:
|
||||
#
|
||||
# TRACKED — Sonarr API knows this exact path → leave it alone
|
||||
# PROTECTED — matches SONARR_PROTECTED_PATTERNS → never delete
|
||||
# ORPHAN — video file, not tracked, older than SONARR_ORPHAN_AGE → delete
|
||||
# JUNK — not a video extension, not protected → delete regardless of age
|
||||
# RECENT — not tracked, under SONARR_ORPHAN_AGE days old → skip (may be mid-import)
|
||||
# RECENT — not tracked, under SONARR_ORPHAN_AGE → skip (may be mid-import)
|
||||
#
|
||||
# ── WHY PROTECTED PATTERNS MATTER ─────────────────────────────────────────────────────────────
|
||||
# Sonarr generates show artwork (*.jpg), metadata (*.nfo) and manages subtitles (*.srt,
|
||||
# *.sub, *.ass) but does NOT include these in its tracked file API response.
|
||||
# Without protection these would be classified as orphans and deleted — breaking
|
||||
# Sonarr and Emby metadata display.
|
||||
# Sonarr generates show artwork (*.jpg), metadata (*.nfo), and manages subtitles
|
||||
# (*.srt, *.sub, *.ass) but does NOT include these in its tracked file API response.
|
||||
# Without PROTECTED classification these would be deleted — breaking Sonarr and
|
||||
# Emby metadata display.
|
||||
#
|
||||
# ── SAFETY LAYERS — ALL MUST PASS BEFORE ANY FILE IS TOUCHED ─────────────────────────────────
|
||||
# 1. Container must be running and not starting/unhealthy
|
||||
# 2. API must be reachable
|
||||
# 3. API version must match tested major version in master.conf
|
||||
# 4. Series count must be > 0
|
||||
# 5. Tracked file count must be > 0
|
||||
# 6. Deletion size must be < SONARR_MAX_DELETE_GB — or --i-know-what-im-doing required
|
||||
# After deletions: notify_emby_scan() triggers Emby "Clean Missing Files" task.
|
||||
# Emby removes ghost entries immediately — no user-facing file-not-found errors.
|
||||
#
|
||||
# ── POST-DELETION ─────────────────────────────────────────────────────────────────────────────
|
||||
# After files are deleted notify_emby_scan() triggers Emby "Clean Missing Files" task.
|
||||
# Emby immediately removes ghost entries — no user-facing file-not-found errors.
|
||||
# ==============================================================================================
|
||||
# OPERATIONAL SAFEGUARDS
|
||||
# ==============================================================================================
|
||||
#
|
||||
# ── OVERRIDE FLAGS ────────────────────────────────────────────────────────────────────────────
|
||||
# --i-know-what-im-doing required when deletion exceeds SONARR_MAX_DELETE_GB
|
||||
# --skip-strike-list bypasses SONARR_ORPHAN_AGE age check
|
||||
# NUCLEAR MODE — both active: age + size bypass, deletes on first pass
|
||||
# ⚠️ User accepts full responsibility — no recovery possible after deletion
|
||||
# Six gates — ALL must pass before any file is touched:
|
||||
# 1. Container running and not starting/unhealthy
|
||||
# 2. API reachable
|
||||
# 3. API version matches SONARR_VERSION_MAJOR in master.conf
|
||||
# 4. Series count > 0
|
||||
# 5. Tracked file count > 0
|
||||
# 6. Deletion size < SONARR_MAX_DELETE_GB — or --i-know-what-im-doing required
|
||||
#
|
||||
# ── HOST AWARENESS ────────────────────────────────────────────────────────────────────────────
|
||||
# HOST1 Sonarr manages Tv_Shows. HOST2 Sonarr manages Anime_Shows.
|
||||
# detect_hosts() sets MY_ID and aliases SONARR_URL, SONARR_API_KEY, SONARR_TV_ROOT.
|
||||
# No manual HOST1/HOST2 comparisons — MY_ID routes correctly on any server.
|
||||
#
|
||||
# ── SAFEGUARDS ────────────────────────────────────────────────────────────────────────────────
|
||||
# acquire_lock "wait" — large scans take time, wait for previous run to finish
|
||||
# jq + curl validation — exits if either tool missing
|
||||
# DOCKER_TIMEOUT — container checks protected against daemon hangs
|
||||
# 6 safety layers — all must pass before any file is touched
|
||||
# notify_emby_scan() — triggers Emby clean after deletion
|
||||
# validate_unraid_cmd — notify script validated before use
|
||||
# Silent by default — orphans/junk warn(), clean library logs silently
|
||||
#
|
||||
# ── CONFIGURATION (master_host*.conf) ─────────────────────────────────────────────────────────
|
||||
# ==============================================================================================
|
||||
# CONFIGURATION
|
||||
# ==============================================================================================
|
||||
#
|
||||
# master_host*.conf
|
||||
#
|
||||
# HOST*_SONARR_URL / HOST*_SONARR_API_KEY / HOST*_SONARR_TV_ROOT
|
||||
# HOST*_SONARR_PATH_MAP — container path → host path translation
|
||||
# All aliased by detect_hosts() — script uses unprefixed names
|
||||
#
|
||||
# ── CONFIGURATION (master.conf) ───────────────────────────────────────────────────────────────
|
||||
# SONARR_ORPHAN_AGE — days before untracked file eligible for deletion
|
||||
# SONARR_MAX_DELETE_GB — require --i-know-what-im-doing above this
|
||||
# SONARR_EXTENSIONS — video file extensions considered for orphan classification
|
||||
# SONARR_PROTECTED_PATTERNS — file patterns never deleted
|
||||
# SONARR_VERSION_MAJOR — expected Sonarr major version for API safety check
|
||||
# SONARR_IMPORT_SCAN_TIMEOUT — seconds to wait for pre-flight import scan (default 600)
|
||||
# ARR_CLEANUP_STATS — stats file path (read by coffee report)
|
||||
# master.conf
|
||||
#
|
||||
# ── USAGE ─────────────────────────────────────────────────────────────────────────────────────
|
||||
# sonarr_cleanup.sh — normal run
|
||||
# sonarr_cleanup.sh --dry-run — preview, no deletions
|
||||
# sonarr_cleanup.sh --log — verbose output
|
||||
# sonarr_cleanup.sh --status — show config and exit
|
||||
# sonarr_cleanup.sh --i-know-what-im-doing — bypass size threshold
|
||||
# SONARR_ORPHAN_AGE — days before untracked file eligible for deletion
|
||||
# SONARR_MAX_DELETE_GB — require --i-know-what-im-doing above this
|
||||
# SONARR_EXTENSIONS — video file extensions for orphan classification
|
||||
# SONARR_PROTECTED_PATTERNS — file patterns never deleted
|
||||
# SONARR_VERSION_MAJOR — expected Sonarr major version for API safety check
|
||||
# SONARR_IMPORT_SCAN_TIMEOUT — seconds to wait for pre-flight import scan (default 600)
|
||||
# ARR_CLEANUP_STATS — stats file path (read by coffee report)
|
||||
#
|
||||
# ==============================================================================================
|
||||
# RUNTIME MODES
|
||||
# ==============================================================================================
|
||||
#
|
||||
# sonarr_cleanup.sh — normal run
|
||||
# sonarr_cleanup.sh --dry-run — preview, no deletions
|
||||
# sonarr_cleanup.sh --log — verbose output
|
||||
# sonarr_cleanup.sh --status — show config and exit
|
||||
# sonarr_cleanup.sh --i-know-what-im-doing — bypass size threshold
|
||||
# sonarr_cleanup.sh --i-know-what-im-doing --skip-strike-list — NUCLEAR MODE
|
||||
#
|
||||
# NUCLEAR MODE: both flags bypass age check AND size threshold. User accepts full
|
||||
# responsibility — the flag name is long and annoying by design.
|
||||
#
|
||||
# ==============================================================================================
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
@@ -2,35 +2,57 @@
|
||||
# ==============================================================================================
|
||||
# ============================= Sonarr — TVDB Removed ==========================================
|
||||
# ==============================================================================================
|
||||
# Removes series from Sonarr that have been dropped from TVDB.
|
||||
# Sonarr marks these with status="deleted" — they generate system health errors and
|
||||
# can never be monitored or downloaded.
|
||||
#
|
||||
# ── WHAT IT DOES ──────────────────────────────────────────────────────────────────────────────
|
||||
# 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 found entry with file count and total size
|
||||
# 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)
|
||||
# Optionally adds to Sonarr's import exclusion list (default: true — prevents re-add)
|
||||
#
|
||||
# ── SAFE DEFAULTS ─────────────────────────────────────────────────────────────────────────────
|
||||
# Files are NOT deleted by default — use --delete-files to also remove from disk
|
||||
# Import exclusion added by default — prevents Sonarr re-adding dropped series
|
||||
# Per-deletion output always visible — deletions are never silently swallowed
|
||||
# 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.
|
||||
#
|
||||
# ── HOST AWARENESS ────────────────────────────────────────────────────────────────────────────
|
||||
# Sonarr runs on HOST1 only. detect_hosts() sets SONARR_URL — if empty (HOST2)
|
||||
# the script exits cleanly.
|
||||
# ==============================================================================================
|
||||
# OPERATIONAL SAFEGUARDS
|
||||
# ==============================================================================================
|
||||
#
|
||||
# ── CONFIGURATION (master.conf) ───────────────────────────────────────────────────────────────
|
||||
# SONARR_DROPPED_ADD_EXCLUSION — add removed series to import exclusion (default: true)
|
||||
# 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)
|
||||
#
|
||||
# master_host*.conf
|
||||
#
|
||||
# HOST1_SONARR_URL / HOST1_SONARR_API_KEY
|
||||
# Aliased by detect_hosts() — script uses SONARR_URL / SONARR_API_KEY
|
||||
#
|
||||
# ==============================================================================================
|
||||
# RUNTIME MODES
|
||||
# ==============================================================================================
|
||||
#
|
||||
# ── USAGE ─────────────────────────────────────────────────────────────────────────────────────
|
||||
# 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)"
|
||||
|
||||
Reference in New Issue
Block a user