Bose SoundTouch Toolkit

Documentation for controlling and preserving Bose SoundTouch devices

View the Project on GitHub gesellix/Bose-SoundTouch

SoundTouch Service

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.

Overview

The service provides:

Architecture

The service consists of several key components:

BMX Services (Bose Media eXchange)

Marge Services (Account & Device Management)

Discovery & Migration

Installation

Install from Source

go install github.com/gesellix/bose-soundtouch/cmd/soundtouch-service@latest

Build from Repository

git clone https://github.com/gesellix/bose-soundtouch.git
cd Bose-SoundTouch
go build -o soundtouch-service ./cmd/soundtouch-service

Docker Support

You can run the SoundTouch service using Docker or Docker Compose.

Note for macOS and Windows users: The --net host option 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.

Using Docker

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_URL and HTTPS_SERVER_URL are automatically added as Subject Alternative Names (SAN) to the generated TLS certificate, ensuring valid SSL connections.

Using Docker Compose

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

Quick Start

1. Start the Service

# Start with default settings (port 8000)
soundtouch-service

2. Access the Web Interface

Open your browser to http://localhost:8000 to access the management interface.

3. Discover Devices

The service will automatically start discovering SoundTouch devices on your network. You can also trigger manual discovery from the web UI or API.

4. Migrate Devices

Use the web interface or API to migrate devices from Bose cloud services to your local instance.

Configuration

Configuration Precedence

The service supports multiple ways to configure its behavior. When multiple sources provide the same setting, the following precedence rules apply (highest to lowest):

  1. 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.
  2. Environment Variables / CLI Flags: If a setting is not present in settings.json, environment variables and flags are used.
  3. Default Values: If no configuration is provided, the service uses its built-in defaults.

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.json file in your data directory, as it might be overriding your manual configuration.

Configuration Options

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

Configuration Examples

# 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

Understanding Migration

Device migration switches your SoundTouch devices from Bose’s cloud services to your local service instance. This process:

  1. Backs up existing device configuration
  2. Updates device service URLs to point to your local server
  3. Maintains all existing presets and settings
  4. Enables offline operation and advanced debugging

Migration Methods

  1. Start the service: soundtouch-service
  2. Open http://localhost:8000
  3. Wait for device discovery to complete
  4. Click “Migrate” next to each device
  5. Monitor migration status in real-time

API Migration

# 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

Advanced Migration Options

# 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"

Post-Migration Verification

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"

ResolvConf Migration (DHCP-Aware DNS Redirection)

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 :53 and ensure the server has successfully bound to it (check Settings for status) before the actual migration.

Advantages:

How it works:

  1. Configuration: A custom file named /mnt/nv/aftertouch.resolv.conf is created on the device’s persistent partition.
  2. Boot Hook: On every boot, /mnt/nv/rc.local checks if the system’s DHCP scripts (/etc/udhcpc.d/50default or /opt/Bose/udhcpc.script) have been patched.
  3. Surgical Patch: If not patched, it injects a one-line check into the relevant DHCP scripts.
  4. Resolution: Whenever the device acquires a DHCP lease, the scripts now read your aftertouch.resolv.conf first, placing your DNS server at the top of /etc/resolv.conf while keeping all other DHCP-provided settings.

Setup:

  1. Enable SSH via the remote_services USB trick.
  2. Create /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
    
  3. Update /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
    
  4. Make the script executable: chmod +x /mnt/nv/rc.local.
  5. Reboot the speaker.

DNS Discovery Server

The SoundTouch service includes a built-in DNS server specifically designed for Bose devices.

How it Works

When enabled, the DNS server:

  1. Receives DNS queries from migrated SoundTouch devices.
  2. Intercepts known Bose domains (e.g., api.bose.com, streaming.bose.com, bmx.bose.com) and resolves them to the AfterTouch service IP.
  3. Logs all other queries for discovery purposes, allowing you to identify new Bose cloud endpoints.
  4. Forwards unknown or non-Bose queries to the configured upstream DNS server (default: 8.8.8.8).

Configuration

You can enable and configure the DNS server via the Web UI or environment variables:

Manual Discovery via DNS

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.

Endpoint Mirroring & Parity Logging

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.

How Mirroring Works

When an endpoint is configured for mirroring:

  1. GET Requests: Handled locally first (Primary). The response is returned to the speaker immediately. In the background, the same request is sent to Bose.
  2. POST/PUT/DELETE Requests: Handled locally first. The service then synchronously (but without blocking the speaker’s response) forwards the request to Bose to ensure the “official” account state stays in sync with your local changes (e.g., updating a preset).

Parity Logging

