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:
co-authored by
Claude Sonnet 4.6
parent
009820e981
commit
b65f572367
+66
-27
@@ -24,6 +24,10 @@
|
||||
# Lidarr runs on HOST1 only. detect_hosts() sets LIDARR_URL — if empty (HOST2) the
|
||||
# script exits cleanly with no action rather than failing.
|
||||
#
|
||||
# ── OUTPUT ────────────────────────────────────────────────────────────────────────────────────
|
||||
# Minimal by default — section headers + per-section summary always visible.
|
||||
# --log shows per-item detail (each album, each artist, each file fetched).
|
||||
#
|
||||
# ── SAFEGUARDS ────────────────────────────────────────────────────────────────────────────────
|
||||
# acquire_lock — prevents concurrent runs during large library scans
|
||||
# curl + jq check — fail fast if tools missing
|
||||
@@ -50,7 +54,7 @@
|
||||
# ── USAGE ─────────────────────────────────────────────────────────────────────────────────────
|
||||
# lidarr_missing_art.sh — fetch all missing artwork
|
||||
# lidarr_missing_art.sh --dry-run — preview without downloading
|
||||
# lidarr_missing_art.sh --log — verbose output
|
||||
# lidarr_missing_art.sh --log — verbose per-item output
|
||||
# lidarr_missing_art.sh --status — show config and exit
|
||||
# ==============================================================================================
|
||||
|
||||
@@ -70,7 +74,6 @@ if [[ "$EUID" -ne 0 ]]; then
|
||||
error "Must be run as root"
|
||||
exit 1
|
||||
fi
|
||||
success "Running as root"
|
||||
|
||||
if ! command -v curl >/dev/null 2>&1; then
|
||||
error "curl not found — required for API calls"
|
||||
@@ -80,7 +83,6 @@ if ! command -v jq >/dev/null 2>&1; then
|
||||
error "jq not found — required for JSON parsing"
|
||||
exit 1
|
||||
fi
|
||||
success "curl and jq found"
|
||||
|
||||
acquire_lock
|
||||
|
||||
@@ -91,7 +93,8 @@ if [[ -z "$LIDARR_URL" ]]; then
|
||||
log "Lidarr not configured for $MY_ID — nothing to do"
|
||||
exit 0
|
||||
fi
|
||||
success "Identity: $MY_ID ($LOCAL_SERVER_NAME)"
|
||||
|
||||
echo " $MY_ID ($LOCAL_SERVER_NAME) — tools OK"
|
||||
|
||||
# ==============================================================================================
|
||||
# ━━━ Status ━━━
|
||||
@@ -115,6 +118,10 @@ fi
|
||||
|
||||
[[ "$DRY_RUN" == true ]] && warn "DRY RUN — no files will be written"
|
||||
|
||||
# ── Temp dir for subshell fetch/fail counters ─────────────────────────────────────────────────
|
||||
LIDARR_TMP=$(mktemp -d)
|
||||
trap 'rm -rf "$LIDARR_TMP"' EXIT
|
||||
|
||||
# ==============================================================================================
|
||||
# ── FUNCTIONS ─────────────────────────────────────────────────────────────────────────────────
|
||||
# ==============================================================================================
|
||||
@@ -143,7 +150,7 @@ download_if_valid() {
|
||||
[[ -f "$dest" ]] && return 0
|
||||
|
||||
if [[ "$DRY_RUN" == true ]]; then
|
||||
info "DRY RUN — would fetch: $(basename "$dest")"
|
||||
log "DRY RUN — would fetch: $(basename "$dest")"
|
||||
return 0
|
||||
fi
|
||||
|
||||
@@ -155,14 +162,14 @@ download_if_valid() {
|
||||
size=$(stat -c%s "$tmp" 2>/dev/null || echo 0)
|
||||
if (( size > LIDARR_ART_MIN_SIZE )); then
|
||||
mv "$tmp" "$dest"
|
||||
log "Fetched: $dest"
|
||||
log " Fetched: $(basename "$dest")"
|
||||
return 0
|
||||
fi
|
||||
rm -f "$tmp"
|
||||
sleep 1
|
||||
done
|
||||
|
||||
warn "Failed to fetch valid image: $(basename "$dest")"
|
||||
warn "Failed to fetch: $(basename "$dest")"
|
||||
return 1
|
||||
}
|
||||
|
||||
@@ -193,19 +200,15 @@ if ! curl_json "$LIDARR_URL/api/v1/system/status?apikey=$LIDARR_API_KEY" | jq -e
|
||||
notify "lidarr_missing_art failed — Lidarr API unreachable on $(hostname)" "Lidarr Missing Art" "warning"
|
||||
exit 1
|
||||
fi
|
||||
success "Lidarr API reachable"
|
||||
echo " Reachable — $LIDARR_URL"
|
||||
|
||||
START=$(date +%s)
|
||||
|
||||
ALBUMS_CHECKED=0
|
||||
ALBUMS_COMPLETE=0
|
||||
ALBUM_FETCHES=0
|
||||
ALBUM_FAILS=0
|
||||
|
||||
ARTISTS_CHECKED=0
|
||||
ARTISTS_COMPLETE=0
|
||||
ARTIST_FETCHES=0
|
||||
ARTIST_FAILS=0
|
||||
|
||||
# ==============================================================================================
|
||||
# ━━━ Albums ━━━
|
||||
@@ -222,7 +225,7 @@ if [[ -z "$albums" || "$albums" == "null" ]]; then
|
||||
fi
|
||||
|
||||
total_albums=$(echo "$albums" | jq '. | length')
|
||||
info "$total_albums albums to process"
|
||||
echo " Processing $total_albums albums..."
|
||||
|
||||
while IFS=$'\t' read -r local_path mbid artist_name album_name; do
|
||||
(( ALBUMS_CHECKED++ ))
|
||||
@@ -242,6 +245,8 @@ while IFS=$'\t' read -r local_path mbid artist_name album_name; do
|
||||
wait_for_slot
|
||||
|
||||
(
|
||||
_fetches=0 _fails=0
|
||||
|
||||
JSON=""
|
||||
if [[ -n "$mbid" && "$mbid" != "null" ]]; then
|
||||
JSON=$(curl_json "http://webservice.fanart.tv/v3/music/albums/$mbid?api_key=$FANART_API_KEY")
|
||||
@@ -250,29 +255,43 @@ while IFS=$'\t' read -r local_path mbid artist_name album_name; do
|
||||
|
||||
if [[ ! -f "$local_path/cover.jpg" ]]; then
|
||||
IMG=$(echo "$JSON" | jq -r '.[].albumcover[0].url // empty')
|
||||
if ! download_if_valid "$IMG" "$local_path/cover.jpg"; then
|
||||
if download_if_valid "$IMG" "$local_path/cover.jpg"; then
|
||||
(( _fetches++ ))
|
||||
else
|
||||
query=$(printf "%s %s" "$artist_name" "$album_name" | sed 's/ /+/g')
|
||||
itunes=$(curl_json "https://itunes.apple.com/search?term=$query&entity=album&limit=1" |
|
||||
jq -r '.results[0].artworkUrl100 // empty' | sed 's/100x100/600x600/')
|
||||
download_if_valid "$itunes" "$local_path/cover.jpg"
|
||||
if download_if_valid "$itunes" "$local_path/cover.jpg"; then
|
||||
(( _fetches++ ))
|
||||
else
|
||||
(( _fails++ ))
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ ! -f "$local_path/cdart.png" ]]; then
|
||||
IMG=$(echo "$JSON" | jq -r '.[].cdart[0].url // empty')
|
||||
download_if_valid "$IMG" "$local_path/cdart.png"
|
||||
if download_if_valid "$IMG" "$local_path/cdart.png"; then (( _fetches++ )); else (( _fails++ )); fi
|
||||
fi
|
||||
|
||||
if [[ ! -f "$local_path/back.jpg" ]]; then
|
||||
IMG=$(echo "$JSON" | jq -r '.[].albumback[0].url // empty')
|
||||
download_if_valid "$IMG" "$local_path/back.jpg"
|
||||
if download_if_valid "$IMG" "$local_path/back.jpg"; then (( _fetches++ )); else (( _fails++ )); fi
|
||||
fi
|
||||
|
||||
(( _fetches > 0 )) && printf '1\n' >> "$LIDARR_TMP/album_fetches"
|
||||
(( _fails > 0 )) && printf '1\n' >> "$LIDARR_TMP/album_fails"
|
||||
) &
|
||||
|
||||
done < <(echo "$albums" | jq -r '.[] | [.path, .foreignAlbumId, .artist.artistName, .title] | @tsv')
|
||||
|
||||
wait
|
||||
|
||||
ALBUM_FETCHED=$(wc -l < "$LIDARR_TMP/album_fetches" 2>/dev/null || echo 0)
|
||||
ALBUM_FAILED=$(wc -l < "$LIDARR_TMP/album_fails" 2>/dev/null || echo 0)
|
||||
ALBUM_MISSING=$(( ALBUMS_CHECKED - ALBUMS_COMPLETE ))
|
||||
echo " Checked: $ALBUMS_CHECKED | Complete: $ALBUMS_COMPLETE | Needed art: $ALBUM_MISSING | Fetched: $ALBUM_FETCHED | Failed: $ALBUM_FAILED"
|
||||
|
||||
# ==============================================================================================
|
||||
# ━━━ Artists ━━━
|
||||
# ==============================================================================================
|
||||
@@ -288,7 +307,7 @@ if [[ -z "$artists" || "$artists" == "null" ]]; then
|
||||
fi
|
||||
|
||||
total_artists=$(echo "$artists" | jq '. | length')
|
||||
info "$total_artists artists to process"
|
||||
echo " Processing $total_artists artists..."
|
||||
|
||||
while IFS=$'\t' read -r local_path mbid name; do
|
||||
(( ARTISTS_CHECKED++ ))
|
||||
@@ -310,43 +329,63 @@ while IFS=$'\t' read -r local_path mbid name; do
|
||||
wait_for_slot
|
||||
|
||||
(
|
||||
_fetches=0 _fails=0
|
||||
|
||||
JSON=$(curl_json "http://webservice.fanart.tv/v3/music/$mbid?api_key=$FANART_API_KEY")
|
||||
sleep "$LIDARR_ART_SLEEP_BETWEEN"
|
||||
|
||||
if [[ ! -f "$local_path/folder.jpg" ]]; then
|
||||
IMG=$(echo "$JSON" | jq -r '.artistthumb[0].url // empty')
|
||||
if ! download_if_valid "$IMG" "$local_path/folder.jpg"; then
|
||||
if download_if_valid "$IMG" "$local_path/folder.jpg"; then
|
||||
(( _fetches++ ))
|
||||
else
|
||||
IMG=$(deezer_artist_image "$name")
|
||||
if ! download_if_valid "$IMG" "$local_path/folder.jpg"; then
|
||||
if download_if_valid "$IMG" "$local_path/folder.jpg"; then
|
||||
(( _fetches++ ))
|
||||
else
|
||||
IMG=$(lastfm_artist_image "$name")
|
||||
download_if_valid "$IMG" "$local_path/folder.jpg"
|
||||
if download_if_valid "$IMG" "$local_path/folder.jpg"; then
|
||||
(( _fetches++ ))
|
||||
else
|
||||
(( _fails++ ))
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ ! -f "$local_path/fanart.jpg" ]]; then
|
||||
IMG=$(echo "$JSON" | jq -r '.artistbackground[0].url // empty')
|
||||
if ! download_if_valid "$IMG" "$local_path/fanart.jpg"; then
|
||||
if download_if_valid "$IMG" "$local_path/fanart.jpg"; then
|
||||
(( _fetches++ ))
|
||||
else
|
||||
IMG=$(deezer_artist_image "$name")
|
||||
download_if_valid "$IMG" "$local_path/fanart.jpg"
|
||||
if download_if_valid "$IMG" "$local_path/fanart.jpg"; then (( _fetches++ )); else (( _fails++ )); fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ ! -f "$local_path/logo.png" ]]; then
|
||||
IMG=$(echo "$JSON" | jq -r '.hdmusiclogo[0].url // empty')
|
||||
download_if_valid "$IMG" "$local_path/logo.png"
|
||||
if download_if_valid "$IMG" "$local_path/logo.png"; then (( _fetches++ )); else (( _fails++ )); fi
|
||||
fi
|
||||
|
||||
if [[ ! -f "$local_path/banner.jpg" ]]; then
|
||||
IMG=$(echo "$JSON" | jq -r '.musicbanner[0].url // empty')
|
||||
download_if_valid "$IMG" "$local_path/banner.jpg"
|
||||
if download_if_valid "$IMG" "$local_path/banner.jpg"; then (( _fetches++ )); else (( _fails++ )); fi
|
||||
fi
|
||||
|
||||
(( _fetches > 0 )) && printf '1\n' >> "$LIDARR_TMP/artist_fetches"
|
||||
(( _fails > 0 )) && printf '1\n' >> "$LIDARR_TMP/artist_fails"
|
||||
) &
|
||||
|
||||
done < <(echo "$artists" | jq -r '.[] | [.path, .foreignArtistId, .artistName] | @tsv')
|
||||
|
||||
wait
|
||||
|
||||
ARTIST_FETCHED=$(wc -l < "$LIDARR_TMP/artist_fetches" 2>/dev/null || echo 0)
|
||||
ARTIST_FAILED=$(wc -l < "$LIDARR_TMP/artist_fails" 2>/dev/null || echo 0)
|
||||
ARTIST_MISSING=$(( ARTISTS_CHECKED - ARTISTS_COMPLETE ))
|
||||
echo " Checked: $ARTISTS_CHECKED | Complete: $ARTISTS_COMPLETE | Needed art: $ARTIST_MISSING | Fetched: $ARTIST_FETCHED | Failed: $ARTIST_FAILED"
|
||||
|
||||
END=$(date +%s)
|
||||
|
||||
# ==============================================================================================
|
||||
@@ -356,8 +395,8 @@ echo ""
|
||||
echo "━━━━━ $ICON_SUMMARY LIDARR MISSING ART SUMMARY ━━━━━"
|
||||
echo "$ICON_HOST Identity: $MY_ID ($LOCAL_SERVER_NAME)"
|
||||
echo "$ICON_TIME Duration: $(format_duration $((END - START)))"
|
||||
echo "$ICON_EMBY Albums: $ALBUMS_CHECKED checked, $ALBUMS_COMPLETE already complete"
|
||||
echo "$ICON_EMBY Artists: $ARTISTS_CHECKED checked, $ARTISTS_COMPLETE already complete"
|
||||
echo "$ICON_EMBY Albums: $ALBUMS_CHECKED checked | $ALBUMS_COMPLETE complete | $ALBUM_FETCHED fetched | $ALBUM_FAILED failed"
|
||||
echo "$ICON_EMBY Artists: $ARTISTS_CHECKED checked | $ARTISTS_COMPLETE complete | $ARTIST_FETCHED fetched | $ARTIST_FAILED failed"
|
||||
|
||||
if [[ "$DRY_RUN" == true ]]; then
|
||||
echo "$ICON_WARN Status: DRY RUN — no files written"
|
||||
|
||||
Reference in New Issue
Block a user