From 66ce1068477e70395f3ea8cff43ee802e39339eb Mon Sep 17 00:00:00 2001 From: Thales Pereira <31625914+thcp@users.noreply.github.com> Date: Sun, 28 Jun 2026 19:27:34 +0100 Subject: [PATCH 1/7] feat: support YouTube Shorts URLs Normalize youtube.com/shorts/ to the standard watch?v= form so yt-dlp receives a URL its extractor already handles. Adds two test cases covering www. and m. variants. --- app/pipeline/download.py | 6 ++++++ tests/test_url_validation.py | 8 ++++++++ 2 files changed, 14 insertions(+) diff --git a/app/pipeline/download.py b/app/pipeline/download.py index c69d6095..7b69c139 100644 --- a/app/pipeline/download.py +++ b/app/pipeline/download.py @@ -115,6 +115,7 @@ def normalize_youtube_url(url: str) -> str: playlists embed the seed in the list ID; YouTube refuses to view the playlist directly with "This playlist type is unviewable.") * `youtu.be/` -> `watch?v=` + * `youtube.com/shorts/` -> `watch?v=` Everything else (PL/OL/algorithmic playlists with no derivable seed) is left alone -- yt-dlp will surface its own error. """ @@ -146,6 +147,11 @@ def normalize_youtube_url(url: str) -> str: if _VIDEO_ID_RE.match(vid): return f"https://www.youtube.com/watch?v={vid}" + if host == "youtube.com" and parsed.path.startswith("/shorts/"): + vid = parsed.path[len("/shorts/"):].lstrip("/").split("/")[0] + if _VIDEO_ID_RE.match(vid): + return f"https://www.youtube.com/watch?v={vid}" + return url diff --git a/tests/test_url_validation.py b/tests/test_url_validation.py index 6538d806..b1ee9c39 100644 --- a/tests/test_url_validation.py +++ b/tests/test_url_validation.py @@ -28,6 +28,14 @@ " https://www.youtube.com/watch?v=dQw4w9WgXcQ ", "https://www.youtube.com/watch?v=dQw4w9WgXcQ", ), + ( + "https://www.youtube.com/shorts/dQw4w9WgXcQ", + "https://www.youtube.com/watch?v=dQw4w9WgXcQ", + ), + ( + "https://m.youtube.com/shorts/dQw4w9WgXcQ", + "https://www.youtube.com/watch?v=dQw4w9WgXcQ", + ), ], ) def test_accepts_youtube_urls(url: str, expected: str) -> None: From a3a141c010391720786f7c559e3dd0aa7f0d0761 Mon Sep 17 00:00:00 2001 From: Thales Pereira <31625914+thcp@users.noreply.github.com> Date: Sun, 28 Jun 2026 19:27:45 +0100 Subject: [PATCH 2/7] fix: allow network access by default in server/Docker mode Two layers were blocking headless server deployments from accepting network clients (reported in discussion #216): 1. docker-compose.yml bound to 127.0.0.1:8000 - Docker itself rejected connections from the network before they reached the app. 2. _default_allow_network() returned False unconditionally, so the network_gate middleware blocked all non-loopback requests even when Docker networking was configured correctly. Fix both: bind the Docker port to 0.0.0.0 and derive the network default from STEMDECK_DESKTOP - desktop keeps its secure off-by-default behavior; server/Docker deployments open the gate automatically since network access is the entire point of a headless deployment. STEMDECK_ALLOW_NETWORK still takes precedence when set explicitly. --- app/core/settings.py | 8 +++++--- build/docker-compose.yml | 3 +-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/app/core/settings.py b/app/core/settings.py index a73cf84a..93cdf121 100644 --- a/app/core/settings.py +++ b/app/core/settings.py @@ -34,12 +34,14 @@ def _default_allow_network() -> bool: - # Off by default everywhere — the user explicitly opts other devices in. - # STEMDECK_ALLOW_NETWORK=1 can pre-enable it (e.g. headless/Docker deploys). + # STEMDECK_ALLOW_NETWORK takes precedence when set explicitly. + # Otherwise: desktop keeps network off (user opts in via UI toggle); + # server/Docker deployments open it by default since network access is + # the entire point of a headless deployment. env = os.environ.get("STEMDECK_ALLOW_NETWORK") if env is not None: return env.strip() == "1" - return False + return os.environ.get("STEMDECK_DESKTOP") != "1" def _load() -> dict: diff --git a/build/docker-compose.yml b/build/docker-compose.yml index 6b9ec74f..2201d457 100644 --- a/build/docker-compose.yml +++ b/build/docker-compose.yml @@ -13,8 +13,7 @@ services: image: stemdeck container_name: stemdeck ports: - # Bind to loopback only -- StemDeck has no auth and is local-only. - - "127.0.0.1:8000:8000" + - "8000:8000" volumes: # Stems and downloaded audio land in /jobs on the # host so you can grab them without going through the container. From 15f16e264b211a5f77b320cb62177ee3bffa65a0 Mon Sep 17 00:00:00 2001 From: Thales Pereira <31625914+thcp@users.noreply.github.com> Date: Sun, 28 Jun 2026 19:40:47 +0100 Subject: [PATCH 3/7] style: ruff format download.py --- app/pipeline/download.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/pipeline/download.py b/app/pipeline/download.py index 7b69c139..6562b289 100644 --- a/app/pipeline/download.py +++ b/app/pipeline/download.py @@ -148,7 +148,7 @@ def normalize_youtube_url(url: str) -> str: return f"https://www.youtube.com/watch?v={vid}" if host == "youtube.com" and parsed.path.startswith("/shorts/"): - vid = parsed.path[len("/shorts/"):].lstrip("/").split("/")[0] + vid = parsed.path[len("/shorts/") :].lstrip("/").split("/")[0] if _VIDEO_ID_RE.match(vid): return f"https://www.youtube.com/watch?v={vid}" From 869990f4e139bffb8ba7baa6972ed6d87d4f233d Mon Sep 17 00:00:00 2001 From: Thales Pereira <31625914+thcp@users.noreply.github.com> Date: Sun, 28 Jun 2026 19:40:47 +0100 Subject: [PATCH 4/7] test: update network gate tests for server-mode default Rename test_default_is_off to clarify it covers desktop mode (now requires STEMDECK_DESKTOP=1). Add test_default_is_on_in_server_mode covering the new behavior where allow_network defaults to True when STEMDECK_DESKTOP is absent. --- tests/test_network_gate.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tests/test_network_gate.py b/tests/test_network_gate.py index 927b218b..ed6c90e0 100644 --- a/tests/test_network_gate.py +++ b/tests/test_network_gate.py @@ -36,12 +36,20 @@ def test_host_request_recognizes_own_lan_ip(monkeypatch): assert _is_host_request("192.168.1.99") is False # a different device -def test_default_is_off(monkeypatch): - # Off by default everywhere — the user must opt in. +def test_default_is_off_in_desktop_mode(monkeypatch): + # Desktop keeps network off; the user opts in via the UI toggle. monkeypatch.delenv("STEMDECK_ALLOW_NETWORK", raising=False) + monkeypatch.setenv("STEMDECK_DESKTOP", "1") assert settings_mod._default_allow_network() is False +def test_default_is_on_in_server_mode(monkeypatch): + # Server/Docker deployments open the gate by default. + monkeypatch.delenv("STEMDECK_ALLOW_NETWORK", raising=False) + monkeypatch.delenv("STEMDECK_DESKTOP", raising=False) + assert settings_mod._default_allow_network() is True + + def test_env_var_pre_enables(monkeypatch): monkeypatch.setenv("STEMDECK_ALLOW_NETWORK", "1") assert settings_mod._default_allow_network() is True From 58d2906e4144e7bc09333a567eef4cd4393177c2 Mon Sep 17 00:00:00 2001 From: Thales Pereira <31625914+thcp@users.noreply.github.com> Date: Sun, 28 Jun 2026 20:32:28 +0100 Subject: [PATCH 5/7] fix: hide network and port settings in server/Docker mode Network toggle and port field are desktop-only controls. In server mode (no window.__TAURI__) the port is fixed by Docker and network access is on by default, so exposing these controls is misleading. Hide both from the Advanced settings tab when not running inside Tauri. --- static/js/catalog.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/static/js/catalog.js b/static/js/catalog.js index 485cca90..5d1170b3 100644 --- a/static/js/catalog.js +++ b/static/js/catalog.js @@ -1996,6 +1996,7 @@ function openLibraryEditor() { + ` : ""}
Out of sync tracks
@@ -2047,7 +2048,7 @@ function openLibraryEditor() { libraryEditor = overlay; refreshLibrarySyncSummary(); wireGeneralSettings(overlay); - wireNetworkSetting(overlay); + if (Boolean(window.__TAURI__?.core?.invoke)) wireNetworkSetting(overlay); } // Poll a job until it reaches a terminal state, so auto-restores run one at a From 75256dd79e87d3efa53f40fe2b25249a68bfa16d Mon Sep 17 00:00:00 2001 From: Thales Pereira <31625914+thcp@users.noreply.github.com> Date: Sun, 28 Jun 2026 20:44:08 +0100 Subject: [PATCH 6/7] fix: make network and port settings read-only in server/Docker mode In server mode (no Tauri) the network toggle is always on and the port is fixed by Docker, so both controls are shown but disabled so the user can see the current state without being able to change them. --- static/js/catalog.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/static/js/catalog.js b/static/js/catalog.js index 5d1170b3..8ec526f4 100644 --- a/static/js/catalog.js +++ b/static/js/catalog.js @@ -1996,7 +1996,6 @@ function openLibraryEditor() { ` : ""} +
Out of sync tracks
@@ -2047,8 +2046,14 @@ function openLibraryEditor() { document.body.appendChild(overlay); libraryEditor = overlay; refreshLibrarySyncSummary(); + const isDesktop = Boolean(window.__TAURI__?.core?.invoke); wireGeneralSettings(overlay); - if (Boolean(window.__TAURI__?.core?.invoke)) wireNetworkSetting(overlay); + wireNetworkSetting(overlay); + if (!isDesktop) { + overlay.querySelector(".net-access-input")?.setAttribute("disabled", ""); + overlay.querySelector(".set-port")?.setAttribute("readonly", ""); + overlay.querySelector(".set-port")?.setAttribute("disabled", ""); + } } // Poll a job until it reaches a terminal state, so auto-restores run one at a From c68265e3fe6f0bc392942a7975ecece89d2fd63a Mon Sep 17 00:00:00 2001 From: Thales Pereira <31625914+thcp@users.noreply.github.com> Date: Sun, 28 Jun 2026 20:47:12 +0100 Subject: [PATCH 7/7] fix: add read-only note to server-mode settings Show a explanatory note at the top of the Advanced tab when running in server mode so users know the network and port controls are intentionally locked and where to make changes. --- static/css/daw.css | 1 + static/js/catalog.js | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/static/css/daw.css b/static/css/daw.css index 7301191b..cfe24e6f 100644 --- a/static/css/daw.css +++ b/static/css/daw.css @@ -811,6 +811,7 @@ input, textarea { font-family: inherit; } .settings-net-list { display: flex; flex-direction: column; gap: 5px; align-items: flex-start; } .settings-net-list code { color: var(--accent); background: rgba(244,183,64,0.1); padding: 3px 9px; border-radius: 5px; font-size: 11.5px; } .settings-net-empty { color: var(--muted); } +.settings-server-note { font-size: 10.5px; color: var(--muted); margin: 0 0 12px; line-height: 1.5; } .settings-subhead { font-size: 10px; text-transform: uppercase; letter-spacing: 0.04em; color: var(--muted); font-weight: 600; margin: 4px 0 7px; } .library-editor-foot { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-top: 11px; } diff --git a/static/js/catalog.js b/static/js/catalog.js index 8ec526f4..c1ad1a44 100644 --- a/static/js/catalog.js +++ b/static/js/catalog.js @@ -2053,6 +2053,10 @@ function openLibraryEditor() { overlay.querySelector(".net-access-input")?.setAttribute("disabled", ""); overlay.querySelector(".set-port")?.setAttribute("readonly", ""); overlay.querySelector(".set-port")?.setAttribute("disabled", ""); + const note = document.createElement("p"); + note.className = "settings-server-note"; + note.textContent = "These settings are read-only in server mode. To change them, update your server configuration (e.g. docker-compose.yml) and restart."; + overlay.querySelector("[data-pane='advanced']")?.prepend(note); } }