feat(install): default installers to the latest release via releases/latest

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>
This commit is contained in:
Tobias Gesellchen
2026-06-25 09:47:19 +02:00
co-authored by Claude Opus 4.8
parent 01ebbb102d
commit ea7f6f36ef
8 changed files with 133 additions and 17 deletions
@@ -34,10 +34,10 @@ sudo bash install.sh
```
The installer detects your Pi's architecture (armv7, arm64, or amd64), downloads
the binary, creates a `soundtouch` system user, and registers a systemd unit that
starts on boot.
the latest release binary, creates a `soundtouch` system user, and registers a
systemd unit that starts on boot.
To install a specific version:
To pin a specific version instead of the latest:
```bash
sudo bash install.sh v0.111.3
@@ -81,7 +81,8 @@ currently running binary, and starts the service:
rw && curl -sSL https://raw.githubusercontent.com/gesellix/Bose-SoundTouch/main/scripts/on-device-install/install.sh | sh
```
To target a specific version instead of the default:
By default this installs the **latest release** — the script resolves it from
GitHub's `releases/latest` redirect. To target a specific version instead:
```bash
# Via environment variable (works with pipe-to-sh)
+2
View File
@@ -13,6 +13,8 @@ Two scripts are available, one per binary:
Both auto-detect CPU architecture (armv7 / arm64 / amd64), create a `soundtouch`
system user, and install a systemd unit. They are safe to re-run for updates.
Run without a version argument, they install the **latest release** (resolved
from GitHub's `releases/latest` redirect); pass a tag to pin a specific version.
Each installer has a matching uninstaller (`uninstall.sh`, `uninstall-player.sh`).
> `install-web.sh` is the previous name for `install-player.sh`. It still works
+9 -1
View File
@@ -48,6 +48,10 @@ Then, run the following command to install AfterTouch on the device.
rw && curl -sSL https://raw.githubusercontent.com/gesellix/Bose-SoundTouch/main/scripts/on-device-install/install.sh | sh
```
This installs the **latest release** by default (the script resolves it from
GitHub's `releases/latest` redirect). To pin a specific version, see
[Updating AfterTouch](#updating-aftertouch) below.
After the installation check if you can access AfterTouch from your local device by navigating to `http://<IP_ADDRESS_OF_SPEAKER>:8000`. If you can access the AfterTouch UI, you're good to go!
### If `http://<IP_ADDRESS_OF_SPEAKER>:8000` fails: SSH port forwarding
@@ -101,7 +105,11 @@ curl -sSLo install.sh https://raw.githubusercontent.com/gesellix/Bose-SoundTouch
sh install.sh --version 0.111.3
```
Running **without** a version override installs the version hard-coded in the script (the latest release at the time the script was published). That default is updated with each release; if you're running from `main`, it reflects the most recent tagged version.
Running **without** a version override installs the latest release: the script
follows GitHub's `https://github.com/gesellix/Bose-SoundTouch/releases/latest`
redirect to discover the newest tag. If that lookup fails (offline, or a `curl`
build without `-w` support), it falls back to a pinned version baked into the
script.
> **Tip — rollback:** if the new binary misbehaves, the installer left a `.backup` file alongside it:
> ```bash
+28 -5
View File
@@ -1,15 +1,14 @@
#!/bin/bash
set -eo pipefail
# Default version installed when no override is provided. Update this value
# each time a new release is cut so that running the canonical one-liner
# Version to install. Left empty by default so the canonical one-liner
# curl -sSL .../install.sh | sh
# picks up the latest binary without extra arguments.
# resolves and installs the latest release automatically (see below).
#
# Override via environment variable or the --version/-v flag:
# 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:-0.111.3}
VERSION=${VERSION:-}
# Parse optional command-line arguments so the script can be invoked as:
# install.sh --version 0.111.3
@@ -27,6 +26,30 @@ while [ $# -gt 0 ]; do
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}
+3 -1
View File
@@ -26,7 +26,9 @@ curl -fsSL -o install-player.sh \
sudo bash install-player.sh
```
Pass a version tag as the first argument to pin a specific release:
Both install the **latest release** by default (resolved from GitHub's
`releases/latest` redirect). Pass a version tag as the first argument to pin a
specific release:
```bash
sudo bash install.sh v0.111.3
+43 -3
View File
@@ -32,11 +32,16 @@ set -euo pipefail
# - Safe to re-run; it will update the binary, env file, and unit and restart.
# ==============================================================================
VERSION="${1:-${VERSION:-v0.111.3}}"
# Normalize version prefix
if [[ ! "$VERSION" =~ ^v ]]; then
# Release to install. Empty means "resolve the latest release" (see
# resolve_version). Pass a tag/number as $1 or VERSION=... to pin a release.
VERSION="${1:-${VERSION:-}}"
# Normalize version prefix for an explicitly provided version.
if [[ -n "$VERSION" && ! "$VERSION" =~ ^v ]]; then
VERSION="v${VERSION}"
fi
GH_REPO="${GH_REPO:-gesellix/Bose-SoundTouch}"
# Used only when the latest-release lookup fails (offline / rate-limited).
FALLBACK_VERSION="${FALLBACK_VERSION:-v0.111.3}"
SERVICE_NAME="${SERVICE_NAME:-soundtouch-player}"
BIN_PATH="${BIN_PATH:-/usr/local/bin/soundtouch-player}"
@@ -157,6 +162,40 @@ download_binary() {
log "Installed binary to ${BIN_PATH}"
}
resolve_version() {
# When no explicit version was given, resolve the latest release tag by
# following the documented stable redirect:
# https://github.com/<owner>/<repo>/releases/latest
# which 302-redirects to .../releases/tag/vX.Y.Z. We read the final URL and
# take the tag from it. Falls back to FALLBACK_VERSION on any failure
# (offline, rate-limited, no usable curl/wget).
if [[ -n "$VERSION" ]]; then
return
fi
local latest_url="https://github.com/${GH_REPO}/releases/latest"
log "Resolving latest release via ${latest_url}"
local effective="" tag=""
if command -v curl >/dev/null 2>&1; then
effective="$(curl -fsSLI -o /dev/null -w '%{url_effective}' "$latest_url" 2>/dev/null)" || true
else
# wget: don't follow the redirect, read the Location header instead.
effective="$(wget -S --max-redirect=0 -O /dev/null "$latest_url" 2>&1 \
| awk 'tolower($1) ~ /location:/ {print $2}' | tr -d '\r' | tail -1)" || true
fi
tag="${effective##*/}"
if [[ "$tag" =~ ^v?[0-9]+\.[0-9]+ ]]; then
[[ "$tag" =~ ^v ]] || tag="v${tag}"
VERSION="$tag"
log "Latest release is ${VERSION}"
else
VERSION="$FALLBACK_VERSION"
log "⚠️ Could not resolve latest release; falling back to ${VERSION}"
fi
}
self_update() {
if [[ "$IS_SELF_UPDATE" == "true" ]]; then
return
@@ -324,6 +363,7 @@ main() {
apt_install_if_missing curl
fi
resolve_version
self_update "$@"
ensure_user_group
+43 -3
View File
@@ -28,11 +28,16 @@ set -euo pipefail
# - Safe to re-run; it will update binary/config/unit and restart the service.
# ==============================================================================
VERSION="${1:-${VERSION:-v0.111.3}}"
# Normalize version prefix
if [[ ! "$VERSION" =~ ^v ]]; then
# Release to install. Empty means "resolve the latest release" (see
# resolve_version). Pass a tag/number as $1 or VERSION=... to pin a release.
VERSION="${1:-${VERSION:-}}"
# Normalize version prefix for an explicitly provided version.
if [[ -n "$VERSION" && ! "$VERSION" =~ ^v ]]; then
VERSION="v${VERSION}"
fi
GH_REPO="${GH_REPO:-gesellix/Bose-SoundTouch}"
# Used only when the latest-release lookup fails (offline / rate-limited).
FALLBACK_VERSION="${FALLBACK_VERSION:-v0.111.3}"
SERVICE_NAME="${SERVICE_NAME:-soundtouch-service}"
BIN_PATH="${BIN_PATH:-/usr/local/bin/soundtouch-service}"
@@ -176,6 +181,40 @@ download_binary() {
log "Installed binary to ${BIN_PATH}"
}
resolve_version() {
# When no explicit version was given, resolve the latest release tag by
# following the documented stable redirect:
# https://github.com/<owner>/<repo>/releases/latest
# which 302-redirects to .../releases/tag/vX.Y.Z. We read the final URL and
# take the tag from it. Falls back to FALLBACK_VERSION on any failure
# (offline, rate-limited, no usable curl/wget).
if [[ -n "$VERSION" ]]; then
return
fi
local latest_url="https://github.com/${GH_REPO}/releases/latest"
log "Resolving latest release via ${latest_url}"
local effective="" tag=""
if command -v curl >/dev/null 2>&1; then
effective="$(curl -fsSLI -o /dev/null -w '%{url_effective}' "$latest_url" 2>/dev/null)" || true
else
# wget: don't follow the redirect, read the Location header instead.
effective="$(wget -S --max-redirect=0 -O /dev/null "$latest_url" 2>&1 \
| awk 'tolower($1) ~ /location:/ {print $2}' | tr -d '\r' | tail -1)" || true
fi
tag="${effective##*/}"
if [[ "$tag" =~ ^v?[0-9]+\.[0-9]+ ]]; then
[[ "$tag" =~ ^v ]] || tag="v${tag}"
VERSION="$tag"
log "Latest release is ${VERSION}"
else
VERSION="$FALLBACK_VERSION"
log "⚠️ Could not resolve latest release; falling back to ${VERSION}"
fi
}
self_update() {
# If we are already a self-update re-exec, don't do it again
if [[ "$IS_SELF_UPDATE" == "true" ]]; then
@@ -364,6 +403,7 @@ main() {
apt_install_if_missing curl
fi
resolve_version
self_update "$@"
ensure_user_group