mirror of
https://github.com/gesellix/Bose-SoundTouch.git
synced 2026-08-18 08:36:13 +00:00
Use the repo's own scripts/decrypt-diagnostic.go (not the generic age CLI), unpack per-file next to the .age, and note the archive layout. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
273 lines
11 KiB
Markdown
273 lines
11 KiB
Markdown
# CLAUDE.md
|
|
|
|
Entry point for any Claude Code (or human) session working on this
|
|
repository. Read it before touching code.
|
|
|
|
## What this project is
|
|
|
|
Go library and toolset for controlling Bose SoundTouch speakers via
|
|
the local network API, plus a local cloud-service emulator. Bose
|
|
discontinued the SoundTouch cloud — this project keeps existing
|
|
speakers usable without it.
|
|
|
|
**Module:** `github.com/gesellix/bose-soundtouch`
|
|
|
|
Key binaries:
|
|
|
|
- `soundtouch-cli` — command-line control of one or more speakers
|
|
(status, play, presets, groups, migration, …).
|
|
- `soundtouch-service` — replacement for `streaming.bose.com`
|
|
and the `bmx` services, default port `8000`.
|
|
- `soundtouch-player` — Web UI for Radio browsing and device control.
|
|
- `soundtouch-backup` — Helper for on-device backup and restore.
|
|
|
|
Per-session pickup notes live in two local files at the repo root (they are `.gitignore`d and only exist if created during a session):
|
|
|
|
- `NEXT.md` — current "pick up here" log of open items.
|
|
- `DONE.md` — archive of recently resolved items.
|
|
|
|
## How a new session should start
|
|
|
|
1. Read this file.
|
|
2. Read `NEXT.md` if it's present — that's where running context lives.
|
|
3. Skim `README.md` for the user-facing pitch.
|
|
4. Skim `docs/` for the area you're touching. Long-form notes
|
|
(analysis, guides, troubleshooting) live there, not in the code.
|
|
5. Run `make check` once to confirm the local environment compiles,
|
|
vets, and tests cleanly.
|
|
|
|
## Build, test, run
|
|
|
|
```bash
|
|
# Build
|
|
make build # All binaries
|
|
make build-cli # Just CLI
|
|
make build-service # Just service
|
|
make build-player # Just web player
|
|
make build-all # Cross-platform builds (Linux, macOS, Windows)
|
|
make install # Install to $GOPATH/bin
|
|
|
|
# Quality
|
|
make test # Unit tests
|
|
make test-coverage # Coverage reports
|
|
make check # fmt + vet + test
|
|
make lint # golangci-lint
|
|
make update-static-deps # Update frontend libraries (preact, htm) from node_modules
|
|
|
|
# Automation
|
|
A GitHub Action automatically runs `make update-static-deps` on Dependabot PRs that modify `package.json` to keep the vendored `.js` files in sync. Note: This requires `npm` to be installed.
|
|
|
|
# Development
|
|
make dev-service # Run local service on port 8000
|
|
make dev-discover # Discover devices on the LAN
|
|
make dev-info HOST=<ip> # Get device info
|
|
|
|
# Docker
|
|
make docker-build
|
|
make docker-run-host
|
|
```
|
|
|
|
**Pre-push quality gate:** `make lint` (golangci-lint) must be clean
|
|
before `git push`. CI runs it on every PR; running it locally first
|
|
saves a round-trip. `make check` covers `lint` is its own target —
|
|
combine as needed.
|
|
|
|
## Integration tests
|
|
|
|
The `.http` integration tests under `tests/integration/http-client/`
|
|
run via `make test-http-client`, which spins up the service plus
|
|
support mocks (`spotify-mock`, `amazon-mock`) using
|
|
`docker-compose.yml` + `docker-compose.ci.yml`, executes the suite
|
|
through the JetBrains HTTP client image, then tears the stack down.
|
|
Requires Docker.
|
|
|
|
The compose CI override mounts `tests/integration/testdata/` into the
|
|
service container as its persistent data dir. That directory is
|
|
listed in `tests/.gitignore` — it's local developer state, not source.
|
|
|
|
**Treat the testdata dir as debug evidence, not disposable scratch.**
|
|
When a fixture or schema change makes the old state stale (e.g.
|
|
post-anonymisation, the previous run's IPs no longer match the
|
|
assertions), don't `rm -rf` it — archive it:
|
|
|
|
```bash
|
|
make test-http-client-rotate # renames testdata/ → testdata_<timestamp>/
|
|
make test-http-client # fresh run on a clean slate
|
|
```
|
|
|
|
The rotate target is non-destructive (it moves, never deletes) and
|
|
opt-in (no other target invokes it). Old archives stay around for
|
|
retrospective diffing whenever something goes sideways.
|
|
|
|
## Decrypting diagnostic reports
|
|
|
|
Reporters attach an encrypted diagnostic archive
|
|
(`aftertouch-diagnostic-*.age`), usually saved under `_/i_<reporter>/`.
|
|
Decrypt it with **this repo's own tool**, not the generic `age` CLI:
|
|
|
|
```bash
|
|
go run scripts/decrypt-diagnostic.go <file.age> | tar xz -C <dir containing the .age>
|
|
```
|
|
|
|
- The tool is `scripts/decrypt-diagnostic.go`; the private key lives at
|
|
`keys/private/diagnostic` (provisioned by `scripts/setup-diagnostic-key.sh`,
|
|
and never committed). It writes the decrypted `.tar.gz` to stdout.
|
|
- Always decrypt/unpack next to the `.age` (not a scratch/tmp dir), into a
|
|
**per-file subfolder** so nothing collides: e.g.
|
|
`mkdir -p <dir>/extracted-<timestamp> && go run scripts/decrypt-diagnostic.go <dir>/<file>.age | tar xz -C <dir>/extracted-<timestamp>`.
|
|
This matters when a reporter folder holds multiple `.age` snapshots or
|
|
already has other files: every archive uses the same inner names
|
|
(`diagnostic.json`, `datastore/`, `http/`, ...), so extracting two into the
|
|
same dir overwrites and mixes them.
|
|
- The archive contains `diagnostic.json` (health/device summary), `datastore/`
|
|
(raw speaker XML: DeviceInfo/Presets/Recents/Sources), `http/` (service
|
|
`full.xml`, `sourceproviders.xml`, captured speaker responses), `logs/`,
|
|
`settings.json`, `env.txt`, `system/`, `ssh/`. See
|
|
`docs/content/docs/appendix/DIAGNOSTIC-EXPORT.md`.
|
|
- Reporter data stays under `_/` and is never committed (see "What never goes
|
|
into this repo").
|
|
|
|
## Project structure
|
|
|
|
```
|
|
cmd/
|
|
soundtouch-cli/ # CLI tool for device control
|
|
soundtouch-service/ # Local cloud service emulator
|
|
soundtouch-player/ # Web UI (TuneIn browser, device control)
|
|
soundtouch-backup/ # On-device backup helper
|
|
example-*/ # Usage examples
|
|
pkg/
|
|
client/ # HTTP + WebSocket client for the SoundTouch Web API
|
|
models/ # XML/JSON data structures
|
|
discovery/ # Device discovery (mDNS + UPnP, unified interface)
|
|
config/ # Configuration management
|
|
service/
|
|
bmx/ # Bose Media eXchange service emulation
|
|
marge/ # Device-management service emulation
|
|
handlers/ # HTTP request handlers (pkg/service/handlers/)
|
|
proxy/ # HTTP proxy with request recording
|
|
datastore/ # Persistent device data storage
|
|
certmanager/ # TLS certificate management
|
|
setup/ # Device migration and configuration
|
|
spotify/ # Spotify integration
|
|
stockholm/ # Optional Stockholm frontend bridge
|
|
soundtouchweb/ # SoundTouch Web UI service logic
|
|
examples/ # Feature demonstration programs
|
|
docs/ # Long-form analysis, guides, troubleshooting
|
|
.junie/ # Communication-style guidelines (see below)
|
|
```
|
|
|
|
## Key technologies
|
|
|
|
- **Go 1.26.3+**
|
|
- **chi v5** — HTTP router
|
|
- **gorilla/websocket** — WebSocket for real-time events
|
|
- **hashicorp/mdns** — mDNS device discovery
|
|
- **miekg/dns** — DNS operations and a custom DNS server
|
|
- **urfave/cli/v2** — CLI framework
|
|
|
|
## Architecture notes
|
|
|
|
- `pkg/client` is the core library for device API calls (HTTP + WebSocket).
|
|
- `pkg/service` is the local cloud replacement; routes wire to the
|
|
handlers in `pkg/service/handlers/` via chi middleware.
|
|
- Discovery supports both mDNS and UPnP/SSDP behind a unified interface.
|
|
- The SoundTouch Web API uses XML on the wire; internal service-to-service
|
|
messages use JSON.
|
|
- Tests cover unit, integration, parity (local vs. official Bose API
|
|
recordings), and regression. Reproducer tests should be refactored
|
|
into permanent regression or documentation tests rather than deleted.
|
|
|
|
## Load-bearing gotchas
|
|
|
|
### `ETag` header literal must stay capitalised
|
|
|
|
Bose speakers emit the response header with exact capitalisation
|
|
`ETag`. Go's `http.Header.Set` canonicalises to `Etag` (lowercase `t`).
|
|
Real speakers parse strictly — `Etag` is rejected. The codebase
|
|
deliberately bypasses the canonicalisation path; do **not** rewrite
|
|
the string literal `"ETag"` to `"Etag"` anywhere in `pkg/service/handlers/`
|
|
or in tests.
|
|
|
|
The contrast is encoded in two named constants in
|
|
`pkg/service/handlers/handlers_etag_test.go`:
|
|
|
|
```go
|
|
const normalizedEtag = "Etag" // what http.Header.Set produces
|
|
const caseSensitiveETag = "ETag" // what the speaker actually expects
|
|
```
|
|
|
|
Linter suppressions on the canonical-header check live alongside the
|
|
test code. Static-analysis warnings about `"ETag"` are expected;
|
|
don't "fix" them.
|
|
|
|
### Destructive git or filesystem actions need explicit confirmation
|
|
|
|
`git reset --hard`, `git checkout` that would overwrite local changes,
|
|
`git clean -fd`, `rm -rf` on non-build paths, `git stash drop` — all
|
|
should be proposed in writing with their consequences before running,
|
|
unless the user has already authorised that specific action in this
|
|
session. Prefer reversible alternatives (`git stash` over
|
|
`git reset --hard`).
|
|
|
|
**Force-flags also require explicit approval.** `git add -f` (force-add
|
|
a gitignored file), `git push --force`, `git push --force-with-lease`,
|
|
and any other flag that overrides a git safety mechanism must be
|
|
proposed and confirmed before running, for the same reason: they
|
|
bypass protections that exist intentionally.
|
|
|
|
## What never goes into this repo
|
|
|
|
This repository is public. The following must never be committed:
|
|
|
|
- **Real LAN IPs** of personal networks. Use RFC-5737 documentation
|
|
ranges in examples and fixtures: `192.0.2.0/24`, `198.51.100.0/24`,
|
|
`203.0.113.0/24`.
|
|
- **Real MAC addresses** or speaker device IDs from anyone's actual
|
|
hardware. Use `AA:BB:CC:DD:EE:FF` or `DEVICEID01` style placeholders.
|
|
- **Bose account IDs**, serial numbers, or tokens belonging to anyone
|
|
other than the committer's own test devices — and even those should
|
|
be sanitised before publication when feasible.
|
|
- **Bose firmware binaries, NAND dumps, or decompiled Bose code.**
|
|
- **Wi-Fi SSIDs or credentials**, captured or otherwise.
|
|
- **Network captures, traces, or logs** that include data from
|
|
accounts or devices other than your own test hardware.
|
|
- **Personal identifiers**: real names of speakers ("LivingRoom",
|
|
custom device names), private email addresses, household member
|
|
names visible in source IDs.
|
|
|
|
If you spot any of the above already in the tree, treat it as a
|
|
sanitisation task: stop, flag it to the maintainer, propose a
|
|
remediation commit before continuing.
|
|
|
|
## Disclaimers
|
|
|
|
"SoundTouch" and "Bose" are registered trademarks of Bose Corporation.
|
|
This project is an unofficial, community-built effort, not affiliated
|
|
with, endorsed by, or authorised by Bose.
|
|
|
|
## Communication style
|
|
|
|
When working with a human user in this repo:
|
|
|
|
- **Prioritise direct answers** to the question being asked, even when
|
|
it sits outside the current task or project context. Don't divert
|
|
back to whatever you were doing when the user asks something else.
|
|
- **Don't substitute assumptions for real information.** When something
|
|
is unclear, ask or check, rather than guessing and proceeding.
|
|
- **An issue is only "resolved" once the reporter confirms.** Prefer
|
|
"candidate fix, awaiting reporter confirmation" over "fixed" or
|
|
"closed" until the person who reported it says it works. A merged PR
|
|
or a shipped release is not confirmation.
|
|
- **Mind GitHub's `#<id>` auto-linking.** `#<id>` links to issues and
|
|
pull requests only — it does **not** resolve to discussions. For a
|
|
discussion, write the full URL
|
|
(`https://github.com/gesellix/Bose-SoundTouch/discussions/<id>`). For
|
|
security alerts, write e.g. "CodeQL alert 280" (no `#`) or the full
|
|
URL, since `#280` would point at an unrelated issue/PR.
|
|
|
|
These principles also apply to other AI assistants pointed at this
|
|
repo. Tool-specific config dirs (e.g. `.junie/`, `.claude/`) should
|
|
defer to this file as the source of truth instead of carrying their
|
|
own copies.
|