feat(setup): add enable-ssh --full-config for devices where sshd never starts (#515)

The default `setup enable-ssh` injects the remote_services/sshd payload
only via `envswitch boseurls set` and relies on the speaker re-reading its
boseurls (~60s) without a reboot. On the SoundTouch Portable (Series I,
FW 27.0.6.46330.5043500) and some CineMate 520 units the device accepts and
persists that injection (getpdo confirms) but sshd never comes up, so :22
stays "Connection refused".

@Henri-be got root on the ST Portable by typing a different sequence by hand
over telnet :17000: the injection rides `sys configuration margeServerUrl`
(the runtime layer) as well as `envswitch`, all four URL keys are written,
and the device is rebooted so it re-parses the config at boot.

Add an opt-in `--full-config` flag that replicates that exact sequence
(EnableSSHViaTelnetFullConfig + telnet reboot via the existing
RebootMethodTelnet). The default single-envswitch path is unchanged, so the
field-confirmed flow on the Wireless Link Adapter and CineMate 520 `lisa`
variant does not regress. Docs (TELNET-COMMAND-REFERENCE, DEVICE-LOGGING)
document both paths and which device models/firmware need `--full-config`.

The flag automation is candidate behaviour awaiting reporter confirmation:
the manual sequence is confirmed on the ST Portable, the flag is not yet.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Tobias Gesellchen
2026-06-22 21:23:15 +02:00
co-authored by Claude Opus 4.8
parent 4e25dacb0d
commit 9331e63b2d
5 changed files with 211 additions and 12 deletions
+53 -9
View File
@@ -538,6 +538,53 @@ func setupSSHCheckCmd() *cli.Command {
}
}
// runEnableSSHInjection runs the port-17000 SSH-enable injection over telnet,
// printing the device transcript as it goes. With fullConfig it sends the
// #515 sequence (all four config URLs with the injection on margeServerUrl, not
// just envswitch) and reboots afterwards; otherwise it sends the single-
// envswitch default that fires on the speaker's next boseurls check.
func runEnableSSHInjection(m *setup.Manager, host, serviceURL string, fullConfig bool) error {
var (
logs string
err error
)
if fullConfig {
fmt.Printf("Enabling SSH on %s via telnet :17000 (full #515 sequence: all four config URLs with the injection on margeServerUrl, then reboot)...\n", host)
logs, err = m.EnableSSHViaTelnetFullConfig(host, serviceURL)
} else {
fmt.Printf("Enabling SSH on %s via telnet :17000 (runs on the speaker's next boseurls check, up to ~60s)...\n", host)
logs, err = m.EnableSSHViaTelnet(host, serviceURL)
}
if logs != "" {
fmt.Print(logs)
}
if err != nil {
PrintError(err.Error())
return err
}
if !fullConfig {
return nil
}
fmt.Println("Rebooting the speaker to apply the new configuration...")
rlogs, rerr := m.Reboot(host, setup.RebootMethodTelnet)
if rlogs != "" {
fmt.Print(rlogs)
}
if rerr != nil {
PrintError(rerr.Error())
return rerr
}
return nil
}
func setupEnableSSHCmd() *cli.Command {
return &cli.Command{
Name: "enable-ssh",
@@ -556,6 +603,11 @@ func setupEnableSSHCmd() *cli.Command {
Value: 90 * time.Second,
Usage: "How long to wait for sshd (:22) after the envswitch injection (it runs on the speaker's next boseurls check, ~60s)",
},
&cli.BoolFlag{
Name: "full-config",
Usage: "For stubborn devices (ST Portable, CineMate 520) where the default single-envswitch injection is accepted but sshd never starts: " +
"replicate the #515 manual sequence — write all four sys configuration URL keys with the SSH-enable injection on margeServerUrl (not just envswitch), then reboot",
},
&cli.BoolFlag{
Name: "no-reset-urls",
Usage: "Skip restoring clean boseurls after SSH is up (leaves the injected marge URL in place)",
@@ -589,15 +641,7 @@ func setupEnableSSHCmd() *cli.Command {
serviceURL = "https://aftertouch.invalid"
}
fmt.Printf("Enabling SSH on %s via telnet :17000 (runs on the speaker's next boseurls check, up to ~60s)...\n", cfg.Host)
logs, err := m.EnableSSHViaTelnet(cfg.Host, serviceURL)
if logs != "" {
fmt.Print(logs)
}
if err != nil {
PrintError(err.Error())
if err := runEnableSSHInjection(m, cfg.Host, serviceURL, c.Bool("full-config")); err != nil {
return err
}