#!/usr/bin/env bash # One-shot Raspberry Pi installer for javis-slave (headless or with touch display). # # sudo ./setup-pi.sh # headless satellite (mic + speaker) # sudo ./setup-pi.sh --gui # Pi with official touch display # sudo ./setup-pi.sh --with-vad # also install silero-vad (+PyTorch, ~200 MB) # # Idempotent: re-running upgrades the venv and rewrites the service unit. set -euo pipefail INSTALL_DIR="${INSTALL_DIR:-/opt/javis-slave}" SERVICE_USER="${SERVICE_USER:-${SUDO_USER:-pi}}" WITH_GUI=0 WITH_VAD=0 for arg in "$@"; do case "$arg" in --gui) WITH_GUI=1 ;; --with-vad) WITH_VAD=1 ;; -h|--help) sed -n '2,9p' "$0"; exit 0 ;; *) echo "unknown option: $arg" >&2; exit 2 ;; esac done if [[ $EUID -ne 0 ]]; then echo "Run with sudo (installs apt packages and a systemd unit)." >&2 exit 1 fi SRC_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" echo "==> apt packages" apt-get update -qq # portaudio: sounddevice; libsndfile: soundfile; alsa-utils: amixer/aplay/arecord; # libatomic1 + libopenblas: onnxruntime/numpy wheels on aarch64. apt-get install -y -qq python3 python3-venv python3-dev libportaudio2 libsndfile1 \ alsa-utils libatomic1 libopenblas0 rsync if [[ $WITH_GUI -eq 1 ]]; then # Qt platform plugin deps for PySide6 wheels on Raspberry Pi OS + colour emoji # glyphs used by the orb/buttons (🎤 🔊 ⚙). apt-get install -y -qq libxcb-cursor0 libxkbcommon-x11-0 libegl1 libgl1 libglib2.0-0 \ libfontconfig1 libdbus-1-3 fonts-noto-color-emoji fi echo "==> copying to ${INSTALL_DIR}" mkdir -p "$INSTALL_DIR" if [[ "$SRC_DIR" != "$INSTALL_DIR" ]]; then rsync -a --delete --exclude .venv --exclude config --exclude '__pycache__' \ "$SRC_DIR"/ "$INSTALL_DIR"/ fi mkdir -p "$INSTALL_DIR/config" chown -R "$SERVICE_USER":"$SERVICE_USER" "$INSTALL_DIR" echo "==> python venv" extras="" [[ $WITH_GUI -eq 1 ]] && extras="gui" [[ $WITH_VAD -eq 1 ]] && extras="${extras:+$extras,}vad" sudo -u "$SERVICE_USER" bash -c " set -e cd '$INSTALL_DIR' [[ -x .venv/bin/python ]] || python3 -m venv .venv .venv/bin/python -m pip install --upgrade pip wheel if [[ -n '$extras' ]]; then .venv/bin/python -m pip install -e '.[$extras]'; else .venv/bin/python -m pip install -e .; fi # Pre-download openWakeWord ONNX models so the service works offline later. .venv/bin/python -c 'from openwakeword.utils import download_models; download_models()' || true " echo "==> permissions (audio, backlight)" usermod -aG audio,video "$SERVICE_USER" cat > /etc/udev/rules.d/90-javis-backlight.rules <<'EOF' # Let members of 'video' set the display brightness (javis-slave /mcp/v1/brightness). SUBSYSTEM=="backlight", ACTION=="add", RUN+="/bin/chgrp video /sys/class/backlight/%k/brightness", RUN+="/bin/chmod g+w /sys/class/backlight/%k/brightness" EOF udevadm control --reload-rules || true # Passwordless power control for the service user (only the exact commands used). cat > /etc/sudoers.d/javis-slave < systemd unit" sed -e "s|^User=.*|User=$SERVICE_USER|" \ -e "s|/opt/javis-slave|$INSTALL_DIR|g" \ "$INSTALL_DIR/javis-slave.service" > /etc/systemd/system/javis-slave.service systemctl daemon-reload if [[ $WITH_GUI -eq 1 ]]; then # With a display the touch UI runs in the user's desktop session (kiosk), # not as a system service — both would fight over the microphone. systemctl disable javis-slave.service >/dev/null 2>&1 || true AUTOSTART_DIR="$(getent passwd "$SERVICE_USER" | cut -d: -f6)/.config/autostart" sudo -u "$SERVICE_USER" mkdir -p "$AUTOSTART_DIR" cat > "$AUTOSTART_DIR/javis-slave.desktop" <