9.2 KiB
sendspin-player: Raspberry Pi quickstart script
Status: Design approved 2026-05-01 Tracks: lower the on-ramp for Pi-as-player setups using prebuilt arm64 release tarballs
Problem
Setting up a Raspberry Pi as a Sendspin player today requires the user to: install build-time deps via install-deps.sh, clone the repo, build with the CGo toolchain, then either run the binary by hand or wire up systemd through make install-player-daemon. None of that is friendly for a "stick a Pi behind the speakers and forget about it" workflow, and almost all of it is unnecessary now that the GitHub Releases pipeline ships ready-to-run linux-arm64 tarballs.
A curl | sudo bash quickstart that takes a fresh 64-bit Raspberry Pi OS install to a running, mDNS-discoverable player in one command closes that gap.
Goals
- Single-command install:
curl -fsSL https://raw.githubusercontent.com/Sendspin/sendspin-go/main/scripts/quickstart-pi.sh | sudo bashbrings up the player on a fresh 64-bit Raspberry Pi OS — Lite is the recommended target (headless, no PulseAudio/PipeWire, lower scheduling jitter), full desktop also works. Bookworm or newer is required for thelibflac12runtime package. - Idempotent re-run: invoking the script a second time upgrades the binary and refreshes the unit file in place.
- Optional one-shot configuration via flags:
bash -s -- --name living-room --device "USB DAC". - Clean uninstall via
--uninstall. - Pin a specific version via
--version v1.6.2(default: latest). - Zero changes to existing build, daemon-install, or release flows.
Non-goals
- No 32-bit Pi support. Releases ship
linux-arm64only. The script detectsarmv6l/armv7land exits with a clear pointer to 64-bit Raspberry Pi OS. Pi 1 / Zero (v1) / Zero W are excluded by hardware; Pi 2 is excluded by OS choice. - No non-Debian distros. The script's package-install step targets
apt-get. Other distros must follow the README's manual install steps. - No Debian < 12 (Bullseye and earlier). The script hard-codes
libflac12, which is Bookworm's package name. On older Debian / Pi OS,apt-get install libflac12fails loudly with "no installation candidate" — that error is acceptable as the user-facing rejection rather than adding fallback logic. - No checksum / signature verification. HTTPS to
github.comis the same trust boundary thecurl | bashitself crosses; layering a second integrity check adds operational cost without materially changing the threat model. May reconsider if signed releases land later. - No interactive device picker.
--device "<exact name>"is the documented path. Users wanting a list runsendspin-player --list-audio-devicesafter install. - No support for running
sendspin-servervia this script. Pi-as-server is a less common configuration; if it becomes one, a sibling script is the right move, not flag bloat in this one. - No purge of
/etc/sendspin/on--uninstall. Matches the existingmake uninstall-player-daemonbehavior; user state is precious.
Approach
A single shell script at scripts/quickstart-pi.sh, fetched via raw GitHub URL and piped to sudo bash. Self-contained: every helper is inlined, no second fetch, no source tree dependency.
The script reuses every artifact already shipped in the repo:
- The
dist/systemd/sendspin-player.serviceunit (already root-running withProtectSystem=strict/ProtectHome=read-onlyhardening; no change). - The
dist/systemd/sendspin-player.envfile (already wiresSENDSPIN_PLAYER_OPTSintoExecStart). - The
dist/config/player.example.yamlfile (already configured with sensible defaults; the daemon writes its ownclient_idback on first launch).
These are downloaded directly from https://raw.githubusercontent.com/Sendspin/sendspin-go/<ref>/dist/... at the resolved release tag (so a --version v1.6.2 install gets the unit/config/env files from that tag, not from main).
The release tarball comes from GitHub's /releases/latest/download/ redirect by default, or /releases/download/<tag>/ when --version is set. No GitHub API call, no JSON parsing.
/etc/default/sendspin-player is touched only on first install OR when --name / --device are passed on this invocation. That makes "re-run with new flags" the documented reconfigure path; "re-run with no flags" is purely an upgrade.
Flow
The script executes the following stages in order. Any non-zero exit aborts the run; idempotency is the recovery story.
- Argument parse.
--name <s>,--device <s>,--version <tag>,--uninstall,-h|--help. Unknown flags abort with usage. - Pre-flight.
- Effective UID must be 0 (else: print sudo hint, exit).
uname -mmust beaarch64(else: print 64-bit-OS hint, exit)./etc/debian_versionmust exist (else: point at README manual steps, exit).command -v systemctlmust succeed.
- Uninstall short-circuit. If
--uninstallis set:systemctl disable --now sendspin-player(tolerate "no such unit" cleanly).- Remove
/usr/local/bin/sendspin-playerand/etc/systemd/system/sendspin-player.service. systemctl daemon-reload.- Print note: "Config preserved at
/etc/sendspin/and/etc/default/sendspin-player. Remove manually for a full purge." - Exit 0.
- Install runtime deps.
apt-get update && apt-get install -y libopus0 libopusfile0 libflac12 libasound2 ca-certificates curl tar.libasound2is the ALSA userspace library miniaudio links against; it is usually present on Pi OS Lite but not guaranteed on a truly minimal image, so we install it explicitly. - Resolve version. Default URL:
https://github.com/Sendspin/sendspin-go/releases/latest/download/sendspin-player-linux-arm64.tar.gz. With--version:https://github.com/Sendspin/sendspin-go/releases/download/<tag>/sendspin-player-linux-arm64.tar.gz. Same logic produces thedist/...raw URL ref:mainfor default,<tag>for pinned. - Stop service if running.
systemctl is-active --quiet sendspin-player && systemctl stop sendspin-player. - Download + extract.
curl -fSL <url> -o $TMP/sp.tar.gz,tar -xzf $TMP/sp.tar.gz -C $TMP,install -m 755 $TMP/sendspin-player /usr/local/bin/sendspin-player.$TMPismktemp -dand removed viaEXITtrap. - Install systemd unit.
curl -fSL <raw-dist-url>/systemd/sendspin-player.service -o /etc/systemd/system/sendspin-player.service. Always overwritten. - Install env file (conditional).
- If
/etc/default/sendspin-playerdoes not exist, fetchdist/systemd/sendspin-player.envfrom raw GitHub. - If
--nameor--devicewere passed, writeSENDSPIN_PLAYER_OPTS="--name <n> --audio-device <d>"to/etc/default/sendspin-player(overwriting). Each flag value is wrapped in single quotes with embedded single quotes escaped via the standard'\''pattern, so names likeBob's Living Roomsurvive intact. Only the flags actually passed are emitted. - Otherwise (file exists, no flags), leave it alone.
- If
- Install YAML config (conditional). If
/etc/sendspin/player.yamldoes not exist,mkdir -p /etc/sendspin && curl -fSL <raw-dist-url>/config/player.example.yaml -o /etc/sendspin/player.yaml. Otherwise leave it alone (preserves anyclient_idthe daemon has already written back). - Reload + enable + start.
systemctl daemon-reload && systemctl enable --now sendspin-player. - Health check. Sleep 2s; check
systemctl is-active sendspin-player. If not active, print last 20 lines ofjournalctl -u sendspin-player --no-pagerand exit non-zero. - Success message. Print resolved version, config file path, env file path, and
journalctl -u sendspin-player -ffor live logs.
Error handling
set -euo pipefailat the top of the script.- Every download uses
curl -fSLso a 404 (e.g. typo'd--version) exits cleanly with the curl error. EXITtrap removes the staging tempdir and, on non-zero status, prints a one-line "If install failed mid-way, re-run the script — it's idempotent" hint.- The binary is
install -m 755'd only after the tarball extracts cleanly. A failed download leaves/usr/local/bin/sendspin-playeruntouched. - Pre-flight failures print the specific missing requirement, never a generic "system not supported".
Testing
- Local smoke: run in
arm64v8/debian:bookworm(without systemd — verify deps install, binary lands, files in place;systemctlsteps will fail loudly which is expected without an init system). - Real Pi: run the actual
curl | bashon a fresh 64-bit Raspberry Pi OS install, confirm the player appears in mDNS and Music Assistant. - Idempotency: run twice on the Pi, confirm second invocation completes cleanly and the service stays up.
- Reconfigure: run with
--nameafter a vanilla install, confirm/etc/default/sendspin-playeris rewritten and the service picks up the new name. - Arch rejection: run on
armv7l(real Pi 2 orqemu-user-static), confirm clean error message naming the supported set. - Uninstall:
bash quickstart-pi.sh --uninstall, confirm service stopped/disabled, binary and unit removed, config preserved. - CI: add
shellcheck scripts/quickstart-pi.shto the existing GitHub Actions lint workflow. No new workflow.
Open questions
None at design time. Flag any post-implementation surprises in the PR.