From c931510384de1b41eb07c6b639577a2bcd23044f Mon Sep 17 00:00:00 2001 From: Tobias Gesellchen Date: Sun, 3 May 2026 18:32:27 +0200 Subject: [PATCH] feat(setup): add 'original' option and harden backup before migration (#202) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rename proxy option values: 'upstream' → 'proxied', 'official' → 'original' - Add 'original' option to preserve current device URL as-is per field - Drop proxyURL guard in applyProxyOptions so 'original' works without a proxy - Abort migration if on-device backup cannot be created (was warning-only) Co-authored-by: Claude Sonnet 4.6 --- pkg/service/handlers/web/index.html | 28 +++++++++++++++------- pkg/service/setup/setup.go | 37 +++++++++++++++++++---------- pkg/service/setup/setup_test.go | 4 ++-- 3 files changed, 46 insertions(+), 23 deletions(-) diff --git a/pkg/service/handlers/web/index.html b/pkg/service/handlers/web/index.html index f108e08..f66c873 100644 --- a/pkg/service/handlers/web/index.html +++ b/pkg/service/handlers/web/index.html @@ -770,8 +770,11 @@ - + @@ -787,8 +790,11 @@ - + @@ -804,8 +810,11 @@ - + @@ -821,8 +830,11 @@ - + diff --git a/pkg/service/setup/setup.go b/pkg/service/setup/setup.go index c713346..0d52372 100644 --- a/pkg/service/setup/setup.go +++ b/pkg/service/setup/setup.go @@ -526,26 +526,35 @@ func (m *Manager) checkCurrentConfig(summary *MigrationSummary, deviceIP string) return "", err } -// applyProxyOptions modifies planned config based on proxy options +// applyProxyOptions modifies planned config based on proxy options. +// Each field accepts: "proxied" (route through proxyURL), "original" (keep current value), or unset (use targetURL via plannedCfg default). func (m *Manager) applyProxyOptions(plannedCfg *PrivateCfg, proxyURL string, options map[string]string, currentCfg *PrivateCfg) { - if proxyURL == "" || currentCfg == nil { + if currentCfg == nil { return } - if options["marge"] == "upstream" && currentCfg.MargeServerUrl != "" { + if options["marge"] == "proxied" && currentCfg.MargeServerUrl != "" { plannedCfg.MargeServerUrl = fmt.Sprintf("%s/proxy/%s", proxyURL, currentCfg.MargeServerUrl) + } else if options["marge"] == "original" && currentCfg.MargeServerUrl != "" { + plannedCfg.MargeServerUrl = currentCfg.MargeServerUrl } - if options["stats"] == "upstream" && currentCfg.StatsServerUrl != "" { + if options["stats"] == "proxied" && currentCfg.StatsServerUrl != "" { plannedCfg.StatsServerUrl = fmt.Sprintf("%s/proxy/%s", proxyURL, currentCfg.StatsServerUrl) + } else if options["stats"] == "original" && currentCfg.StatsServerUrl != "" { + plannedCfg.StatsServerUrl = currentCfg.StatsServerUrl } - if options["sw_update"] == "upstream" && currentCfg.SwUpdateUrl != "" { + if options["sw_update"] == "proxied" && currentCfg.SwUpdateUrl != "" { plannedCfg.SwUpdateUrl = fmt.Sprintf("%s/proxy/%s", proxyURL, currentCfg.SwUpdateUrl) + } else if options["sw_update"] == "original" && currentCfg.SwUpdateUrl != "" { + plannedCfg.SwUpdateUrl = currentCfg.SwUpdateUrl } - if options["bmx"] == "upstream" && currentCfg.BmxRegistryUrl != "" { + if options["bmx"] == "proxied" && currentCfg.BmxRegistryUrl != "" { plannedCfg.BmxRegistryUrl = fmt.Sprintf("%s/proxy/%s", proxyURL, currentCfg.BmxRegistryUrl) + } else if options["bmx"] == "original" && currentCfg.BmxRegistryUrl != "" { + plannedCfg.BmxRegistryUrl = currentCfg.BmxRegistryUrl } } @@ -721,7 +730,7 @@ func (m *Manager) migrateViaXML(deviceIP, targetURL, proxyURL string, options ma BmxRegistryUrl: fmt.Sprintf("%s/bmx/registry/v1/services", targetURL), } - // If we have a proxyURL and can read current config, use it + // If we can read current config, apply per-field options if curCfg, curCfgErr := client.Run(fmt.Sprintf("cat %s", SoundTouchSdkPrivateCfgPath)); curCfgErr == nil && curCfg != "" { logs += "Read current configuration\n" @@ -758,16 +767,18 @@ func (m *Manager) migrateViaXML(deviceIP, targetURL, proxyURL string, options ma fmt.Printf("Backing up original config to %s.original\n", remotePath) // Try to copy existing config to .original, ensuring filesystem is writable if output, err := client.Run(fmt.Sprintf("%s && cp %s %s.original", rwCmd, remotePath, remotePath)); err != nil { - logs += fmt.Sprintf("Warning: failed to cp backup config: %v (output: %s)\n", err, output) - fmt.Printf("Warning: failed to cp backup config: %v (output: %s)\n", err, output) + logs += fmt.Sprintf("cp backup failed: %v (output: %s)\n", err, output) + fmt.Printf("cp backup failed: %v (output: %s)\n", err, output) // Fallback to manual upload if cp failed (might not have cp?) if config, err := client.Run(fmt.Sprintf("cat %s", remotePath)); err == nil && config != "" { if err := client.UploadContent([]byte(config), remotePath+".original"); err != nil { - logs += "Warning: failed to upload backup config: " + err.Error() + "\n" - fmt.Printf("Warning: failed to upload backup config: %v\n", err) - } else { - logs += "Uploaded backup config via fallback\n" + logs += "failed to upload backup config: " + err.Error() + "\n" + return logs, fmt.Errorf("cannot create backup of %s before migration: %w", remotePath, err) } + + logs += "Uploaded backup config via fallback\n" + } else { + return logs, fmt.Errorf("cannot create backup of %s before migration: failed to read original config", remotePath) } } else { logs += "Copied backup config to .original\n" diff --git a/pkg/service/setup/setup_test.go b/pkg/service/setup/setup_test.go index cd98ffb..7d27b9c 100644 --- a/pkg/service/setup/setup_test.go +++ b/pkg/service/setup/setup_test.go @@ -258,9 +258,9 @@ func TestGetMigrationSummary_WithProxyOptions(t *testing.T) { // If SSH fails, ParsedCurrentConfig will be nil. options := map[string]string{ - "marge": "upstream", + "marge": "proxied", "stats": "self", - "sw_update": "upstream", + "sw_update": "proxied", "bmx": "self", }