Two per-device checks run against each speaker's CA bundle via a
single SSH probe round-trip:
(1) Every PEM block from ca-bundle.crt.original (the factory backup
written by TrustCACertFromBytes on first CA injection) must be
present in the live ca-bundle.crt. A missing block means the
original trust store was truncated, which would break external
HTTPS (Spotify, Amazon, firmware updates).
(2) The AfterTouch CA sentinel (# AfterTouch) must be present in
the live bundle. Without it the speaker rejects AfterTouch's
TLS cert and migration is effectively inactive.
Both findings carry a QuickFix:
- FixIDRestoreAndInjectCA: cp .original → live bundle over SSH,
then TrustCACert to re-inject the AfterTouch CA.
- FixIDInjectCACert: TrustCACert only (original certs intact).
Graceful degradation:
- SSH unavailable → SeverityInfo, no fix offered.
- .original absent (device never had install-ca run) → SeverityWarning,
suggest install-ca; check (2) still runs.
Infrastructure changes:
- ssh_probe.go: add ca-bundle.crt.original to probeFilePaths (free
in the existing single-round-trip batch).
- setup.go: export ProbeCABundles and RestoreCABundleFromOriginal so
the handlers package can use them without exposing speakerProbe.
- Fix executors live in handlers (need setup.Manager) per the
established boundary used by completeSpeakerPairingFix.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add `soundtouch-cli setup` subcommand group covering the full reset →
re-provision → pair lifecycle as a scriptable alternative to the web UI:
inspect, verify, plan, factory-reset, wait-ap, wifi-push, wait-online,
ssh-check, install-ca, migrate, reboot, pair (bare | full state machine)
Supporting library code lives in pkg/service/setup: factory_reset.go,
wifi_provision.go, inspect.go, init_plan.go, setup_session.go.
Confirmed against ST10 firmware 27.0.6 that bare setMargeAccount over
WebSocket — no SETUP_START/SETUP_ENTER/SETUP_LEAVE bracket — is
sufficient to pair a factory-reset speaker; the firmware materializes
SystemConfigurationDB.xml and Sources.xml itself and the pairing
survives reboot. Result and field-by-field SystemConfigurationDB
comparison documented in docs/analysis/SETUP-WEBSOCKET-EXPERIMENT.md.
Captures the device's pre-reset DELETE-to-marge plus its LAN peer
notification flow in docs/analysis/FACTORY-RESET-PROTOCOL.md.
Perf: batch GetMigrationSummary's SSH probes into one Run() call via
ssh_probe.go / ssh_probe_apply.go — was ~8 sequential dials at
500-1000 ms each on FW 27 crypto, now one round-trip. Same data shape,
same MigrationSummary fields populated.
Fixes /clockTime and /clockDisplay wire formats — firmware 27 rejects
the legacy flat XML ("Error parsing request"). ClockTimeRequest now
uses utcTime attribute; ClockDisplayRequest emits the nested
<clockConfig> envelope with timezoneInfo/timeFormat/brightnessLevel.
Removes cmd/example-init-speaker (superseded by setup pair).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>