minerva/scripts/setup_voice_assistant.sh
pyr0ball 173f7f37d4 feat: import mycroft-precise work as Minerva foundation
Ports prior voice assistant research and prototypes from devl/Devops
into the Minerva repo. Includes:

- docs/: architecture, wake word guides, ESP32-S3 spec, hardware buying guide
- scripts/: voice_server.py, voice_server_enhanced.py, setup scripts
- hardware/maixduino/: edge device scripts with WiFi credentials scrubbed
  (replaced hardcoded password with secrets.py pattern)
- config/.env.example: server config template
- .gitignore: excludes .env, secrets.py, model blobs, ELF firmware
- CLAUDE.md: Minerva product context and connection to cf-voice roadmap
2026-04-06 22:21:12 -07:00

429 lines
13 KiB
Bash
Executable file

#!/usr/bin/env bash
#
# Path: setup_voice_assistant.sh
#
# Purpose and usage:
# Sets up the voice assistant server environment on Heimdall
# - Creates conda environment
# - Installs dependencies (Whisper, Flask, Piper TTS)
# - Downloads and configures TTS models
# - Sets up systemd service (optional)
# - Configures environment variables
#
# Requirements:
# - conda/miniconda installed
# - Internet connection for downloads
# - Sudo access (for systemd service setup)
#
# Usage:
# ./setup_voice_assistant.sh [--no-service] [--env-name NAME]
#
# Author: PRbL Library
# Created: $(date +"%Y-%m-%d")
# ----- PRbL Color and output functions -----
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
BLUE='\033[0;34m'
PURPLE='\033[0;35m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color
print_status() {
local level="$1"
shift
case "$level" in
"info") echo -e "${BLUE}[INFO]${NC} $*" >&2 ;;
"success") echo -e "${GREEN}[SUCCESS]${NC} $*" >&2 ;;
"warning") echo -e "${YELLOW}[WARNING]${NC} $*" >&2 ;;
"error") echo -e "${RED}[ERROR]${NC} $*" >&2 ;;
"debug") [[ "$VERBOSE" == "true" ]] && echo -e "${PURPLE}[DEBUG]${NC} $*" >&2 ;;
*) echo -e "$*" >&2 ;;
esac
}
# ----- Configuration -----
CONDA_ENV_NAME="voice-assistant"
PROJECT_DIR="$HOME/voice-assistant"
INSTALL_SYSTEMD=true
VERBOSE=false
# ----- Dependency checking -----
command_exists() {
command -v "$1" &> /dev/null
}
check_conda() {
if ! command_exists conda; then
print_status error "conda not found. Please install miniconda first."
print_status info "Install with: wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh"
print_status info " bash Miniconda3-latest-Linux-x86_64.sh"
return 1
fi
return 0
}
# ----- Parse arguments -----
parse_args() {
while [[ $# -gt 0 ]]; do
case "$1" in
--no-service)
INSTALL_SYSTEMD=false
shift
;;
--env-name)
CONDA_ENV_NAME="$2"
shift 2
;;
-v|--verbose)
VERBOSE=true
shift
;;
-h|--help)
cat << EOF
Usage: $(basename "$0") [OPTIONS]
Options:
--no-service Don't install systemd service
--env-name NAME Custom conda environment name (default: voice-assistant)
-v, --verbose Enable verbose output
-h, --help Show this help message
EOF
exit 0
;;
*)
print_status error "Unknown option: $1"
exit 1
;;
esac
done
}
# ----- Setup functions -----
create_project_directory() {
print_status info "Creating project directory: $PROJECT_DIR"
if [[ ! -d "$PROJECT_DIR" ]]; then
mkdir -p "$PROJECT_DIR" || {
print_status error "Failed to create project directory"
return 1
}
fi
# Create subdirectories
mkdir -p "$PROJECT_DIR"/{logs,models,config}
print_status success "Project directory created"
return 0
}
create_conda_environment() {
print_status info "Creating conda environment: $CONDA_ENV_NAME"
# Check if environment already exists
if conda env list | grep -q "^${CONDA_ENV_NAME}\s"; then
print_status warning "Environment $CONDA_ENV_NAME already exists"
read -p "Remove and recreate? (y/N): " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
print_status info "Removing existing environment..."
conda env remove -n "$CONDA_ENV_NAME" -y
else
print_status info "Using existing environment"
return 0
fi
fi
# Create new environment
print_status info "Creating Python 3.10 environment..."
conda create -n "$CONDA_ENV_NAME" python=3.10 -y || {
print_status error "Failed to create conda environment"
return 1
}
print_status success "Conda environment created"
return 0
}
install_python_dependencies() {
print_status info "Installing Python dependencies..."
# Activate conda environment
eval "$(conda shell.bash hook)"
conda activate "$CONDA_ENV_NAME" || {
print_status error "Failed to activate conda environment"
return 1
}
# Install base dependencies
print_status info "Installing base packages..."
pip install --upgrade pip --break-system-packages || true
# Install Whisper (OpenAI)
print_status info "Installing OpenAI Whisper..."
pip install -U openai-whisper --break-system-packages || {
print_status error "Failed to install Whisper"
return 1
}
# Install Flask
print_status info "Installing Flask..."
pip install flask --break-system-packages || {
print_status error "Failed to install Flask"
return 1
}
# Install requests
print_status info "Installing requests..."
pip install requests --break-system-packages || {
print_status error "Failed to install requests"
return 1
}
# Install python-dotenv
print_status info "Installing python-dotenv..."
pip install python-dotenv --break-system-packages || {
print_status warning "Failed to install python-dotenv (optional)"
}
# Install Piper TTS
print_status info "Installing Piper TTS..."
# Note: Piper TTS installation method varies, adjust as needed
# For now, we'll install the Python package if available
pip install piper-tts --break-system-packages || {
print_status warning "Piper TTS pip package not found"
print_status info "You may need to install Piper manually from: https://github.com/rhasspy/piper"
}
# Install PyAudio for audio handling
print_status info "Installing PyAudio dependencies..."
if command_exists apt-get; then
sudo apt-get install -y portaudio19-dev python3-pyaudio || {
print_status warning "Failed to install portaudio dev packages"
}
fi
pip install pyaudio --break-system-packages || {
print_status warning "Failed to install PyAudio (may need manual installation)"
}
print_status success "Python dependencies installed"
return 0
}
download_piper_models() {
print_status info "Downloading Piper TTS models..."
local models_dir="$PROJECT_DIR/models/piper"
mkdir -p "$models_dir"
# Download a default voice model
# Example: en_US-lessac-medium
local model_url="https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_US/lessac/medium/en_US-lessac-medium.onnx"
local config_url="https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_US/lessac/medium/en_US-lessac-medium.onnx.json"
if [[ ! -f "$models_dir/en_US-lessac-medium.onnx" ]]; then
print_status info "Downloading voice model..."
wget -q --show-progress -O "$models_dir/en_US-lessac-medium.onnx" "$model_url" || {
print_status warning "Failed to download Piper model (manual download may be needed)"
}
wget -q --show-progress -O "$models_dir/en_US-lessac-medium.onnx.json" "$config_url" || {
print_status warning "Failed to download Piper config"
}
else
print_status info "Piper model already downloaded"
fi
print_status success "Piper models ready"
return 0
}
create_config_file() {
print_status info "Creating configuration file..."
local config_file="$PROJECT_DIR/config/.env"
if [[ -f "$config_file" ]]; then
print_status warning "Config file already exists: $config_file"
return 0
fi
cat > "$config_file" << 'EOF'
# Voice Assistant Configuration
# Path: ~/voice-assistant/config/.env
# Home Assistant Configuration
HA_URL=http://homeassistant.local:8123
HA_TOKEN=your_long_lived_access_token_here
# Server Configuration
SERVER_HOST=0.0.0.0
SERVER_PORT=5000
# Whisper Configuration
WHISPER_MODEL=medium
# Piper TTS Configuration
PIPER_MODEL=/path/to/piper/model.onnx
PIPER_CONFIG=/path/to/piper/model.onnx.json
# Logging
LOG_LEVEL=INFO
LOG_FILE=/home/$USER/voice-assistant/logs/voice_assistant.log
EOF
# Update paths in config
sed -i "s|/path/to/piper/model.onnx|$PROJECT_DIR/models/piper/en_US-lessac-medium.onnx|g" "$config_file"
sed -i "s|/path/to/piper/model.onnx.json|$PROJECT_DIR/models/piper/en_US-lessac-medium.onnx.json|g" "$config_file"
sed -i "s|/home/\$USER|$HOME|g" "$config_file"
chmod 600 "$config_file"
print_status success "Config file created: $config_file"
print_status warning "Please edit $config_file and add your Home Assistant token"
return 0
}
create_systemd_service() {
if [[ "$INSTALL_SYSTEMD" != "true" ]]; then
print_status info "Skipping systemd service installation"
return 0
fi
print_status info "Creating systemd service..."
local service_file="/etc/systemd/system/voice-assistant.service"
# Create service file
sudo tee "$service_file" > /dev/null << EOF
[Unit]
Description=Voice Assistant Server
After=network.target
[Service]
Type=simple
User=$USER
WorkingDirectory=$PROJECT_DIR
Environment="PATH=$HOME/miniconda3/envs/$CONDA_ENV_NAME/bin:/usr/local/bin:/usr/bin:/bin"
EnvironmentFile=$PROJECT_DIR/config/.env
ExecStart=$HOME/miniconda3/envs/$CONDA_ENV_NAME/bin/python $PROJECT_DIR/voice_server.py
Restart=on-failure
RestartSec=10
StandardOutput=append:$PROJECT_DIR/logs/voice_assistant.log
StandardError=append:$PROJECT_DIR/logs/voice_assistant_error.log
[Install]
WantedBy=multi-user.target
EOF
# Reload systemd
sudo systemctl daemon-reload
print_status success "Systemd service created"
print_status info "To enable and start the service:"
print_status info " sudo systemctl enable voice-assistant"
print_status info " sudo systemctl start voice-assistant"
return 0
}
create_test_script() {
print_status info "Creating test script..."
local test_script="$PROJECT_DIR/test_server.sh"
cat > "$test_script" << 'EOF'
#!/bin/bash
# Test script for voice assistant server
# Activate conda environment
eval "$(conda shell.bash hook)"
conda activate voice-assistant
# Load environment variables
if [[ -f ~/voice-assistant/config/.env ]]; then
export $(grep -v '^#' ~/voice-assistant/config/.env | xargs)
fi
# Run server
cd ~/voice-assistant
python voice_server.py --verbose
EOF
chmod +x "$test_script"
print_status success "Test script created: $test_script"
return 0
}
install_voice_server_script() {
print_status info "Installing voice_server.py..."
# Check if voice_server.py exists in outputs
if [[ -f "$HOME/voice_server.py" ]]; then
cp "$HOME/voice_server.py" "$PROJECT_DIR/voice_server.py"
print_status success "voice_server.py installed"
elif [[ -f "./voice_server.py" ]]; then
cp "./voice_server.py" "$PROJECT_DIR/voice_server.py"
print_status success "voice_server.py installed"
else
print_status warning "voice_server.py not found in current directory"
print_status info "Please copy voice_server.py to $PROJECT_DIR manually"
fi
return 0
}
# ----- Main -----
main() {
print_status info "Starting voice assistant setup..."
# Parse arguments
parse_args "$@"
# Check dependencies
check_conda || exit 1
# Setup steps
create_project_directory || exit 1
create_conda_environment || exit 1
install_python_dependencies || exit 1
download_piper_models || exit 1
create_config_file || exit 1
install_voice_server_script || exit 1
create_test_script || exit 1
if [[ "$INSTALL_SYSTEMD" == "true" ]]; then
create_systemd_service || exit 1
fi
# Final instructions
print_status success "Setup complete!"
echo
print_status info "Next steps:"
print_status info "1. Edit config file: vim $PROJECT_DIR/config/.env"
print_status info "2. Add your Home Assistant long-lived access token"
print_status info "3. Test the server: $PROJECT_DIR/test_server.sh"
print_status info "4. Configure your Maix Duino device"
if [[ "$INSTALL_SYSTEMD" == "true" ]]; then
echo
print_status info "To run as a service:"
print_status info " sudo systemctl enable voice-assistant"
print_status info " sudo systemctl start voice-assistant"
print_status info " sudo systemctl status voice-assistant"
fi
echo
print_status info "Project directory: $PROJECT_DIR"
print_status info "Conda environment: $CONDA_ENV_NAME"
print_status info "Activate with: conda activate $CONDA_ENV_NAME"
}
# Run main
main "$@"