diff --git a/README.md b/README.md index 9a5c92d..9e4e7eb 100644 --- a/README.md +++ b/README.md @@ -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/)) --- diff --git a/docs/content/docs/guides/ON-DEVICE-INSTALL-WALKTHROUGH.md b/docs/content/docs/guides/ON-DEVICE-INSTALL-WALKTHROUGH.md index 0a94017..2ba8a04 100644 --- a/docs/content/docs/guides/ON-DEVICE-INSTALL-WALKTHROUGH.md +++ b/docs/content/docs/guides/ON-DEVICE-INSTALL-WALKTHROUGH.md @@ -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. --- diff --git a/docs/content/docs/guides/TROUBLESHOOTING.md b/docs/content/docs/guides/TROUBLESHOOTING.md index b6ef965..7277f4e 100644 --- a/docs/content/docs/guides/TROUBLESHOOTING.md +++ b/docs/content/docs/guides/TROUBLESHOOTING.md @@ -684,6 +684,35 @@ soundtouch-cli --host 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://: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://: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" diff --git a/docs/content/docs/reference/MODEL-SUPPORT-MATRIX.md b/docs/content/docs/reference/MODEL-SUPPORT-MATRIX.md new file mode 100644 index 0000000..33e223b --- /dev/null +++ b/docs/content/docs/reference/MODEL-SUPPORT-MATRIX.md @@ -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://:8090/info + +# is AfterTouch's own port reachable directly? (only meaningful once +# AfterTouch is installed on the device) +curl -v --max-time 5 http://: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://:$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 +``` diff --git a/scripts/on-device-install/README.md b/scripts/on-device-install/README.md index f633f28..75dc1c3 100644 --- a/scripts/on-device-install/README.md +++ b/scripts/on-device-install/README.md @@ -56,7 +56,53 @@ After the installation check if you can access AfterTouch from your local device ### If `http://: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://: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: diff --git a/scripts/on-device-install/aftertouch b/scripts/on-device-install/aftertouch index 3a51e10..bab3b89 100644 --- a/scripts/on-device-install/aftertouch +++ b/scripts/on-device-install/aftertouch @@ -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 diff --git a/scripts/on-device-install/install.sh b/scripts/on-device-install/install.sh index 372ade7..5709d54 100644 --- a/scripts/on-device-install/install.sh +++ b/scripts/on-device-install/install.sh @@ -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" < 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 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="" + + # 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://: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@" + 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 diff --git a/scripts/on-device-install/uninstall.sh b/scripts/on-device-install/uninstall.sh index 342e92e..a1ab36d 100644 --- a/scripts/on-device-install/uninstall.sh +++ b/scripts/on-device-install/uninstall.sh @@ -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