8f10353f0c
CI / lint (push) Has been cancelled
CI / js-syntax (push) Successful in 10m24s
CI / deps-audit (push) Has been cancelled
CI / test (push) Failing after 24m55s
CI / sast-bandit (push) Failing after 11m13s
CI / trivy (push) Failing after 9m32s
Docker Publish / build-and-push (push) Failing after 34m3s
193 lines
8.1 KiB
Bash
Executable File
193 lines
8.1 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# Build a portable Linux StemDeck package: a single .tar.gz containing the
|
|
# Tauri binary plus a self-contained Python runtime (torch + demucs), so the
|
|
# user extracts and runs ./StemDeck with no toolchain.
|
|
#
|
|
# This is the Linux analog of scripts/windows/make-portable.ps1. Like the macOS
|
|
# runtime pack (scripts/macos/make-runtime-pack.sh) it bundles a full
|
|
# python-build-standalone install — a plain `venv` will not work because the
|
|
# desktop shell checks for the stdlib under python/lib/ (python_stdlib_present
|
|
# in desktop/src-tauri/src/main.rs).
|
|
#
|
|
# Phase 1 ships the CPU-only variant. FFmpeg is NOT bundled in the tarball (so we
|
|
# don't redistribute it); instead the desktop shell downloads a static build on
|
|
# first launch into the user data dir, falling back to a system `ffmpeg` on PATH
|
|
# when one exists (see ensure_ffmpeg / download_linux_ffmpeg).
|
|
#
|
|
# Layout produced (so find_repo_root matches its backend/app + python branch):
|
|
# StemDeck-Linux-x64/
|
|
# StemDeck # Tauri ELF binary
|
|
# cpu-only # marker read by is_cpu_only_package
|
|
# README-LINUX.txt
|
|
# THIRD_PARTY_NOTICES.txt
|
|
# backend/{app,static,pyproject.toml,uv.lock}
|
|
# python/{bin/python,lib/pythonX.Y/...} # full PBS install
|
|
|
|
set -euo pipefail
|
|
|
|
PACKAGE_NAME="${PACKAGE_NAME:-StemDeck-Linux-x64}"
|
|
PACKAGE_VERSION="${PACKAGE_VERSION:-}"
|
|
OUTPUT_ROOT="${OUTPUT_ROOT:-dist}"
|
|
PYTHON_VERSION="${PYTHON_VERSION:-3.12}"
|
|
TORCH_VERSION="${TORCH_VERSION:-2.6.0}"
|
|
SKIP_TAURI_BUILD="${SKIP_TAURI_BUILD:-0}"
|
|
# CPU_ONLY=1 (default): force the CPU-only torch wheel and mark the package so the
|
|
# desktop shell skips GPU detection. CPU_ONLY=0: keep the project's default torch,
|
|
# which on Linux x86_64 is the CUDA build (NVIDIA variant) — the shell then detects
|
|
# the GPU and uses CUDA at runtime.
|
|
CPU_ONLY="${CPU_ONLY:-1}"
|
|
|
|
REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
|
|
STAGE="${REPO_ROOT}/${OUTPUT_ROOT}/${PACKAGE_NAME}"
|
|
ARCHIVE_PATH="${REPO_ROOT}/${OUTPUT_ROOT}/${PACKAGE_NAME}.tar.gz"
|
|
CHECKSUM_PATH="${ARCHIVE_PATH}.sha256"
|
|
PYTHON_DIR="${STAGE}/python"
|
|
BACKEND_DIR="${STAGE}/backend"
|
|
TARGET_BIN="${REPO_ROOT}/desktop/src-tauri/target/release/stemdeck"
|
|
|
|
if [[ "$(uname -s)" != "Linux" ]]; then
|
|
echo "ERROR: this packaging script must run on Linux." >&2
|
|
exit 1
|
|
fi
|
|
|
|
require_command() {
|
|
if ! command -v "$1" >/dev/null 2>&1; then
|
|
echo "ERROR: required command not found on PATH: $1" >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
require_command uv
|
|
require_command cargo
|
|
require_command node
|
|
require_command npm
|
|
require_command tar
|
|
require_command sha256sum
|
|
|
|
# python-build-standalone (PBS) Python for x86_64 Linux. Unlike a venv, the
|
|
# full install carries its own stdlib under lib/, which the desktop shell needs.
|
|
echo "==> Installing python-build-standalone ${PYTHON_VERSION}"
|
|
uv python install "cpython-${PYTHON_VERSION}-linux-x86_64-gnu"
|
|
PBS_PYTHON="$(uv python find "cpython-${PYTHON_VERSION}-linux-x86_64-gnu")"
|
|
PBS_BASE_PREFIX="$("$PBS_PYTHON" -c 'import sys; print(sys.base_prefix)')"
|
|
if [[ ! -d "${PBS_BASE_PREFIX}/lib" ]]; then
|
|
echo "ERROR: PBS base prefix has no lib/ dir: ${PBS_BASE_PREFIX}" >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "==> Cleaning stage"
|
|
rm -rf "$STAGE" "$ARCHIVE_PATH" "$CHECKSUM_PATH"
|
|
mkdir -p "$STAGE" "$BACKEND_DIR" "$PYTHON_DIR"
|
|
|
|
# Copy the entire PBS install into python/ (-a preserves symlinks/permissions).
|
|
echo "==> Bundling Python runtime from ${PBS_BASE_PREFIX}"
|
|
cp -a "$PBS_BASE_PREFIX/." "$PYTHON_DIR/"
|
|
# PBS ships an EXTERNALLY-MANAGED marker that blocks installs into the copy.
|
|
find "$PYTHON_DIR/lib" -name "EXTERNALLY-MANAGED" -delete 2>/dev/null || true
|
|
|
|
BUNDLED_PYTHON="${PYTHON_DIR}/bin/python"
|
|
|
|
echo "==> Installing StemDeck into bundled Python"
|
|
# --system is required because python/ is a full PBS install, not a venv.
|
|
uv pip install --system --python "$BUNDLED_PYTHON" pip setuptools wheel
|
|
# Version is git-derived (hatch-vcs / setuptools-scm). Pin it so the install
|
|
# does not depend on git tags in the build checkout (#169).
|
|
if [[ -n "$PACKAGE_VERSION" ]]; then
|
|
export SETUPTOOLS_SCM_PRETEND_VERSION="${PACKAGE_VERSION#v}"
|
|
fi
|
|
uv pip install --system --python "$BUNDLED_PYTHON" "$REPO_ROOT"
|
|
|
|
# Always bake the small CPU-only torch wheel — for BOTH variants. On Linux the
|
|
# default PyPI torch wheel bundles the full CUDA runtime (~2.5 GB), which makes
|
|
# the packaged tarball exceed GitHub's 2 GiB per-asset release limit. So we
|
|
# mirror what the Windows NVIDIA package actually does: ship CPU torch, and let
|
|
# the desktop shell download the matching CUDA wheel at first run on GPU
|
|
# machines (install_cuda_torch, gated cfg(not(macos)) so it covers Linux). The
|
|
# NVIDIA variant differs only by omitting the cpu-only marker below.
|
|
#
|
|
# pip strips the local '+cpu' version when resolving, so the project install
|
|
# pulls the CUDA wheel even if a CPU wheel was requested; --force-reinstall
|
|
# --no-deps replaces just the torch/torchaudio wheels (proven on Windows).
|
|
echo "==> Baking CPU-only torch (NVIDIA variant downloads CUDA at first run)"
|
|
"$BUNDLED_PYTHON" -m pip install \
|
|
"torch==${TORCH_VERSION}+cpu" "torchaudio==${TORCH_VERSION}+cpu" \
|
|
--index-url https://download.pytorch.org/whl/cpu \
|
|
--force-reinstall --no-deps
|
|
|
|
# The project install above pulled the default Linux torch, which is the CUDA
|
|
# build, dragging in nvidia-* CUDA runtime packages (cuDNN, cuBLAS, NCCL, ...) and
|
|
# triton -- together ~2.5 GB. The CPU torch swap used --no-deps, so those packages
|
|
# are now orphaned but still installed, bloating the tarball past GitHub's 2 GiB
|
|
# asset limit. Remove them: CPU torch does not use them, and the NVIDIA variant
|
|
# re-downloads CUDA at first run anyway.
|
|
echo "==> Removing orphaned CUDA runtime packages"
|
|
orphans=$("$BUNDLED_PYTHON" -m pip list --format=freeze 2>/dev/null \
|
|
| sed -n 's/^\(nvidia-[^=]*\)==.*/\1/p')
|
|
orphans="$orphans triton"
|
|
echo " removing:$orphans"
|
|
"$BUNDLED_PYTHON" -m pip uninstall -y $orphans 2>/dev/null || true
|
|
|
|
echo "==> Verifying imports"
|
|
"$BUNDLED_PYTHON" -c "import fastapi, uvicorn, yt_dlp, demucs, torch, torchaudio, librosa, pyloudnorm, soundfile; print('torch', torch.__version__, 'cuda', torch.version.cuda)"
|
|
|
|
echo "==> Staging backend"
|
|
cp -R "$REPO_ROOT/app" "$BACKEND_DIR/app"
|
|
cp -R "$REPO_ROOT/static" "$BACKEND_DIR/static"
|
|
cp "$REPO_ROOT/pyproject.toml" "$BACKEND_DIR/pyproject.toml"
|
|
cp "$REPO_ROOT/uv.lock" "$BACKEND_DIR/uv.lock"
|
|
RESOLVED_VERSION="${PACKAGE_VERSION#v}"
|
|
printf '{ "version": "%s" }\n' "$RESOLVED_VERSION" > "$BACKEND_DIR/static/version.json"
|
|
|
|
cp "$REPO_ROOT/packaging/linux/README-LINUX.txt" "$STAGE/README-LINUX.txt"
|
|
cp "$REPO_ROOT/packaging/linux/THIRD_PARTY_NOTICES.txt" "$STAGE/THIRD_PARTY_NOTICES.txt"
|
|
|
|
# CPU-only marker: read by is_cpu_only_package so the shell skips GPU detection.
|
|
# Omitted for the NVIDIA variant so the shell detects the GPU and uses CUDA.
|
|
if [[ "$CPU_ONLY" == "1" ]]; then
|
|
touch "$STAGE/cpu-only"
|
|
fi
|
|
|
|
echo "==> Stripping build-time artifacts from bundled Python"
|
|
find "$PYTHON_DIR" -type d -name "__pycache__" -prune -exec rm -rf {} + 2>/dev/null || true
|
|
find "$PYTHON_DIR" -type f \( -name "*.pyc" -o -name "*.pyo" \) -delete 2>/dev/null || true
|
|
TORCH_LIB="${PYTHON_DIR}/lib/python${PYTHON_VERSION}/site-packages/torch"
|
|
for rel in include test share/cmake; do
|
|
rm -rf "${TORCH_LIB:?}/${rel}" 2>/dev/null || true
|
|
done
|
|
# Static link archives are only needed to build C++ extensions, never to run.
|
|
find "$TORCH_LIB" -name "*.a" -type f -delete 2>/dev/null || true
|
|
|
|
echo "==> Building Tauri desktop binary"
|
|
if [[ "$SKIP_TAURI_BUILD" != "1" ]]; then
|
|
pushd "$REPO_ROOT/desktop" >/dev/null
|
|
if [[ -f package-lock.json ]]; then
|
|
npm ci --include=dev
|
|
else
|
|
npm install --include=dev
|
|
fi
|
|
CI=true node node_modules/@tauri-apps/cli/tauri.js build
|
|
popd >/dev/null
|
|
fi
|
|
|
|
if [[ ! -f "$TARGET_BIN" ]]; then
|
|
echo "ERROR: Tauri binary not found at ${TARGET_BIN}" >&2
|
|
exit 1
|
|
fi
|
|
cp "$TARGET_BIN" "$STAGE/StemDeck"
|
|
chmod +x "$STAGE/StemDeck"
|
|
|
|
echo "==> Creating archive"
|
|
tar -czf "$ARCHIVE_PATH" -C "${REPO_ROOT}/${OUTPUT_ROOT}" "$PACKAGE_NAME"
|
|
( cd "${REPO_ROOT}/${OUTPUT_ROOT}" && sha256sum "${PACKAGE_NAME}.tar.gz" > "${PACKAGE_NAME}.tar.gz.sha256" )
|
|
|
|
echo "==> Done"
|
|
if [[ "$CPU_ONLY" == "1" ]]; then
|
|
echo "Variant : CPU-only"
|
|
else
|
|
echo "Variant : NVIDIA/CUDA"
|
|
fi
|
|
echo "Stage : ${STAGE}"
|
|
echo "Archive : ${ARCHIVE_PATH}"
|
|
echo "Checksum: ${CHECKSUM_PATH}"
|