mirror of
https://github.com/gesellix/Bose-SoundTouch.git
synced 2026-08-18 08:36:13 +00:00
feat(cli/setup): friendlier enable-ssh timeout + re-sync boseurls after XML migration (refs #471)
Two follow-ups from the #471 field reports on the BETA `setup enable-ssh`: 1. enable-ssh: when sshd (:22) does not come up within the wait window, this is no longer treated as a hard error. On some devices (e.g. the Wireless Link Adapter) the envswitch injection is accepted but sshd only starts after the speaker restarts. The command now prints a warning with power-cycle + retry guidance (and the exact ssh command), deliberately leaves the injected boseurls in place so a restart re-triggers the unlock, and exits cleanly instead of failing. 2. XML migration: re-apply the boseurls over telnet at the end of migrateViaXML so the runtime layer reported by `getpdo CurrentSystemConfiguration` matches the persisted SoundTouchSdkPrivateCfg.xml. After enable-ssh bootstraps SSH, that runtime layer still points at the placeholder (https://aftertouch.invalid), so the preflight cross-check keeps warning that margeServerUrl/swUpdateUrl differ between transports until a reboot. The re-apply reconciles it now. Best-effort: if telnet is unavailable (e.g. port 17000 was closed via --close-17000), a reboot still reconciles the layers, so it only logs a note and never fails the migration. Tests cover the re-apply command and its best-effort (non-fatal) behavior. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
ae67e1e8ad
commit
fa158bc498
@@ -1077,9 +1077,39 @@ func (m *Manager) migrateViaXML(deviceIP, targetURL, proxyURL string, options ma
|
||||
}
|
||||
}
|
||||
|
||||
logs += m.resyncBoseURLsAfterXML(deviceIP, cfg.MargeServerUrl, cfg.SwUpdateUrl)
|
||||
|
||||
return logs, nil
|
||||
}
|
||||
|
||||
// resyncBoseURLsAfterXML re-applies the boseurls over telnet so the runtime
|
||||
// URL layer matches the XML just written by migrateViaXML.
|
||||
//
|
||||
// The XML migration only updates the persisted SoundTouchSdkPrivateCfg.xml; it
|
||||
// does not touch the runtime/persistence layer that `getpdo
|
||||
// CurrentSystemConfiguration` reports. When SSH was bootstrapped via #471
|
||||
// (`enable-ssh`), that layer still points at the placeholder boseurls
|
||||
// (https://aftertouch.invalid), so the preflight cross-check keeps warning that
|
||||
// margeServerUrl/swUpdateUrl differ between transports until a reboot.
|
||||
// Re-applying the real boseurls over telnet :17000 reconciles it immediately.
|
||||
//
|
||||
// Best-effort: telnet may be unavailable (no port 17000, or it was closed via
|
||||
// --close-17000), in which case a reboot still reconciles the layers, so this
|
||||
// only returns a note and never fails the migration. Returns the log lines to
|
||||
// append.
|
||||
func (m *Manager) resyncBoseURLsAfterXML(deviceIP, marge, swUpdate string) string {
|
||||
if m.NewTelnet == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
rlogs, rerr := m.setBoseURLsViaTelnet(deviceIP, marge, swUpdate)
|
||||
if rerr != nil {
|
||||
return fmt.Sprintf("Note: could not re-sync boseurls over telnet (%v); a device reboot will reconcile the runtime layer.\n", rerr)
|
||||
}
|
||||
|
||||
return "Re-applied boseurls over telnet so the runtime layer matches the new configuration:\n" + rlogs
|
||||
}
|
||||
|
||||
// BackupConfigOffDevice creates a local backup of the speaker's configuration files in the DataStore.
|
||||
func (m *Manager) BackupConfigOffDevice(deviceIP string) error {
|
||||
if m.DataStore == nil {
|
||||
|
||||
@@ -2018,3 +2018,69 @@ func TestMigrateSpeaker_ResolvBlocking(t *testing.T) {
|
||||
t.Errorf("Did not expect pre-flight DNS errors, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestMigrateViaXML_ReappliesBoseURLsOverTelnet verifies the #471 follow-up:
|
||||
// after an XML migration, the runtime boseurls layer is re-applied over telnet
|
||||
// so it matches the persisted config (otherwise the preflight cross-check keeps
|
||||
// warning about the enable-ssh placeholder until a reboot).
|
||||
func TestMigrateViaXML_ReappliesBoseURLsOverTelnet(t *testing.T) {
|
||||
cm := certmanager.NewCertificateManager(filepath.Join(t.TempDir(), "certs"))
|
||||
if err := cm.EnsureCA(); err != nil {
|
||||
t.Fatalf("EnsureCA: %v", err)
|
||||
}
|
||||
|
||||
target := "https://192.0.2.10:8443"
|
||||
m := NewManager(target, nil, cm)
|
||||
m.NewSSH = func(string) SSHClient {
|
||||
return &mockSSH{runFunc: func(string) (string, error) { return "", nil }}
|
||||
}
|
||||
|
||||
ft := &fakeTelnet{banner: "->", responses: map[string]string{}}
|
||||
m.NewTelnet = func(string) TelnetClient { return ft }
|
||||
|
||||
if _, err := m.MigrateSpeaker("192.0.2.10", target, "", nil, MigrationMethodXML); err != nil {
|
||||
t.Fatalf("MigrateSpeaker: %v", err)
|
||||
}
|
||||
|
||||
want := `envswitch boseurls set "` + target + `" "` + target + `/updates/soundtouch"`
|
||||
|
||||
var found bool
|
||||
for _, c := range ft.commands {
|
||||
if c == want {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
t.Errorf("expected boseurls re-apply %q after XML migration; sent: %v", want, ft.commands)
|
||||
}
|
||||
}
|
||||
|
||||
// TestMigrateViaXML_BoseURLsResyncIsBestEffort verifies that a telnet failure
|
||||
// during the post-migration boseurls re-apply does not fail the migration (a
|
||||
// device reboot still reconciles the runtime layer).
|
||||
func TestMigrateViaXML_BoseURLsResyncIsBestEffort(t *testing.T) {
|
||||
cm := certmanager.NewCertificateManager(filepath.Join(t.TempDir(), "certs"))
|
||||
if err := cm.EnsureCA(); err != nil {
|
||||
t.Fatalf("EnsureCA: %v", err)
|
||||
}
|
||||
|
||||
target := "https://192.0.2.10:8443"
|
||||
m := NewManager(target, nil, cm)
|
||||
m.NewSSH = func(string) SSHClient {
|
||||
return &mockSSH{runFunc: func(string) (string, error) { return "", nil }}
|
||||
}
|
||||
m.NewTelnet = func(string) TelnetClient {
|
||||
return &fakeTelnet{dialErr: errors.New("connection refused")}
|
||||
}
|
||||
|
||||
logs, err := m.MigrateSpeaker("192.0.2.10", target, "", nil, MigrationMethodXML)
|
||||
if err != nil {
|
||||
t.Fatalf("MigrateSpeaker should not fail when the telnet re-sync fails: %v", err)
|
||||
}
|
||||
|
||||
if !strings.Contains(logs, "could not re-sync boseurls over telnet") {
|
||||
t.Errorf("expected a best-effort note about the failed telnet re-sync; logs:\n%s", logs)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user