Files
alembic/pipeline/configs/regen.sh
T
andrew 5eaae8e813 Add playlist_service and credential_service
playlist_service: CRUD for the playlists table, playlists.json export,
regen.sh invocation, and a one-time seed_legacy() importing the 17-entry
legacy array. Creating/updating a playlist automatically re-renders confs
and re-patches credentials into any newly-generated .conf file.

credential_service: encrypted credential storage (via security/crypto.py)
and per-scope rendering to the exact files the pipeline scripts read --
every <playlist>.conf (spotify/soulseek), navidrome/admin.env,
bandcamp/config.env+cookies.txt, azuracast/api_key, qobuz/{token,app_id,region},
telegram/notify.env. set_credentials() batches multi-field saves so a render
never sees a half-populated scope.

Also fixes a real bug found via integration testing: regen.sh's embedded
python3 -c snippet used backslash-escaped quotes inside a single-quoted
bash string, which is invalid Python syntax (backslash passed through
literally) -- switched to double-quoted bash wrapper + single-quoted Python
strings.

Verified end-to-end: seeded 17 playlists, regenerated all 17 .conf files,
saved spotify/soulseek/navidrome credentials and confirmed correct
patching into rendered files (including bash-sourcing admin.env with
special characters in the password), and confirmed delete/create both
correctly add/remove .conf files with credentials pre-patched on create.
2026-07-08 13:32:37 -06:00

50 lines
1.8 KiB
Bash
Executable File

#!/bin/bash
# regen.sh — regenerate per-playlist sldl configs from _template.conf.
#
# Source of truth is $ALEMBIC_CONFIG_DIR/pipeline/playlists.json, written by
# the alembic app's playlist_service whenever a playlist is added/edited/
# removed via the UI. This script never edits that source of truth — it only
# renders it. Run automatically by the app before each playlist sync, or
# manually: regen.sh
#
# playlists.json shape: a JSON array of objects, each with at least
# {"name": "...", "spotify_url": "...", "active": true, "no_m3u": false}.
# Inactive playlists still get a .conf rendered (so a manual run is always
# possible) but are skipped by the scheduler.
set -euo pipefail
ALEMBIC_CONFIG_DIR="${ALEMBIC_CONFIG_DIR:-/config}"
MUSIC_DATA_DIR="${MUSIC_DATA_DIR:-/data/music}"
PIPELINE_DIR="${PIPELINE_DIR:-/app/pipeline}"
CONFIG_OUT_DIR="$ALEMBIC_CONFIG_DIR/pipeline"
PLAYLISTS_JSON="$CONFIG_OUT_DIR/playlists.json"
TEMPLATE="$PIPELINE_DIR/configs/_template.conf"
SLDL_DROPBOX_ROOT="$MUSIC_DATA_DIR/sldl-dropbox"
if [[ ! -f "$PLAYLISTS_JSON" ]]; then
echo "ERROR: $PLAYLISTS_JSON not found — nothing to regenerate." >&2
echo " This file is written by the alembic app; run its playlist" >&2
echo " import/seed step first if this is a fresh setup." >&2
exit 1
fi
mkdir -p "$CONFIG_OUT_DIR"
# One "name<TAB>spotify_url" line per playlist, active or not (see header).
python3 -c "
import json, sys
with open(sys.argv[1]) as f:
playlists = json.load(f)
for p in playlists:
print(p['name'] + '\t' + p['spotify_url'])
" "$PLAYLISTS_JSON" | while IFS=$'\t' read -r name url; do
[[ -z "$name" ]] && continue
sed -e "s|PLAYLIST_NAME|${name}|g" \
-e "s|SPOTIFY_URL|${url}|g" \
-e "s|SLDL_DROPBOX_ROOT|${SLDL_DROPBOX_ROOT}|g" \
"$TEMPLATE" > "$CONFIG_OUT_DIR/${name}.conf"
echo "Generated $CONFIG_OUT_DIR/${name}.conf"
done