run-playlist.sh and import-track.sh had the same beets->#EXTM3U block, but
import-track.sh wrote the file non-atomically. Move it to pipeline/lib/m3u.sh
as regen_m3u <name> <out>, sourced by both. The helper always writes atomically
(temp + rename), so a reader like Navidrome never sees a partial file and a
stray wrong-owner leftover .m3u8 is replaced without a chown. Returns 0 always
(empty result is a warning), so it's safe as a standalone call under set -e.
Verified: tests/test_m3u.py (atomic write with tracks, no file when empty; 44
tests green) plus a live regen against the real library (techno -> 229 tracks,
no temp leftover).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Moves all ~30 pipeline scripts, configs, and the vendored sldl binary into
this repo (source /opt/sldl left untouched). Removes all docker exec/docker
compose dependencies now that beets and sldl run in-process/as a subprocess
of this container instead of via soulbeet/on-demand sldl containers.
Replaces hardcoded host paths, Navidrome credentials, and Spotify credential
sourcing with env-var-driven paths and shared credential loaders. Adds
Dockerfile, entrypoint.sh, requirements.txt, docker-compose.snippet.yml, and
the initial app DB schema.