Files
andrew b135f11557 Security hardening and first-run/portability improvements
Security (P0):
- Remove committed session-secret default; auto-generate and persist a
  random secret to the config volume when SESSION_SECRET is unset
  (prevents forgeable session cookies / auth bypass).
- Validate playlist names to a safe charset and render sldl configs via
  literal Python substitution instead of sed (closes a command-injection
  and path-traversal path through playlist names).
- shlex-quote credential values written to shell-sourced env files, and
  strip newlines from values patched into .conf files.
- Render playlist .conf files 0600; warn at startup if the master key is
  co-located with the config volume; document keeping it separate.

Portability:
- Configurable timezone via TZ (default UTC) instead of hardcoded Edmonton.
- Remove personal defaults (navidrome user "andrew", ephemeral.club URLs).
- Ship generic example seeds; move the cross-album dedup keep-list and the
  legacy playlist import to editable config files; drop the personal
  _upgrade.csv.
- Generic VPN reference in docker-compose.snippet.yml.

First-run experience:
- Redirect to /setup instead of 500 when OIDC is unconfigured; surface a
  missing master key inline; entrypoint exits with an actionable message
  when the config folder is not writable.
- Add unauthenticated /health (JSON) and /setup (checklist) diagnostics.

Docs:
- Write docs/ARCHITECTURE.md and docs/MIGRATION.md (previously referenced
  but missing); expand README with ownership, backups, advanced settings,
  and migration guidance.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 14:25:55 -06:00

2.8 KiB

Migrating from the legacy scripts

This is only for people moving from the earlier version of this project, when it was a collection of scripts under /opt/sldl with credentials scattered in separate config folders. If you are setting up alembic fresh, ignore this file and follow the README.

The migration does two things: bring your old playlists in, and bring your old credentials in. Order matters, so do them in this order.

1. Import your playlists

seed_legacy() reads a JSON file at /config/pipeline/legacy-playlists.json and creates a playlist row for each entry. The file is not shipped with your playlists in it; you provide it. There is an example to copy from at pipeline/configs/legacy-playlists.json.example.

Each entry looks like this:

[
  {
    "name": "techno",
    "spotify_url": "https://open.spotify.com/playlist/your-id-here",
    "cron_expr": "0 1 * * *",
    "no_m3u": false
  }
]

Put your file at /config/pipeline/legacy-playlists.json, then run the seed once:

docker exec alembic python -c "from app.db import SessionLocal; from app.services import playlist_service; db=SessionLocal(); print('created', playlist_service.seed_legacy(db)); db.close()"

It is safe to run more than once. It skips any playlist name that already exists.

2. Import your credentials

Do this after step 1, not before. The credential import re-renders every existing playlist config file with your Soulseek and Spotify details. If no playlists exist yet, that render has nothing to write to and the step quietly does nothing.

scripts/import_legacy_credentials.py reads your old plaintext credentials and saves them into alembic's encrypted store. It only reads the legacy files, it never changes them, and it is safe to run more than once.

It expects your old config folders bind-mounted read-only at /legacy/*. Run it as a one-off container against the same image and config volume, for example:

docker run --rm \
  -v /path/to/alembic-config:/config \
  -v /path/to/old/opt-sldl:/legacy/opt-sldl:ro \
  -v /path/to/old/bandcampconfig:/legacy/bandcampconfig:ro \
  -v /path/to/old/azuracastconfig:/legacy/azuracastconfig:ro \
  -v /path/to/old/qobuzconfig:/legacy/qobuzconfig:ro \
  -v /path/to/old/telegramconfig:/legacy/telegramconfig:ro \
  --secret ... \
  --entrypoint python \
  alembic:latest /app/scripts/import_legacy_credentials.py

Leave off any -v /legacy/... mount for a service you never used. The importer skips a legacy folder that is not present.

3. Retire the old services

Once alembic is running your syncs and you have confirmed a playlist downloads and imports correctly, you can remove the old on-demand download service and the old import service from your compose file. Keep your old config folders backed up until you are confident, since the importer only reads them.