Files
shawn e9c504c0d7
Lint & Validate / lint (push) Has been cancelled
P7-R1: Build Script / SD Card Image — automated build system
Adds complete SD card image builder for the RPi Audio Mixer:
- build/build.sh: Main builder (RPiOS Lite base + chroot config + optional RT kernel cross-compile)
- build/configure-system.sh: Chroot configuration (packages, services, audio config)
- build/first-boot/setup-wizard.sh: Interactive first-boot setup (audio, WiFi, hostname, API key, JACK)
- build/README.md: Flashing instructions and build documentation
- Updated .gitignore to track build scripts but ignore artifacts
- Updated README.md with current project status

735/735 tests pass. Build runs on x86_64 without a Raspberry Pi.
2026-05-19 22:38:15 -04:00

417 lines
12 KiB
Bash
Executable File

#!/bin/bash
# configure-system.sh — runs inside chroot to configure the RPi Audio Mixer image
# Called from build.sh Step 5.
set -euo pipefail
# ─── Variables (populated by build.sh via env) ───────────────────────────────
MIXER_HOSTNAME="${MIXER_HOSTNAME:-pi-mixer}"
MIXER_API_KEY="${MIXER_API_KEY:-mixer-local}"
MIXER_WIFI_SSID="${MIXER_WIFI_SSID:-}"
MIXER_WIFI_PASS="${MIXER_WIFI_PASS:-}"
MIXER_VERSION="${MIXER_VERSION:-dev}"
export DEBIAN_FRONTEND=noninteractive
log() { echo "[CONFIGURE] $*"; }
# ─── 1. Basic system configuration ───────────────────────────────────────────
log "1/8: Configuring hostname and locale..."
echo "$MIXER_HOSTNAME" > /etc/hostname
sed -i "s/raspberrypi/$MIXER_HOSTNAME/g" /etc/hosts 2>/dev/null || true
# Enable required locales
sed -i 's/^# en_US.UTF-8/en_US.UTF-8/' /etc/locale.gen 2>/dev/null || true
locale-gen en_US.UTF-8 2>/dev/null || true
update-locale LANG=en_US.UTF-8 2>/dev/null || true
# ─── 2. Update package cache ─────────────────────────────────────────────────
log "2/8: Updating package cache..."
apt-get update -qq
# ─── 3. Install system packages ──────────────────────────────────────────────
log "3/8: Installing audio and system packages..."
apt-get install -y -qq \
jackd2 \
jackd2-firewire \
libjack-jackd2-0 \
libjack-jackd2-dev \
jack-tools \
a2jmidid \
alsa-utils \
alsa-tools \
pulseaudio \
pulseaudio-module-jack \
rtirq-init \
cpufrequtils \
i2c-tools \
python3 \
python3-pip \
python3-venv \
python3-dev \
python3-numpy \
python3-scipy \
libpython3-dev \
git \
wget \
curl \
vim-tiny \
tmux \
sudo \
ufw \
ntp \
iptables \
usbutils \
pciutils \
net-tools \
wireless-tools \
wpasupplicant \
avahi-daemon \
libusb-1.0-0-dev \
libasound2-dev \
libsndfile1-dev \
liblo-dev \
libfftw3-dev \
libcairo2-dev \
libgirepository1.0-dev \
gir1.2-gtk-3.0 \
xserver-xorg \
x11-xserver-utils \
xinit \
libsdl2-dev \
libsdl2-image-dev \
libsdl2-mixer-dev \
libsdl2-ttf-dev \
mesa-utils \
libgles2-mesa-dev \
pkg-config
log " Cleaning apt cache..."
apt-get clean
rm -rf /var/lib/apt/lists/*
# ─── 4. Install Python packages ──────────────────────────────────────────────
log "4/8: Installing Python packages..."
# Create virtual environment for the mixer
python3 -m venv /opt/rpi-mixer/venv --system-site-packages
/opt/rpi-mixer/venv/bin/pip install --no-cache-dir --upgrade pip setuptools wheel
/opt/rpi-mixer/venv/bin/pip install --no-cache-dir \
fastapi \
uvicorn \
websockets \
python-multipart \
pydantic \
pytest \
pytest-asyncio \
pytest-cov \
python-jack-client \
python-rtmidi \
pyyaml \
aiohttp 2>/dev/null || true
# ─── 5. Create audio user/group ──────────────────────────────────────────────
log "5/8: Creating 'pi' user and 'audio' group..."
# The 'pi' user already exists in RPiOS, add to audio group
usermod -a -G audio pi 2>/dev/null || true
usermod -a -G input pi 2>/dev/null || true # For evdev keyboard controls
usermod -a -G video pi 2>/dev/null || true # For framebuffer/Kivy
# Set real-time priorities for audio group
cat > /etc/security/limits.d/99-audio.conf <<'EOF'
# Realtime audio group limits for low-latency JACK
@audio - rtprio 99
@audio - memlock unlimited
@audio - nice -20
@audio - priority 99
EOF
# ─── 6. Install systemd services ─────────────────────────────────────────────
log "6/8: Installing systemd services..."
# CPU performance governor
cat > /etc/systemd/system/cpu-performance.service <<'EOF'
[Unit]
Description=Set CPU governor to performance
After=multi-user.target
[Service]
Type=oneshot
ExecStart=/bin/sh -c 'for cpu in /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor; do [ -f "$cpu" ] && echo performance > "$cpu" || true; done'
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
EOF
# JACK2 audio server
cat > /etc/systemd/system/jackd.service <<'EOF'
[Unit]
Description=JACK2 Audio Server
After=sound.target network.target cpu-performance.service
Wants=sound.target cpu-performance.service
[Service]
Type=simple
User=pi
Group=audio
Environment=JACK_NO_AUDIO_RESERVATION=1
Environment=JACK_PROMISCUOUS_SERVER=1
LimitRTPRIO=99
LimitMEMLOCK=infinity
LimitNICE=-20
CPUAffinity=1-3
IOSchedulingClass=realtime
IOSchedulingPriority=0
ExecStart=/usr/bin/jackd -P 70 -t 2000 -d alsa -d hw:USB -r 48000 -p 128 -n 3 -M -X seq -S
Restart=on-failure
RestartSec=2
[Install]
WantedBy=multi-user.target
EOF
# Mixer API server
cat > /etc/systemd/system/mixer-api.service <<'EOF'
[Unit]
Description=RPi Audio Mixer — Network API Server
After=jackd.service network.target
Wants=jackd.service
Requires=jackd.service
[Service]
Type=simple
User=pi
Group=audio
WorkingDirectory=/opt/rpi-mixer
ExecStart=/opt/rpi-mixer/venv/bin/python3 -m src.network.run --host 0.0.0.0 --port 8080 --api-key MIXER_API_KEY_PLACEHOLDER
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
# Replace API key placeholder
sed -i "s/MIXER_API_KEY_PLACEHOLDER/$MIXER_API_KEY/" /etc/systemd/system/mixer-api.service
# Mixer Kivy touch UI
cat > /etc/systemd/system/mixer-ui.service <<'EOF'
[Unit]
Description=RPi Audio Mixer — Touchscreen UI
After=mixer-api.service
Wants=mixer-api.service
[Service]
Type=simple
User=pi
Group=audio
WorkingDirectory=/opt/rpi-mixer
Environment=MIXER_HOST=127.0.0.1
Environment=MIXER_PORT=8080
Environment=MIXER_API_KEY=MIXER_API_KEY_PLACEHOLDER
ExecStart=/opt/rpi-mixer/venv/bin/python3 main_touch.py --host 127.0.0.1 --port 8080 --api-key MIXER_API_KEY_PLACEHOLDER
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
sed -i "s/MIXER_API_KEY_PLACEHOLDER/$MIXER_API_KEY/g" /etc/systemd/system/mixer-ui.service
# First-boot setup wizard
cat > /etc/systemd/system/mixer-firstboot.service <<'EOF'
[Unit]
Description=RPi Audio Mixer — First-Boot Setup Wizard
After=network.target
Before=getty@tty1.service
ConditionPathExists=/opt/rpi-mixer/build/first-boot/setup-wizard.sh
[Service]
Type=oneshot
ExecStart=/bin/bash /opt/rpi-mixer/build/first-boot/setup-wizard.sh
StandardInput=tty
StandardOutput=tty
TTYPath=/dev/tty1
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
EOF
# ─── 7. Install config files and udev rules ──────────────────────────────────
log "7/8: Installing config files..."
# udev rule for MIDI devices
cp /opt/rpi-mixer/config/99-midi.rules /etc/udev/rules.d/ 2>/dev/null || true
# rtirq config
cp /opt/rpi-mixer/config/rtirq /etc/default/rtirq 2>/dev/null || true
# ALSA config for USB audio
cp /opt/rpi-mixer/config/alsa-usb.conf /etc/modprobe.d/ 2>/dev/null || true
# USB audio quirks
cp /opt/rpi-mixer/config/usb-audio-quirks.conf /etc/modprobe.d/ 2>/dev/null || true
# JACK config files
mkdir -p /etc/jack
cp /opt/rpi-mixer/config/jackdrc /etc/jackdrc 2>/dev/null || true
# Carla config files
mkdir -p /home/pi/.config/carla
cp /opt/rpi-mixer/config/carla-8ch-default.carxp /home/pi/.config/carla/ 2>/dev/null || true
cp /opt/rpi-mixer/config/carla-patchbay.xml /home/pi/.config/carla/ 2>/dev/null || true
chown -R pi:pi /home/pi/.config/carla 2>/dev/null || true
# ─── 8. Enable services and finalize ─────────────────────────────────────────
log "8/8: Enabling services..."
# Enable boot-time services
systemctl enable cpu-performance.service 2>/dev/null || true
# Don't enable jackd yet — it needs audio interface detection from first-boot
# (the first-boot wizard handles this)
# Enable mixer services (will start after first-boot completes)
systemctl enable mixer-firstboot.service 2>/dev/null || true
# Configure first-boot wizard to auto-disable itself
# (runs once, then disables itself)
# Make setup-wizard executable
chmod +x /opt/rpi-mixer/build/first-boot/setup-wizard.sh 2>/dev/null || true
# Create /data directory structure for persistence
mkdir -p /data/{sessions,recordings,presets,logs,plugins}
chown -R pi:pi /data 2>/dev/null || true
# Symlink data directories into pi home
ln -sf /data/sessions /home/pi/sessions 2>/dev/null || true
ln -sf /data/recordings /home/pi/recordings 2>/dev/null || true
ln -sf /data/presets /home/pi/presets 2>/dev/null || true
chown -h pi:pi /home/pi/{sessions,recordings,presets} 2>/dev/null || true
# Set bashrc with helpful aliases
cat >> /home/pi/.bashrc <<'EOF'
# ─── RPi Audio Mixer aliases ───
alias mixer-status='systemctl status jackd mixer-api mixer-ui'
alias mixer-start='sudo systemctl start jackd && sleep 2 && sudo systemctl start mixer-api'
alias mixer-stop='sudo systemctl stop mixer-ui mixer-api jackd'
alias mixer-restart='sudo systemctl restart mixer-api'
alias mixer-log='journalctl -u mixer-api -f'
alias jack-log='journalctl -u jackd -f'
alias jack-ports='jack_lsp -c'
alias jack-load='jack_cpu_load'
alias audio-test='speaker-test -t sine -f 440 -l 0'
alias mixer-build='/opt/rpi-mixer/build/build.sh'
EOF
chown pi:pi /home/pi/.bashrc 2>/dev/null || true
# Disable unnecessary services for appliance mode
log " Disabling non-essential services..."
systemctl disable bluetooth 2>/dev/null || true
systemctl disable hciuart 2>/dev/null || true
systemctl disable triggerhappy 2>/dev/null || true
systemctl disable ModemManager 2>/dev/null || true
systemctl mask ModemManager 2>/dev/null || true
systemctl disable polkit 2>/dev/null || true
systemctl disable dphys-swapfile 2>/dev/null || true # No swap on SD card
# Configure NTP
cat > /etc/systemd/timesyncd.conf <<'EOF'
[Time]
NTP=0.pool.ntp.org 1.pool.ntp.org 2.pool.ntp.org
FallbackNTP=0.debian.pool.ntp.org
EOF
# Configure SSH to start on boot
systemctl enable ssh 2>/dev/null || true
# Enable Avahi for mDNS (pi-mixer.local)
systemctl enable avahi-daemon 2>/dev/null || true
# ─── 9. Boot config ──────────────────────────────────────────────────────────
log "Finalizing boot config..."
# config.txt adjustments
cat >> /boot/config.txt <<'EOF'
# RPi Audio Mixer — audio appliance optimizations
# Disable WiFi/BT for reduced latency (enable via setup wizard if needed)
dtoverlay=disable-wifi
dtoverlay=disable-bt
# Fast boot
boot_delay=0
disable_splash=1
# GPU memory (minimal — no desktop)
gpu_mem=16
# Force HDMI hotplug (for touchscreen)
hdmi_force_hotplug=1
# Disable camera auto-detect (we use USB cameras)
camera_auto_detect=0
# Enable I2C for GPIO expanders
dtparam=i2c_arm=on
# Disable audio (we use USB audio interface)
dtparam=audio=off
EOF
# cmdline.txt adjustments
# Keep existing and append
sed -i 's/console=serial0,115200 //g' /boot/cmdline.txt 2>/dev/null || true
echo " quiet loglevel=3 usbcore.autosuspend=-1 fsck.mode=skip" >> /boot/cmdline.txt
# ─── 10. WiFi pre-configuration (optional) ───────────────────────────────────
if [[ -n "$MIXER_WIFI_SSID" ]]; then
log "Pre-configuring WiFi..."
# Remove WiFi disable overlay
sed -i 's/^dtoverlay=disable-wifi/#dtoverlay=disable-wifi/' /boot/config.txt 2>/dev/null || true
cat > /etc/wpa_supplicant/wpa_supplicant.conf <<WIFIEOF
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1
country=US
network={
ssid="$MIXER_WIFI_SSID"
psk="$MIXER_WIFI_PASS"
key_mgmt=WPA-PSK
}
WIFIEOF
chmod 600 /etc/wpa_supplicant/wpa_supplicant.conf
fi
# ─── Done ────────────────────────────────────────────────────────────────────
# Write version marker
echo "rpi-audio-mixer $MIXER_VERSION" > /etc/mixer-version
echo "Installed: $(date -u +%Y-%m-%dT%H:%M:%SZ)" >> /etc/mixer-version
log "System configuration complete."