4 Commits
Author SHA1 Message Date
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 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
Tim Vahlbrock dbe5b90d8d fix typo 2026-05-10 12:58:00 +02:00
Tim Vahlbrock 370b587fcf feat: Provide scripts and documentation for on-device install 2026-05-10 12:58:00 +02:00