mirror of
https://github.com/gesellix/Bose-SoundTouch.git
synced 2026-08-13 22:26:14 +00:00
The on-device and Raspberry Pi installers hardcoded the release version, which had to be bumped on every release. Default VERSION to empty and resolve the newest tag by following GitHub's documented stable redirect (https://github.com/<repo>/releases/latest -> .../releases/tag/vX.Y.Z), reading the effective URL. This avoids the GitHub API rate limit and needs no jq. An explicit version (positional arg / VERSION= / --version) still pins a release. If the lookup fails (offline, rate-limited, or a curl without -w support), each script falls back to a pinned FALLBACK_VERSION so installs still work. The Pi self_update path runs after resolution, so it fetches the resolved tag's installer. Docs updated to state the default installs the latest release; the pinned-version examples remain as illustrations. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
169 lines
6.5 KiB
Bash
169 lines
6.5 KiB
Bash
#!/bin/bash
|
|
set -eo pipefail
|
|
|
|
# Version to install. Left empty by default so the canonical one-liner
|
|
# curl -sSL .../install.sh | sh
|
|
# resolves and installs the latest release automatically (see below).
|
|
#
|
|
# Pin a specific version via environment variable or the --version/-v flag:
|
|
# VERSION=0.111.3 curl -sSL .../install.sh | sh
|
|
# curl -sSL .../install.sh | sh -s -- --version 0.111.3
|
|
VERSION=${VERSION:-}
|
|
|
|
# Parse optional command-line arguments so the script can be invoked as:
|
|
# install.sh --version 0.111.3
|
|
# install.sh -v 0.111.3
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--version|-v)
|
|
if [ -z "$2" ]; then
|
|
echo "ERROR: --version requires an argument." >&2; exit 1
|
|
fi
|
|
VERSION="$2"; shift 2;;
|
|
--) shift; break;;
|
|
*) echo "Unknown argument: $1" >&2; exit 1;;
|
|
esac
|
|
done
|
|
|
|
GH_REPO=${GH_REPO:-gesellix/Bose-SoundTouch}
|
|
|
|
# Used only when the latest-release lookup fails (offline / rate-limited /
|
|
# a curl without -w support).
|
|
FALLBACK_VERSION=${FALLBACK_VERSION:-0.111.3}
|
|
|
|
# Resolve the latest release when no explicit version was provided, by
|
|
# following the stable redirect https://github.com/<repo>/releases/latest
|
|
# -> .../releases/tag/vX.Y.Z and taking the tag from the final URL. The
|
|
# leading "v" is stripped because the URLs below add it back (v$VERSION).
|
|
if [ -z "$VERSION" ]; then
|
|
LATEST_URL="https://github.com/$GH_REPO/releases/latest"
|
|
echo "Resolving latest release via $LATEST_URL ..."
|
|
EFFECTIVE=$(curl -sSLI -o /dev/null -w '%{url_effective}' "$LATEST_URL" 2>/dev/null) || true
|
|
TAG=${EFFECTIVE##*/}
|
|
VER=${TAG#v}
|
|
case "$VER" in
|
|
[0-9]*.[0-9]*) VERSION="$VER" ;;
|
|
*)
|
|
VERSION="$FALLBACK_VERSION"
|
|
echo "WARNING: could not resolve latest release; using fallback $VERSION" >&2
|
|
;;
|
|
esac
|
|
fi
|
|
|
|
BINARY_URL=${BINARY_URL:-https://github.com/$GH_REPO/releases/download/v$VERSION/soundtouch-service-v$VERSION-linux-armv7}
|
|
INIT_SCRIPT_URL=${INIT_SCRIPT_URL:-https://raw.githubusercontent.com/$GH_REPO/v$VERSION/scripts/on-device-install/aftertouch}
|
|
|
|
# Default install location is /mnt/nv/aftertouch (the persistent
|
|
# partition), not /opt/aftertouch on rootfs. Stock SoundTouch rootfs
|
|
# has ~4 MB free on devices like the ST20 (issue #268); the
|
|
# AfterTouch binary is ~12 MB. /mnt/nv typically has tens of MB
|
|
# free and persists across reboots the same way /opt would.
|
|
#
|
|
# /opt/aftertouch becomes a symlink into the install target so the
|
|
# init script's hardcoded DAEMON path keeps working unchanged.
|
|
#
|
|
# Power users can override with INSTALL_DIR=/some/other/path.
|
|
INSTALL_DIR=${INSTALL_DIR:-/mnt/nv/aftertouch}
|
|
|
|
# Scratch directory for the download. /media is tmpfs on most
|
|
# SoundTouch firmware, fine for transient files but unrelated to
|
|
# the persistent install target.
|
|
UPDATE_TMP_DIR=${UPDATE_TMP_DIR:-/media/aftertouch}
|
|
|
|
rm -rf "$UPDATE_TMP_DIR" || true
|
|
mkdir -p "$UPDATE_TMP_DIR"
|
|
|
|
echo "Installing AfterTouch $VERSION to $INSTALL_DIR ..."
|
|
mkdir -p "$INSTALL_DIR"
|
|
|
|
# Wire /opt/aftertouch -> $INSTALL_DIR so the init script
|
|
# (DAEMON=/opt/aftertouch/aftertouch-service) finds the binary
|
|
# regardless of which target we picked. Replace any prior
|
|
# /opt/aftertouch (directory or stale symlink) before re-creating.
|
|
if [ "$INSTALL_DIR" != "/opt/aftertouch" ]; then
|
|
rm -rf /opt/aftertouch
|
|
ln -sf "$INSTALL_DIR" /opt/aftertouch
|
|
fi
|
|
|
|
curl \
|
|
-sSL \
|
|
-o "$UPDATE_TMP_DIR/binary" \
|
|
--fail \
|
|
"$BINARY_URL"
|
|
|
|
# Back up the current binary before overwriting so a one-step rollback
|
|
# is always available. The version string comes from the binary itself;
|
|
# if it is absent (very old build or corrupted) we fall back to a timestamp.
|
|
BACKUP_FILE=""
|
|
if [ -f "$INSTALL_DIR/aftertouch-service" ]; then
|
|
current_version=$("$INSTALL_DIR/aftertouch-service" --version 2>/dev/null \
|
|
| awk '{print $NF}') || true
|
|
if [ -z "$current_version" ] || [ "$current_version" = "dev" ]; then
|
|
current_version=$(date +%Y%m%d-%H%M%S)
|
|
fi
|
|
BACKUP_FILE="$INSTALL_DIR/aftertouch-service.${current_version}.backup"
|
|
cp -p "$INSTALL_DIR/aftertouch-service" "$BACKUP_FILE"
|
|
echo "Backed up current binary ($current_version) → $BACKUP_FILE"
|
|
fi
|
|
|
|
mv "$UPDATE_TMP_DIR/binary" "$INSTALL_DIR/aftertouch-service"
|
|
chmod +x "$INSTALL_DIR/aftertouch-service"
|
|
|
|
# Keep only the backup we just created; prune all older *.backup, *.old, and
|
|
# *.new artefacts left by earlier installs. /mnt/nv is small (tens of MB),
|
|
# so accumulation quickly causes "no space left on device" during downloads.
|
|
if [ -n "$BACKUP_FILE" ]; then
|
|
echo "Disk usage before GC:"; df -h "$INSTALL_DIR"
|
|
for f in "$INSTALL_DIR/aftertouch-service".*.backup \
|
|
"$INSTALL_DIR/aftertouch-service".*.old \
|
|
"$INSTALL_DIR/aftertouch-service.new"; do
|
|
[ -f "$f" ] || continue
|
|
[ "$f" = "$BACKUP_FILE" ] && continue
|
|
rm -f "$f"
|
|
echo "Removed stale artefact: $f"
|
|
done
|
|
echo "Disk usage after GC:"; df -h "$INSTALL_DIR"
|
|
fi
|
|
|
|
echo "Creating init script..."
|
|
curl \
|
|
-sSL \
|
|
-o "$UPDATE_TMP_DIR/init-script" \
|
|
--fail \
|
|
"$INIT_SCRIPT_URL"
|
|
|
|
mv "$UPDATE_TMP_DIR/init-script" /etc/init.d/aftertouch
|
|
chmod +x /etc/init.d/aftertouch
|
|
update-rc.d aftertouch defaults
|
|
|
|
echo "Installation complete. Running initial startup..."
|
|
/etc/init.d/aftertouch start
|
|
|
|
/etc/init.d/aftertouch status
|
|
|
|
# Post-install verification: the init script's own poll loop only
|
|
# checks that the daemon registered a PID file; that's not enough
|
|
# evidence the listener is actually serving HTTP. Issue #250 shipped
|
|
# with a "running but unreachable" state where status was green and
|
|
# `curl :8000` got connection-refused. Re-check directly here and
|
|
# surface the recent syslog if it fails — the init script pipes the
|
|
# daemon's stdout/stderr through `logger -t aftertouch`, so panics
|
|
# land in busybox syslog and `logread` reads them out.
|
|
if curl -fsS --max-time 10 http://localhost:8000 >/dev/null 2>&1; then
|
|
echo "Installation complete. AfterTouch $VERSION is now running on your device."
|
|
echo "Connect to http://<your-device-ip>:8000 from another machine on the LAN."
|
|
echo "If the device doesn't expose :8000 directly, port-forward via SSH:"
|
|
echo " ssh -L 8000:localhost:8000 root@<IP_ADDRESS_OF_SPEAKER>"
|
|
else
|
|
echo "WARNING: the init script reports AfterTouch as running, but" >&2
|
|
echo " http://localhost:8000 isn't responding. The daemon may have" >&2
|
|
echo " panicked shortly after start. Recent aftertouch syslog:" >&2
|
|
echo "" >&2
|
|
logread 2>/dev/null | grep aftertouch | tail -20 >&2 || \
|
|
echo " (logread returned nothing for tag 'aftertouch'; the daemon" >&2
|
|
echo "" >&2
|
|
echo " For a live view of the daemon's output, run:" >&2
|
|
echo " logread -f | grep aftertouch" >&2
|
|
exit 1
|
|
fi
|