P7-R1: Build Script / SD Card Image — automated build system
Lint & Validate / lint (push) Has been cancelled
Lint & Validate / lint (push) Has been cancelled
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.
This commit is contained in:
Executable
+344
@@ -0,0 +1,344 @@
|
||||
#!/bin/bash
|
||||
# setup-wizard.sh — First-boot configuration wizard for RPi Audio Mixer
|
||||
#
|
||||
# Runs on first boot (tty1) before getty starts. Guides the user through:
|
||||
# 1. Audio interface detection and selection
|
||||
# 2. WiFi configuration
|
||||
# 3. Hostname setting
|
||||
# 4. API key generation
|
||||
# 5. JACK device configuration
|
||||
#
|
||||
# After completion, the wizard disables itself, enables the mixer services,
|
||||
# and reboots into normal operation.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ─── Colours ─────────────────────────────────────────────────────────────────
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
BOLD='\033[1m'
|
||||
NC='\033[0m'
|
||||
|
||||
# ─── Configuration paths ────────────────────────────────────────────────────
|
||||
|
||||
JACKD_SERVICE="/etc/systemd/system/jackd.service"
|
||||
MIXER_API_SERVICE="/etc/systemd/system/mixer-api.service"
|
||||
MIXER_UI_SERVICE="/etc/systemd/system/mixer-ui.service"
|
||||
CONFIG_TXT="/boot/config.txt"
|
||||
CMDLINE_TXT="/boot/cmdline.txt"
|
||||
WPA_CONF="/etc/wpa_supplicant/wpa_supplicant.conf"
|
||||
HOSTNAME_FILE="/etc/hostname"
|
||||
HOSTS_FILE="/etc/hosts"
|
||||
|
||||
# ─── Helper functions ────────────────────────────────────────────────────────
|
||||
|
||||
header() {
|
||||
clear
|
||||
echo -e "${BLUE}${BOLD}"
|
||||
echo "╔══════════════════════════════════════════╗"
|
||||
echo "║ RPi Audio Mixer — First-Boot Setup ║"
|
||||
echo "║ $1"
|
||||
echo "╚══════════════════════════════════════════╝"
|
||||
echo -e "${NC}"
|
||||
echo ""
|
||||
}
|
||||
|
||||
prompt() {
|
||||
local var_name="$1"
|
||||
local default="$2"
|
||||
local question="$3"
|
||||
local value
|
||||
|
||||
echo -en "${GREEN}${question}${NC} [${default}]: "
|
||||
read -r value
|
||||
value="${value:-$default}"
|
||||
eval "$var_name=\"$value\""
|
||||
}
|
||||
|
||||
prompt_yn() {
|
||||
local question="$1"
|
||||
local default="${2:-y}"
|
||||
local answer
|
||||
|
||||
while true; do
|
||||
echo -en "${GREEN}${question}${NC} (y/n) [${default}]: "
|
||||
read -r answer
|
||||
answer="${answer:-$default}"
|
||||
case "$answer" in
|
||||
[Yy]*) return 0 ;;
|
||||
[Nn]*) return 1 ;;
|
||||
*) echo "Please answer y or n" ;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
press_enter() {
|
||||
echo ""
|
||||
echo -n "Press Enter to continue..."
|
||||
read -r
|
||||
}
|
||||
|
||||
# ─── 1. Welcome ──────────────────────────────────────────────────────────────
|
||||
|
||||
header "Welcome"
|
||||
|
||||
echo "This wizard will configure your Raspberry Pi Audio Mixer."
|
||||
echo "You'll need:"
|
||||
echo " - USB audio interface connected"
|
||||
echo " - (Optional) WiFi network name and password"
|
||||
echo " - (Optional) Touchscreen connected via HDMI+USB"
|
||||
echo ""
|
||||
echo "This only runs once. Settings can be changed later via:"
|
||||
echo " /opt/rpi-mixer/config/ — audio/jack/carla configs"
|
||||
echo " raspi-config — system settings"
|
||||
echo ""
|
||||
press_enter
|
||||
|
||||
# ─── 2. Audio Interface Detection ────────────────────────────────────────────
|
||||
|
||||
header "Step 1/5: Audio Interface"
|
||||
|
||||
echo "Detecting USB audio interfaces..."
|
||||
echo ""
|
||||
|
||||
# List sound cards
|
||||
ALSA_CARDS=""
|
||||
if command -v aplay >/dev/null 2>&1; then
|
||||
ALSA_CARDS=$(aplay -l 2>/dev/null | grep -E '^card [0-9]' || true)
|
||||
fi
|
||||
|
||||
if [[ -z "$ALSA_CARDS" ]]; then
|
||||
echo -e "${YELLOW}No sound cards detected!${NC}"
|
||||
echo ""
|
||||
echo "Make sure your USB audio interface is connected."
|
||||
echo "You can continue anyway and configure later."
|
||||
echo ""
|
||||
|
||||
if prompt_yn "Continue without audio interface?" "y"; then
|
||||
echo "Skipping audio interface config."
|
||||
else
|
||||
echo "Please connect your audio interface and reboot."
|
||||
echo "The setup wizard will run again on next boot."
|
||||
exit 0
|
||||
fi
|
||||
else
|
||||
echo "Found sound cards:"
|
||||
echo "$ALSA_CARDS"
|
||||
echo ""
|
||||
|
||||
# Detect USB audio card
|
||||
USB_CARD=""
|
||||
if aplay -l 2>/dev/null | grep -qi "USB"; then
|
||||
USB_CARD=$(aplay -l 2>/dev/null | grep -i "USB" | head -1 | awk '{print $2}' | tr -d ':')
|
||||
echo -e "${GREEN}USB audio interface detected: card $USB_CARD${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}No USB audio card found. Using first available card.${NC}"
|
||||
USB_CARD=$(echo "$ALSA_CARDS" | head -1 | awk '{print $2}' | tr -d ':')
|
||||
fi
|
||||
|
||||
echo ""
|
||||
prompt AUDIO_DEV "hw:USB" "ALSA device name for JACK?"
|
||||
|
||||
# Update JACK service with detected device
|
||||
sed -i "s/-d hw:USB/-d $AUDIO_DEV/" "$JACKD_SERVICE" 2>/dev/null || true
|
||||
|
||||
# Also update jackdrc
|
||||
mkdir -p /etc/jack
|
||||
sed -i "s/-d hw:USB/-d $AUDIO_DEV/" /etc/jackdrc 2>/dev/null || true
|
||||
echo "/usr/bin/jackd -P 70 -t 2000 -d alsa -d $AUDIO_DEV -r 48000 -p 128 -n 3 -M -X seq" > /etc/jackdrc
|
||||
fi
|
||||
|
||||
press_enter
|
||||
|
||||
# ─── 3. WiFi Configuration ───────────────────────────────────────────────────
|
||||
|
||||
header "Step 2/5: Network"
|
||||
|
||||
# Check if Ethernet is connected
|
||||
ETH_CONNECTED=false
|
||||
if ip link show eth0 2>/dev/null | grep -q "state UP"; then
|
||||
ETH_CONNECTED=true
|
||||
echo -e "${GREEN}Ethernet connected.${NC}"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
if prompt_yn "Configure WiFi?" "n"; then
|
||||
prompt WIFI_SSID "" "WiFi network name (SSID)"
|
||||
echo -n "WiFi password: "
|
||||
read -s WIFI_PASS
|
||||
echo ""
|
||||
|
||||
# Remove WiFi disable from config.txt
|
||||
sed -i 's/^dtoverlay=disable-wifi/#dtoverlay=disable-wifi/' "$CONFIG_TXT" 2>/dev/null || true
|
||||
|
||||
# Write wpa_supplicant config
|
||||
cat > "$WPA_CONF" <<WIFIEOF
|
||||
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
|
||||
update_config=1
|
||||
country=US
|
||||
|
||||
network={
|
||||
ssid="$WIFI_SSID"
|
||||
psk="$WIFI_PASS"
|
||||
key_mgmt=WPA-PSK
|
||||
}
|
||||
WIFIEOF
|
||||
chmod 600 "$WPA_CONF"
|
||||
echo -e "${GREEN}WiFi configured.${NC}"
|
||||
else
|
||||
echo "Skipping WiFi configuration."
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Network status:"
|
||||
ip -4 addr show | grep -E "inet " | grep -v "127.0.0.1" | sed 's/^/ /' || echo " No network interfaces active"
|
||||
|
||||
press_enter
|
||||
|
||||
# ─── 4. Hostname ─────────────────────────────────────────────────────────────
|
||||
|
||||
header "Step 3/5: Hostname"
|
||||
|
||||
CURRENT_HOSTNAME=$(hostname 2>/dev/null || cat "$HOSTNAME_FILE" 2>/dev/null || echo "pi-mixer")
|
||||
prompt NEW_HOSTNAME "$CURRENT_HOSTNAME" "System hostname"
|
||||
|
||||
if [[ "$NEW_HOSTNAME" != "$CURRENT_HOSTNAME" ]]; then
|
||||
echo "$NEW_HOSTNAME" > "$HOSTNAME_FILE"
|
||||
sed -i "s/$CURRENT_HOSTNAME/$NEW_HOSTNAME/g" "$HOSTS_FILE" 2>/dev/null || true
|
||||
hostname "$NEW_HOSTNAME" 2>/dev/null || true
|
||||
echo -e "${GREEN}Hostname set to: $NEW_HOSTNAME${NC}"
|
||||
echo " Local access: http://${NEW_HOSTNAME}.local:8080"
|
||||
fi
|
||||
|
||||
press_enter
|
||||
|
||||
# ─── 5. API Key ──────────────────────────────────────────────────────────────
|
||||
|
||||
header "Step 4/5: Security"
|
||||
|
||||
CURRENT_API_KEY=$(grep "MIXER_API_KEY" "$MIXER_API_SERVICE" 2>/dev/null | grep -oP 'MIXER_API_KEY=\K[^"]+' | head -1 || echo "")
|
||||
|
||||
if [[ -z "$CURRENT_API_KEY" ]] || [[ "$CURRENT_API_KEY" == "MIXER_API_KEY_PLACEHOLDER" ]] || [[ "$CURRENT_API_KEY" == "mixer-local" ]]; then
|
||||
NEW_API_KEY=$(head -c 24 /dev/urandom | base64 | tr -dc 'a-zA-Z0-9' | head -c 24)
|
||||
else
|
||||
NEW_API_KEY="$CURRENT_API_KEY"
|
||||
fi
|
||||
|
||||
echo "API key for the mixer web UI and REST API:"
|
||||
echo -e " ${BOLD}${NEW_API_KEY}${NC}"
|
||||
echo ""
|
||||
echo "You'll need this to access the web interface from another device."
|
||||
echo "Keep it safe. It can be changed in:"
|
||||
echo " $MIXER_API_SERVICE"
|
||||
echo ""
|
||||
|
||||
if prompt_yn "Use a custom API key?" "n"; then
|
||||
prompt NEW_API_KEY "$NEW_API_KEY" "API key (no spaces)"
|
||||
fi
|
||||
|
||||
# Update API key in service files
|
||||
sed -i "s/--api-key [^ ]*/--api-key $NEW_API_KEY/" "$MIXER_API_SERVICE" 2>/dev/null || true
|
||||
sed -i "s/--api-key [^ ]*/--api-key $NEW_API_KEY/" "$MIXER_UI_SERVICE" 2>/dev/null || true
|
||||
sed -i "s/MIXER_API_KEY=[^ ]*/MIXER_API_KEY=$NEW_API_KEY/" "$MIXER_UI_SERVICE" 2>/dev/null || true
|
||||
|
||||
# Also update the env variable export line
|
||||
sed -i "/^ExecStart.*mixer-api/,/^$/s/MIXER_API_KEY_PLACEHOLDER/$NEW_API_KEY/" "$MIXER_API_SERVICE" 2>/dev/null || true
|
||||
|
||||
echo -e "${GREEN}API key updated.${NC}"
|
||||
|
||||
press_enter
|
||||
|
||||
# ─── 6. JACK Configuration ───────────────────────────────────────────────────
|
||||
|
||||
header "Step 5/5: Audio Settings"
|
||||
|
||||
echo "JACK audio server settings:"
|
||||
echo ""
|
||||
|
||||
prompt SAMPLE_RATE "48000" "Sample rate (44100/48000/96000)"
|
||||
prompt BUFFER_SIZE "128" "Buffer size (64/128/256/512 — lower = less latency)"
|
||||
prompt PERIODS "3" "Periods per buffer (2 or 3)"
|
||||
|
||||
# Update JACK service
|
||||
sed -i "s/-r [0-9]*/-r $SAMPLE_RATE/" "$JACKD_SERVICE" 2>/dev/null || true
|
||||
sed -i "s/-p [0-9]*/-p $BUFFER_SIZE/" "$JACKD_SERVICE" 2>/dev/null || true
|
||||
sed -i "s/-n [0-9]*/-n $PERIODS/" "$JACKD_SERVICE" 2>/dev/null || true
|
||||
|
||||
# Calculate approximate latency
|
||||
LATENCY_MS=$(echo "scale=1; ($BUFFER_SIZE * $PERIODS) / ($SAMPLE_RATE / 1000)" | bc 2>/dev/null || echo "?")
|
||||
echo ""
|
||||
echo -e "${GREEN}JACK configured:${NC}"
|
||||
echo " Sample rate: $SAMPLE_RATE Hz"
|
||||
echo " Buffer size: $BUFFER_SIZE frames"
|
||||
echo " Periods: $PERIODS"
|
||||
echo " Latency: ~${LATENCY_MS}ms round-trip"
|
||||
echo ""
|
||||
echo "Lower latency requires more CPU. If you hear xruns (clicks/pops),"
|
||||
echo "increase buffer size or periods in: $JACKD_SERVICE"
|
||||
|
||||
press_enter
|
||||
|
||||
# ─── 7. Touchscreen Check ────────────────────────────────────────────────────
|
||||
|
||||
header "Touchscreen (optional)"
|
||||
|
||||
echo "If a touchscreen is connected via HDMI+USB, the Kivy touch UI"
|
||||
echo "will start automatically on boot."
|
||||
echo ""
|
||||
|
||||
if prompt_yn "Enable touchscreen UI on boot?" "y"; then
|
||||
systemctl enable mixer-ui.service 2>/dev/null || true
|
||||
echo -e "${GREEN}Touchscreen UI enabled.${NC}"
|
||||
|
||||
# Check HDMI connection
|
||||
if tvservice -s 2>/dev/null | grep -q "HDMI"; then
|
||||
echo " HDMI display detected."
|
||||
else
|
||||
echo -e " ${YELLOW}HDMI display not detected. UI may not be visible.${NC}"
|
||||
fi
|
||||
else
|
||||
systemctl disable mixer-ui.service 2>/dev/null || true
|
||||
echo "Touchscreen UI disabled."
|
||||
echo "Start manually: sudo systemctl start mixer-ui"
|
||||
fi
|
||||
|
||||
press_enter
|
||||
|
||||
# ─── 8. Finalize ─────────────────────────────────────────────────────────────
|
||||
|
||||
header "Setup Complete"
|
||||
|
||||
echo "Configuration summary:"
|
||||
echo " Hostname: $(cat $HOSTNAME_FILE 2>/dev/null || echo unknown)"
|
||||
echo " Audio device: ${AUDIO_DEV:-hw:USB}"
|
||||
echo " JACK: ${SAMPLE_RATE:-48000}Hz / ${BUFFER_SIZE:-128}frames / ${PERIODS:-3}periods"
|
||||
echo " API key: ${NEW_API_KEY:-mixer-local}"
|
||||
echo " Web UI: http://$(cat $HOSTNAME_FILE 2>/dev/null || echo pi-mixer).local:8080"
|
||||
echo " SSH: ssh pi@$(cat $HOSTNAME_FILE 2>/dev/null || echo pi-mixer).local"
|
||||
echo ""
|
||||
|
||||
# Enable services
|
||||
log "Enabling mixer services..."
|
||||
systemctl enable jackd.service 2>/dev/null || true
|
||||
systemctl enable mixer-api.service 2>/dev/null || true
|
||||
systemctl daemon-reload 2>/dev/null || true
|
||||
|
||||
# Disable this wizard
|
||||
log "Disabling first-boot wizard..."
|
||||
systemctl disable mixer-firstboot.service 2>/dev/null || true
|
||||
|
||||
# Write setup-complete marker
|
||||
date -u +%Y-%m-%dT%H:%M:%SZ > /etc/mixer-setup-complete
|
||||
|
||||
echo -e "${GREEN}${BOLD}Rebooting in 5 seconds...${NC}"
|
||||
echo ""
|
||||
echo "After reboot, the mixer will start automatically."
|
||||
echo " Web UI: http://$(cat $HOSTNAME_FILE 2>/dev/null || echo pi-mixer).local:8080"
|
||||
echo " API Key: ${NEW_API_KEY:-mixer-local}"
|
||||
echo ""
|
||||
|
||||
sleep 5
|
||||
reboot
|
||||
Reference in New Issue
Block a user