kettui reported the dev image roughly doubled in size after a recent
nightly build. codex investigation traced it back to:
1. nightly workflow runs `python -m pytest` before docker build
2. one of the new tests imports web_server (test_tidal_auth_instructions.py)
3. importing web_server constructs YouTubeClient
4. YouTubeClient.__init__ called _check_ffmpeg() — which auto-downloads
a ~388 MB ffmpeg/ffprobe bundle into ./tools/ when system ffmpeg
isn't on PATH (CI runner doesn't have it)
5. .dockerignore didn't exclude tools/ffmpeg or tools/ffprobe
6. docker `COPY . .` shipped the binaries
7. the immediately-following `chown -R /app` rewrote every file into
a new layer — so the 388 MB payload got counted twice in image
size
three fixes:
1. .dockerignore — block the auto-downloaded binaries even if they
leak into the workspace (tools/ffmpeg, tools/ffprobe, .exe variants,
.zip and .tar.xz download archives). Defense-in-depth so a future
regression in the test/import path can't bloat the image again.
2. youtube_client — split _check_ffmpeg into a side-effect-free
_locate_ffmpeg (pure existence check) and the original auto-
download _check_ffmpeg. __init__ now calls _locate_ffmpeg + logs
a warning when missing instead of triggering download. is_available()
and the actual download dispatch paths still call _check_ffmpeg —
so end users still get auto-download on first YouTube use, but
`import web_server` doesn't drag a 388 MB binary into the workspace.
3. Dockerfile — replaced `COPY . .` + `chown -R /app` with
`COPY --chown=soulsync:soulsync . .` + a scoped chown on just the
runtime mount-point dirs. eliminates the layer that duplicated
the entire /app tree just to flip ownership bits, so even legit
workspace content isn't double-counted in the image.
Combined effect: image size returns to baseline + future ffmpeg leaks
can't bloat it. Inside the container nothing changes — the Dockerfile
already installs system ffmpeg via apt, so YouTube downloads find it
on PATH on first use and the auto-download path never fires.
2259 passed, 1 skipped, 0 failed.
Switch the web UI from Werkzeug's built-in server to Gunicorn for a more stable production deployment path.
Keep a separate dev config so local runs still reload quickly, while the production path uses a dedicated WSGI entrypoint and cleaner startup behavior.
The main motivation is to reduce the websocket teardown noise and make the server behavior more predictable under the app's mostly background-driven workload.
Builder stage compiles Python dependencies with gcc/build tools.
Runtime stage only includes curl, gosu, ffmpeg, and libchromaprint-tools.
Build tools are not shipped in the final image, reducing size and
attack surface.
Inspired by kettui's PR #273.
New configurable path for storing music videos separately from audio
files, following Plex's global music video folder convention.
- Settings: library.music_videos_path (default: ./MusicVideos)
- UI: Music Videos Dir field on Settings Downloads tab with lock/unlock
- Docker: /app/MusicVideos volume mount in Dockerfile and docker-compose
- Added 'library' to settings save whitelist (was missing — music_paths
also wasn't persisting through main settings save)
- No download functionality yet — path infrastructure only
New automation action that executes user scripts from a dedicated
scripts/ directory. Available as both a DO action and THEN action.
Scripts are selected from a dropdown populated by /api/scripts.
Security: only scripts in the scripts dir can run, path traversal
blocked, no shell=True, stdout/stderr capped, configurable timeout
(max 300s). Scripts receive SOULSYNC_EVENT, SOULSYNC_AUTOMATION,
and SOULSYNC_SCRIPTS_DIR environment variables.
Includes Dockerfile + docker-compose.yml changes for the scripts
volume mount, and three example scripts (hello_world.sh,
system_info.py, notify_ntfy.sh).
Add optional post-download audio fingerprint verification using AcoustID.
Downloads are verified against expected track/artist using fuzzy string
matching on AcoustID results. Mismatched files are quarantined and
automatically added to the wishlist for retry.
- AcoustID verification with title/artist fuzzy matching (not MBID comparison)
- Quarantine system with JSON metadata sidecars for failed verifications
- fpcalc binary auto-download for Windows, macOS (universal), and Linux
- MusicBrainz enrichment worker with live status UI and track badges
- Settings page AcoustID section with real-fingerprint connection test
- Source reuse for album downloads to keep tracks from same Soulseek user
- Enhanced search queries for better track matching
- Bug fixes: wishlist tracking, album splitting, regex & handling, log rotation
Updated Dockerfile, entrypoint.sh, and Python code to store database files in /app/data instead of /app/database, avoiding conflicts with the Python package. The database path is now configurable via the DATABASE_PATH environment variable, improving flexibility for container deployments.
Implements manual track matching (discovery fix modal) for YouTube, Tidal, and Beatport platforms, allowing users to search and select Spotify tracks for unmatched results. Adds backend endpoints and frontend logic for updating matches, improves conversion of discovery results for sync/download, and updates Dockerfile/entrypoint for dynamic PUID/PGID/UMASK support. Includes a new DOCKER_PERMISSIONS.md guide.
Improves Docker integration by copying config.example.json as the default config.json and updating ownership in the Dockerfile. Adjusts config paths in config.example.json for container compatibility and updates docker-compose.yml to build the image and comment out the config volume for baked-in config testing. Also updates .dockerignore to allow config.example.json.
Introduces Docker deployment files (.dockerignore, Dockerfile, docker-compose.yml, docker-setup.sh, requirements-webui.txt, and README-Docker.md) for SoulSync WebUI. Refactors core/database_update_worker.py and core/media_scan_manager.py to support headless operation without PyQt6, enabling signal/callback compatibility for both GUI and non-GUI environments. Removes logs/app.log file.