mirror of
https://github.com/gesellix/Bose-SoundTouch.git
synced 2026-08-20 09:36:16 +00:00
deprecate(service): mark active telnet round-trip probe for removal
The swUpdate daemon caches its target URL at boot and ignores live
`sys configuration` writes, so the active flip in
RunTelnetRoundTripProbe never reaches the running daemon — confirmed
empirically on a fully-migrated speaker (FW 27.0.6) where both the
runtime and persistence layers were flipped and the device still
dialed the previously-cached `/updates/soundtouch` URL plus
DNS-intercepted `/streaming/software/update/account/*`. The probe URL
was never observed.
Marks DEPRECATED:
- pkg/service/setup/telnet_probe.go: ProbeRegistrar,
TelnetProbeResult, generateProbeToken, RunTelnetRoundTripProbe.
- pkg/service/handlers/handlers_telnet_probe.go: HandleTelnetProbe,
HandleProbeInbound, telnetProbeTimeout, telnetProbeResponse.
- pkg/service/handlers/probe_registry.go: probeRegistry.
- Server.probes field.
- /probe/{token}[/*] and /setup/telnet-probe/{deviceId} routes.
Adds §9.8 to docs/analysis/TELNET-MIGRATION-METHOD.md documenting the
daemon-cache finding, the diagnostic that confirmed it, the passive
observer replacement, the pre-flight branch on migration state, and
the canonical telnet flow (Apply config → reboot → passive
validation). All code symbols remain in place this commit; the
follow-up commit performs the hard delete.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
9a7646bf58
commit
f0de4864b6
@@ -872,6 +872,11 @@ func setupRouter(server *handlers.Server) *chi.Mux {
|
||||
|
||||
r.Get("/", server.HandleRoot)
|
||||
r.Get("/health", server.HandleHealth)
|
||||
// DEPRECATED: /probe/{token}[/*] backs the deprecated round-trip
|
||||
// probe (RunTelnetRoundTripProbe). Removed in a follow-up commit
|
||||
// alongside the rest of the active-probe code path; see
|
||||
// docs/analysis/TELNET-MIGRATION-METHOD.md §9.8.
|
||||
//
|
||||
// Telnet round-trip probe inbound. The orchestrator temporarily
|
||||
// sets the speaker's swUpdateUrl to /probe/{token}; the speaker
|
||||
// then fans out a request that we observe here. Catch-all suffix
|
||||
@@ -1103,6 +1108,7 @@ func setupRouter(server *handlers.Server) *chi.Mux {
|
||||
r.Post("/test-connection/{deviceId}", server.HandleTestConnection)
|
||||
r.Post("/test-hosts/{deviceId}", server.HandleTestHostsRedirection)
|
||||
r.Post("/test-dns/{deviceId}", server.HandleTestDNSRedirection)
|
||||
// DEPRECATED: see §9.8; removed in the follow-up commit.
|
||||
r.Post("/telnet-probe/{deviceId}", server.HandleTelnetProbe)
|
||||
r.Get("/ca.crt", server.HandleGetCACert)
|
||||
r.Get("/proxy-settings", server.HandleGetProxySettings)
|
||||
|
||||
@@ -625,4 +625,99 @@ configured `swUpdateUrl`.
|
||||
implemented.
|
||||
- Running the round-trip probe on SSH-capable speakers too (as
|
||||
additional validation alongside the curl-from-device HTTPS test),
|
||||
not just as the SSH-less fallback it is today.
|
||||
not just as the SSH-less fallback it is today. **Subsumed by §9.8
|
||||
— the round-trip probe is being removed; the passive observer is
|
||||
transport-agnostic and replaces it for migrated speakers.**
|
||||
|
||||
### 9.8 The swUpdate daemon-cache finding and removal of §9.5
|
||||
|
||||
The §9.5 round-trip probe was retired after empirical testing on a
|
||||
fully-migrated speaker (FW 27.0.6) revealed that the `swUpdate`
|
||||
daemon **caches its target URL at boot and ignores live config
|
||||
writes**. The diagnostic sequence:
|
||||
|
||||
1. Manual telnet flip of both layers — `sys configuration swUpdateUrl
|
||||
<probe-url>` (runtime) **and** `envswitch boseurls set <marge>
|
||||
<probe-url>` (persistence). `getpdo CurrentSystemConfiguration`
|
||||
confirmed both writes stuck.
|
||||
2. HTTP GET `:8090/swUpdateCheck` to trigger fan-out.
|
||||
3. Service access log showed the device outbound landed on
|
||||
`/updates/soundtouch` (the **previous** `swUpdateUrl` value, current
|
||||
at the last daemon boot) and `/streaming/software/update/account/<id>`
|
||||
(a separate Bose URL the daemon hits, routed to this service by DNS
|
||||
interception). The probe URL was never dialed.
|
||||
|
||||
This falsifies the original NEXT.md hypothesis that the persistence
|
||||
layer would override the runtime layer for the daemon's fan-out, and
|
||||
points instead at daemon-level URL caching. Two consequences:
|
||||
|
||||
- **The §9.5 probe cannot work on migrated speakers without a
|
||||
reboot.** The cached URL is set when the daemon starts; flipping
|
||||
config after that point has no effect on what the daemon dials.
|
||||
- **The §9.5 probe likely cannot work on unmigrated speakers
|
||||
either**, for the same reason — the daemon caches whatever URL it
|
||||
read at startup, which on an unmigrated speaker is the Bose cloud
|
||||
URL. We have no service running with the probe URL registered on
|
||||
unmigrated speakers, so the original "it worked in testing" claim
|
||||
has no empirical basis; it likely failed silently because nothing
|
||||
was watching.
|
||||
|
||||
The honest replacement is a **passive observer** (see
|
||||
`pkg/service/setup/peer_probe.go`):
|
||||
|
||||
1. Register the device IP with an in-process observer
|
||||
(`handlers.peerObserver`, wired via `PeerObserverMiddleware`).
|
||||
2. Nudge `:8090/swUpdateCheck` to make the daemon fan out *something*
|
||||
sooner than its ~5min timer.
|
||||
3. Wait up to 30s for any inbound from that IP. On a migrated
|
||||
speaker, DNS interception means the daemon's outbounds (update
|
||||
fan-out, marge polls, BMX registry calls) all funnel through this
|
||||
service regardless of which URL the daemon resolved internally —
|
||||
so reachability reduces to *"did the device dial us at all."*
|
||||
|
||||
Endpoint: `POST /setup/peer-probe/{deviceId}`. No device-state
|
||||
mutation; safe to re-run. Returns `{ok, result: {reached,
|
||||
observed_path, elapsed_ms}, error}` with the same UI keying as the
|
||||
old probe (`result.reached`).
|
||||
|
||||
#### 9.8.1 The pre-flight panel branch
|
||||
|
||||
The web UI's pre-flight orchestrator (`runApplyPreflight` in
|
||||
`script.js`) branches on `summary.is_migrated`:
|
||||
|
||||
| Migration state | Reachability row |
|
||||
|-----------------------------------|------------------------------------------------------------------------------------------------------------------------------|
|
||||
| Migrated (`is_migrated=true`) | "Reachability check (passive observer)" — calls `POST /setup/peer-probe/{deviceId}`. |
|
||||
| Not migrated (incl. partial) | Skip row "Round-trip validation runs after Apply + reboot" with the rationale "daemon caches swUpdateUrl at boot". |
|
||||
|
||||
Per-axis booleans (`xml_migrated`, `hosts_migrated`, `resolv_migrated`,
|
||||
`telnet_migrated`) remain visible in the State card, so the user can
|
||||
see which parts of the migration are already in place even when the
|
||||
overall flag is false. The skip row does not attempt the active probe
|
||||
on unmigrated speakers — the canonical telnet flow is:
|
||||
|
||||
```
|
||||
Apply telnet config → user-initiated reboot → re-run pre-flight on
|
||||
the now-migrated speaker → passive observer confirms fan-out.
|
||||
```
|
||||
|
||||
#### 9.8.2 Removal trail
|
||||
|
||||
Removed (or scheduled for removal in a follow-up commit) at the time
|
||||
of §9.8 landing:
|
||||
|
||||
- `pkg/service/setup/telnet_probe.go` — `RunTelnetRoundTripProbe`,
|
||||
`ProbeRegistrar`, `TelnetProbeResult`, `generateProbeToken`.
|
||||
- `pkg/service/handlers/handlers_telnet_probe.go` — `HandleTelnetProbe`,
|
||||
`HandleProbeInbound`, `telnetProbeTimeout`, `telnetProbeResponse`.
|
||||
- `pkg/service/handlers/probe_registry.go` — `probeRegistry` + tests.
|
||||
- `Server.probes` field.
|
||||
- Routes `/probe/{token}`, `/probe/{token}/*`, `/setup/telnet-probe/{deviceId}`.
|
||||
- The `target_url` query-param plumbing on the deprecated endpoint.
|
||||
- `script.js` — `checkTelnetRoundTrip` (orchestrator call site removed
|
||||
in the commit that added the branch; function itself removed later).
|
||||
|
||||
`isCommandNotFound` and `parseGetpdoConfig` stay — they are also used
|
||||
by the migration writer (`telnet_migration.go`), preflight reader
|
||||
(`telnet_preflight.go`), pairing path (`marge_pairing.go`), and
|
||||
cross-check (`preflight_crosscheck.go`).
|
||||
|
||||
@@ -8,12 +8,21 @@ import (
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
// DEPRECATED: handlers_telnet_probe.go (HandleTelnetProbe,
|
||||
// HandleProbeInbound, telnetProbeTimeout, telnetProbeResponse) is
|
||||
// scheduled for removal. The swUpdate daemon caches its target URL
|
||||
// at boot; the active flip in RunTelnetRoundTripProbe never reaches
|
||||
// the running daemon. See docs/analysis/TELNET-MIGRATION-METHOD.md
|
||||
// §9.8. Replaced by HandlePeerProbe (handlers_peer_probe.go).
|
||||
|
||||
// telnetProbeTimeout caps how long the orchestrator waits for the
|
||||
// device's outbound swUpdateCheck fan-out to land on /probe/{token}.
|
||||
// 6s lines up with the existing telnet preflight budgets and is well
|
||||
// above the median observed round-trip (<1s on FW 27.0.6).
|
||||
const telnetProbeTimeout = 6 * time.Second
|
||||
|
||||
// DEPRECATED: see the file-level note above.
|
||||
//
|
||||
// HandleProbeInbound is the catch-all for /probe/{token}/* — the path
|
||||
// the round-trip orchestrator sets as the speaker's swUpdateUrl. Any
|
||||
// hit signals the registered channel; the response body is a minimal
|
||||
@@ -36,6 +45,8 @@ type telnetProbeResponse struct {
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// DEPRECATED: see the file-level note above.
|
||||
//
|
||||
// HandleTelnetProbe runs the SSH-less round-trip reachability check.
|
||||
// Generates a token, temporarily points the speaker's swUpdateUrl at
|
||||
// /probe/{token} via telnet, triggers :8090/swUpdateCheck, and reports
|
||||
|
||||
@@ -2,6 +2,13 @@ package handlers
|
||||
|
||||
import "sync"
|
||||
|
||||
// DEPRECATED: probeRegistry is scheduled for removal. The active
|
||||
// round-trip probe it backs cannot work without a reboot (the swUpdate
|
||||
// daemon caches its URL at boot). See
|
||||
// docs/analysis/TELNET-MIGRATION-METHOD.md §9.8. Replaced by
|
||||
// peerObserver (peer_observer.go) which keys on device IP and supports
|
||||
// passive post-migration reachability checks.
|
||||
|
||||
// probeRegistry is the rendezvous between the telnet round-trip probe
|
||||
// orchestrator (which registers a one-shot token and waits for an
|
||||
// inbound) and the /probe/{token}/* HTTP handler (which closes the
|
||||
|
||||
@@ -61,8 +61,12 @@ type Server struct {
|
||||
amazonClientSecret string
|
||||
amazonRedirectURI string
|
||||
amazonService *amazon.Service
|
||||
probes *probeRegistry
|
||||
peerObserver *peerObserver
|
||||
// DEPRECATED: probes (probeRegistry) is removed in a follow-up
|
||||
// commit alongside the round-trip probe handlers; see
|
||||
// docs/analysis/TELNET-MIGRATION-METHOD.md §9.8. peerObserver is
|
||||
// the replacement substrate.
|
||||
probes *probeRegistry
|
||||
peerObserver *peerObserver
|
||||
}
|
||||
|
||||
// RequestSnapshot represents an immutable snapshot of an HTTP request.
|
||||
|
||||
@@ -10,6 +10,15 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// DEPRECATED: ProbeRegistrar, TelnetProbeResult, generateProbeToken,
|
||||
// and RunTelnetRoundTripProbe are scheduled for removal. The swUpdate
|
||||
// daemon caches its target URL at boot and ignores live `sys
|
||||
// configuration` writes, so the temporary flip never reaches the
|
||||
// running daemon. See docs/analysis/TELNET-MIGRATION-METHOD.md §9.8.
|
||||
// Replaced by Manager.RunPeerReachabilityProbe in peer_probe.go for
|
||||
// the migrated case; pre-migration validation relies on the other
|
||||
// pre-flight rows plus the Apply + reboot cycle.
|
||||
|
||||
// ProbeRegistrar is the rendezvous between the round-trip probe
|
||||
// orchestrator (which registers a token and waits) and an HTTP layer
|
||||
// (which signals the channel when the device's outbound lands on the
|
||||
@@ -48,6 +57,8 @@ func generateProbeToken() (string, error) {
|
||||
return hex.EncodeToString(b), nil
|
||||
}
|
||||
|
||||
// DEPRECATED: see the package-level note above.
|
||||
//
|
||||
// RunTelnetRoundTripProbe is the SSH-less reachability check that
|
||||
// fills the gap the curl-from-device HTTPS test leaves on USB-
|
||||
// unlock-refusing speakers. The sequence:
|
||||
|
||||
Reference in New Issue
Block a user