name: Release on: workflow_dispatch: push: tags: - "v*" jobs: release: permissions: contents: write strategy: fail-fast: false matrix: include: - platform: "macos-latest" args: "--target aarch64-apple-darwin" python-version: "3.12" backend: "mlx" - platform: "macos-15-intel" args: "--target x86_64-apple-darwin" python-version: "3.12" backend: "pytorch" - platform: "windows-latest" args: "" python-version: "3.12" backend: "pytorch" runs-on: ${{ matrix.platform }} steps: - uses: actions/checkout@v4 # Ubuntu runners ship with ~14 GB free; pip + PyInstaller + torch can # peak well above that during the build. Reclaim ~25 GB by pruning # preinstalled toolchains we don't use. This is what likely tripped # the March 2026 Linux release attempts (see commit 103e98b # "github runners suck") — not a code issue, a disk-pressure one. - name: Free up disk space (ubuntu) if: contains(matrix.platform, 'ubuntu') || contains(matrix.platform, 'namespace') # Pinned to v1.3.1 (SHA) — this job runs with contents: write and # handles signing secrets later, so we don't want a floating ref. uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be with: tool-cache: false android: true dotnet: true haskell: true # large-packages: true would `apt-get remove '^llvm-.*'`, which # cascade-removes reverse deps that won't be pulled back in by the # `llvm-dev` install below. The other flags already free ~20 GB, # enough for the Python + torch + PyInstaller build. large-packages: false swap-storage: true - name: Install dependencies (ubuntu only) if: contains(matrix.platform, 'ubuntu') || contains(matrix.platform, 'namespace') run: | sudo apt-get update sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf llvm-dev libasound2-dev - name: Install LLVM (macOS) if: matrix.platform == 'macos-latest' || matrix.platform == 'macos-15-intel' run: | brew install llvm@20 echo "$(brew --prefix llvm@20)/bin" >> $GITHUB_PATH echo "LLVM_CONFIG=$(brew --prefix llvm@20)/bin/llvm-config" >> $GITHUB_ENV - name: Setup Python uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} cache: "pip" - name: Install CPU-only PyTorch (Linux) if: contains(matrix.platform, 'ubuntu') || contains(matrix.platform, 'namespace') run: | pip install torch torchaudio --index-url https://download.pytorch.org/whl/cpu - name: Install Python dependencies run: | python -m pip install --upgrade pip pip install pyinstaller pip install -r backend/requirements.txt pip install --no-deps chatterbox-tts pip install --no-deps hume-tada - name: Install MLX dependencies (Apple Silicon only) if: matrix.backend == 'mlx' run: | pip install -r backend/requirements-mlx.txt # mlx-audio>=0.3.1 and mlx-lm>=0.31.1 both declare transformers>=5.x, # which conflicts with our 4.57.x cap. The runtime APIs we use work # fine on transformers 4.57.x in practice (verified in dev), so install # them --no-deps. mlx-audio's other runtime deps (huggingface_hub, # librosa, numpy, numba, pyloudnorm) are already in requirements.txt; # miniaudio is in requirements-mlx.txt (needed by mlx_audio.stt, # not transitively pulled by anything else — see issue #505); the # rest (sounddevice, protobuf, sentencepiece, pyyaml, jinja2) are # pulled in by other engines. pip install --no-deps mlx-lm==0.31.1 pip install --no-deps mlx-audio==0.4.1 - name: Build Python server (Linux/macOS) if: matrix.platform != 'windows-latest' run: | chmod +x scripts/build-server.sh ./scripts/build-server.sh - name: Build Python server (Windows) if: matrix.platform == 'windows-latest' shell: bash run: | cd backend python build_binary.py python build_binary.py --shim # Get platform tuple PLATFORM=$(rustc --print host-tuple) # Create binaries directory mkdir -p ../tauri/src-tauri/binaries # Copy with platform suffix cp dist/voicebox-server.exe ../tauri/src-tauri/binaries/voicebox-server-${PLATFORM}.exe cp dist/voicebox-mcp.exe ../tauri/src-tauri/binaries/voicebox-mcp-${PLATFORM}.exe echo "Built voicebox-server-${PLATFORM}.exe" echo "Built voicebox-mcp-${PLATFORM}.exe" - name: Setup Bun uses: oven-sh/setup-bun@v2 - name: Install Rust stable uses: dtolnay/rust-toolchain@stable with: targets: ${{ (matrix.platform == 'macos-latest' && 'aarch64-apple-darwin') || (matrix.platform == 'macos-15-intel' && 'x86_64-apple-darwin') || '' }} - name: Rust cache uses: swatinem/rust-cache@v2 with: workspaces: "./tauri/src-tauri -> target" - name: Install dependencies run: bun install - name: Install Apple API key if: matrix.platform == 'macos-latest' || matrix.platform == 'macos-15-intel' run: | mkdir -p ~/.appstoreconnect/private_keys/ cd ~/.appstoreconnect/private_keys/ echo ${{ secrets.APPLE_API_KEY_BASE64 }} >> AuthKey_${{ secrets.APPLE_API_KEY }}.p8.base64 base64 --decode -i AuthKey_${{ secrets.APPLE_API_KEY }}.p8.base64 -o AuthKey_${{ secrets.APPLE_API_KEY }}.p8 rm AuthKey_${{ secrets.APPLE_API_KEY }}.p8.base64 - name: Install Codesigning Certificate if: matrix.platform == 'macos-latest' || matrix.platform == 'macos-15-intel' uses: apple-actions/import-codesign-certs@v3 with: p12-file-base64: ${{ secrets.APPLE_CERTIFICATE }} p12-password: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} - name: Disk / environment snapshot (pre-bundle debug) if: contains(matrix.platform, 'ubuntu') || contains(matrix.platform, 'namespace') run: | echo "=== df -h ===" df -h echo "=== free -h ===" free -h echo "=== Rust / Cargo ===" rustc --version cargo --version echo "=== Bun ===" bun --version echo "=== Tauri CLI ===" cd tauri && bun run tauri --version - name: Extract release notes from CHANGELOG.md id: changelog shell: bash run: | # Get the version from the tag (strip leading 'v') VERSION="${GITHUB_REF_NAME#v}" # Extract the section for this version from CHANGELOG.md # Matches from "## [X.Y.Z]" until the next "## [" heading NOTES=$(sed -n "/^## \[${VERSION}\]/,/^## \[/{/^## \[${VERSION}\]/d;/^## \[/d;p;}" CHANGELOG.md) # Fall back to a placeholder if the version isn't in the changelog if [ -z "$(echo "$NOTES" | tr -d '[:space:]')" ]; then NOTES="See the assets below to download and install this version." fi # Use multiline output syntax { echo "notes<> "$GITHUB_OUTPUT" # Linux hang watchdog: previous releases silently wedged inside tauri # bundling (possibly linuxdeploy/AppImage download, possibly cargo link). # Cap the step at 30 min so we get logs instead of waiting out the 6hr # job timeout. Other platforms historically complete in ~25 min, so 45 # is comfortable. - uses: tauri-apps/tauri-action@v0.6 timeout-minutes: ${{ (contains(matrix.platform, 'ubuntu') || contains(matrix.platform, 'namespace')) && 30 || 45 }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} ENABLE_CODE_SIGNING: ${{ secrets.APPLE_CERTIFICATE }} APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }} APPLE_PROVIDER_SHORT_NAME: ${{ secrets.APPLE_PROVIDER_SHORT_NAME }} APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }} APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }} # Stream subprocess stdout/stderr so the hang is visible in logs. CARGO_TERM_VERBOSE: "true" RUST_BACKTRACE: "1" with: projectPath: tauri tagName: v__VERSION__ releaseName: "voicebox v__VERSION__" releaseBody: ${{ steps.changelog.outputs.notes }} releaseDraft: true prerelease: false args: ${{ matrix.args }} includeUpdaterJson: true # Tauri's bundler signs the .app and notarizes it, but the .dmg wrapper # ships unnotarized. Gatekeeper rejects that on macOS 15 Sequoia (caught # by Homebrew Cask CI) and causes "app isn't signed" dialogs on older # Intel Macs when Apple's notarization servers are slow (see issue #509). # Submit the .dmg to notarytool, staple the ticket, and overwrite the # release asset uploaded by tauri-action. - name: Notarize and staple DMG (macOS) if: matrix.platform == 'macos-latest' || matrix.platform == 'macos-15-intel' env: APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY }} APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | set -euo pipefail KEY_PATH="$HOME/.appstoreconnect/private_keys/AuthKey_${APPLE_API_KEY_ID}.p8" TARGET=$(echo "${{ matrix.args }}" | sed -n 's/.*--target \([a-z0-9_-]*\).*/\1/p') DMG_DIR="tauri/src-tauri/target/${TARGET}/release/bundle/dmg" # Match the release tag tauri-action resolved from tauri.conf.json's # version field; GITHUB_REF_NAME is a branch name under workflow_dispatch. RELEASE_TAG="v$(jq -r '.version' tauri/src-tauri/tauri.conf.json)" shopt -s nullglob dmgs=("${DMG_DIR}"/*.dmg) if [ ${#dmgs[@]} -eq 0 ]; then echo "::error::No DMGs found in ${DMG_DIR} — tauri bundler output path may have changed" exit 1 fi for dmg in "${dmgs[@]}"; do echo "::group::Notarize $(basename "$dmg")" xcrun notarytool submit "$dmg" \ --key "$KEY_PATH" \ --key-id "$APPLE_API_KEY_ID" \ --issuer "$APPLE_API_ISSUER" \ --wait --timeout 20m xcrun stapler staple "$dmg" spctl -a -t open --context context:primary-signature -vv "$dmg" gh release upload "${RELEASE_TAG}" "$dmg" --clobber \ --repo "${GITHUB_REPOSITORY}" echo "::endgroup::" done build-cuda-windows: runs-on: windows-latest permissions: contents: write steps: - uses: actions/checkout@v4 - name: Setup Python uses: actions/setup-python@v5 with: python-version: "3.12" cache: "pip" - name: Install Python dependencies run: | python -m pip install --upgrade pip pip install pyinstaller pip install -r backend/requirements.txt pip install --no-deps chatterbox-tts pip install --no-deps hume-tada - name: Install PyTorch with CUDA 12.8 run: | pip install torch --index-url https://download.pytorch.org/whl/cu128 --force-reinstall --no-deps pip install torchaudio --index-url https://download.pytorch.org/whl/cu128 --force-reinstall --no-deps - name: Verify CUDA support in torch run: | python -c "import torch; print(f'CUDA available in build: {torch.cuda.is_available()}'); print(f'CUDA version: {torch.version.cuda}')" - name: Build CUDA server binary (onedir) shell: bash working-directory: backend env: # Include Blackwell (sm_120) via PTX forward compatibility. # Pre-built PyTorch cu128 wheels ship native kernels for sm_80/86/89/90 # but not sm_120. Setting this env var causes torch.utils.cpp_extension # (and any JIT-compiled kernels) to target Blackwell GPUs as well. TORCH_CUDA_ARCH_LIST: "8.0;8.6;8.9;9.0;12.0+PTX" run: python build_binary.py --cuda - name: Package into server core + CUDA libs archives shell: bash run: | python scripts/package_cuda.py \ backend/dist/voicebox-server-cuda/ \ --output release-assets/ \ --cuda-libs-version cu128-v1 \ --torch-compat ">=2.7.0,<2.11.0" - name: Upload archives to GitHub Release if: startsWith(github.ref, 'refs/tags/') uses: softprops/action-gh-release@v2 with: files: | release-assets/voicebox-server-cuda.tar.gz release-assets/voicebox-server-cuda.tar.gz.sha256 release-assets/cuda-libs-cu128-v1.tar.gz release-assets/cuda-libs-cu128-v1.tar.gz.sha256 release-assets/cuda-libs.json draft: true env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Upload onedir as workflow artifact uses: actions/upload-artifact@v4 with: name: voicebox-server-cuda-windows path: backend/dist/voicebox-server-cuda/ retention-days: 7 build-rocm-windows: runs-on: windows-latest permissions: contents: write steps: - uses: actions/checkout@v4 - name: Setup Python uses: actions/setup-python@v5 with: # ROCm wheels are cp312-cp312-specific — build_binary.py --rocm enforces this. python-version: "3.12" cache: "pip" - name: Install Python dependencies run: | python -m pip install --upgrade pip pip install pyinstaller pip install -r backend/requirements.txt pip install --no-deps chatterbox-tts pip install --no-deps hume-tada - name: Build ROCm server binary (onedir) shell: bash working-directory: backend # build_binary.py --rocm pulls the official AMD Radeon torch + rocm_sdk # wheels (rocm-rel-7.2.1) itself when ROCm torch is not already present, # then restores the dev torch afterwards. run: python build_binary.py --rocm - name: Package into server core + ROCm libs archives shell: bash run: | python scripts/package_rocm.py \ backend/dist/voicebox-server-rocm/ \ --output release-assets/ \ --rocm-libs-version rocm7.2-v1 \ --torch-compat ">=2.9.0,<2.10.0" - name: Upload archives to GitHub Release if: startsWith(github.ref, 'refs/tags/') uses: softprops/action-gh-release@v2 with: files: | release-assets/voicebox-server-rocm.tar.gz release-assets/voicebox-server-rocm.tar.gz.sha256 release-assets/rocm-libs-rocm7.2-v1.tar.gz release-assets/rocm-libs-rocm7.2-v1.tar.gz.sha256 release-assets/rocm-libs.json draft: true env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Upload onedir as workflow artifact uses: actions/upload-artifact@v4 with: name: voicebox-server-rocm-windows path: backend/dist/voicebox-server-rocm/ retention-days: 7