From 8756ee08ffa58296e458ddccde08c3d81ef7d5bf Mon Sep 17 00:00:00 2001 From: Thales Pereira <31625914+thcp@users.noreply.github.com> Date: Fri, 10 Jul 2026 20:38:29 +0100 Subject: [PATCH] fix(server): persistent library on self-hosted web server (no TTL sweep) The 24h job TTL sweep was only disabled under the desktop shell (STEMDECK_DESKTOP=1). Running the bare web server via run.sh left the sweep active, so it deleted processed tracks older than 24h on startup and hourly, turning saved library entries into "audio no longer available" / out-of-sync (local-file tracks can't be auto-restored). Add STEMDECK_PERSIST_LIBRARY=1 as a second opt-out in _sweep_disabled, and set it by default in run.sh so the self-hosted server behaves like the desktop app (persistent, user-managed library via Trash). Shared/Docker deployments that set neither flag keep the sweep. Overridable with STEMDECK_PERSIST_LIBRARY=0. --- app/main.py | 15 +++++++++++---- run.sh | 4 ++++ tests/test_sweep.py | 13 +++++++++++++ 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/app/main.py b/app/main.py index 1b821490..483baffa 100644 --- a/app/main.py +++ b/app/main.py @@ -102,14 +102,21 @@ def _sweep_disabled() -> bool: with its track list persisted permanently in ~/Documents/StemDeck. The 24h job TTL sweep -- a sensible disk-hygiene default for the shared server/Docker deployment -- would wrongly purge stems the user kept, leaving orphaned - library entries that ask to "re-upload to restore". So skip the sweep under - the desktop shell (STEMDECK_DESKTOP=1); the user manages disk via Trash.""" - return os.environ.get("STEMDECK_DESKTOP") == "1" + library entries that ask to "re-upload to restore". + + So skip the sweep under the desktop shell (STEMDECK_DESKTOP=1), or when a + self-hosted deployment opts into a persistent library + (STEMDECK_PERSIST_LIBRARY=1 -- set by default in run.sh). The user manages + disk via Trash. Shared/Docker deployments that set neither keep the sweep.""" + return ( + os.environ.get("STEMDECK_DESKTOP") == "1" + or os.environ.get("STEMDECK_PERSIST_LIBRARY") == "1" + ) async def _sweep_loop() -> None: if _sweep_disabled(): - _log.info("desktop mode: job TTL sweep disabled (library is user-managed)") + _log.info("job TTL sweep disabled (persistent library; user-managed)") return while True: try: diff --git a/run.sh b/run.sh index 26e00389..a4403782 100755 --- a/run.sh +++ b/run.sh @@ -8,6 +8,10 @@ cd "$(dirname "$0")" HOST="${HOST:-0.0.0.0}" PORT="${PORT:-8080}" RELOAD="${RELOAD:-0}" +# Treat the self-hosted server as a persistent, user-managed library (like the +# desktop app): opt out of the 24h job TTL sweep so processed tracks are not +# auto-deleted. Override with STEMDECK_PERSIST_LIBRARY=0 for disk-hygiene mode. +export STEMDECK_PERSIST_LIBRARY="${STEMDECK_PERSIST_LIBRARY:-1}" FOREGROUND="${FOREGROUND:-0}" PID_FILE=".run/uvicorn.pid" LOG_FILE=".run/uvicorn.log" diff --git a/tests/test_sweep.py b/tests/test_sweep.py index 944f9735..ef255a33 100644 --- a/tests/test_sweep.py +++ b/tests/test_sweep.py @@ -60,12 +60,25 @@ def test_sweep_disabled_under_desktop(monkeypatch): user's curated library isn't purged; the server/Docker default keeps it.""" from app.main import _sweep_disabled + monkeypatch.delenv("STEMDECK_PERSIST_LIBRARY", raising=False) monkeypatch.setenv("STEMDECK_DESKTOP", "1") assert _sweep_disabled() is True monkeypatch.delenv("STEMDECK_DESKTOP", raising=False) assert _sweep_disabled() is False +def test_sweep_disabled_under_persistent_library(monkeypatch): + """A self-hosted server (run.sh) opts into a persistent library + (STEMDECK_PERSIST_LIBRARY=1) so its processed tracks aren't purged.""" + from app.main import _sweep_disabled + + monkeypatch.delenv("STEMDECK_DESKTOP", raising=False) + monkeypatch.setenv("STEMDECK_PERSIST_LIBRARY", "1") + assert _sweep_disabled() is True + monkeypatch.delenv("STEMDECK_PERSIST_LIBRARY", raising=False) + assert _sweep_disabled() is False + + @pytest.mark.asyncio async def test_sweep_loop_returns_immediately_under_desktop(monkeypatch): """In desktop mode the loop returns at once instead of entering the hourly