OmniVoice-Studio / scripts /desktop-prod.sh
LΓͺ Phi Nam
Deploy to HF Space
94004a2
Raw
History Blame Contribute Delete
9.58 kB
#!/usr/bin/env bash
# ──────────────────────────────────────────────────────────────────────────
# desktop-prod.sh β€” Build & launch OmniVoice Studio as a "fresh install"
#
# This gives you the EXACT same experience as a user downloading the
# installer (DMG on macOS, AppImage on Linux):
# β€’ Full Rust bootstrap (venv creation, uv sync, model setup)
# β€’ Splash screen with live logs
# β€’ Region selector, version badge, etc.
#
# Usage:
# bun desktop-prod # build debug + wipe + launch
# bun desktop-prod:run # re-launch last build (skip compile)
# bun desktop-prod:upgrade # rebuild, but keep data (test upgrade)
# ──────────────────────────────────────────────────────────────────────────
set -euo pipefail
APP_ID="com.debpalash.omnivoice-studio"
TAURI_DIR="frontend/src-tauri"
APP_NAME="OmniVoice Studio"
# ── Detect platform ───────────────────────────────────────────────────────
OS="$(uname -s)"
case "$OS" in
Darwin) PLATFORM="macos" ;;
Linux) PLATFORM="linux" ;;
*) echo "❌ Unsupported platform: $OS"; exit 1 ;;
esac
# ── Platform-specific paths ───────────────────────────────────────────────
# Two directories matter for fresh-install simulation:
# APP_DATA β€” Tauri's bundle dir (keyed by APP_ID); holds the post-install
# Python venv + webview state.
# BACKEND_DATA β€” Where backend/core/config.py::get_app_data_dir() writes:
# SQLite db, voice profiles, generation outputs, logs. This is
# NOT under APP_ID β€” it's a separate hardcoded name. Cleaning
# only APP_DATA leaves all user data behind, defeating the
# fresh-emulation promise.
if [ "$PLATFORM" = "macos" ]; then
APP_DATA="$HOME/Library/Application Support/${APP_ID}"
BACKEND_DATA="$HOME/Library/Application Support/OmniVoice"
TAURI_LOGS="$HOME/Library/Logs/${APP_ID}"
WEBKIT_DATA="$HOME/Library/WebKit/${APP_ID}"
else
# Linux: backend uses ~/.omnivoice (not XDG β€” see backend/core/config.py).
APP_DATA="${XDG_DATA_HOME:-$HOME/.local/share}/${APP_ID}"
BACKEND_DATA="$HOME/.omnivoice"
TAURI_LOGS="${XDG_DATA_HOME:-$HOME/.local/share}/${APP_ID}/logs"
WEBKIT_DATA="${XDG_DATA_HOME:-$HOME/.local/share}/${APP_ID}/webview"
fi
# HF cache β€” where downloaded models live
HF_CACHE="${HF_HOME:-$HOME/.cache/huggingface}"
# ── Flags ──────────────────────────────────────────────────────────────────
SKIP_BUILD=false
KEEP_DATA=false
PILL_MODE=false
for arg in "$@"; do
case "$arg" in
--skip-build) SKIP_BUILD=true ;;
--keep-data) KEEP_DATA=true ;;
--pill) PILL_MODE=true ;;
-h|--help)
echo "Usage: $0 [--skip-build] [--keep-data] [--pill]"
echo ""
echo " --skip-build Skip cargo build, use last compiled binary"
echo " --keep-data Don't wipe app data (test upgrade path)"
echo " --pill Launch in dictation-widget mode (no main window)"
exit 0
;;
esac
done
# ── Wipe app data for fresh-install simulation ─────────────────────────────
if [ "$KEEP_DATA" = false ]; then
echo "🧹 Cleaning all OmniVoice data for fresh prod emulation..."
echo ""
# 1. App data (Tauri bundle dir: post-install venv + webview state)
if [ -d "${APP_DATA}" ]; then
echo " βœ— App data: ${APP_DATA}"
rm -rf "${APP_DATA}"
else
echo " β—‹ App data: (already clean)"
fi
# 1b. Backend data (SQLite db, voice profiles, outputs, logs)
# β€” separate dir hardcoded in backend/core/config.py, NOT under APP_ID.
if [ -d "${BACKEND_DATA}" ]; then
BD_SIZE=$(du -sh "${BACKEND_DATA}" 2>/dev/null | cut -f1)
echo " βœ— Backend data: ${BACKEND_DATA} (${BD_SIZE})"
rm -rf "${BACKEND_DATA}"
else
echo " β—‹ Backend data: (already clean)"
fi
# 2. HF model cache (downloaded .safetensors, tokenizers, etc.)
if [ -d "${HF_CACHE}" ]; then
HF_SIZE=$(du -sh "${HF_CACHE}" 2>/dev/null | cut -f1)
echo " βœ— HF cache: ${HF_CACHE} (${HF_SIZE})"
rm -rf "${HF_CACHE}"
else
echo " β—‹ HF cache: (already clean)"
fi
# 3. Tauri log dir
if [ -d "${TAURI_LOGS}" ]; then
echo " βœ— Tauri logs: ${TAURI_LOGS}"
rm -rf "${TAURI_LOGS}"
else
echo " β—‹ Tauri logs: (already clean)"
fi
# 4. WebView cache / local storage
if [ -d "${WEBKIT_DATA}" ]; then
echo " βœ— WebKit data: ${WEBKIT_DATA}"
rm -rf "${WEBKIT_DATA}"
else
echo " β—‹ WebKit data: (already clean)"
fi
echo ""
echo " βœ… All clean β€” next launch bootstraps from zero."
else
echo "πŸ“¦ Keeping existing app data (upgrade test mode)"
fi
# ── Build debug binary ─────────────────────────────────────────────────────
if [ "$SKIP_BUILD" = false ]; then
echo ""
echo "πŸ”¨ Building debug bundle (this takes 1-3 min first time)..."
# Remove stale bundle so we never accidentally launch old code
if [ "$PLATFORM" = "macos" ]; then
APP_BUNDLE="${TAURI_DIR}/target/debug/bundle/macos/${APP_NAME}.app"
[ -d "$APP_BUNDLE" ] && rm -rf "$APP_BUNDLE"
fi
# Linux: linuxdeploy uses FUSE to mount itself; if FUSE is unavailable
# (containers, some hardened kernels), set APPIMAGE_EXTRACT_AND_RUN=1 to
# extract-and-run instead. Safe to always set on Linux.
if [ "$PLATFORM" = "linux" ]; then
export APPIMAGE_EXTRACT_AND_RUN=1
fi
# The build creates the bundle successfully, but then may fail trying
# to sign the updater artifact (no TAURI_SIGNING_PRIVATE_KEY) or to
# run linuxdeploy. The binary itself is fine β€” tolerate known errors.
BUILD_LOG=$(mktemp)
cd frontend
set +e
bunx tauri build --debug 2>&1 | tee "$BUILD_LOG"
BUILD_EXIT=$?
set -e
cd ..
if [ $BUILD_EXIT -ne 0 ]; then
# Known-harmless failures:
# - Missing TAURI_SIGNING_PRIVATE_KEY (updater signing)
# - "failed to run linuxdeploy" (AppImage bundling β€” binary still works)
if grep -qi "TAURI_SIGNING_PRIVATE_KEY\|private key\|failed to run linuxdeploy\|failed to bundle" "$BUILD_LOG"; then
echo "⚠️ Non-fatal bundle error β€” binary is fine (see above for details)."
else
echo "❌ Build failed with exit code $BUILD_EXIT"
rm -f "$BUILD_LOG"
exit $BUILD_EXIT
fi
fi
rm -f "$BUILD_LOG"
echo "βœ… Build complete."
else
echo "⏭️ Skipping build (--skip-build)"
fi
# ── Build launch args ──────────────────────────────────────────────────────
LAUNCH_ARGS=()
if [ "$PILL_MODE" = true ]; then
LAUNCH_ARGS+=("--pill")
echo "πŸ“Œ Launch mode: pill (dictation-only widget, no main window)"
fi
# ── Find and launch the app ────────────────────────────────────────────────
if [ "$PLATFORM" = "macos" ]; then
APP_BUNDLE="${TAURI_DIR}/target/debug/bundle/macos/${APP_NAME}.app"
BINARY="${TAURI_DIR}/target/debug/omnivoice-studio"
if [ -d "$APP_BUNDLE" ]; then
echo ""
echo "πŸš€ Launching ${APP_NAME} (.app bundle)..."
echo " Bundle: ${APP_BUNDLE}"
# macOS `open` needs -n to spawn a fresh instance, --args to forward flags.
if [ ${#LAUNCH_ARGS[@]} -gt 0 ]; then
open -n "$APP_BUNDLE" --args "${LAUNCH_ARGS[@]}"
else
open "$APP_BUNDLE"
fi
elif [ -f "$BINARY" ]; then
echo ""
echo "πŸš€ Launching ${APP_NAME} (raw binary β€” no .app bundle)..."
echo " Binary: ${BINARY}"
"$BINARY" "${LAUNCH_ARGS[@]}" &
else
echo "❌ No bundle or binary found. Run without --skip-build first."
exit 1
fi
else
# Linux: prefer AppImage, fall back to raw binary
APPIMAGE=$(find "${TAURI_DIR}/target/debug/bundle/appimage" -name "*.AppImage" -type f 2>/dev/null | head -1)
BINARY="${TAURI_DIR}/target/debug/omnivoice-studio"
if [ -n "$APPIMAGE" ] && [ -f "$APPIMAGE" ]; then
echo ""
echo "πŸš€ Launching ${APP_NAME} (AppImage)..."
echo " AppImage: ${APPIMAGE}"
chmod +x "$APPIMAGE"
"$APPIMAGE" "${LAUNCH_ARGS[@]}" &
elif [ -f "$BINARY" ]; then
echo ""
echo "πŸš€ Launching ${APP_NAME} (raw binary)..."
echo " Binary: ${BINARY}"
"$BINARY" "${LAUNCH_ARGS[@]}" &
else
echo "❌ No AppImage or binary found. Run without --skip-build first."
exit 1
fi
fi
echo " App data: ${APP_DATA}"
echo ""
echo "βœ… App launched. Check the splash screen for bootstrap logs."
if [ "$PILL_MODE" = true ]; then
echo " To re-run pill mode without rebuilding: bun desktop-prod:run:pill"
echo " To switch back to studio: bun desktop-prod:run"
else
echo " To re-run without rebuilding: bun desktop-prod:run"
echo " To launch as dictation widget: bun desktop-prod:pill"
fi