- Use relURL (no leading slash) for the sponsor link so it respects
the /Bose-SoundTouch/ base path on GitHub Pages; absURL and relURL
both ignore the base path when the input starts with /
- Inject HUGO_PARAMS_GITHASH (github.sha) via the docs workflow and
forward it into the Hugo container via docker-compose.docs.yml +
make dev-docs, so the deployed footer shows a clickable short hash
linking to the exact commit
- Use site.Params.githash (global) instead of .Site.Params.githash
because Hextra calls custom/footer.html with a dict context, not a
page; .Site is nil in that scope
- Use substr not slice to trim the hash to 7 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace docs/_config.yml + docs/SUMMARY.md with Hugo + Hextra theme.
Move all content into docs/content/, images into docs/static/images/.
Update docs_consistency_test.go to check Hugo front matter instead of
SUMMARY.md inclusion. Update CI workflow and screenshot script paths.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Sets up Dependabot for JS dependency updates
- Adds GitHub workflow for automated static dependency updates
- Creates update script for Preact and other static JS libraries
- Updates Preact to latest version via new automation
Surfaced via the rfc-5737-cleanup sweep: after the anonymisation pass
updated test-suite assertions to RFC-5737 IPs, the next
`make test-http-client` run failed against the stale local
tests/integration/testdata/ left over from a previous build (which
still carried the old 192.168.1.x state via the compose volume).
Two changes, in one commit so the doc references the target it
documents:
1. Makefile: new `test-http-client-rotate` target that renames any
existing tests/integration/testdata/ to
tests/integration/testdata_<timestamp>/. Non-destructive (mv, not
rm), opt-in (no other target invokes it). Archives stay around
for retrospective debugging — that directory is debug evidence,
not disposable scratch.
2. CLAUDE.md: new "Integration tests" section under Build/test/run.
Explains the docker-compose stack, the testdata mount, the
per-machine-only nature (via tests/.gitignore), and the
rotate-then-run pattern when fixtures or schemas have changed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Five `192.168.1.x` references in Makefile usage-error messages and
the `make help` example block. Same hygiene argument as the docs
sweep in 136d24a — replaced with `192.0.2.x` so the example output
clearly reads as a placeholder, not a real LAN.
Behaviour unchanged: these are echo-only strings printed when the
user forgets to set HOST=… or asks for `make help`. The
HOST=<your-IP> contract is unaffected.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two patching gaps caused every Stockholm HTTP-proxy call from a
/stockholm/* page to hit /api/http-proxy (404) instead of the
basePath-prefixed /stockholm/api/http-proxy:
1. The proxy URL constant in browser_http_proxy.js is declared as
`var PROXY_PATH` (uppercase). Our patch script only knew about the
lowercase `var proxyPath` form used in app_comm.js, so it never
matched the upstream file.
2. Even if the constant had matched, browser_http_proxy.js's IIFE
evaluates the URL at script-load time — but the injected bootstrap
that defines window.__stockholmBase is placed just before </head>,
i.e. after the <script src=…> tags. The captured value would
always fall back to the unprefixed "/api/http-proxy".
3. The Makefile never passed browser_http_proxy.js to the patch script
at all.
Fix:
- Add an uppercase `PROXY_PATH` replacement entry in
patch-stockholm-bridge.py (keeps the lowercase one for
app_comm.js).
- Add a second replacement that rewrites the **use site** in
browser_http_proxy.js to inline `(window.__stockholmBase||"") +
"/api/http-proxy?url=" + ...`. Reading __stockholmBase at
call-time bypasses the load-order trap; the patched
`var PROXY_PATH = …` declaration above becomes dead code but
stays harmless.
- Pass `$(STOCKHOLM_DIR)/js/browser_http_proxy.js` to the patch
script in the prepare-stockholm target so it actually gets
rewritten.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Compresses the local roundtrip to a single command:
make build-stockholm-image # one-time
make prepare-stockholm # once per zip update
make dev-service-stockholm # iterative loop
The target only checks that prepare-stockholm has produced
stockholm/index.html (a fast file stat) — it deliberately does NOT
re-run the Docker preparation step on every launch, since that takes
tens of seconds and produces identical output most of the time. Fails
loudly with a hint if Stockholm isn't prepared.
Listed in `make help` under the existing dev-* group. Not added to
.PHONY because the surrounding dev-service / dev-service-proxy targets
aren't either — matching local convention rather than gold-plating.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Implements pkg/service/stockholm with bridge (appSend/runQueue), HTTP
proxy, static serving, config URL rewriting, native state persistence,
and device discovery. Mounts under a configurable base path (/stockholm
by default) with correct http.StripPrefix routing and apiBase-prefixed
bridge API routes matching the patched JS window.__stockholmBase calls.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Dockerfile.stockholm clones github.com/krahl/soundcork-stockholm-app at build
time and installs the required tools (prettier, patch, unzip, jq). No pre-built
image is published upstream, so users must run `make build-stockholm-image` once
before `make prepare-stockholm`.
`make prepare-stockholm` runs the upstream entrypoint logic (extract zip,
run prettier, apply patches) via a volume-mounted docker run, stopping before
`exec java` so we only collect the processed stockholm/ output. The Go service
then serves that directory directly with no patching required at runtime.
Prerequisites: Docker with internet access, and stockholm_zip/stockholm.zip
(Stockholm source zip placed manually — tracked directory, zip gitignored).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds rename_device.http between get_group.http and unregister_device.http
in the make test-http-client sequence. The new test fires the PUT
the speaker emits after a rename and asserts:
- 200 OK, content type vnd.bose.streaming-v1.2+xml
- the response carries the renamed value
- createdOn matches the value captured during register_device.http
(cross-request global), locking in the "first-paired" semantics
- ipaddress is preserved from the prior power_on, not reset by the
rename body's empty IP field
- a mismatched body deviceid is rejected with 400
register_device.http captures the initial createdOn into a global so
the rename test can assert equality rather than a flakier
updatedOn != createdOn heuristic. The variant POST's stale
updatedOn === createdOn assertion is replaced with an upsert-aware
equality against the same captured global.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add an end-to-end IntelliJ HTTP Client test that replays the exact
request shape a SoundTouch 10 master sends to its configured Marge
server during stereo-pair formation (captured live in issue #252):
POST /streaming/account/{accountId}/group/
Authorization: Bearer <token>
Content-Type: application/vnd.bose.streaming-v1.2+xml
<group>
<masterDeviceId>...</masterDeviceId>
<name>TEST</name>
<roles>
<groupRole><deviceId>...</deviceId><role>LEFT</role></groupRole>
<groupRole><deviceId>...</deviceId><role>RIGHT</role></groupRole>
</roles>
</group>
Assertions cover the wire contract that fails loudly if regressed:
trailing-slash URL is matched, response is 201 Created with the vendor
media type, Location header references the new group under the
account, and the body echoes masterDeviceId, name, and both groupRole
entries.
Wired into the make test-http-client target, sequenced before
get_group.http so the GET runs against the post-create state.
get_group.http's assertion only checks for the presence of a <group>
element, so adding a populated group beforehand is compatible.
Refs #252
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Refreshes docs/images/ui-{settings,devices,sync,migration}.png by
driving the web UI in chromedp against a synthetic speaker, so
documentation can be regenerated without real hardware and without
leaking personal data from the local network.
Three independent pieces:
- pkg/service/testing/fakespeaker — embeddable library serving the
HTTP and telnet surface the migration wizard probes (/info,
/presets, /recents and a getpdo CurrentSystemConfiguration reply
that places the device on the unmigrated happy path).
- cmd/dummy-speaker — thin CLI wrapping the library; self-registers
with a running service via POST /setup/devices.
- scripts/screenshots — chromedp runner driven by a JSON manifest;
decoupled from speaker/service setup so it can target any backend
URL. run.sh orchestrates a one-shot end-to-end capture and seeds
settings.json with a generic hostname plus discovery disabled to
keep real-network state out of the captures.
Captures are at DPR=2 for retina-sharp text.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds build-linux-armv7 target (GOOS=linux GOARCH=arm GOARM=7 CGO_ENABLED=0)
for deployment to old embedded Linux devices (kernel 3.14+). Introduces
BUILDFLAGS=-trimpath -ldflags="-s -w" applied to all build targets for
smaller, reproducible binaries without local path leakage.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Introduces a standalone `soundtouch-backup` CLI with three subcommands:
- `all`: authenticates with the Bose cloud, backs up account data, then
reads device IPs from devices.xml and backs up each reachable speaker
- `cloud`: fetches account profile, devices, sources, presets, and full
endpoint from streaming.bose.com
- `local`: backs up each speaker via HTTP API (12 endpoints) and
optionally via SSH (individual files + /opt/Bose/etc/ and
/mnt/nv/BoseApp-Persistence/1/ directories)
Also centralises pkg/service/ssh → pkg/ssh so both the service and the
backup tool share the same SSH client; adds ReadFile and ReadDir
methods, and handles the firmware quirk where cat exits 1 on empty
files.
Output is a single dated .tar.gz or .zip archive.
Example flow:
```shell
gesellix@Mac Bose-SoundTouch % go run ./cmd/soundtouch-backup all --output _/cloud-backup --email user@example.com
Password:
Authenticating as user@example.com...
✓ Authenticated (account ID: 1234567)
✓ email address (107 bytes)
✓ devices (1492 bytes)
✓ sources (1111 bytes)
✓ presets (2585 bytes)
✓ full account (55037 bytes)
Found 2 device(s) in cloud account, attempting local backup...
✓ ST20: 12 files via HTTP
⚠ ST20: SSH skipped /etc/remote_services (Process exited with status 1)
⚠ ST20: SSH empty file /mnt/nv/remote_services
✓ ST20: 64 files via SSH
✓ ST10: 12 files via HTTP
⚠ ST10: SSH empty file /etc/remote_services
⚠ ST10: SSH skipped /mnt/nv/remote_services (Process exited with status 1)
✓ ST10: 48 files via SSH
Archive written: _/cloud-backup/soundtouch-backup-2026-05-02.tar.gz (141 files)
```
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add cmd/mock-amazon/main.go (mirrors mock-spotify, uses testutils/amazon)
- Add amazon-mock service to docker-compose.yml (port 8082)
- Add AMAZON_CLIENT_ID/SECRET/TOKEN_URL/PROFILE_URL to docker-compose.ci.yml
- Add amazon_registration.http: registers account via /mgmt/amazon/callback
before the token-refresh test runs (mirrors spotify_registration.http)
- Update {{amazonRefreshToken}} in env to match mock response (Atzr|amazon-refresh-token)
- Log amazon-mock output on test failure in Makefile
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
feat: improve Bose SoundTouch parity, Spotify integration, and data
reliability
- Update XML marshaling for ServicePreset and ServiceRecent to match
Bose parity requirements.
- Add support for adding music sources via
`/streaming/account/{account}/source`.
- Implement HandleBoseAccountToken for Spotify OAuth code exchange and
token persistence.
- Implement atomic file writes in the datastore to prevent data
corruption.
- Add startup logic to initialize default sources for existing devices.
- Expand test coverage with new parity regression and Spotify
integration tests.
---------
Co-authored-by: Junie <junie@jetbrains.com>
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
- Use debug.ReadBuildInfo() for version information (Go 1.18+ best practice)
- Extract version from module info and VCS settings (vcs.revision, vcs.time)
- Remove complex ldflags setup from Makefile and GitHub workflows
- Simplify build process while maintaining all version information
- Cleaner approach recommended by Go community
Thanks to Gopher Slack feedback for this improvement!
- Replace hardcoded version with build-time injected variables
- Add version, commit, and date variables to main.go with default values
- Update Makefile ldflags to use consistent variable names
- Add detailed 'version' subcommand showing build info, Go version, and platform
- Maintain compatibility with existing release workflow
- Support both --version flag (simple) and version subcommand (detailed)
- Implement GET/POST /clockTime endpoints for device time management
- Implement GET/POST /clockDisplay endpoints for clock display configuration
- Implement GET /networkInfo endpoint with real API structure
- Add comprehensive models for ClockTime, ClockDisplay, and NetworkInformation
- Update NetworkInformation to match real SoundTouch API responses:
* WiFi interfaces with SSID, frequency, signal strength, and connection state
* Ethernet interfaces with connection state
* Proper attribute-based XML structure matching actual device responses
- Add CLI support for all system endpoints with detailed output formatting
- Add comprehensive test coverage for all new models and client methods
- Update documentation to reflect real API structure and capabilities
- Anonymize all personal data (IP addresses, device IDs, device names)
- Add SYSTEM-ENDPOINTS.md documentation with real-world examples
Features:
- Clock time sync with current system time or specific timestamps
- Clock display configuration (enable/disable, format, brightness, auto-dim)
- Rich network interface information with WiFi signal quality and frequency bands
- Support for both WiFi and Ethernet SoundTouch devices
- Validated against real SoundTouch 10 and SoundTouch 20 device responses
All tests pass and builds successfully.
Features Added:
• mDNS/Bonjour discovery using hashicorp/mdns library
• Unified discovery service combining UPnP + mDNS + configuration
• Parallel discovery execution for optimal performance
• Comprehensive logging for both UPnP and mDNS discovery
• Network diagnostic tools for troubleshooting
New Discovery Methods:
• Configuration-based (fastest, most reliable)
• UPnP/SSDP discovery (widely supported, enhanced logging)
• mDNS/Bonjour discovery (Apple ecosystem friendly)
New Programs & Tools:
• cmd/example-mdns - Standalone mDNS discovery testing
• cmd/example-upnp - Isolated UPnP/SSDP discovery testing
• cmd/mdns-scanner - Network diagnostic tool for mDNS services
Enhanced Build System:
• make dev-mdns / dev-mdns-verbose (mDNS testing)
• make dev-upnp / dev-upnp-verbose (UPnP testing)
• make dev-scan-all (scan all network services)
• make dev-scan-soundtouch (scan for SoundTouch services)
Documentation:
• docs/DISCOVERY.md - Comprehensive discovery guide
• Updated README.md with new features and commands
• Full API documentation and troubleshooting guide
Technical Improvements:
• Detailed request/response logging for UPnP M-SEARCH
• Step-by-step mDNS service discovery tracking
• IP address resolution with IPv4/IPv6 handling
• Service name parsing and device info extraction
• Robust error handling and network diagnostics
Backward Compatibility:
• No breaking changes to existing APIs
• All existing tests pass
• CLI interface unchanged but enhanced
• Legacy UPnP-only service still available
- Implement HTTP client with XML support for SoundTouch Web API
- Add UPnP device discovery with SSDP protocol
- Create type-safe Go models for API responses
- Build CLI tool with device discovery and info commands
- Add comprehensive configuration management via .env and env vars
- Include extensive documentation (API endpoints, patterns, development guide)
- Translate all German documentation to English
- Set up modern Go project structure with testing framework
- Add Makefile for cross-platform builds and development workflow
Features:
✅ Device discovery (UPnP + manual configuration)
✅ Device information retrieval
✅ XML request/response handling
✅ CLI interface with flexible device targeting
✅ Cross-platform compatibility
✅ Comprehensive test coverage with mock data
✅ Production-ready configuration management