Quantcast
Channel: Raspberry Pi Forums
Viewing all articles
Browse latest Browse all 7771

Raspberry Pi OS • NCSpot and Spotifyd .sh script make compiler for raspberry pi 4.

$
0
0
Hello Everyone,

I've been messing with Claude ai for the first time. I wanted to make it easier to understand or install GitHub project and compile them. My main goal was to get some native spotify premium working on my pi outside of the browser. While Raspberry pi 400 is amazing for chrome browser of spotify. I wanted something little more light weight and give myself a project to figure out. I've at one time or another created Spotifyd working on my pi but I've never truly understood what I was doing was unsure of how I would uninstall it without nuking my whole sd card.

So with claude I had it make script that loads a menu with install options and uninstall options.

It works well and it takes about 30mins to compile on raspberry pi 400 overclocked to 2.2 ghz. I wanted to install two light weight apps. One is a terminal based Spotify app called NCSpot and another is the long time running Spotifyd that is turns your device into a spotify connect device. I didn't want this to always run in the background so both of these apps run in terminal and terminate when terminal closes. Another nice thing I had the script do is install start menu shortcuts when it installs in the sound and audio category. It also removes these when you uninstall.

These are the two projects I sourced to make this script:
https://github.com/Spotifyd/spotifyd


https://github.com/hrkfdn/ncspot




once you download the script you need two commands:
**Step 1 — Transfer the script to your Pi**

You have a few options depending on how you access your Pi:

Download the code / file to your home directory as build_ncspot_deb.sh

Code:

chmod +x ~/build_ncspot_deb.sh && ~/build_ncspot_deb.sh
**A few things to expect:**
- The **first build will take 15–30 minutes** — Rust compiles everything from scratch on the Pi. This is normal, don't worry if it looks stuck.
- You'll need to enter your **sudo password** when it asks — this is for the `apt` installs and `dpkg`.
- Make sure your Pi is **plugged into power** rather than running on battery/USB for a build this long.

Code:

