commit d0f4a33902392b8874196c09fcbc8b4c50d97cbc Author: Felix Förtsch Date: Wed Feb 18 10:50:25 2026 +0100 snapshot current state before gitea sync diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..ddbedf8 Binary files /dev/null and b/.DS_Store differ diff --git a/install-airplay-to-raspi.sh b/install-airplay-to-raspi.sh new file mode 100644 index 0000000..6ecc01f --- /dev/null +++ b/install-airplay-to-raspi.sh @@ -0,0 +1,1003 @@ +#!/bin/bash + +# =================================================================================== +# Shairport-Sync AirPlay 2 ROBUST Installer - ENHANCED VERSION 3.0 +# +# Tailored for: Raspberry Pi (Zero 2/3/4/5) with USB DAC +# Version: 3.0 - Production Ready +# Features: +# - Comprehensive error handling with rollback capability +# - Dependency validation before installation +# - Build failure recovery +# - Audio device validation +# - Service health checks +# - Firewall configuration +# - Latest package versions +# =================================================================================== + +# curl -X PUT "http://192.168.23.218:3689/api/player/stop" +# curl -X POST "http://192.168.23.218:3689/api/queue/items/add?uris=http://icecast.radiofrance.fr/fip-hifi.aac&clear=true&playback=start" + + +set -eo pipefail # Exit on error and pipe failures +IFS=$'\n\t' # Safer word splitting + +# --- Global Variables --- +SCRIPT_VERSION="3.0" +LOG_FILE="/tmp/airplay_install_$(date +%Y%m%d_%H%M%S).log" +BACKUP_DIR="/tmp/airplay_backup_$(date +%Y%m%d_%H%M%S)" +INSTALLATION_FAILED=0 + +# Audio configuration variables +audio_device="" +audio_device_plug="" +card_number="" +device_number="" +mixer_control="" +selected_device="" +airplay_name="" +disable_wifi_pm=false + +# --- Cleanup Handler --- +cleanup() { + local exit_code=$? + if [ $exit_code -ne 0 ] && [ $INSTALLATION_FAILED -eq 1 ]; then + cecho "red" "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + cecho "red" " Installation Failed - Cleaning Up" + cecho "red" "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + + # Stop services if they were started + sudo systemctl stop shairport-sync 2>/dev/null || true + sudo systemctl stop nqptp 2>/dev/null || true + + # Restore backups if they exist + if [ -d "$BACKUP_DIR" ]; then + cecho "yellow" "Restoring original configuration..." + [ -f "$BACKUP_DIR/shairport-sync.conf" ] && \ + sudo cp "$BACKUP_DIR/shairport-sync.conf" /etc/shairport-sync.conf 2>/dev/null || true + fi + + cecho "yellow" "Installation log saved to: $LOG_FILE" + cecho "yellow" "Please check the log for details." + fi + + # Cleanup temp build directories + rm -rf /tmp/nqptp /tmp/shairport-sync 2>/dev/null || true +} + +trap cleanup EXIT ERR INT TERM + +# --- Logging Function --- +log() { + # Create log file if it doesn't exist + touch "$LOG_FILE" 2>/dev/null || true + echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE" +} + +# --- Helper Functions --- +cecho() { + local code="\033[" + case "$1" in + "red") color="${code}1;31m" ;; + "green") color="${code}1;32m" ;; + "yellow") color="${code}1;33m" ;; + "blue") color="${code}1;34m" ;; + "magenta") color="${code}1;35m" ;; + *) color="${code}0m" ;; + esac + echo -e "${color}$2\033[0m" +} + +# Show spinner during long operations +show_spinner() { + local pid=$1 + local delay=0.1 + local spinstr='|/-\' + while ps -p $pid > /dev/null 2>&1; do + local temp=${spinstr#?} + printf " [%c] " "$spinstr" + local spinstr=$temp${spinstr%"$temp"} + sleep $delay + printf "\b\b\b\b\b\b" + done + printf " \b\b\b\b" +} + +# Check if a command exists +command_exists() { + command -v "$1" >/dev/null 2>&1 +} + +# Check if a service is running properly +check_service() { + local service_name=$1 + local max_retries=${2:-3} + local retry=0 + + while [ $retry -lt $max_retries ]; do + if systemctl is-active --quiet "$service_name"; then + cecho "green" "✓ $service_name is running" + return 0 + fi + retry=$((retry + 1)) + [ $retry -lt $max_retries ] && sleep 2 + done + + cecho "red" "✗ $service_name failed to start after $max_retries attempts" + cecho "yellow" "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + cecho "yellow" " Diagnostic Information:" + cecho "yellow" "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo + echo "Service Status:" | tee -a "$LOG_FILE" + sudo systemctl status "$service_name" --no-pager -l 2>&1 | tee -a "$LOG_FILE" + echo + echo "Recent Logs:" | tee -a "$LOG_FILE" + sudo journalctl -u "$service_name" -n 30 --no-pager 2>&1 | tee -a "$LOG_FILE" + echo + cecho "yellow" "Check the log file for more details: $LOG_FILE" + return 1 +} + +# Validate package installation +validate_package() { + local package=$1 + if dpkg -l "$package" 2>/dev/null | grep -q "^ii"; then + return 0 + else + cecho "red" "✗ Package $package failed to install" + return 1 + fi +} + +# Safe directory change +safe_cd() { + cd "$1" || { + cecho "red" "Failed to change directory to: $1" + exit 1 + } +} + +# --- Pre-flight Checks --- +pre_flight_checks() { + cecho "blue" "═══════════════════════════════════════" + cecho "blue" " Running Pre-Flight Checks..." + cecho "blue" "═══════════════════════════════════════" + echo + + # Check for root user + if [ "$EUID" -eq 0 ]; then + cecho "red" "❌ Error: Don't run this script with sudo or as root." + cecho "yellow" " Just run: bash install_airplay_v3.sh" + exit 1 + fi + + # Check if user can sudo + if ! sudo -n true 2>/dev/null; then + cecho "yellow" "Checking sudo access..." + if ! sudo true; then + cecho "red" "❌ This script requires sudo access." + exit 1 + fi + fi + cecho "green" "✓ Sudo access confirmed" + + # Check for internet connection + cecho "yellow" "Checking internet connection..." + local test_hosts=("8.8.8.8" "1.1.1.1" "github.com") + local connection_ok=0 + + for host in "${test_hosts[@]}"; do + # Use timeout command to prevent hanging + cecho "blue" " Testing $host..." + timeout 8 ping -c 1 -W 5 "$host" >/dev/null 2>&1 || true + local result=$? + if [ $result -eq 0 ]; then + connection_ok=1 + log "Internet check: Successfully pinged $host" + cecho "green" " ✓ Connected" + break + elif [ $result -eq 124 ]; then + log "Internet check: Timeout pinging $host" + cecho "yellow" " ✗ Timeout" + else + log "Internet check: Failed to ping $host (exit $result)" + cecho "yellow" " ✗ Failed" + fi + done + + if [ $connection_ok -eq 0 ]; then + cecho "red" "❌ No internet connection detected." + cecho "yellow" " Troubleshooting:" + cecho "yellow" " 1. Check Wi-Fi is connected: iwconfig" + cecho "yellow" " 2. Test manually: ping -c 3 8.8.8.8" + cecho "yellow" " 3. Check DNS: ping -c 3 github.com" + echo + cecho "blue" " Your network status:" + ip addr show wlan0 2>/dev/null | grep "inet " || echo " No wlan0 IP address" + echo + read -p "Skip internet check and continue anyway? (y/N): " skip_check || true + if [[ "$skip_check" =~ ^[Yy]$ ]]; then + cecho "yellow" "⚠ Continuing without internet check (may fail later)" + log "User skipped internet check" + else + exit 1 + fi + else + cecho "green" "✓ Internet connection OK" + fi + + # Check if running on Raspberry Pi + if [ ! -f /proc/device-tree/model ]; then + cecho "yellow" "⚠ Warning: This doesn't appear to be a Raspberry Pi." + read -p "Continue anyway? (y/N): " continue_choice || true + [[ ! "$continue_choice" =~ ^[Yy]$ ]] && exit 1 + else + local pi_model + pi_model=$(tr -d '\0' < /proc/device-tree/model) + cecho "green" "✓ Detected: $pi_model" + + # Warn on older Pi models + if echo "$pi_model" | grep -qE "Pi Zero W|Pi 1"; then + cecho "yellow" "⚠ Warning: $pi_model may not have enough power for AirPlay 2" + cecho "yellow" " Recommended: Pi Zero 2 or newer" + read -p "Continue anyway? (y/N): " continue_choice || true + [[ ! "$continue_choice" =~ ^[Yy]$ ]] && exit 1 + fi + fi + + # Check available disk space (need at least 1GB) + local available_space + available_space=$(df / | tail -1 | awk '{print $4}') + if [ "$available_space" -lt 1000000 ]; then + cecho "red" "❌ Not enough disk space. Need at least 1GB free." + cecho "yellow" " Current available: $((available_space / 1024)) MB" + exit 1 + fi + cecho "green" "✓ Sufficient disk space available: $((available_space / 1024)) MB" + + # Check available memory + local available_mem + available_mem=$(free -m | awk '/^Mem:/{print $7}') + if [ "$available_mem" -lt 100 ]; then + cecho "yellow" "⚠ Warning: Low available memory ($available_mem MB)" + cecho "yellow" " Consider closing other applications." + else + cecho "green" "✓ Available memory: $available_mem MB" + fi + + # Check for required base tools + cecho "yellow" "Checking required tools..." + local required_tools=("git" "gcc" "make" "aplay" "amixer") + local missing_tools=() + + for tool in "${required_tools[@]}"; do + if ! command_exists "$tool"; then + missing_tools+=("$tool") + fi + done + + if [ ${#missing_tools[@]} -gt 0 ]; then + cecho "yellow" "⚠ Missing tools: ${missing_tools[*]}" + cecho "yellow" " These will be installed with dependencies." + else + cecho "green" "✓ All base tools present" + fi + + echo +} + +# --- Detect and Select Audio Device (FIXED) --- +select_audio_device() { + echo + cecho "yellow" "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + cecho "yellow" " Step 1: Audio Device Selection" + cecho "yellow" "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo + cecho "cyan" "⏸ PLEASE RESPOND TO THIS PROMPT ⏸" + echo + + # Get list of all audio cards into an array + cecho "blue" "Scanning for audio devices..." + mapfile -t all_cards < <(aplay -l 2>/dev/null | grep '^card') + + if [ ${#all_cards[@]} -eq 0 ]; then + cecho "red" "❌ No audio devices detected at all!" + cecho "yellow" " Make sure your USB DAC is connected." + cecho "yellow" " Try running 'lsusb' to check hardware connection." + exit 1 + fi + + # Always show the menu and force manual selection + cecho "green" "Available audio devices:" + for i in "${!all_cards[@]}"; do + # Display 1-based index for user friendliness + echo " [$((i+1))] ${all_cards[$i]}" + done + echo + + local device_choice + local array_index + + while true; do + read -p "Enter the number of your device [1-${#all_cards[@]}]: " device_choice || true + + # Check if input is a number + if [[ "$device_choice" =~ ^[0-9]+$ ]]; then + # Convert 1-based input to 0-based array index + array_index=$((device_choice - 1)) + + # Check if index exists in array + if [ "$array_index" -ge 0 ] && [ "$array_index" -lt "${#all_cards[@]}" ]; then + selected_device="${all_cards[$array_index]}" + break + fi + fi + cecho "red" "Invalid selection. Please enter a number between 1 and ${#all_cards[@]}." + done + + cecho "green" "✓ You selected: $selected_device" + + # Extract card and device numbers + # Regex looks for "card X" and "device Y" + card_number=$(echo "$selected_device" | grep -oP 'card \K\d+' || echo "") + device_number=$(echo "$selected_device" | grep -oP 'device \K\d+' || echo "0") + + if [ -z "$card_number" ]; then + cecho "red" "❌ Failed to extract card number from selection." + exit 1 + fi + + audio_device="hw:$card_number,$device_number" + audio_device_plug="plughw:$card_number,$device_number" + + cecho "green" "✓ Audio device set to: $audio_device_plug" + + # Validate the audio device actually works + cecho "blue" "Validating audio device..." + if aplay -D "$audio_device_plug" -l >/dev/null 2>&1; then + cecho "green" "✓ Audio device validation passed" + else + cecho "red" "❌ Audio device validation failed" + cecho "yellow" " The device may not support playback." + read -p "Continue anyway? (y/N): " continue_choice || true + [[ ! "$continue_choice" =~ ^[Yy]$ ]] && exit 1 + fi + + # Detect available mixer controls for this card + cecho "blue" "Detecting volume controls..." + mapfile -t mixers < <(amixer -c "$card_number" scontrols 2>/dev/null | grep -oP "Simple mixer control '\K[^']+" || true) + + if [ ${#mixers[@]} -eq 0 ]; then + cecho "yellow" "⚠ No hardware mixer controls found." + cecho "yellow" " Software volume will be used." + mixer_control="" + else + cecho "green" "Available mixer controls:" + for mixer in "${mixers[@]}"; do + echo " - $mixer" + done + + # Try to find the best mixer control + mixer_control="" + for preferred in "PCM" "Master" "Speaker" "Headphone" "Digital"; do + for mixer in "${mixers[@]}"; do + if [[ "$mixer" == "$preferred" ]]; then + mixer_control="$mixer" + break 2 + fi + done + done + + # If no preferred mixer found, use the first one + if [ -z "$mixer_control" ]; then + mixer_control="${mixers[0]}" + fi + + cecho "green" "✓ Selected volume control: $mixer_control" + fi + echo +} + +# --- Get AirPlay Name --- +get_airplay_name() { + echo + cecho "yellow" "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + cecho "yellow" " Step 2: Name Your AirPlay Device" + cecho "yellow" "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo + cecho "cyan" "⏸ PLEASE RESPOND TO THIS PROMPT ⏸" + echo + + local hostname + hostname=$(hostname) + cecho "blue" "This is the name that will appear on your iPhone/iPad." + cecho "blue" "Examples: Living Room, Bedroom Speaker, Kitchen Audio" + echo + cecho "green" ">>> " + read -p "Enter a name (or press Enter for '$hostname AirPlay'): " airplay_name || true + + if [ -z "$airplay_name" ]; then + airplay_name="$hostname AirPlay" + fi + + # Sanitize the name (remove special characters that might cause issues) + airplay_name=$(echo "$airplay_name" | sed 's/[^a-zA-Z0-9 _-]//g') + + cecho "green" "✓ AirPlay name: '$airplay_name'" + echo +} + +# --- Wi-Fi Power Management --- +configure_wifi() { + echo + cecho "yellow" "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + cecho "yellow" " Step 3: Wi-Fi Optimization" + cecho "yellow" "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo + cecho "cyan" "⏸ PLEASE RESPOND TO THIS PROMPT ⏸" + echo + + # Check if Wi-Fi is actually being used + if ! ip link show wlan0 &>/dev/null; then + cecho "yellow" "⚠ No Wi-Fi interface detected (wlan0 not found)" + cecho "yellow" " Skipping Wi-Fi optimization." + disable_wifi_pm=false + echo + return + fi + + cecho "blue" "Wi-Fi power saving can cause audio stuttering and dropouts." + cecho "blue" "Disabling it ensures smooth, uninterrupted playback." + echo + cecho "green" ">>> " + read -p "Disable Wi-Fi power saving? (Y/n): " wifi_choice || true + + if [[ -z "$wifi_choice" || "$wifi_choice" =~ ^[Yy]$ ]]; then + disable_wifi_pm=true + cecho "green" "✓ Wi-Fi power management will be disabled" + else + disable_wifi_pm=false + cecho "yellow" "⚠ Keeping default Wi-Fi settings (may cause dropouts)" + fi + echo +} + +# --- Main Installation --- +main() { + # Initialize log file immediately + touch "$LOG_FILE" 2>/dev/null || LOG_FILE="/tmp/airplay_install_fallback.log" + + clear + cecho "green" "╔═════════════════════════════════════════════════════╗" + cecho "green" "║ ║" + cecho "green" "║ AirPlay 2 Installer for Raspberry Pi + DAC ║" + cecho "green" "║ Version $SCRIPT_VERSION ║" + cecho "green" "║ ║" + cecho "green" "╚═════════════════════════════════════════════════════╝" + echo + cecho "blue" "This installer will turn your Raspberry Pi into a" + cecho "blue" "high-quality AirPlay 2 receiver. Just follow the prompts!" + echo + cecho "yellow" "Installation log: $LOG_FILE" + echo + + log "=== AirPlay 2 Installation Started ===" + log "Script Version: $SCRIPT_VERSION" + log "Date: $(date)" + log "User: $(whoami)" + log "System: $(uname -a)" + echo + + # Check if running interactively + if [ -t 0 ]; then + read -p "Press Enter to begin..." || true + else + cecho "yellow" "⚠ Non-interactive mode detected - using defaults" + sleep 2 + fi + echo + + # Run all setup steps + pre_flight_checks + select_audio_device + get_airplay_name + configure_wifi + + # --- Confirmation --- + echo + echo + cecho "magenta" "╔═════════════════════════════════════════════════════╗" + cecho "magenta" "║ INSTALLATION CONFIGURATION ║" + cecho "magenta" "╚═════════════════════════════════════════════════════╝" + echo + cecho "yellow" " 📱 AirPlay Name: $airplay_name" + cecho "yellow" " 🔊 Audio Output: $audio_device_plug" + cecho "yellow" " 🎚️ Volume Control: ${mixer_control:-None (fixed volume)}" + cecho "yellow" " 📡 Disable Wi-Fi PM: $disable_wifi_pm" + echo + cecho "blue" "Installation will take 10-30 minutes depending on your Pi model." + cecho "blue" "(Pi Zero 2 will be slower, Pi 4/5 will be faster)" + echo + echo + cecho "cyan" "⏸ FINAL CONFIRMATION - PRESS ENTER TO CONTINUE ⏸" + echo + if [ -t 0 ]; then + read -p "Press Enter to start installation, or Ctrl+C to cancel..." || true + else + cecho "yellow" "Auto-starting in 5 seconds (non-interactive mode)..." + sleep 5 + fi + echo + + INSTALLATION_FAILED=1 # Mark that installation has started + + # Create backup directory + mkdir -p "$BACKUP_DIR" + [ -f /etc/shairport-sync.conf ] && cp /etc/shairport-sync.conf "$BACKUP_DIR/" 2>/dev/null || true + + # --- System Update --- + cecho "blue" "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + cecho "blue" " Updating System Packages..." + cecho "blue" "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + log "Updating package lists..." + + if ! sudo apt-get update -qq 2>&1 | tee -a "$LOG_FILE"; then + cecho "red" "❌ Failed to update package lists" + exit 1 + fi + + cecho "yellow" "Upgrading existing packages (this may take a few minutes)..." + if ! sudo apt-get upgrade -y 2>&1 | tee -a "$LOG_FILE"; then + cecho "yellow" "⚠ Package upgrade had issues, but continuing..." + fi + + cecho "green" "✓ System updated" + echo + + # --- Install Dependencies --- + cecho "blue" "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + cecho "blue" " Installing Dependencies..." + cecho "blue" "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + log "Installing build dependencies..." + + local dependencies=( + build-essential git autoconf automake libtool pkg-config + libpopt-dev libconfig-dev libasound2-dev + avahi-daemon libavahi-client-dev libssl-dev + libsoxr-dev libplist-dev libsodium-dev + libavutil-dev libavcodec-dev libavformat-dev + uuid-dev libgcrypt20-dev xxd alsa-utils + ) + + if ! sudo apt-get install -y "${dependencies[@]}" 2>&1 | tee -a "$LOG_FILE"; then + cecho "red" "❌ Failed to install dependencies" + exit 1 + fi + + # Validate critical packages + local critical_packages=("build-essential" "git" "libasound2-dev" "avahi-daemon") + for package in "${critical_packages[@]}"; do + if ! validate_package "$package"; then + cecho "red" "❌ Critical package $package is not installed" + exit 1 + fi + done + + cecho "green" "✓ Dependencies installed" + echo + + # Make sure avahi-daemon is running + if ! systemctl is-active --quiet avahi-daemon; then + cecho "yellow" "Starting avahi-daemon..." + sudo systemctl enable avahi-daemon + sudo systemctl start avahi-daemon + fi + + # --- Install NQPTP --- + cecho "blue" "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + cecho "blue" " Installing NQPTP (Timing System)..." + cecho "blue" "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + log "Cloning NQPTP repository..." + + # Verify /tmp is writable + if [ ! -w /tmp ]; then + cecho "red" "❌ /tmp directory is not writable" + exit 1 + fi + + safe_cd /tmp + rm -rf nqptp 2>/dev/null || true + + if ! git clone https://github.com/mikebrady/nqptp.git 2>&1 | tee -a "$LOG_FILE"; then + cecho "red" "❌ Failed to clone NQPTP repository" + cecho "yellow" " Possible causes:" + cecho "yellow" " - No internet connection" + cecho "yellow" " - GitHub is down" + cecho "yellow" " - Firewall blocking access" + exit 1 + fi + + safe_cd nqptp + log "Building NQPTP..." + + if ! autoreconf -fi 2>&1 | tee -a "$LOG_FILE"; then + cecho "red" "❌ NQPTP autoreconf failed" + exit 1 + fi + + if ! ./configure --with-systemd-startup 2>&1 | tee -a "$LOG_FILE"; then + cecho "red" "❌ NQPTP configure failed" + exit 1 + fi + + if ! make -j"$(nproc)" 2>&1 | tee -a "$LOG_FILE"; then + cecho "red" "❌ NQPTP compilation failed" + exit 1 + fi + + if ! sudo make install 2>&1 | tee -a "$LOG_FILE"; then + cecho "red" "❌ NQPTP installation failed" + exit 1 + fi + + # Verify binary was installed + if ! command_exists nqptp; then + cecho "red" "❌ NQPTP binary not found after installation" + cecho "yellow" " Expected location: /usr/local/bin/nqptp" + exit 1 + fi + + # Enable and start NQPTP + sudo systemctl enable nqptp 2>&1 | tee -a "$LOG_FILE" + sudo systemctl restart nqptp 2>&1 | tee -a "$LOG_FILE" + sleep 3 + + if ! check_service "nqptp"; then + cecho "red" "❌ NQPTP service failed to start" + exit 1 + fi + echo + + # --- Install Shairport-Sync --- + cecho "blue" "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + cecho "blue" " Installing Shairport-Sync..." + cecho "blue" " (This takes 10-20 mins on slower Pis)" + cecho "blue" "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + log "Cloning Shairport-Sync repository..." + + safe_cd /tmp + rm -rf shairport-sync 2>/dev/null || true + + if ! git clone https://github.com/mikebrady/shairport-sync.git 2>&1 | tee -a "$LOG_FILE"; then + cecho "red" "❌ Failed to clone Shairport-Sync repository" + cecho "yellow" " Possible causes:" + cecho "yellow" " - No internet connection" + cecho "yellow" " - GitHub is down" + cecho "yellow" " - Firewall blocking access" + exit 1 + fi + + safe_cd shairport-sync + log "Building Shairport-Sync..." + + if ! autoreconf -fi 2>&1 | tee -a "$LOG_FILE"; then + cecho "red" "❌ Shairport-Sync autoreconf failed" + exit 1 + fi + + cecho "yellow" "Configuring build..." + if ! ./configure --sysconfdir=/etc --with-alsa --with-avahi \ + --with-ssl=openssl --with-soxr --with-systemd \ + --with-airplay-2 2>&1 | tee -a "$LOG_FILE"; then + cecho "red" "❌ Shairport-Sync configure failed" + exit 1 + fi + + cecho "yellow" "Compiling (be patient, this takes time)..." + log "Starting compilation with $(nproc) cores..." + + # Run make in background so we can show spinner + local make_log="${LOG_FILE}.make" + make -j"$(nproc)" > "$make_log" 2>&1 & + local make_pid=$! + show_spinner $make_pid + + # Wait for make to complete and check status + if ! wait $make_pid; then + cecho "red" "❌ Shairport-Sync compilation failed" + cecho "yellow" "Last 20 lines of build log:" + tail -20 "$make_log" 2>/dev/null || echo " (log file not available)" + exit 1 + fi + cat "$make_log" >> "$LOG_FILE" 2>/dev/null || true + + cecho "yellow" "Installing..." + # Note: make install may fail on systemd service install, but that's OK + # We'll create the service file manually later + sudo make install 2>&1 | tee -a "$LOG_FILE" || true + + # What matters is that the binary was installed + if ! command_exists shairport-sync; then + cecho "red" "❌ Shairport-Sync binary not found after installation" + cecho "yellow" " Expected location: /usr/local/bin/shairport-sync" + cecho "yellow" " The 'make install' may have failed - check the log" + exit 1 + fi + + cecho "green" "✓ Shairport-Sync compiled and installed" + log "Note: make install may have shown errors about systemd service - this is normal" + echo + + # --- Configure Shairport-Sync --- + cecho "blue" "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + cecho "blue" " Configuring Shairport-Sync..." + cecho "blue" "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + log "Creating configuration file from sample..." + + # Copy sample config as base (installed by make install) + if [ -f /etc/shairport-sync.conf.sample ]; then + sudo cp /etc/shairport-sync.conf.sample /etc/shairport-sync.conf + log "Copied sample config to /etc/shairport-sync.conf" + else + cecho "yellow" "⚠ Sample config not found, creating minimal config" + # Fallback to minimal config if sample doesn't exist + sudo tee /etc/shairport-sync.conf > /dev/null <<'FALLBACK_EOF' +// Minimal configuration - sample file was not available +general = {}; +alsa = {}; +FALLBACK_EOF + fi + + # Now edit the config file to set our values using simple, reliable sed commands + log "Configuring AirPlay name: $airplay_name" + + # Set the AirPlay device name - uncomment and set it + sudo sed -i "s|^//[[:space:]]*name = .*| name = \"$airplay_name\";|" /etc/shairport-sync.conf + + # Set output device - uncomment and set it + log "Configuring audio output: $audio_device_plug" + sudo sed -i "s|^[[:space:]]*output_device = .*| output_device = \"$audio_device_plug\";|" /etc/shairport-sync.conf + sudo sed -i "s|^//[[:space:]]*output_device = .*| output_device = \"$audio_device_plug\";|" /etc/shairport-sync.conf + + # Set mixer control if available + if [ -n "$mixer_control" ]; then + log "Configuring mixer control: $mixer_control on hw:$card_number" + sudo sed -i "s|^//[[:space:]]*mixer_control_name = .*| mixer_control_name = \"$mixer_control\";|" /etc/shairport-sync.conf + sudo sed -i "s|^[[:space:]]*mixer_control_name = .*| mixer_control_name = \"$mixer_control\";|" /etc/shairport-sync.conf + # Also set mixer_device if needed (usually commented out by default) + sudo sed -i "s|^//[[:space:]]*mixer_device = .*| mixer_device = \"hw:$card_number\";|" /etc/shairport-sync.conf + sudo sed -i "s|^[[:space:]]*mixer_device = .*| mixer_device = \"hw:$card_number\";|" /etc/shairport-sync.conf + fi + + # Set output format + sudo sed -i "s|^//[[:space:]]*output_rate = .*| output_rate = \"auto\";|" /etc/shairport-sync.conf + sudo sed -i "s|^[[:space:]]*output_rate = .*| output_rate = \"auto\";|" /etc/shairport-sync.conf + sudo sed -i "s|^//[[:space:]]*output_format = .*| output_format = \"S16\";|" /etc/shairport-sync.conf + sudo sed -i "s|^[[:space:]]*output_format = .*| output_format = \"S16\";|" /etc/shairport-sync.conf + + # Set volume settings + sudo sed -i "s|^//[[:space:]]*volume_max_db = .*| volume_max_db = 4.0;|" /etc/shairport-sync.conf + sudo sed -i "s|^//[[:space:]]*default_airplay_volume = .*| default_airplay_volume = -6.0;|" /etc/shairport-sync.conf + sudo sed -i "s|^//[[:space:]]*high_volume_idle_timeout_in_minutes = .*| high_volume_idle_timeout_in_minutes = 1;|" /etc/shairport-sync.conf + + # Verify config file was created + if [ ! -f /etc/shairport-sync.conf ]; then + cecho "red" "❌ Configuration file was not created" + exit 1 + fi + + cecho "green" "✓ Configuration file created and customized" + + # Set mixer volume to maximum if available + if [ -n "$mixer_control" ]; then + cecho "blue" "Setting mixer volume to 100%..." + if amixer -c "$card_number" set "$mixer_control" 100% unmute > /dev/null 2>&1; then + sudo alsactl store > /dev/null 2>&1 || true + cecho "green" "✓ Mixer volume set to maximum" + else + cecho "yellow" "⚠ Could not set mixer volume (may not be supported)" + fi + fi + echo + + # --- Create/Update Systemd Service --- + cecho "blue" "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + cecho "blue" " Setting Up Auto-Start Service..." + cecho "blue" "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + log "Creating shairport-sync user and group..." + + # Create user and group for shairport-sync service + if ! getent group shairport-sync >/dev/null 2>&1; then + sudo groupadd -r shairport-sync + log "Created shairport-sync group" + fi + + if ! getent passwd shairport-sync >/dev/null 2>&1; then + sudo useradd -r -M -g shairport-sync -s /usr/sbin/nologin -G audio shairport-sync + log "Created shairport-sync user" + fi + + log "Creating systemd service manually (make install sometimes fails at this step)..." + + # Create systemd service file manually - this is more reliable than 'make install' + # which often fails on the systemd service installation step on Raspberry Pi + sudo tee /lib/systemd/system/shairport-sync.service > /dev/null <&1 | tee -a "$LOG_FILE" + sudo systemctl restart shairport-sync 2>&1 | tee -a "$LOG_FILE" + sleep 5 + + if ! check_service "shairport-sync" 5; then + cecho "red" "❌ Shairport-Sync service failed to start" + cecho "yellow" "Checking service status..." + sudo systemctl status shairport-sync --no-pager -l | tail -20 + exit 1 + fi + + # Verify avahi-daemon is running (required for AirPlay discovery) + cecho "blue" "Checking Avahi daemon (required for device discovery)..." + if ! systemctl is-active --quiet avahi-daemon; then + cecho "yellow" "⚠ Avahi daemon is not running, attempting to start..." + sudo systemctl start avahi-daemon + sleep 2 + if systemctl is-active --quiet avahi-daemon; then + cecho "green" "✓ Avahi daemon started" + else + cecho "red" "❌ Avahi daemon failed to start - AirPlay device may not be discoverable" + fi + else + cecho "green" "✓ Avahi daemon is running" + fi + echo + + # --- Wi-Fi Power Management Instructions --- + if [ "$disable_wifi_pm" = true ]; then + cecho "blue" "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + cecho "blue" " Wi-Fi Power Management" + cecho "blue" "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + log "User requested Wi-Fi power management disable" + + cecho "yellow" "📝 Manual Wi-Fi Power Management Configuration Needed:" + echo + cecho "blue" "After installation completes, disable Wi-Fi power saving to prevent" + cecho "blue" "audio dropouts. You have two options:" + echo + cecho "green" "Option 1: Using raspi-config (Recommended)" + cecho "blue" " 1. Run: sudo raspi-config" + cecho "blue" " 2. Go to: Performance Options → Wireless LAN → Power Management" + cecho "blue" " 3. Select: Disable" + echo + cecho "green" "Option 2: Manual command" + cecho "blue" " Run: sudo iw dev wlan0 set power_save off" + cecho "blue" " (Note: This is temporary, resets on reboot)" + echo + cecho "yellow" "⚠ We're not doing this automatically to avoid disconnecting your SSH session" + log "Wi-Fi power management instructions provided to user" + echo + fi + + # --- Configure Firewall (if active) --- + if command_exists ufw && sudo ufw status | grep -q "Status: active"; then + cecho "blue" "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + cecho "blue" " Configuring Firewall..." + cecho "blue" "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + + # Allow mDNS for AirPlay discovery + sudo ufw allow 5353/udp comment 'mDNS for AirPlay' 2>&1 | tee -a "$LOG_FILE" + # Allow NQPTP ports + sudo ufw allow 319/udp comment 'NQPTP PTP' 2>&1 | tee -a "$LOG_FILE" + sudo ufw allow 320/udp comment 'NQPTP PTP' 2>&1 | tee -a "$LOG_FILE" + # AirPlay ports + sudo ufw allow 7000/tcp comment 'AirPlay' 2>&1 | tee -a "$LOG_FILE" + + cecho "green" "✓ Firewall rules added" + echo + fi + + # --- Audio Test --- + cecho "blue" "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + cecho "blue" " Testing Audio Output..." + cecho "blue" "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo + + read -p "Do you want to test audio output? (Y/n): " test_audio || true + if [[ -z "$test_audio" || "$test_audio" =~ ^[Yy]$ ]]; then + cecho "yellow" "Playing test sound in 2 seconds..." + cecho "yellow" "(You should hear a voice saying 'Front Left', 'Front Right')" + sleep 2 + + if timeout 10 speaker-test -D "$audio_device_plug" -c 2 -t wav -l 1 > /dev/null 2>&1; then + echo + cecho "green" "✓ Audio test completed!" + read -p "Did you hear the test sound? (y/N): " heard_sound || true + if [[ ! "$heard_sound" =~ ^[Yy]$ ]]; then + cecho "yellow" "⚠ If you didn't hear sound, check:" + cecho "yellow" " - Speaker/headphone connections" + cecho "yellow" " - Volume level on your amplifier/speakers" + cecho "yellow" " - USB DAC power" + fi + else + cecho "yellow" "⚠ Audio test couldn't run, but setup is complete." + cecho "yellow" " You can test manually with: speaker-test -D $audio_device_plug -c 2 -t wav" + fi + fi + echo + + # --- Cleanup --- + cecho "blue" "Cleaning up temporary files..." + rm -rf /tmp/nqptp /tmp/shairport-sync 2>/dev/null || true + rm -f "${LOG_FILE}.make" 2>/dev/null || true + cecho "green" "✓ Cleanup complete" + echo + + INSTALLATION_FAILED=0 # Installation succeeded + + # --- Success Message --- + cecho "green" "╔═════════════════════════════════════════════════════╗" + cecho "green" "║ ║" + cecho "green" "║ ✅ INSTALLATION COMPLETE! ✅ ║" + cecho "green" "║ ║" + cecho "green" "╚═════════════════════════════════════════════════════╝" + echo + log "=== Installation completed successfully ===" + + cecho "magenta" "🎵 Your AirPlay 2 device is ready!" + echo + cecho "yellow" " 📱 Device Name: $airplay_name" + cecho "yellow" " 🔊 Audio Output: $audio_device_plug" + cecho "yellow" " 🎚️ Volume: ${mixer_control:-Fixed (no hardware control)}" + echo + cecho "blue" "┌─────────────────────────────────────────────────────┐" + cecho "blue" "│ How to use: │" + cecho "blue" "│ 1. Open Music/Spotify/YouTube on your iPhone/iPad │" + cecho "blue" "│ 2. Tap the AirPlay icon (📡) │" + cecho "blue" "│ 3. Select '$airplay_name' │" + cecho "blue" "│ 4. Enjoy high-quality wireless audio! │" + cecho "blue" "└─────────────────────────────────────────────────────┘" + echo + cecho "yellow" "💡 Tips:" + cecho "yellow" " • Device should appear within 30 seconds after reboot" + cecho "yellow" " • Make sure iPhone and Pi are on the same Wi-Fi network" + cecho "yellow" " • For best quality, use lossless audio sources" + if [ "$disable_wifi_pm" = true ]; then + echo + cecho "yellow" "📝 IMPORTANT - After reboot:" + cecho "yellow" " Don't forget to disable Wi-Fi power management using raspi-config!" + cecho "yellow" " This prevents audio dropouts and stuttering." + fi + echo + cecho "blue" "📋 Useful commands:" + cecho "blue" " View live logs: sudo journalctl -u shairport-sync -f" + cecho "blue" " Restart service: sudo systemctl restart shairport-sync" + cecho "blue" " Check status: sudo systemctl status shairport-sync" + cecho "blue" " Edit config: sudo nano /etc/shairport-sync.conf" + cecho "blue" " Installation log: $LOG_FILE" + echo + + read -p "Press Enter to reboot now (recommended), or Ctrl+C to reboot later..." || { + echo + cecho "yellow" "Reboot cancelled. Remember to reboot later with: sudo reboot" + exit 0 + } + + log "User initiated reboot" + cecho "yellow" "Rebooting in 3 seconds..." + sleep 3 + sudo reboot +} + +# --- Script Entry Point --- +main "$@"