feat(web): add RPi installer for soundtouch-web + GET /health endpoint

- Add scripts/raspberry-pi/install-web.sh: mirrors install.sh but for
  the stateless soundtouch-web binary (no privileged ports, no data dir,
  no HTTPS). Default port 8080; override via HTTP_PORT at install time.
- Add GET /health to soundtouch-web (handler + mount); returns
  {"status":"ok","version":"…"} — used by the installer's health check
  and by monitoring.
- Update scripts/raspberry-pi/README.md to document both installers side
  by side (installation, config, service management, updates, removal).
- Bump default VERSION to v0.97.0 in all three installer scripts
  (install.sh, install-web.sh, on-device-install/install.sh).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Tobias Gesellchen
2026-05-28 22:59:35 +02:00
co-authored by Claude Sonnet 4.6
parent a5f5bdb916
commit adcdc26d8d
6 changed files with 474 additions and 198 deletions
+134 -192
View File
@@ -1,45 +1,39 @@
Here is a `README.md` you can place next to your install script (or in your repo) to document installation, configuration, updates, and debugging.
# Raspberry Pi installers
Two installer scripts are available, one for each binary:
| Script | Binary | Role | Default port |
|------------------|----------------------|-----------------------------------------|--------------|
| `install.sh` | `soundtouch-service` | Cloud-replacement relay — must run 24/7 | 80 / 443 |
| `install-web.sh` | `soundtouch-web` | Browser control panel — run on demand | 8080 |
Both scripts auto-detect CPU architecture (armv7 / arm64 / amd64), create a systemd unit,
and are safe to re-run for updates.
---
# SoundTouch Service (systemd install)
# soundtouch-service
This setup installs `soundtouch-service` from the official GitHub release and runs it as a hardened systemd service.
It supports:
* Automatic start on boot
* Binding to privileged ports (80 / 443) without running as root
* Config via environment file
* Clean updates
* Safe re-runs of the installer
---
# Installation
Run the installer script:
## Installation
```bash
sudo bash install-soundtouch-service.sh
curl -fsSL -o install.sh \
https://raw.githubusercontent.com/gesellix/Bose-SoundTouch/main/scripts/raspberry-pi/install.sh
sudo bash install.sh
```
You can override defaults:
Override defaults at install time:
```bash
sudo \
VERSION=v0.93.1 \
VERSION=v0.97.0 \
HOSTNAME_FQDN=soundtouch.local \
HTTP_PORT=80 \
HTTPS_PORT=443 \
bash install-soundtouch-service.sh
bash install.sh
```
---
# Configuration
Configuration lives in:
## Configuration
```
/etc/soundtouch-service/soundtouch-service.env
@@ -61,139 +55,41 @@ SERVER_URL=http://soundtouch.local
HTTPS_SERVER_URL=https://soundtouch.local
```
---
# Important: Applying Configuration Changes
If you change the environment file, you must reload and restart the service.
Full roundtrip:
```bash
sudo systemctl daemon-reload
sudo systemctl restart soundtouch-service
```
Usually `daemon-reload` is only needed if the **unit file** changed.
If only the `.env` file changed:
After editing the env file:
```bash
sudo systemctl restart soundtouch-service
```
---
# Service Management
Check status:
## Service management
```bash
systemctl status soundtouch-service
```
Enable at boot:
```bash
sudo systemctl enable soundtouch-service
```
Disable:
```bash
sudo systemctl enable soundtouch-service # start on boot
sudo systemctl disable soundtouch-service
```
Stop / start manually:
```bash
sudo systemctl stop soundtouch-service
sudo systemctl start soundtouch-service
sudo systemctl restart soundtouch-service
```
---
# Logs & Debugging
View recent logs:
## Logs
```bash
journalctl -u soundtouch-service -e --no-pager
journalctl -u soundtouch-service -e --no-pager # recent
journalctl -u soundtouch-service -f # follow
journalctl -u soundtouch-service -b # this boot
```
Follow logs live:
```bash
journalctl -u soundtouch-service -f
```
Show logs from current boot:
```bash
journalctl -u soundtouch-service -b
```
If the service fails to start:
```bash
systemctl status soundtouch-service --no-pager
```
Look for:
* `bind: permission denied` → capability issue
* `address already in use` → port conflict
* permission errors in DATA_DIR → ownership issue
---
# Port Conflicts
Check if 80/443 are in use:
```bash
sudo ss -tulpn | grep -E ':80|:443'
```
If another service is using the port, either:
* stop/disable that service
* or change `PORT` / `HTTPS_PORT` in the env file
Then restart the service.
---
# Updating to a New Version
To upgrade, simply run the installer with the desired version as an argument:
## Updates
```bash
sudo bash install.sh vX.Y.Z
```
The script will:
The script self-updates, downloads the new binary, backs up the old one to `.old`, and
restarts the service. Your env file and data are preserved.
* Automatically fetch the latest version of the installer script for that release
* Download the new service binary
* Backup the old binary to `.old`
* Overwrite the binary and restart the service
No need to reconfigure anything; your existing `.env` file and data will be preserved.
---
# Reinstall / Reset
To fully reset:
```bash
sudo systemctl stop soundtouch-service
sudo rm -rf /var/lib/soundtouch-service/*
sudo systemctl start soundtouch-service
```
To completely remove:
## Removal
```bash
sudo systemctl disable --now soundtouch-service
@@ -206,72 +102,118 @@ sudo systemctl daemon-reload
---
# Architecture Auto-Detection
# soundtouch-web
The installer auto-detects:
* `linux-armv7`
* `linux-arm64`
* `linux-amd64`
Override manually if needed:
## Installation
```bash
sudo ARCH_ASSET=linux-arm64 bash install-soundtouch-service.sh
curl -fsSL -o install-web.sh \
https://raw.githubusercontent.com/gesellix/Bose-SoundTouch/main/scripts/raspberry-pi/install-web.sh
sudo bash install-web.sh
```
Override defaults at install time:
```bash
sudo \
VERSION=v0.97.0 \
HTTP_PORT=8081 \
bash install-web.sh
```
`soundtouch-web` is **stateless** — it holds no persistent data and can be stopped or
restarted at any time without data loss.
## Configuration
```
/etc/soundtouch-web/soundtouch-web.env
```
Example:
```bash
PORT=8080
BIND_ADDR=
DISCOVERY_INTERFACE=
SOUNDTOUCH_DEVICES=
```
`SOUNDTOUCH_DEVICES` accepts a comma-separated list of IP addresses for manual device
registration (useful when mDNS auto-discovery is unreliable on your network).
After editing the env file:
```bash
sudo systemctl restart soundtouch-web
```
## Port conflicts
Port 8080 is a common default for other services. To use a different port, either pass
`HTTP_PORT=<port>` to the installer, or edit the env file after installation:
```bash
sudo ss -tulpn | grep :8080 # check what's using the port
```
## Service management
```bash
systemctl status soundtouch-web
sudo systemctl enable soundtouch-web # start on boot
sudo systemctl disable soundtouch-web
sudo systemctl stop soundtouch-web
sudo systemctl start soundtouch-web
sudo systemctl restart soundtouch-web
```
## Logs
```bash
journalctl -u soundtouch-web -e --no-pager
journalctl -u soundtouch-web -f
```
## Updates
```bash
sudo bash install-web.sh vX.Y.Z
```
## Removal
```bash
sudo systemctl disable --now soundtouch-web
sudo rm /etc/systemd/system/soundtouch-web.service
sudo rm -rf /etc/soundtouch-web
sudo rm /usr/local/bin/soundtouch-web
sudo systemctl daemon-reload
```
---
# Security Notes
# Architecture auto-detection
The service:
Both installers detect the CPU and pick the matching release asset automatically:
* Runs as a dedicated `soundtouch` system user
* Uses `AmbientCapabilities=CAP_NET_BIND_SERVICE`
* Does not require `setcap`
* Does not run as root
* Uses systemd sandboxing (`ProtectSystem`, `PrivateTmp`, etc.)
| `uname -m` | asset suffix |
|---------------------|---------------|
| `aarch64` | `linux-arm64` |
| `armv7l` / `armv6l` | `linux-armv7` |
| `x86_64` | `linux-amd64` |
Override if needed:
```bash
sudo ARCH_ASSET=linux-arm64 bash install.sh
sudo ARCH_ASSET=linux-arm64 bash install-web.sh
```
---
# Quick Troubleshooting Checklist
# Security
If something does not work:
1. Check status:
```
systemctl status soundtouch-service
```
2. Check logs:
```
journalctl -u soundtouch-service -e
```
3. Confirm ports:
```
ss -tulpn | grep -E ':80|:443'
```
4. Confirm env file:
```
cat /etc/soundtouch-service/soundtouch-service.env
```
5. Restart cleanly:
```
sudo systemctl restart soundtouch-service
```
---
If youd like, I can also provide:
* A `make update` style wrapper
* A rollback mechanism
* Or a self-update script with checksum verification
Both services run as the `soundtouch` system user (no login shell, no home directory
ownership required for `soundtouch-web`). `soundtouch-service` additionally uses
`AmbientCapabilities=CAP_NET_BIND_SERVICE` to bind ports 80/443 without root.
+322
View File
@@ -0,0 +1,322 @@
#!/usr/bin/env bash
set -euo pipefail
# ==============================================================================
# Bose-SoundTouch soundtouch-web installer (systemd, headless)
#
# Usage:
# sudo bash install-web.sh [vX.Y.Z]
#
# Examples (override defaults via env vars):
#
# sudo \
# VERSION=v0.95.0 \
# HTTP_PORT=8081 \
# bash install-web.sh
#
# Or with a version argument to perform an update:
# sudo bash install-web.sh v0.95.0
#
# Notes:
# - This script downloads a release binary for your CPU (auto-detects armv7/arm64/amd64).
# - soundtouch-web is stateless (no data directory) — it is safe to stop/restart freely.
# - Default port is 8080 (unprivileged — no special capabilities needed).
# - If soundtouch-service is already installed, soundtouch-web reuses the
# existing soundtouch:soundtouch user/group.
# - Safe to re-run; it will update the binary, env file, and unit and restart.
# ==============================================================================
VERSION="${1:-${VERSION:-v0.97.0}}"
# Normalize version prefix
if [[ ! "$VERSION" =~ ^v ]]; then
VERSION="v${VERSION}"
fi
SERVICE_NAME="${SERVICE_NAME:-soundtouch-web}"
BIN_PATH="${BIN_PATH:-/usr/local/bin/soundtouch-web}"
CONFIG_DIR="${CONFIG_DIR:-/etc/soundtouch-web}"
ENV_FILE="${ENV_FILE:-$CONFIG_DIR/soundtouch-web.env}"
SERVICE_USER="${SERVICE_USER:-soundtouch}"
SERVICE_GROUP="${SERVICE_GROUP:-soundtouch}"
# Port (unprivileged — no CAP_NET_BIND_SERVICE needed)
HTTP_PORT="${HTTP_PORT:-8080}"
# Optional discovery / device config
BIND_ADDR="${BIND_ADDR:-}"
DISCOVERY_INTERFACE="${DISCOVERY_INTERFACE:-}"
SOUNDTOUCH_DEVICES="${SOUNDTOUCH_DEVICES:-}"
# Override if you want to force a specific asset suffix:
# ARCH_ASSET=linux-armv7|linux-arm64|linux-amd64
ARCH_ASSET="${ARCH_ASSET:-}"
# Internal variables
SCRIPT_PATH="$(realpath "$0" 2>/dev/null || echo "$0")"
IS_SELF_UPDATE="${IS_SELF_UPDATE:-false}"
log() { printf "\n==> %s\n" "$*"; }
die() { echo "ERROR: $*" >&2; exit 1; }
need_root() {
[[ "${EUID}" -eq 0 ]] || die "Please run as root (e.g. sudo bash $0)."
}
ensure_cmd() {
command -v "$1" >/dev/null 2>&1 || die "Missing required command: $1"
}
apt_install_if_missing() {
log "Installing dependencies: $*"
apt-get update -y
apt-get install -y --no-install-recommends "$@"
}
detect_arch_asset() {
local m
m="$(uname -m)"
case "$m" in
armv7l|armv6l)
echo "linux-armv7"
;;
aarch64)
echo "linux-arm64"
;;
x86_64|amd64)
echo "linux-amd64"
;;
*)
die "Unsupported architecture from uname -m: $m (set ARCH_ASSET manually)"
;;
esac
}
download_url_for() {
local asset="$1"
echo "https://github.com/gesellix/Bose-SoundTouch/releases/download/${VERSION}/soundtouch-web-${VERSION}-${asset}"
}
ensure_user_group() {
log "Ensuring service user/group exist: ${SERVICE_USER}:${SERVICE_GROUP}"
if ! getent group "${SERVICE_GROUP}" >/dev/null; then
groupadd --system "${SERVICE_GROUP}"
fi
if ! id -u "${SERVICE_USER}" >/dev/null 2>&1; then
useradd --system \
--no-create-home \
--shell /usr/sbin/nologin \
--gid "${SERVICE_GROUP}" \
"${SERVICE_USER}"
fi
}
ensure_dirs() {
log "Creating config directory"
mkdir -p "${CONFIG_DIR}"
chmod 0755 "${CONFIG_DIR}"
}
download_binary() {
local asset url tmp=""
asset="${ARCH_ASSET:-$(detect_arch_asset)}"
url="$(download_url_for "$asset")"
log "Downloading binary for ${asset}: ${url}"
tmp="$(mktemp -d)"
trap 'rm -rf "${tmp}"' EXIT
if command -v curl >/dev/null 2>&1; then
curl -fsSL -o "${tmp}/soundtouch-web" "${url}"
else
wget -qO "${tmp}/soundtouch-web" "${url}"
fi
chmod +x "${tmp}/soundtouch-web"
if [[ -f "${BIN_PATH}" ]]; then
log "Backing up existing binary to ${BIN_PATH}.old"
cp -p "${BIN_PATH}" "${BIN_PATH}.old"
fi
install -m 0755 "${tmp}/soundtouch-web" "${BIN_PATH}"
log "Installed binary to ${BIN_PATH}"
}
self_update() {
if [[ "$IS_SELF_UPDATE" == "true" ]]; then
return
fi
local url="https://raw.githubusercontent.com/gesellix/Bose-SoundTouch/${VERSION}/scripts/raspberry-pi/install-web.sh"
local tmp_script="/tmp/soundtouch-web-install-${VERSION}.sh"
log "Checking for installer updates for ${VERSION}..."
log "URL: ${url}"
if command -v curl >/dev/null 2>&1; then
if ! curl -fsSL -o "${tmp_script}" "${url}"; then
log "⚠️ Could not fetch installer for ${VERSION}, continuing with current script."
return
fi
else
if ! wget -qO "${tmp_script}" "${url}"; then
log "⚠️ Could not fetch installer for ${VERSION}, continuing with current script."
return
fi
fi
if diff -q "${SCRIPT_PATH}" "${tmp_script}" >/dev/null 2>&1; then
log "Installer is already up to date."
rm -f "${tmp_script}"
return
fi
log "Newer installer found for ${VERSION}. Updating ${SCRIPT_PATH} and re-executing..."
install -m 0755 "${tmp_script}" "${SCRIPT_PATH}"
rm -f "${tmp_script}"
export IS_SELF_UPDATE="true"
export VERSION HTTP_PORT BIND_ADDR DISCOVERY_INTERFACE SOUNDTOUCH_DEVICES
export BIN_PATH CONFIG_DIR ENV_FILE SERVICE_USER SERVICE_GROUP
exec "${SCRIPT_PATH}" "$@"
}
write_env_file() {
log "Updating env file: ${ENV_FILE}"
local vars=(
"PORT=${HTTP_PORT}"
"BIND_ADDR=${BIND_ADDR}"
"DISCOVERY_INTERFACE=${DISCOVERY_INTERFACE}"
"SOUNDTOUCH_DEVICES=${SOUNDTOUCH_DEVICES}"
)
if [[ ! -f "${ENV_FILE}" ]]; then
for entry in "${vars[@]}"; do
echo "${entry}" >> "${ENV_FILE}"
done
else
for entry in "${vars[@]}"; do
local key="${entry%%=*}"
local val="${entry#*=}"
if ! grep -q "^${key}=" "${ENV_FILE}"; then
echo "${key}=${val}" >> "${ENV_FILE}"
fi
done
fi
chmod 0640 "${ENV_FILE}"
chown root:"${SERVICE_GROUP}" "${ENV_FILE}" || true
}
write_systemd_unit() {
log "Writing systemd unit: /etc/systemd/system/${SERVICE_NAME}.service"
cat > "/etc/systemd/system/${SERVICE_NAME}.service" <<EOF
[Unit]
Description=Bose SoundTouch Web UI
Wants=network-online.target
After=network-online.target
[Service]
Type=simple
User=${SERVICE_USER}
Group=${SERVICE_GROUP}
EnvironmentFile=${ENV_FILE}
ExecStart=${BIN_PATH}
Restart=on-failure
RestartSec=2
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
[Install]
WantedBy=multi-user.target
EOF
}
reload_enable_start() {
log "Reloading systemd, enabling and starting service"
systemctl daemon-reload
systemctl enable "${SERVICE_NAME}.service"
systemctl restart "${SERVICE_NAME}.service"
log "Verifying service health..."
local health_url="http://localhost:${HTTP_PORT}/health"
local max_retries=5
local count=0
local success=false
while [[ $count -lt $max_retries ]]; do
if curl -fs "$health_url" >/dev/null 2>&1; then
success=true
break
fi
echo "Waiting for service to respond at $health_url... ($((count+1))/$max_retries)"
sleep 2
count=$((count+1))
done
if [[ "$success" = true ]]; then
log "✅ soundtouch-web is healthy and responding!"
else
log "⚠️ Service started but did not respond at $health_url within timeout."
log "Check logs with: journalctl -u ${SERVICE_NAME}.service -n 50"
fi
}
show_status() {
log "Service status"
systemctl --no-pager --full status "${SERVICE_NAME}.service" || true
log "Listening socket (:${HTTP_PORT})"
ss -tulpn | grep -E ":${HTTP_PORT}\b" || true
if command -v ufw >/dev/null 2>&1 && ufw status | grep -q "Status: active"; then
log "Firewall check (UFW is active)"
if ! ufw status | grep -qE "${HTTP_PORT}.*ALLOW"; then
log "⚠️ UFW is active but port ${HTTP_PORT} might be blocked."
log "Run: sudo ufw allow ${HTTP_PORT}/tcp"
else
log "✅ UFW rule for port ${HTTP_PORT} appears to be in place."
fi
fi
cat <<EOF
Open in your browser:
http://<pi-ip>:${HTTP_PORT}/
soundtouch-web is a control panel — you can stop it when not in use:
sudo systemctl stop ${SERVICE_NAME}
sudo systemctl start ${SERVICE_NAME}
Logs:
journalctl -u ${SERVICE_NAME}.service -e --no-pager
EOF
}
main() {
need_root
ensure_cmd systemctl
ensure_cmd ss
if ! command -v curl >/dev/null 2>&1 && ! command -v wget >/dev/null 2>&1; then
apt_install_if_missing curl
fi
self_update "$@"
ensure_user_group
ensure_dirs
download_binary
write_env_file
write_systemd_unit
reload_enable_start
show_status
}
main "$@"
+4 -4
View File
@@ -10,7 +10,7 @@ set -euo pipefail
# Examples (override defaults via env vars):
#
# sudo \
# VERSION=v0.93.1 \
# VERSION=v0.97.0 \
# HOSTNAME_FQDN=soundtouch.local \
# HTTP_PORT=80 \
# HTTPS_PORT=443 \
@@ -18,7 +18,7 @@ set -euo pipefail
# bash install.sh
#
# Or with a version argument to perform an update:
# sudo bash install.sh v0.93.1
# sudo bash install.sh v0.97.0
#
# Notes:
# - This script downloads a release binary for your CPU (auto-detects armv7/arm64/amd64).
@@ -28,7 +28,7 @@ set -euo pipefail
# - Safe to re-run; it will update binary/config/unit and restart the service.
# ==============================================================================
VERSION="${1:-${VERSION:-v0.93.1}}"
VERSION="${1:-${VERSION:-v0.97.0}}"
# Normalize version prefix
if [[ ! "$VERSION" =~ ^v ]]; then
VERSION="v${VERSION}"
@@ -117,7 +117,7 @@ detect_arch_asset() {
download_url_for() {
local asset="$1"
# Release asset pattern used by you earlier:
# soundtouch-service-v0.93.1-linux-armv7
# soundtouch-service-v0.97.0-linux-armv7
echo "https://github.com/gesellix/Bose-SoundTouch/releases/download/${VERSION}/soundtouch-service-${VERSION}-${asset}"
}