Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 126 additions & 0 deletions .github/workflows/linux-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
name: Linux Release

on:
release:
types: [published]
# Manual test build: builds and scans both variants on the self-hosted runner
# but does NOT upload (no release to attach to). Lets you validate the runner
# toolchain and the CUDA build without cutting a release tag.
workflow_dispatch:
inputs:
version:
description: "Version for the test build (must be valid PEP 440, e.g. 0.0.0)"
required: false
default: "0.0.0"

permissions: {}

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
build-and-upload:
# Runner must be linux/x64 with rustup, Node.js, Docker, and sudo apt access.
runs-on: [self-hosted, linux, x64]
timeout-minutes: 90
permissions:
contents: write
defaults:
run:
shell: bash
steps:
- name: clean workspace
run: rm -rf .build dist

- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3

- name: install build dependencies
run: |
sudo apt-get update
# Tauri v2 build deps + tooling. webkit2gtk-4.1 matches the tauri = "2"
# crate; ffmpeg is a runtime dependency, not bundled.
sudo apt-get install -y --no-install-recommends \
build-essential curl file wget libssl-dev libxdo-dev \
libwebkit2gtk-4.1-dev libgtk-3-dev \
libayatana-appindicator3-dev librsvg2-dev

- name: install uv
run: |
curl -LsSf https://astral.sh/uv/install.sh | sh
echo "$HOME/.local/bin" >> "$GITHUB_PATH"

- name: install rust toolchain
run: rustup default stable

- name: write version files
env:
DISPATCH_VERSION: ${{ inputs.version }}
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
VERSION="${DISPATCH_VERSION#v}"
else
VERSION="${GITHUB_REF_NAME#v}"
fi
if [ -z "$VERSION" ]; then
echo "Could not determine a version" >&2
exit 1
fi
printf '{ "version": "%s" }\n' "$VERSION" > static/version.json
sed -i "s/^version = \".*\"/version = \"$VERSION\"/" desktop/src-tauri/Cargo.toml
sed -i "s/\"version\": \"[^\"]*\"/\"version\": \"$VERSION\"/" desktop/src-tauri/tauri.conf.json
sed -i "s/^version = \".*\"/version = \"$VERSION\"/" pyproject.toml
sed -i "s/\"version\": \"[^\"]*\"/\"version\": \"$VERSION\"/" desktop/package.json
echo "VERSION=$VERSION" >> "$GITHUB_ENV"
echo "Wrote version $VERSION to all version files"

- name: build Linux CPU
run: |
PACKAGE_NAME=StemDeck-Linux-x64 \
PACKAGE_VERSION="$VERSION" \
bash scripts/linux/make-portable.sh
# Drop the uncompressed stage but keep the tarball; frees disk for the
# larger NVIDIA build. The Tauri binary under target/ is preserved.
rm -rf dist/StemDeck-Linux-x64

- name: build Linux NVIDIA
run: |
# CPU_ONLY=0 keeps the CUDA torch wheel; SKIP_TAURI_BUILD=1 reuses the
# binary built in the CPU step (identical for both variants).
PACKAGE_NAME=StemDeck-Linux-x64.NVIDIA \
PACKAGE_VERSION="$VERSION" \
CPU_ONLY=0 \
SKIP_TAURI_BUILD=1 \
bash scripts/linux/make-portable.sh
rm -rf dist/StemDeck-Linux-x64.NVIDIA

- name: scan artifacts
run: |
echo "Artifacts staged in: $PWD/dist"
ls -lh dist
echo "SHA256 checksums:"
( cd dist && sha256sum *.tar.gz )

echo "Pulling latest ClamAV scanner image..."
docker pull clamav/clamav:latest

echo "Running ClamAV scan over dist/..."
docker run --rm -v "$PWD/dist:/scan:ro" clamav/clamav:latest \
clamscan --recursive --infected --bell /scan
echo "ClamAV scan completed successfully. No infected files reported."

- name: upload artifacts
# Only attach to a real release; a manual test build has nothing to upload to.
if: github.event_name == 'release'
uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0
with:
files: |
dist/StemDeck-Linux-x64.tar.gz
dist/StemDeck-Linux-x64.tar.gz.sha256
dist/StemDeck-Linux-x64.NVIDIA.tar.gz
dist/StemDeck-Linux-x64.NVIDIA.tar.gz.sha256
append_body: true
body: |
### Artifact scan

- The Linux portable packages (CPU and NVIDIA) were scanned with ClamAV in CI before upload.
29 changes: 16 additions & 13 deletions desktop/src-tauri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -550,11 +550,14 @@ fn start_backend(
(Stdio::null(), Stdio::null())
});

