37 Commits
Author SHA1 Message Date
Tobias GesellchenandClaude Sonnet 5 9e56c4f3f4 fix(setup,admin-ui,install): three bugs from #621 follow-up feedback
- setup: resync all four boseurls (not just marge/swUpdate) over telnet
  after an SSH-XML migration. `envswitch boseurls set` persists whatever
  is currently in the runtime layer, so leaving stats/bmx untouched froze
  their stale pre-migration values into the persistence layer permanently
  -- surviving reboot and previously requiring a factory reset to clear.
- admin-ui: Migrate tab's Target Domain edits now propagate into the four
  service URL fields (tracked via a dataset.autofilled flag so real manual
  edits still aren't clobbered), closing the gap where changing Target
  Domain to a new value left the four fields pointed at a stale default.
- install.sh: prune stale binary backups before the download too, not
  only after a successful install, so a backup left by a previously
  aborted (out-of-space) run gets cleaned up instead of compounding.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 19:57:02 +02:00
Tobias GesellchenandClaude Sonnet 5 81b915bfca fix(on-device): stop leaking the speaker's own hostname into BMX/TLS URLs
On an on-device install, soundtouch-service defaulted its server URL to
os.Hostname() when --server-url wasn't set. Since the service runs on the
speaker's own Linux, that returns the speaker's internal variant codename
(e.g. "spotty", "mojo") -- never resolvable, not even by the speaker itself
-- breaking TuneIn/BMX playback with CURL ErrorCode 6 (issue #546).

Add a --deployment-mode/DEPLOYMENT_MODE flag (on-device, private-network,
public-network) so the fallback is chosen deliberately instead of guessed:
on-device defaults to localhost, public-network refuses to start rather
than guess a public address, and the previous hostname-guessing behavior
is kept for private-network/unset installs, now with a startup warning.

The on-device init script sets DEPLOYMENT_MODE=on-device automatically and
now auto-exports aftertouch.conf into the daemon's environment generally,
which also unblocks discussion #610 (setting MGMT_USERNAME/MGMT_PASSWORD
on-device) without any further code change.

Verified end-to-end on real ST20 hardware: service now resolves
http://localhost:8000, a re-migrate updates the speaker's own runtime
config to match, and TuneIn playback works again.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-16 20:12:44 +02:00
Tobias GesellchenandClaude Opus 5 b8427b0bbe feat(on-device): reach AfterTouch from the LAN without an SSH tunnel
On-device installs were only reachable through an SSH tunnel, and the
docs blamed it on the service binding loopback-only. That was wrong.

Some SoundTouch chassis carry a BCO ("SMSC") Wi-Fi/Bluetooth
co-processor, and inbound LAN traffic reaches the main Linux SoC only
for a fixed set of Bose's own service ports, a list that appears to be
compiled into the co-processor firmware. AfterTouch's :8000 was never
part of that design, so connections never arrive at the SoC at all.
Confirmed on an ST20: a port sweep from a LAN client showed Bose's
:82/:8080/:8090/:8091/:8200/:17000 all answering while :8000 failed,
and tcpdump on the speaker's own eth0 recorded zero packets for it.
Ruled out along the way: iptables (empty), nft/ebtables (absent), the
router, Wi-Fi isolation, and the binding itself (0.0.0.0 is correct).

The init script now redirects one of the relayed ports to AfterTouch,
so http://<speaker-ip>:17008 works with no tunnel. 17008 is Bose's
software-update listener, whose cloud no longer exists. Only external
traffic is matched, so anything on the speaker still reaches :8000 as
before. Auto-enabled only where has-bco reports the co-processor, and
configurable via AFTERTOUCH_LAN_PORT (auto/none/port) in
aftertouch.conf. The rule is re-applied on every start and removed on
stop and uninstall, so it needs no watchdog; unlike prior art it is not
pinned to the LAN IP, so it also survives DHCP changes.

Credit for the REDIRECT technique goes to the STR / SoundTouch Reborn
project, which documented and shipped it first.

Also de-hardcodes the service port, which was baked independently into
the daemon args, the readiness poll and status, and makes install.sh
print the speaker's real address instead of a <your-device-ip>
placeholder it never filled in.

Adds a model support matrix, since the repo had no per-model
compatibility record and this behaviour is entirely chassis-dependent.
Only the verified ST20 row is filled in; everything else is marked
unknown rather than inferred.

Verified on hardware: auto-detection, idempotency across restarts,
teardown and restore, persistence across a full reboot, and LAN access
returning the service's health JSON.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 15:54:00 +02:00
Tobias GesellchenandClaude Sonnet 5 4a1e722069 fix(on-device-install): daemon restart never replaced the running process
Three stacked bugs, found and confirmed on real hardware while
downgrading a speaker: the new binary landed on disk correctly, but
the running service kept reporting the old version indefinitely.

- install.sh called `/etc/init.d/aftertouch start`, not `restart`,
  after installing. start-stop-daemon silently refuses to launch a
  second instance when one is already running, and the init script
  never checked its exit status, so the old process was never
  replaced.

- The init script started the daemon through a `sh -c "exec ... |
  logger"` pipeline, on the assumption that `exec` lets --make-pidfile
  record the daemon's own PID. POSIX forks each side of a pipe into
  its own process, so the wrapper shell (not the daemon) was the one
  actually tracked. `stop` killed the wrapper, which doesn't forward
  SIGTERM to its children, orphaning the real daemon to keep running
  and keep holding :8000 forever.

- Once the wrapper correctly tracked the daemon's own PID, a further
  race surfaced: start-stop-daemon's own "already running?" check
  matched on generic `/bin/sh` identity, so a `restart`'s `start`
  phase could catch the previous wrapper still mid-teardown and
  silently refuse to launch a new one (masked by --quiet, looking
  like a 120s hang).

Fixed by calling `restart` instead of `start` in install.sh, and by
having the wrapper shell record the daemon's real PID itself (via $!)
while keying start-stop-daemon's own check on that same pidfile
instead of process identity.

Verified on hardware: three consecutive restart cycles, each fast,
each with the pidfile matching the live daemon PID and daemon output
flowing through syslog again via logread.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-16 15:54:00 +02:00
Tobias Gesellchen a8f4469eb0 fix(docs): VERSION= env var must go on sh, not curl, in the install pipe
Confirmed on real hardware: `VERSION=0.123.0 curl -sSL .../install.sh | sh`
silently does NOT pin the version, despite the docs claiming it "works
with pipe-to-sh". Shell variable-assignment prefixes only apply to the one
command they're attached to; in a pipe each command is its own process, so
the env var was set for `curl` (which never reads it) and never reached
`sh` (which does). A real attempt to pin v0.123.0 for a #614 pure
reproduction silently installed "latest" instead.

Verified the fix with a minimal repro (VERSION=X cat file | sh vs.
cat file | VERSION=X sh) before changing anything.

Moves the VERSION= prefix onto sh -- the last command in the pipe, the one
that actually reads it -- in ON-DEVICE-INSTALL-WALKTHROUGH.md (both
occurrences), scripts/on-device-install/README.md, and
scripts/on-device-install/install.sh's own header comment.

RASPBERRY-PI.md/EXTERNAL-HOST-WALKTHROUGH.md's `sudo VERSION=x ... bash
install.sh` pattern is unaffected -- that's a direct invocation, not a
pipe, so the env var already reaches the right process there.
2026-08-16 15:54:00 +02:00
Tobias Gesellchen ba43b9ac16 docs/scripts: bump stale 0.111.x example versions to current release
Repo-wide sweep of example version strings still pinned around 0.111.2/
0.111.3 (four releases behind) across install-script comments, README
walkthroughs, the FALLBACK_VERSION defaults in on-device-install and
raspberry-pi install scripts, and the bug-report issue template's version
placeholder. Bumped to 0.123.0, the current release.

Left untouched: RFC-5737 example IPs and Go test fixtures that happened to
match the same version-number pattern, dated blog posts, and the Hugo
theme's own unrelated version pin.
2026-08-16 15:54:00 +02:00
Tobias GesellchenandClaude Opus 4.8 ea7f6f36ef 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>
2026-06-25 09:47:19 +02:00
Tobias GesellchenandClaude Opus 4.8 01ebbb102d docs(install): bump example/default version v0.107.0 -> v0.111.3
The installer docs and the on-device + Raspberry Pi installer scripts all
defaulted to and showed v0.107.0. Update every install example and the
VERSION defaults to the current release v0.111.3 across the on-device and
Pi guides and scripts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 09:47:19 +02:00
Tobias Gesellchen c297a90be3 chore: update version to v0.107.0 in docs/scripts 2026-06-04 17:17:09 +02:00
Tobias Gesellchen 8232fd1401 chore: update version to v0.104.1 in all installer scripts 2026-05-31 23:43:44 +02:00
Tobias GesellchenandClaude Opus 4.8 7051793e81 chore: bump version to v0.104.0 and refresh UI screenshots
Update v0.103.0 -> v0.104.0 across installer scripts, walkthrough docs,
and example go.mod files, and refresh the devices/migration/settings/sync
UI screenshots.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 22:48:24 +02:00
Tobias Gesellchen 80cab16239 chore: update version to v0.103.0 in all installer scripts 2026-05-31 13:25:42 +02:00
Tobias Gesellchen d8166facf0 chore: update version to v0.102.0 in all installer scripts 2026-05-30 23:45:48 +02:00
Tobias Gesellchen 6071146851 chore: update version to v0.100.0 in all installer scripts 2026-05-30 11:18:02 +02:00
Tobias Gesellchen c521414eb1 chore: update version to v0.99.0 in all installer scripts 2026-05-29 00:36:07 +02:00
Tobias Gesellchen d70e336e52 chore: update version to v0.98.0 in all installer scripts 2026-05-28 23:11:15 +02:00
Tobias GesellchenandClaude Sonnet 4.6 adcdc26d8d 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>
2026-05-28 22:59:35 +02:00
Tobias Gesellchen 872a121cbd chore: bump to v0.93.1 2026-05-24 17:49:17 +02:00
Tobias GesellchenandClaude Sonnet 4.6 73d0d4b176 fix(install): reliable stop, VERSION flag, and on-device walkthrough
- aftertouch init script: stop) now waits up to 15 s for SIGTERM
  to take effect, then escalates to SIGKILL; prevents stale daemon
  processes after '/etc/init.d/aftertouch stop' returns (weissigera's
  workaround was manual 'killall aftertouch-service')

- install.sh: add --version / -v CLI flag so the version to install
  can be passed as a command-line argument in addition to the VERSION
  env var; document the trade-off of the hard-coded default in a
  comment; update scripts/on-device-install/README.md with concrete
  usage examples for env-override, CLI flag, and rollback tip

- docs/guides/ON-DEVICE-INSTALL-WALKTHROUGH.md: 10-step runbook
  derived from weissigera's field-tested procedure (issue #329
  comment #4521280831): SSH connection, storage cleanup, install via
  install.sh, reboot, SSH tunnel, Health QuickFix, pairing
  verification, soundtouch-cli download, custom-radio preset setup,
  and final verification; troubleshooting table at the end

Closes #329 (remaining two tasks)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 11:31:16 +02:00
Tobias GesellchenandClaude Sonnet 4.6 124414943c fix(install): back up current binary and GC stale artefacts on upgrade
Before overwriting the binary, read its version via --version and save
a copy as aftertouch-service.<version>.backup (falls back to a timestamp
if the flag is absent or the build is a dev build).

After the new binary is in place, delete every older *.backup, *.old,
and *.new artefact in INSTALL_DIR.  /mnt/nv on SoundTouch SCM modules
has only tens of MB free; accumulating one ~12 MB backup per upgrade
quickly causes 'no space left on device' on the next download.

Only the backup created in this run (the <current-release>-1 binary) is
kept, giving a single one-step rollback point without wasting disk.

Relates to #329 (on-device install friction reported by weissigera).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 11:31:16 +02:00
Tobias Gesellchen b3bab622cd Bump 2026-05-23 01:30:40 +02:00
Tobias Gesellchen abfe540864 chore: update default version to v0.89.0 2026-05-21 23:40:00 +02:00
Tobias Gesellchen 9dcde21f39 Bump release version to v0.80.1 in installer scripts 2026-05-15 19:25:01 +02:00
Tobias GesellchenandClaude Opus 4.7 064fe80e18 fix(on-device-install): persistent install path + syslog-based logging
Bundles the install-time hygiene work for issues #268 and #250.

# Install location — #268

Stock SoundTouch rootfs has only a few MB free (~4 MB on the ST20
the reporter captured); the AfterTouch binary is ~12 MB. The previous
flow downloaded into tmpfs (/media/aftertouch) and then `mv`'d the
binary into /opt/aftertouch on rootfs — which fails with
"No space left on device" on any speaker with the standard layout.

install.sh now installs to /mnt/nv/aftertouch by default (the
persistent partition, ~30 MB free on the same captures) and points
/opt/aftertouch at it via a symlink so the init script's hardcoded
DAEMON path keeps working unchanged. Power users can override with
INSTALL_DIR=/some/other/path. The interactive prompt from the
community patch in #268's thread is dropped — STDIN is the curl
pipe under the documented `curl | sh` invocation, so a read prompt
would hang or read garbage.

uninstall.sh is updated to resolve the symlink and remove the
target before unlinking, so the 12 MB binary doesn't get orphaned
on /mnt/nv when users uninstall.

# Logging — #250

Issue #250 surfaced a "running but unreachable" state: the install
script reported AfterTouch as running, the init script's status
agreed, but `curl :8000` returned connection-refused. start-stop-
daemon's --background detaches stdout/stderr, so any panic the
daemon emitted before dying went to /dev/null with no diagnostic
trail.

The fix is to route the daemon's stdout/stderr through `logger -t
aftertouch` so output lands in BusyBox syslog — a bounded in-memory
ring buffer that never grows on disk (writing to a file in /mnt/nv
would have eaten the volume over months). Diagnostic flow is now:

    logread        | grep aftertouch | tail -20
    logread -f     | grep aftertouch     # live tail

Matches the recipe already documented in TROUBLESHOOTING.md for the
speaker's own logs (Curl 7 section).

Tightening on top of the syslog change:

  - The init script's `status` case now also curls localhost:8000
    when the PID is alive — distinguishes "PID alive, listener up"
    from "PID alive, listener silently died" (which is what fooled
    everyone on #250). A bare PID-liveness check returned "running"
    in both cases.

  - install.sh's post-install verification now does its own 10s
    curl probe after the init script returns; on failure it tails
    the aftertouch syslog so the user sees the actual error rather
    than the install script claiming success.

  - `exec` is added inside the start-stop-daemon's shell wrapper so
    --make-pidfile records the daemon's own PID (not the shell's),
    which keeps `stop` semantics correct.

README updated to document the install location, INSTALL_DIR
override, and the syslog tag.

No automated tests — these are shell scripts the install pipeline
runs once on the device. All three scripts pass `bash -n` /
`sh -n` syntax checks. Real validation is end-user retest, gated on
the next release.

Refs #268, refs #250.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 17:11:02 +02:00
Tobias GesellchenandClaude Opus 4.7 5ba0776787 Bump install scripts to v0.79.0
on-device-install and raspberry-pi installers default to the new
v0.79.0 release binary. Also refreshes two stale comment examples in
the raspberry-pi install script (v0.17.0 → v0.78.0, v0.18.1 → v0.79.0)
so the in-file usage hints reflect the same era as the default.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 12:56:43 +02:00
Tobias Gesellchen b6a207e33d Bump default service version to v0.78.0 2026-05-14 23:36:59 +02:00
Tobias Gesellchen 93c3f68443 Bump the default version in install scripts to v0.74.0 2026-05-11 00:49:16 +02:00
Tim VahlbrockandTobias Gesellchen 8c01edaae4 allow usage of custom tmp directory for updates 2026-05-10 12:58:00 +02:00
Tim VahlbrockandTobias Gesellchen 759c6da52a create tmp/aftertouch directory 2026-05-10 12:58:00 +02:00
Tim VahlbrockandTobias Gesellchen 3da023aa78 make curl less verbose 2026-05-10 12:58:00 +02:00
Tim VahlbrockandTobias Gesellchen 7d410eef24 store updates on tmp 2026-05-10 12:58:00 +02:00
Tim VahlbrockandTobias Gesellchen b2dc2cb802 make curl verbose 2026-05-10 12:58:00 +02:00
Tim VahlbrockandTobias Gesellchen bd2e594ba8 download updates to /media to not require additional storage space 2026-05-10 12:58:00 +02:00
Tim VahlbrockandTobias Gesellchen ff8bf75982 make default version number the next minor release 2026-05-10 12:58:00 +02:00
Tim VahlbrockandTobias Gesellchen 981ecf6d89 fix typo 2026-05-10 12:58:00 +02:00
Tim VahlbrockandTobias Gesellchen 37d758f7f3 minor fixes 2026-05-10 12:58:00 +02:00
Tim VahlbrockandTobias Gesellchen 370b587fcf feat: Provide scripts and documentation for on-device install 2026-05-10 12:58:00 +02:00