diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 5d7b16f..b705621 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -9,7 +9,14 @@ * [Initial Device Setup](guides/DEVICE-INITIAL-SETUP.md) * [HTTPS Setup](guides/HTTPS-SETUP.md) * [Deployment](guides/DEPLOYMENT.md) +* [Raspberry Pi Guide](guides/RASPBERRY-PI.md) * [Troubleshooting](guides/TROUBLESHOOTING.md) +* [Useful Links](#useful-links) + +### Useful Links +* [Cloud Shutdown Survival Guide](guides/SURVIVAL-GUIDE.md) +* [Raspberry Pi Installer](../../scripts/raspberry-pi/README.md) +* [CLI Reference](guides/CLI-REFERENCE.md) ## Technical Reference * [API Cookbook](reference/API-COOKBOOK.md) diff --git a/docs/guides/DEPLOYMENT.md b/docs/guides/DEPLOYMENT.md index 4286278..09862e5 100644 --- a/docs/guides/DEPLOYMENT.md +++ b/docs/guides/DEPLOYMENT.md @@ -13,6 +13,10 @@ This guide covers everything you need to know to deploy robust, scalable SoundTo - [Performance Optimization](#performance-optimization) - [Error Handling Recovery](#error-handling-recovery) - [Deployment Strategies](#deployment-strategies) + - [Docker Deployment](#docker-deployment) + - [Kubernetes Deployment](#kubernetes-deployment) + - [Systemd Service](#systemd-service) + - [Raspberry Pi Installer](#raspberry-pi-installer) - [Maintenance Operations](#maintenance-operations) --- @@ -926,39 +930,49 @@ data: device_hosts: "192.168.1.100,192.168.1.101,192.168.1.102" ``` -### Systemd Service +#### Systemd Service + +A standard systemd unit for manual installation. This example assumes the binary is at `/usr/local/bin/soundtouch-service` and data is stored in `/var/lib/soundtouch-service`. ```ini -# /etc/systemd/system/soundtouch.service +# /etc/systemd/system/soundtouch-service.service [Unit] -Description=SoundTouch Control Service -After=network.target -Wants=network.target +Description=Bose SoundTouch Service +Wants=network-online.target +After=network-online.target [Service] Type=simple User=soundtouch Group=soundtouch -WorkingDirectory=/opt/soundtouch -ExecStart=/opt/soundtouch/bin/soundtouch-app -ExecReload=/bin/kill -HUP $MAINPID -Restart=always -RestartSec=5 -Environment=DEVICE_HOSTS=192.168.1.100,192.168.1.101 -Environment=LOG_LEVEL=info -Environment=CONFIG_FILE=/opt/soundtouch/config/production.yaml +WorkingDirectory=/var/lib/soundtouch-service +ExecStart=/usr/local/bin/soundtouch-service +Environment=PORT=80 +Environment=SERVER_URL=http://soundtouch.local -# Security settings -NoNewPrivileges=true -ProtectSystem=strict -ProtectHome=true -ReadWritePaths=/opt/soundtouch/logs +# Allow binding to privileged ports (80/443) without running as root +AmbientCapabilities=CAP_NET_BIND_SERVICE +CapabilityBoundingSet=CAP_NET_BIND_SERVICE + +Restart=on-failure +RestartSec=5 + +# Security hardening PrivateTmp=true +ProtectSystem=full +ProtectHome=true +ReadWritePaths=/var/lib/soundtouch-service [Install] WantedBy=multi-user.target ``` +#### Raspberry Pi Installer + +For users deploying on a Raspberry Pi, we provide a specialized automated installer that handles everything from architecture detection to security hardening. + +See the [Raspberry Pi Installation Guide](RASPBERRY-PI.md) for step-by-step instructions. + --- ## Maintenance Operations @@ -1071,4 +1085,4 @@ func init() { // Set GC target percentage if os.Getenv("GOGC") == "" { - debug.SetGCPerc \ No newline at end of file + debug.SetGCPerc diff --git a/docs/guides/RASPBERRY-PI.md b/docs/guides/RASPBERRY-PI.md new file mode 100644 index 0000000..ae6360f --- /dev/null +++ b/docs/guides/RASPBERRY-PI.md @@ -0,0 +1,59 @@ +# Raspberry Pi Installation Guide + +This guide explains how to install the `soundtouch-service` as a persistent systemd service on a Raspberry Pi (tested on Raspberry Pi Zero 2W, 3, and 4). + +## Automated Installer + +We provide a specialized installer script located in the `scripts/raspberry-pi/` directory of the repository. + +### Features +* **Automatic start on boot**: Installs a systemd unit. +* **Non-root operation**: Uses `AmbientCapabilities` to bind to ports 80/443 without root privileges. +* **Arch Detection**: Automatically selects the correct binary for `armv7`, `arm64`, or `amd64`. +* **Easy Updates**: Re-running the script updates the binary to the latest version. + +### Installation Steps + +1. **Download the installer**: + ```bash + curl -fsSL -o install.sh https://raw.githubusercontent.com/gesellix/bose-soundtouch/main/scripts/raspberry-pi/install.sh + ``` + +2. **Run with sudo**: + ```bash + sudo bash install.sh + ``` + +### Overriding Defaults + +You can customize the installation using environment variables: + +```bash +sudo \ + VERSION=v0.17.0 \ + HOSTNAME_FQDN=soundtouch.local \ + HTTP_PORT=80 \ + HTTPS_PORT=443 \ + bash install.sh +``` + +## Management + +Once installed, use standard `systemctl` commands to manage the service: + +```bash +# Check status +systemctl status soundtouch-service + +# Follow logs +journalctl -u soundtouch-service -f + +# Restart +sudo systemctl restart soundtouch-service +``` + +## Configuration + +Configuration is stored in `/etc/soundtouch-service/soundtouch-service.env`. Note that settings saved via the Web UI (in `settings.json`) will take precedence over these environment variables once the service is running. + +For more details, see the [scripts/raspberry-pi/README.md](../../scripts/raspberry-pi/README.md) in the repository. diff --git a/scripts/raspberry-pi/install.sh b/scripts/raspberry-pi/install.sh index 1486bf6..d1f4dd0 100644 --- a/scripts/raspberry-pi/install.sh +++ b/scripts/raspberry-pi/install.sh @@ -120,7 +120,13 @@ ensure_user_group() { ensure_dirs() { log "Creating directories" mkdir -p "${CONFIG_DIR}" "${DATA_DIR}" - chown -R "${SERVICE_USER}:${SERVICE_GROUP}" "${DATA_DIR}" + + # Optimized ownership check: only chown if not already owned by service user + if [[ "$(stat -c '%U:%G' "${DATA_DIR}")" != "${SERVICE_USER}:${SERVICE_GROUP}" ]]; then + log "Adjusting ownership of ${DATA_DIR} to ${SERVICE_USER}:${SERVICE_GROUP}" + chown -R "${SERVICE_USER}:${SERVICE_GROUP}" "${DATA_DIR}" + fi + chmod 0755 "${CONFIG_DIR}" "${DATA_DIR}" } @@ -136,10 +142,17 @@ download_binary() { if command -v curl >/dev/null 2>&1; then curl -fsSL -o "${tmp}/soundtouch-service" "${url}" else - wget -O "${tmp}/soundtouch-service" "${url}" + wget -qO "${tmp}/soundtouch-service" "${url}" fi chmod +x "${tmp}/soundtouch-service" + + # Backup existing binary if it exists + 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-service" "${BIN_PATH}" log "Installed binary to ${BIN_PATH}" } @@ -201,8 +214,31 @@ EOF reload_enable_start() { log "Reloading systemd, enabling and starting service" systemctl daemon-reload - systemctl enable --now "${SERVICE_NAME}.service" + 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 "✅ Service is healthy and responding!" + else + log "⚠️ Service started but did not respond to health check at $health_url within timeout." + log "Check logs with: journalctl -u ${SERVICE_NAME}.service -n 50" + fi } show_status() { @@ -212,6 +248,16 @@ show_status() { log "Listening sockets (${HTTP_PORT}/${HTTPS_PORT})" ss -tulpn | grep -E ":((${HTTP_PORT})|(${HTTPS_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|${HTTPS_PORT}.*ALLOW"; then + log "⚠️ UFW is active but ports ${HTTP_PORT}/${HTTPS_PORT} might be blocked." + log "Run: sudo ufw allow ${HTTP_PORT}/tcp && sudo ufw allow ${HTTPS_PORT}/tcp" + else + log "✅ UFW rules for service ports appear to be in place." + fi + fi + cat <