// On macOS, python-build-standalone detects its own prefix by walking up from
// bin/ — PYTHONHOME is not needed and actively breaks startup when mis-computed.
// On Windows the venv launcher needs PYTHONHOME to locate the bundled stdlib.
// On macOS and Linux, python-build-standalone detects its own prefix by
// walking up from bin/ — PYTHONHOME is not needed and actively breaks
// startup when mis-computed (it would point at python/bin, whose
// lib/python3.X has no stdlib, so even `encodings` fails to import).
// Only Windows, whose portable venv keeps the stdlib under base/Lib,
// needs PYTHONHOME to locate the bundled stdlib.
// Compute before moving python into Command::new.
#[cfg(not(target_os = "macos"))]
#[cfg(windows)]
let pythonhome = python
.parent()
.and_then(|bin_dir| bin_dir.parent().map(|venv| (venv, bin_dir)))
Expand All @@ -570,7 +573,7 @@ fn start_backend(
"--port",
&port.to_string(),
]);
#[cfg(not(target_os = "macos"))]
#[cfg(windows)]
if let Some(ref pythonhome) = pythonhome {
cmd.env("PYTHONHOME", pythonhome);
}
Expand Down Expand Up @@ -929,17 +932,17 @@ fn python_stdlib_ok(python: &Path) -> bool {
}
let mut cmd = Command::new(python);
cmd.args(["-c", "import encodings"]);
#[cfg(not(target_os = "macos"))]
// Only Windows needs PYTHONHOME: its portable venv keeps the stdlib under
// base/Lib. macOS and Linux use python-build-standalone, which auto-detects
// its prefix from bin/ — setting PYTHONHOME there points at the wrong dir
// and breaks the import (parity with start_backend).
#[cfg(windows)]
{
let venv_root = python.parent().and_then(|b| b.parent());
// On Windows the portable venv layout puts the stdlib in base/Lib/, not Lib/.
let pythonhome = venv_root.map(|venv| {
#[cfg(windows)]
{
let base = venv.join("base");
if base.join("Lib").join("os.py").is_file() {
return base;
}
let base = venv.join("base");
if base.join("Lib").join("os.py").is_file() {
return base;
}
venv.to_path_buf()
});
Expand Down
72 changes: 72 additions & 0 deletions packaging/linux/README-LINUX.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
StemDeck Linux Portable Alpha
=============================

This comes in two variants. Pick one:

- StemDeck-Linux-x64.tar.gz CPU-only (smaller; runs anywhere)
- StemDeck-Linux-x64.NVIDIA.tar.gz NVIDIA/CUDA (larger; much faster on an
NVIDIA GPU, falls back to CPU if no GPU)

Run
---

1. Extract the tarball, e.g.:
tar -xzf StemDeck-Linux-x64.tar.gz
2. Install the runtime prerequisites (see below).
3. Run the launcher:
cd StemDeck-Linux-x64 # or StemDeck-Linux-x64.NVIDIA
./StemDeck
4. Let first-run setup prepare local runtime assets.

Prerequisites
-------------

This portable package bundles its own Python runtime (torch + demucs), but the
desktop shell links against your system's WebKitGTK libraries, and StemDeck
expects FFmpeg on your PATH. Install both with your package manager.

Debian / Ubuntu:
sudo apt update
sudo apt install libwebkit2gtk-4.1-0 libgtk-3-0 ffmpeg

Fedora:
sudo dnf install webkit2gtk4.1 gtk3 ffmpeg

Arch:
sudo pacman -S webkit2gtk-4.1 gtk3 ffmpeg

NVIDIA variant
--------------

The NVIDIA/CUDA build bundles a CUDA-enabled PyTorch. To use the GPU you also
need a working NVIDIA driver on the host such that `nvidia-smi` runs and reports
your GPU. The CUDA runtime itself is bundled — you do NOT need a separate CUDA
toolkit install, only the driver.

Check your driver:
nvidia-smi

If no usable GPU is detected, the NVIDIA build still runs but falls back to CPU.
If you do not have an NVIDIA GPU, use the CPU-only tarball instead — it is
smaller and avoids downloading the CUDA runtime.

Notes
-----

- This is a portable folder, not a system package. No .desktop entry, service,
or package-manager integration is created.
- User data lives under $XDG_DATA_HOME/stemdeck (or ~/.local/share/stemdeck).
- Your stem library is written to ~/Documents/StemDeck/.
- Demucs model weights download from the backend on first use into the data
directory under models/.

Troubleshooting
---------------

- "./StemDeck: error while loading shared libraries" — install the WebKitGTK
and GTK packages listed above.
- "ffmpeg not found" or a job failing immediately — install ffmpeg and ensure
`ffmpeg -version` works in your shell.
- If setup fails, check internet access and retry.
- Inspect logs under the data directory's logs/ folder.
- Deleting the data directory forces first-run setup to recreate runtime state.
77 changes: 77 additions & 0 deletions packaging/linux/THIRD_PARTY_NOTICES.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
THIRD-PARTY NOTICES
===================

StemDeck includes third-party open-source software. Each component is
copyrighted by its respective authors and distributed under its own license.

This starter notice is not a substitute for the full license inventory that
must be generated from the final packaged Python runtime before a public
release.

Bundled Components
------------------

Python
License: Python Software Foundation License
Website: https://www.python.org/

Tauri
License: MIT or Apache-2.0, depending on component
Website: https://tauri.app/

FastAPI
License: MIT
Website: https://fastapi.tiangolo.com/

Uvicorn
License: BSD
Website: https://www.uvicorn.org/

yt-dlp
License: Unlicense
Website: https://github.com/yt-dlp/yt-dlp

Demucs
License: MIT
Website: https://github.com/facebookresearch/demucs

PyTorch / Torch
License: BSD-style
Website: https://pytorch.org/

torchaudio
License: BSD-style
Website: https://pytorch.org/audio/

librosa
License: ISC
Website: https://librosa.org/

pyloudnorm
License: MIT
Website: https://github.com/csteinmetz1/pyloudnorm

soundfile
License: BSD
Website: https://github.com/bastibe/python-soundfile

System Requirements (Not Bundled)
---------------------------------

FFmpeg is not bundled in the Linux package. StemDeck calls the `ffmpeg` binary
from your PATH; install it via your system package manager. FFmpeg may be
distributed under LGPL or GPL terms depending on how your distribution compiles
it.

WebKitGTK and GTK shared libraries are provided by your Linux distribution and
are not bundled. They are distributed under LGPL terms.

Demucs model weights are not bundled. They are downloaded during first use.
StemDeck should display the model source and license/usage terms before or
during download.

Disclaimer
----------

This notice file is not legal advice. Before public release, verify the exact
licenses of every bundled package and generated binary artifact.
Loading