Files
shawn c38a7b0fd8 Add main entry point + systemd services + integration tests
New files:
  main.py                   - PedalApp: boots all subsystems in order,
                              wires MIDI/footswitch callbacks, graceful
                              teardown reverses boot order
  src/system/config.py      - YAML config loader with deep-merge
                              (separated to avoid hardware deps)
  src/system/services.py    - systemd unit generator for pedal.service
                              + multi-fx-pedal.target
  scripts/install_service.sh - copies project, creates venv, installs
                              + enables service units
  tests/test_integration.py - 41 tests: boot, routing, display sync,
                              teardown, systemd content, CLI, edge cases

Modified:
  tests/conftest.py         - add project root to sys.path
2026-06-07 23:39:50 -04:00

166 lines
6.3 KiB
Bash
Executable File

#!/usr/bin/env bash
# ────────────────────────────────────────────────────────────────────
# Pi Multi-FX Pedal — systemd service installer
#
# Installs pi-multifx-pedal.service + multi-fx-pedal.target and
# enables them for auto-start on boot.
#
# Usage:
# sudo ./install_service.sh [install_dir]
#
# install_dir Path to the pedal installation (default: /opt/pi-multifx-pedal)
# ────────────────────────────────────────────────────────────────────
set -euo pipefail
INSTALL_DIR="${1:-/opt/pi-multifx-pedal}"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
# ── Colour helpers ─────────────────────────────────────────────────
info() { printf "\e[34m[INFO] %s\e[0m\n" "$*"; }
ok() { printf "\e[32m[ OK ] %s\e[0m\n" "$*"; }
warn() { printf "\e[33m[WARN] %s\e[0m\n" "$*"; }
err() { printf "\e[31m[FAIL] %s\e[0m\n" "$*"; }
# ── Root check ─────────────────────────────────────────────────────
if [[ $EUID -ne 0 ]]; then
err "This script must be run as root (sudo ./install_service.sh)"
exit 1
fi
# ── Architecture check (must be RPi) ───────────────────────────────
ARCH="$(uname -m)"
if [[ "$ARCH" != "aarch64" && "$ARCH" != "armv7l" ]]; then
warn "Not a Raspberry Pi (detected: $ARCH) — service files will be written"
warn "but will not be enabled (no systemctl available in containers)."
NO_SYSTEMCTL=true
fi
info "========== Pi Multi-FX Pedal — Service Installer =========="
info "Install dir: $INSTALL_DIR"
info "Project dir: $PROJECT_DIR"
echo ""
# ── Step 1: Create install directory ───────────────────────────────
info "Step 1: Creating install directory..."
mkdir -p "$INSTALL_DIR"
ok "Install directory ready: $INSTALL_DIR"
# ── Step 2: Copy project files ─────────────────────────────────────
info "Step 2: Copying project files..."
# Copy everything except .venv, __pycache__, .git, etc.
rsync -a --delete \
--exclude='.git' \
--exclude='__pycache__' \
--exclude='*.pyc' \
--exclude='.venv' \
--exclude='venv' \
--exclude='.scone' \
--exclude='systemd/' \
--exclude='*.so' \
--exclude='*.nam' \
--exclude='*.wav' \
--exclude='*.aiff' \
"$PROJECT_DIR/" "$INSTALL_DIR/"
ok "Project files copied to $INSTALL_DIR"
# ── Step 3: Create Python virtual environment ──────────────────────
info "Step 3: Setting up Python virtual environment..."
if [[ ! -d "$INSTALL_DIR/.venv" ]]; then
python3 -m venv "$INSTALL_DIR/.venv"
ok "Virtual environment created at $INSTALL_DIR/.venv"
else
info "Virtual environment already exists"
fi
# Install/update dependencies
"$INSTALL_DIR/.venv/bin/pip" install --quiet --upgrade pip
"$INSTALL_DIR/.venv/bin/pip" install --quiet -r "$INSTALL_DIR/requirements.txt" 2>&1 | tail -1
ok "Python dependencies installed"
# ── Step 4: Generate service files from Python module ──────────────
info "Step 4: Generating systemd service units..."
PYTHON_BIN="$INSTALL_DIR/.venv/bin/python3"
SERVICES_MODULE="src.system.services"
# Generate pi-multifx-pedal.service
$PYTHON_BIN -c "
import sys
sys.path.insert(0, '$INSTALL_DIR')
from src.system.services import pedal_service_content
print(pedal_service_content(install_dir='$INSTALL_DIR'))
" > /etc/systemd/system/pi-multifx-pedal.service
ok "Generated /etc/systemd/system/pi-multifx-pedal.service"
# Generate multi-fx-pedal.target
$PYTHON_BIN -c "
import sys
sys.path.insert(0, '$INSTALL_DIR')
from src.system.services import pedal_target_content
print(pedal_target_content())
" > /etc/systemd/system/multi-fx-pedal.target
ok "Generated /etc/systemd/system/multi-fx-pedal.target"
# ── Step 5: Reload and enable ──────────────────────────────────────
info "Step 5: Reloading systemd and enabling services..."
if [[ "${NO_SYSTEMCTL:-false}" != true ]]; then
systemctl daemon-reload
systemctl enable pi-multifx-pedal.service
systemctl enable multi-fx-pedal.target
ok "Services enabled: pi-multifx-pedal.service + multi-fx-pedal.target"
else
info "Skipping systemctl enable (non-RPi environment)"
fi
# ── Step 6: Add 'pi' user to audio group ───────────────────────────
if groups pi | grep -q '\baudio\b' 2>/dev/null; then
ok "User 'pi' already in audio group"
else
usermod -a -G audio pi 2>/dev/null || true
info "Added 'pi' to audio group (will take effect on next login)"
fi
# ── Optional: Set up log rotation ──────────────────────────────────
LOGROTATE_FILE="/etc/logrotate.d/pi-multifx-pedal"
if [[ ! -f "$LOGROTATE_FILE" ]]; then
cat > "$LOGROTATE_FILE" <<'LOGROTATE'
/var/log/pi-multifx-pedal/*.log {
daily
rotate 7
compress
delaycompress
missingok
notifempty
copytruncate
}
LOGROTATE
ok "Log rotation configured at $LOGROTATE_FILE"
fi
# ── Summary ────────────────────────────────────────────────────────
echo ""
info "========== Installation Complete =========="
echo ""
echo " Service: pi-multifx-pedal.service"
echo " Target: multi-fx-pedal.target"
echo " Install: $INSTALL_DIR"
echo " Config: ~/.pedal/config.yaml"
echo " Presets: ~/.pedal/presets/"
echo ""
info "Start now:"
echo " sudo systemctl start multi-fx-pedal.target"
echo ""
info "Check status:"
echo " sudo systemctl status pi-multifx-pedal.service"
echo ""
info "Tail logs:"
echo " journalctl -fu pi-multifx-pedal.service"
echo ""
info "Stop the pedal:"
echo " sudo systemctl stop multi-fx-pedal.target"