Overview
Voicebox uses a two-stage build process:
- Python Server Binary — PyInstaller bundles the FastAPI backend into a standalone executable
- Tauri Desktop App — Bundles the React frontend, Rust wrapper, and Python server as a sidecar
Build Commands
just build # Build everything (server + Tauri)
just build-server # Build Python server binary only
just build-tauri # Build Tauri app only
Server Binary Build
Build Script
scripts/build-server.sh orchestrates the build:
# Determine platform (e.g., x86_64-apple-darwin)
PLATFORM=$(rustc --print host-tuple)
# Run PyInstaller via build_binary.py
cd backend
python build_binary.py
# Copy to Tauri's binaries directory
cp dist/voicebox-server ../tauri/src-tauri/binaries/voicebox-server-${PLATFORM}
PyInstaller Configuration
backend/build_binary.py contains the PyInstaller configuration:
Entry Point: Uses server.py (not main.py) for Tauri sidecar support
Key Options:
--onefile— Single executable--hidden-import— Explicitly import modules PyInstaller can't detect--collect-all— Bundle data files and native libraries for packages likemlx,zipvoice--exclude-module— Strip NVIDIA packages from CPU builds
Platform-Specific Logic:
# Apple Silicon — include MLX backend
if is_apple_silicon() and not cuda:
args.extend([
"--hidden-import", "mlx",
"--collect-all", "mlx", # Bundles .dylib and .metallib files
])
# CUDA builds — include torch.cuda
if cuda:
args.extend(["--hidden-import", "torch.cuda"])
# CPU builds — exclude NVIDIA packages to save ~3GB
else:
for pkg in ["nvidia", "nvidia.cublas", "nvidia.cudnn", ...]:
args.extend(["--exclude-module", pkg])
Environment Variable:
export QWEN_TTS_PATH=~/path/to/Qwen3-TTS # Use local Qwen3-TTS source
CUDA Binary
The CUDA-enabled server is built separately due to size (~2.43 GB vs ~410 MB CPU version):
cd backend
python build_binary.py --cuda
The resulting binary is too large for GitHub Releases, so it's split into parts for distribution (see Auto-Updater docs for the download mechanism).
Tauri App Build
Tauri bundles everything together:
cd tauri
bun run tauri build
What happens:
- Vite builds the React frontend
- Rust compiles the Tauri wrapper
- Sidecar binary is copied from
src-tauri/binaries/ - Platform-specific installer created (DMG, MSI, AppImage)
Output locations:
Sidecar Configuration
The server binary is declared as an external binary in tauri.conf.json:
{
"tauri": {
"bundle": {
"externalBin": ["binaries/voicebox-server"]
}
}
}
Tauri looks for voicebox-server-${PLATFORM} in src-tauri/binaries/ and bundles it.
GitHub Actions Release
.github/workflows/release.yml automates the full build:
Matrix Strategy
| Platform | Target | Backend | Notes |
|---|---|---|---|
| macos-latest | aarch64-apple-darwin | MLX | Apple Silicon native |
| macos-15-intel | x86_64-apple-darwin | PyTorch | Intel Macs |
| windows-latest | x86_64-pc-windows-msvc | PyTorch | Windows with CUDA optional |
Build Steps
- Setup — Python, Rust, Bun, dependencies
- Build Server —
build-server.sh(Unix) orbuild_binary.py(Windows) - Build Tauri —
tauri-actionwith signing keys - Upload — Release artifacts and
latest.json
Code Signing
macOS:
- Apple Developer certificate imported from secrets
- Notarization via App Store Connect API
Windows:
- Tauri handles signing via
TAURI_SIGNING_PRIVATE_KEY
CUDA Binary (Separate Job)
The build-cuda-windows job runs separately:
- Install PyTorch with CUDA 12.8
- Build with
build_binary.py --cuda(produces--onediroutput) - Package with
scripts/package_cuda.pyinto two archives:voicebox-server-cuda.tar.gz— server core (~945 MB)cuda-libs-cu128-v1.tar.gz— NVIDIA runtime libraries (~1.7 GB, cached independently)
- Upload archives as release artifacts
This binary is downloaded on-demand by users who enable CUDA in settings. The CUDA libs archive is only re-downloaded when the CUDA toolkit version changes, not on every app update.
Troubleshooting
Binary not found in dist/
PyInstaller failed to create the output. Check:
- Python venv is activated
- All dependencies installed:
pip install -r requirements.txt - PyInstaller installed:
pip install pyinstaller
MLX/Metal libraries missing in bundle
macOS Apple Silicon builds need --collect-all mlx to include .dylib and .metallib files, not just --collect-data.
CUDA DLLs bloating CPU build
If building CPU version but CUDA torch is installed locally, the script auto-detects and swaps to CPU torch temporarily, then restores CUDA torch after.
Tauri can't find sidecar
Ensure binary exists at tauri/src-tauri/binaries/voicebox-server-${PLATFORM} before running Tauri build.