Documentation for controlling and preserving Bose SoundTouch devices
The soundtouch-service is a comprehensive local server that emulates Bose’s cloud services, enabling offline SoundTouch device operation and advanced debugging capabilities. This service is particularly valuable given Bose’s announcement that cloud support will end in May 2026.
The service provides:
/etc/hosts, or /etc/resolv.conf.http files.tar.gz for offline analysisThe service consists of several key components:
go install github.com/gesellix/bose-soundtouch/cmd/soundtouch-service@latest
git clone https://github.com/gesellix/bose-soundtouch.git
cd Bose-SoundTouch
go build -o soundtouch-service ./cmd/soundtouch-service
You can run the SoundTouch service using Docker or Docker Compose.
Note for macOS and Windows users: The
--net hostoption is only supported on Linux. On macOS and Windows, service discovery (mDNS, UPnP) will not work automatically within the container. You will need to manually enter your device’s IP address in the management UI, and the service will communicate with it directly.
Linux (with host networking for discovery):
docker run -d \
--name soundtouch-service \
--network host \
-v $(pwd)/data:/app/data \
ghcr.io/gesellix/bose-soundtouch:latest
macOS / Windows (with port mapping):
docker run --rm -it \
-p 8000:8000 -p 8443:8443 \
-v $(pwd)/data:/app/data \
--env SERVER_URL=http://soundtouch.local:8000 \
--env HTTPS_SERVER_URL=https://soundtouch.local:8443 \
ghcr.io/gesellix/bose-soundtouch:latest
Note: The hostnames configured via
SERVER_URLandHTTPS_SERVER_URLare automatically added as Subject Alternative Names (SAN) to the generated TLS certificate, ensuring valid SSL connections.
Create a docker-compose.yml file:
services:
soundtouch-service:
image: ghcr.io/gesellix/bose-soundtouch:latest
container_name: soundtouch-service
# Linux users: use host networking for device discovery
# network_mode: host
# macOS/Windows users: use port mapping (discovery will be manual)
ports:
- "8000:8000"
- "8443:8443"
environment:
- PORT=8000
- SERVER_URL=http://soundtouch.local:8000
- HTTPS_SERVER_URL=https://soundtouch.local:8443
- DATA_DIR=/app/data
volumes:
- soundtouch-data:/app/data
restart: unless-stopped
volumes:
soundtouch-data:
And run:
docker-compose up -d
# Start with default settings (port 8000)
soundtouch-service
Open your browser to http://localhost:8000 to access the management interface.
The service will automatically start discovering SoundTouch devices on your network. You can also trigger manual discovery from the web UI or API.
Use the web interface or API to migrate devices from Bose cloud services to your local instance.
The service supports multiple ways to configure its behavior. When multiple sources provide the same setting, the following precedence rules apply (highest to lowest):
settings.json: Settings saved via the Web UI (stored in the data directory) take the highest precedence. This ensures that changes made in the browser persist across service restarts even if environment variables or flags change.settings.json, environment variables and flags are used.Tip: If you find that changes to environment variables are not taking effect, check the Settings tab in the Web UI or inspect the
settings.jsonfile in your data directory, as it might be overriding your manual configuration.
| Variable | Flag | Description | Default |
|---|---|---|---|
PORT |
--port, -p |
HTTP port to bind the service to | 8000 |
BIND_ADDR |
--bind |
Network interface to bind to | all (ipv4 and ipv6) |
DATA_DIR |
--data-dir |
Directory for persistent data | ./data |
SERVER_URL |
--server-url, -s |
External URL of this service | http://<hostname>:8000 |
HTTPS_PORT |
--https-port |
HTTPS port to bind the service to | 8443 |
HTTPS_SERVER_URL |
--https-server-url, -S |
External HTTPS URL | https://<hostname>:8443 |
PYTHON_BACKEND_URL, TARGET_URL |
--target-url |
URL for Python-based service components (legacy) | http://localhost:8001 |
REDACT_PROXY_LOGS |
--redact-logs |
Redact sensitive data in proxy logs | true |
LOG_PROXY_BODY |
--log-bodies |
Log full request/response bodies | false |
RECORD_INTERACTIONS |
--record-interactions |
Record HTTP interactions to disk | true |
DISCOVERY_INTERVAL |
--discovery-interval |
Device discovery interval | 5m |
ENABLE_DNS_DISCOVERY |
--dns-discovery |
Enable DNS discovery server | false |
DNS_UPSTREAM |
--dns-upstream |
Upstream DNS server for non-Bose queries | 8.8.8.8 |
DNS_BIND_ADDR |
--dns-bind |
Bind address for the DNS discovery server (standard port :53 is required for resolv.conf migration) |
:53 |
MIRROR_ENABLED |
Enable background mirroring of specific endpoints to Bose cloud | false |
|
MIRROR_ENDPOINTS |
Comma-separated list of path patterns to mirror (e.g., /streaming/account/*/device/*/recent) |
[] |
|
INTERNAL_PATHS |
--internal-paths |
Paths for internal requests to exclude from recording (e.g., /setup/*, /web/*) |
[] |
DISCOVERY_DISABLED |
Disable automated device discovery | false |
# Custom port and data directory
PORT=9000 DATA_DIR=/home/user/soundtouch soundtouch-service
# External server with custom URL
SERVER_URL=https://my-soundtouch.example.com soundtouch-service --port 443
# Development mode with full logging
LOG_PROXY_BODY=true REDACT_PROXY_LOGS=false soundtouch-service
Device migration switches your SoundTouch devices from Bose’s cloud services to your local service instance. This process:
soundtouch-servicehttp://localhost:8000# Get migration summary first
curl http://localhost:8000/setup/migration-summary/192.168.1.100
# Perform migration
curl -X POST http://localhost:8000/setup/migrate/192.168.1.100
# Verify migration status
curl http://localhost:8000/setup/devices
# Migration with proxy fallback for original services
curl -X POST "http://localhost:8000/setup/migrate/192.168.1.100?proxy_url=http://localhost:8000&marge=original&stats=original"
# Migration with custom target URL
curl -X POST "http://localhost:8000/setup/migrate/192.168.1.100?target_url=https://my-server.com:8000"
After migration, verify the device is working correctly:
# Check device status
curl http://localhost:8000/setup/devices
# Test preset functionality
curl "http://192.168.1.100:8090/presets"
# Monitor device events (if needed)
curl "http://localhost:8000/events/192.168.1.100"
The most robust and flexible DNS-based migration method. It utilizes the device’s persistent /mnt/nv/rc.local script to inject a priority DNS hook into the system’s DHCP configuration.
Note: This method requires the DNS Discovery Server to be bound to port 53 on your local IP and actually running. Most devices do not support custom DNS ports in
/etc/resolv.conf. If you use a custom port for testing, remember to switch back to:53and ensure the server has successfully bound to it (check Settings for status) before the actual migration.
Advantages:
*.bose.com redirection via your local DNS server.How it works:
/mnt/nv/aftertouch.resolv.conf is created on the device’s persistent partition./mnt/nv/rc.local checks if the system’s DHCP scripts (/etc/udhcpc.d/50default or /opt/Bose/udhcpc.script) have been patched.aftertouch.resolv.conf first, placing your DNS server at the top of /etc/resolv.conf while keeping all other DHCP-provided settings.Setup:
remote_services USB trick./mnt/nv/aftertouch.resolv.conf with your server details:
# Created by Aftertouch/SoundTouch-Service
# Priority nameserver for Bose service redirection
nameserver 192.168.1.XXX
/mnt/nv/rc.local with the idempotent patch:
#!/bin/sh
# Aftertouch DNS hook: prioritizes our custom nameserver if it exists
HOOK_MARKER="/mnt/nv/aftertouch.resolv.conf"
if [ -f "$HOOK_MARKER" ]; then
# Patch 50default if it exists
TARGET_FILE="/etc/udhcpc.d/50default"
if [ -f "$TARGET_FILE" ] && ! grep -q "$HOOK_MARKER" "$TARGET_FILE"; then
sed -i '/echo "search \$domain"/a \ [ -f '"$HOOK_MARKER"' ] && cat '"$HOOK_MARKER"' && dns=""' "$TARGET_FILE"
fi
# Patch udhcpc.script if it exists (e.g. SoundTouch 10)
TARGET_SCRIPT="/opt/Bose/udhcpc.script"
if [ -f "$TARGET_SCRIPT" ] && ! grep -q "$HOOK_MARKER" "$TARGET_SCRIPT"; then
sed -i '/echo "search \$search_list # \$interface" >> \$RESOLV_CONF/a \ [ -f '"$HOOK_MARKER"' ] && cat '"$HOOK_MARKER"' >> '"\$RESOLV_CONF"' && dns=""' "$TARGET_SCRIPT"
fi
fi
chmod +x /mnt/nv/rc.local.The SoundTouch service includes a built-in DNS server specifically designed for Bose devices.
When enabled, the DNS server:
api.bose.com, streaming.bose.com, bmx.bose.com) and resolves them to the AfterTouch service IP.8.8.8.8).You can enable and configure the DNS server via the Web UI or environment variables:
ENABLE_DNS_DISCOVERY=true: Turns on the DNS server.DNS_BIND_ADDR=:53: The port to listen on (requires root privileges for port 53).DNS_UPSTREAM=1.1.1.1: Your preferred upstream DNS provider. Note: Ensure this is not set to the same address as the DNS server itself (loopback or local IP) to avoid forwarding loops. The server includes built-in loop prevention, but misconfiguration will cause forwarding to fail. DNS Discovery cannot be enabled if this setting is empty.Even without migrating a device, you can use the DNS server to discover what a device is querying by manually setting your router’s DNS or the device’s DNS to point to the AfterTouch service.
The SoundTouch service includes a powerful Mirroring feature that allows you to handle requests locally while simultaneously forwarding them to the official Bose cloud in the background. This is primarily used for maintaining long-term compatibility and verifying the accuracy of the local emulation.
When an endpoint is configured for mirroring:
The Parity Logger automatically compares the response from your local service with the one received from Bose. If it detects any discrepancies, it:
[PARITY] Mismatch detected for GET /...data/parity_mismatches/.Each report includes the full request, both response bodies, and a summary of what differed (status codes, content types, or missing/different XML tags).
Mirroring is configured via the Settings tab in the Web UI or through global settings:
*) to match variable parts like account or device IDs.
/streaming/account/*/device/*/recent/accounts/*/devices/*/presets/*Mirrored requests are also recorded in the Interaction Log under the category upstream-mirror, allowing you to see side-by-side exactly how our service’s behavior compares to the official one.
GET /setup/devicesLists all discovered SoundTouch devices with their current status.
Response:
[
{
"device_id": "08DF1F0BA325",
"name": "Living Room Speaker",
"ip_address": "192.168.1.100",
"product_code": "SoundTouch 20",
"firmware_version": "19.0.5",
"migrated": true,
"last_seen": "2024-01-15T10:30:00Z"
}
]
POST /setup/discoverTriggers immediate network device discovery.
GET /setup/info/{deviceIP}Gets detailed device information and configuration.
GET /setup/migration-summary/{deviceIP}Analyzes device configuration and provides migration preview.
Response:
{
"device_name": "Living Room Speaker",
"device_model": "SoundTouch 20",
"firmware_version": "19.0.5",
"ssh_success": true,
"current_config": "<?xml version=\"1.0\"?>...",
"planned_config": "<?xml version=\"1.0\"?>...",
"remote_services_enabled": false,
"migration_required": true
}
POST /setup/migrate/{deviceIP}Migrates device to use local services.
Query Parameters:
target_url: Custom service URL (optional)proxy_url: Proxy URL for fallback (optional)marge: Set to “original” to proxy Marge requests (optional)stats: Set to “original” to proxy stats requests (optional)sw_update: Set to “original” to proxy update requests (optional)bmx: Set to “original” to proxy BMX requests (optional)GET /bmx/registry/v1/servicesReturns available media services for device registration.
GET /bmx/tunein/v1/playbook/station/{stationID}Provides TuneIn station playback information.
GET /bmx/tunein/v1/podcast/{podcastID}Returns podcast episode information and playback URLs.
GET /marge/streaming/sourceprovidersLists available music service providers.
GET /marge/accounts/{account}/devices/any/presetsReturns user presets for synchronization.
GET /marge/accounts/{account}/devices/any/recentsReturns recent playback items.
PUT /marge/accounts/{account}/devices/{device}/presets/{slot}Updates a specific preset slot.
POST /marge/streaming/support/addrecentAdds item to recent playback history.
GET /marge/updates/soundtouchReturns software update configuration (disabled by default).
GET /proxy/{encodedURL}Proxies requests to external services with logging.
Example:
# Proxy request to Bose services
curl "http://localhost:8000/proxy/aHR0cHM6Ly9hcGkuc291bmR0b3VjaC5ib3NlLmNvbS8="
GET /healthReturns service health status.
GET /events/{deviceID}WebSocket endpoint for real-time device events.
GET /stats/usageReturns usage statistics.
GET /stats/errorsReturns error statistics.
The web management interface provides a comprehensive dashboard for managing your SoundTouch devices:
URL: http://localhost:8000/
.http recording content directly in the browser..tar.gz archives for offline analysis or bug reports.The service automatically records all HTTP interactions (both those handled locally and those proxied upstream) as .http files. These files are compatible with the IntelliJ IDEA HTTP Client.
To prevent internal management traffic (like the Web UI or setup API calls) from cluttering your interaction logs, you can configure Internal Paths. Requests matching these patterns will be processed normally but will not be recorded by the RecordMiddleware.
By default, we recommend adding:
/setup/*: Management API calls/web/*: Static Web UI resources/media/*: Icons and static mediaYou can configure these via the Settings tab in the Web UI or using the --internal-paths flag.
{timestamp}-{pid}.0001-, 0002-) to preserve the exact order of requests across the entire session.,). The original values are preserved as comments at the top of the recorded .http files for easy identification.http-client.env.json file is generated for each session, allowing you to re-play the recorded requests immediately in IntelliJ IDEA.By default, the service redacts sensitive information from the recorded .http files, including:
Authorization headersCookie headersX-Bose-Token headersX-Bose-Key headersProxy-Authorization headersThis behavior is controlled by the --redact-logs flag or the REDACT_PROXY_LOGS environment variable.
The service uses regex patterns to identify variable segments in URL paths. These patterns are loaded from data/patterns.json. You can add custom patterns to this file to support additional variable segments:
[
{
"name": "MyVariable",
"regexp": "^[0-9]{5}$",
"replacement": "{myVar}"
}
]
Variables found via these patterns will be:
interactions/ folder..http files.http-client.env.json file with their actual values.By default, the service creates a data/ directory in the current working directory:
data/
├── accounts/
│ └── default/
│ ├── devices/
│ │ ├── {DEVICE_ID}/
│ │ │ ├── DeviceInfo.xml
│ │ │ └── config_backup_*.xml
│ │ └── ...
│ ├── Sources.xml
│ ├── Presets.xml
│ └── Recents.xml
├── interactions/
│ └── {SESSION_ID}/
│ ├── self/
│ │ └── {PATH}/
│ │ └── {SEQ}-{TIME}-{METHOD}.http
│ ├── upstream/
│ │ └── {PATH}/
│ │ └── {SEQ}-{TIME}-{METHOD}.http
│ └── http-client.env.json
├── dns/
│ └── discoveries.json
├── stats/
│ ├── usage/
│ │ └── *.json
│ └── error/
│ └── *.json
└── events/
└── device_events_*.log
accounts/default/devices/{DEVICE_ID}/)accounts/default/)dns/)stats/)events/)interactions/)YYYYMMDD-HHMMSS-PID).# Manual backup
cp -r data/ backup-$(date +%Y%m%d)/
# Automated backup (cron example)
0 2 * * * cp -r /path/to/data/ /backup/soundtouch-$(date +\%Y\%m\%d)/
# Moving to new server
tar czf soundtouch-data.tar.gz data/
# Transfer to new server
tar xzf soundtouch-data.tar.gz
# Clean old event logs (older than 30 days)
find data/events/ -name "*.log" -mtime +30 -delete
# Clean old statistics (older than 90 days)
find data/stats/ -name "*.json" -mtime +90 -delete
http://localhost:8000/ or http://localhost:8000/web/GET /setup/devices: List all known (auto-discovered and manual) devices.POST /setup/devices: Manually add a device by IP.POST /setup/discover: Trigger a new network discovery scan.GET /setup/discovery-status: Check if a scan is currently in progress.POST /setup/sync/{deviceIP}: Fetch presets, recents, and sources from a device.GET /setup/summary/{deviceIP}: Get a detailed migration readiness summary.POST /setup/migrate/{deviceIP}: Migrate a device using the specified method (XML/Hosts).GET /setup/ca.crt: Download the Root CA certificate for manual installation.GET /setup/interactionsLists recorded interactions with optional filtering.
Query Parameters:
session: Filter by session ID (optional)category: Filter by category (self or upstream) (optional)since: Filter by timestamp (e.g., 2026-02-15 15:00:00) (optional)GET /setup/interaction-statsReturns aggregate statistics about recorded interactions across all sessions.
GET /setup/interaction-content?file={path}Returns the raw content of a specific recorded .http file.
DELETE /setup/interactions/sessions/{sessionID}Deletes all recordings associated with a specific session.
DELETE /setup/interactions/sessions?keep={N}Bulk cleanup: deletes all but the most recent N sessions.
GET /setup/dns-discoveriesReturns merged in-memory and persisted DNS discoveries, sorted by last seen timestamp.
DELETE /setup/dns-discoveriesClears all recorded DNS discovery data from memory and disk.
/bmx/registry/v1/services: BMX service registry./bmx/tunein/v1/*: TuneIn radio emulation./marge/accounts/*: Account and device management./marge/updates/soundtouch: Software update emulation./proxy/*: Logging proxy for original Bose services.# Check network connectivity
ping 192.168.1.100
# Trigger manual discovery
curl -X POST http://localhost:8000/setup/discover
# Check device accessibility
curl http://192.168.1.100:8090/info
# Check SSH connectivity
ssh-keyscan 192.168.1.100
# Get migration summary
curl http://localhost:8000/setup/migration-summary/192.168.1.100
# Verify device configuration
curl http://192.168.1.100:8090/info
# Test local service endpoints
curl http://localhost:8000/health
curl http://localhost:8000/bmx/registry/v1/services
curl http://localhost:8000/marge/streaming/sourceproviders
Enable debug logging for detailed troubleshooting:
LOG_PROXY_BODY=true REDACT_PROXY_LOGS=false soundtouch-service
# Monitor service logs
tail -f /var/log/soundtouch-service.log
# Analyze proxy traffic
grep "PROXY" /var/log/soundtouch-service.log
# Check device events
ls -la data/events/
This service implementation is based on and inspired by several excellent community projects:
We are grateful to these projects for paving the way and providing the research foundation that made this comprehensive service implementation possible.
// Example: Custom BMX service handler
package main
import (
"net/http"
"github.com/go-chi/chi/v5"
)
func customBMXHandler(w http.ResponseWriter, r *http.Request) {
// Custom BMX service logic
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(`{"custom": "service"}`))
}
func main() {
r := chi.NewRouter()
r.Get("/bmx/custom/endpoint", customBMXHandler)
http.ListenAndServe(":8000", r)
}
# configuration.yaml
soundtouch:
- host: 192.168.1.100
port: 8090
name: "Living Room Speaker"
rest:
- resource: "http://localhost:8000/setup/devices"
scan_interval: 60
sensor:
- name: "SoundTouch Devices"
value_template: ""
# Health check script
#!/bin/bash
response=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:8000/health)
if [ $response != "200" ]; then
echo "SoundTouch service is down!" | mail -s "Alert" admin@example.com
fi
BIND_ADDR=127.0.0.1 for localhost-only access.REDACT_PROXY_LOGS only in development environments.# For many devices, increase discovery interval
DISCOVERY_INTERVAL=10m soundtouch-service
# For high-traffic environments, consider reverse proxy
nginx -> soundtouch-service instances