#!/bin/sh # Install Prismix on a Raspberry Pi running Raspberry Pi OS (64-bit), or bring it up to date, and # run it in auto mode from boot (docs/installer.md). Run on the Pi as root: # # curl https://cli.prismix.cymant.com | sudo sh # # Settings, as environment variables (after sudo): PRISMIX_RELEASES, where the releases are (the # folder scripts/release.sh writes, served over HTTP(S); https://cli.prismix.cymant.com if unset, until cli.getprismix.com); # PRISMIX_INTERFACE, the players' interface (eth0 if unset); PRISMIX_LOG, the log level (info if # unset). PRISMIX_ROOT puts every installed file under another folder, for scripts/test-install.sh # only. # # The script is only definitions until its last line, so a download cut off anywhere runs nothing. # It reads nothing from its input, which is the script itself. set -eu fail() { echo "$*" >&2 exit 1 } # One URL to a file, with curl or else wget. fetch() { if command -v curl >/dev/null 2>&1; then curl -fsSL -o "$2" "$1" /dev/null 2>&1; then wget -q -O "$2" "$1" /dev/null 2>&1 || fail "Prismix needs systemd, which this system does not have." # Raspberry Pi OS before Trixie (Debian 13) has no link-local fallback for the players' Ethernet # (set_ethernet), so it is refused. The founder, 2026-10-31: "old OS, just, yeah, refuse it." if [ -r "$root/etc/os-release" ]; then system=$(sed -n 's/^ID=//p' "$root/etc/os-release" | tr -d '"') version=$(sed -n 's/^VERSION_ID=//p' "$root/etc/os-release" | tr -d '"') codename=$(sed -n 's/^VERSION_CODENAME=//p' "$root/etc/os-release" | tr -d '"') case "$system" in debian | raspbian) [ "${version:-0}" -ge 13 ] 2>/dev/null || fail "Prismix needs Raspberry Pi OS Trixie or later; this Pi runs ${codename:-an older one}. Write Raspberry Pi OS Lite (64-bit) to its card with Raspberry Pi Imager, then run this again." ;; esac fi } # The release the pointer names, downloaded and checked, unpacked into $work/release. download() { fetch "$releases/latest" "$work/latest" || fail "Could not download the release pointer from $releases/latest." commit=$(sed -n 's/^commit //p' "$work/latest") archive=$(sed -n 's/^archive //p' "$work/latest") sha256=$(sed -n 's/^sha256 //p' "$work/latest") case "$archive" in '' | *[!A-Za-z0-9._-]*) fail "The release pointer at $releases/latest is not one Prismix wrote." ;; esac [ -n "$commit" ] && [ -n "$sha256" ] || fail "The release pointer at $releases/latest is not one Prismix wrote." echo "Downloading Prismix $commit." fetch "$releases/$archive" "$work/$archive" || fail "Could not download $releases/$archive." [ "$(sha256sum "$work/$archive" | cut -d ' ' -f 1)" = "$sha256" ] || fail "The download of Prismix $commit is damaged (its SHA-256 does not match): nothing was changed." mkdir "$work/release" tar -xzf "$work/$archive" -C "$work/release" prismix record-network.sh 2>/dev/null || fail "The archive of Prismix $commit is damaged: nothing was changed." } # tcpdump records the players' network; without it Prismix runs and the recorder waits. install_tcpdump() { command -v tcpdump >/dev/null 2>&1 && return 0 echo "Installing tcpdump, to record the players' network." DEBIAN_FRONTEND=noninteractive apt-get install -y tcpdump /dev/null 2>&1 || echo "Recording the players' network waits for tcpdump (no internet for apt?)." } # Each file is copied next to its place, the one there kept as .previous, then renamed in: a link # at the place is replaced, never written through. place() { install -D -m 755 "$1" "$2.new" if [ -e "$2" ] || [ -L "$2" ]; then mv -f "$2" "$2.previous" else rm -f "$2.previous" fi mv -f "$2.new" "$2" } restore() { if [ -e "$1.previous" ] || [ -L "$1.previous" ]; then mv -f "$1.previous" "$1" fi } # A build that does not stay running is undone: the previous one runs again. check_running() { sleep 5 systemctl is-active --quiet prismix "$units/prismix.service" printf '%s\n' \ '[Unit]' \ "Description=Prismix: record the players' network when enabled" \ 'After=network.target' \ '' \ '[Service]' \ "ExecStart=/usr/local/lib/prismix/record-network.sh $interface" \ 'Restart=always' \ 'RestartSec=5' \ '' \ '[Install]' \ 'WantedBy=multi-user.target' >"$units/prismix-record.service" } # The players' interface takes an address from a router if there is one, or else picks its own # link-local one (169.254.x.x), as the players do: so Prismix works on a switch with no router, the # usual DJ booth, and on a network with one. The founder, 2026-10-31: "Yeah, fallback sounds good." # Fallback needs NetworkManager 1.52 (Raspberry Pi OS Trixie). It keeps asking for a router: with # NetworkManager's 45-second limit, the connection fails each time no router answers, and after # four failures it gives up and drops the link-local address too. set_ethernet() { if ! command -v nmcli >/dev/null 2>&1; then echo "NetworkManager is not installed: $interface is left as it is." return 0 fi name=$(nmcli -t -f NAME,DEVICE connection show /dev/null 2>&1; then echo "This Raspberry Pi OS is older than Trixie: $interface is left as it is, so players on a switch with no router may not find Prismix." return 0 fi [ "$(nmcli -g ipv4.method,ipv4.link-local,ipv4.dhcp-timeout connection show "$name" /dev/null 2>&1 || echo "$interface takes its address when its cable is in." } start() { systemctl daemon-reload