Repository navigation
Installation
Five ways in, depending on what you have. If you are on Linux and want the short version, Getting Started on Linux does all of this in one command; Getting Started on Windows is the same for Windows.
| You have | Take |
|---|---|
| A Linux box or a Raspberry Pi | The linux-x86_64, linux-arm64, linux-armv7 or linux-armv6 tarball |
| An Apple-silicon Mac | The macos-arm64 installer .pkg, or the tarball |
| A Windows 10 1809+ or Windows 11 PC | The windows-x86_64 zip |
| Anything else | Build from source |
Everything published is on the
Releases page, and every release
carries all seven archives. Per-commit builds of unreleased work are under the repository's
Actions tab and expire after 14 days — six archives per commit there, linux-armv6 being
built by a workflow of its own rather than on every push. See
the Actions tab.
Every tarball is a staged cmake --install payload, which means every path under usr/
is the path the file installs to (the Windows zip is flat instead; see Windows):
sendspin-cli-0.1.0-linux-arm64/
├── BUILD-INFO.txt # what this build is, and what it needs
└── usr/local/
├── bin/sendspin-cli
├── lib/systemd/system/sendspin-cli.service # Linux only
├── lib/systemd/user/sendspin-cli.service # Linux only
├── lib/sysusers.d/sendspin-cli.conf # Linux only
└── share/doc/sendspin-cli/
├── README.md
├── contributors.md
├── LICENSE
└── sendspin-cli.conf.example
The prefix is baked in at build time — the unit's ExecStart is an absolute
/usr/local/bin/sendspin-cli — so a binary moved somewhere else leaves the unit pointing at
nothing. Read BUILD-INFO.txt first; it names the runtime packages that build needs.
# 1. Take the archive for this machine's architecture, and the checksums
VERSION=0.1.0
# The *userland*, not `uname -m`: a 32-bit Raspberry Pi OS on a Pi 4 runs a 64-bit kernel
# and reports aarch64, and the arm64 archive will not load there.
case "$(dpkg --print-architecture)" in
amd64) LEG=linux-x86_64 ;;
arm64) LEG=linux-arm64 ;;
armhf)
# An ARMv6 board reports armhf too, and it takes an archive of its own: the ARMv7 one's
# instructions are illegal on an ARM1176.
if [ "$(uname -m)" = armv6l ]; then LEG=linux-armv6; else LEG=linux-armv7; fi ;;
*) echo 'no release is built for this architecture'; exit 1 ;;
esac
BASE=https://github.com/Sendspin/sendspin-cpp-cli/releases/download/v$VERSION
curl -fLO "$BASE/sendspin-cli-$VERSION-$LEG.tar.gz"
curl -fLO "$BASE/SHA256SUMS"
# 2. Verify before unpacking anything
sha256sum --ignore-missing -c SHA256SUMS
# 3. Install
sudo tar -xzf "sendspin-cli-$VERSION-$LEG.tar.gz" --strip-components=1 -C / \
"sendspin-cli-$VERSION-$LEG/usr"
sudo systemd-sysusers
sudo systemctl daemon-reloadsystemd-sysusers is the one thing unpacking cannot do for itself. The unit runs as an
unprivileged sendspin-cli account, the archive carries the declaration for it at
usr/local/lib/sysusers.d/sendspin-cli.conf, and a tarball has no postinst to turn one into
the other. It is idempotent, and without it the unit reports 217/USER rather than starting.
See Running as a Service.
--ignore-missing because SHA256SUMS covers every archive the release carries and you
have taken one of them; without it the other four are reported as failures. It is not a
way of passing with nothing checked — sha256sum still exits non-zero if the flag leaves
it with no file to verify.
Naming <name>/usr as the member to extract is what leaves BUILD-INFO.txt in the
archive instead of writing it to /. --strip-components=1 drops the archive's own top
level so the rest lands where it belongs.
Runtime packages, if the binary will not start — ldd /usr/local/bin/sendspin-cli | grep 'not found'
names what is missing, and the getting-started script installs these for you:
# Debian / Ubuntu; libasound2 and libpipewire-0.3-0 before the t64 renames
sudo apt install libasound2t64 libportaudio2 libpulse0 libpipewire-0.3-0t64 libavahi-compat-libdnssd1
# Fedora / RHEL
sudo dnf install alsa-lib portaudio pulseaudio-libs pipewire-libs avahi-compat-libdns_sd
# Arch
sudo pacman -S --needed alsa-lib portaudio libpulse libpipewire avahiTo run it without installing anywhere, unpack it and use it in place:
tar -xzf sendspin-cli-0.1.0-linux-arm64.tar.gz
./sendspin-cli-0.1.0-linux-arm64/usr/local/bin/sendspin-cli --helpThen Running as a Service for the systemd half.
The installer is the easier of the two:
sudo installer -pkg sendspin-cli-0.1.0-macos-arm64.pkg -target /
sendspin-cli --versionA released macOS build needs nothing installed alongside it. Audio goes out through
CoreAudio and discovery through Bonjour, both part of macOS, so there is no Homebrew step
and otool -L on the shipped binary shows only /usr/lib and /System.
It refuses a Mac it cannot run on — the architectures are read off the binary at build time
and declared in the package — so an Intel Mac is turned away rather than told the install
worked. To undo it: remove the four files and
sudo pkgutil --forget io.github.chrisuthe.sendspin-cli.
Or take the tarball, and unpack it from a terminal, not in Finder:
tar -xzf sendspin-cli-0.1.0-macos-arm64.tar.gz
./sendspin-cli-0.1.0-macos-arm64/usr/local/bin/sendspin-cli --versionNeither the binary nor the .pkg is signed or notarized. They are ad-hoc signed — the
minimum an arm64 Mach-O needs to execute at all — so Gatekeeper has no developer identity
to check. Whether you notice depends entirely on the quarantine flag, which tar and
unzip do not propagate and Finder's Archive Utility does. If macOS refuses it:
xattr -d com.apple.quarantine ./sendspin-cli-0.1.0-macos-arm64/usr/local/bin/sendspin-cliA Developer ID signature and notarization are owed and tracked as roadmap item 7, so
sudo installer and the tarball are both affected until that lands.
There is no launchd job in the payload. On macOS the player runs from a shell or under a launch agent you write.
The zip holds one folder: sendspin-cli.exe, BUILD-INFO.txt, README.md,
contributors.md, LICENSE and sendspin-cli.conf.example. It needs nothing installed
alongside it — PortAudio is linked in, and mDNS is Windows' own.
Getting Started on Windows installs it for you, verified and starting at logon. By hand:
Get-FileHash .\sendspin-cli-0.5.0-windows-x86_64.zip -Algorithm SHA256 # compare with SHA256SUMS
Expand-Archive .\sendspin-cli-0.5.0-windows-x86_64.zip .
Get-ChildItem .\sendspin-cli-0.5.0-windows-x86_64 | Unblock-File
.\sendspin-cli-0.5.0-windows-x86_64\sendspin-cli.exe --versionThe binary is not signed, so SmartScreen may warn the first time it runs; that is what
checking it against SHA256SUMS is for. There is no Windows service in the payload: the
guided installer starts it at logon from a scheduled task instead.
The Pi takes the linux-arm64 archive on a 64-bit OS and the linux-armv7 one on a 32-bit
OS, like any other Linux host. An ARMv6 board — a Pi Zero, a Pi Zero W, an original Pi — takes
linux-armv6, which is built for an ARM1176 and, unlike the others, loads on bookworm as well
as trixie. See
Getting Started on a Raspberry Pi.
For an architecture with no release — an Intel Mac, anything CI does not build — or to build against a different version of the library.
sudo apt install pkg-config libasound2-dev portaudio19-dev libavahi-compat-libdnssd-dev # Debian / Ubuntu
sudo dnf install pkgconf alsa-lib-devel portaudio-devel avahi-compat-libdns_sd-devel # Fedora / RHEL
# macOS needs nothing: CoreAudio and Bonjour are part of the system. For the optional
# PortAudio backend as well, `brew install portaudio pkgconf`.
git clone https://github.com/Sendspin/sendspin-cpp-cli.git
cd sendspin-cpp-cli
cmake -B build
cmake --build build
./build/sendspin-cli --helpOn Windows, build with clang-cl from a Visual Studio developer prompt with the "C++ Clang tools for Windows" component installed; PortAudio is built from source and mDNS needs nothing:
cmake -B build -G Ninja -DCMAKE_C_COMPILER=clang-cl -DCMAKE_CXX_COMPILER=clang-cl
cmake --build build
.\build\sendspin-cli.exe --helpNeeds CMake ≥ 3.16, a C++20 compiler, and network access on the first configure —
sendspin-cpp is pulled in with FetchContent at a pinned tag and fetches its own
dependencies in turn.
The audio backends and mDNS are optional and auto-detected, so read the configure
output rather than assuming; a missing -dev package does not fail a configure, it just
produces a binary that cannot do that thing:
-- sendspin-cli audio backends: null, stdout, alsa, portaudio
-- sendspin-cli mDNS: dns_sd (/usr/lib/aarch64-linux-gnu/libdns_sd.so)
To install what you built:
cmake -B build -DCMAKE_INSTALL_PREFIX=/usr/local # the prefix is chosen HERE
cmake --build build
sudo cmake --install build --component sendspin-cli--component sendspin-cli is not garnish — without it, cmake --install also stages 143
files belonging to a fetched dependency. The prefix is fixed at configure time because
the unit's ExecStart names it absolutely, so reconfigure rather than passing
--install --prefix. Building from source is covered in
contributors.md.
- Configuration — pick an output device and make it stick
- Running as a Service — the systemd unit
- Controlling the Player — drive it from its own host
Get it running
Use it
When it misbehaves