Files
voculinux/install.d/interactive.sh
T
be690f0bd8 fix(installer): add upgrade mode that keeps the existing configuration (#1026)
* fix(installer): add upgrade mode that keeps the existing configuration

Re-running install.sh on an existing install replayed first-time setup:
the mode menu offered only interactive or automatic, automatic mode
forced whisper.cpp regardless of the configured engine, and nothing said
config.json would be kept, even though docs/UPDATE.md promised the
installer updates in place and preserves configuration.

Detect an existing install early (venv activate + config.json) and offer
an upgrade path:

- The curl|bash mode menu gains 'Upgrade (keep current settings)' as the
  default when an install is detected; the guided setup offers it first
  too.
- A --upgrade flag runs the same path non-interactively for scripts, and
  falls back to a normal automatic install when nothing is installed.
- Upgrade mode reads speech_recognition.engine from the existing
  config.json instead of forcing whisper.cpp, reuses the venv, updates
  that engine's dependencies, and rebuilds pywhispercpp only when the
  installed version drifts from the pinned release or GPU support is
  missing.
- config.json is never modified: every engine path that may rewrite it
  (parakeet, faster_whisper, the whisper.cpp -> vosk fallback) goes
  through a wrapper that is a logged no-op in upgrade mode, and
  verify_configured_engine reports an unimportable configured engine
  instead of repairing the file.
- The installer prints 'Existing configuration found at
  ~/.config/vocalinux/config.json - it will be kept.' when it detects
  one.

Update docs/UPDATE.md with the upgrade behavior and add
tests/test_installer_upgrade.py covering detection, the mode menu
default, engine resolution from config, config immutability, the guided
offer, --upgrade parsing, and an end-to-end pty run of the piped
installer.

Fixes #1009

* test(installer): isolate the upgrade pty test from host changes

Skip system-dependency and model steps in the end-to-end installer run
so the only host-mutating calls are gated out entirely; the timed
interrupt is no longer the only thing between the marker and sudo.

---------

Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com>
2026-10-26 20:55:39 -07:00

338 lines
18 KiB
Bash

#!/bin/bash
# Sourced by install.sh after it has resolved the local or tagged repository.
# Keep this file declarative: function definitions only, with no top-level actions.
# Print section header for interactive mode
clear_screen() {
if [ -t 1 ] && command -v clear >/dev/null 2>&1 && [ -n "${TERM:-}" ]; then
clear >/dev/null 2>&1 || true
fi
}
print_header() {
local title="$1"
echo ""
echo "============================================================"
echo " $title"
echo "============================================================"
}
# Function to run interactive guided installation
run_interactive_install() {
clear_screen
cat << "EOF"
Interactive Installation Guide
===============================
EOF
echo "Welcome! This guided installation will help you set up Vocalinux"
echo "with the best options for your system."
echo ""
echo "Local engines (whisper.cpp, Whisper, Faster Whisper, VOSK) process audio on-device after you download a model."
echo "Optional Remote API is off unless you choose it; then audio goes only to the server you configure."
echo ""
# An existing install is an upgrade by default: keep the saved configuration
# and update in place instead of redoing first-time setup. Choosing the full
# guided setup continues below and can re-point the engine deliberately.
if [[ "${EXISTING_INSTALL:-no}" == "yes" ]]; then
echo "An existing Vocalinux installation was detected:"
echo " Configuration: $CONFIG_DIR/config.json (kept unchanged by an upgrade)"
echo " Virtual env: $VENV_DIR (reused by an upgrade)"
echo ""
echo " 1. Upgrade (keep current settings) - update Vocalinux only"
echo " 2. Full guided setup - choose engine, backend, and model options"
echo ""
read_prompt -p "Choose [1-2] (default: 1): " UPGRADE_CHOICE
UPGRADE_CHOICE=${UPGRADE_CHOICE:-1}
if [[ "$UPGRADE_CHOICE" != "2" ]]; then
UPGRADE_MODE="yes"
INTERACTIVE_MODE="no"
NON_INTERACTIVE="yes"
echo ""
return 0
fi
echo ""
fi
# Step 1: Detect and display system info
print_header "Step 1: Your System"
echo "Detected: $DISTRO_NAME $DISTRO_VERSION"
# Get hardware recommendation
local RECOMMENDATION=$(get_engine_recommendation)
local RECOMMENDED_ENGINE=$(echo "$RECOMMENDATION" | cut -d':' -f1)
local RECOMMENDED_ICON=$(echo "$RECOMMENDATION" | cut -d':' -f2)
local RECOMMENDED_REASON=$(echo "$RECOMMENDATION" | cut -d':' -f3-)
echo "Hardware: $RECOMMENDED_REASON"
echo ""
# Step 2: Choose speech recognition engine
print_header "Step 2: Choose Speech Recognition Engine"
echo ""
echo " ┌─────────────────────────────────────────────────────────────┐"
echo " │ 1. WHISPER.CPP * RECOMMENDED │"
echo " │ • Local default engine; CUDA, Vulkan, or CPU │"
echo " │ • Supports NVIDIA (CUDA), AMD, Intel (Vulkan) │"
echo " │ • CPU-only mode available for older systems │"
echo " │ • Models: tiny (39MB) to large (1.5GB) │"
echo " │ • ~33 languages plus Auto-detect │"
echo " └─────────────────────────────────────────────────────────────┘"
echo ""
echo " ┌─────────────────────────────────────────────────────────────┐"
echo " │ 2. WHISPER (OpenAI) │"
echo " │ • PyTorch-based, high accuracy │"
echo " │ • Only supports NVIDIA GPUs (CUDA) │"
echo " │ • Larger download (~2GB with CUDA) │"
echo " │ • Good for development/research │"
echo " └─────────────────────────────────────────────────────────────┘"
echo ""
echo " ┌─────────────────────────────────────────────────────────────┐"
echo " │ 3. VOSK │"
echo " │ • Lightweight and fast │"
echo " │ • Works on older/low-RAM systems │"
echo " │ • ~40MB download │"
echo " │ • Good for basic dictation needs │"
echo " └─────────────────────────────────────────────────────────────┘"
echo ""
echo " ┌─────────────────────────────────────────────────────────────┐"
echo " │ 4. FASTER-WHISPER │"
echo " │ • Optional CTranslate2 Whisper backend │"
echo " │ • Fast on CPU with INT8 quantization │"
echo " │ • Checksum-verified Hugging Face models │"
echo " └─────────────────────────────────────────────────────────────┘"
echo ""
echo " ┌─────────────────────────────────────────────────────────────┐"
echo " │ 5. REMOTE API (ADVANCED) │"
echo " │ • Offload processing to a GPU server on your network │"
echo " │ • Ideal for laptops without GPU │"
echo " │ • Supports whisper.cpp server & OpenAI-compatible APIs │"
echo " │ • Minimal local resources needed │"
echo " │ • Requires a remote server to be running │"
echo " └─────────────────────────────────────────────────────────────┘"
echo ""
# Show recommendation
case "$RECOMMENDED_ENGINE" in
whisper_cpp)
echo " → Recommendation: whisper.cpp (recommended for your hardware)"
DEFAULT_CHOICE="1"
;;
vosk)
echo " → Recommendation: VOSK (lightweight option for your system)"
DEFAULT_CHOICE="3"
;;
*)
echo " → Recommendation: whisper.cpp (recommended default)"
DEFAULT_CHOICE="1"
;;
esac
echo ""
read_prompt -p "Choose engine [1-5] (default: $DEFAULT_CHOICE): " ENGINE_CHOICE
ENGINE_CHOICE=${ENGINE_CHOICE:-$DEFAULT_CHOICE}
case "$ENGINE_CHOICE" in
1)
SELECTED_ENGINE="whisper_cpp"
ENGINE_DISPLAY="Whisper.cpp (Recommended)"
;;
2)
SELECTED_ENGINE="whisper"
ENGINE_DISPLAY="Whisper (OpenAI)"
;;
3)
SELECTED_ENGINE="vosk"
ENGINE_DISPLAY="VOSK (Lightweight)"
;;
4)
SELECTED_ENGINE="faster_whisper"
ENGINE_DISPLAY="Faster-Whisper"
;;
5)
SELECTED_ENGINE="remote_api"
ENGINE_DISPLAY="Remote API"
;;
*)
SELECTED_ENGINE="whisper_cpp"
ENGINE_DISPLAY="Whisper.cpp (Recommended)"
;;
esac
# Step 3: Whisper.cpp backend selection (if whisper.cpp chosen)
if [[ "$SELECTED_ENGINE" == "whisper_cpp" ]]; then
print_header "Step 3: Choose Whisper.cpp Backend"
echo ""
# Detect available backends
local BACKEND_INFO=$(detect_whispercpp_backends)
local RECOMMENDED_BACKEND=$(echo "$BACKEND_INFO" | cut -d':' -f1)
local RECOMMENDED_REASON=$(echo "$BACKEND_INFO" | cut -d':' -f2)
local CAN_BUILD_GPU=$(echo "$BACKEND_INFO" | cut -d':' -f3)
local HAS_VULKAN=$(echo "$BACKEND_INFO" | cut -d':' -f4)
local HAS_NVIDIA=$(echo "$BACKEND_INFO" | cut -d':' -f5)
local HAS_VULKAN_DEV=$(echo "$BACKEND_INFO" | cut -d':' -f6)
local HAS_CUDA_DEV=$(echo "$BACKEND_INFO" | cut -d':' -f7)
local VULKAN_COMPAT=$(echo "$BACKEND_INFO" | cut -d':' -f8)
local VULKAN_COMPAT_REASON=$(echo "$BACKEND_INFO" | cut -d':' -f9)
# Show warning for incompatible GPUs
if [[ "$VULKAN_COMPAT" == "incompatible" ]]; then
echo ""
print_warning "═══════════════════════════════════════════════════════════════"
print_warning " ⚠️ INCOMPATIBLE GPU DETECTED"
print_warning "═══════════════════════════════════════════════════════════════"
print_warning ""
print_warning " Your GPU: $VULKAN_COMPAT_REASON"
print_warning ""
print_warning " This Intel GPU lacks VK_KHR_16bit_storage support, which is"
print_warning " required for whisper.cpp Vulkan acceleration."
print_warning ""
print_warning " The CPU backend will be used instead, which is still fast!"
print_warning ""
print_warning "═══════════════════════════════════════════════════════════════"
echo ""
fi
echo "Whisper.cpp can use different backends for speech recognition:"
echo ""
if [[ "$CAN_BUILD_GPU" == "true" ]]; then
echo " ┌─────────────────────────────────────────────────────────────┐"
echo " │ 1. GPU (Vulkan/CUDA) * RECOMMENDED │"
echo " │ • Fastest performance with GPU acceleration │"
printf " │ • %-*s│\n" 54 "$RECOMMENDED_REASON"
echo " │ • Requires building from source (takes ~2-5 min) │"
echo " └─────────────────────────────────────────────────────────────┘"
echo ""
echo " ┌─────────────────────────────────────────────────────────────┐"
echo " │ 2. CPU (Pre-built) │"
echo " │ • Works on all systems │"
echo " │ • Faster installation (no compilation) │"
echo " │ • Good performance on modern CPUs │"
echo " └─────────────────────────────────────────────────────────────┘"
echo ""
echo " → Recommendation: GPU backend for best performance"
local DEFAULT_BACKEND="1"
else
echo " ┌─────────────────────────────────────────────────────────────┐"
echo " │ 1. GPU (Vulkan/CUDA) │"
echo " │ • ⚠️ GPU libraries not detected │"
echo " │ • Requires: libvulkan-dev, glslc/glslang-tools (Vulkan) │"
echo " │ or: CUDA toolkit (NVIDIA) │"
echo " └─────────────────────────────────────────────────────────────┘"
echo ""
echo " ┌─────────────────────────────────────────────────────────────┐"
echo " │ 2. CPU (Pre-built) * RECOMMENDED │"
echo " │ • Works on all systems │"
echo " │ • Fast installation (no compilation) │"
echo " │ • Good performance on modern CPUs │"
echo " └─────────────────────────────────────────────────────────────┘"
echo ""
if [[ "$HAS_VULKAN" == "yes" && "$HAS_VULKAN_DEV" != "true" ]]; then
echo " 💡 Tip: Install 'libvulkan-dev' and a shader compiler for GPU support:"
echo " sudo apt install libvulkan-dev glslc 2>/dev/null || sudo apt install libvulkan-dev glslang-tools"
echo ""
elif [[ "$HAS_NVIDIA" == "yes" && "$HAS_CUDA_DEV" != "true" ]]; then
echo " 💡 Tip: Install CUDA toolkit for NVIDIA GPU support:"
echo " https://developer.nvidia.com/cuda-downloads"
echo ""
fi
echo " → Recommendation: CPU backend (GPU libraries not detected)"
local DEFAULT_BACKEND="2"
fi
read_prompt -p "Choose backend [1-2] (default: $DEFAULT_BACKEND): " BACKEND_CHOICE
BACKEND_CHOICE=${BACKEND_CHOICE:-$DEFAULT_BACKEND}
if [[ "$BACKEND_CHOICE" == "1" ]]; then
WHISPERCPP_BACKEND="gpu"
BACKEND_DISPLAY="GPU (Vulkan/CUDA)"
else
WHISPERCPP_BACKEND="cpu"
BACKEND_DISPLAY="CPU (Pre-built)"
fi
echo ""
fi
# Step 3 for Remote API: Configure server URL
if [[ "$SELECTED_ENGINE" == "remote_api" ]]; then
print_header "Step 3: Configure Remote Server"
echo ""
print_info "You need a speech recognition server running on your local network."
echo ""
echo " Supported servers:"
echo " • whisper.cpp server: ./server -m model.bin --host 0.0.0.0 --port 8080"
echo " • LocalAI: docker run -p 8080:8080 localai/localai"
echo " • Faster Whisper: faster-whisper-server --host 0.0.0.0 --port 8080"
echo " • Any OpenAI-compatible speech API"
echo ""
read_prompt -p "Enter remote server URL (or leave blank to set later): " REMOTE_API_URL_INPUT
if [ -n "$REMOTE_API_URL_INPUT" ]; then
REMOTE_API_URL="$REMOTE_API_URL_INPUT"
REMOTE_DISPLAY="$REMOTE_API_URL"
else
REMOTE_API_URL=""
REMOTE_DISPLAY="(configure later in Settings)"
fi
echo ""
fi
# Step 4: Model download preference (skip for remote_api)
if [[ "$SELECTED_ENGINE" != "remote_api" ]]; then
print_header "Step 4: Model Download"
echo ""
echo "Speech recognition models can be downloaded now or later."
echo ""
echo " 1. Download now (recommended)"
echo " • Faster first run - ready to use immediately"
echo " • Offline capable right after install"
echo ""
echo " 2. Download later"
echo " • Smaller initial install"
echo " • Models download automatically on first use"
echo ""
read_prompt -p "Download models now? [1-2] (default: 1): " MODELS_CHOICE
MODELS_CHOICE=${MODELS_CHOICE:-1}
if [[ "$MODELS_CHOICE" == "2" ]]; then
SKIP_MODELS="yes"
MODELS_DISPLAY="Download on first use"
else
MODELS_DISPLAY="Download now (recommended)"
fi
else
# Remote API: No need to download model
SKIP_MODELS="yes"
MODELS_DISPLAY="Not needed (remote processing)"
fi
# Summary
print_header "Installation Summary"
echo ""
echo " Speech Engine: $ENGINE_DISPLAY"
if [[ "$SELECTED_ENGINE" == "whisper_cpp" ]]; then
echo " Backend: ${BACKEND_DISPLAY:-CPU (Pre-built)}"
if [[ "${WHISPERCPP_BACKEND}" == "gpu" ]]; then
echo " Note: GPU build will compile from source (2-5 minutes)"
fi
fi
if [[ "$SELECTED_ENGINE" == "remote_api" ]]; then
echo " Remote Server: $REMOTE_DISPLAY"
fi
echo " Models: $MODELS_DISPLAY"
echo " Install Location: ${INSTALL_DIR:-\$HOME/.local/share/vocalinux}"
echo ""
read_prompt -p "Press Enter to continue with installation, or Ctrl+C to cancel..."
echo ""
}