Files
VoiceStudio/scripts/desktop-prod.sh
T
Palash DebnathandClaude Opus 4.7 0f73a64101 fix(desktop-prod): also clean backend data dir for actual fresh-install emulation (#103)
The script was cleaning Tauri's APP_ID dir
(~/Library/Application Support/com.debpalash.omnivoice-studio) but the
Python backend writes to ~/Library/Application Support/OmniVoice — a
separate hardcoded path in backend/core/config.py::get_app_data_dir().

Result: "🧹 Cleaning all OmniVoice data for fresh prod emulation" was
deleting an empty directory while the real user data (SQLite db, voice
profiles, dub jobs, outputs, logs) sat untouched. Developers running
desktop-prod thought they were testing a clean install path, but were
actually testing on accumulated state.

Fix: add a BACKEND_DATA variable and a 1b cleanup step targeting the
backend's actual data dir. Per platform:
  - macOS:   ~/Library/Application Support/OmniVoice
  - Linux:   ~/.omnivoice
  - Windows: %APPDATA%/OmniVoice (not in this script; Windows uses .bat)

Surfaced while running `bun desktop-prod` for the first time today on a
clean tree — the .app launched fine but Settings showed a pre-existing
voice profile from a prior session, contradicting the "fresh" claim.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 09:50:10 +05:30

240 lines
9.4 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" ;;
*) 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