The Parity Logger automatically compares the response from your local service with the one received from Bose. If it detects any discrepancies, it:

  1. Logs a warning to the console: [PARITY] Mismatch detected for GET /...
  2. Saves a detailed JSON report to 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).

Configuration

Mirroring is configured via the Settings tab in the Web UI or through global settings:

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.

API Reference

Discovery & Setup

GET /setup/devices

Lists 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/discover

Triggers 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:

BMX Services (Bose Media eXchange)

GET /bmx/registry/v1/services

Returns 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.

Marge Services (Account & Device Management)

GET /marge/streaming/sourceproviders

Lists available music service providers.

GET /marge/accounts/{account}/devices/any/presets

Returns user presets for synchronization.

GET /marge/accounts/{account}/devices/any/recents

Returns recent playback items.

PUT /marge/accounts/{account}/devices/{device}/presets/{slot}

Updates a specific preset slot.

POST /marge/streaming/support/addrecent

Adds item to recent playback history.

GET /marge/updates/soundtouch

Returns software update configuration (disabled by default).

Proxy Services

GET /proxy/{encodedURL}

Proxies requests to external services with logging.

Example:

# Proxy request to Bose services
curl "http://localhost:8000/proxy/aHR0cHM6Ly9hcGkuc291bmR0b3VjaC5ib3NlLmNvbS8="

Health & Monitoring

GET /health

Returns service health status.

GET /events/{deviceID}

WebSocket endpoint for real-time device events.

GET /stats/usage

Returns usage statistics.

GET /stats/errors

Returns error statistics.

Web Interface

Overview

The web management interface provides a comprehensive dashboard for managing your SoundTouch devices:

URL: http://localhost:8000/

Features

Device Dashboard

Device Management

Monitoring & Debugging

Interactions & Traffic Analysis

Usage Tips

  1. First Time Setup: The interface will guide you through initial device discovery
  2. Migration Monitoring: Watch migration progress in real-time with detailed status updates
  3. Troubleshooting: Use the debug tools to diagnose device connectivity issues
  4. Log Analysis: Enable detailed logging for development and troubleshooting

HTTP Interaction Recording

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.

Internal Paths (Excluding Traffic)

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:

You can configure these via the Settings tab in the Web UI or using the --internal-paths flag.

Key Features

Configuration

Redaction

By default, the service redacts sensitive information from the recorded .http files, including:

This behavior is controlled by the --redact-logs flag or the REDACT_PROXY_LOGS environment variable.

Custom Patterns

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:

  1. Used as directory names in the interactions/ folder.
  2. Parameterized as `` within the .http files.
  3. Added to the http-client.env.json file with their actual values.

Persistent Data

Data Directory Structure

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

Data Components

Device Data (accounts/default/devices/{DEVICE_ID}/)

Account Data (accounts/default/)

DNS Data (dns/)

Statistics (stats/)

Events (events/)

HTTP Interactions (interactions/)

Data Management

Backup Strategy

# 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)/

Data Migration

# Moving to new server
tar czf soundtouch-data.tar.gz data/
# Transfer to new server
tar xzf soundtouch-data.tar.gz

Cleanup

# 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

API Endpoints

Management UI

Setup API

GET /setup/interactions

Lists recorded interactions with optional filtering.

Query Parameters:

GET /setup/interaction-stats

Returns 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.

DNS Discovery API

GET /setup/dns-discoveries

Returns merged in-memory and persisted DNS discoveries, sorted by last seen timestamp.

DELETE /setup/dns-discoveries

Clears all recorded DNS discovery data from memory and disk.

Emulated Services

Troubleshooting

Common Issues

Device Not Discovered

# 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

Migration Failures

# 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

Service Connectivity Issues

# Test local service endpoints
curl http://localhost:8000/health
curl http://localhost:8000/bmx/registry/v1/services
curl http://localhost:8000/marge/streaming/sourceproviders

Debug Mode

Enable debug logging for detailed troubleshooting:

LOG_PROXY_BODY=true REDACT_PROXY_LOGS=false soundtouch-service

Log Analysis

# 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/

Credits & Inspiration

This service implementation is based on and inspired by several excellent community projects:

SoundCork

ÜberBöse API

We are grateful to these projects for paving the way and providing the research foundation that made this comprehensive service implementation possible.

Advanced Usage

Custom Service Integration

// 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)
}

Integration with Home Assistant

# 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: ""

Monitoring & Alerting

# 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

Security Considerations

Performance Tuning

Resource Usage

Scaling Considerations

# For many devices, increase discovery interval
DISCOVERY_INTERVAL=10m soundtouch-service

# For high-traffic environments, consider reverse proxy
nginx -> soundtouch-service instances