mirror of
https://github.com/jamiepine/voicebox.git
synced 2026-09-16 13:20:39 -07:00
* fix(release): notarize and staple macOS DMGs Tauri's bundler signs the .app and notarizes it, but ships the .dmg wrapper unnotarized. Gatekeeper rejects that on macOS 15 Sequoia (caught by Homebrew Cask CI) and causes the 'app isn't signed' dialog on older Intel Macs when Apple's notarization servers are slow (issue #509). New step submits each built DMG to notarytool, staples the ticket, verifies with spctl, then overwrites the release asset tauri-action already uploaded to the draft release. Adds ~5-10 min per macOS job (notarytool round-trip). * fix(release): fail loudly when no DMG is found to notarize Empty glob + nullglob was silently skipping the loop body, so if Tauri's bundler output path changed we'd re-publish the unnotarized DMGs with a green CI. Assert the glob matched at least one file. * fix(release): resolve release tag from tauri.conf.json, not GITHUB_REF_NAME GITHUB_REF_NAME is the branch name when the workflow runs via workflow_dispatch, so the gh release upload targeted the wrong thing on manual runs. tauri-action derives its tag from tauri.conf.json's version field via the v__VERSION__ template; use the same source so the two always agree.
340 lines
14 KiB
YAML
340 lines
14 KiB
YAML
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
|
|
|
|
# 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
|
|
echo "Built voicebox-server-${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<<CHANGELOG_EOF"
|
|
echo "$NOTES"
|
|
echo "CHANGELOG_EOF"
|
|
} >> "$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/[email protected]
|
|
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
|