mirror of
https://github.com/gesellix/Bose-SoundTouch.git
synced 2026-08-18 08:36:13 +00:00
docs(telnet): correct envswitch/getpdo claims from #515/#471 measurements
Community hardware testing (bitranox, JRpersonal) on 2026-08-09 retracted the earlier "inter-command delay is necessary" theory and established that envswitch boseurls set commits the whole runtime layer (not just its two arguments), has no read form, and doesn't ack with "OK". Corrects TELNET-MIGRATION-METHOD.md and TELNET-COMMAND-REFERENCE.md accordingly, retracts the stale "confirmed necessary" command-delay claim in enable_ssh.go/cmd_setup.go, and lowers DefaultTelnetCommandDelay 5s -> 3s as a smaller hedge now that the delay itself is known not to be the mechanism. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
27da179082
commit
d36cd75d26
@@ -661,9 +661,8 @@ func setupEnableSSHCmd() *cli.Command {
|
||||
&cli.DurationFlag{
|
||||
Name: "command-delay",
|
||||
Value: setup.DefaultTelnetCommandDelay,
|
||||
Usage: "Only affects --full-config: pause between each of its 6 steps (5 commands + reboot). Confirmed necessary on a real device (#515) — " +
|
||||
"the same commands sent back-to-back left sshd down after reboot, but succeeded sent one at a time with ~7s gaps. Raise this if the " +
|
||||
"default doesn't work on your device; 0 sends everything back-to-back (the old behavior)",
|
||||
Usage: "Only affects --full-config: pause between each of its 6 steps (5 commands + reboot). " +
|
||||
"Raise this if the default doesn't work on your device; 0 sends everything back-to-back",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "no-auto-pair",
|
||||
|
||||
@@ -150,11 +150,15 @@ Each `sys configuration` setter is reported by users to return `OK` on success.
|
||||
|
||||
`envswitch` writes to a separate, lower-level persistence store that **wins on next reboot** if the corresponding `sys configuration` value differs. So our migration writes both — see TELNET-MIGRATION-METHOD.md §2.1.
|
||||
|
||||
| Command | Purpose | Source |
|
||||
|---------------------------------------------------|-----------------------------------------------------------------------------------------------|---------|
|
||||
| `envswitch boseurls set <margeUrl> <swUpdateUrl>` | Persist the marge and update URLs. **Two arguments**, in that order. | S6 |
|
||||
| `envswitch accountid set <numeric-id>` | Equivalent to the HTTP `/setMargeAccount` POST. Used as fallback in our `PairAccount` helper. | S6 |
|
||||
| `envswitch accountid get` | Plausible by symmetry but **not yet confirmed** across firmwares; we probe it best-effort. | (probe) |
|
||||
**It's a commit point, not just a two-field setter.** `envswitch boseurls set` persists whatever is currently in the runtime layer at the moment it runs — not only its own two arguments. Confirmed on five variants (`lisa`, `mojo`, `spotty`, `ginger`, `taigan`; [#515 comment 5231931569](https://github.com/gesellix/Bose-SoundTouch/issues/515#issuecomment-5231931569)): a `sys configuration` write survives a reboot **if and only if** an `envswitch boseurls set` runs after it. The same command sequence in reverse order silently loses the later `sys configuration` values on reboot — every command still answers, nothing looks wrong until the reboot. This is why our migration and SSH-enable sequences always issue all four `sys configuration` writes first and `envswitch boseurls set` last (see `telnetURLs.Commands()` / `EnableSSHViaTelnetFullConfig`).
|
||||
|
||||
**It does not acknowledge with `OK`.** Unlike `sys configuration` (which does), `envswitch boseurls set` responds with a different string (observed: `Setting Bose Server URLs to <a> and <b> ->`, no `OK` substring). An implementation that waits for the literal token `OK` will hit its own timeout on this exact command. Our `pkg/telnet.Client.SendCommand` doesn't string-match at all — it reads until the connection goes idle — so this only matters if you're hand-typing the sequence or reimplementing the client elsewhere.
|
||||
|
||||
| Command | Purpose | Source |
|
||||
|-------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------|
|
||||
| `envswitch boseurls set <margeUrl> <swUpdateUrl>` | Persist the marge and update URLs, committing the runtime layer as it stands (see above). **Two arguments**, in that order. | S6 |
|
||||
| `envswitch accountid set <numeric-id>` | Equivalent to the HTTP `/setMargeAccount` POST. Used as fallback in our `PairAccount` helper. | S6 |
|
||||
| `envswitch accountid get`, bare `envswitch`, `envswitch boseurls` | **Confirmed unsupported** — all answer `Invalid Command Option` on `lisa`/`mojo`/`spotty` ([#515 comment 5231931569](https://github.com/gesellix/Bose-SoundTouch/issues/515#issuecomment-5231931569)). `envswitch` has no read form on any variant tested; the persisted layer can only be written, then observed indirectly after a reboot (e.g. via `getpdo`, which then reflects the *new* value). | (probe) |
|
||||
|
||||
---
|
||||
|
||||
@@ -166,6 +170,8 @@ Each `sys configuration` setter is reported by users to return `OK` on success.
|
||||
|-------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------|
|
||||
| `getpdo CurrentSystemConfiguration` | Echoes the resolved URL set, including margeServerUrl/bmxRegistryUrl/statsServerUrl/swUpdateUrl. We grep our targetURL out of this to confirm a successful migration. | S6 |
|
||||
|
||||
**The two layers are inverted in `getpdo` visibility around a reboot** ([#515 comment 5231931569](https://github.com/gesellix/Bose-SoundTouch/issues/515#issuecomment-5231931569)): *before* a reboot, `getpdo` shows the runtime (`sys configuration`) values immediately, while an `envswitch`-written value isn't visible yet; *after* a reboot, the `sys configuration` values are gone and the `envswitch`-persisted values are what's now applied. So a `getpdo` check run before rebooting confirms the writes were accepted, but it is **not** evidence the configuration will survive the reboot — only the `envswitch` write (in the right order, see above) determines that. This is why our own migration verification (`migrateViaTelnet`) checks `getpdo` before reboot only to confirm the runtime layer accepted the values, and never claims persistence from it.
|
||||
|
||||
---
|
||||
|
||||
## The `scm` family — service control
|
||||
@@ -295,6 +301,12 @@ sys reboot
|
||||
|
||||
**Which devices need `--full-config`:** observed on the **SoundTouch Portable (Series I, model 412540, FW `27.0.6.46330.5043500`)** (#515) and on some **CineMate 520** units where the default path leaves `sshd` down. The structural differences from the default path that appear to matter are (1) the injection riding `sys configuration margeServerUrl`, not just `envswitch`, and (2) the explicit `sys reboot`. The `--full-config` automation is **candidate behaviour awaiting reporter confirmation** — the manual sequence is confirmed working on the ST Portable, but the flag that automates it has not yet been re-confirmed on hardware. Not every device responds even to the manual sequence (some ST10 and CineMate 520 units never start `sshd` over telnet at all and need the serial / U-Boot route).
|
||||
|
||||
**On the `--command-delay` between steps:** originally added because a reporter's back-to-back run left `sshd` down while a ~7s-gapped run succeeded ([#515 comment 5228449448](https://github.com/gesellix/Bose-SoundTouch/issues/515#issuecomment-5228449448)). That theory was **retracted** by the same reporter after a controlled A/B across three variants showed identical outcomes at 0s and 5s gaps ([comment 5231931569](https://github.com/gesellix/Bose-SoundTouch/issues/515#issuecomment-5231931569)) — the delay itself doesn't appear to matter. The default is kept small and non-zero (`setup.DefaultTelnetCommandDelay`) as a low-cost hedge for untested variants, not because the delay is known to help.
|
||||
|
||||
**The account-pairing precondition** (raised by `Henri-be`, [#515 comment 5230785528](https://github.com/gesellix/Bose-SoundTouch/issues/515#issuecomment-5230785528), tracing back to [#471 comment 4903016740](https://github.com/gesellix/Bose-SoundTouch/issues/471#issuecomment-4903016740); confirmed empirically by `bitranox`, [#515 comment 5232241580](https://github.com/gesellix/Bose-SoundTouch/issues/515#issuecomment-5232241580)): a genuinely unpaired (factory-reset, empty `margeAccountUUID`) device does not poll `margeServerUrl` **at all** — confirmed by pointing a reset device's marge URL at a listener and observing zero requests over 10+ minutes. The SSH-enable injection has no read cycle to fire on until the device is paired. `enable-ssh` handles this automatically by default (`EnsureMargeAccountPaired`, `--no-auto-pair` to skip).
|
||||
|
||||
**Factory reset does not remove root access, if it was ever persisted.** Confirmed on a genuinely factory-reset `spotty` ([#471 comment 5232232575](https://github.com/gesellix/Bose-SoundTouch/issues/471#issuecomment-5232232575)): after the reset, `margeAccountUUID` was empty, all four service URLs were back to `streaming.bose.com`, and presets were gone — but `/etc/remote_services` and `/mnt/nv/remote_services` **survived**, and SSH (:22) and telnet (:17000) stayed open. So once a device has been through `setup enable-ssh` with persistence (`EnsureRemoteServices`, the default), a later factory reset only wipes configuration, not root access — recovery is re-migrate + re-pair + rename + restore presets, with **no USB stick and no re-running the injection**.
|
||||
|
||||
---
|
||||
|
||||
## Out of scope here, but worth recording
|
||||
|
||||
@@ -82,6 +82,16 @@ Three important details from the discussion:
|
||||
silently restored on reboot — i.e. there is a parallel "envswitch" persistence
|
||||
layer that wins on next boot if you don't also write to it. **We must always
|
||||
issue both.**
|
||||
|
||||
A later, more precise measurement ([#515 comment 5231931569](https://github.com/gesellix/Bose-SoundTouch/issues/515#issuecomment-5231931569), confirmed on
|
||||
five variants: `lisa`/`mojo`/`spotty`/`ginger`/`taigan`) explains *why*
|
||||
order matters: `envswitch boseurls set` is not just a two-field setter, it
|
||||
**commits whatever is currently in the runtime layer at the moment it
|
||||
runs**. A `sys configuration` write only survives a reboot if `envswitch
|
||||
boseurls set` runs **after** it; the same commands in reverse order lose
|
||||
the `sys configuration` values silently on reboot, with every individual
|
||||
command still answering normally. This is why the sequence above is
|
||||
ordered all-four-`sys-configuration`-then-`envswitch`, never the reverse.
|
||||
2. **margeServerUrl path is bare for `soundtouch-service`.** We mount the marge
|
||||
endpoints at the **root** of port 8000, matching what the existing XML
|
||||
migration writes (`Manager.migrateViaXML` in `pkg/service/setup/setup.go`
|
||||
@@ -91,8 +101,15 @@ Three important details from the discussion:
|
||||
routes marge under that sub-path. **For our service: bare URL. For users
|
||||
redirecting to soundcork: append `/marge`** to both `margeServerUrl` and
|
||||
the first argument of `envswitch boseurls set`.
|
||||
3. **Each command must be sent one at a time, waiting for the device's `OK`
|
||||
response** before sending the next one (`foob61451`'s explicit warning).
|
||||
3. **Each command must be sent one at a time, waiting for the device's
|
||||
response** before sending the next one (`foob61451`'s original warning).
|
||||
Note the exception: `sys configuration` commands ack with `OK`, but
|
||||
`envswitch boseurls set` does **not** — it acks with a different string
|
||||
entirely (observed: `Setting Bose Server URLs to <a> and <b> ->`, no `OK`
|
||||
substring; [#515 comment 5231931569](https://github.com/gesellix/Bose-SoundTouch/issues/515#issuecomment-5231931569)). An implementation that waits for the
|
||||
literal token `OK` will time out on exactly that command. Wait for the
|
||||
shell's prompt (or, as our own `pkg/telnet.Client` does, for the
|
||||
connection to go idle) rather than string-matching `OK`.
|
||||
|
||||
### 2.2 Account pairing fallback
|
||||
|
||||
@@ -145,6 +162,22 @@ The values are not validated by the local service, so any numeric `accountId`
|
||||
will work — soundcork's runbook (#228) literally calls the token
|
||||
`soundcorkdoesntcare` to make the point.
|
||||
|
||||
> **Booby trap, confirmed on hardware: never send an empty or truncated body
|
||||
> to this endpoint.** On one firmware, a `POST /setMargeAccount` with an
|
||||
> empty body returned `HTTP 200` and cleared `margeAccountUUID`, un-pairing
|
||||
> an already-working speaker
|
||||
> ([#471 comment 5231977172](https://github.com/gesellix/Bose-SoundTouch/issues/471#issuecomment-5231977172)).
|
||||
> A later retry on the same device instead returned `400` and changed
|
||||
> nothing, so the same reporter corrected the finding to
|
||||
> **state-dependent, not a reliable rule you can rely on either way**
|
||||
> ([#471 comment 5232232575](https://github.com/gesellix/Bose-SoundTouch/issues/471#issuecomment-5232232575)). A `400` is not proof the
|
||||
> endpoint rejected a bad request (a booting device also answers a bare
|
||||
> `400` with an empty body before its services are ready, per
|
||||
> `JRpersonal`), and a `200` is not proof it did what you wanted. Practical
|
||||
> takeaway: our own `postSetMargeAccount` always sends a well-formed XML
|
||||
> body, so this doesn't affect the CLI/service — but don't probe this
|
||||
> endpoint by hand against a speaker that currently works.
|
||||
|
||||
### 3.2 Why it's broken in practice
|
||||
|
||||
There are **three independent failure modes** observed:
|
||||
@@ -189,10 +222,16 @@ control:
|
||||
recipes).
|
||||
3. **Randomize.** A "Generate" button that picks a 7-digit number and
|
||||
re-rolls if it collides with an existing account in the local datastore.
|
||||
- **Telnet read-back (best-effort).** `envswitch accountid get` is plausible by
|
||||
symmetry with `envswitch accountid set` (#221) but is not yet confirmed
|
||||
across firmwares. We will probe it during preflight; if it returns a value
|
||||
we cross-check it against `:8090/info` and warn on mismatch.
|
||||
- **Telnet read-back: confirmed unsupported.** `envswitch accountid get` was
|
||||
originally listed as "plausible by symmetry with `envswitch accountid set`
|
||||
(#221), not yet confirmed." It's now confirmed the other way: on
|
||||
`lisa`/`mojo`/`spotty`, `envswitch` has **no read form at all** — both bare
|
||||
`envswitch` and `envswitch boseurls` answer `Invalid Command Option`
|
||||
([#515 comment 5231931569](https://github.com/gesellix/Bose-SoundTouch/issues/515#issuecomment-5231931569)).
|
||||
The persisted layer can only be written, then
|
||||
observed indirectly after a reboot (e.g. via `getpdo`, mindful of the
|
||||
layer-visibility caveat in
|
||||
[TELNET-COMMAND-REFERENCE.md](TELNET-COMMAND-REFERENCE.md)).
|
||||
|
||||
This means the user is never *forced* to invent a number — the common path is
|
||||
"the device already has an ID, reuse it" — and the manual/randomize controls
|
||||
|
||||
@@ -37,16 +37,19 @@ func (m *Manager) ResetBoseURLs(deviceIP, serviceURL string) (string, error) {
|
||||
}
|
||||
|
||||
// DefaultTelnetCommandDelay is the pause between successive commands in
|
||||
// EnableSSHViaTelnetFullConfig's sequence. Confirmed necessary on a real
|
||||
// device (#515, issue comment 5228449448): the same six commands sent
|
||||
// back-to-back left sshd down after reboot, but succeeded when sent one at a
|
||||
// time with ~7s gaps — sending fast enough may not let the device fully
|
||||
// process one command before the next arrives. Settled on 5s as the default
|
||||
// (issue comment 5230881285): a bit more headroom than the original 3s
|
||||
// guess, still well under the ~7s the reporter used without having tried to
|
||||
// find the true minimum; the caller exposes it as a flag so a specific
|
||||
// device can be tuned without a code change.
|
||||
const DefaultTelnetCommandDelay = 5 * time.Second
|
||||
// EnableSSHViaTelnetFullConfig's sequence. Originally set based on #515
|
||||
// comment 5228449448 (same six commands, back-to-back left sshd down after
|
||||
// reboot but succeeded with ~7s gaps). That inter-command-delay theory was
|
||||
// RETRACTED by the same reporter after a controlled A/B on three variants
|
||||
// (issue comment 5231931569): back-to-back and 5s-gapped runs produced
|
||||
// identical results (all writes applied, confirmed via verified reboots),
|
||||
// so the delay itself does not appear to be the mechanism — the likely real
|
||||
// gate was the account-pairing precondition (see EnsureMargeAccountPaired),
|
||||
// fixed independently. The flag is kept at a small non-zero default (3s)
|
||||
// as a low-cost hedge for firmware variants nobody has A/B-tested yet
|
||||
// (only lisa/mojo/spotty/ginger/taigan are confirmed); 0 sends everything
|
||||
// back-to-back.
|
||||
const DefaultTelnetCommandDelay = 3 * time.Second
|
||||
|
||||
// EnableSSHViaTelnetFullConfig is the #515 variant of EnableSSHViaTelnet for
|
||||
// devices where the single-envswitch injection is accepted and persisted but
|
||||
|
||||
Reference in New Issue
Block a user