feat: arr-native distributed media sync + config architecture fixes

Arr sync (new):
- Media/arr_sync.sh — full mesh bidirectional sync across all HOST* nodes
  - Lidarr (MusicBrainz), Sonarr (TVDB), Radarr (TMDB) all handled in one script
  - Remote API keys read live from config.xml via SSH — never stored in conf files
  - Shared blocklist (DATA_DIR/arr_sync_blocklist.tsv) merged from all nodes at runtime
  - Graceful skip if arr not configured locally or not reachable on a remote node
  - --blocklist-add / --blocklist-remove / --blocklist-list management flags
- daily_sync_maintenance.sh — arr sync runs as explicit phase before rsync
- partnership_onboard.sh — Step 3 bootstraps merged library on both sides at onboard
- master.conf — ARR_SYNC_* config block, DOCKER_APPDATA_BASE

Rsync / cleanup:
- DEFAULT_RSYNC_OPTS — removed --delete; arr_cleanup.sh owns orphan enforcement
- lidarr_cleanup.sh — removed HOST1-only guard; runs on any node with Lidarr configured

Config architecture:
- HOST1/HOST2 hostnames moved from master_host*.conf → master.conf (not credentials)
- Sparse checkout now works correctly: each server only needs its own host conf
- detect_hosts() still resolves MY_ID + REMOTE_ID via master.conf hostname values

Bug fix:
- common.sh line 493 — watchdog toggle eval had broken quoting; all SYS_WATCHDOG_CHECK_*
  globals were silently set to empty instead of their configured values

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Gmer4Lfe
2026-05-09 09:53:40 -04:00
co-authored by Claude Sonnet 4.6
parent 009820e981
commit b65f572367
21 changed files with 1548 additions and 272 deletions
+41 -9
View File
@@ -9,25 +9,32 @@
# Pre-sync:
# git_pull_execute.sh — pull latest scripts first, always
#
# Arr Sync (arr_sync.sh):
# Syncs Lidarr/Sonarr/Radarr libraries across all nodes bidirectionally.
# All nodes agree on tracked library before any files are transferred.
# Remote nodes that don't have an arr running are skipped gracefully.
#
# Rsync window (DAILY_SYNC_SHARES per host):
# HOST*_DAILY_SYNC_SHARES — media shares pushed to mirror
# HOST*_DAILY_SYNC_SHARES — media shares spread to all nodes (no --delete)
# HOST*_PERSONAL_SHARES — encrypted personal shares
#
# Post-sync maintenance (DAILY_MAINTENANCE_SCRIPTS):
# media_shares_permissions.sh — fix ownership before arr cleanup
# media_cleaner.sh anime — remove junk from anime shares
# media_cleaner.sh media — remove junk from media shares
# lidarr_cleanup.sh — remove orphaned music files
# sonarr_cleanup.sh — remove orphaned TV files
# radarr_cleanup.sh — remove orphaned movie files
# lidarr_cleanup.sh — remove orphaned music files (local arr = truth)
# sonarr_cleanup.sh — remove orphaned TV files (local arr = truth)
# radarr_cleanup.sh — remove orphaned movie files (local arr = truth)
# docker_daily_restart.sh — restart containers needing daily restart
#
# ── WHY ORDER MATTERS ─────────────────────────────────────────────────────────────────────────
# git pull first — maintenance runs on latest code, not yesterday's
# Rsync before cleanup — cleanup sees the post-sync state
# Permissions before arr cleanup — arrs need correct ownership to delete/rename
# Arr cleanup after permissions — clean ownership = successful orphan deletion
# Docker restart last — containers already processed by cleanup
# git pull first — maintenance runs on latest code, not yesterday's
# arr sync before rsync — all nodes track the same library before files are spread;
# prevents remote arrs from searching for content already owned
# rsync before cleanup — cleanup sees fully spread state, rsync has no --delete
# permissions before arr cleanup — arrs need correct ownership to delete/rename
# arr cleanup after permissions — clean ownership = successful orphan deletion
# docker restart last — containers already processed by cleanup
#
# ── HOST AWARENESS ────────────────────────────────────────────────────────────────────────────
# Bidirectional — same script runs on both servers, correct direction automatic.
@@ -193,6 +200,31 @@ if [[ ${#PRE_SYNC_SCRIPTS[@]} -gt 0 ]]; then
done
fi
# ==============================================================================================
# ━━━ Arr Sync — library reconciliation before file spreading ━━━
# ==============================================================================================
echo ""
echo "━━━ $ICON_SYNC Arr Sync ━━━"
ARR_SYNC_SCRIPT="$SCRIPTS_ROOT/Media/arr_sync.sh"
if [[ "${ARR_SYNC_ENABLED:-true}" != "true" ]]; then
log "ARR_SYNC_ENABLED=false — skipping"
elif [[ ! -f "$ARR_SYNC_SCRIPT" ]]; then
warn "arr_sync.sh not found at $ARR_SYNC_SCRIPT — skipping"
JOB_FAIL+=("arr_sync.sh")
else
_arr_sync_args=()
[[ "$DRY_RUN" == true ]] && _arr_sync_args+=("--dry-run")
if bash "$ARR_SYNC_SCRIPT" "${_arr_sync_args[@]}"; then
log "Arr sync complete ✅"
JOB_PASS+=("arr_sync.sh")
else
warn "Arr sync completed with errors — continuing to rsync"
JOB_FAIL+=("arr_sync.sh")
fi
unset _arr_sync_args
fi
# ==============================================================================================
# ━━━ Media Share Sync ━━━
# ==============================================================================================