════════════════════════════════════════════════════════════  Spotify Suite Manager — Raspberry Pi 4  ncspot (terminal player)  +  spotifyd (Spotify Connect)════════════════════════════════════════════════════════════  Version status  (checking GitHub…)  ncspot      installed: 0.13.2          latest: 0.13.3        ⚡ Update available  spotifyd    installed: 0.4.0           latest: 0.4.0         ✔ up to date════════════════════════════════════════════════════════════  What would you like to do?  ...
If you not comfortable with using my sh file here is the code in full (keep in mind I'm not programmer I'm just using claude to make this but it was super easy to use so if you have a claude account you maybe able to figure this out too by copy and paste the code there to troubleshoot too. Put this code into a text file and and save it as build_ncspot_deb.sh in your home folder.

Code:

#!/usr/bin/env bash# =============================================================================# build_ncspot_deb.sh# All-in-one manager for ncspot + spotifyd on Raspberry Pi 4## ncspot  — Terminal Spotify client with Vim keybindings, album art, MPRIS#           Source: https://github.com/hrkfdn/ncspot## spotifyd — Lightweight Spotify Connect daemon: makes your Pi appear as a#            speaker in every Spotify app on your network#            Source: https://github.com/Spotifyd/spotifyd## Both are compiled from source and packaged as .deb files.# Desktop/launcher shortcuts are created for both apps.## Requirements:#   - Raspberry Pi 4 running Raspberry Pi OS Bookworm/Bullseye or Debian arm64#   - Internet connection#   - ~2 GB free disk space (Rust toolchain + build artifacts)#   - Spotify Premium account (required at runtime)## Audio:#   Raspberry Pi OS Bookworm uses PipeWire. PipeWire exposes a PulseAudio-#   compatible interface so pulseaudio_backend works transparently.#   This script auto-detects PipeWire / PulseAudio / ALSA automatically.## Spotify Connect (spotifyd):#   spotifyd implements the full Spotify Connect SPIRC protocol, making your#   Pi appear as a speaker device in every Spotify app on your network.#   Reference: https://github.com/Spotifyd/spotifyd#   Cargo features: https://github.com/Spotifyd/spotifyd/blob/master/Cargo.toml## MPRIS (ncspot):#   Enables D-Bus control of ncspot — media keys, playerctl, desktop#   environment integration.#   Reference: https://specifications.freedesktop.org/mpris-spec/latest/## Usage:#   chmod +x build_ncspot_deb.sh#   ./build_ncspot_deb.sh# =============================================================================set -euo pipefail# ── Colours ───────────────────────────────────────────────────────────────────RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'BOLD='\033[1m'; NC='\033[0m'info()    { echo -e "${GREEN}[INFO]${NC}  $*"; }warn()    { echo -e "${YELLOW}[WARN]${NC}  $*"; }error()   { echo -e "${RED}[ERROR]${NC} $*"; exit 1; }step()    { echo -e "\n${CYAN}── $* ──${NC}"; }divider() { echo -e "${CYAN}════════════════════════════════════════════════════════════${NC}"; }# =============================================================================# DESKTOP SHORTCUT HELPERS# Reference: https://specifications.freedesktop.org/desktop-entry-spec/latest/# =============================================================================DESKTOP_DIR_SYSTEM="/usr/share/applications"DESKTOP_DIR_USER="$HOME/.local/share/applications"# Pick the best available music icon from the system icon themepick_music_icon() {    local ICON="audio-x-generic"    for candidate in audio-headphones audio-player audio-x-generic multimedia-player; do        if [ -n "$(find /usr/share/icons /usr/local/share/icons \                    "$HOME/.local/share/icons" \                    -name "${candidate}.*" 2>/dev/null | head -1)" ]; then            ICON="$candidate"            break        fi    done    echo "$ICON"}install_desktop_shortcut() {    local APP_NAME="$1"        # e.g. "ncspot"    local DISPLAY_NAME="$2"    # e.g. "ncspot"    local GENERIC_NAME="$3"    # e.g. "Music Player"    local COMMENT="$4"    local EXEC_CMD="$5"        # full Exec= value    local ICON    ICON=$(pick_music_icon)    local CONTENT    CONTENT="[Desktop Entry]Version=1.0Type=ApplicationName=${DISPLAY_NAME}GenericName=${GENERIC_NAME}Comment=${COMMENT}Exec=${EXEC_CMD}Icon=${ICON}Terminal=falseCategories=Audio;Music;Player;AudioVideo;Keywords=spotify;music;StartupNotify=false"    local SYSTEM_FILE="${DESKTOP_DIR_SYSTEM}/${APP_NAME}.desktop"    local USER_FILE="${DESKTOP_DIR_USER}/${APP_NAME}.desktop"    if echo "$CONTENT" | sudo tee "$SYSTEM_FILE" > /dev/null 2>&1; then        sudo chmod 644 "$SYSTEM_FILE"        info "Shortcut installed system-wide: $SYSTEM_FILE"    else        mkdir -p "$DESKTOP_DIR_USER"        echo "$CONTENT" > "$USER_FILE"        chmod 644 "$USER_FILE"        info "Shortcut installed for current user: $USER_FILE"    fi    if command -v update-desktop-database &>/dev/null; then        sudo update-desktop-database "$DESKTOP_DIR_SYSTEM" 2>/dev/null || \        update-desktop-database "$DESKTOP_DIR_USER" 2>/dev/null || true    fi}remove_desktop_shortcut() {    local APP_NAME="$1"    local REMOVED=false    local SYSTEM_FILE="${DESKTOP_DIR_SYSTEM}/${APP_NAME}.desktop"    local USER_FILE="${DESKTOP_DIR_USER}/${APP_NAME}.desktop"    if [[ -f "$SYSTEM_FILE" ]]; then        sudo rm -f "$SYSTEM_FILE"; info "Removed: $SYSTEM_FILE"; REMOVED=true    fi    if [[ -f "$USER_FILE" ]]; then        rm -f "$USER_FILE"; info "Removed: $USER_FILE"; REMOVED=true    fi    if [[ "$REMOVED" == "false" ]]; then        warn "No shortcut found for $APP_NAME — skipping."    fi    if command -v update-desktop-database &>/dev/null; then        sudo update-desktop-database "$DESKTOP_DIR_SYSTEM" 2>/dev/null || \        update-desktop-database "$DESKTOP_DIR_USER" 2>/dev/null || true    fi}# =============================================================================# VERSION CHECK HELPERS# Queries the GitHub Releases API silently and compares against the locally# installed dpkg version.  Output is captured into variables so the menu can# display a clean status block before prompting the user.## GitHub Releases API reference:#   https://docs.github.com/en/rest/releases/releases#get-the-latest-release## Strategy:#   • Uses curl with a short timeout so a slow/absent network never hangs the menu.#   • jq is used when available; falls back to pure-bash grep/sed for minimal installs.#   • Locally installed version is read from dpkg — works whether the .deb was#     installed by this script or any other package manager.# =============================================================================# gh_latest_tag OWNER/REPO#   Prints the latest release tag (e.g. "v0.13.3") or "unknown" on failure.gh_latest_tag() {    local REPO="$1"    local URL="https://api.github.com/repos/${REPO}/releases/latest"    local RAW    # --max-time 6  — give the API 6 s; more than enough on any reasonable link    # --silent      — suppress curl progress output    # --fail        — return non-zero on HTTP errors (rate-limit, 404, etc.)    RAW=$(curl --silent --fail --max-time 6 \               -H "Accept: application/vnd.github+json" \               "$URL" 2>/dev/null) || { echo "unknown"; return; }    if command -v jq &>/dev/null; then        echo "$RAW" | jq -r '.tag_name // "unknown"'    else        # Pure-bash fallback: extract "tag_name":"v1.2.3" without jq        echo "$RAW" | grep -o '"tag_name"[[:space:]]*:[[:space:]]*"[^"]*"' \                    | head -1 \                    | sed 's/.*"tag_name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/'    fi}# dpkg_installed_version PACKAGE_NAME#   Prints the installed version (e.g. "0.13.3") or "not installed".dpkg_installed_version() {    local PKG="$1"    local VER    VER=$(dpkg-query -W -f='${Version}' "$PKG" 2>/dev/null) || { echo "not installed"; return; }    [[ -z "$VER" ]] && echo "not installed" || echo "$VER"}# strip_v TAG#   Strips a leading "v" so "v0.13.3" and "0.13.3" compare cleanly.strip_v() { echo "${1#v}"; }# render_version_row  APP  OWNER/REPO  LABEL#   Prints one formatted status row and sets the UPDATE_AVAILABLE_<APP> flag.render_version_row() {    local APP="$1"          # ncspot | spotifyd    local REPO="$2"         # e.g. hrkfdn/ncspot    local LABEL="$3"        # display label    local INSTALLED LATEST INSTALLED_CLEAN LATEST_CLEAN    INSTALLED=$(dpkg_installed_version "$APP")    LATEST=$(gh_latest_tag "$REPO")    INSTALLED_CLEAN=$(strip_v "$INSTALLED")    LATEST_CLEAN=$(strip_v "$LATEST")    # Pad label to fixed width for alignment    printf "  %-10s" "$LABEL"    if [[ "$INSTALLED" == "not installed" ]]; then        printf "  installed: %-16s" "—"    else        printf "  installed: %-16s" "$INSTALLED_CLEAN"    fi    if [[ "$LATEST" == "unknown" ]]; then        printf "  latest: %-12s\n" "(offline?)"    elif [[ "$INSTALLED" == "not installed" ]]; then        printf "  latest: %s\n" "$LATEST_CLEAN"    elif [[ "$INSTALLED_CLEAN" == "$LATEST_CLEAN" ]]; then        printf "  latest: %-12s  ${GREEN}✔ up to date${NC}\n" "$LATEST_CLEAN"    else        printf "  latest: %-12s  ${YELLOW}⚡ Update available${NC}\n" "$LATEST_CLEAN"        # Flag used later if the user wants context        declare -g "UPDATE_AVAILABLE_${APP^^}=true"    fi}# =============================================================================# MAIN MENU# =============================================================================# ── Silent background version check (runs before the menu is drawn) ──────────UPDATE_AVAILABLE_NCSPOT=falseUPDATE_AVAILABLE_SPOTIFYD=falseecho ""dividerecho -e "${BOLD}  Spotify Suite Manager — Raspberry Pi 4${NC}"echo -e "  ncspot (terminal player)  +  spotifyd (Spotify Connect)"dividerecho ""echo -e "  ${BOLD}Version status${NC}  (checking GitHub…)"echo ""render_version_row "ncspot"   "hrkfdn/ncspot"      "ncspot"render_version_row "spotifyd" "Spotifyd/spotifyd"  "spotifyd"echo ""dividerecho ""echo "  What would you like to do?"echo ""echo -e "  ${CYAN}1)${NC} Build / Update both ncspot and spotifyd"echo -e "  ${CYAN}2)${NC} Build / Update ncspot only"echo -e "  ${CYAN}3)${NC} Build / Update spotifyd only"echo -e "  ${CYAN}4)${NC} Uninstall everything and restore Pi to original state"echo -e "  ${CYAN}5)${NC} Exit"echo ""read -rp "$(echo -e "${CYAN}Enter choice [1-5]:${NC} ")" MAIN_CHOICEBUILD_NCSPOT=falseBUILD_SPOTIFYD=falseUNINSTALL_MODE=falsecase "$MAIN_CHOICE" in    1) BUILD_NCSPOT=true;  BUILD_SPOTIFYD=true  ;;    2) BUILD_NCSPOT=true                         ;;    3) BUILD_SPOTIFYD=true                       ;;    4) UNINSTALL_MODE=true                       ;;    5) echo "Goodbye!"; exit 0                   ;;    *) error "Invalid choice. Run the script again and enter 1–5." ;;esac# =============================================================================# UNINSTALL# =============================================================================do_uninstall() {    echo ""    divider    echo -e "${BOLD}  Full Uninstall — ncspot + spotifyd${NC}"    divider    echo ""    echo -e "  ${RED}[Always removed]${NC}"    echo "    • ncspot binary (dpkg)"    echo "    • spotifyd binary (dpkg)"    echo "    • All desktop / launcher shortcuts"    echo "    • Build folders (ncspot/ and spotifyd/)"    echo "    • cargo-deb"    echo ""    echo -e "  ${YELLOW}[You will be asked]${NC}"    echo "    • Rust toolchain (~/.cargo, ~/.rustup)"    echo "    • ncspot config (~/.config/ncspot/)"    echo "    • ncspot cache (~/.cache/ncspot/)"    echo "    • spotifyd config (~/.config/spotifyd/)"    echo "    • Build-only apt packages"    echo ""    read -rp "$(echo -e "${RED}Are you sure you want to uninstall everything? [y/N]:${NC} ")" CONFIRM_UN    [[ ! "$CONFIRM_UN" =~ ^[Yy]$ ]] && { info "Cancelled. Nothing changed."; exit 0; }    # ── dpkg remove ───────────────────────────────────────────────────────────    for PKG in ncspot spotifyd; do        step "Removing $PKG package"        if dpkg -l "$PKG" &>/dev/null 2>&1; then            sudo dpkg --remove "$PKG" && info "$PKG removed." || warn "dpkg remove $PKG failed — may not be installed."        else            warn "$PKG not installed via dpkg — skipping."        fi    done    # ── Desktop shortcuts ─────────────────────────────────────────────────────    step "Removing desktop shortcuts"    remove_desktop_shortcut "ncspot"    remove_desktop_shortcut "spotifyd"    # ── Build folders ─────────────────────────────────────────────────────────    SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"    for DIR in ncspot spotifyd; do        step "Removing build folder: $DIR"        if [[ -d "$SCRIPT_DIR/$DIR" ]]; then            rm -rf "${SCRIPT_DIR:?}/$DIR" && info "$SCRIPT_DIR/$DIR removed."        else            warn "$SCRIPT_DIR/$DIR not found — skipping."        fi    done    # ── cargo-deb ─────────────────────────────────────────────────────────────    step "Removing cargo-deb"    source "$HOME/.cargo/env" 2>/dev/null || true    if command -v cargo-deb &>/dev/null; then        cargo uninstall cargo-deb 2>/dev/null && info "cargo-deb removed." || warn "cargo uninstall failed."    else        warn "cargo-deb not found — skipping."    fi    # ── Rust toolchain (optional) ─────────────────────────────────────────────    step "Rust toolchain (optional)"    if [[ -d "$HOME/.rustup" ]] || [[ -d "$HOME/.cargo" ]]; then        echo ""        echo "  The Rust toolchain (~/.cargo and ~/.rustup) is only needed to build"        echo "  these apps. Safe to remove if you don't use Rust for anything else (~1-2 GB)."        echo ""        read -rp "$(echo -e "${CYAN}Remove the Rust toolchain? [y/N]:${NC} ")" RM_RUST        if [[ "$RM_RUST" =~ ^[Yy]$ ]]; then            if command -v rustup &>/dev/null; then                source "$HOME/.cargo/env" 2>/dev/null || true                rustup self uninstall -y && info "Rust toolchain removed."            else                rm -rf "$HOME/.cargo" "$HOME/.rustup" && info "Rust folders removed."            fi        else            info "Rust toolchain kept."        fi    else        warn "Rust toolchain not found — skipping."    fi    # ── ncspot config (optional) ──────────────────────────────────────────────    step "ncspot config and cache (optional)"    for DIR_INFO in "$HOME/.config/ncspot:ncspot config" "$HOME/.cache/ncspot:ncspot cache"; do        DIR="${DIR_INFO%%:*}"; LABEL="${DIR_INFO##*:}"        if [[ -d "$DIR" ]]; then            SIZE=$(du -sh "$DIR" 2>/dev/null | cut -f1 || echo "?")            echo ""            echo "  Found $LABEL at: $DIR  ($SIZE)"            read -rp "$(echo -e "${CYAN}Remove $LABEL? [y/N]:${NC} ")" RM_DIR            if [[ "$RM_DIR" =~ ^[Yy]$ ]]; then                rm -rf "$DIR" && info "$LABEL removed."            else                info "$LABEL kept."            fi        else            warn "No $LABEL found — skipping."        fi    done    # ── spotifyd config + OAuth credentials (optional) ────────────────────────    step "spotifyd config and credentials (optional)"    for DIR_INFO in \        "$HOME/.config/spotifyd:spotifyd config (device name, audio settings)" \        "$HOME/.cache/spotifyd:spotifyd cache (OAuth login credentials, audio cache)"; do        DIR="${DIR_INFO%%:*}"; LABEL="${DIR_INFO##*:}"        if [[ -d "$DIR" ]]; then            SIZE=$(du -sh "$DIR" 2>/dev/null | cut -f1 || echo "?")            echo ""            echo "  Found $LABEL at: $DIR  ($SIZE)"            [[ "$DIR" == *cache* ]] && \                echo "  Note: removing this will require you to log in via OAuth again after reinstall."            read -rp "$(echo -e "${CYAN}Remove $LABEL? [y/N]:${NC} ")" RM_DIR            if [[ "$RM_DIR" =~ ^[Yy]$ ]]; then                rm -rf "$DIR" && info "$LABEL removed."            else                info "$LABEL kept."            fi        else            warn "No $LABEL found — skipping."        fi    done    # ── Build-only apt packages (optional) ────────────────────────────────────    step "Build-only apt packages (optional)"    BUILD_ONLY_PKGS=("libncursesw5-dev" "libxcb1-dev" "libxcb-render0-dev"                     "libxcb-shape0-dev" "libxcb-xfixes0-dev" "pkgconf")    INSTALLED_BUILD_PKGS=()    for pkg in "${BUILD_ONLY_PKGS[@]}"; do        dpkg -l "$pkg" &>/dev/null 2>&1 && INSTALLED_BUILD_PKGS+=("$pkg")    done    if [[ ${#INSTALLED_BUILD_PKGS[@]} -gt 0 ]]; then        echo ""        echo "  Build-only packages installed by this script:"        for pkg in "${INSTALLED_BUILD_PKGS[@]}"; do echo "    • $pkg"; done        echo ""        echo "  Note: libdbus-dev, libssl-dev, libpulse-dev/libasound2-dev kept"        echo "  intentionally as other apps may depend on them."        echo ""        read -rp "$(echo -e "${CYAN}Remove these build-only packages? [y/N]:${NC} ")" RM_PKGS        if [[ "$RM_PKGS" =~ ^[Yy]$ ]]; then            sudo apt-get remove -y "${INSTALLED_BUILD_PKGS[@]}"            sudo apt-get autoremove -y            info "Build packages removed."        else            info "apt packages kept."        fi    else        warn "No tracked build-only packages found — skipping."    fi    echo ""    divider    echo -e "${GREEN}  Uninstall complete! Pi restored to pre-install state.${NC}"    divider    echo ""    echo "  To reinstall, run this script again and choose option 1."    echo ""    exit 0}[[ "$UNINSTALL_MODE" == "true" ]] && do_uninstall# =============================================================================# SHARED SETUP (runs if building either or both apps)# =============================================================================# ── Platform check ────────────────────────────────────────────────────────────step "Platform check"ARCH=$(uname -m)info "Detected architecture: $ARCH"[[ "$ARCH" != "aarch64" && "$ARCH" != "armv7l" ]] && \    warn "Designed for arm64/armhf (Pi 4). Detected $ARCH — continuing anyway."# ── Auto-detect audio system ──────────────────────────────────────────────────## Raspberry Pi OS Bookworm: PipeWire (exposes PulseAudio-compatible interface)# Older Pi OS / Bullseye:   PulseAudio# Minimal installs:         ALSA fallback## References:#   https://www.raspberrypi.com/news/bookworm-the-new-version-of-raspberry-pi-os/#   https://github.com/hrkfdn/ncspot/blob/main/Cargo.toml#   https://github.com/Spotifyd/spotifyd/blob/master/Cargo.tomlstep "Detecting active audio system"AUDIO_BACKEND=""AUDIO_DEP=""if systemctl --user is-active --quiet pipewire-pulse 2>/dev/null || \   pactl info 2>/dev/null | grep -qi "pipewire"; then    info "PipeWire detected (Bookworm default) — using pulseaudio_backend."    AUDIO_BACKEND="pulseaudio_backend"    AUDIO_DEP="libpulse-dev"elif systemctl --user is-active --quiet pulseaudio 2>/dev/null || \     pactl info 2>/dev/null | grep -qi "pulseaudio"; then    info "PulseAudio detected — using pulseaudio_backend."    AUDIO_BACKEND="pulseaudio_backend"    AUDIO_DEP="libpulse-dev"else    warn "No PipeWire/PulseAudio detected — falling back to alsa_backend."    warn "Check your audio setup with:  aplay -l"    AUDIO_BACKEND="alsa_backend"    AUDIO_DEP="libasound2-dev"fiinfo "Audio backend: $AUDIO_BACKEND"# ── System dependencies ───────────────────────────────────────────────────────step "Installing system dependencies"sudo apt-get update -qqsudo apt-get install -y \    build-essential curl git python3 pkgconf \    libdbus-1-dev libncursesw5-dev libssl-dev \    libxcb1-dev libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev \    desktop-file-utils \    "$AUDIO_DEP"info "System dependencies ready."# ── Rust toolchain ────────────────────────────────────────────────────────────step "Rust toolchain"if ! command -v cargo &>/dev/null; then    info "Installing Rust via rustup..."    curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --no-modify-path    source "$HOME/.cargo/env"    info "Rust installed: $(rustc --version)"else    source "$HOME/.cargo/env" 2>/dev/null || true    info "Rust already installed: $(rustc --version)"    info "Updating Rust toolchain..."    rustup update stable --no-self-updatefi# ── cargo-deb ─────────────────────────────────────────────────────────────────# Reference: https://github.com/hrkfdn/ncspot/blob/main/doc/package_maintainers.mdstep "cargo-deb"if ! command -v cargo-deb &>/dev/null; then    cargo install cargo-deb && info "cargo-deb installed."else    cargo install cargo-deb --force && info "cargo-deb updated."fi# =============================================================================# SAFE UNINSTALL BEFORE UPGRADE# Called automatically by fetch_or_clone whenever a new version is confirmed.## Why a clean uninstall before every upgrade?#   Rust compiled binaries can carry stale symbol tables, leftover compiled#   artifacts, or changed Cargo feature flags from a previous build.  Simply#   overwriting with dpkg -i is usually fine for minor bumps, but major version#   changes (e.g. new audio backend, renamed binaries, changed config schema)#   can leave the system in a broken half-upgraded state that is hard to debug.#   A clean slate guarantees the new .deb installs into a known-good environment.## What is removed:#   • The installed dpkg package (binary only — no configs touched)#   • The build directory for that app (stale Rust artifacts)#   • The desktop shortcut (reinstalled fresh after the new build)## What is deliberately KEPT:#   • ~/.config/<app>/   — user config files#   • ~/.cache/<app>/    — OAuth credentials / audio cache#   • Rust toolchain     — no need to reinstall, just cargo clean inside the dir#   • All other apps     — only the one being upgraded is touched# =============================================================================safe_uninstall_for_upgrade() {    local APP="$1"          # ncspot | spotifyd    local SCRIPT_DIR="$2"   # absolute path to directory containing build folders    echo ""    step "Clean uninstall of $APP before upgrade"    echo ""    echo -e "  ${YELLOW}A version change was detected. Running a clean uninstall first${NC}"    echo -e "  ${YELLOW}to avoid stale build artifacts or broken dpkg state.${NC}"    echo ""    echo "  The following will be removed:"    echo "    • $APP dpkg package (binary)"    echo "    • $SCRIPT_DIR/$APP build folder"    echo "    • $APP desktop shortcut"    echo ""    echo "  The following will be KEPT:"    echo "    • ~/.config/$APP/   (your config)"    echo "    • ~/.cache/$APP/    (OAuth credentials / cache)"    echo "    • Rust toolchain"    echo ""    # ── Remove dpkg package ───────────────────────────────────────────────────    if dpkg -l "$APP" &>/dev/null 2>&1; then        info "Removing $APP package via dpkg..."        sudo dpkg --remove "$APP" && info "$APP package removed." \            || warn "dpkg remove $APP failed — may already be uninstalled."    else        info "$APP not currently installed via dpkg — skipping."    fi    # ── Remove build folder ───────────────────────────────────────────────────    if [[ -d "$SCRIPT_DIR/$APP" ]]; then        info "Removing build folder: $SCRIPT_DIR/$APP"        rm -rf "${SCRIPT_DIR:?}/$APP"        info "Build folder removed."    else        info "No build folder found for $APP — skipping."    fi    # ── Remove desktop shortcut ───────────────────────────────────────────────    remove_desktop_shortcut "$APP"    echo ""    info "Clean uninstall complete. Proceeding with fresh build of $APP."    echo ""}# =============================================================================# CLONE / UPDATE HELPER# Returns 0 if a rebuild is needed, 1 if already up to date# =============================================================================fetch_or_clone() {    local REPO_URL="$1"    local DIR="$2"    local NEEDS_REBUILD=0   # 0 = yes rebuild, 1 = up to date    if [[ -d "$DIR/.git" ]]; then        info "Existing $DIR repo found. Checking for updates..."        cd "$DIR"        git fetch origin HEAD --quiet        LOCAL_COMMIT=$(git rev-parse HEAD)        REMOTE_COMMIT=$(git rev-parse FETCH_HEAD)        LOCAL_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "unknown")        REMOTE_TAG=$(git describe --tags --abbrev=0 FETCH_HEAD 2>/dev/null || echo "unknown")        if [[ "$LOCAL_COMMIT" == "$REMOTE_COMMIT" ]]; then            echo ""            echo -e "${GREEN}  $DIR is already up to date: $LOCAL_TAG (${LOCAL_COMMIT:0:7})${NC}"            echo ""            EXISTING_DEB=$(ls target/debian/${DIR}_*.deb 2>/dev/null | head -1 || true)            if [[ -n "$EXISTING_DEB" ]]; then                info "Existing .deb: $PWD/$EXISTING_DEB"                cd ..                return 1   # no rebuild needed            else                warn "No .deb found — will rebuild."                git merge --ff-only FETCH_HEAD --quiet            fi        else            echo ""            echo -e "${YELLOW}  Update available for $DIR!${NC}"            echo -e "  Current: $LOCAL_TAG  (${LOCAL_COMMIT:0:7})"            echo -e "  Latest:  $REMOTE_TAG  (${REMOTE_COMMIT:0:7})"            echo ""            echo "  Recent changes:"            echo "  ────────────────────────────────────────────"            git log HEAD..FETCH_HEAD --oneline --no-decorate | head -15            echo "  ────────────────────────────────────────────"            echo ""            read -rp "$(echo -e "${CYAN}  Rebuild $DIR with latest changes? [y/N]:${NC} ")" CONFIRM            if [[ ! "$CONFIRM" =~ ^[Yy]$ ]]; then                info "Update skipped for $DIR."                cd ..                return 1            fi            # ── Safe clean uninstall before upgrade ───────────────────────────            # Step back to the parent directory before calling safe_uninstall_for_upgrade,            # because that function will delete the build folder we are currently inside.            # The build functions will re-clone via a second fetch_or_clone call below.            local REPO_URL_SAVE="$REPO_URL"            cd ..            local SCRIPT_DIR_ABS            SCRIPT_DIR_ABS="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"            safe_uninstall_for_upgrade "$DIR" "$SCRIPT_DIR_ABS"            # Re-clone now that the build folder is gone, then return 0 so the            # calling build function proceeds straight to cargo build.            info "Re-cloning $DIR after clean uninstall..."            git clone "$REPO_URL_SAVE" "$DIR"            info "Re-clone complete."            return 0        fi        cd ..    else        [[ -d "$DIR" ]] && { warn "$DIR exists but is not a git repo — removing..."; rm -rf "$DIR"; }        info "Cloning $DIR from GitHub..."        git clone "$REPO_URL" "$DIR"        info "Clone complete."    fi    return 0   # rebuild needed}# =============================================================================# BUILD ncspot# Source: https://github.com/hrkfdn/ncspot# Features ref: https://github.com/hrkfdn/ncspot/blob/main/Cargo.toml# =============================================================================build_ncspot() {    divider    echo -e "${BOLD}  Building ncspot${NC}"    divider    fetch_or_clone "https://github.com/hrkfdn/ncspot.git" "ncspot"    local SKIP=$?    [[ $SKIP -eq 1 ]] && return    cd ncspot    echo ""    echo -e "  ${CYAN}ncspot features being compiled:${NC}"    echo "    cover             — Album art display (press 'a' inside ncspot)"    echo "    mpris             — Media key support + D-Bus / playerctl control"    echo "    $AUDIO_BACKEND    — Auto-detected audio backend"    echo "    notify            — Now-playing desktop notifications"    echo "    share_clipboard   — Copy track/playlist URLs to clipboard"    echo "    crossterm_backend — Terminal rendering"    echo ""    # Build    # --no-default-features for a clean explicit feature set (no backend conflicts)    # Feature reference: https://github.com/hrkfdn/ncspot/blob/main/Cargo.toml    cargo build --release \        --no-default-features \        --features "cover,mpris,${AUDIO_BACKEND},notify,share_clipboard,crossterm_backend"    info "ncspot binary built: target/release/ncspot"    # Package    cargo deb --no-build    info "ncspot .deb packaged."    NCSPOT_DEB=$(ls target/debian/ncspot_*.deb 2>/dev/null | head -1)    [[ -z "$NCSPOT_DEB" ]] && error "ncspot .deb not found after packaging."    echo ""    divider    echo -e "${GREEN}  ncspot .deb ready: $PWD/$NCSPOT_DEB${NC}"    divider    echo ""    read -rp "$(echo -e "${CYAN}Install ncspot now? [y/N]:${NC} ")" INSTALL_NCSPOT    if [[ "$INSTALL_NCSPOT" =~ ^[Yy]$ ]]; then        sudo dpkg -i "$NCSPOT_DEB"        info "ncspot installed."        # Desktop shortcut — launches in default terminal emulator        # Categories=AudioVideo makes it appear under Sound & Video in the launcher        install_desktop_shortcut \            "ncspot" \            "ncspot" \            "Music Player" \            "Terminal Spotify client — Vim keybindings, album art, MPRIS" \            "x-terminal-emulator -e ncspot"        echo ""        echo -e "${GREEN}  ncspot ready!${NC}"        echo "    • Launch from terminal:   ncspot"        echo "    • Launch from app menu:   Sound & Video → ncspot"        echo "    • Album art:              Press 'a' inside ncspot"        echo "    • Media keys:             MPRIS active via D-Bus"        echo "    • Config:                 ~/.config/ncspot/config.toml"    else        echo ""        echo "  To install later:  sudo dpkg -i $PWD/$NCSPOT_DEB"        echo "  Note: desktop shortcut is created automatically on install."    fi    cd ..}# =============================================================================# BUILD spotifyd# Source: https://github.com/Spotifyd/spotifyd# Config ref: https://spotifyd.github.io/spotifyd/config/File.html# Cargo features: https://github.com/Spotifyd/spotifyd/blob/master/Cargo.toml## Features:#   $AUDIO_BACKEND — matches same backend chosen for ncspot (PulseAudio/ALSA)#   dbus_mpris     — MPRIS D-Bus support: media keys, playerctl integration## Spotify Connect is always active in spotifyd — no extra flag needed.# Once running, the Pi appears as a speaker in every Spotify app on the network.# =============================================================================build_spotifyd() {    divider    echo -e "${BOLD}  Building spotifyd${NC}"    divider    fetch_or_clone "https://github.com/Spotifyd/spotifyd.git" "spotifyd"    local SKIP=$?    [[ $SKIP -eq 1 ]] && return    cd spotifyd    echo ""    echo -e "  ${CYAN}spotifyd features being compiled:${NC}"    echo "    $AUDIO_BACKEND — Auto-detected audio backend"    echo "    dbus_mpris     — MPRIS D-Bus + media key support"    echo ""    echo -e "  ${CYAN}Spotify Connect:${NC}"    echo "    Always active — your Pi will appear as a speaker in every"    echo "    Spotify app on the same network once spotifyd is running."    echo "    Reference: https://github.com/Spotifyd/spotifyd"    echo ""    # Build    # Feature reference: https://github.com/Spotifyd/spotifyd/blob/master/Cargo.toml    cargo build --release \        --no-default-features \        --features "${AUDIO_BACKEND},dbus_mpris"    info "spotifyd binary built: target/release/spotifyd"    # Package    cargo deb --no-build    info "spotifyd .deb packaged."    SPOTIFYD_DEB=$(ls target/debian/spotifyd_*.deb 2>/dev/null | head -1)    [[ -z "$SPOTIFYD_DEB" ]] && error "spotifyd .deb not found after packaging."    echo ""    divider    echo -e "${GREEN}  spotifyd .deb ready: $PWD/$SPOTIFYD_DEB${NC}"    divider    echo ""    read -rp "$(echo -e "${CYAN}Install spotifyd now? [y/N]:${NC} ")" INSTALL_SPOTIFYD    if [[ "$INSTALL_SPOTIFYD" =~ ^[Yy]$ ]]; then        sudo dpkg -i "$SPOTIFYD_DEB"        info "spotifyd installed."        # ── Create config file if it doesn't exist ────────────────────────────        # Reference: https://spotifyd.github.io/spotifyd/config/File.html        #        # NOTE: Spotify removed username/password authentication in 2024.        # spotifyd now uses two methods:        #   1) Discovery (Zeroconf) — Pi appears automatically in Spotify apps        #      on your network. Just open any Spotify app and tap the speaker        #      icon to select this Pi. No credentials needed in the config.        #   2) OAuth — Run "spotifyd authenticate" to log in via web browser.        #      Credentials are stored securely and reused on every restart.        # Reference: https://docs.spotifyd.rs/configuration/auth.html        SPOTIFYD_CONF_DIR="$HOME/.config/spotifyd"        SPOTIFYD_CONF_FILE="$SPOTIFYD_CONF_DIR/spotifyd.conf"        SPOTIFYD_CACHE_DIR="$HOME/.cache/spotifyd"        if [[ ! -f "$SPOTIFYD_CONF_FILE" ]]; then            mkdir -p "$SPOTIFYD_CONF_DIR"            mkdir -p "$SPOTIFYD_CACHE_DIR"            PI_HOSTNAME=$(hostname)            cat > "$SPOTIFYD_CONF_FILE" << CONF# spotifyd configuration# Full config reference:   https://spotifyd.github.io/spotifyd/config/File.html# Authentication options:  https://docs.spotifyd.rs/configuration/auth.html## Authentication:#   spotifyd supports two login methods:##   METHOD 1 — Discovery (easiest, no config needed):#     Just run spotifyd and open the Spotify app on any device on your network.#     Tap the speaker/device icon and select this Pi from the list.#     spotifyd handles authentication automatically via the Spotify app.##   METHOD 2 — OAuth (logs in once, reconnects automatically on restart):#     Run:  spotifyd authenticate#     A browser link will be shown. Open it, log into Spotify, confirm.#     Your credentials are stored securely and reused every time spotifyd starts.## NOTE: Username/password authentication was removed by Spotify in 2024.#       Do NOT add username= or password= lines — they no longer work.[global]# The name this Pi appears as in Spotify Connect device lists on your network.# Change this to something you will recognise (e.g. "Living Room Pi").device_name = "${PI_HOSTNAME}-spotifyd"# Audio backend — auto-detected to match your Pi's audio system.# Bookworm (PipeWire): pulse   |   Older Pi OS: pulse   |   Minimal: alsabackend = "${AUDIO_BACKEND//_backend/}"# Streaming bitrate: 96, 160, or 320 kbps# 320 = highest quality (recommended for Pi 4 with good network)bitrate = 320# Cache directory — stores OAuth credentials and audio data between sessions.# Do not change this after running "spotifyd authenticate" or you will need# to log in again.cache_path = "${SPOTIFYD_CACHE_DIR}"# Volume control: softvol (software), alsa (hardware mixer), or nonevolume_controller = "softvol"# Zeroconf port for Spotify Connect discovery.# Leave commented to use a random port (works for most setups).# Uncomment and set a fixed port if you have firewall rules:# zeroconf_port = 4070CONF            info "spotifyd config created at: $SPOTIFYD_CONF_FILE"        else            info "Existing spotifyd config kept: $SPOTIFYD_CONF_FILE"        fi        # ── OAuth authentication ───────────────────────────────────────────────        # Offer to run "spotifyd authenticate" right now so the Pi logs in        # once via browser and reconnects automatically on every future restart.        # Reference: https://docs.spotifyd.rs/configuration/auth.html        echo ""        divider        echo -e "${BOLD}  Spotify Account Login (OAuth)${NC}"        divider        echo ""        echo "  spotifyd needs to be linked to your Spotify Premium account."        echo "  There are two ways to do this:"        echo ""        echo -e "  ${CYAN}Option A — Log in now via OAuth (recommended):${NC}"        echo "    • spotifyd will show a web link in this terminal"        echo "    • Open that link in a browser on this Pi or any other device"        echo "    • Log into your Spotify account and click Agree"        echo "    • Your credentials are saved — spotifyd reconnects automatically"        echo "      every time it starts, no phone or app needed"        echo ""        echo -e "  ${CYAN}Option B — Skip for now, use Discovery instead:${NC}"        echo "    • No login needed right now"        echo "    • Each time you launch spotifyd, open the Spotify app on your"        echo "      phone/computer, tap the speaker icon, and select this Pi"        echo "    • Spotify authenticates automatically via the network handshake"        echo "    • You can always run 'spotifyd authenticate' later to switch"        echo "      to Option A"        echo ""        read -rp "$(echo -e "${CYAN}Log in via OAuth now? [y/N]:${NC} ")" DO_OAUTH        if [[ "$DO_OAUTH" =~ ^[Yy]$ ]]; then            echo ""            echo -e "${YELLOW}  A browser link will appear below. Open it and log into Spotify.${NC}"            echo -e "${YELLOW}  You can open the link on this Pi's browser or any other device.${NC}"            echo ""            # Pass --cache to ensure credentials land in the same path as the config            if spotifyd authenticate --cache "$SPOTIFYD_CACHE_DIR"; then                echo ""                info "OAuth login successful! spotifyd will reconnect automatically on every start."            else                echo ""                warn "OAuth login did not complete. You can run it manually later:"                warn "  spotifyd authenticate --cache $SPOTIFYD_CACHE_DIR"                warn "Or use Discovery: launch spotifyd and select this Pi from the Spotify app."            fi        else            echo ""            info "Skipped OAuth login. Using Discovery mode."            echo ""            echo "  When you launch spotifyd, just open the Spotify app on your phone"            echo "  or computer, tap the speaker/device icon, and select this Pi."            echo "  To log in via OAuth later, run:"            echo "    spotifyd authenticate --cache $SPOTIFYD_CACHE_DIR"        fi        # ── Desktop shortcut ──────────────────────────────────────────────────        # Launches spotifyd in a terminal so output is visible and closing the        # window stops the daemon cleanly.        # Categories=AudioVideo places it under Sound & Video in the launcher.        install_desktop_shortcut \            "spotifyd" \            "Spotifyd (Spotify Connect)" \            "Spotify Connect Speaker" \            "Make this Pi a Spotify Connect speaker visible on your network" \            "x-terminal-emulator -e bash -c 'echo Starting Spotifyd — Spotify Connect Speaker...; echo; spotifyd --no-daemon; echo; read -p \"spotifyd stopped. Press Enter to close.\"'"        echo ""        echo -e "${GREEN}  spotifyd ready!${NC}"        echo "    • Launch from app menu:   Sound & Video → Spotifyd (Spotify Connect)"        echo "    • Launch from terminal:   spotifyd --no-daemon"        echo "    • Once running, open Spotify on any device on your network"        echo "      and this Pi will appear as a speaker in the device list."        echo "    • Config file:            $SPOTIFYD_CONF_FILE"        echo "    • Auth docs:              https://docs.spotifyd.rs/configuration/auth.html"    else        echo ""        echo "  To install later:  sudo dpkg -i $PWD/$SPOTIFYD_DEB"        echo "  Note: desktop shortcut and config are created automatically on install."    fi    cd ..}# =============================================================================# RUN THE SELECTED BUILDS# =============================================================================[[ "$BUILD_NCSPOT"   == "true" ]] && build_ncspot[[ "$BUILD_SPOTIFYD" == "true" ]] && build_spotifyd# ── Final summary ─────────────────────────────────────────────────────────────echo ""dividerecho -e "${BOLD}  All done!${NC}"dividerecho ""if [[ "$BUILD_NCSPOT" == "true" ]]; then    echo -e "  ${CYAN}ncspot${NC}    — Terminal Spotify client"    echo "              Launch: ncspot  |  App menu: Sound & Video → ncspot"fiif [[ "$BUILD_SPOTIFYD" == "true" ]]; then    echo -e "  ${CYAN}spotifyd${NC}  — Spotify Connect speaker daemon"    echo "              Launch: spotifyd --no-daemon  |  App menu: Sound & Video → Spotifyd"    echo ""    echo -e "  ${CYAN}Authentication:${NC}"    echo "    OAuth (auto-reconnect):  spotifyd authenticate --cache ~/.cache/spotifyd"    echo "    Discovery (easy):        Just launch spotifyd and select this Pi"    echo "                             from the speaker list in any Spotify app"    echo "    Docs:                    https://docs.spotifyd.rs/configuration/auth.html"fiecho ""echo "  References:"echo "    ncspot source:        https://github.com/hrkfdn/ncspot"echo "    ncspot features:      https://github.com/hrkfdn/ncspot/blob/main/Cargo.toml"echo "    spotifyd source:      https://github.com/Spotifyd/spotifyd"echo "    spotifyd features:    https://github.com/Spotifyd/spotifyd/blob/master/Cargo.toml"echo "    spotifyd config:      https://spotifyd.github.io/spotifyd/config/File.html"echo "    spotifyd auth:        https://docs.spotifyd.rs/configuration/auth.html"echo "    Pi OS Bookworm audio: https://www.raspberrypi.com/news/bookworm-the-new-version-of-raspberry-pi-os/"echo "    MPRIS spec:           https://specifications.freedesktop.org/mpris-spec/latest/"
build_ncspot_deb.sh.zip

Statistics: Posted by PuppetHoundZ — Sun May 31, 2026 9:03 pm



Viewing all articles
Browse latest Browse all 7771

Latest Images

Trending Articles



Latest Images