Skip to content
On-Device Install Walkthrough

On-Device Install Walkthrough

A complete end-to-end runbook for installing AfterTouch directly on a Bose SoundTouch speaker — from first SSH connection through verified radio preset playback.

Credit: This guide is based on a step-by-step walkthrough contributed by weissigera in issue #329, documenting a successful fresh installation on a SoundTouch 20 Series I.


Prerequisites

  • SSH enabled on the speaker — either the usual “USB stick with remote_services” procedure, or soundtouch-cli setup enable-ssh (no stick needed, see Step 1).
  • Your machine can reach the speaker on the LAN.
  • The speaker’s LAN IP address — replace 192.0.2.1 throughout with the actual address shown in your router or arp -a.

Note on SSH host-key negotiation: SoundTouch speakers only advertise legacy host-key algorithms (ssh-rsa, ssh-dss). Modern OpenSSH clients reject these by default. The -oHostKeyAlgorithms=+ssh-rsa flag below opts them back in. Without it you’ll see no matching host key type found.


Step 1 — Connect to the speaker via SSH

If SSH isn’t enabled yet, you don’t need a USB stick: soundtouch-cli can bootstrap it purely over the network (#471), using the speaker’s telnet:17000 diagnostic shell (open by default on most firmware) to inject the SSH-enable command:

soundtouch-cli --host 192.0.2.1 setup enable-ssh

This waits for :22 to come up and persists it (survives a reboot) by default. The USB-stick method (format FAT32, create an empty remote_services file in its root, insert, power-cycle) still works as a fallback if telnet:17000 is closed or the injection doesn’t take on your model.

Either way, connect the same way:

ssh -oHostKeyAlgorithms=+ssh-rsa root@192.0.2.1

You should see a prompt such as root@soundtouch-device:~#.


Step 2 — Check free space (and clean up if needed)

The persistent /mnt/nv partition is ~31 MB in total, with ~20 MB free once AfterTouch (~15.5 MB at v0.131.0) is installed. That is enough for the binary plus one gzip-compressed rollback backup, but not for much else. Check first:

rw            # remount rootfs read-write
df -h /mnt/nv

If you have an older installation with multiple backup or artefact files left behind by earlier upgrades, remove them:

# List what's there
ls -lh /mnt/nv/aftertouch/

# Remove specific stale files (adjust version numbers to what you see)
rm -f /mnt/nv/aftertouch/aftertouch-service.v0.80.1.backup
rm -f /mnt/nv/aftertouch/aftertouch-service.v0.86.0.backup
rm -f /mnt/nv/aftertouch/aftertouch-service.v0.86.0.old
rm -f /mnt/nv/aftertouch/aftertouch-service.new
rm -f /mnt/nv/soundtouch-cli        # cli binary if left there by hand
rm -f /mnt/nv/aftertouch/soundtouch-cli

df -h /mnt/nv   # confirm space recovered

From v0.93.0 onwards the installer prunes stale artefacts automatically during every upgrade — manual cleanup should no longer be necessary on fresh installs. The flip side: if an install still aborts for lack of space, there is probably nothing left for you to delete. See the Troubleshooting entry for that error below.


Step 3 — Install (or upgrade) AfterTouch

Run the canonical one-liner. It downloads the binary and init script, creates /mnt/nv/aftertouch/, symlinks /opt/aftertouch, backs up the 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

By default this installs the latest release — the script resolves it from GitHub’s releases/latest redirect. To target a specific version instead:

# Via environment variable — note it goes on `sh`, not `curl`: shell
# variable-assignment prefixes only apply to the one command they're
# attached to, and in a pipe each command is a separate process.
# `VERSION=0.123.0 curl ... | sh` silently does NOT set it for `sh`.
rw && curl -sSL https://raw.githubusercontent.com/gesellix/Bose-SoundTouch/main/scripts/on-device-install/install.sh | VERSION=0.123.0 sh

# Via command-line flag (pass args after sh -s --)
curl -sSL https://raw.githubusercontent.com/gesellix/Bose-SoundTouch/main/scripts/on-device-install/install.sh | sh -s -- --version 0.123.0

Verify the installed version:

wget -qO- http://localhost:8000/health

The JSON response should include "version":"v0.123.0" (or whichever version you installed).


Step 4 — Reboot the speaker

sync
reboot

Wait 2–3 minutes for the speaker to come back up, then reconnect:

ssh -oHostKeyAlgorithms=+ssh-rsa root@192.0.2.1

Step 5 — Open an SSH tunnel and access the Admin UI

Open a new terminal on your machine (not inside the speaker’s SSH session — see issue #250 for the port-forward-from-inside trap) and run:

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.

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.

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.


Step 6 — Migrate (point the speaker at itself)

The speaker isn’t pointed at the AfterTouch instance you just installed yet — this step does that. On-device, the speaker and the AfterTouch instance are the same machine, so loopback is the correct and recommended Target Domain value: http://localhost:8000. This is the one case where the general migration guide’s “must not be localhost” warning does not apply — that warning is about the external-host/cloud scenarios, where localhost would resolve on the wrong machine (the service host, not the speaker). Here there is no wrong machine to resolve on.

Note: as of the fix for issue #546, the on-device init script already sets DEPLOYMENT_MODE=on-device, so a fresh (or reinstalled/updated) on-device install’s own Target Domain already defaults to http://localhost:8000 automatically — no manual Settings-tab step needed for that part. Older installs still default to the speaker’s own unresolvable Linux hostname (e.g. http://spotty:8000) until reinstalled with a build that includes the fix, or until the Target Domain is corrected by hand. Either way, you still need to run Migrate below — that step tells the speaker to use this address, which is separate from what the service defaults its own identity to.

Via the Admin UI:

  1. Go to Settings, set Target Domain to http://localhost:8000.
  2. Go to Devices, find your speaker (it self-discovers on its own LAN IP), click Migrate.
  3. Accept the suggested plan and let it apply.
  4. Reboot to apply the change:
    sync
    reboot

Or via the CLI (equivalent, no browser needed — grab soundtouch-cli from Step 9 below first if you want this path):

/mnt/nv/aftertouch/soundtouch-cli --host 127.0.0.1 setup migrate \
  --service-url http://localhost:8000 --method telnet
sync
reboot

Step 7 — Run the Health QuickFix for empty margeAccountUUID

In the AfterTouch UI:

  1. Open the Health tab.
  2. Run or refresh the health checks.
  3. Look for the warning:

    Speaker reports an empty <margeAccountUUID>

  4. Click the QuickFix button (labelled “Fix”, “Pair account”, or “Apply QuickFix” depending on the version) and confirm.

Or via the CLI, which pairs by a different mechanism and is worth trying when the QuickFix does not stick (see Step 9 to grab soundtouch-cli first, or run it from any other machine on your LAN with --host <speaker-ip>):

/mnt/nv/aftertouch/soundtouch-cli --host 127.0.0.1 setup pair \
  --mode=bare --account=1111111 --service-url http://localhost:8000

The two are not equivalent, so if one leaves the speaker unpaired the other is still worth a try:

  • The Health QuickFix posts to the speaker’s /setMargeAccount HTTP endpoint, falling back to the telnet envswitch accountid set command.
  • --mode=bare opens the speaker’s own setup WebSocket on port 8080 and pairs through that session. This is the path that has been observed to move a speaker out of source="SETUP", though it is still marked experimental.
  • --mode=full runs the complete SETUP_ENTER / SETUP_LEAVE bracket and is the next thing to try if --mode=bare does not help.

Note that --service-url is not dialled by the CLI itself. It is handed to the speaker over the pairing session and stored as the speaker’s own server URL, which it evaluates later. On an on-device install that value stays http://localhost:8000 even when you run the command from a different machine, because it is the speaker that has to resolve it.

Then reboot again to let the pairing take effect:

sync
reboot

Step 8 — Verify pairing and sources

After the reboot reconnect via SSH and check:

ssh -oHostKeyAlgorithms=+ssh-rsa root@192.0.2.1

# margeAccountUUID must NOT be empty after the QuickFix
wget -qO- http://localhost:8090/info | grep margeAccountUUID

# Sources must include LOCAL_INTERNET_RADIO, TUNEIN, and RADIO_BROWSER
wget -qO- http://localhost:8090/sources

If margeAccountUUID is still empty, re-run the Health QuickFix (Step 7) and reboot again. If it stays empty after a second attempt, or if the speaker reports source="SETUP" and refuses to play presets, try the soundtouch-cli setup pair route from Step 7 instead: it pairs over a different channel, so it can succeed where the QuickFix does not.


Step 9 — Download soundtouch-cli (optional, for preset setup)

If you want to program preset buttons from the command line, download the CLI binary to /mnt/nv/aftertouch (the same persistent partition AfterTouch itself lives on) rather than /tmp: /tmp is tmpfs and gets wiped on every reboot, and if you used the CLI alternatives in Steps 6/7 above, it needs to survive those steps’ reboots too, not just the final one:

cd /mnt/nv/aftertouch

curl -L --fail -o soundtouch-cli \
  https://github.com/gesellix/Bose-SoundTouch/releases/download/v0.123.0/soundtouch-cli-v0.123.0-linux-armv7
chmod +x soundtouch-cli

/mnt/nv/aftertouch/soundtouch-cli --version

Replace v0.123.0 with the version you installed. If you want the CLI alternatives in Steps 6/7, download it here first, before doing those steps — it’ll be in place and already persistent either way.


Step 10 — Store custom radio streams to preset buttons

Each station must be playing before it can be saved. The sleep 5 gives the speaker time to buffer and confirm the stream before storing.

Press preset buttons briefly. A long press on the physical hardware overwrites the stored preset.

# Preset 1 — Hitradio OE3
/mnt/nv/aftertouch/soundtouch-cli --host 127.0.0.1 source custom-radio \
  --url "http://orf-live.ors-shoutcast.at/oe3-q2a" \
  --name "Hitradio OE3" \
  --service-url "http://localhost:8000"
sleep 5
/mnt/nv/aftertouch/soundtouch-cli --host 127.0.0.1 preset store-current --slot 1

# Preset 2 — Lounge FM
/mnt/nv/aftertouch/soundtouch-cli --host 127.0.0.1 source custom-radio \
  --url "http://188.138.9.183/digital.mp3" \
  --name "Lounge FM" \
  --service-url "http://localhost:8000"
sleep 5
/mnt/nv/aftertouch/soundtouch-cli --host 127.0.0.1 preset store-current --slot 2

# Preset 3 — Country Nonstop
/mnt/nv/aftertouch/soundtouch-cli --host 127.0.0.1 source custom-radio \
  --url "https://stream.laut.fm/country-nonstop" \
  --name "Country Nonstop" \
  --service-url "http://localhost:8000"
sleep 5
/mnt/nv/aftertouch/soundtouch-cli --host 127.0.0.1 preset store-current --slot 3

# Preset 4 — Radio Piterpan
/mnt/nv/aftertouch/soundtouch-cli --host 127.0.0.1 source custom-radio \
  --url "https://klasse1.fluidstream.eu/piterpan.mp3?FLID=8" \
  --name "Radio Piterpan" \
  --service-url "http://localhost:8000"
sleep 5
/mnt/nv/aftertouch/soundtouch-cli --host 127.0.0.1 preset store-current --slot 4

# Preset 5 — kronehit
/mnt/nv/aftertouch/soundtouch-cli --host 127.0.0.1 source custom-radio \
  --url "https://secureonair.krone.at/kronehit-hp.mp3" \
  --name "kronehit" \
  --service-url "http://localhost:8000"
sleep 5
/mnt/nv/aftertouch/soundtouch-cli --host 127.0.0.1 preset store-current --slot 5

# Preset 6 — Radio Niederösterreich
/mnt/nv/aftertouch/soundtouch-cli --host 127.0.0.1 source custom-radio \
  --url "http://orf-live.ors-shoutcast.at/noe-q2a" \
  --name "Radio Niederoesterreich" \
  --service-url "http://localhost:8000"
sleep 5
/mnt/nv/aftertouch/soundtouch-cli --host 127.0.0.1 preset store-current --slot 6

These are the stations from weissigera’s setup (Austrian public and internet radio). Replace any or all of them with your own streams — the pattern is the same regardless of station.


Step 11 — Verify presets and final reboot

wget -qO- http://localhost:8090/presets

You should see all six preset slots populated. Then do a final reboot and test the physical buttons:

sync
reboot

After the speaker comes back up, press preset buttons 1–6 briefly — each should start playing the corresponding stream.


Troubleshooting

SymptomFirst check
SSH “no matching host key type”Add -oHostKeyAlgorithms=+ssh-rsa
Port 8000 not reachable from LANUse the SSH tunnel (Step 5)
margeAccountUUID still empty after rebootRe-run Health QuickFix, reboot again; if it still won’t stick, try setup pair --mode=bare (Step 7), which pairs over a different channel
Radio source error 1005margeAccountUUID is empty — complete Step 7 first
http://localhost:8000 not responding after installlogread | grep aftertouch | tail -20
No space left on device during installRun the cleanup in Step 2; check df -h /mnt/nv
Install aborts with ERROR: not enough free space … neededThe installer has already pruned its own leftovers, so there is usually nothing left for you to delete. Update the installer first (it is fetched fresh from main by the one-liner), and if it still aborts, install an older, smaller release with --version
Continue without a backup? [y/N] prompt during install/mnt/nv has room for the new binary but not also for a rollback backup. Answering n (the default) aborts and changes nothing. To proceed unattended, re-run with AFTERTOUCH_FORCE_NO_BACKUP=yes — but then keep your own copy of the current binary first
subsystem request failed when copying a binary with scpThe speakers ship no sftp-server; use scp -O

For more detail see TROUBLESHOOTING.md.


Updating AfterTouch

Re-run the installer with the version you want. The script backs up the running binary (named after its version), installs the new one, and prunes older artefacts to keep /mnt/nv free:

# Update to latest release
rw && curl -sSL https://raw.githubusercontent.com/gesellix/Bose-SoundTouch/main/scripts/on-device-install/install.sh | sh

# Update to a specific version — three equivalent forms
rw && curl -sSL https://raw.githubusercontent.com/gesellix/Bose-SoundTouch/main/scripts/on-device-install/install.sh | VERSION=0.123.0 sh

rw && curl -sSL https://raw.githubusercontent.com/gesellix/Bose-SoundTouch/main/scripts/on-device-install/install.sh | sh -s -- --version 0.123.0

curl -sSLo install.sh https://raw.githubusercontent.com/gesellix/Bose-SoundTouch/main/scripts/on-device-install/install.sh
sh install.sh --version 0.123.0

The script’s own final output already confirms the new version came up and is answering on :8000. If you separately check the version yourself (wget -qO- http://localhost:8000/health, or the Admin UI), reboot the speaker first: an Admin UI tab left open from before the update, or a browser cache of the previous page load, can otherwise still show the old version even though the new binary is already running.

Rollback: the installer keeps one backup alongside the binary, named after the version it replaced. It is normally gzip-compressed (.backup.gz); the uncompressed .backup form only appears when gzip was unavailable:

ls /mnt/nv/aftertouch/aftertouch-service*.backup*   # note the trailing *

# compressed (.backup.gz) — the usual case:
gunzip -c /mnt/nv/aftertouch/aftertouch-service.<old-version>.backup.gz \
   > /mnt/nv/aftertouch/aftertouch-service

# or, for an uncompressed .backup:
cp /mnt/nv/aftertouch/aftertouch-service.<old-version>.backup \
   /mnt/nv/aftertouch/aftertouch-service

chmod +x /mnt/nv/aftertouch/aftertouch-service
/etc/init.d/aftertouch restart

You usually won’t need this by hand: an install that fails to write the binary, or whose new binary doesn’t answer on :8000, restores this backup and restarts by itself before reporting failure (installers released after v0.131.0).

Testing a pre-release build (from main, not yet tagged): install.sh only ever downloads from GitHub Releases, so there’s no one-line installer for an unreleased commit. Cross-compile and swap the binary manually instead — this is a direct extension of the rollback procedure above:

# On your own machine, from a checkout of the branch/commit you want:
make build-linux-armv7   # builds build/soundtouch-service-linux-armv7,
                          # build/soundtouch-cli-linux-armv7, and
                          # build/soundtouch-backup-linux-armv7

# First, on the speaker: stop the service and compress the current binary
# into a rollback backup. Order matters — /mnt/nv has only ~20 MB free, so a
# second full-size binary plus an uncompressed backup does not fit.
ssh -oHostKeyAlgorithms=+ssh-rsa root@192.0.2.1
rw
/etc/init.d/aftertouch stop
gzip -c /mnt/nv/aftertouch/aftertouch-service \
   > /mnt/nv/aftertouch/aftertouch-service.pre-test.backup.gz
exit

# Then, from your machine, copy the new binary straight over the old one.
# -O is required: the speakers ship no sftp-server, and OpenSSH 9.0+ uses
# SFTP by default, so plain `scp` fails with "subsystem request failed".
scp -O build/soundtouch-service-linux-armv7 \
   root@192.0.2.1:/mnt/nv/aftertouch/aftertouch-service

# Back on the speaker:
ssh -oHostKeyAlgorithms=+ssh-rsa root@192.0.2.1
chmod +x /mnt/nv/aftertouch/aftertouch-service
/etc/init.d/aftertouch start

If you’re testing an unreleased soundtouch-cli change (not just the service), swap that binary too — same idea, and it lands in the same /mnt/nv/aftertouch directory Step 9 above uses:

scp -O build/soundtouch-cli-linux-armv7 root@192.0.2.1:/mnt/nv/aftertouch/soundtouch-cli
ssh -oHostKeyAlgorithms=+ssh-rsa root@192.0.2.1 chmod +x /mnt/nv/aftertouch/soundtouch-cli

Roll back the same way as above, using the .pre-test.backup.gz file.


Service management

/etc/init.d/aftertouch start
/etc/init.d/aftertouch stop
/etc/init.d/aftertouch restart
/etc/init.d/aftertouch status   # distinguishes "running + listener up" from "PID alive but listener down"

Logs

The daemon writes to BusyBox syslog (tagged aftertouch). Disk usage stays bounded — the syslog ring buffer is in memory:

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

If the service is running but port 8000 isn’t responding, check the syslog tail first — panics and startup errors appear there.


Uninstalling

Before uninstalling, consider reverting the speaker migration from the AfterTouch Admin UI so the speaker URL is set back to the Bose cloud (though neither Bose nor AfterTouch will be reachable once both are removed).

curl -sSL https://raw.githubusercontent.com/gesellix/Bose-SoundTouch/main/scripts/on-device-install/uninstall.sh | sh
Last updated on