#!/usr/bin/env bash # navidrome-scan.sh — trigger a Navidrome full scan ONLY if the music library # share is healthy. # # Why this exists: with ND_SCANNER_PURGEMISSING=always (which we keep, so # on-disk deletions propagate to playlists and out to Traktor), a full scan # that runs while the QNAP NFS share is unavailable makes Navidrome see the # entire library as "missing" and purge it — wiping every playlist. That is # exactly what happened on 2026-06-01 and lost the manual dj-* playlists. # # This wrapper refuses to trigger the scan unless the share is mounted, a # canary file is readable, and the library has a sane number of artist dirs. # Skipping a scan is harmless (the next healthy run picks up changes); running # one against a dead mount is catastrophic. set -euo pipefail : "${MUSIC_DATA_DIR:=/data/music}" : "${ALEMBIC_CONFIG_DIR:=/config}" NAVIDROME_ENV="$ALEMBIC_CONFIG_DIR/pipeline/navidrome/admin.env" [ -f "$NAVIDROME_ENV" ] && source "$NAVIDROME_ENV" LIB="$MUSIC_DATA_DIR/Library" CANARY="$LIB/.navidrome-canary" MIN_ARTIST_DIRS="${MIN_ARTIST_DIRS:-500}" # override via env for a small library ND="${ND_BASE:-http://navidrome:4533}" ND_USER="${ND_USER:-}" ND_PASS="${ND_PASS:-}" log() { echo "[$(date -Iseconds)] navidrome-scan: $*"; } # `mountpoint -q` on the host detected an unmounted/dead NFS share directly. # Running inside a container against a bind-mounted view, that check doesn't # apply (a bind mount has no distinct device number to detect) — the canary # file + artist-dir-count checks below are what actually carry the # 2026-06-01 protection now: an unhealthy/empty share fails both of them. # (Confirmed as an intentional adaptation, not a silent drop — see alembic # migration plan / MIGRATION.md.) if [ ! -r "$CANARY" ]; then log "ABORT: canary $CANARY missing/unreadable — share degraded — skipping scan" exit 0 fi n=$(find "$LIB" -maxdepth 1 -mindepth 1 -type d 2>/dev/null | wc -l) if [ "$n" -lt "$MIN_ARTIST_DIRS" ]; then log "ABORT: only $n artist dirs under $LIB (< $MIN_ARTIST_DIRS) — library looks truncated — skipping scan" exit 0 fi log "library healthy ($n artist dirs) — triggering full scan" # Pass the password via stdin (--data-urlencode "p@-"), not on the command # line, so it isn't visible in ps/proc. printf %s avoids a trailing newline. resp=$(printf '%s' "$ND_PASS" | curl -s -G "$ND/rest/startScan.view" \ --data-urlencode "u=$ND_USER" --data-urlencode "p@-" \ --data-urlencode 'v=1.16.0' --data-urlencode 'c=cron' \ --data-urlencode 'f=json' --data-urlencode 'fullScan=true' || true) # Don't log "scan triggered" on faith: a rotated password or Navidrome error # would otherwise report success forever while no scan ever runs. if echo "$resp" | grep -q '"status":"ok"'; then log "scan triggered" else log "ERROR: scan trigger failed — response: ${resp:-}" exit 1 fi