b135f11557
Security (P0): - Remove committed session-secret default; auto-generate and persist a random secret to the config volume when SESSION_SECRET is unset (prevents forgeable session cookies / auth bypass). - Validate playlist names to a safe charset and render sldl configs via literal Python substitution instead of sed (closes a command-injection and path-traversal path through playlist names). - shlex-quote credential values written to shell-sourced env files, and strip newlines from values patched into .conf files. - Render playlist .conf files 0600; warn at startup if the master key is co-located with the config volume; document keeping it separate. Portability: - Configurable timezone via TZ (default UTC) instead of hardcoded Edmonton. - Remove personal defaults (navidrome user "andrew", ephemeral.club URLs). - Ship generic example seeds; move the cross-album dedup keep-list and the legacy playlist import to editable config files; drop the personal _upgrade.csv. - Generic VPN reference in docker-compose.snippet.yml. First-run experience: - Redirect to /setup instead of 500 when OIDC is unconfigured; surface a missing master key inline; entrypoint exits with an actionable message when the config folder is not writable. - Add unauthenticated /health (JSON) and /setup (checklist) diagnostics. Docs: - Write docs/ARCHITECTURE.md and docs/MIGRATION.md (previously referenced but missing); expand README with ownership, backups, advanced settings, and migration guidance. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
61 lines
2.7 KiB
Bash
Executable File
61 lines
2.7 KiB
Bash
Executable File
#!/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=500 # library has ~1500; 500 is "clearly not wiped"
|
|
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"
|
|
resp=$(curl -s -G "$ND/rest/startScan.view" \
|
|
--data-urlencode "u=$ND_USER" --data-urlencode "p=$ND_PASS" \
|
|
--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:-<empty/no response>}"
|
|
exit 1
|
|
fi
|