mirror of
https://github.com/gesellix/Bose-SoundTouch.git
synced 2026-08-18 08:36:13 +00:00
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>
This commit is contained in:
co-authored by
Claude Opus 5
parent
1396bb32dc
commit
b8427b0bbe
@@ -113,6 +113,7 @@ See the [API Reference](https://gesellix.github.io/Bose-SoundTouch/docs/referenc
|
||||
- **[SoundTouch Plus](https://github.com/thlucas1/homeassistantcomponent_soundtouchplus)** (Todd Lucas) — Home Assistant integration; extensive undocumented API documentation
|
||||
- **[ÜberBöse API](https://github.com/julius-d/ueberboese-api)** (Julius) — API research and advanced endpoint discovery
|
||||
- **[Bose SoundTouch Hook](https://github.com/CodeFinder2/bose-soundtouch-hook)** (Adrian Böckenkamp) — `LD_PRELOAD` hooking for reverse engineering device internals
|
||||
- **[STR, SoundTouch Reborn](https://github.com/JRpersonal/streborn)** ([st-reborn.de](https://st-reborn.de)) — on-device agent plus desktop app; its published `iptables` REDIRECT technique is what makes AfterTouch's on-device install reachable over the LAN on co-processor chassis (see [Model Support Matrix](https://gesellix.github.io/Bose-SoundTouch/docs/reference/model-support-matrix/))
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -153,9 +153,19 @@ ssh -oHostKeyAlgorithms=+ssh-rsa -L 8000:localhost:8000 root@192.0.2.1
|
||||
Keep this terminal open. Navigate to **http://localhost:8000** in your
|
||||
browser.
|
||||
|
||||
> Skip this step if your speaker's firmware exposes port 8000 on the LAN
|
||||
> directly — you can reach `http://192.0.2.1:8000` without a tunnel in that
|
||||
> case.
|
||||
> **You may not need the tunnel at all.** Try `http://192.0.2.1:8000` first.
|
||||
> If that doesn't load, try **`http://192.0.2.1:17008`**: on speakers whose
|
||||
> Wi-Fi co-processor refuses to pass `:8000` through (the ST20 and likely
|
||||
> others), the installer automatically redirects port `17008` to AfterTouch,
|
||||
> so the Admin UI is reachable from the LAN without any tunnel. Check with
|
||||
> `/etc/init.d/aftertouch status` on the speaker, which reports the LAN port
|
||||
> when the redirect is active. Details and per-model status:
|
||||
> [Model Support Matrix](../../reference/MODEL-SUPPORT-MATRIX/).
|
||||
>
|
||||
> Keep the tunnel in mind anyway for **linking music-service accounts**:
|
||||
> Spotify only accepts `https://` or *loopback* OAuth redirect URIs, so
|
||||
> `http://localhost:8000` through a tunnel succeeds where a plain LAN
|
||||
> address is rejected.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -684,6 +684,35 @@ soundtouch-cli --host <SPEAKER-IP> setup migrate --method telnet \
|
||||
|
||||
**Fix:** `pkg/ssh.Client` now supports an opt-in persistent connection (`Connect()`/`Close()`) that `RevertMigration` uses to collapse those 17 connections into 1 — confirmed on the same real hardware (2026-08-16): a subsequent `setup revert` completed quickly, and the restored config file diffed byte-identical against `.original`. If you're on a build that predates this fix, don't retry `setup revert` back-to-back — if it fails, wait a minute and confirm the speaker is reachable again (`ping`, a single plain `ssh ... echo ok`) before retrying. If all you actually need is to point the speaker's URLs somewhere else (back to AfterTouch, or back to the original Bose cloud), the lighter-weight `setup migrate --method telnet` with explicit URL overrides (previous entry) uses one telnet connection instead of SSH entirely.
|
||||
|
||||
### ❌ On-device install: AfterTouch answers on the speaker but not from other machines on the LAN
|
||||
|
||||
**Symptoms:**
|
||||
|
||||
- On the speaker itself, `curl http://localhost:8000/health` works and `/etc/init.d/aftertouch status` is green.
|
||||
- From any other machine, `http://<speaker-ip>:8000` fails immediately (connection refused/reset, not a timeout).
|
||||
- SSH to the same speaker works fine, so it is clearly reachable in general.
|
||||
|
||||
**Cause:**
|
||||
|
||||
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's firmware. AfterTouch's `:8000` was never part of that original design, so the connection never arrives at the SoC at all. Confirmed on an ST20 (`spotty`, FW 27.0.6) in 2026-08: `tcpdump -i eth0` on the speaker saw **zero packets** for `:8000` while Bose's `:8090`/`:8091`/`:17000` answered normally from the same client. This is not a firewall (the speaker's `iptables` is empty) and not a binding problem (the service does listen on `0.0.0.0:8000`).
|
||||
|
||||
**Fix:**
|
||||
|
||||
The on-device installer handles this automatically: on an affected speaker it redirects a relayed Bose port to AfterTouch, so use:
|
||||
|
||||
```
|
||||
http://<speaker-ip>:17008
|
||||
```
|
||||
|
||||
To check or change it, on the speaker:
|
||||
|
||||
```bash
|
||||
/etc/init.d/aftertouch status # reports the LAN port when active
|
||||
iptables -t nat -S PREROUTING # shows the redirect rule
|
||||
```
|
||||
|
||||
Set `AFTERTOUCH_LAN_PORT` in `/opt/aftertouch/aftertouch.conf` to a different port, or to `none` to disable the redirect and use an SSH tunnel instead; then `/etc/init.d/aftertouch restart`. Note that **linking music-service accounts still works best through the tunnel** (`http://localhost:8000`), because Spotify only accepts `https://` or loopback OAuth redirect URIs. If you also run the `streborn` project on the same speaker, note it defaults to the same port, so change one of them. Which models are affected is tracked in [MODEL-SUPPORT-MATRIX.md](../reference/MODEL-SUPPORT-MATRIX.md).
|
||||
|
||||
## 🔊 **Volume & Audio Issues**
|
||||
|
||||
### ❌ "Volume control not working"
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
---
|
||||
title: "Model Support Matrix"
|
||||
---
|
||||
A living record of how individual SoundTouch models behave with AfterTouch,
|
||||
built up from things actually observed on hardware.
|
||||
|
||||
**This table only claims what someone has verified.** Anything not tested is
|
||||
marked `?` rather than inferred from a similar-looking model. Bose used
|
||||
several different chassis designs across the SoundTouch line, and at least
|
||||
one behaviour (LAN reachability, below) differs between them in a way that is
|
||||
invisible from the outside. If you have a model that isn't filled in yet,
|
||||
[the commands below](#how-to-fill-in-a-row) produce everything a row needs.
|
||||
|
||||
## What the columns mean
|
||||
|
||||
- **variant / moduleType**: the speaker's own identifiers, straight out of
|
||||
`/info`. `variant` is Bose's internal codename for the product; `moduleType`
|
||||
distinguishes chassis generations (`scm` and `sm2` are the two seen so far).
|
||||
- **BCO**: whether the board carries a BCO co-processor (Bose's internal name
|
||||
for the SMSC Wi-Fi/Bluetooth combo chip that also handles AirPlay). Bose's
|
||||
own `has-bco` helper on the device is simply
|
||||
`[ "$(cat /proc/module_type)" = scm ]`.
|
||||
- **`:8000` from LAN**: whether AfterTouch's own port is reachable from
|
||||
another machine on the network *without* any workaround.
|
||||
- **Entry port**: when `:8000` isn't reachable, the port AfterTouch redirects
|
||||
to itself so the admin UI still works. See
|
||||
[LAN access on co-processor chassis](#lan-access-on-co-processor-chassis).
|
||||
|
||||
## Matrix
|
||||
|
||||
| Model | variant | moduleType | BCO | On-device install | `:8000` from LAN | Entry port | Evidence |
|
||||
|---------------------|----------|------------|-----|-------------------|------------------|------------|-----------------------------------------------------------------------|
|
||||
| SoundTouch 20 | `spotty` | `scm` | yes | works | ✗ blocked | `17008` | verified on hardware 2026-08-16 (FW 27.0.6), redirect survives reboot |
|
||||
| SoundTouch 10 | ? | ? | ? | reported working | ? | ? | not tested for LAN reachability |
|
||||
| SoundTouch 30 | ? | ? | ? | reported working | ? | ? | not tested for LAN reachability |
|
||||
| SoundTouch Portable | ? | ? | ? | ? | ? | ? | not tested |
|
||||
| Wave / SA-4 | ? | ? | ? | ? | ? | ? | not tested |
|
||||
|
||||
Not every SoundTouch shares one firmware image, so treat a `?` as genuinely
|
||||
unknown. In particular, do not assume a model is unaffected just because it is
|
||||
newer or older than a model that is.
|
||||
|
||||
## LAN access on co-processor chassis
|
||||
|
||||
On chassis with a BCO co-processor, inbound LAN traffic reaches the speaker's
|
||||
main Linux SoC only for a fixed set of Bose's *own* service ports. That list
|
||||
appears to be compiled into the co-processor's firmware, and AfterTouch's
|
||||
`:8000` is not on it, so a connection attempt never arrives at the SoC at
|
||||
all. On a verified ST20, `tcpdump -i eth0` on the speaker recorded **zero
|
||||
packets** for `:8000` while Bose's `:8090`, `:8091`, `:8200`, `:82`, `:8080`
|
||||
and `:17000` all answered normally from the same client.
|
||||
|
||||
This is not a firewall, and not something AfterTouch can fix by binding
|
||||
differently: the service already listens on `0.0.0.0:8000`, and the speaker's
|
||||
`iptables` is empty (there is no `nft` or `ebtables` at all).
|
||||
|
||||
The on-device installer works around it by redirecting one of the relayed
|
||||
ports to AfterTouch. **Credit for this technique goes to the
|
||||
[STR / SoundTouch Reborn](https://github.com/JRpersonal/streborn) project**,
|
||||
which documented and shipped it first (their agent uses the same entry port
|
||||
for the same reason); finding their prior art is what turned this from an
|
||||
apparent hardware dead end into a one-line fix:
|
||||
|
||||
```
|
||||
iptables -t nat -I PREROUTING 1 ! -i lo -p tcp --dport 17008 -j REDIRECT --to-ports 8000
|
||||
```
|
||||
|
||||
`17008` is Bose's `SoftwareUpdate` listener. Its cloud service no longer
|
||||
exists, so taking over its inbound traffic costs nothing in practice. Only
|
||||
external traffic is matched (`! -i lo`), so anything running on the speaker
|
||||
still reaches AfterTouch on `:8000` exactly as before.
|
||||
|
||||
The rule is re-applied by the init script on every start, so it survives
|
||||
reboots (confirmed on the ST20) without any background watchdog. It is
|
||||
removed again on `stop` and on uninstall.
|
||||
|
||||
The redirect is applied automatically on chassis that need it, and configured
|
||||
via `AFTERTOUCH_LAN_PORT` in `/opt/aftertouch/aftertouch.conf`:
|
||||
|
||||
| Value | Effect |
|
||||
|------------|-----------------------------------------------------------------|
|
||||
| `auto` | *(default)* redirect only where the co-processor blocks `:8000` |
|
||||
| `none` | never redirect; use an SSH tunnel instead |
|
||||
| *(a port)* | always redirect that inbound port to AfterTouch |
|
||||
|
||||
Two caveats worth knowing:
|
||||
|
||||
- **Account linking still prefers the SSH tunnel.** Spotify only accepts
|
||||
`https://` or *loopback* OAuth redirect URIs, so `http://localhost:8000`
|
||||
through a tunnel works for linking where a plain LAN address does not.
|
||||
- **The `streborn` project defaults to the same port** for the same reason. If
|
||||
you run both on one speaker, change `AFTERTOUCH_LAN_PORT`.
|
||||
|
||||
## How to fill in a row
|
||||
|
||||
Run these from a machine on the same network (replace the address), then open
|
||||
an issue or PR with the output:
|
||||
|
||||
```bash
|
||||
# variant, moduleType, and whether an SCM/SMSC component is listed
|
||||
curl -s http://<speaker-ip>:8090/info
|
||||
|
||||
# is AfterTouch's own port reachable directly? (only meaningful once
|
||||
# AfterTouch is installed on the device)
|
||||
curl -v --max-time 5 http://<speaker-ip>:8000/health
|
||||
|
||||
# which Bose ports the chassis relays at all
|
||||
for p in 82 8080 8090 8091 8200 17000 17008; do
|
||||
printf '%s: ' "$p"
|
||||
curl -s -o /dev/null -w '%{http_code}\n' --max-time 3 "http://<speaker-ip>:$p/" || echo unreachable
|
||||
done
|
||||
```
|
||||
|
||||
And on the speaker itself, if you have SSH access:
|
||||
|
||||
```bash
|
||||
has-bco; echo "has-bco exit status: $?" # 0 = BCO co-processor present
|
||||
cat /proc/module_type /proc/variant
|
||||
```
|
||||
@@ -56,7 +56,53 @@ After the installation check if you can access AfterTouch from your local device
|
||||
|
||||
### If `http://<IP_ADDRESS_OF_SPEAKER>:8000` fails: SSH port forwarding
|
||||
|
||||
Some firmware images only bind the AfterTouch HTTP port to loopback (see issue #196). The workaround is an SSH tunnel — your machine talks to its own local `:8000`, the SSH connection forwards to the speaker's `:8000` on loopback.
|
||||
On some device models AfterTouch's port is reachable from other machines on
|
||||
your LAN out of the box. On others (see issue #196) it isn't, and (unlike
|
||||
the phrasing this README used to have) that's not AfterTouch or its
|
||||
firewall configuration choosing to bind loopback-only. AfterTouch itself
|
||||
binds `0.0.0.0` (all interfaces) correctly, confirmed by inspecting the
|
||||
running device directly, and there's no firewall rule (`iptables`,
|
||||
`nftables`, or otherwise) blocking it either.
|
||||
|
||||
**Current knowledge (2026-08-16), confirmed on real hardware via a
|
||||
decrypted firmware backup plus simultaneous packet captures on both the
|
||||
speaker and a client machine:** some SoundTouch models built around a
|
||||
"combo" WiFi/Bluetooth co-processor (used for AirPlay) route LAN traffic
|
||||
through that co-processor before it reaches the main application
|
||||
processor where AfterTouch actually runs. That co-processor only relays a
|
||||
fixed set of the device's own original service ports (the same ones the
|
||||
stock SoundTouch app and companion services always used), a list that,
|
||||
as far as we can tell, is compiled into the co-processor's own firmware.
|
||||
AfterTouch's ports were never part of that original design, so they never
|
||||
got included. This isn't a bug in AfterTouch, a router/firewall setting,
|
||||
or WiFi client isolation; all three were separately ruled out.
|
||||
|
||||
**The installer works around this automatically.** On an affected speaker
|
||||
it redirects one of the ports the co-processor *does* relay to AfterTouch,
|
||||
so the UI is reachable from the LAN without any tunnel:
|
||||
|
||||
```
|
||||
http://<IP_ADDRESS_OF_SPEAKER>:17008
|
||||
```
|
||||
|
||||
Port `17008` is Bose's software-update listener; that cloud service no
|
||||
longer exists, so taking over its inbound traffic costs nothing. Only
|
||||
traffic from other machines is affected; anything running on the speaker
|
||||
still reaches AfterTouch on `:8000` as before. Change or disable this with
|
||||
`AFTERTOUCH_LAN_PORT` (`auto` / `none` / a port number) in
|
||||
`/opt/aftertouch/aftertouch.conf`, or pass it at install time:
|
||||
|
||||
```bash
|
||||
rw && curl -sSL https://raw.githubusercontent.com/gesellix/Bose-SoundTouch/main/scripts/on-device-install/install.sh | AFTERTOUCH_LAN_PORT=none sh
|
||||
```
|
||||
|
||||
Which models need this, and how to report one that isn't listed yet, is
|
||||
tracked in
|
||||
[MODEL-SUPPORT-MATRIX.md](../../docs/content/docs/reference/MODEL-SUPPORT-MATRIX.md).
|
||||
The SSH tunnel below still works, and remains the better route for
|
||||
**linking music-service accounts**: Spotify only accepts `https://` or
|
||||
loopback OAuth redirect URIs, so `http://localhost:8000` through a tunnel
|
||||
succeeds where a plain LAN address is rejected.
|
||||
|
||||
**Open a fresh terminal on your own machine** (Linux/macOS/Windows — NOT another shell inside the speaker's SSH session — see issue #250 for the trap that catches everyone here) and run:
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ DESC="Bose AfterTouch service"
|
||||
DAEMON="/opt/aftertouch/aftertouch-service"
|
||||
PIDFILE="/var/run/$NAME.pid"
|
||||
DATADIR="/opt/aftertouch/data"
|
||||
CONFFILE="/opt/aftertouch/aftertouch.conf"
|
||||
SCRIPTNAME="/etc/init.d/$NAME"
|
||||
USER="root"
|
||||
LOG_TAG="aftertouch"
|
||||
@@ -24,6 +25,21 @@ LOG_TAG="aftertouch"
|
||||
export PATH="/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin"
|
||||
|
||||
|
||||
# Optional settings written by install.sh (AFTERTOUCH_LAN_PORT, SERVICE_PORT).
|
||||
# Sourced before the defaults below so it can override either.
|
||||
# shellcheck source=/dev/null
|
||||
[ -r "$CONFFILE" ] && . "$CONFFILE"
|
||||
|
||||
# Port the daemon binds locally. Kept in one variable because it appears in
|
||||
# the daemon arguments, the readiness poll and `status` -- three places that
|
||||
# used to hardcode 8000 independently, so changing one silently broke the
|
||||
# other two.
|
||||
SERVICE_PORT="${SERVICE_PORT:-8000}"
|
||||
|
||||
# LAN entry port: a port number, "auto" (default), or "none".
|
||||
LAN_PORT_MODE="${AFTERTOUCH_LAN_PORT:-auto}"
|
||||
|
||||
|
||||
# Sanity check executable
|
||||
test -x "$DAEMON" || {
|
||||
echo "ERROR: Cannot execute $DAEMON (check path and permissions)." >&2
|
||||
@@ -31,6 +47,101 @@ test -x "$DAEMON" || {
|
||||
}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# LAN entry-port redirect
|
||||
#
|
||||
# On chassis built around a BCO ("SMSC") Wi-Fi/Bluetooth co-processor,
|
||||
# inbound LAN traffic only reaches this Linux SoC for a fixed set of Bose's
|
||||
# own service ports, which appears to be compiled into the co-processor's
|
||||
# firmware. AfterTouch's :8000 is not on that list, so a LAN client's SYN
|
||||
# never arrives here at all -- confirmed on an ST20 (`spotty`), where
|
||||
# `tcpdump -i eth0` on the speaker saw zero packets for :8000 while Bose's
|
||||
# own :8090/:8091/:17000 answered normally from the same client. The usual
|
||||
# suspects were all ruled out: the service does bind 0.0.0.0 correctly, the
|
||||
# speaker's iptables is empty, and SSH over the same path works.
|
||||
#
|
||||
# Workaround: NAT one of the relayed Bose ports to ours. The default, 17008,
|
||||
# is Bose's SoftwareUpdate listener -- its cloud is gone, so taking over its
|
||||
# inbound traffic costs nothing real. Only external traffic is matched
|
||||
# (`! -i lo`), so anything running on the speaker still reaches both the real
|
||||
# service on loopback and AfterTouch on :8000 as before.
|
||||
#
|
||||
# Credit: the STR / SoundTouch Reborn project (github.com/JRpersonal/streborn)
|
||||
# documented and shipped this REDIRECT technique first, using the same entry
|
||||
# port for the same reason.
|
||||
#
|
||||
# Which models need this is tracked in
|
||||
# docs/content/docs/reference/MODEL-SUPPORT-MATRIX.md.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# Resolve LAN_PORT_MODE into $LAN_PORT. Returns non-zero when no redirect
|
||||
# should be installed.
|
||||
lan_redirect_port() {
|
||||
case "$LAN_PORT_MODE" in
|
||||
none|off|disabled|0)
|
||||
return 1
|
||||
;;
|
||||
auto|"")
|
||||
# Only auto-enable where direct LAN access is known not to work.
|
||||
# has-bco is Bose's own helper: [ "$(cat /proc/module_type)" = scm ]
|
||||
has-bco >/dev/null 2>&1 || return 1
|
||||
LAN_PORT=17008
|
||||
;;
|
||||
*[!0-9]*)
|
||||
echo "WARNING: ignoring AFTERTOUCH_LAN_PORT='$LAN_PORT_MODE'; expected a port number, 'auto' or 'none'." >&2
|
||||
return 1
|
||||
;;
|
||||
*)
|
||||
LAN_PORT="$LAN_PORT_MODE"
|
||||
;;
|
||||
esac
|
||||
return 0
|
||||
}
|
||||
|
||||
# Remove every PREROUTING rule pointing at our service port, whatever entry
|
||||
# port it used, so changing AFTERTOUCH_LAN_PORT cannot orphan the old rule.
|
||||
lan_redirect_purge() {
|
||||
iptables -t nat -S PREROUTING 2>/dev/null \
|
||||
| grep -- "--to-ports $SERVICE_PORT" \
|
||||
| sed 's/^-A /-D /' \
|
||||
| while read -r rule; do
|
||||
# shellcheck disable=SC2086
|
||||
iptables -t nat $rule 2>/dev/null || true
|
||||
done
|
||||
}
|
||||
|
||||
lan_redirect_apply() {
|
||||
lan_redirect_port || return 0
|
||||
|
||||
if ! iptables -t nat -L PREROUTING -n >/dev/null 2>&1; then
|
||||
echo "WARNING: this kernel has no iptables nat table; :$LAN_PORT was not" >&2
|
||||
echo " redirected. Reach AfterTouch over an SSH tunnel instead." >&2
|
||||
return 0
|
||||
fi
|
||||
|
||||
lan_redirect_purge
|
||||
|
||||
# Safety net for a kernel whose iptables lacks -S (purge would no-op):
|
||||
# without this, every restart would stack another duplicate rule.
|
||||
if iptables -t nat -C PREROUTING ! -i lo -p tcp --dport "$LAN_PORT" \
|
||||
-j REDIRECT --to-ports "$SERVICE_PORT" 2>/dev/null; then
|
||||
echo "LAN access already active on port $LAN_PORT."
|
||||
return 0
|
||||
fi
|
||||
|
||||
if iptables -t nat -I PREROUTING 1 ! -i lo -p tcp --dport "$LAN_PORT" \
|
||||
-j REDIRECT --to-ports "$SERVICE_PORT" 2>/dev/null; then
|
||||
echo "LAN access: port $LAN_PORT now reaches AfterTouch on :$SERVICE_PORT."
|
||||
else
|
||||
echo "WARNING: could not install the :$LAN_PORT -> :$SERVICE_PORT redirect." >&2
|
||||
fi
|
||||
}
|
||||
|
||||
lan_redirect_remove() {
|
||||
lan_redirect_purge
|
||||
}
|
||||
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
echo "Starting $DESC..."
|
||||
@@ -102,19 +213,22 @@ case "$1" in
|
||||
--background \
|
||||
--chuid "$USER" \
|
||||
--startas "/bin/sh" \
|
||||
-- -c "logger -t $LOG_TAG <'$LOGFIFO' & \"$DAEMON\" --data-dir '$DATADIR' --record-interactions=false --discovery-interval=60m >'$LOGFIFO' 2>&1 & echo \$! >'$PIDFILE'; wait"
|
||||
-- -c "logger -t $LOG_TAG <'$LOGFIFO' & \"$DAEMON\" --data-dir '$DATADIR' --port '$SERVICE_PORT' --record-interactions=false --discovery-interval=60m >'$LOGFIFO' 2>&1 & echo \$! >'$PIDFILE'; wait"
|
||||
|
||||
tries=0
|
||||
max_tries=60
|
||||
while [ $tries -lt $max_tries ]; do
|
||||
if curl -fsS http://localhost:8000 >/dev/null 2>&1; then
|
||||
if curl -fsS "http://localhost:$SERVICE_PORT" >/dev/null 2>&1; then
|
||||
# Only once the service actually answers is it worth pointing LAN
|
||||
# traffic at it.
|
||||
lan_redirect_apply
|
||||
exit 0
|
||||
fi
|
||||
sleep 2
|
||||
tries=$((tries + 1))
|
||||
done
|
||||
|
||||
echo "ERROR: daemon started but http://localhost:8000 never responded within $((max_tries * 2))s." >&2
|
||||
echo "ERROR: daemon started but http://localhost:$SERVICE_PORT never responded within $((max_tries * 2))s." >&2
|
||||
echo " Inspect the daemon's syslog output:" >&2
|
||||
echo " logread | grep $LOG_TAG | tail -20" >&2
|
||||
exit 1
|
||||
@@ -122,6 +236,9 @@ case "$1" in
|
||||
|
||||
stop)
|
||||
echo "Stopping $DESC..."
|
||||
# Drop the LAN redirect first: leaving it in place while nothing listens
|
||||
# would silently blackhole the entry port.
|
||||
lan_redirect_remove
|
||||
if [ -f "$PIDFILE" ]; then
|
||||
PID=$(cat "$PIDFILE")
|
||||
start-stop-daemon --stop \
|
||||
@@ -162,11 +279,19 @@ case "$1" in
|
||||
# (Gustour's ST30: status said running, curl said
|
||||
# connection-refused). Distinguish the two states here so
|
||||
# status isn't a false-positive.
|
||||
if curl -fsS --max-time 3 http://localhost:8000 >/dev/null 2>&1; then
|
||||
echo "$NAME is running (PID $PID, http://localhost:8000 responding)."
|
||||
if curl -fsS --max-time 3 "http://localhost:$SERVICE_PORT" >/dev/null 2>&1; then
|
||||
echo "$NAME is running (PID $PID, http://localhost:$SERVICE_PORT responding)."
|
||||
if lan_redirect_port; then
|
||||
if iptables -t nat -C PREROUTING ! -i lo -p tcp --dport "$LAN_PORT" \
|
||||
-j REDIRECT --to-ports "$SERVICE_PORT" 2>/dev/null; then
|
||||
echo "LAN access: reachable from other machines on port $LAN_PORT."
|
||||
else
|
||||
echo "LAN access: redirect for port $LAN_PORT is NOT installed." >&2
|
||||
fi
|
||||
fi
|
||||
exit 0
|
||||
else
|
||||
echo "$NAME PID $PID is alive but http://localhost:8000 is not responding." >&2
|
||||
echo "$NAME PID $PID is alive but http://localhost:$SERVICE_PORT is not responding." >&2
|
||||
echo "Recent log:" >&2
|
||||
logread 2>/dev/null | grep "$LOG_TAG" | tail -10 >&2
|
||||
exit 3
|
||||
|
||||
@@ -127,6 +127,29 @@ if [ -n "$BACKUP_FILE" ]; then
|
||||
echo "Disk usage after GC:"; df -h "$INSTALL_DIR"
|
||||
fi
|
||||
|
||||
# Settings file sourced by the init script. Written before the service is
|
||||
# (re)started so the very first start already sees it.
|
||||
#
|
||||
# An existing file is left alone on upgrade -- it may carry the operator's own
|
||||
# choices -- unless AFTERTOUCH_LAN_PORT was passed to this script explicitly.
|
||||
CONF_FILE="$INSTALL_DIR/aftertouch.conf"
|
||||
if [ -n "${AFTERTOUCH_LAN_PORT:-}" ] || [ ! -f "$CONF_FILE" ]; then
|
||||
cat > "$CONF_FILE" <<CONFEOF
|
||||
# AfterTouch on-device settings. Sourced by /etc/init.d/aftertouch.
|
||||
#
|
||||
# AFTERTOUCH_LAN_PORT: how AfterTouch is reached from other machines.
|
||||
# auto (default) redirect a spare Bose port to AfterTouch, but only on
|
||||
# speakers whose Wi-Fi co-processor refuses to pass :8000 through.
|
||||
# none never redirect; use an SSH tunnel instead.
|
||||
# <port> always redirect this inbound port to AfterTouch.
|
||||
# See docs: reference/MODEL-SUPPORT-MATRIX.md
|
||||
AFTERTOUCH_LAN_PORT=${AFTERTOUCH_LAN_PORT:-auto}
|
||||
CONFEOF
|
||||
echo "Wrote settings to $CONF_FILE (AFTERTOUCH_LAN_PORT=${AFTERTOUCH_LAN_PORT:-auto})"
|
||||
else
|
||||
echo "Keeping existing settings in $CONF_FILE"
|
||||
fi
|
||||
|
||||
echo "Creating init script..."
|
||||
curl \
|
||||
-sSL \
|
||||
@@ -162,10 +185,35 @@ echo "Installation complete. (Re)starting the service..."
|
||||
# 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
|
||||
# We are running ON the speaker, so print the address people actually need
|
||||
# rather than a <your-device-ip> placeholder they have to resolve themselves.
|
||||
LAN_IP=$(ip -4 addr show scope global 2>/dev/null \
|
||||
| awk '/inet /{sub(/\/.*/,"",$2); print $2; exit}')
|
||||
[ -n "$LAN_IP" ] || LAN_IP="<your-device-ip>"
|
||||
|
||||
# If the init script installed a LAN entry-port redirect, that port -- not
|
||||
# 8000 -- is the one reachable from other machines.
|
||||
LAN_PORT=$(iptables -t nat -S PREROUTING 2>/dev/null \
|
||||
| grep -- '-j REDIRECT' \
|
||||
| sed -n 's/.*--dport \([0-9][0-9]*\).*--to-ports 8000.*/\1/p' \
|
||||
| head -1)
|
||||
|
||||
echo ""
|
||||
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>"
|
||||
echo ""
|
||||
if [ -n "$LAN_PORT" ]; then
|
||||
echo " Open http://$LAN_IP:$LAN_PORT from any machine on your network."
|
||||
echo ""
|
||||
echo " (This speaker's Wi-Fi co-processor does not pass port 8000 through to"
|
||||
echo " AfterTouch, so port $LAN_PORT is redirected to it instead. Set"
|
||||
echo " AFTERTOUCH_LAN_PORT in $CONF_FILE to change or disable this.)"
|
||||
else
|
||||
echo " Open http://$LAN_IP:8000 from any machine on your network."
|
||||
fi
|
||||
echo ""
|
||||
echo "If that doesn't load, reach it through an SSH tunnel instead:"
|
||||
echo " ssh -oHostKeyAlgorithms=+ssh-rsa -L 8000:localhost:8000 root@$LAN_IP"
|
||||
echo "then open http://localhost:8000"
|
||||
else
|
||||
echo "WARNING: the init script reports AfterTouch as running, but" >&2
|
||||
echo " http://localhost:8000 isn't responding. The daemon may have" >&2
|
||||
|
||||
@@ -5,6 +5,18 @@
|
||||
set -eu
|
||||
|
||||
/etc/init.d/aftertouch stop || true
|
||||
|
||||
# `stop` normally removes the LAN entry-port redirect. Repeat it directly in
|
||||
# case the init script was already gone or failed, so no rule is left behind
|
||||
# pointing at a service that no longer exists.
|
||||
iptables -t nat -S PREROUTING 2>/dev/null \
|
||||
| grep -- '--to-ports 8000' \
|
||||
| sed 's/^-A /-D /' \
|
||||
| while read -r rule; do
|
||||
# shellcheck disable=SC2086
|
||||
iptables -t nat $rule 2>/dev/null || true
|
||||
done
|
||||
|
||||
rm -f /etc/init.d/aftertouch
|
||||
update-rc.d -f aftertouch remove
|
||||
|
||||
|
||||
Reference in New Issue
Block a user