diff --git a/README.md b/README.md index 6cdf1e7..9d3b040 100644 --- a/README.md +++ b/README.md @@ -73,10 +73,10 @@ Optional, add these later if you want them: Pull the prebuilt image onto your Docker host: ```bash -docker pull git.kretzer.club/andrew/alembic:0.6.13 +docker pull git.kretzer.club/andrew/alembic:0.6.14 ``` -That is the whole install. You do not need to download the source or build anything. The `0.6.13` is the version; you can pin to it so nothing changes under you, or use `latest` to always get the newest. +That is the whole install. You do not need to download the source or build anything. The `0.6.14` is the version; you can pin to it so nothing changes under you, or use `latest` to always get the newest. (If you would rather build it yourself from source, you can, but you do not need to.) @@ -136,7 +136,7 @@ Create a file called `docker-compose.yml` on your server (put it wherever you ke ```yaml services: alembic: - image: git.kretzer.club/andrew/alembic:0.6.13 + image: git.kretzer.club/andrew/alembic:0.6.14 container_name: alembic ports: - "8420:8420" diff --git a/pipeline/bin/import-track.sh b/pipeline/bin/import-track.sh index ccc6f62..16742e5 100755 --- a/pipeline/bin/import-track.sh +++ b/pipeline/bin/import-track.sh @@ -242,6 +242,30 @@ BEETS_EXIT=0 beet import -q -s "$DEST_DIR" >> "$LOG" 2>&1 || BEETS_EXIT=$? log "beets import finished with exit code $BEETS_EXIT" +# ==== Safety net: clean up stragglers + exact duplicates ==== +# With duplicate_action=keep, beets always imports rather than rejecting a +# real conflict (see beets/config.yaml) -- any file whose (albumartist, +# album, title) already exists in the library gets imported anyway as a +# `.1.ext` sibling. That's true whether this run came from the web UI, an +# SMB drop, or sync-bandcamp.sh, so the cleanup has to run here rather than +# per-caller (a 2026-07-23 incident: a Bandcamp sync's own call into this +# script failed and silently stranded ~150 already-owned files in import-me/ +# for two weeks; a later unrelated manual import swept them back in and +# duplicated them, and nothing had deduped since). +log "Running replace-with-better safety pass on /downloads stragglers" +if ${PIPELINE_DIR:-/app/pipeline}/lib/replace-with-better.sh --apply >> "$LOG" 2>&1; then + log "replace-with-better OK" +else + log "WARN: replace-with-better.sh exited non-zero (exit $?)" +fi + +log "Running dedup pass (exact-match duplicates only; FLAC > MP3, then largest file)" +if ${PIPELINE_DIR:-/app/pipeline}/lib/dedup-library.sh --apply >> "$LOG" 2>&1; then + log "dedup OK" +else + log "WARN: dedup-library.sh exited non-zero (exit $?)" +fi + # ==== Regenerate M3U if playlist was specified ==== if [[ -n "$PLAYLIST_NAME" ]]; then log "Regenerating M3U for $PLAYLIST_NAME" diff --git a/pipeline/lib/notify-telegram.sh b/pipeline/lib/notify-telegram.sh index 6f553d8..4844db6 100755 --- a/pipeline/lib/notify-telegram.sh +++ b/pipeline/lib/notify-telegram.sh @@ -20,7 +20,9 @@ # Returns exit 0 on send-OK, non-zero otherwise. set -euo pipefail -PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin +# /opt/venv/bin leads PATH for consistency with the other pipeline scripts, +# even though this one only shells out to curl. +PATH=/opt/venv/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin PARSE_MODE="" if [[ "${1:-}" == "--html" ]]; then diff --git a/pipeline/lib/sync-bandcamp.sh b/pipeline/lib/sync-bandcamp.sh index 9e82899..677f4f3 100755 --- a/pipeline/lib/sync-bandcamp.sh +++ b/pipeline/lib/sync-bandcamp.sh @@ -7,7 +7,15 @@ # Run as root (cron). Logs to ${ALEMBIC_CONFIG_DIR:-/config}/logs/bandcamp-YYYYMMDD.log. set -euo pipefail -PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin +# /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" @@ -87,7 +95,10 @@ 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, and Navidrome scan all kick in normally. +# 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" @@ -95,28 +106,5 @@ else log "WARN: import-track.sh exited non-zero (exit $?)" fi -# Safety net: if anything is stranded in ${MUSIC_DATA_DIR:-/data/music}/downloads (rare with -# duplicate_action=keep set in beets config, but possible for files beets -# couldn't process), find each one's library counterpart and replace if the -# new file is higher quality, OR import as new if there's no counterpart. -log "Running replace-with-better safety pass on /downloads stragglers" -if ${PIPELINE_DIR:-/app/pipeline}/lib/replace-with-better.sh --apply >> "$LOG" 2>&1; then - log "replace-with-better OK" -else - log "WARN: replace-with-better.sh exited non-zero (exit $?)" -fi - -# Now dedupe across the library. With duplicate_action=keep beets imported -# every Bandcamp file even when it conflicts with an existing track at the -# same path (it creates `.1.ext` siblings). dedup-library.sh's policy is -# "FLAC > MP3, then largest file" — Bandcamp version wins, soulseek version -# is removed from both the beets DB and disk. -log "Running dedup pass (Bandcamp FLAC wins over older Soulseek copies)" -if ${PIPELINE_DIR:-/app/pipeline}/lib/dedup-library.sh --apply >> "$LOG" 2>&1; then - log "dedup OK" -else - log "WARN: dedup-library.sh exited non-zero (exit $?)" -fi - log "=== Bandcamp sync done ===" exit 0 diff --git a/pipeline/lib/upgrade-mp3-to-flac.sh b/pipeline/lib/upgrade-mp3-to-flac.sh index abe6f3f..bae74a2 100755 --- a/pipeline/lib/upgrade-mp3-to-flac.sh +++ b/pipeline/lib/upgrade-mp3-to-flac.sh @@ -20,7 +20,8 @@ # upgrade-mp3-to-flac.sh --csv-only # just write the CSV; don't run sldl set -euo pipefail -PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin +# /opt/venv/bin must lead PATH -- `beet` (used heavily below) lives there. +PATH=/opt/venv/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin export PATH # These helpers are the same ones import-track.sh sources and runs under its