5eaae8e813
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.
50 lines
1.8 KiB
Bash
Executable File
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
|