Commit Graph
462 Commits
Author SHA1 Message Date
Tim VahlbrockandTobias Gesellchen 3da023aa78 make curl less verbose 2026-05-10 12:58:00 +02:00
Tim VahlbrockandTobias Gesellchen 7d410eef24 store updates on tmp 2026-05-10 12:58:00 +02:00
Tim VahlbrockandTobias Gesellchen b2dc2cb802 make curl verbose 2026-05-10 12:58:00 +02:00
Tim VahlbrockandTobias Gesellchen bd2e594ba8 download updates to /media to not require additional storage space 2026-05-10 12:58:00 +02:00
Tim VahlbrockandTobias Gesellchen 4257c100ac note on reverting the migration in uninstallation guide 2026-05-10 12:58:00 +02:00
Tim VahlbrockandTobias Gesellchen e008bb6a2b Apply suggestions from code review
Co-authored-by: Tobias Gesellchen <tobias@gesellix.de>
2026-05-10 12:58:00 +02:00
Tim VahlbrockandTobias Gesellchen 1d9264437d add reference to on-device installer to README.md 2026-05-10 12:58:00 +02:00
Tim VahlbrockandTobias Gesellchen ff8bf75982 make default version number the next minor release 2026-05-10 12:58:00 +02:00
Tim VahlbrockandTobias Gesellchen dbe5b90d8d fix typo 2026-05-10 12:58:00 +02:00
Tim VahlbrockandTobias Gesellchen 981ecf6d89 fix typo 2026-05-10 12:58:00 +02:00
Tim VahlbrockandTobias Gesellchen 37d758f7f3 minor fixes 2026-05-10 12:58:00 +02:00
Tim VahlbrockandTobias Gesellchen 370b587fcf feat: Provide scripts and documentation for on-device install 2026-05-10 12:58:00 +02:00
Tobias GesellchenandClaude Opus 4.7 e3dac8b5a6 fix(ui): close CodeQL js/xss-through-dom finding (PR #240 review)
CodeQL alert 132 flagged the reboot status line as a sink that received
user-controlled DOM text (device names from the migration/sync select
options and table rows) without escaping. Six data-flow paths converged
on script.js:1950.

Switch the sink at line 1950 from .innerHTML to .textContent — the
status message has never needed HTML formatting. The pre-existing
display-into-innerHTML pattern still exists elsewhere in this file but
those lines aren't in this PR's scope and are tracked by their own
historical alerts.

Also harden the (newer) `currentP.innerHTML = ... <strong> + data.current
+ </strong> ...` line in loadAccountIDSuggestions: rebuild the paragraph
with replaceChildren + createElement so the account ID never becomes
HTML, even though it's expected to be a 7-digit string.

Coerce known account IDs to String() when populating the existing-account
dropdown so the IDE's type inference stops complaining about
opt.value = id; / opt.textContent = id; on data of unknown[] type.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 12:43:36 +02:00
Tobias GesellchenandClaude Opus 4.7 0b579a7e59 fix(ui): clarify telnet pane wording about deferred Pair Account panel
The previous copy said "see the panel below" while the Pair Account panel
is intentionally hidden until migration succeeds (loadAccountIDSuggestions
makes it visible). Reword so users know the panel will appear after they
click Migrate.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 12:43:36 +02:00
Tobias GesellchenandClaude Opus 4.7 d3b1593953 docs(analysis): add device compatibility matrix for telnet migration
New section §8 records what is currently known about which devices and
firmware our migrateViaTelnet flow handles end-to-end, derived from the
six community sources catalogued in TELNET-COMMAND-REFERENCE.md plus our
issue threads.

* §8.1 — proven to work end-to-end (ST 10, 20, 300, Wave III, Wave IV on
  FW 27.0.6 with multi-reporter agreement).
* §8.2 — proven to need the PairAccount telnet fallback (ST Portable,
  BST20 Portable: /setMargeAccount missing or wedged on those firmware
  builds).
* §8.3 — likely to fail (SA-5 on FW 9.x with the older shell generation;
  newer ST Portable builds with shrunk command set). The preflight +
  abort-on-first-rejection design ensures these fail cleanly, leaving no
  half-configured state.
* §8.4 — unverified targets that are expected to work but lack concrete
  captures (ST 30, ST 520, Wave Music System I/II).
* §8.5 — flags the apparent contradiction between S5's enumerated
  "valid roots" on ST 10 / FW 27.0.6 (which omits envswitch) and #221's
  successful envswitch use on the same firmware. Most plausible reading:
  S5 is a non-exhaustive probe, not a negative claim; preflight catches
  any real absence.
* §8.6 — maps every failure mode to its observable outcome and the unit
  test that exercises it.
* §8.7 — TL;DR.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 12:43:36 +02:00
Tobias GesellchenandClaude Opus 4.7 889470716b docs(analysis): add consolidated Telnet command reference
Synthesises every Bose SoundTouch port-17000 telnet command we have evidence
for, across six community sources: flarn2006's 2014 root-shell post,
Sam Hobbs's 2016 ST 10 setup-mode walkthrough, izndgroup's 2021 reissue,
sijeffrey's 2017 `bose` remote-control script, the 2026 r/bose telnet
probing thread (FW 27.0.6 ST 10), and our own #221 / #236 / soundcork#141
findings.

Groups the commands by family — `key` (front-panel button emulation, the
addition the Reddit thread brought in), `network` (WiFi profile management),
`sys` (verbs + the XML-tag-keyed `sys configuration` setter our migration
uses), `envswitch` (parallel persistence layer), `getpdo` (PDO read), `scm`,
`ws`, `swupdate`, and the historic shell-unlock commands. Each entry notes
firmware-era availability so implementations know whether to expect
"Command not found" on newer builds.

Records the four top-level command roots that S5 confirmed reachable on a
vanilla FW 27.x ST 10 (`key`, `net`, `sys`, `getpdo`), and flags that
`envswitch` works on other ST 20 / Wave models running the same firmware
family — a per-model variation the migration's preflight already handles.

Cross-linked from TELNET-MIGRATION-METHOD.md §2 and indexed in SUMMARY.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 12:43:36 +02:00
Tobias GesellchenandClaude Opus 4.7 12ca412ed2 feat(ui): wire up telnet migration method and account-id picker
* Migration dropdown gains a "Telnet (Port 17000) — no SSH required" option
  and drops the deprecated /etc/hosts entry from the visible choices. The
  hosts code path still exists in the backend for now; it is just no longer
  reachable through the UI.

* New `telnet-method-pane` shows a brief explanation, the HTTP-only
  limitation, and a hint that pairing may be required after migration.

* New `pair-account-pane` (initially hidden) renders three controls:
  - dropdown of accounts already in the local datastore (so a fresh device
    can be re-attached to an existing account),
  - 7-digit input field with HTML pattern validation,
  - a Generate button that picks a random non-colliding 7-digit ID.
  When :8090/info already exposes a margeAccountUUID the panel pre-fills
  it and offers to keep it; otherwise the device is treated as fresh.

* `pairAccount(deviceId)` POSTs to /setup/pair-account/{deviceId} with the
  selected ID and surfaces the breadcrumb (HTTP vs telnet fallback) in the
  status line.

* `reboot()` now passes ?method=telnet|ssh, derived from the migration
  method dropdown (telnet for telnet, ssh otherwise) so a device that was
  migrated without SSH access can also be rebooted without SSH access.

* After a successful telnet migration, `loadAccountIDSuggestions` runs
  automatically so the user is led straight into the pairing step.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 12:43:36 +02:00
Tobias GesellchenandClaude Opus 4.7 fb47807f70 feat(telnet): add port-17000 migration method and account pairing
Adds an SSH-free third migration path that drives the SoundTouch device's
diagnostic shell on TCP port 17000, plus a hardened replacement for the
fragile /setMargeAccount HTTP pairing call.

* `pkg/telnet` — new reusable, dependency-free client (sibling of `pkg/ssh`)
  with deadline-driven Dial / Probe / SendCommand / Close. Mock-server tests
  cover happy path, command-not-found, mid-stream close, and the wedged-device
  read-timeout scenario.

* `setup.MigrationMethodTelnet` — runs `sys configuration` for all four URLs
  plus the parallel `envswitch boseurls set` persistence layer that otherwise
  wins on reboot, then verifies with `getpdo CurrentSystemConfiguration`.
  Aborts on the first non-OK response so configuration is never half-written.
  No SSH backup or rw pre-flight (the path is SSH-free by design).

* `setup.PairAccount` — probes :8090/supportedURLs first, time-bounds
  POST /setMargeAccount aggressively (5s connect / 12s total) to avoid the
  hangs reported in #236, and falls back to `envswitch accountid set <id>`
  over telnet when the HTTP endpoint is missing or wedged. Returns a
  PairAccountResult breadcrumb so the UI can show which path actually
  succeeded.

* `setup.Reboot(deviceIP, method)` — gains a RebootMethod selector;
  RebootMethodSSH stays the default (preserving prior behavior),
  RebootMethodTelnet sends `sys reboot` over a fresh telnet session and
  treats the inevitable socket-close as success.

* New endpoints on `/setup`:
  - GET  /account-id-suggestions/{deviceId} — returns the device's current
    margeAccountUUID (from :8090/info) plus known account IDs from the
    datastore, so the UI can offer reuse.
  - POST /pair-account/{deviceId}?account_id=NNNNNNN — invokes PairAccount;
    the existing reboot endpoint reads ?method=ssh|telnet from the query
    string.

* Helpers `IsValidAccountID` (exactly 7 digits) and `GenerateAccountID`
  (crypto/rand, retries on collision against a known-IDs list).

Documentation in docs/analysis/TELNET-MIGRATION-METHOD.md is updated to match
the implementation: bare-URL convention for `soundtouch-service`, no automatic
`sys reboot` (user-initiated via the existing button with a method selector),
and the realised package layout. The /etc/hosts method is intentionally not
exposed in the new flow.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 12:43:36 +02:00
Tobias GesellchenandClaude Opus 4.7 d9894be7db docs(analysis): add Telnet (port 17000) migration method analysis
Documents the SSH-free third migration path on top of the device's diagnostic
shell, synthesised from #221, #236, scheilch/opencloudtouch#167,
deborahgu/soundcork#228, and deborahgu/soundcork#141.

Captures the URL configuration command sequence, the dual persistence layers
(`sys configuration` + `envswitch boseurls set`), the `/setMargeAccount`
failure modes (404, hang, post-migration 502 on power_on) with their bounded
fallbacks, port-17000 preflight requirements, and account-ID sourcing rules
(reuse from `:8090/info`, pick from `DataStore.ListAccounts`, or 7-digit
manual/randomized entry). Cross-links the new doc from
DEVICE-REDIRECT-METHODS.md, marks the `/etc/hosts` method as deprecated, and
fixes the existing margeServerUrl example to use our service's bare-URL
convention with an explicit note for soundcork's `/marge` sub-path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 12:43:36 +02:00
255dd9612a fix(datastore): normalize AUX source to canonical id/type after sync (#233)
The on-device Sources.xml carries only displayName + sourceKey for AUX,
no id and no type. The previous read path synthesized id="2000001+i" and
type="AUX" (echoed from SourceKey.Type), which the speaker rejects as
INVALID_SOURCE once it pulls config from soundtouch-service after
migration. Look up known providers in getDefaultSources and fill
canonical id/type/sourceproviderid; also drop the AUX carve-out in
marge's ensureSourceType so existing poisoned type="AUX" entries are
normalized to type="Audio" at the served-XML layer.

Relates to https://github.com/gesellix/Bose-SoundTouch/issues/195

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 14:16:19 +02:00
cf81fc033f ci: build all binaries on 7 platforms and publish PR preview Docker images (#237)
- Match release matrix: linux/amd64, linux/arm64, linux/armv7,
darwin/amd64, darwin/arm64, windows/amd64, freebsd/amd64; build cli,
service, web, backup
- Push Docker images on same-repo PRs with preview-pr-N /
preview-sha-<sha> tags so previews are unambiguous and tied to the PR
(forks build but skip push)
- Add a step summary listing each published image as docker pull
commands

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 14:12:27 +02:00
653652b57d deps(deps): bump the golang group with 6 updates (#231)
Bumps the golang group with 6 updates:

| Package | From | To |
| --- | --- | --- |
| [golang.org/x/crypto](https://github.com/golang/crypto) | `0.50.0` |
`0.51.0` |
| [golang.org/x/term](https://github.com/golang/term) | `0.42.0` |
`0.43.0` |
| [golang.org/x/image](https://github.com/golang/image) | `0.39.0` |
`0.40.0` |
| [golang.org/x/mod](https://github.com/golang/mod) | `0.35.0` |
`0.36.0` |
| [golang.org/x/sys](https://github.com/golang/sys) | `0.43.0` |
`0.44.0` |
| [golang.org/x/text](https://github.com/golang/text) | `0.36.0` |
`0.37.0` |

Updates `golang.org/x/crypto` from 0.50.0 to 0.51.0
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/golang/crypto/commit/b8a14a8d65f88c0c79c139171f1354c69a6cdb8a"><code>b8a14a8</code></a>
go.mod: update golang.org/x dependencies</li>
<li><a
href="https://github.com/golang/crypto/commit/9d9d5078968ddb8a279092c665a24e7de4178778"><code>9d9d507</code></a>
x509roots/fallback/bundle: fix bundle test with Go 1.27+</li>
<li><a
href="https://github.com/golang/crypto/commit/fd0b90d21f9ab4b5dd398e9526b570bfea86e370"><code>fd0b90d</code></a>
acme: include Problem in OrderError.Error</li>
<li><a
href="https://github.com/golang/crypto/commit/b9e53593a6073e6a786c49e9ad27956a9b77e54e"><code>b9e5359</code></a>
pbkdf2: turn into a wrapper for crypto/pbkdf2</li>
<li><a
href="https://github.com/golang/crypto/commit/cc0e4fc1d49127130b0d00612a2eeed2ab745d40"><code>cc0e4fc</code></a>
hkdf: forward Extract to the standard library</li>
<li><a
href="https://github.com/golang/crypto/commit/a8e9237a216b050e1b11e041863825104a6811db"><code>a8e9237</code></a>
x509roots/fallback: update bundle</li>
<li>See full diff in <a
href="https://github.com/golang/crypto/compare/v0.50.0...v0.51.0">compare
view</a></li>
</ul>
</details>
<br />

Updates `golang.org/x/term` from 0.42.0 to 0.43.0
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/golang/term/commit/3c3e4855f7d2eb06c3e48933554add9ec6b599b5"><code>3c3e485</code></a>
go.mod: update golang.org/x dependencies</li>
<li>See full diff in <a
href="https://github.com/golang/term/compare/v0.42.0...v0.43.0">compare
view</a></li>
</ul>
</details>
<br />

Updates `golang.org/x/image` from 0.39.0 to 0.40.0
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/golang/image/commit/542a3d9571611fd83b47afa41e76e7c6c7b3f991"><code>542a3d9</code></a>
go.mod: update golang.org/x dependencies</li>
<li><a
href="https://github.com/golang/image/commit/5cbe89a0e573c3c4e2cc193c1e24d8401bdf3e60"><code>5cbe89a</code></a>
tiff: reject 0-size images</li>
<li>See full diff in <a
href="https://github.com/golang/image/compare/v0.39.0...v0.40.0">compare
view</a></li>
</ul>
</details>
<br />

Updates `golang.org/x/mod` from 0.35.0 to 0.36.0
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/golang/mod/commit/643da9ba74f1165d8cae1505d453b3de3cf21b7b"><code>643da9b</code></a>
go.mod: update golang.org/x dependencies</li>
<li><a
href="https://github.com/golang/mod/commit/ccc3cdf529d1eee2a832437eb1b85240044d21cb"><code>ccc3cdf</code></a>
zip: include 'but content has correct sum' note in TestVCS</li>
<li><a
href="https://github.com/golang/mod/commit/ab3031803214705d2c9f1102318b083e7086a155"><code>ab30318</code></a>
zip: update zip hashes for new flate compression</li>
<li>See full diff in <a
href="https://github.com/golang/mod/compare/v0.35.0...v0.36.0">compare
view</a></li>
</ul>
</details>
<br />

Updates `golang.org/x/sys` from 0.43.0 to 0.44.0
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/golang/sys/commit/fb1facd76f95fa87c151018200ea5e4892ff115d"><code>fb1facd</code></a>
windows: avoid uint16 overflow in NewNTUnicodeString</li>
<li><a
href="https://github.com/golang/sys/commit/94ad893e1e59c1d079221324d38945d2aad8703f"><code>94ad893</code></a>
windows: add GetIfTable2Ex, GetIpInterface{Entry,Table},
GetUnicastIpAddressT...</li>
<li><a
href="https://github.com/golang/sys/commit/54fe89f8411576c06b345b341ca79a77d878a4ad"><code>54fe89f</code></a>
cpu: use IsProcessorFeaturePresent to calculate ARM64 on windows</li>
<li><a
href="https://github.com/golang/sys/commit/df7d5d7b60641d17d87e2b50911124cb65f954fd"><code>df7d5d7</code></a>
unix: automatically remove container created by mkall.sh</li>
<li><a
href="https://github.com/golang/sys/commit/68a4a8e945b22751c1a619261b1d755372a1d5f7"><code>68a4a8e</code></a>
unix: avoid nil pointer dereference in Utime</li>
<li><a
href="https://github.com/golang/sys/commit/690c91f6ecf3b3ef141ad2aedb1306a868b3a176"><code>690c91f</code></a>
unix: add CPUSetDynamic for systems with more than 1024 CPUs</li>
<li>See full diff in <a
href="https://github.com/golang/sys/compare/v0.43.0...v0.44.0">compare
view</a></li>
</ul>
</details>
<br />

Updates `golang.org/x/text` from 0.36.0 to 0.37.0
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/golang/text/commit/3ef517e623a4bfc08d6457f87d73afda7af7d8e1"><code>3ef517e</code></a>
go.mod: update golang.org/x dependencies</li>
<li>See full diff in <a
href="https://github.com/golang/text/compare/v0.36.0...v0.37.0">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
v0.72.0
2026-05-08 21:42:39 +02:00
Tobias GesellchenandGitHub 396b359c11 Update to Golang 1.26.3 (#230)
See https://go.dev/doc/devel/release#go1.26.3 and
https://groups.google.com/g/golang-dev/c/h6eZjndBMqQ
2026-05-08 21:21:52 +02:00
969bdf8704 feat(service): add --discovery-enabled CLI flag and treat 0 interval as disabled (#229)
Why: Operators need to control device discovery from the command line
without touching the persisted settings file, and a zero discovery
interval should be unambiguously off rather than running an
immediate-fire scan loop.

- Add --discovery-enabled BoolFlag (default true, env DISCOVERY_ENABLED)
and thread it through serviceConfig, applyPersistedSettings, and
createDefaultSettings so CLI/env can seed initial state and persisted
settings still take precedence on subsequent runs.
- HandleUpdateSettings now forces discoveryEnabled=false whenever the
resulting discoveryInterval is zero.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 21:18:15 +02:00
ac5e67d198 fix(client): default sourceAccount to "AUX" for AUX source selection (#228)
The speaker rejects /select with source="AUX" and an empty sourceAccount
as INVALID_SOURCE, so the audio path never reaches APAuxSrc. Default the
sourceAccount to "AUX" inside SelectSource and align ItemName to "AUX
IN" to match the device's own button-press payload.

Relates to https://github.com/gesellix/Bose-SoundTouch/issues/195

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 21:11:00 +02:00
ea4d8bacac revert(setup): revert OverrideSdkPrivateCfg.xml migration approach (#220)
The OverrideSdkPrivateCfg.xml override path introduced in #209 does not
work on SoundTouch 10 (and likely other models): the firmware ignores
the override file, leaving the device pointing at the original Bose
cloud URLs. Revert to editing SoundTouchSdkPrivateCfg.xml directly with
a .original backup, which is the approach known to work.

Relates to #214

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
v0.71.2
2026-05-07 08:43:34 +02:00
bcffbc7719 fix(setup): test override file existence before treating cat output as config (#215)
client.Run uses CombinedOutput, so when
`/mnt/nv/OverrideSdkPrivateCfg.xml` is absent (the default for devices
migrated with pre-0.71.0 code) the cat stderr is returned as the
override config and surfaced to the migration page UI as "Current Config
(on Speaker)". Gate the branch on `[ -f ... ]` first, mirroring the
legacy .original check.

Relates to #209
Relates to #214

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
v0.71.1
2026-05-06 12:06:31 +02:00
d14f4691a6 fix(amazon): use email and AMAZON type to match old Bose cloud format (#212)
Store the user's email address (not Amazon account ID) in
sourceKey.account and set source type to "AMAZON" so the speaker
firmware recognises Amazon Music sources the same way as the original
Bose cloud.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
v0.71.0
2026-05-06 08:23:04 +02:00
a1d0add5a1 feat(ui): add CA certificate download to Settings tab and Migration tab (#210)
Adds a "Download CA Certificate" button in the Settings tab
(system-level convenience for importing the cert into browsers, curl,
Python clients, etc.) and a "Download CA cert" link next to the existing
"Trust CA Now" button in the Migration tab. Both link to the existing
/setup/ca.crt endpoint.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04 20:51:40 +02:00
8b196a8260 fix(setup): write XML migration to OverrideSdkPrivateCfg.xml instead of editing original (#209)
Use /mnt/nv/OverrideSdkPrivateCfg.xml (the firmware's override path)
rather than editing /opt/Bose/etc/SoundTouchSdkPrivateCfg.xml directly.
A malformed override cannot cause a reboot loop because the device falls
back to the untouched original.

Revert now removes the override file; legacy .original backups are still
restored for devices migrated with older code. checkCurrentConfig reads
the override path first so IsMigrated detection works correctly with the
new approach.

Credit: Ueberbose team, discovered via [soundcork
documentation](https://github.com/deborahgu/soundcork#configuring-the-bose-speaker-to-use-the-soundcork-server).

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04 20:51:05 +02:00
Tobias GesellchenandClaude Sonnet 4.6 3be654da17 chore(build): add -trimpath to GitHub workflow build commands
Consistent with the Makefile which already applies -trimpath globally.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
v0.70.1
2026-05-03 21:55:14 +02:00
Tobias GesellchenandClaude Sonnet 4.6 3891c08dd1 docs(migration): add Docker Compose quickstart with .env config guidance
Adds a "Docker Compose (recommended for home servers and VMs)" section
to Step 1, pointing users to the existing docker-compose.yml and
.env.example. Clarifies the purpose of docker-compose.ci.yml (CI tests
only) and docker-compose.override.yml (local modifications, not in VCS).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-03 21:36:34 +02:00
Tobias GesellchenandClaude Sonnet 4.6 f6e733de6b fix(certmanager): use hostname as server cert CN instead of a random Bose domain
domains[0] was non-deterministic (Go map iteration) and could resolve to
any domain in the list including Bose-owned domains. Adds CommonName field
to CertificateManager, defaulting to "localhost", set to the device hostname
at startup. All Bose domains remain in the SAN where clients actually look.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
v0.70.0
2026-05-03 21:15:48 +02:00
Tobias GesellchenandClaude Sonnet 4.6 84585b8034 perf(service): move TLS cert generation off the startup path
On constrained hardware (e.g. ARMv7), RSA key generation can block
startup for minutes. HTTP now starts immediately; HTTPS is brought up
in a background goroutine once cert generation completes. A log message
informs the user that HTTPS will be available shortly after startup.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-03 21:11:41 +02:00
Tobias GesellchenandClaude Sonnet 4.6 ac44fdace6 perf(certmanager): reduce CA key size from RSA-4096 to RSA-2048
RSA-4096 CA generation blocks service startup for minutes on slow ARM
hardware. The CA key is only used to sign server certs, never in TLS
handshakes, so 2048 bits provides sufficient security for a local CA
while being ~4-8x faster to generate.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-03 21:11:41 +02:00
Tobias GesellchenandClaude Sonnet 4.6 06042f8728 chore(build): add ARMv7 target and apply trimpath/-s/-w flags globally
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>
2026-05-03 21:11:41 +02:00
ca19bb32f7 feat(setup): harden hostname resolution before migration (#204)
- resolveIP now returns (string, error): error when result did not come
  from the device's own SSH ping (service-side fallback or total
failure)
- migrateViaResolvConf and parseTargetURLAndResolveIP abort on error,
  preventing a bad IP from being written to the device
- GetMigrationSummary captures the error in ResolveIPError and falls
back
  to the hostname for the preview display; XML migration is unaffected
- Web UI shows a warning box with the error and a docs link when
resolution
  is uncertain; migrate button stays enabled for the XML method
- Add hostname resolution troubleshooting section to TROUBLESHOOTING.md

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-03 20:00:24 +02:00
c931510384 feat(setup): add 'original' option and harden backup before migration (#202)
- Rename proxy option values: 'upstream' → 'proxied', 'official' →
'original'
- Add 'original' option to preserve current device URL as-is per field
- Drop proxyURL guard in applyProxyOptions so 'original' works without a
proxy
- Abort migration if on-device backup cannot be created (was
warning-only)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
v0.69.0
2026-05-03 18:32:27 +02:00
3a1a93333e chore(compose): slim down base config, move test concerns to ci overlay (#203)
- Move spotify-mock and amazon-mock services to docker-compose.ci.yml
- Move soundtouch-test-net network definition to docker-compose.ci.yml
- Pin image version via SOUNDTOUCH_VERSION env var (defaults to
'latest')
- Document SOUNDTOUCH_VERSION in .env.example

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-03 18:30:25 +02:00
Tobias GesellchenandClaude Sonnet 4.6 fb0465bf5f docs: add UI screenshots to migration guide and device setup (#159)
Copy 5 screenshots from _/screenshots/ into docs/images/ and wire them
into the migration guide (Settings, Devices, Sync, Migration tabs) and
the device initial setup guide (speaker AP mode Wi-Fi page). Replace the
images README wishlist with a table of what is actually present.

Also correct the AP mode IP address (192.0.2.1, verified on ST10) and
update the Settings step to match actual UI labels (Target Domain, DNS
Bind Address).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
v0.68.0
2026-05-02 22:58:29 +02:00
Tobias GesellchenandClaude Sonnet 4.6 624da2c2b8 docs: rewrite migration guide, fix broken images (#159)
Replace the placeholder MIGRATION-GUIDE.md (which had a "planned to be"
header, a nonexistent install.sh reference, and 9 broken screenshot links)
with a complete, image-free step-by-step walkthrough covering all 6 steps:
install, configure URL, enable SSH via USB stick, discover/sync, migrate
(XML or DNS/DHCP), and verify.

Add the Migration Guide to the README docs section and link to it from
the Survival Guide.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-02 22:58:29 +02:00
Tobias GesellchenandClaude Sonnet 4.6 6b90d2c994 docs: rewrite README and survival guide for post-shutdown user journey
Rewrite README.md to be concise and tool-focused (no code snippets),
clearly presenting all five tools and their use cases. Expand the
soundtouch-service section to cover both user scenarios and redirect
method trade-offs.

Rewrite SURVIVAL-GUIDE.md around the same two scenarios with step-by-step
instructions. Remove deprecated hosts-file method from all user-facing
docs; update MIGRATION-SAFETY.md, HTTPS-SETUP.md, and SOUNDTOUCH-SERVICE.md
to reflect only the two supported methods (XML redirect and DNS/DHCP).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-02 22:58:29 +02:00
16ab9dbba1 feat(alexa): stub POST /alexa/certificate with 501 and add voice.api.bose.io to DNS (#200)
Registers HandleAlexaCertificate on POST /alexa/certificate. The handler
logs the device MAC from the request body and returns 501 Not
Implemented with a JSON error explaining that AWS IoT integration is
required to provision Alexa device certificates.

Adds voice.api.bose.io to both /etc/hosts domain lists in setup.go (DNS
intercept was already covered by the bose.io wildcard entry in dns.go).

Relates to https://github.com/gesellix/Bose-SoundTouch/discussions/84

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-02 21:37:30 +02:00
e99c04888c feat: implement missing endpoints and serve static resources from downloads/media hosts (#199)
Endpoints:

- POST /streaming/music/musicprovider/{id}/trial/is_eligible (reuses
is_eligible handler)
- POST /bmx/tunein/v1/favorite/{stationID} with datastore persistence
(SaveTuneInFavorite)
- DELETE /bmx/tunein/v1/favorite/{stationID} (DeleteTuneInFavorite)
- POST /bmx/core02/svc-bmx-adapter-orion/prod/orion/token (anonymous
Orion token)
- GET /bmx-icons/* serving embedded static/media assets (media.bose.io)
- GET /ced/* serving embedded firmware index, release notes, and 10
app-help XMLs (downloads.bose.com)

Add media.bose.io and downloads.bose.com to DNS redirect lists (setup.go
both domain slices, dns.go shouldIntercept list, main.go getDomains
map). Document implemented endpoints in
tests/interactions_20260502_missing_external.md; mark rows 0246–0247 as
self/☑ in the interactions table.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-02 21:22:36 +02:00
Tobias GesellchenandClaude Sonnet 4.6 0b2e03820b docs: add CAPTURE-MIGRATION-TRAFFIC.md to SUMMARY.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-02 18:08:39 +02:00
Tobias GesellchenandClaude Sonnet 4.6 faacba5d91 docs(mitm): add .mitm to .http conversion script and document workflow
- Add scripts/convert_mitm_script.py (mitmproxy addon, converts flows to .http files)
- Gitignore scripts/android/mitm/ (converted output, derived from captures)
- Document conversion step in CAPTURE-DEVICE-PAIRING.md Phase 5
- Document conversion step in CAPTURE-MIGRATION-TRAFFIC.md Step 6.2

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-02 18:08:39 +02:00
Tobias GesellchenandClaude Sonnet 4.6 aecd41bdfa docs(migration): add migration traffic capture runbook with session trace
- Add CAPTURE-MIGRATION-TRAFFIC.md with step-by-step migration runbook
- Include session trace from first interactive ST10 migration run
- Genericize example IP addresses in BOSE-APP-ADB-Emulator.md and CAPTURE-DEVICE-PAIRING.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-02 18:08:39 +02:00
Tobias GesellchenandClaude Sonnet 4.6 cf7f3431f6 feat(android): scripted MITM setup with emulator snapshot and Frida SSL unpinning
- Add scripts/android/ with setup-mitm-avd.sh (one-time) and start-mitm-session.sh (per-session)
- Move frida Dockerfile to scripts/android/; extract frida-server + SSL scripts via Docker
- Use native macOS mitmproxy app for capture (Docker NAT blocks emulator traffic)
- Add native-connect-hook.js to Frida launch — required for Bose app's native networking
- Document verified AP mode Wi-Fi provisioning endpoint (POST :8090/addWirelessProfile)
- Correct factory reset sequences for ST10/ST20 from official Bose guides
- Remove old scripts/setup-mitm-avd.sh and scripts/start-mitm-session.sh (moved to android/)
- Add session trace with lessons learned from first interactive capture run

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-02 18:08:39 +02:00
86825c44af feat(backup): add soundtouch-backup tool for cloud and local speaker backup (#197)
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>
v0.67.0
2026-05-02 14:00:23 +02:00
Tobias GesellchenandClaude Sonnet 4.6 ee44526d25 docs(amazon): confirm amazon_music:access scope requires device client ID
Attempting to request amazon_music:access with a standard application
client ID (amzn1.application-oa2-client.*) returns HTTP 400
lwa-invalid-parameter-bad-scope from the LWA authorization endpoint.
The scope is gated to Amazon Music partner device client IDs.

Revert scope to "profile" (working state) and document the confirmed
blocker with the exact error. Path forward: Amazon Music partner
registration for a device client ID; one-line change to AmazonScopes
when available.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
v0.66.0
2026-04-29 20:30:06 +02:00