The repository was renamed. 724 references across 59 files now point at the new URL — README badges, docs, install guides, the updater's releases API call, CONTRIBUTING, the Colab link and the probe harness. GitHub redirects the old URLs, so nothing was broken in the meantime. Deliberately NOT renamed, because each breaks something on a user's machine: the Tauri bundle identifier (the path to every existing user's data), /usr/lib/omnivoice-studio and the compose container names, and the published Docker image paths. The image path needed a code change to STAY still: docker.yml derived it from github.repository, so the next build would have published to ghcr.io/debpalash/voicestudio while Docker Hub, a hardcoded literal, stayed put — everyone pulling the documented GHCR path would have kept receiving the last pre-rename image forever. It is now pinned, with a test that fails if it ever derives from the repo name again. Also makes the probe's repo-name assertion shape-based: it hardcoded the old name and failed on every PR after the rename while the code it tests worked perfectly.
390 lines
15 KiB
Bash
Executable File
390 lines
15 KiB
Bash
Executable File
#!/bin/sh
|
||
# OmniVoice Studio — universal installer.
|
||
#
|
||
# Works on macOS (ARM + Intel), Linux (Debian/Ubuntu, Fedora, Arch), and WSL.
|
||
# Run once, then `./run.sh` each time you want to use the app.
|
||
#
|
||
# Usage:
|
||
# curl -fsSL https://raw.githubusercontent.com/debpalash/VoiceStudio/main/install.sh | sh
|
||
# # or locally:
|
||
# sh install.sh
|
||
# sh install.sh --verbose # show all subcommand output
|
||
# sh install.sh --python 3.12 # override Python version
|
||
set -e
|
||
|
||
# ── Output style ────────────────────────────────────────────────────────────
|
||
RULE=""
|
||
_rule_i=0
|
||
while [ "$_rule_i" -lt 56 ]; do
|
||
RULE="${RULE}─"
|
||
_rule_i=$((_rule_i + 1))
|
||
done
|
||
|
||
if [ -n "${NO_COLOR:-}" ]; then
|
||
C_TITLE="" C_DIM="" C_OK="" C_WARN="" C_ERR="" C_RST=""
|
||
elif [ -t 1 ] || [ -n "${FORCE_COLOR:-}" ]; then
|
||
_ESC="$(printf '\033')"
|
||
C_TITLE="${_ESC}[1;38;5;141m" # bold purple
|
||
C_DIM="${_ESC}[38;5;245m"
|
||
C_OK="${_ESC}[38;5;108m" # green
|
||
C_WARN="${_ESC}[38;5;136m" # yellow
|
||
C_ERR="${_ESC}[91m" # red
|
||
C_RST="${_ESC}[0m"
|
||
else
|
||
C_TITLE="" C_DIM="" C_OK="" C_WARN="" C_ERR="" C_RST=""
|
||
fi
|
||
|
||
step() { printf " ${C_DIM}%-18.18s${C_RST}${3:-$C_OK}%s${C_RST}\n" "$1" "$2"; }
|
||
note() { printf " ${C_DIM}%-18s${2:-$C_DIM}%s${C_RST}\n" "" "$1"; }
|
||
warn() { printf " ${C_WARN}⚠ %s${C_RST}\n" "$1"; }
|
||
die() { printf " ${C_ERR}✗ %s${C_RST}\n" "$1" >&2; exit 1; }
|
||
have() { command -v "$1" >/dev/null 2>&1; }
|
||
|
||
# ── Parse flags ─────────────────────────────────────────────────────────────
|
||
_VERBOSE=false
|
||
_USER_PYTHON=""
|
||
_next_is_python=false
|
||
for arg in "$@"; do
|
||
if [ "$_next_is_python" = true ]; then
|
||
_USER_PYTHON="$arg"
|
||
_next_is_python=false
|
||
continue
|
||
fi
|
||
case "$arg" in
|
||
--verbose|-v) _VERBOSE=true ;;
|
||
--python) _next_is_python=true ;;
|
||
esac
|
||
done
|
||
if [ "$_next_is_python" = true ]; then
|
||
die "--python requires a version argument (e.g. --python 3.12)"
|
||
fi
|
||
|
||
run_quiet() {
|
||
if [ "$_VERBOSE" = true ]; then
|
||
"$@"
|
||
else
|
||
"$@" > /dev/null 2>&1
|
||
fi
|
||
}
|
||
|
||
# ── Helper: download (curl or wget) ────────────────────────────────────────
|
||
download() {
|
||
if have curl; then
|
||
curl -LsSf "$1" -o "$2"
|
||
elif have wget; then
|
||
wget -qO "$2" "$1"
|
||
else
|
||
die "Neither curl nor wget found. Install one and re-run."
|
||
fi
|
||
}
|
||
|
||
# ── Helper: open browser (cross-platform) ──────────────────────────────────
|
||
open_browser() {
|
||
_url="$1"
|
||
if [ "$(uname)" = "Darwin" ] && have open; then
|
||
open "$_url"
|
||
elif grep -qi microsoft /proc/version 2>/dev/null; then
|
||
# WSL: use Windows browser
|
||
if have powershell.exe; then
|
||
powershell.exe -NoProfile -Command "Start-Process '$_url'" >/dev/null 2>&1 &
|
||
elif have cmd.exe; then
|
||
cmd.exe /c start "" "$_url" >/dev/null 2>&1 &
|
||
elif have xdg-open; then
|
||
xdg-open "$_url" >/dev/null 2>&1 &
|
||
else
|
||
echo " Open in your browser: $_url"
|
||
fi
|
||
elif have xdg-open; then
|
||
xdg-open "$_url" >/dev/null 2>&1 &
|
||
else
|
||
echo " Open in your browser: $_url"
|
||
fi
|
||
}
|
||
|
||
# ── Detect platform ────────────────────────────────────────────────────────
|
||
OS="linux"
|
||
case "$(uname -s)" in
|
||
Darwin) OS="macos" ;;
|
||
MINGW*|MSYS*|CYGWIN*) OS="windows" ;;
|
||
*) if grep -qi microsoft /proc/version 2>/dev/null; then OS="wsl"; fi ;;
|
||
esac
|
||
|
||
if [ "$OS" = "windows" ]; then
|
||
echo "⚠ This source installer is for macOS / Linux (it installs system deps via brew/apt)."
|
||
echo " On Windows: download the OmniVoice Studio installer (.msi) from the Releases page,"
|
||
echo " or run this script inside WSL (Windows Subsystem for Linux)."
|
||
exit 0
|
||
fi
|
||
ARCH=$(uname -m)
|
||
|
||
echo ""
|
||
printf " ${C_TITLE}%s${C_RST}\n" "🎙 OmniVoice Studio Installer"
|
||
printf " ${C_DIM}%s${C_RST}\n" "$RULE"
|
||
echo ""
|
||
|
||
step "platform" "$OS ($ARCH)"
|
||
|
||
# Detect Rosetta on macOS
|
||
if [ "$OS" = "macos" ] && [ "$ARCH" = "x86_64" ]; then
|
||
if [ "$(sysctl -in hw.optional.arm64 2>/dev/null || echo 0)" = "1" ]; then
|
||
warn "Apple Silicon detected running under Rosetta (x86_64)."
|
||
note "Re-run from a native arm64 terminal for full MLX support."
|
||
fi
|
||
fi
|
||
|
||
# ── Resolve script directory (for local installs) ──────────────────────────
|
||
SCRIPT_DIR=""
|
||
if [ -n "${0:-}" ] && [ -f "$0" ]; then
|
||
SCRIPT_DIR=$(cd "$(dirname "$0")" 2>/dev/null && pwd) || true
|
||
fi
|
||
if [ -z "$SCRIPT_DIR" ]; then
|
||
SCRIPT_DIR=$(pwd)
|
||
fi
|
||
|
||
# If run via curl pipe, clone the repo first
|
||
if [ ! -f "$SCRIPT_DIR/pyproject.toml" ]; then
|
||
step "clone" "downloading OmniVoice Studio..."
|
||
INSTALL_DIR="$HOME/OmniVoice"
|
||
if [ -d "$INSTALL_DIR/.git" ]; then
|
||
note "Updating existing clone at $INSTALL_DIR"
|
||
(cd "$INSTALL_DIR" && git pull --ff-only 2>/dev/null || true)
|
||
else
|
||
if have git; then
|
||
git clone --depth 1 https://github.com/debpalash/VoiceStudio.git "$INSTALL_DIR"
|
||
else
|
||
die "git is required. Install git and re-run."
|
||
fi
|
||
fi
|
||
SCRIPT_DIR="$INSTALL_DIR"
|
||
fi
|
||
|
||
cd "$SCRIPT_DIR"
|
||
|
||
# ── Python version ──────────────────────────────────────────────────────────
|
||
if [ -n "$_USER_PYTHON" ]; then
|
||
PYTHON_VERSION="$_USER_PYTHON"
|
||
note "Using user-specified Python $PYTHON_VERSION"
|
||
else
|
||
PYTHON_VERSION="3.11"
|
||
fi
|
||
|
||
# ── System dependencies ────────────────────────────────────────────────────
|
||
|
||
# Helper: install system packages with the right package manager
|
||
_install_sys_pkgs() {
|
||
case "$OS" in
|
||
macos)
|
||
if ! have brew; then
|
||
note "Installing Homebrew..."
|
||
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" </dev/null
|
||
if [ -x /opt/homebrew/bin/brew ]; then
|
||
eval "$(/opt/homebrew/bin/brew shellenv)"
|
||
elif [ -x /usr/local/bin/brew ]; then
|
||
eval "$(/usr/local/bin/brew shellenv)"
|
||
fi
|
||
fi
|
||
brew install "$@" </dev/null 2>/dev/null || true
|
||
;;
|
||
linux|wsl)
|
||
if have apt-get; then
|
||
# Try without sudo first, then escalate
|
||
apt-get update -y </dev/null >/dev/null 2>&1 || true
|
||
apt-get install -y "$@" </dev/null >/dev/null 2>&1 || {
|
||
if have sudo; then
|
||
echo ""
|
||
echo " Need elevated permissions to install: $*"
|
||
sudo apt-get update -y </dev/null
|
||
sudo apt-get install -y "$@" </dev/null
|
||
else
|
||
die "Cannot install $*. Run as root or install manually."
|
||
fi
|
||
}
|
||
elif have dnf; then
|
||
sudo dnf install -y "$@" </dev/null 2>/dev/null || true
|
||
elif have yum; then
|
||
sudo yum install -y "$@" </dev/null 2>/dev/null || true
|
||
elif have pacman; then
|
||
sudo pacman -S --noconfirm "$@" 2>/dev/null || true
|
||
else
|
||
warn "No supported package manager found. Please install manually: $*"
|
||
fi
|
||
;;
|
||
esac
|
||
}
|
||
|
||
# Xcode CLT (macOS only)
|
||
if [ "$OS" = "macos" ]; then
|
||
step "xcode" "checking..."
|
||
if ! xcode-select -p >/dev/null 2>&1; then
|
||
note "Installing Xcode Command Line Tools..."
|
||
xcode-select --install </dev/null 2>/dev/null || true
|
||
until xcode-select -p >/dev/null 2>&1; do
|
||
note "Waiting for Xcode CLT install..."
|
||
sleep 10
|
||
done
|
||
fi
|
||
step "xcode" "$(xcode-select -p)"
|
||
fi
|
||
|
||
# ffmpeg (required for audio/video processing)
|
||
step "ffmpeg" "checking..."
|
||
if ! have ffmpeg; then
|
||
note "Installing ffmpeg..."
|
||
case "$OS" in
|
||
macos) _install_sys_pkgs ffmpeg ;;
|
||
linux|wsl)
|
||
if have apt-get; then
|
||
_install_sys_pkgs ffmpeg
|
||
elif have dnf; then
|
||
_install_sys_pkgs ffmpeg-free # Fedora
|
||
elif have pacman; then
|
||
_install_sys_pkgs ffmpeg
|
||
else
|
||
warn "Please install ffmpeg manually."
|
||
fi
|
||
;;
|
||
esac
|
||
fi
|
||
if have ffmpeg; then
|
||
step "ffmpeg" "$(ffmpeg -version 2>/dev/null | head -n1 | cut -d' ' -f1-3)"
|
||
else
|
||
warn "ffmpeg not found — some features will be unavailable."
|
||
fi
|
||
|
||
# ── Install uv ──────────────────────────────────────────────────────────────
|
||
step "uv" "checking..."
|
||
UV_MIN_VERSION="0.7.0"
|
||
|
||
_version_ge() {
|
||
# Returns 0 if $1 >= $2 (dotted version comparison)
|
||
_a=$1; _b=$2
|
||
while [ -n "$_a" ] || [ -n "$_b" ]; do
|
||
_a_part=${_a%%.*}; _b_part=${_b%%.*}
|
||
[ "$_a" = "$_a_part" ] && _a="" || _a=${_a#*.}
|
||
[ "$_b" = "$_b_part" ] && _b="" || _b=${_b#*.}
|
||
[ -z "$_a_part" ] && _a_part=0
|
||
[ -z "$_b_part" ] && _b_part=0
|
||
if [ "$_a_part" -gt "$_b_part" ] 2>/dev/null; then return 0; fi
|
||
if [ "$_a_part" -lt "$_b_part" ] 2>/dev/null; then return 1; fi
|
||
done
|
||
return 0
|
||
}
|
||
|
||
_uv_ok() {
|
||
have uv || return 1
|
||
_raw=$(uv --version 2>/dev/null | awk '{print $2}') || return 1
|
||
[ -n "$_raw" ] || return 1
|
||
_ver=${_raw%%[-+]*}
|
||
_version_ge "$_ver" "$UV_MIN_VERSION"
|
||
}
|
||
|
||
if ! _uv_ok; then
|
||
note "Installing uv package manager..."
|
||
_uv_tmp=$(mktemp)
|
||
download "https://astral.sh/uv/install.sh" "$_uv_tmp"
|
||
run_quiet sh "$_uv_tmp" </dev/null
|
||
rm -f "$_uv_tmp"
|
||
# Source env if uv's installer created it
|
||
if [ -f "$HOME/.local/bin/env" ]; then
|
||
. "$HOME/.local/bin/env"
|
||
fi
|
||
export PATH="$HOME/.local/bin:$PATH"
|
||
fi
|
||
step "uv" "$(uv --version 2>/dev/null)"
|
||
|
||
# ── Install bun (JS runtime for frontend) ──────────────────────────────────
|
||
step "bun" "checking..."
|
||
if ! have bun; then
|
||
note "Installing bun..."
|
||
if have curl; then
|
||
curl -fsSL https://bun.sh/install | sh </dev/null
|
||
else
|
||
die "curl is required to install bun."
|
||
fi
|
||
export PATH="$HOME/.bun/bin:$PATH"
|
||
fi
|
||
step "bun" "bun $(bun --version 2>/dev/null)"
|
||
|
||
# ── GPU detection (informational) ──────────────────────────────────────────
|
||
step "gpu" "detecting..."
|
||
GPU_INFO="CPU only"
|
||
if [ "$OS" = "macos" ] && [ "$ARCH" = "arm64" ]; then
|
||
GPU_INFO="Apple Silicon (Metal/MPS)"
|
||
elif have nvidia-smi; then
|
||
_gpu_name=$(nvidia-smi --query-gpu=name --format=csv,noheader 2>/dev/null | head -1)
|
||
_cuda_ver=$(nvidia-smi 2>/dev/null | sed -n 's/.*CUDA Version:[[:space:]]*\([0-9]*\.[0-9]*\).*/\1/p' | head -1)
|
||
if [ -n "$_gpu_name" ]; then
|
||
GPU_INFO="NVIDIA $_gpu_name (CUDA $_cuda_ver)"
|
||
fi
|
||
elif have rocminfo; then
|
||
_amd_name=$(rocminfo 2>/dev/null | awk '/Marketing Name:/{$1=$2=""; print; exit}' | sed 's/^ *//')
|
||
if [ -n "$_amd_name" ]; then
|
||
GPU_INFO="AMD $_amd_name (ROCm)"
|
||
fi
|
||
fi
|
||
step "gpu" "$GPU_INFO"
|
||
|
||
# ── Python dependencies via uv ──────────────────────────────────────────────
|
||
step "python" "syncing dependencies..."
|
||
note "This can take 5–10 min the first time (torch + torchaudio + demucs...)"
|
||
|
||
# Restricted-network support: when OMNIVOICE_REGION is set to china/russia/restricted,
|
||
# route python-build-standalone downloads through ghproxy.net. See issues #57, #60.
|
||
# Honors any existing UV_* env vars (power-user override).
|
||
case "${OMNIVOICE_REGION:-}" in
|
||
china|russia|restricted)
|
||
: "${UV_PYTHON_INSTALL_MIRROR:=https://ghproxy.net/https://github.com/astral-sh/python-build-standalone/releases/download}"
|
||
export UV_PYTHON_INSTALL_MIRROR
|
||
note "Using ghproxy.net mirror for Python download (OMNIVOICE_REGION=${OMNIVOICE_REGION})"
|
||
;;
|
||
esac
|
||
: "${UV_HTTP_TIMEOUT:=120}"
|
||
: "${UV_HTTP_RETRIES:=5}"
|
||
export UV_HTTP_TIMEOUT UV_HTTP_RETRIES
|
||
|
||
# Create venv with the target Python version if it doesn't exist.
|
||
# If the managed-python download fails (restricted network, mirror unreachable),
|
||
# fall back to the user's system Python.
|
||
if [ ! -d .venv ]; then
|
||
if ! uv venv --python "$PYTHON_VERSION"; then
|
||
warn "uv venv failed (likely Python download). Retrying with system Python..."
|
||
uv venv --python "$PYTHON_VERSION" --python-preference only-system \
|
||
|| die "uv venv failed: install Python $PYTHON_VERSION system-wide, set OMNIVOICE_REGION=china|russia|restricted to route through a mirror, or check your network."
|
||
fi
|
||
fi
|
||
|
||
# Sync all deps from pyproject.toml + uv.lock
|
||
uv sync
|
||
|
||
step "python" "OK — virtualenv at .venv/"
|
||
|
||
# ── Frontend deps + build ──────────────────────────────────────────────────
|
||
step "frontend" "installing dependencies..."
|
||
(cd frontend && bun install)
|
||
step "frontend" "OK"
|
||
|
||
step "frontend" "building bundle..."
|
||
(cd frontend && bun run build)
|
||
step "frontend" "OK — output at frontend/dist/"
|
||
|
||
# ── Log directory ──────────────────────────────────────────────────────────
|
||
case "$OS" in
|
||
macos) LOG_DIR="$HOME/Library/Application Support/OmniVoice" ;;
|
||
*) LOG_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/OmniVoice" ;;
|
||
esac
|
||
mkdir -p "$LOG_DIR"
|
||
|
||
# ── Done ────────────────────────────────────────────────────────────────────
|
||
echo ""
|
||
printf " ${C_TITLE}%s${C_RST}\n" "✓ Install complete!"
|
||
printf " ${C_DIM}%s${C_RST}\n" "$RULE"
|
||
echo ""
|
||
step "next" "Run ./run.sh to start OmniVoice Studio"
|
||
echo ""
|
||
note "First launch downloads ~5 GB of ML model weights (OmniVoice TTS + Whisper)."
|
||
note "After that, launches are instant."
|
||
echo ""
|
||
note "GPU: $GPU_INFO"
|
||
note "Logs: $LOG_DIR/omnivoice.log"
|
||
echo ""
|