Files
alembic/pipeline/lib/sync-bandcamp.sh
T
andrew 55a059b6da 0.6.14: Fix Bandcamp sync silently failing to import new purchases
sync-bandcamp.sh set a hardcoded PATH at startup that left out /opt/venv/bin,
which is where beet lives in this image. Every time the Bandcamp sync had a
new purchase to import, its call into import-track.sh would run beet import
with that broken PATH, fail with "command not found", and import-track.sh
would just log the exit code and carry on, so sync-bandcamp.sh still reported
success. The purchase sat on disk, never entered the beets library, and never
showed up in Navidrome. This went unnoticed for weeks because most daily
syncs have nothing new to import, so the broken code path rarely ran.

Fixed the same copy-pasted PATH line in upgrade-mp3-to-flac.sh (which also
calls beet directly) and notify-telegram.sh (harmless there, but fixed for
consistency).

Also moved the post-import duplicate cleanup (replace-with-better.sh and
dedup-library.sh) out of sync-bandcamp.sh and into import-track.sh itself, so
every import gets the same cleanup, not just Bandcamp purchases. A manual
import or SMB drop that happens to match something already in the library no
longer leaves a duplicate copy sitting there until someone runs the dedup
review by hand.
2026-07-23 10:09:18 -06:00

111 lines
4.6 KiB
Bash
Executable File

#!/bin/bash
# Wrapper: sync new Bandcamp purchases, then push them through the standard
# import-track.sh pipeline so they end up in the Navidrome library tagged
# the same way as everything else.
#
# Reads $ALEMBIC_CONFIG_DIR/pipeline/bandcamp/config.env for username, format pref, etc.
# Run as root (cron). Logs to ${ALEMBIC_CONFIG_DIR:-/config}/logs/bandcamp-YYYYMMDD.log.
set -euo pipefail
# /opt/venv/bin must lead PATH -- that's where `beet` lives (the app's own
# subprocess env puts it there too, see pipeline_runner._subprocess_env()).
# This script used to run as a bare root cron job (pre-2026-07-08 cutover to
# the app scheduler) where a minimal hardened PATH made sense; omitting the
# venv here silently broke every downstream `beet import` call inside
# import-track.sh once this started running through the app instead --
# import-track.sh swallows that failure and still reports OK, so a purchase
# would sit unimported until someone noticed it missing from Navidrome.
PATH=/opt/venv/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
export PATH
CONFIG="${ALEMBIC_CONFIG_DIR:-/config}/pipeline/bandcamp/config.env"
LOG=${ALEMBIC_CONFIG_DIR:-/config}/logs/bandcamp-$(date +%Y%m%d).log
SYNC_PY=${PIPELINE_DIR:-/app/pipeline}/lib/sync-bandcamp.py
IMPORT_ME=${MUSIC_DATA_DIR:-/data/music}/import-me
mkdir -p "$(dirname "$LOG")"
log() { echo "[$(date -Iseconds)] $*" | tee -a "$LOG"; }
if [[ ! -f "$CONFIG" ]]; then
log "ERROR: $CONFIG not found"
exit 1
fi
# shellcheck disable=SC1090
source "$CONFIG"
if [[ ! -f "$BANDCAMP_COOKIES" ]]; then
log "ERROR: cookies file missing at $BANDCAMP_COOKIES"
log " Export Bandcamp cookies in Netscape format (Get cookies.txt LOCALLY"
log " extension, while logged into bandcamp.com), save to that path."
exit 1
fi
# Refuse to run against a dead/empty share: mkdir -p below would otherwise
# silently create shadow directories inside the container, downloads would
# land there invisibly, and state.json would mark those purchases as done —
# permanently skipping them after the mount comes back. `mountpoint -q` (the
# original host-side check) doesn't apply to a bind-mounted container path;
# a canary file + minimum artist-dir count is the container-compatible
# equivalent (same pattern as navidrome-scan.sh's share-health gate).
LIB="${MUSIC_DATA_DIR:-/data/music}/Library"
CANARY="$LIB/.navidrome-canary"
MIN_ARTIST_DIRS="${MIN_ARTIST_DIRS:-500}"
if [[ ! -r "$CANARY" ]]; then
log "ERROR: canary $CANARY missing/unreadable — share looks unhealthy — aborting sync"
exit 1
fi
n=$(find "$LIB" -maxdepth 1 -mindepth 1 -type d 2>/dev/null | wc -l)
if [[ "$n" -lt "$MIN_ARTIST_DIRS" ]]; then
log "ERROR: only $n artist dirs under $LIB (< $MIN_ARTIST_DIRS) — library looks truncated — aborting sync"
exit 1
fi
mkdir -p "$BANDCAMP_STAGING" "$IMPORT_ME"
log "=== Bandcamp sync starting ==="
log "user=$BANDCAMP_USERNAME staging=$BANDCAMP_STAGING format=$BANDCAMP_FORMAT_PREF"
if ! python3 "$SYNC_PY" \
"$BANDCAMP_USERNAME" \
"$BANDCAMP_COOKIES" \
"$BANDCAMP_STATE" \
"$BANDCAMP_STAGING" \
"$BANDCAMP_FORMAT_PREF" >> "$LOG" 2>&1; then
log "WARN: sync-bandcamp.py exited non-zero (likely auth issue or network)"
fi
# Anything new? If staging dir has audio, hand it off to import-track.sh.
NEW_COUNT=$(find "$BANDCAMP_STAGING" -type f \( -iname "*.flac" -o -iname "*.mp3" -o -iname "*.wav" -o -iname "*.m4a" \) 2>/dev/null | wc -l)
if [[ "$NEW_COUNT" -eq 0 ]]; then
log "No new files to import"
log "=== Bandcamp sync done (no-op) ==="
exit 0
fi
log "$NEW_COUNT new audio file(s) staged; moving to $IMPORT_ME for import"
# Move everything from staging into import-me. Preserve the band/album folder
# structure since import-track.sh handles directories fine.
shopt -s dotglob nullglob
for entry in "$BANDCAMP_STAGING"/*; do
[[ -e "$entry" ]] || continue
mv "$entry" "$IMPORT_ME/" 2>>"$LOG" || log "WARN: failed to move $entry into $IMPORT_ME"
done
shopt -u dotglob nullglob
# Hand off to the standard manual-import pipeline. No playlist tag — Bandcamp
# purchases aren't part of any Spotify playlist. The import-track.sh guard,
# albumartist fallback, beets import, straggler/dedup safety net, and
# Navidrome scan all kick in normally (import-track.sh runs the
# replace-with-better + dedup-library safety net itself now, for every
# caller, not just this one).
log "Calling import-track.sh"
if ${PIPELINE_DIR:-/app/pipeline}/bin/import-track.sh >> "$LOG" 2>&1; then
log "import-track.sh OK"
else
log "WARN: import-track.sh exited non-zero (exit $?)"
fi
log "=== Bandcamp sync done ==="
exit 0