From 11a8d0e98c6bc170c8d402c44e613f30e7648884 Mon Sep 17 00:00:00 2001 From: ermannonicoletti Date: Mon, 18 May 2026 20:14:58 +0200 Subject: [PATCH 1/4] Expand audio device support: added compatibility for built-in audio and HATs, improved device selection UI. --- .../install_airplay_v3.sh | 68 +++++++++---------- 1 file changed, 32 insertions(+), 36 deletions(-) diff --git a/RaspberryPi-AirPlay-Installer-Scripts/install_airplay_v3.sh b/RaspberryPi-AirPlay-Installer-Scripts/install_airplay_v3.sh index 6b9cbee..63f9420 100644 --- a/RaspberryPi-AirPlay-Installer-Scripts/install_airplay_v3.sh +++ b/RaspberryPi-AirPlay-Installer-Scripts/install_airplay_v3.sh @@ -3,7 +3,8 @@ # =================================================================================== # Shairport-Sync AirPlay 2 ROBUST Installer - ENHANCED VERSION 3.0 # -# Tailored for: Raspberry Pi (Zero 2/3/4/5) with USB DAC +# Tailored for: Raspberry Pi (Zero 2/3/4/5) with USB DAC, audio HAT +# or built-in audio (3.5mm jack / HDMI) # Version: 3.0 - Production Ready # Features: # - Comprehensive error handling with rollback capability @@ -299,60 +300,55 @@ select_audio_device() { if [ -z "$all_cards" ]; then cecho "red" "❌ No audio devices detected at all!" - cecho "yellow" " Make sure your USB DAC is properly connected." - cecho "yellow" " Try: lsusb (to check if USB device is recognized)" + cecho "yellow" " Make sure your audio output (USB DAC, HAT or built-in) is enabled." + cecho "yellow" " Try: lsusb (to check if a USB device is recognized)" + cecho "yellow" " Or: sudo raspi-config (to enable built-in audio)" exit 1 fi - # Show all detected devices - cecho "green" "Found these audio devices:" - echo "$all_cards" | nl -w2 -s'. ' - echo - - # Filter out built-in audio (bcm2835, Headphones, vc4-hdmi) - mapfile -t external_devices < <(echo "$all_cards" | grep -iv 'bcm2835\|Headphones\|vc4-hdmi' || true) - - if [ ${#external_devices[@]} -eq 0 ]; then - cecho "yellow" "⚠ No external USB DAC detected!" - cecho "yellow" " Only built-in audio found." - echo - read -p "Do you want to use built-in audio? (y/N): " use_builtin || true + # Build the full list of available devices (built-in audio included) + mapfile -t all_devices < <(echo "$all_cards") - if [[ "$use_builtin" =~ ^[Yy]$ ]]; then - mapfile -t external_devices < <(echo "$all_cards") + # Mark built-in devices so the user can recognise them in the menu + local device_labels=() + local i + for i in "${!all_devices[@]}"; do + local label="${all_devices[$i]}" + if echo "$label" | grep -qi 'bcm2835\|Headphones\|vc4-hdmi'; then + label="$label [built-in]" else - cecho "yellow" " Please:" - cecho "yellow" " 1. Connect your USB DAC" - cecho "yellow" " 2. Wait 5 seconds" - cecho "yellow" " 3. Run this script again" - exit 1 + label="$label [external/DAC]" fi - fi + device_labels+=("$label") + done - # Auto-select if only one device - if [ ${#external_devices[@]} -eq 1 ]; then + # Auto-select if only one device is available + if [ ${#all_devices[@]} -eq 1 ]; then cecho "green" "✓ Found one audio device, auto-selecting:" - cecho "magenta" " → ${external_devices[0]}" - selected_device="${external_devices[0]}" + cecho "magenta" " → ${device_labels[0]}" + selected_device="${all_devices[0]}" else - # Multiple devices - let user choose - cecho "yellow" "Found ${#external_devices[@]} audio devices:" - for i in "${!external_devices[@]}"; do - echo " [$i] ${external_devices[$i]}" + cecho "yellow" "Found ${#all_devices[@]} audio devices:" + for i in "${!device_labels[@]}"; do + echo " [$i] ${device_labels[$i]}" done echo + cecho "blue" "You can select either a USB DAC / HAT or the Raspberry Pi's built-in audio" + cecho "blue" "(3.5mm jack / HDMI). Pick the one connected to your speakers/amplifier." + echo local device_choice while true; do - read -p "Enter the number [0-$((${#external_devices[@]}-1))]: " device_choice || true + read -p "Enter the number [0-$((${#all_devices[@]}-1))]: " device_choice || true - if [[ "$device_choice" =~ ^[0-9]+$ ]] && [ "$device_choice" -lt "${#external_devices[@]}" ]; then + if [[ "$device_choice" =~ ^[0-9]+$ ]] && [ "$device_choice" -lt "${#all_devices[@]}" ]; then break fi cecho "red" "Invalid selection. Please try again." done - selected_device="${external_devices[$device_choice]}" + selected_device="${all_devices[$device_choice]}" + cecho "green" "✓ Selected: ${device_labels[$device_choice]}" fi # Extract card and device numbers more reliably @@ -486,7 +482,7 @@ main() { clear cecho "green" "╔═════════════════════════════════════════════════════╗" cecho "green" "║ ║" - cecho "green" "║ AirPlay 2 Installer for Raspberry Pi + DAC ║" + cecho "green" "║ AirPlay 2 Installer for Raspberry Pi ║" cecho "green" "║ Version $SCRIPT_VERSION ║" cecho "green" "║ ║" cecho "green" "╚═════════════════════════════════════════════════════╝" From 9686254587eecd9c168b55f2af6491cc54444193 Mon Sep 17 00:00:00 2001 From: ermannonicoletti Date: Mon, 18 May 2026 20:27:05 +0200 Subject: [PATCH 2/4] Add uninstall, modify, and manager scripts for AirPlay setup. Added libraries for trixie --- README.md | 167 ++++++-- .../airplay_manager.sh | 117 ++++++ .../install_airplay_v3.sh | 2 +- .../modify_airplay.sh | 358 ++++++++++++++++++ .../uninstall_airplay.sh | 170 +++++++++ 5 files changed, 774 insertions(+), 40 deletions(-) create mode 100755 RaspberryPi-AirPlay-Installer-Scripts/airplay_manager.sh mode change 100644 => 100755 RaspberryPi-AirPlay-Installer-Scripts/install_airplay_v3.sh create mode 100755 RaspberryPi-AirPlay-Installer-Scripts/modify_airplay.sh create mode 100755 RaspberryPi-AirPlay-Installer-Scripts/uninstall_airplay.sh diff --git a/README.md b/README.md index 23015ac..143278f 100644 --- a/README.md +++ b/README.md @@ -1,83 +1,172 @@ # RaspberryPi-AirPlay-Installer 📻 -Turn any Raspberry Pi (Zero 2 W, 3, 4, 5) into a modern, high-quality AirPlay 2 receiver in just 5 minutes. This project uses a set of robust scripts to automate the entire installation process, making it incredibly easy to revive your old home theater or favorite speakers. +Turn any Raspberry Pi (Zero 2 W, 3, 4, 5) into a modern, high-quality **AirPlay 2** receiver in just a few minutes. This project automates the entire build of [Shairport-Sync](https://github.com/mikebrady/shairport-sync) + [NQPTP](https://github.com/mikebrady/nqptp) with a set of robust, interactive scripts. -> **If you find this project helpful, please consider giving it a ⭐ star on GitHub!** It helps others discover it and shows your appreciation for the work. Also, please like the video and **[subscribe to the channel](https://www.youtube.com/@ravis1ngh)**. It helps us create more content like this. +> **If you find this project helpful, please consider giving it a ⭐ star on GitHub!** -The goal of this project was to simplify the manual installation process, making it accessible to everyone. +--- + +## ✨ Features -| The Old, Manual Way (40+ Minutes) | The New, Automated Way (5 Minutes!) | -| :---: | :---: | -| [![Manual AirPlay 2 Pi Setup](https://img.youtube.com/vi/WeibcfMywXU/0.jpg)](https://www.youtube.com/watch?v=WeibcfMywXU) | **[Link to New Video Coming Soon!]**
*(Placeholder for your new, shorter video)* | +* **🚀 Fast setup** — From a fresh Raspberry Pi OS to a working AirPlay 2 speaker in minutes. +* **🤖 Fully automated** — Handles system update, dependencies, compiling and configuration. +* **✅ Smart pre-flight checks** — Validates internet, disk space, memory and detected hardware before changing anything. +* **🔊 Flexible audio** — Works with **USB DAC**, **audio HAT**, or the **Raspberry Pi's built-in audio** (3.5mm jack / HDMI). All detected devices are listed and labelled `[built-in]` / `[external/DAC]`. +* **🛠️ Idempotent management** — Dedicated scripts to **modify** or **uninstall** an existing installation without reinstalling from scratch. +* **🎚️ Volume control aware** — Auto-selects the best ALSA mixer (`PCM`, `Master`, `Speaker`, ...) and falls back to software volume if no hardware mixer is available. +* **🔁 Rollback on failure** — Backs up configuration files and cleans up on failed installs. +* **📋 Detailed logging** — Every installation writes a timestamped log under `/tmp/airplay_install_*.log`. --- -### ✨ Features +## 🧰 Hardware Requirements + +| Component | Recommended | +| --- | --- | +| Raspberry Pi | Zero 2 W, 3, 4 or 5 | +| MicroSD card | Quality card, ≥ 8 GB | +| Power supply | Official PSU for your Pi | +| Audio output | USB DAC, audio HAT **or** built-in 3.5mm / HDMI | -* **🚀 5-Minute Setup:** Go from a fresh Raspberry Pi OS to a working AirPlay 2 speaker in minutes. -* **🤖 Fully Automated:** The script handles system updates, dependency installation, compiling, and configuration. -* **✅ Smart Pre-Checks:** A pre-installation script verifies your system is ready, checking for internet, disk space, and audio devices to prevent errors. -* **🔌 USB DAC Auto-Detection:** Intelligently finds your external USB sound card and lets you choose the correct one if you have multiple. -* **⚙️ Optimized for Performance:** Automatically configures settings for the best audio quality and prompts to disable Wi-Fi power saving to prevent dropouts. -* **🛠️ Robust & Reliable:** Includes error handling and detailed logging for easy troubleshooting. +> The older Pi 1 / Pi Zero W are not officially supported — they typically lack the CPU headroom for AirPlay 2. --- -### Hardware Requirements +## 📦 What's in the box -* **Raspberry Pi:** A Pi Zero 2 W, 3, 4, or 5 is recommended. -* **MicroSD Card:** A quality card with at least 8GB. -* **Power Supply:** The official power supply for your Pi model. -* **Audio Output:** - * For Pi Zero: An **OTG cable** and a **USB DAC** with a 3.5mm output. - * For Pi 3/4/5: The built-in 3.5mm jack or an optional USB DAC. +All scripts live under `RaspberryPi-AirPlay-Installer-Scripts/`: + +| Script | Purpose | +| --- | --- | +| `pre_check_airplay_on_pi.sh` | Read-only system check before installing. | +| `install_airplay_v3.sh` | Main installer: deps, build, service, config. | +| `modify_airplay.sh` | Edit an existing install (name, audio device, mixer, volume...). | +| `uninstall_airplay.sh` | Cleanly remove Shairport-Sync, NQPTP, services and config. | +| `airplay_manager.sh` | Unified menu that dispatches to the three scripts above. | --- -### 🚀 Quick Start Installation +## 🚀 Quick Start + +After flashing **Raspberry Pi OS** (Lite is fine) and connecting via SSH, you have two options. + +### Option A — Run from this repo (recommended for development) + +```bash +git clone https://github.com/Techposts/RaspberryPi-AirPlay-Installer.git +cd RaspberryPi-AirPlay-Installer/RaspberryPi-AirPlay-Installer-Scripts +bash airplay_manager.sh # unified menu +``` + +The menu lets you install, modify or uninstall, and tail live service logs. -After installing Raspberry Pi OS Lite and connecting to your Pi via SSH, run this single command. It will download the pre-check script and, if successful, automatically launch the main installer. +### Option B — One-shot install from upstream ```bash curl -sSL https://raw.githubusercontent.com/Techposts/RaspberryPi-AirPlay-Installer/main/RaspberryPi-AirPlay-Installer-Scripts/pre_check_airplay_on_pi.sh | bash -curl -sSl https://raw.githubusercontent.com/Techposts/RaspberryPi-AirPlay-Installer/main/RaspberryPi-AirPlay-Installer-Scripts/install_airplay_v3.sh | bash +curl -sSL https://raw.githubusercontent.com/Techposts/RaspberryPi-AirPlay-Installer/main/RaspberryPi-AirPlay-Installer-Scripts/install_airplay_v3.sh | bash ``` -The script is interactive and will guide you through the process. Once finished, it will reboot, and your AirPlay 2 receiver will be ready to use! +The installer is interactive: it will ask you to pick the audio device, give your AirPlay endpoint a name and decide on Wi-Fi power management. When it's done, the Pi will offer to reboot and your speaker is ready. + +> **Do not run any of these scripts with `sudo`.** They invoke `sudo` only where needed and will refuse to start as `root`. --- -### ✅ The Final Result +## 🎛️ Modifying an existing installation + +Need to rename the speaker, change the audio output or adjust volume limits? You don't have to reinstall. + +```bash +bash modify_airplay.sh +``` + +The interactive menu provides: -When you're done, your setup will be seamless. Your Raspberry Pi will appear as a native AirPlay device on your network, ready to stream from any Apple device. +1. Change AirPlay name +2. Change audio output device (USB DAC / HAT / built-in) +3. Change mixer / hardware volume control (or disable it) +4. Change volume limits (`volume_max_db`, `default_airplay_volume`) +5. Test audio output +6. View current configuration +7. Show service status +8. Restart service +9. Edit `/etc/shairport-sync.conf` manually (+ auto restart) -| Mobile Screenshot | Hardware Setup | -| :---: | :---: | -| ****
*Your new device, ready to connect.* | ****
*The simple and clean hardware setup.* | +All changes are written to `/etc/shairport-sync.conf` and the service is restarted automatically. --- -### How It Works +## 🧹 Uninstalling + +```bash +bash uninstall_airplay.sh +``` + +Removes: -This project uses a two-script system for a safe and reliable installation: +* `shairport-sync` and `nqptp` binaries +* `/etc/shairport-sync.conf` and sample +* systemd units (`shairport-sync.service`, `nqptp.service`) +* The `shairport-sync` user and group +* UFW firewall rules added during install (`5353/udp`, `319/udp`, `320/udp`, `7000/tcp`) -1. **`pre_check_airplay_on_pi.sh`:** A non-invasive script that checks your system for common issues without making any changes. If all checks pass, it automatically downloads and runs the main installer. -2. **`install_airplay_v3.sh`:** The powerful main installer that performs all the required actions to build and configure the AirPlay 2 software (`Shairport-Sync` and `nqptp`). +A backup of the current config is saved under `/tmp/airplay_uninstall_backup_/` before anything is deleted. + +> APT build dependencies (`libsoxr-dev`, `libplist-dev`, ...) are intentionally **not** removed — other software on your system may rely on them. The uninstaller prints the `apt-get` command to remove them manually if you want a fully clean state. --- -### ❤️ Support the Project +## 🐛 Troubleshooting + +**`configure: error: plistutil can not be found`** (Debian 13 "Trixie" / Pi OS Bookworm successor) + +On recent releases the `plistutil` binary moved from `libplist-dev` to a separate package `libplist-utils`. The installer in this repo already pulls it in. If you hit this on an older copy: + +```bash +sudo apt-get install -y libplist-utils +bash install_airplay_v3.sh +``` + +**The Pi doesn't appear in the AirPlay picker** + +* Make sure iPhone/iPad and Pi are on the **same Wi-Fi network and same subnet**. +* Check that `avahi-daemon` is running: `systemctl status avahi-daemon`. +* Tail the service: `sudo journalctl -u shairport-sync -f`. -If this installer helped you bring your old speakers back to life, please consider showing your support! +**Audio stutters / drops out** -* **⭐ Star the Repository:** Starring this project on GitHub is a great way to show your appreciation and helps others find it. -* **👍 Like & Subscribe:** If you came from the video tutorial, please **like the video** and **[subscribe to the channel](https://www.youtube.com/@ravis1ngh)**. It helps us create more content like this. +* Disable Wi-Fi power management: `sudo raspi-config` → Performance → Wireless LAN → Power Management → Disable. +* On Pi Zero 2, prefer a wired ethernet adapter or stay close to the access point. + +**Useful one-liners** + +```bash +sudo systemctl status shairport-sync # service health +sudo journalctl -u shairport-sync -f # live logs +sudo nano /etc/shairport-sync.conf # manual edit (then restart) +sudo systemctl restart shairport-sync +``` --- +## ⚙️ How it works -### License +1. **`pre_check_airplay_on_pi.sh`** — non-invasive system check (no changes made). +2. **`install_airplay_v3.sh`** — installs build deps, clones and compiles `nqptp` and `shairport-sync` with `--with-airplay-2`, writes `/etc/shairport-sync.conf`, creates a systemd service and a dedicated user, configures UFW if active. +3. **`modify_airplay.sh`** — edits `/etc/shairport-sync.conf` in place via targeted `sed` rules and restarts the service. +4. **`uninstall_airplay.sh`** — reverses everything the installer did, in dependency-safe order. +5. **`airplay_manager.sh`** — thin wrapper that picks the right script based on what's currently installed. -This project is licensed under the MIT License. See the `LICENSE` file for details. +--- + +## 🙏 Credits +* [Mike Brady](https://github.com/mikebrady) — author of Shairport-Sync and NQPTP, the upstream projects that make all of this possible. +* Original installer scripts: [Techposts/RaspberryPi-AirPlay-Installer](https://github.com/Techposts/RaspberryPi-AirPlay-Installer). +--- + +## 📜 License + +This project is licensed under the MIT License. See the `LICENSE` file for details. diff --git a/RaspberryPi-AirPlay-Installer-Scripts/airplay_manager.sh b/RaspberryPi-AirPlay-Installer-Scripts/airplay_manager.sh new file mode 100755 index 0000000..e427dac --- /dev/null +++ b/RaspberryPi-AirPlay-Installer-Scripts/airplay_manager.sh @@ -0,0 +1,117 @@ +#!/bin/bash + +# =================================================================================== +# AirPlay 2 Manager — Unified menu for install / modify / uninstall +# +# Dispatches to the dedicated scripts in the same directory: +# install_airplay_v3.sh — First-time installation +# modify_airplay.sh — Modify existing installation +# uninstall_airplay.sh — Remove the installation +# =================================================================================== + +set -eo pipefail +IFS=$'\n\t' + +SCRIPT_VERSION="1.0" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +cecho() { + local code="\033[" + local color + 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" ;; + "cyan") color="${code}1;36m" ;; + *) color="${code}0m" ;; + esac + echo -e "${color}$2\033[0m" +} + +run_script() { + local script_name="$1" + local script_path="$SCRIPT_DIR/$script_name" + if [ ! -f "$script_path" ]; then + cecho "red" "❌ Script not found: $script_path" + read -p "Press Enter to continue..." || true + return 1 + fi + echo + bash "$script_path" || true + echo + read -p "Press Enter to return to the menu..." || true +} + +is_installed() { + [ -f /etc/shairport-sync.conf ] && command -v shairport-sync >/dev/null 2>&1 +} + +service_status_line() { + if systemctl is-active --quiet shairport-sync 2>/dev/null; then + echo "active" + elif systemctl list-unit-files 2>/dev/null | grep -q '^shairport-sync\.service'; then + echo "inactive" + else + echo "not registered" + fi +} + +current_name() { + [ -f /etc/shairport-sync.conf ] || { echo ""; return; } + grep -oE '^[[:space:]]*name[[:space:]]*=[[:space:]]*"[^"]*"' /etc/shairport-sync.conf 2>/dev/null \ + | head -1 | sed -E 's/.*"([^"]*)".*/\1/' || true +} + +while true; do + clear + cecho "green" "╔═════════════════════════════════════════════════════╗" + cecho "green" "║ AirPlay 2 Manager (Raspberry Pi) v$SCRIPT_VERSION ║" + cecho "green" "╚═════════════════════════════════════════════════════╝" + echo + if is_installed; then + cecho "green" " ✓ Shairport-Sync installed" + cecho "blue" " Service: $(service_status_line)" + cecho "blue" " AirPlay name: $(current_name)" + else + cecho "yellow" " ⚠ Shairport-Sync NOT installed." + fi + echo + echo " 1) Install AirPlay 2" + echo " 2) Modify existing installation" + echo " 3) Uninstall" + echo " 4) Show service logs (live, Ctrl+C to exit)" + echo " 0) Exit" + echo + read -p "Choose: " choice || true + case "$choice" in + 1) run_script "install_airplay_v3.sh" ;; + 2) + if ! is_installed; then + cecho "red" "❌ No installation detected. Install first." + read -p "Press Enter..." || true + continue + fi + run_script "modify_airplay.sh" + ;; + 3) + if ! is_installed; then + cecho "red" "❌ No installation detected to uninstall." + read -p "Press Enter..." || true + continue + fi + run_script "uninstall_airplay.sh" + ;; + 4) + if ! is_installed; then + cecho "red" "❌ No installation detected." + read -p "Press Enter..." || true + continue + fi + sudo journalctl -u shairport-sync -f || true + ;; + 0|q|Q|"") cecho "blue" "Bye!"; exit 0 ;; + *) cecho "red" "Invalid choice."; sleep 1 ;; + esac +done diff --git a/RaspberryPi-AirPlay-Installer-Scripts/install_airplay_v3.sh b/RaspberryPi-AirPlay-Installer-Scripts/install_airplay_v3.sh old mode 100644 new mode 100755 index 63f9420..8ea80d7 --- a/RaspberryPi-AirPlay-Installer-Scripts/install_airplay_v3.sh +++ b/RaspberryPi-AirPlay-Installer-Scripts/install_airplay_v3.sh @@ -576,7 +576,7 @@ main() { 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 + libsoxr-dev libplist-dev libplist-utils libsodium-dev libavutil-dev libavcodec-dev libavformat-dev uuid-dev libgcrypt20-dev xxd alsa-utils ) diff --git a/RaspberryPi-AirPlay-Installer-Scripts/modify_airplay.sh b/RaspberryPi-AirPlay-Installer-Scripts/modify_airplay.sh new file mode 100755 index 0000000..73f2ae7 --- /dev/null +++ b/RaspberryPi-AirPlay-Installer-Scripts/modify_airplay.sh @@ -0,0 +1,358 @@ +#!/bin/bash + +# =================================================================================== +# Shairport-Sync AirPlay 2 - Configuration Modifier +# +# Modify an existing AirPlay 2 installation (name, audio device, mixer, volume...) +# without reinstalling. Designed to work with installs done by install_airplay_v3.sh. +# =================================================================================== + +set -eo pipefail +IFS=$'\n\t' + +SCRIPT_VERSION="1.0" +CONFIG_FILE="/etc/shairport-sync.conf" +SERVICE_NAME="shairport-sync" + +# --- Helpers --- +cecho() { + local code="\033[" + local color + 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" ;; + "cyan") color="${code}1;36m" ;; + *) color="${code}0m" ;; + esac + echo -e "${color}$2\033[0m" +} + +require_install() { + if [ ! -f "$CONFIG_FILE" ]; then + cecho "red" "❌ Configuration file $CONFIG_FILE not found." + cecho "yellow" " Shairport-Sync does not appear to be installed." + cecho "yellow" " Run install_airplay_v3.sh first." + exit 1 + fi + if ! command -v shairport-sync >/dev/null 2>&1; then + cecho "red" "❌ shairport-sync binary not found in PATH." + cecho "yellow" " Run install_airplay_v3.sh first." + exit 1 + fi +} + +require_sudo() { + if [ "$EUID" -eq 0 ]; then + cecho "red" "❌ Don't run this script with sudo or as root." + cecho "yellow" " Just run: bash modify_airplay.sh" + exit 1 + fi + if ! sudo -n true 2>/dev/null; then + cecho "yellow" "Checking sudo access..." + sudo true || { cecho "red" "Sudo required."; exit 1; } + fi +} + +restart_service() { + cecho "blue" "Restarting $SERVICE_NAME..." + if sudo systemctl restart "$SERVICE_NAME"; then + sleep 2 + if systemctl is-active --quiet "$SERVICE_NAME"; then + cecho "green" "✓ $SERVICE_NAME is running" + else + cecho "red" "✗ $SERVICE_NAME is not active after restart" + sudo systemctl status "$SERVICE_NAME" --no-pager -l | tail -20 + fi + else + cecho "red" "✗ Failed to restart $SERVICE_NAME (check config syntax)" + sudo systemctl status "$SERVICE_NAME" --no-pager -l | tail -20 + fi +} + +# --- Current value readers (best-effort, tolerate missing keys) --- +current_name() { + grep -oE '^[[:space:]]*name[[:space:]]*=[[:space:]]*"[^"]*"' "$CONFIG_FILE" 2>/dev/null \ + | head -1 | sed -E 's/.*"([^"]*)".*/\1/' || true +} + +current_output_device() { + grep -oE '^[[:space:]]*output_device[[:space:]]*=[[:space:]]*"[^"]*"' "$CONFIG_FILE" 2>/dev/null \ + | head -1 | sed -E 's/.*"([^"]*)".*/\1/' || true +} + +current_mixer() { + grep -oE '^[[:space:]]*mixer_control_name[[:space:]]*=[[:space:]]*"[^"]*"' "$CONFIG_FILE" 2>/dev/null \ + | head -1 | sed -E 's/.*"([^"]*)".*/\1/' || true +} + +# --- Actions --- +action_change_name() { + local cur new_name + cur=$(current_name) + cecho "blue" "Current AirPlay name: ${cur:-}" + echo + read -p "Enter new name (empty to cancel): " new_name || true + if [ -z "$new_name" ]; then + cecho "yellow" "Cancelled." + return + fi + new_name=$(echo "$new_name" | sed 's/[^a-zA-Z0-9 _-]//g') + if [ -z "$new_name" ]; then + cecho "red" "Name became empty after sanitization. Cancelled." + return + fi + sudo sed -i -E "s|^[[:space:]]*(//[[:space:]]*)?name[[:space:]]*=.*| name = \"$new_name\";|" "$CONFIG_FILE" + cecho "green" "✓ AirPlay name updated to '$new_name'" + restart_service +} + +action_change_audio_device() { + cecho "blue" "Scanning for audio devices..." + local all_cards + all_cards=$(aplay -l 2>/dev/null | grep '^card' || true) + if [ -z "$all_cards" ]; then + cecho "red" "❌ No audio devices detected." + return + fi + + local all_devices device_labels=() i + mapfile -t all_devices < <(echo "$all_cards") + for i in "${!all_devices[@]}"; do + local label="${all_devices[$i]}" + if echo "$label" | grep -qi 'bcm2835\|Headphones\|vc4-hdmi'; then + label="$label [built-in]" + else + label="$label [external/DAC]" + fi + device_labels+=("$label") + done + + cecho "yellow" "Available audio devices:" + for i in "${!device_labels[@]}"; do + echo " [$i] ${device_labels[$i]}" + done + echo + cecho "blue" "Current output_device: $(current_output_device)" + echo + + local choice + while true; do + read -p "Enter number [0-$((${#all_devices[@]}-1))] (empty=cancel): " choice || true + [ -z "$choice" ] && { cecho "yellow" "Cancelled."; return; } + if [[ "$choice" =~ ^[0-9]+$ ]] && [ "$choice" -lt "${#all_devices[@]}" ]; then + break + fi + cecho "red" "Invalid selection." + done + + local selected="${all_devices[$choice]}" + local card_number device_number + card_number=$(echo "$selected" | grep -oE 'card [0-9]+' | grep -oE '[0-9]+') + device_number=$(echo "$selected" | grep -oE 'device [0-9]+' | grep -oE '[0-9]+') + [ -z "$device_number" ] && device_number=0 + local audio_device_plug="plughw:$card_number,$device_number" + + sudo sed -i -E "s|^[[:space:]]*(//[[:space:]]*)?output_device[[:space:]]*=.*| output_device = \"$audio_device_plug\";|" "$CONFIG_FILE" + cecho "green" "✓ output_device set to $audio_device_plug" + + # Refresh mixer config to match the new card + local mixers=() + 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 mixer controls on card $card_number — disabling hardware mixer in config." + sudo sed -i -E "s|^[[:space:]]*(//[[:space:]]*)?mixer_control_name[[:space:]]*=.*|// mixer_control_name = \"PCM\";|" "$CONFIG_FILE" + sudo sed -i -E "s|^[[:space:]]*(//[[:space:]]*)?mixer_device[[:space:]]*=.*|// mixer_device = \"default\";|" "$CONFIG_FILE" + else + local mixer_control="" preferred m + for preferred in "PCM" "Master" "Speaker" "Headphone" "Digital"; do + for m in "${mixers[@]}"; do + if [[ "$m" == "$preferred" ]]; then + mixer_control="$m"; break 2 + fi + done + done + [ -z "$mixer_control" ] && mixer_control="${mixers[0]}" + cecho "green" "✓ mixer_control_name = $mixer_control (on hw:$card_number)" + sudo sed -i -E "s|^[[:space:]]*(//[[:space:]]*)?mixer_control_name[[:space:]]*=.*| mixer_control_name = \"$mixer_control\";|" "$CONFIG_FILE" + sudo sed -i -E "s|^[[:space:]]*(//[[:space:]]*)?mixer_device[[:space:]]*=.*| mixer_device = \"hw:$card_number\";|" "$CONFIG_FILE" + fi + + restart_service +} + +action_change_mixer() { + local cur_out + cur_out=$(current_output_device) + if [ -z "$cur_out" ]; then + cecho "yellow" "No output_device configured yet. Change the audio device first." + return + fi + local card_number + card_number=$(echo "$cur_out" | grep -oE '[0-9]+' | head -1) + if [ -z "$card_number" ]; then + cecho "red" "Could not parse card number from current output_device: $cur_out" + return + fi + + local mixers=() + mapfile -t mixers < <(amixer -c "$card_number" scontrols 2>/dev/null | grep -oP "Simple mixer control '\K[^']+" || true) + cecho "blue" "Current mixer_control_name: $(current_mixer)" + echo + + if [ ${#mixers[@]} -eq 0 ]; then + cecho "yellow" "No mixer controls available on card $card_number." + local ans + read -p "Disable hardware mixer in config (use software volume)? (y/N): " ans || true + if [[ "$ans" =~ ^[Yy]$ ]]; then + sudo sed -i -E "s|^[[:space:]]*(//[[:space:]]*)?mixer_control_name[[:space:]]*=.*|// mixer_control_name = \"PCM\";|" "$CONFIG_FILE" + cecho "green" "✓ Hardware mixer disabled." + restart_service + fi + return + fi + + cecho "yellow" "Available mixer controls on card $card_number:" + local i + for i in "${!mixers[@]}"; do + echo " [$i] ${mixers[$i]}" + done + echo " [d] Disable hardware mixer (software volume only)" + echo + local choice + read -p "Choose [0-$((${#mixers[@]}-1))] / d / empty=cancel: " choice || true + if [ -z "$choice" ]; then + cecho "yellow" "Cancelled."; return + fi + if [ "$choice" = "d" ] || [ "$choice" = "D" ]; then + sudo sed -i -E "s|^[[:space:]]*(//[[:space:]]*)?mixer_control_name[[:space:]]*=.*|// mixer_control_name = \"PCM\";|" "$CONFIG_FILE" + cecho "green" "✓ Hardware mixer disabled." + restart_service + return + fi + if [[ "$choice" =~ ^[0-9]+$ ]] && [ "$choice" -lt "${#mixers[@]}" ]; then + local mixer_control="${mixers[$choice]}" + sudo sed -i -E "s|^[[:space:]]*(//[[:space:]]*)?mixer_control_name[[:space:]]*=.*| mixer_control_name = \"$mixer_control\";|" "$CONFIG_FILE" + sudo sed -i -E "s|^[[:space:]]*(//[[:space:]]*)?mixer_device[[:space:]]*=.*| mixer_device = \"hw:$card_number\";|" "$CONFIG_FILE" + cecho "green" "✓ Mixer set to $mixer_control" + restart_service + else + cecho "red" "Invalid selection." + fi +} + +action_change_volume_limits() { + cecho "blue" "Volume limits are expressed in dB (0 = max, negative attenuates)." + cecho "blue" "Examples: volume_max_db = 0 | -10 to cap max output" + cecho "blue" " default_airplay_volume = -6 (volume at first connection)" + echo + local vmax vdef + read -p "Enter volume_max_db (empty = skip): " vmax || true + read -p "Enter default_airplay_volume (empty = skip): " vdef || true + local changed=0 + if [ -n "$vmax" ]; then + if [[ "$vmax" =~ ^-?[0-9]+(\.[0-9]+)?$ ]]; then + sudo sed -i -E "s|^[[:space:]]*(//[[:space:]]*)?volume_max_db[[:space:]]*=.*| volume_max_db = ${vmax};|" "$CONFIG_FILE" + cecho "green" "✓ volume_max_db = $vmax" + changed=1 + else + cecho "red" "✗ '$vmax' is not a valid number, skipped." + fi + fi + if [ -n "$vdef" ]; then + if [[ "$vdef" =~ ^-?[0-9]+(\.[0-9]+)?$ ]]; then + sudo sed -i -E "s|^[[:space:]]*(//[[:space:]]*)?default_airplay_volume[[:space:]]*=.*| default_airplay_volume = ${vdef};|" "$CONFIG_FILE" + cecho "green" "✓ default_airplay_volume = $vdef" + changed=1 + else + cecho "red" "✗ '$vdef' is not a valid number, skipped." + fi + fi + if [ "$changed" -eq 1 ]; then + restart_service + else + cecho "yellow" "Nothing changed." + fi +} + +action_test_audio() { + local dev + dev=$(current_output_device) + if [ -z "$dev" ]; then + cecho "red" "No output_device configured." + return + fi + cecho "blue" "Stopping shairport-sync to free the audio device..." + sudo systemctl stop "$SERVICE_NAME" 2>/dev/null || true + sleep 1 + cecho "yellow" "Playing test sound on $dev..." + timeout 10 speaker-test -D "$dev" -c 2 -t wav -l 1 || true + cecho "blue" "Restarting service..." + sudo systemctl start "$SERVICE_NAME" || true +} + +action_view_config() { + cecho "blue" "Current configuration ($CONFIG_FILE):" + echo + cecho "yellow" " AirPlay name: $(current_name)" + cecho "yellow" " Output device: $(current_output_device)" + cecho "yellow" " Mixer control: $(current_mixer)" + echo + cecho "blue" "Active uncommented settings (first 50 lines):" + grep -vE '^[[:space:]]*//|^[[:space:]]*$|^[[:space:]]*#' "$CONFIG_FILE" | head -50 +} + +action_service_status() { + cecho "blue" "─── shairport-sync ───" + sudo systemctl status shairport-sync --no-pager -l | head -20 || true + echo + cecho "blue" "─── nqptp ───" + sudo systemctl status nqptp --no-pager -l | head -10 || true +} + +# --- Menu --- +main() { + require_install + require_sudo + while true; do + echo + cecho "magenta" "═══════════════════════════════════════════════════════" + cecho "magenta" " AirPlay 2 — Modify Existing Installation v$SCRIPT_VERSION" + cecho "magenta" "═══════════════════════════════════════════════════════" + echo + cecho "yellow" " Current name: $(current_name)" + cecho "yellow" " Current device: $(current_output_device)" + cecho "yellow" " Current mixer: $(current_mixer)" + echo + echo " 1) Change AirPlay name" + echo " 2) Change audio output device" + echo " 3) Change mixer / hardware volume control" + echo " 4) Change volume limits (volume_max_db, default_airplay_volume)" + echo " 5) Test audio output" + echo " 6) View configuration" + echo " 7) Show service status" + echo " 8) Restart service" + echo " 9) Edit configuration file manually (nano)" + echo " 0) Exit" + echo + local choice + read -p "Choose: " choice || true + case "$choice" in + 1) action_change_name ;; + 2) action_change_audio_device ;; + 3) action_change_mixer ;; + 4) action_change_volume_limits ;; + 5) action_test_audio ;; + 6) action_view_config ;; + 7) action_service_status ;; + 8) restart_service ;; + 9) sudo nano "$CONFIG_FILE" && restart_service ;; + 0|q|Q|"") cecho "blue" "Bye!"; return 0 ;; + *) cecho "red" "Invalid choice." ;; + esac + done +} + +main "$@" diff --git a/RaspberryPi-AirPlay-Installer-Scripts/uninstall_airplay.sh b/RaspberryPi-AirPlay-Installer-Scripts/uninstall_airplay.sh new file mode 100755 index 0000000..789788f --- /dev/null +++ b/RaspberryPi-AirPlay-Installer-Scripts/uninstall_airplay.sh @@ -0,0 +1,170 @@ +#!/bin/bash + +# =================================================================================== +# Shairport-Sync AirPlay 2 - Uninstaller +# +# Completely removes Shairport-Sync, NQPTP, configuration files, systemd services, +# the dedicated user/group and UFW firewall rules added by install_airplay_v3.sh. +# +# APT build dependencies are left installed (they may be in use by other software). +# =================================================================================== + +set -eo pipefail +IFS=$'\n\t' + +SCRIPT_VERSION="1.0" + +cecho() { + local code="\033[" + local color + 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" ;; + "cyan") color="${code}1;36m" ;; + *) color="${code}0m" ;; + esac + echo -e "${color}$2\033[0m" +} + +if [ "$EUID" -eq 0 ]; then + cecho "red" "❌ Don't run this script with sudo or as root." + cecho "yellow" " Just run: bash uninstall_airplay.sh" + exit 1 +fi + +if ! sudo -n true 2>/dev/null; then + cecho "yellow" "Sudo access is required for uninstallation." + sudo true || { cecho "red" "Sudo required."; exit 1; } +fi + +cecho "magenta" "╔═════════════════════════════════════════════════════╗" +cecho "magenta" "║ AirPlay 2 / Shairport-Sync Uninstaller v$SCRIPT_VERSION ║" +cecho "magenta" "╚═════════════════════════════════════════════════════╝" +echo +cecho "yellow" "This will REMOVE:" +cecho "yellow" " • shairport-sync and nqptp binaries (/usr/local/bin)" +cecho "yellow" " • /etc/shairport-sync.conf and sample" +cecho "yellow" " • systemd services (shairport-sync, nqptp)" +cecho "yellow" " • shairport-sync user and group" +cecho "yellow" " • UFW firewall rules for AirPlay (5353/udp, 319/udp, 320/udp, 7000/tcp)" +echo +cecho "blue" "APT build dependencies (libsoxr-dev, libplist-dev, ...) are NOT removed." +cecho "blue" "Other software on your system may rely on them." +echo +read -p "Type 'yes' to confirm uninstall: " confirm || true +if [ "$confirm" != "yes" ]; then + cecho "yellow" "Cancelled." + exit 0 +fi + +# Backup current configuration (best effort) +BACKUP_DIR="/tmp/airplay_uninstall_backup_$(date +%Y%m%d_%H%M%S)" +mkdir -p "$BACKUP_DIR" +[ -f /etc/shairport-sync.conf ] && sudo cp /etc/shairport-sync.conf "$BACKUP_DIR/" 2>/dev/null || true +[ -f /etc/shairport-sync.conf.sample ] && sudo cp /etc/shairport-sync.conf.sample "$BACKUP_DIR/" 2>/dev/null || true +cecho "blue" "Config backup saved to: $BACKUP_DIR" +echo + +# --- Stop services --- +cecho "blue" "Stopping services..." +sudo systemctl stop shairport-sync 2>/dev/null || true +sudo systemctl stop nqptp 2>/dev/null || true + +cecho "blue" "Disabling services..." +sudo systemctl disable shairport-sync 2>/dev/null || true +sudo systemctl disable nqptp 2>/dev/null || true + +# --- Remove systemd service files --- +cecho "blue" "Removing systemd service files..." +sudo rm -f /lib/systemd/system/shairport-sync.service +sudo rm -f /etc/systemd/system/shairport-sync.service +sudo rm -f /usr/local/lib/systemd/system/shairport-sync.service +sudo rm -f /lib/systemd/system/nqptp.service +sudo rm -f /etc/systemd/system/nqptp.service +sudo rm -f /usr/local/lib/systemd/system/nqptp.service +sudo systemctl daemon-reload +sudo systemctl reset-failed 2>/dev/null || true + +# --- Remove binaries --- +cecho "blue" "Removing binaries..." +sudo rm -f /usr/local/bin/shairport-sync +sudo rm -f /usr/local/bin/nqptp + +# --- Remove configuration files --- +cecho "blue" "Removing configuration files..." +sudo rm -f /etc/shairport-sync.conf +sudo rm -f /etc/shairport-sync.conf.sample + +# --- Remove ancillary files --- +cecho "blue" "Removing ancillary files (man pages, shared data)..." +sudo rm -rf /usr/local/share/shairport-sync 2>/dev/null || true +sudo rm -rf /etc/shairport-sync 2>/dev/null || true +sudo rm -f /usr/local/share/man/man7/shairport-sync.7 2>/dev/null || true +sudo rm -f /usr/local/share/man/man7/nqptp.7 2>/dev/null || true + +# --- Remove user and group --- +cecho "blue" "Removing shairport-sync user and group..." +if getent passwd shairport-sync >/dev/null 2>&1; then + sudo userdel shairport-sync 2>/dev/null || true +fi +if getent group shairport-sync >/dev/null 2>&1; then + sudo groupdel shairport-sync 2>/dev/null || true +fi + +# --- Remove firewall rules --- +if command -v ufw >/dev/null 2>&1; then + cecho "blue" "Removing UFW firewall rules..." + sudo ufw delete allow 5353/udp 2>/dev/null || true + sudo ufw delete allow 319/udp 2>/dev/null || true + sudo ufw delete allow 320/udp 2>/dev/null || true + sudo ufw delete allow 7000/tcp 2>/dev/null || true +fi + +# --- Verify --- +echo +cecho "blue" "Verifying removal..." +failures=0 +if command -v shairport-sync >/dev/null 2>&1; then + cecho "yellow" "⚠ shairport-sync still present at $(command -v shairport-sync)" + failures=$((failures+1)) +fi +if command -v nqptp >/dev/null 2>&1; then + cecho "yellow" "⚠ nqptp still present at $(command -v nqptp)" + failures=$((failures+1)) +fi +if [ -f /etc/shairport-sync.conf ]; then + cecho "yellow" "⚠ /etc/shairport-sync.conf still present" + failures=$((failures+1)) +fi +if systemctl list-unit-files 2>/dev/null | grep -qE '^(shairport-sync|nqptp)\.service'; then + cecho "yellow" "⚠ Some systemd unit files are still registered" + failures=$((failures+1)) +fi + +echo +if [ "$failures" -eq 0 ]; then + cecho "green" "╔═════════════════════════════════════════════════════╗" + cecho "green" "║ ✅ UNINSTALL COMPLETE ✅ ║" + cecho "green" "╚═════════════════════════════════════════════════════╝" +else + cecho "yellow" "⚠ Uninstall finished with $failures leftover item(s) — see warnings above." +fi +echo +cecho "blue" "Config backup (if any): $BACKUP_DIR" +echo +cecho "blue" "To also remove the APT build dependencies (only if not used by anything else):" +cecho "blue" " sudo apt-get remove --purge libsoxr-dev libplist-dev libplist-utils libsodium-dev \\" +cecho "blue" " libavutil-dev libavcodec-dev libavformat-dev libpopt-dev libconfig-dev \\" +cecho "blue" " libgcrypt20-dev libavahi-client-dev libssl-dev" +cecho "blue" " sudo apt-get autoremove" +echo + +read -p "Reboot now to ensure a clean state? (y/N): " do_reboot || true +if [[ "$do_reboot" =~ ^[Yy]$ ]]; then + cecho "yellow" "Rebooting in 3 seconds..." + sleep 3 + sudo reboot +fi From b0b2ec85b1ba203713d5f34b91131ceac70a7896 Mon Sep 17 00:00:00 2001 From: ermannonicoletti Date: Mon, 18 May 2026 20:58:28 +0200 Subject: [PATCH 3/4] Add optional Spotify Connect support via raspotify: installer, configuration, and management --- README.md | 15 +- .../airplay_manager.sh | 19 +- .../install_airplay_v3.sh | 123 ++++++++++ .../modify_airplay.sh | 224 ++++++++++++++++-- .../uninstall_airplay.sh | 23 +- 5 files changed, 377 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 143278f..a2f334a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # RaspberryPi-AirPlay-Installer 📻 -Turn any Raspberry Pi (Zero 2 W, 3, 4, 5) into a modern, high-quality **AirPlay 2** receiver in just a few minutes. This project automates the entire build of [Shairport-Sync](https://github.com/mikebrady/shairport-sync) + [NQPTP](https://github.com/mikebrady/nqptp) with a set of robust, interactive scripts. +Turn any Raspberry Pi (Zero 2 W, 3, 4, 5) into a modern, high-quality **AirPlay 2** receiver — and, optionally, a **Spotify Connect** endpoint — in just a few minutes. This project automates the entire build of [Shairport-Sync](https://github.com/mikebrady/shairport-sync) + [NQPTP](https://github.com/mikebrady/nqptp) and the install/configuration of [raspotify](https://github.com/dtcooper/raspotify) with a set of robust, interactive scripts. > **If you find this project helpful, please consider giving it a ⭐ star on GitHub!** @@ -12,6 +12,7 @@ Turn any Raspberry Pi (Zero 2 W, 3, 4, 5) into a modern, high-quality **AirPlay * **🤖 Fully automated** — Handles system update, dependencies, compiling and configuration. * **✅ Smart pre-flight checks** — Validates internet, disk space, memory and detected hardware before changing anything. * **🔊 Flexible audio** — Works with **USB DAC**, **audio HAT**, or the **Raspberry Pi's built-in audio** (3.5mm jack / HDMI). All detected devices are listed and labelled `[built-in]` / `[external/DAC]`. +* **🎧 Optional Spotify Connect** — Installer can also set up `raspotify` (librespot) so the same Pi appears as a Spotify Connect endpoint. Coexists with AirPlay; one source plays at a time. * **🛠️ Idempotent management** — Dedicated scripts to **modify** or **uninstall** an existing installation without reinstalling from scratch. * **🎚️ Volume control aware** — Auto-selects the best ALSA mixer (`PCM`, `Master`, `Speaker`, ...) and falls back to software volume if no hardware mixer is available. * **🔁 Rollback on failure** — Backs up configuration files and cleans up on failed installs. @@ -95,6 +96,16 @@ The interactive menu provides: All changes are written to `/etc/shairport-sync.conf` and the service is restarted automatically. +The Spotify Connect section of the same menu lets you: + +* Install / reconfigure Spotify Connect (raspotify) +* Change the Spotify device name (independent from the AirPlay one) +* Sync the Spotify audio device to the current AirPlay one +* Uninstall Spotify Connect + +> **Spotify Premium is required** on the controller device. +> Shairport-Sync and raspotify share the same ALSA card, so only one source can play at a time — the inactive one releases the device automatically, no extra config needed. + --- ## 🧹 Uninstalling @@ -110,6 +121,7 @@ Removes: * systemd units (`shairport-sync.service`, `nqptp.service`) * The `shairport-sync` user and group * UFW firewall rules added during install (`5353/udp`, `319/udp`, `320/udp`, `7000/tcp`) +* `raspotify` package + apt repository, if Spotify Connect was installed A backup of the current config is saved under `/tmp/airplay_uninstall_backup_/` before anything is deleted. @@ -163,6 +175,7 @@ sudo systemctl restart shairport-sync ## 🙏 Credits * [Mike Brady](https://github.com/mikebrady) — author of Shairport-Sync and NQPTP, the upstream projects that make all of this possible. +* [David Cooper](https://github.com/dtcooper) — author of [raspotify](https://github.com/dtcooper/raspotify), used here for optional Spotify Connect support. * Original installer scripts: [Techposts/RaspberryPi-AirPlay-Installer](https://github.com/Techposts/RaspberryPi-AirPlay-Installer). --- diff --git a/RaspberryPi-AirPlay-Installer-Scripts/airplay_manager.sh b/RaspberryPi-AirPlay-Installer-Scripts/airplay_manager.sh index e427dac..5d1654e 100755 --- a/RaspberryPi-AirPlay-Installer-Scripts/airplay_manager.sh +++ b/RaspberryPi-AirPlay-Installer-Scripts/airplay_manager.sh @@ -64,6 +64,20 @@ current_name() { | head -1 | sed -E 's/.*"([^"]*)".*/\1/' || true } +spotify_installed() { + dpkg -l raspotify 2>/dev/null | grep -q '^ii' +} + +spotify_status_line() { + if ! spotify_installed; then + echo "not installed" + elif systemctl is-active --quiet raspotify 2>/dev/null; then + echo "active" + else + echo "inactive" + fi +} + while true; do clear cecho "green" "╔═════════════════════════════════════════════════════╗" @@ -72,8 +86,9 @@ while true; do echo if is_installed; then cecho "green" " ✓ Shairport-Sync installed" - cecho "blue" " Service: $(service_status_line)" - cecho "blue" " AirPlay name: $(current_name)" + cecho "blue" " AirPlay service: $(service_status_line)" + cecho "blue" " AirPlay name: $(current_name)" + cecho "blue" " Spotify Connect: $(spotify_status_line)" else cecho "yellow" " ⚠ Shairport-Sync NOT installed." fi diff --git a/RaspberryPi-AirPlay-Installer-Scripts/install_airplay_v3.sh b/RaspberryPi-AirPlay-Installer-Scripts/install_airplay_v3.sh index 8ea80d7..0e68cad 100755 --- a/RaspberryPi-AirPlay-Installer-Scripts/install_airplay_v3.sh +++ b/RaspberryPi-AirPlay-Installer-Scripts/install_airplay_v3.sh @@ -5,6 +5,7 @@ # # Tailored for: Raspberry Pi (Zero 2/3/4/5) with USB DAC, audio HAT # or built-in audio (3.5mm jack / HDMI) +# Optional: Spotify Connect support via the raspotify package # Version: 3.0 - Production Ready # Features: # - Comprehensive error handling with rollback capability @@ -35,6 +36,12 @@ selected_device="" airplay_name="" disable_wifi_pm=false +# Spotify Connect configuration +install_spotify=false +spotify_name="" +spotify_bitrate="320" +SPOTIFY_ZEROCONF_PORT="5354" + # --- Cleanup Handler --- cleanup() { local exit_code=$? @@ -46,6 +53,7 @@ cleanup() { # Stop services if they were started sudo systemctl stop shairport-sync 2>/dev/null || true sudo systemctl stop nqptp 2>/dev/null || true + sudo systemctl stop raspotify 2>/dev/null || true # Restore backups if they exist if [ -d "$BACKUP_DIR" ]; then @@ -474,6 +482,104 @@ configure_wifi() { echo } +# --- Spotify Connect Configuration Prompt --- +configure_spotify() { + echo + cecho "yellow" "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + cecho "yellow" " Step 4: Spotify Connect (optional)" + cecho "yellow" "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo + cecho "cyan" "⏸ PLEASE RESPOND TO THIS PROMPT ⏸" + echo + + cecho "blue" "Spotify Connect lets you stream from the Spotify app to this Pi." + cecho "blue" "It is installed via the raspotify package (requires Spotify Premium)" + cecho "blue" "and coexists with AirPlay: only one source plays at a time." + echo + cecho "green" ">>> " + read -p "Install Spotify Connect as well? (Y/n): " spotify_choice || true + + if [[ -z "$spotify_choice" || "$spotify_choice" =~ ^[Yy]$ ]]; then + install_spotify=true + echo + read -p "Spotify device name (Enter for '$airplay_name'): " spotify_name || true + [ -z "$spotify_name" ] && spotify_name="$airplay_name" + spotify_name=$(echo "$spotify_name" | sed 's/[^a-zA-Z0-9 _-]//g') + cecho "green" "✓ Spotify Connect will be installed as '$spotify_name'" + else + install_spotify=false + cecho "yellow" "⚠ Spotify Connect will NOT be installed" + fi + echo +} + +# --- Spotify Connect Installation (raspotify) --- +install_spotify_connect() { + [ "$install_spotify" != true ] && return 0 + + cecho "blue" "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + cecho "blue" " Installing Spotify Connect (raspotify)..." + cecho "blue" "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + log "Setting up raspotify..." + + # Add raspotify apt repo if not already configured + if [ ! -f /etc/apt/sources.list.d/raspotify.list ]; then + cecho "yellow" "Adding raspotify apt repository..." + if ! curl -fsSL https://dtcooper.github.io/raspotify/key.asc \ + | sudo tee /usr/share/keyrings/raspotify_key.asc > /dev/null; then + cecho "red" "❌ Failed to fetch raspotify repository key" + cecho "yellow" " Skipping Spotify Connect installation, continuing..." + install_spotify=false + return 0 + fi + sudo chmod 644 /usr/share/keyrings/raspotify_key.asc + echo 'deb [signed-by=/usr/share/keyrings/raspotify_key.asc] https://dtcooper.github.io/raspotify raspotify main' \ + | sudo tee /etc/apt/sources.list.d/raspotify.list > /dev/null + sudo apt-get update -qq 2>&1 | tee -a "$LOG_FILE" || true + fi + + log "Installing raspotify package..." + if ! sudo apt-get install -y raspotify 2>&1 | tee -a "$LOG_FILE"; then + cecho "red" "❌ Failed to install raspotify" + cecho "yellow" " Skipping Spotify Connect installation, continuing..." + install_spotify=false + return 0 + fi + + # Configure raspotify via /etc/raspotify/conf + local raspotify_conf="/etc/raspotify/conf" + if [ ! -f "$raspotify_conf" ]; then + cecho "yellow" "⚠ $raspotify_conf not found after install — skipping config" + return 0 + fi + + log "Configuring raspotify: name='$spotify_name' device='$audio_device_plug'" + sudo cp "$raspotify_conf" "$BACKUP_DIR/raspotify.conf" 2>/dev/null || true + + # Replace any prior managed block, then append our settings + sudo sed -i '/^# >>> airplay-installer >>>$/,/^# <<< airplay-installer <<<$/d' "$raspotify_conf" + sudo tee -a "$raspotify_conf" > /dev/null <>> airplay-installer >>> +LIBRESPOT_NAME="$spotify_name" +LIBRESPOT_DEVICE="$audio_device_plug" +LIBRESPOT_BITRATE="$spotify_bitrate" +LIBRESPOT_INITIAL_VOLUME="100" +LIBRESPOT_ZEROCONF_PORT="$SPOTIFY_ZEROCONF_PORT" +# <<< airplay-installer <<< +EOF + + sudo systemctl enable raspotify 2>&1 | tee -a "$LOG_FILE" || true + sudo systemctl restart raspotify 2>&1 | tee -a "$LOG_FILE" || true + sleep 3 + + if check_service "raspotify"; then + cecho "green" "✓ Spotify Connect (raspotify) is running as '$spotify_name'" + else + cecho "yellow" "⚠ raspotify service is not active — check 'journalctl -u raspotify'" + fi + echo +} + # --- Main Installation --- main() { # Initialize log file immediately @@ -514,6 +620,7 @@ main() { select_audio_device get_airplay_name configure_wifi + configure_spotify # --- Confirmation --- echo @@ -526,6 +633,11 @@ main() { 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" + if [ "$install_spotify" = true ]; then + cecho "yellow" " 🎧 Spotify Connect: yes ($spotify_name)" + else + cecho "yellow" " 🎧 Spotify Connect: no" + fi 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)" @@ -875,6 +987,9 @@ EOF fi echo + # --- Spotify Connect (optional) --- + install_spotify_connect + # --- Wi-Fi Power Management Instructions --- if [ "$disable_wifi_pm" = true ]; then cecho "blue" "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" @@ -915,6 +1030,11 @@ EOF # AirPlay ports sudo ufw allow 7000/tcp comment 'AirPlay' 2>&1 | tee -a "$LOG_FILE" + # Spotify Connect (librespot zeroconf) port if installed + if [ "$install_spotify" = true ]; then + sudo ufw allow "$SPOTIFY_ZEROCONF_PORT"/tcp comment 'librespot/Spotify Connect' 2>&1 | tee -a "$LOG_FILE" + fi + cecho "green" "✓ Firewall rules added" echo fi @@ -971,6 +1091,9 @@ EOF cecho "yellow" " 📱 Device Name: $airplay_name" cecho "yellow" " 🔊 Audio Output: $audio_device_plug" cecho "yellow" " 🎚️ Volume: ${mixer_control:-Fixed (no hardware control)}" + if [ "$install_spotify" = true ]; then + cecho "yellow" " 🎧 Spotify Connect: $spotify_name (Premium account required)" + fi echo cecho "blue" "┌─────────────────────────────────────────────────────┐" cecho "blue" "│ How to use: │" diff --git a/RaspberryPi-AirPlay-Installer-Scripts/modify_airplay.sh b/RaspberryPi-AirPlay-Installer-Scripts/modify_airplay.sh index 73f2ae7..00985a5 100755 --- a/RaspberryPi-AirPlay-Installer-Scripts/modify_airplay.sh +++ b/RaspberryPi-AirPlay-Installer-Scripts/modify_airplay.sh @@ -13,6 +13,8 @@ IFS=$'\n\t' SCRIPT_VERSION="1.0" CONFIG_FILE="/etc/shairport-sync.conf" SERVICE_NAME="shairport-sync" +RASPOTIFY_CONF="/etc/raspotify/conf" +SPOTIFY_ZEROCONF_PORT="5354" # --- Helpers --- cecho() { @@ -88,6 +90,53 @@ current_mixer() { | head -1 | sed -E 's/.*"([^"]*)".*/\1/' || true } +# --- Spotify helpers --- +spotify_installed() { + dpkg -l raspotify 2>/dev/null | grep -q '^ii' +} + +spotify_current_name() { + [ -f "$RASPOTIFY_CONF" ] || { echo ""; return; } + grep -oE '^[[:space:]]*LIBRESPOT_NAME[[:space:]]*=[[:space:]]*"[^"]*"' "$RASPOTIFY_CONF" 2>/dev/null \ + | head -1 | sed -E 's/.*"([^"]*)".*/\1/' || true +} + +spotify_current_device() { + [ -f "$RASPOTIFY_CONF" ] || { echo ""; return; } + grep -oE '^[[:space:]]*LIBRESPOT_DEVICE[[:space:]]*=[[:space:]]*"[^"]*"' "$RASPOTIFY_CONF" 2>/dev/null \ + | head -1 | sed -E 's/.*"([^"]*)".*/\1/' || true +} + +write_spotify_managed_block() { + # Args: name, device + local name="$1" device="$2" + sudo sed -i '/^# >>> airplay-installer >>>$/,/^# <<< airplay-installer <<<$/d' "$RASPOTIFY_CONF" + sudo tee -a "$RASPOTIFY_CONF" > /dev/null <>> airplay-installer >>> +LIBRESPOT_NAME="$name" +LIBRESPOT_DEVICE="$device" +LIBRESPOT_BITRATE="320" +LIBRESPOT_INITIAL_VOLUME="100" +LIBRESPOT_ZEROCONF_PORT="$SPOTIFY_ZEROCONF_PORT" +# <<< airplay-installer <<< +EOF +} + +restart_spotify() { + cecho "blue" "Restarting raspotify..." + if sudo systemctl restart raspotify 2>/dev/null; then + sleep 2 + if systemctl is-active --quiet raspotify; then + cecho "green" "✓ raspotify is running" + else + cecho "red" "✗ raspotify is not active after restart" + sudo systemctl status raspotify --no-pager -l | tail -15 + fi + else + cecho "red" "✗ Failed to restart raspotify" + fi +} + # --- Actions --- action_change_name() { local cur new_name @@ -312,6 +361,118 @@ action_service_status() { sudo systemctl status nqptp --no-pager -l | head -10 || true } +# --- Spotify actions --- +action_install_spotify() { + if spotify_installed; then + cecho "yellow" "raspotify is already installed." + read -p "Reinstall / refresh configuration? (y/N): " ans || true + [[ ! "$ans" =~ ^[Yy]$ ]] && { cecho "yellow" "Cancelled."; return; } + fi + + local cur_dev cur_name spotify_name + cur_dev=$(current_output_device) + cur_name=$(current_name) + if [ -z "$cur_dev" ]; then + cecho "red" "❌ No AirPlay output_device configured. Configure AirPlay audio first." + return + fi + + echo + read -p "Spotify device name (Enter for '$cur_name'): " spotify_name || true + [ -z "$spotify_name" ] && spotify_name="$cur_name" + spotify_name=$(echo "$spotify_name" | sed 's/[^a-zA-Z0-9 _-]//g') + if [ -z "$spotify_name" ]; then + cecho "red" "Name became empty after sanitization. Cancelled." + return + fi + + if [ ! -f /etc/apt/sources.list.d/raspotify.list ]; then + cecho "yellow" "Adding raspotify apt repository..." + if ! curl -fsSL https://dtcooper.github.io/raspotify/key.asc \ + | sudo tee /usr/share/keyrings/raspotify_key.asc > /dev/null; then + cecho "red" "❌ Failed to fetch raspotify repository key. Cancelled." + return + fi + sudo chmod 644 /usr/share/keyrings/raspotify_key.asc + echo 'deb [signed-by=/usr/share/keyrings/raspotify_key.asc] https://dtcooper.github.io/raspotify raspotify main' \ + | sudo tee /etc/apt/sources.list.d/raspotify.list > /dev/null + sudo apt-get update -qq || true + fi + + cecho "blue" "Installing raspotify..." + if ! sudo apt-get install -y raspotify; then + cecho "red" "❌ Failed to install raspotify." + return + fi + + if [ ! -f "$RASPOTIFY_CONF" ]; then + cecho "red" "❌ $RASPOTIFY_CONF not found after install." + return + fi + + write_spotify_managed_block "$spotify_name" "$cur_dev" + cecho "green" "✓ raspotify configured: '$spotify_name' on $cur_dev" + + sudo systemctl enable raspotify >/dev/null 2>&1 || true + restart_spotify +} + +action_uninstall_spotify() { + if ! spotify_installed; then + cecho "yellow" "raspotify is not installed." + return + fi + read -p "Remove Spotify Connect (raspotify)? (y/N): " ans || true + [[ ! "$ans" =~ ^[Yy]$ ]] && { cecho "yellow" "Cancelled."; return; } + + sudo systemctl stop raspotify 2>/dev/null || true + sudo systemctl disable raspotify 2>/dev/null || true + sudo apt-get remove --purge -y raspotify || true + sudo rm -f /etc/apt/sources.list.d/raspotify.list + sudo rm -f /usr/share/keyrings/raspotify_key.asc + cecho "green" "✓ Spotify Connect removed" +} + +action_change_spotify_name() { + if ! spotify_installed; then + cecho "yellow" "raspotify is not installed." + return + fi + local cur new_name + cur=$(spotify_current_name) + cecho "blue" "Current Spotify name: ${cur:-}" + echo + read -p "Enter new name (empty to cancel): " new_name || true + [ -z "$new_name" ] && { cecho "yellow" "Cancelled."; return; } + new_name=$(echo "$new_name" | sed 's/[^a-zA-Z0-9 _-]//g') + [ -z "$new_name" ] && { cecho "red" "Name became empty after sanitization."; return; } + + local cur_dev + cur_dev=$(spotify_current_device) + [ -z "$cur_dev" ] && cur_dev=$(current_output_device) + write_spotify_managed_block "$new_name" "$cur_dev" + cecho "green" "✓ Spotify name updated to '$new_name'" + restart_spotify +} + +action_sync_spotify_to_airplay() { + if ! spotify_installed; then + cecho "yellow" "raspotify is not installed." + return + fi + local cur_dev cur_spo_name + cur_dev=$(current_output_device) + if [ -z "$cur_dev" ]; then + cecho "red" "No AirPlay output_device configured." + return + fi + cur_spo_name=$(spotify_current_name) + [ -z "$cur_spo_name" ] && cur_spo_name=$(current_name) + write_spotify_managed_block "$cur_spo_name" "$cur_dev" + cecho "green" "✓ Spotify audio device synced to $cur_dev" + restart_spotify +} + # --- Menu --- main() { require_install @@ -322,35 +483,52 @@ main() { cecho "magenta" " AirPlay 2 — Modify Existing Installation v$SCRIPT_VERSION" cecho "magenta" "═══════════════════════════════════════════════════════" echo - cecho "yellow" " Current name: $(current_name)" - cecho "yellow" " Current device: $(current_output_device)" - cecho "yellow" " Current mixer: $(current_mixer)" + cecho "yellow" " AirPlay name: $(current_name)" + cecho "yellow" " Audio device: $(current_output_device)" + cecho "yellow" " Mixer: $(current_mixer)" + if spotify_installed; then + cecho "yellow" " Spotify: installed — $(spotify_current_name)" + else + cecho "yellow" " Spotify: not installed" + fi + echo + cecho "cyan" " AirPlay:" + echo " 1) Change AirPlay name" + echo " 2) Change audio output device" + echo " 3) Change mixer / hardware volume control" + echo " 4) Change volume limits (volume_max_db, default_airplay_volume)" + echo " 5) Test audio output" + echo " 6) View configuration" + echo " 7) Show service status" + echo " 8) Restart service" + echo " 9) Edit configuration file manually (nano)" + echo + cecho "cyan" " Spotify Connect:" + echo " 10) Install / reconfigure Spotify Connect" + echo " 11) Change Spotify device name" + echo " 12) Sync Spotify audio device to AirPlay one" + echo " 13) Uninstall Spotify Connect" echo - echo " 1) Change AirPlay name" - echo " 2) Change audio output device" - echo " 3) Change mixer / hardware volume control" - echo " 4) Change volume limits (volume_max_db, default_airplay_volume)" - echo " 5) Test audio output" - echo " 6) View configuration" - echo " 7) Show service status" - echo " 8) Restart service" - echo " 9) Edit configuration file manually (nano)" - echo " 0) Exit" + echo " 0) Exit" echo local choice read -p "Choose: " choice || true case "$choice" in - 1) action_change_name ;; - 2) action_change_audio_device ;; - 3) action_change_mixer ;; - 4) action_change_volume_limits ;; - 5) action_test_audio ;; - 6) action_view_config ;; - 7) action_service_status ;; - 8) restart_service ;; - 9) sudo nano "$CONFIG_FILE" && restart_service ;; + 1) action_change_name ;; + 2) action_change_audio_device ;; + 3) action_change_mixer ;; + 4) action_change_volume_limits ;; + 5) action_test_audio ;; + 6) action_view_config ;; + 7) action_service_status ;; + 8) restart_service ;; + 9) sudo nano "$CONFIG_FILE" && restart_service ;; + 10) action_install_spotify ;; + 11) action_change_spotify_name ;; + 12) action_sync_spotify_to_airplay ;; + 13) action_uninstall_spotify ;; 0|q|Q|"") cecho "blue" "Bye!"; return 0 ;; - *) cecho "red" "Invalid choice." ;; + *) cecho "red" "Invalid choice." ;; esac done } diff --git a/RaspberryPi-AirPlay-Installer-Scripts/uninstall_airplay.sh b/RaspberryPi-AirPlay-Installer-Scripts/uninstall_airplay.sh index 789788f..304cc0f 100755 --- a/RaspberryPi-AirPlay-Installer-Scripts/uninstall_airplay.sh +++ b/RaspberryPi-AirPlay-Installer-Scripts/uninstall_airplay.sh @@ -50,6 +50,9 @@ cecho "yellow" " • /etc/shairport-sync.conf and sample" cecho "yellow" " • systemd services (shairport-sync, nqptp)" cecho "yellow" " • shairport-sync user and group" cecho "yellow" " • UFW firewall rules for AirPlay (5353/udp, 319/udp, 320/udp, 7000/tcp)" +if dpkg -l raspotify 2>/dev/null | grep -q '^ii'; then + cecho "yellow" " • raspotify (Spotify Connect) package + apt repo" +fi echo cecho "blue" "APT build dependencies (libsoxr-dev, libplist-dev, ...) are NOT removed." cecho "blue" "Other software on your system may rely on them." @@ -72,10 +75,24 @@ echo cecho "blue" "Stopping services..." sudo systemctl stop shairport-sync 2>/dev/null || true sudo systemctl stop nqptp 2>/dev/null || true +sudo systemctl stop raspotify 2>/dev/null || true cecho "blue" "Disabling services..." sudo systemctl disable shairport-sync 2>/dev/null || true sudo systemctl disable nqptp 2>/dev/null || true +sudo systemctl disable raspotify 2>/dev/null || true + +# --- Remove raspotify (Spotify Connect) --- +if dpkg -l raspotify 2>/dev/null | grep -q '^ii'; then + cecho "blue" "Removing raspotify package..." + sudo cp /etc/raspotify/conf "$BACKUP_DIR/raspotify.conf" 2>/dev/null || true + sudo apt-get remove --purge -y raspotify 2>/dev/null || true +fi +if [ -f /etc/apt/sources.list.d/raspotify.list ]; then + cecho "blue" "Removing raspotify apt repository..." + sudo rm -f /etc/apt/sources.list.d/raspotify.list + sudo rm -f /usr/share/keyrings/raspotify_key.asc +fi # --- Remove systemd service files --- cecho "blue" "Removing systemd service files..." @@ -139,10 +156,14 @@ if [ -f /etc/shairport-sync.conf ]; then cecho "yellow" "⚠ /etc/shairport-sync.conf still present" failures=$((failures+1)) fi -if systemctl list-unit-files 2>/dev/null | grep -qE '^(shairport-sync|nqptp)\.service'; then +if systemctl list-unit-files 2>/dev/null | grep -qE '^(shairport-sync|nqptp|raspotify)\.service'; then cecho "yellow" "⚠ Some systemd unit files are still registered" failures=$((failures+1)) fi +if dpkg -l raspotify 2>/dev/null | grep -q '^ii'; then + cecho "yellow" "⚠ raspotify package still installed" + failures=$((failures+1)) +fi echo if [ "$failures" -eq 0 ]; then From 0d599718add07fab23f9e84911e1517afd6798f9 Mon Sep 17 00:00:00 2001 From: Ermanno Nicoletti <80400985+ermanno00@users.noreply.github.com> Date: Wed, 20 May 2026 17:08:54 +0200 Subject: [PATCH 4/4] Add alternative repository clone option in README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index a2f334a..f6ab645 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,7 @@ After flashing **Raspberry Pi OS** (Lite is fine) and connecting via SSH, you ha ```bash git clone https://github.com/Techposts/RaspberryPi-AirPlay-Installer.git +or git clone https://github.com/ermanno00/RaspberryPi-AirPlay-Installer.git cd RaspberryPi-AirPlay-Installer/RaspberryPi-AirPlay-Installer-Scripts bash airplay_manager.sh # unified menu ```