diff --git a/README.md b/README.md
index 23015ac..f6ab645 100644
--- a/README.md
+++ b/README.md
@@ -1,83 +1,186 @@
# 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 โ 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!** 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!) |
-| :---: | :---: |
-| [](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]`.
+* **๐ง 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.
+* **๐ 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.
-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 A โ Run from this repo (recommended for development)
+
+```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
+```
+
+The menu lets you install, modify or uninstall, and tail live service logs.
+
+### 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
-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.
+Need to rename the speaker, change the audio output or adjust volume limits? You don't have to reinstall.
-| Mobile Screenshot | Hardware Setup |
-| :---: | :---: |
-| ****
*Your new device, ready to connect.* | ****
*The simple and clean hardware setup.* |
+```bash
+bash modify_airplay.sh
+```
+
+The interactive menu provides:
+
+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)
+
+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.
---
-### How It Works
+## ๐งน Uninstalling
+
+```bash
+bash uninstall_airplay.sh
+```
+
+Removes:
+
+* `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`)
+* `raspotify` package + apt repository, if Spotify Connect was installed
-This project uses a two-script system for a safe and reliable installation:
+A backup of the current config is saved under `/tmp/airplay_uninstall_backup_/` before anything is deleted.
-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`).
+> 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`.
+
+**Audio stutters / drops out**
-If this installer helped you bring your old speakers back to life, please consider showing your support!
+* 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.
-* **โญ 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.
+**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.
+* [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).
+
+---
+
+## ๐ 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..5d1654e
--- /dev/null
+++ b/RaspberryPi-AirPlay-Installer-Scripts/airplay_manager.sh
@@ -0,0 +1,132 @@
+#!/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
+}
+
+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" "โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ"
+ cecho "green" "โ AirPlay 2 Manager (Raspberry Pi) v$SCRIPT_VERSION โ"
+ cecho "green" "โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ"
+ echo
+ if is_installed; then
+ cecho "green" " โ Shairport-Sync installed"
+ 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
+ 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 6b9cbee..0e68cad
--- a/RaspberryPi-AirPlay-Installer-Scripts/install_airplay_v3.sh
+++ b/RaspberryPi-AirPlay-Installer-Scripts/install_airplay_v3.sh
@@ -3,7 +3,9 @@
# ===================================================================================
# 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)
+# Optional: Spotify Connect support via the raspotify package
# Version: 3.0 - Production Ready
# Features:
# - Comprehensive error handling with rollback capability
@@ -34,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=$?
@@ -45,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
@@ -299,60 +308,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)
+ # Build the full list of available devices (built-in audio included)
+ mapfile -t all_devices < <(echo "$all_cards")
- 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
-
- 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
@@ -478,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
@@ -486,7 +588,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" "โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ"
@@ -518,6 +620,7 @@ main() {
select_audio_device
get_airplay_name
configure_wifi
+ configure_spotify
# --- Confirmation ---
echo
@@ -530,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)"
@@ -580,7 +688,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
)
@@ -879,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" "โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ"
@@ -919,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
@@ -975,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
new file mode 100755
index 0000000..00985a5
--- /dev/null
+++ b/RaspberryPi-AirPlay-Installer-Scripts/modify_airplay.sh
@@ -0,0 +1,536 @@
+#!/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"
+RASPOTIFY_CONF="/etc/raspotify/conf"
+SPOTIFY_ZEROCONF_PORT="5354"
+
+# --- 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
+}
+
+# --- 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
+ 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
+}
+
+# --- 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
+ require_sudo
+ while true; do
+ echo
+ cecho "magenta" "โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ"
+ cecho "magenta" " AirPlay 2 โ Modify Existing Installation v$SCRIPT_VERSION"
+ cecho "magenta" "โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ"
+ echo
+ 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 " 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 ;;
+ 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." ;;
+ 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..304cc0f
--- /dev/null
+++ b/RaspberryPi-AirPlay-Installer-Scripts/uninstall_airplay.sh
@@ -0,0 +1,191 @@
+#!/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)"
+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."
+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
+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..."
+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|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
+ 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