uname -s returns MINGW64_NT/MSYS_NT/CYGWIN_NT under Git Bash, which hit the catch-all 'Unsupported platform' error. Add a windows case + a Windows data- path branch (%APPDATA%\OmniVoice backend data + %LOCALAPPDATA% Tauri/HF dirs, per backend/core/config.py) + launch the debug .exe. The cross-platform 'tauri build' step is unchanged; every rm stays [ -d ]-guarded so an off path is a no-op, never a wrong delete. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
264 lines
11 KiB
Bash
Executable File
264 lines
11 KiB
Bash
Executable File
#!/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" ;;
|
|
MINGW*|MSYS*|CYGWIN*) PLATFORM="windows" ;; # Git Bash / MSYS2 / Cygwin on Windows
|
|
*) 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}"
|
|
HF_CACHE="${HF_HOME:-$HOME/.cache/huggingface}"
|
|
elif [ "$PLATFORM" = "windows" ]; then
|
|
# Git Bash exposes Windows env vars. Backend writes to %APPDATA%\OmniVoice
|
|
# (backend/core/config.py::get_app_data_dir) and relocates the HF cache to
|
|
# %LOCALAPPDATA%\OmniVoice\hf_cache. Tauri keys its data by APP_ID under
|
|
# LOCALAPPDATA; WebView2 state lives in EBWebView. All paths are APP_ID/
|
|
# OmniVoice-scoped, and each rm is guarded by `[ -d ]`, so a slightly-off
|
|
# path is a no-op, never a wrong delete.
|
|
APP_DATA="${LOCALAPPDATA}/${APP_ID}"
|
|
BACKEND_DATA="${APPDATA}/OmniVoice"
|
|
TAURI_LOGS="${LOCALAPPDATA}/${APP_ID}/logs"
|
|
WEBKIT_DATA="${LOCALAPPDATA}/${APP_ID}/EBWebView"
|
|
HF_CACHE="${HF_HOME:-${LOCALAPPDATA}/OmniVoice/hf_cache}"
|
|
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"
|
|
HF_CACHE="${HF_HOME:-$HOME/.cache/huggingface}"
|
|
fi
|
|
|
|
# ── 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
|
|
elif [ "$PLATFORM" = "windows" ]; then
|
|
# Windows: launch the raw debug .exe (Git Bash can exec it directly).
|
|
BINARY="${TAURI_DIR}/target/debug/omnivoice-studio.exe"
|
|
if [ -f "$BINARY" ]; then
|
|
echo ""
|
|
echo "🚀 Launching ${APP_NAME} (Windows debug .exe)..."
|
|
echo " Binary: ${BINARY}"
|
|
"$BINARY" "${LAUNCH_ARGS[@]}" &
|
|
else
|
|
echo "❌ No .exe found at ${BINARY}. 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
|