Bose SoundTouch Toolkit

Documentation for controlling and preserving Bose SoundTouch devices

View the Project on GitHub gesellix/Bose-SoundTouch

What a SoundTouch speaker does during factory reset

Observed live on ST10 firmware 27.0.6.46330.5043500 (build epdbuild.trunk.hepdswbld04.2022-08-04) on 2026-05-12, by running soundtouch-cli setup factory-reset and tailing the speaker’s logread over SSH. The trace is preserved at _/logs/factory-reset.txt for reference.

Sequence

  1. Telnet receives sys factorydefault. The diagnostic shell on port 17000 accepts the command and acknowledges. Some firmwares close the socket as part of the reboot — our CLI’s setup factory-reset tolerates that as success.

  2. Speaker DELETEs itself from its marge account. Before wiping anything, the firmware does:
    [MargeStateAssociated] HandleRemoveDeviceRequest - Removing this device from the user's Marge account
    [MargeClient]          RemoveDevice calling Marge Server with https://streaming.bose.com/streaming/account/{accountId}/device/{deviceId}
    [MargeClient]          RemoveDeviceCB - Device removed from the user's Marge account
    [MargeStateAssociated] HandleRemoveDeviceRequestSuccessCB, Marge returned: {"ok": true}
    

    AfterTouch already handles this — HandleMargeRemoveDevice (pkg/service/handlers/handlers_marge.go:633) routed via r.Delete("/device/{device}", …) in cmd/soundtouch-service/main.go:955. The handler calls marge.RemoveDeviceFromAccount(s.ds, account, device) and prunes the device from the datastore.

  3. Speaker notifies its LAN peers. Two HTTP POSTs to each known peer at :8090/notification:
    [NotificationSender] SendNotifyLisas_: URL: >>http://192.168.123.122:8090/notification<<, m_msgdata.size(58)
    [SimpleURLFetcher]   multipart/form-data text/xml
    

    ~58 bytes of multipart/form-data carrying text/xml. “Lisas” is the firmware’s internal term for LAN peers (devices on the same account on the same network segment). AfterTouch is not on this path — it’s pure peer-to-peer over the LAN. Peers presumably refresh their account info as a result.

  4. Local state teardown. Bluetooth pairings cleared (BTRemoteDeviceAccess::ClearPrevPairedList), zone/group state torn down, all source proxies disconnected (STSAccountProxy::Disconnect Requested × many).

  5. Persistence cleanup. Logs, core dumps wiped (FactoryDefault: Clearing the CoreDump and BoseLogs … rm -rf /mnt/nv/BoseLog/*). Notably NOT wiped: /mnt/nv/aftertouch.resolv.conf, /mnt/nv/rc.local’s Aftertouch hook, and /mnt/nv/BoseApp-Persistence/1/SystemConfigurationDB.xml. The reset only touches log directories and account-specific persistence under the same /mnt/nv/BoseApp-Persistence/1/ tree.

  6. Reboot into setup mode. Speaker drops Wi-Fi, comes back as its own AP Bose SoundTouch XXXX on 192.0.2.1.

Implications for migration ordering

The DELETE in step 2 only reaches AfterTouch if the speaker’s margeURL already points at AfterTouch at the moment of reset. A speaker still pointing at streaming.bose.com sends it into the void → AfterTouch keeps a stale account/{id}/device/{id} entry until someone manually prunes it.

Therefore for a clean datastore lifecycle on an already-Bose-paired speaker:

  1. Migrate URLs first (setup migrate --method=resolv or --method=telnet).
  2. Reboot to apply.
  3. Factory reset.
  4. Re-provision.

soundtouch-cli setup plan --reset currently runs factory-reset first (optimal for already-on-AfterTouch speakers); both setup plan --reset and setup factory-reset print a one-line note explaining the ordering tradeoff so users can pick the right sequence for their starting state.

Implications for AfterTouch behaviour

Open questions

If you want either of these answered, capture two synchronised logread -f streams from two LAN speakers while one is being reset.

Runbook — reset & re-provision an ST10 on AfterTouch

End-to-end command sequence used during the 2026-05-12 bare-pairing experiment, recorded verbatim from the test session. Replace IPs, SSID, password, service URL, and account ID with your own. Two manual Wi-Fi switches happen between factory-reset and wifi-push (host joins the speaker’s AP) and again between wifi-push and wait-online (host re-joins home Wi-Fi).

# === 1. Reconnaissance — confirm what state the speaker is in before touching it. ===

# Identity, network, sources, presets.
go run ./cmd/soundtouch-cli --host 192.168.123.123 setup inspect

# Green/red status across every migration axis (SSH, telnet, CA, pairing, …).
go run ./cmd/soundtouch-cli --host 192.168.123.123 setup verify \
  --service-url=https://soundtouch.fritz.box

# What `setup plan --reset` would recommend, so you can preview the sequence.
go run ./cmd/soundtouch-cli --host 192.168.123.123 setup plan \
  --service-url=https://soundtouch.fritz.box --reset


# === 2. Reset and Wi-Fi re-provisioning. ===

# Tell the speaker to wipe itself. Speaker drops Wi-Fi and reboots into AP mode.
go run ./cmd/soundtouch-cli --host 192.168.123.123 setup factory-reset

# Manual: switch this host to the speaker's setup AP.
#   macOS: networksetup -setairportnetwork en0 "Bose SoundTouch XXXX"

# Poll 192.0.2.1:8090/info until the speaker answers (interval=2s, timeout=5m).
go run ./cmd/soundtouch-cli --host 192.168.123.123 setup wait-ap

# Push home Wi-Fi credentials. NOTE the single-quoted password: zsh expands `!`
# inside double quotes as history-expansion and will refuse the command.
go run ./cmd/soundtouch-cli --host 192.168.123.123 setup wifi-push \
  --ssid="wifi-name" --pass='a.secure!password'

# Manual: switch host back to home Wi-Fi.
#   macOS: networksetup -setairportnetwork en0 "wifi-name" 'a.secure!password'

# mDNS-poll for the speaker on the home network, matched by deviceID suffix
# (which survives the reset since it's the MAC). Returns the new IP.
go run ./cmd/soundtouch-cli --host 192.168.123.123 setup wait-online --match=536A98


# === 3. Clock, migrate, pair. From here on use the new IP wait-online reported. ===

# Set the speaker's wall-clock. `clock set --time=now` fails on FW 27;
# `clock now` is the working subcommand.
go run ./cmd/soundtouch-cli --host 192.168.123.123 clock now

# Reboot to clear any half-initialized resolver / NTP state from the wifi-push flap.
go run ./cmd/soundtouch-cli --host 192.168.123.123 setup reboot

# Apply DNS-redirect migration: routes *.bose.com to AfterTouch and installs its CA.
# Idempotent; safe to re-run.
go run ./cmd/soundtouch-cli --host 192.168.123.123 setup migrate \
  --service-url=https://soundtouch.fritz.box --method=resolv

# Reboot again so the envswitch parallel-persistence layer and the resolv hook
# both take effect on the next boot.
go run ./cmd/soundtouch-cli --host 192.168.123.123 setup reboot

# Pair the device with an AfterTouch account — bare experiment variant.
# Drop --mode=bare and add --name=… / --language=… for the full state-machine variant.
go run ./cmd/soundtouch-cli --host 192.168.123.123 setup pair \
  --mode=bare --account=1111111 --service-url='https://soundtouch.fritz.box'


# === 4. Verify. ===

# Reboot to verify persistence survives.
go run ./cmd/soundtouch-cli --host 192.168.123.123 setup reboot

# Snapshot the result. margeAccountUUID should still equal --account, and Sources
# should list ~14 entries (TUNEIN, RADIO_BROWSER, LOCAL_INTERNET_RADIO,
# SPOTIFY slots, AIRPLAY, etc.) materialized by the firmware.
go run ./cmd/soundtouch-cli --host 192.168.123.123 setup inspect

Total wall-clock for the above on this hardware: roughly 5 minutes including the two manual Wi-Fi switches and three reboots.