diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml new file mode 100644 index 00000000..8da1a27c --- /dev/null +++ b/.github/workflows/docker-publish.yml @@ -0,0 +1,86 @@ +name: Docker Publish + +# Builds the server container (build/Dockerfile) and pushes it to GHCR so it can +# be pulled by self-hosted deployments and the Unraid Community Applications app +# (ghcr.io/stemdeckapp/stemdeck). The image keeps the default Linux x86_64 torch +# wheel, which is the CUDA build -- so the same image runs on CPU by default and +# uses the GPU automatically when started with `--runtime=nvidia` (see the +# Unraid template in templates/unraid/). + +on: + # Every merge to main publishes a rolling :edge image. The version is derived + # from git (hatch-vcs style); :edge never clobbers :latest. + push: + branches: [main] + release: + types: [published] + # Same as a main push but on demand, from any ref. + workflow_dispatch: + +permissions: {} + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + IMAGE: ghcr.io/${{ github.repository_owner }}/stemdeck + +jobs: + build-and-push: + runs-on: ubuntu-latest + timeout-minutes: 60 + permissions: + contents: read + packages: write + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + # Full history + tags so git describe can derive a version on manual runs. + fetch-depth: 0 + + # Derive the version fed to the Dockerfile's VERSION build-arg. On a + # release, use the tag; on manual runs, derive it from git (hatch-vcs + # style, e.g. 0.8.0a6.dev3+g). Leading "v" stripped either way. + - name: resolve version + id: ver + run: | + if [ "${{ github.event_name }}" = "release" ]; then + v="${{ github.event.release.tag_name }}" + else + v="$(git describe --tags --dirty --always)" + fi + echo "value=${v#v}" >> "$GITHUB_OUTPUT" + + - name: docker metadata (tags/labels) + id: meta + uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0 + with: + images: ${{ env.IMAGE }} + tags: | + type=raw,value=latest,enable=${{ github.event_name == 'release' && !github.event.release.prerelease }} + type=raw,value=${{ steps.ver.outputs.value }},enable=${{ github.event_name == 'release' }} + type=raw,value=edge,enable=${{ github.event_name == 'push' || github.event_name == 'workflow_dispatch' }} + + - uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0 + + - name: log in to GHCR + uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: build and push + uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0 + with: + context: . + file: build/Dockerfile + # Unraid is x86_64; arm64 has no CUDA torch and is not a target. + platforms: linux/amd64 + push: true + build-args: | + VERSION=${{ steps.ver.outputs.value }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + provenance: false diff --git a/README.md b/README.md index feb4ab62..c368d95f 100644 --- a/README.md +++ b/README.md @@ -247,6 +247,27 @@ docker compose -f build/docker-compose.yml up --build Stems land in `./jobs/` on the host. Demucs weights are cached in a named volume so they don't re-download on rebuild. Note: no GPU passthrough on macOS Docker. +A prebuilt image is published to GHCR. Tags: `edge` (rolling, rebuilt on every merge to main), `latest` (newest stable release), and `X.Y.Z` (pinned to a release). + +```sh +docker run -d --name stemdeck -p 8000:8000 \ + -v /path/to/jobs:/app/jobs \ + -v /path/to/cache:/cache \ + -e STEMDECK_PERSIST_LIBRARY=1 \ + ghcr.io/stemdeckapp/stemdeck:edge +``` + +On a Linux host with an NVIDIA GPU (driver + NVIDIA Container Toolkit installed), add `--runtime=nvidia -e NVIDIA_VISIBLE_DEVICES=all` and StemDeck auto-detects CUDA. The image already bundles CUDA-enabled torch, so no separate CUDA install is needed. + +#### Unraid + +StemDeck is available in Unraid Community Applications: open **Apps**, search "StemDeck", and install. Map the two volumes to persistent appdata paths: + +- `/app/jobs` -> `/mnt/user/appdata/stemdeck/jobs` (library + stems) +- `/cache` -> `/mnt/user/appdata/stemdeck/cache` (model weights) + +The library is persistent by default (`STEMDECK_PERSIST_LIBRARY=1`), so tracks are never auto-deleted. For GPU acceleration, install the **Nvidia Driver** plugin, then set the container's Extra Parameters to `--runtime=nvidia` (the `NVIDIA_VISIBLE_DEVICES` and `NVIDIA_DRIVER_CAPABILITIES` variables are already in the template). CPU-only works with no extra configuration. + #### `run.sh` control script ```sh diff --git a/app/core/settings.py b/app/core/settings.py index 93cdf121..1b7e9f7e 100644 --- a/app/core/settings.py +++ b/app/core/settings.py @@ -30,7 +30,7 @@ _DURATION_MIN, _DURATION_MAX = 60, 1200 # 1 min .. 20 min _HEIGHT_MIN, _HEIGHT_MAX = 144, 2160 _PORT_MIN, _PORT_MAX = 1024, 65535 -DEFAULT_PORT = 8080 +DEFAULT_PORT = 8000 def _default_allow_network() -> bool: @@ -124,7 +124,7 @@ def set_video_max_height(value: int) -> int: # ── port ── # The preferred port the server binds on launch. The desktop launcher reads this -# (default 8080) before spawning the backend; a self-hosted server's --port wins. +# (default 8000) before spawning the backend; a self-hosted server's --port wins. # Changing it needs a restart — the socket is bound at startup. def get_port() -> int: with _LOCK: diff --git a/ca_profile.xml b/ca_profile.xml new file mode 100644 index 00000000..b8ecce83 --- /dev/null +++ b/ca_profile.xml @@ -0,0 +1,12 @@ + + + + StemDeck splits any song into isolated stems (vocals, drums, bass, guitar, + piano, other) and plays them back in a DAW-style multitrack player, for + practice and transcription. This repository maintains the official StemDeck + self-hosted server container. For help, open an issue on GitHub. + + https://raw.githubusercontent.com/stemdeckapp/stemdeck/main/desktop/src-tauri/icons/icon.png + https://github.com/stemdeckapp/stemdeck + https://github.com/stemdeckapp/stemdeck/issues + diff --git a/desktop/src-tauri/src/main.rs b/desktop/src-tauri/src/main.rs index ed2bfae1..bcd2fd30 100644 --- a/desktop/src-tauri/src/main.rs +++ b/desktop/src-tauri/src/main.rs @@ -1857,7 +1857,7 @@ fn free_port() -> Result<(u16, TcpListener), String> { /// The user's preferred port (Settings -> port), read from the backend's /// settings.json before launch. Defaults to 8080. fn configured_port() -> u16 { - const DEFAULT_PORT: u16 = 8080; + const DEFAULT_PORT: u16 = 8000; let Ok(data_dir) = local_data_dir() else { return DEFAULT_PORT; }; diff --git a/run.sh b/run.sh index a4403782..826ab1ed 100755 --- a/run.sh +++ b/run.sh @@ -6,7 +6,7 @@ set -euo pipefail cd "$(dirname "$0")" HOST="${HOST:-0.0.0.0}" -PORT="${PORT:-8080}" +PORT="${PORT:-8000}" 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 diff --git a/templates/stemdeck.xml b/templates/stemdeck.xml new file mode 100644 index 00000000..00574c6a --- /dev/null +++ b/templates/stemdeck.xml @@ -0,0 +1,57 @@ + + + StemDeck + ghcr.io/stemdeckapp/stemdeck:edge + https://github.com/stemdeckapp/stemdeck/pkgs/container/stemdeck + bridge + sh + false + https://github.com/stemdeckapp/stemdeck/issues + https://github.com/stemdeckapp/stemdeck + https://raw.githubusercontent.com/stemdeckapp/stemdeck/main/templates/stemdeck.xml + https://raw.githubusercontent.com/stemdeckapp/stemdeck/main/README.md + https://raw.githubusercontent.com/stemdeckapp/stemdeck/main/desktop/src-tauri/icons/icon.png + http://[IP]:[PORT:8000]/ + + + MediaApp:Music MediaApp:Video Tools: + stems demucs vocal remover karaoke isolation multitrack youtube audio separation + Optional: install the Unraid "Nvidia Driver" plugin and set Extra Parameters to --runtime=nvidia for GPU acceleration. Runs on CPU without a GPU. + + StemDeck splits any song into isolated stems (vocals, drums, bass, guitar, + piano, other) and plays them back in a DAW-style multitrack player. Paste a + YouTube URL or upload a file, get separated tracks you can mute, solo, loop, + slow down, and pitch-shift for practice and transcription. Powered by Demucs. + + Data: processed tracks and downloads live under /app/jobs; Demucs model + weights and the torch cache live under /cache. Map both to persistent + appdata paths so nothing re-downloads on update. + + The library is persistent and user-managed on Unraid + (STEMDECK_PERSIST_LIBRARY=1 is set by default); tracks are never auto-deleted. + + GPU (optional): install the Unraid "Nvidia Driver" plugin, then set Extra + Parameters to --runtime=nvidia and keep NVIDIA_VISIBLE_DEVICES / + NVIDIA_DRIVER_CAPABILITIES. StemDeck auto-detects CUDA and uses it, + processing several times faster. Without a GPU it runs on CPU with no extra + configuration. Note: very new Blackwell cards (RTX 50 series) may fall back + to CPU until a newer CUDA build ships. + + Split songs into stems and play them in a DAW-style multitrack player. Self-hosted, GPU-accelerated with the Unraid Nvidia Driver plugin. + + 8000 + + /mnt/user/appdata/stemdeck/jobs + + /mnt/user/appdata/stemdeck/cache + + 1 + + 1200 + + 3 + + all + + all + diff --git a/tests/test_network_gate.py b/tests/test_network_gate.py index ed6c90e0..033745f2 100644 --- a/tests/test_network_gate.py +++ b/tests/test_network_gate.py @@ -72,7 +72,7 @@ def test_runtime_settings_round_trip_and_clamp(): def test_port_default_and_clamp(): - assert settings_mod.get_port() == 8080 # default + assert settings_mod.get_port() == 8000 # default with TestClient(app) as c: assert c.post("/api/settings", json={"port": 9000}).json()["port"] == 9000 assert settings_mod.set_port(80) == 1024 # floor (privileged ports rejected)