From 9f7cb81b450e86b650fe2607e1b7e42c877c0c7b Mon Sep 17 00:00:00 2001 From: Tobias Gesellchen Date: Sun, 22 Mar 2026 10:53:36 +0100 Subject: [PATCH] Implement skip mirror endpoints to reduce false positives in parity checks (#126) Added 'Skip Mirror Endpoints' setting to allow specific requests like `/oauth/device/*/music/musicprovider/15/token/cs3` to be handled exclusively locally, even when mirroring is enabled. Updated MirrorMiddleware to check against the skip list before performing mirroring or parity logic. Exposed the setting via the Web UI Settings tab and the CLI. Updated relevant tests to accommodate the configuration changes. Co-authored-by: Junie --- cmd/soundtouch-service/main.go | 40 +++++--- pkg/service/datastore/datastore.go | 31 ++++--- pkg/service/handlers/handlers_oauth_test.go | 2 +- pkg/service/handlers/handlers_setup.go | 93 ++++++++++--------- .../handlers/mirror_loop_prevention_test.go | 2 +- pkg/service/handlers/mirror_middleware.go | 18 +++- pkg/service/handlers/mirror_preferred_test.go | 8 +- pkg/service/handlers/mirror_test.go | 4 +- pkg/service/handlers/server.go | 4 +- .../handlers/snapshot_integrity_test.go | 4 +- pkg/service/handlers/web/index.html | 7 ++ pkg/service/handlers/web/js/script.js | 8 ++ pkg/service/setup/setup.go | 4 + 13 files changed, 136 insertions(+), 89 deletions(-) diff --git a/cmd/soundtouch-service/main.go b/cmd/soundtouch-service/main.go index 65a8d3a..1d822ae 100644 --- a/cmd/soundtouch-service/main.go +++ b/cmd/soundtouch-service/main.go @@ -189,6 +189,11 @@ func main() { Usage: "Endpoints to mirror to Bose Cloud (comma-separated or multiple flags)", EnvVars: []string{"MIRROR_ENDPOINTS"}, }, + &cli.StringSliceFlag{ + Name: "skip-mirror-endpoints", + Usage: "Endpoints to skip mirroring to Bose Cloud (comma-separated or multiple flags)", + EnvVars: []string{"SKIP_MIRROR_ENDPOINTS"}, + }, &cli.StringSliceFlag{ Name: "internal-paths", Usage: "Paths for internal requests (comma-separated or multiple flags)", @@ -241,7 +246,7 @@ func main() { server.SetVersionInfo(version, commit, date) server.SetDiscoverySettings(config.discoveryInterval, persisted.DiscoveryEnabled) server.SetDNSSettings(persisted.DNSEnabled, strings.Join(persisted.DNSUpstream, ","), persisted.DNSBindAddr) - server.SetMirrorSettings(persisted.MirrorEnabled, persisted.MirrorEndpoints, persisted.PreferredSource) + server.SetMirrorSettings(persisted.MirrorEnabled, persisted.MirrorEndpoints, persisted.SkipMirrorEndpoints, persisted.PreferredSource) server.SetInternalPaths(persisted.InternalPaths) server.SetSpotifyConfig(config.spotifyClientID, config.spotifyClientSecret, config.spotifyRedirectURI) server.SetMgmtConfig(config.mgmtUsername, config.mgmtPassword) @@ -374,6 +379,7 @@ type serviceConfig struct { dnsBind string mirrorEnabled bool mirrorEndpoints []string + skipMirrorEndpoints []string internalPaths []string discoveryInterval time.Duration domains []string @@ -448,6 +454,7 @@ func loadConfig(c *cli.Context) serviceConfig { mgmtPassword := c.String("mgmt-password") mirrorEnabled := c.Bool("mirror-enabled") mirrorEndpoints := c.StringSlice("mirror-endpoints") + skipMirrorEndpoints := c.StringSlice("skip-mirror-endpoints") internalPaths := c.StringSlice("internal-paths") migrationEnabled := c.Bool("migration-enabled") migrationDryRun := c.Bool("migration-dry-run") @@ -469,6 +476,7 @@ func loadConfig(c *cli.Context) serviceConfig { dnsBind: dnsBind, mirrorEnabled: mirrorEnabled, mirrorEndpoints: mirrorEndpoints, + skipMirrorEndpoints: skipMirrorEndpoints, internalPaths: internalPaths, discoveryInterval: discoveryInterval, domains: domains, @@ -565,6 +573,7 @@ func applyPersistedSettings(ds *datastore.DataStore, config *serviceConfig) data config.mirrorEnabled = persisted.MirrorEnabled config.mirrorEndpoints = persisted.MirrorEndpoints + config.skipMirrorEndpoints = persisted.SkipMirrorEndpoints config.preferredSource = persisted.PreferredSource config.internalPaths = persisted.InternalPaths @@ -573,20 +582,21 @@ func applyPersistedSettings(ds *datastore.DataStore, config *serviceConfig) data func createDefaultSettings(ds *datastore.DataStore, config serviceConfig) datastore.Settings { settings := datastore.Settings{ - ServerURL: config.serverURL, - HTTPServerURL: config.httpsServerURL, - RedactLogs: config.redact, - LogBodies: config.logBody, - RecordInteractions: config.record, - DiscoveryInterval: config.discoveryInterval.String(), - DiscoveryEnabled: true, - DNSEnabled: config.dnsEnabled, - DNSUpstream: strings.Split(config.dnsUpstream, ","), - DNSBindAddr: config.dnsBind, - MirrorEnabled: config.mirrorEnabled, - MirrorEndpoints: config.mirrorEndpoints, - PreferredSource: config.preferredSource, - InternalPaths: config.internalPaths, + ServerURL: config.serverURL, + HTTPServerURL: config.httpsServerURL, + RedactLogs: config.redact, + LogBodies: config.logBody, + RecordInteractions: config.record, + DiscoveryInterval: config.discoveryInterval.String(), + DiscoveryEnabled: true, + DNSEnabled: config.dnsEnabled, + DNSUpstream: strings.Split(config.dnsUpstream, ","), + DNSBindAddr: config.dnsBind, + MirrorEnabled: config.mirrorEnabled, + MirrorEndpoints: config.mirrorEndpoints, + SkipMirrorEndpoints: config.skipMirrorEndpoints, + PreferredSource: config.preferredSource, + InternalPaths: config.internalPaths, Shortcuts: map[string]int{ "/.well-known/appspecific/com.chrome.devtools.json": http.StatusNotFound, "/sw.js": http.StatusNotFound, diff --git a/pkg/service/datastore/datastore.go b/pkg/service/datastore/datastore.go index 6e310bc..7ca259a 100644 --- a/pkg/service/datastore/datastore.go +++ b/pkg/service/datastore/datastore.go @@ -1138,21 +1138,22 @@ func (ds *DataStore) GetETagForAccount(account, device string) int64 { // Settings represents the global service settings. type Settings struct { - ServerURL string `json:"server_url"` - HTTPServerURL string `json:"https_server_url,omitempty"` - RedactLogs bool `json:"redact_logs"` - LogBodies bool `json:"log_bodies"` - RecordInteractions bool `json:"record_interactions"` - DiscoveryInterval string `json:"discovery_interval,omitempty"` - DiscoveryEnabled bool `json:"discovery_enabled"` - DNSEnabled bool `json:"dns_enabled"` - DNSUpstream []string `json:"dns_upstream,omitempty"` - DNSBindAddr string `json:"dns_bind_addr,omitempty"` - MirrorEnabled bool `json:"mirror_enabled"` - MirrorEndpoints []string `json:"mirror_endpoints,omitempty"` - PreferredSource string `json:"preferred_source,omitempty"` - InternalPaths []string `json:"internal_paths,omitempty"` - Shortcuts map[string]int `json:"shortcuts,omitempty"` + ServerURL string `json:"server_url"` + HTTPServerURL string `json:"https_server_url,omitempty"` + RedactLogs bool `json:"redact_logs"` + LogBodies bool `json:"log_bodies"` + RecordInteractions bool `json:"record_interactions"` + DiscoveryInterval string `json:"discovery_interval,omitempty"` + DiscoveryEnabled bool `json:"discovery_enabled"` + DNSEnabled bool `json:"dns_enabled"` + DNSUpstream []string `json:"dns_upstream,omitempty"` + DNSBindAddr string `json:"dns_bind_addr,omitempty"` + MirrorEnabled bool `json:"mirror_enabled"` + MirrorEndpoints []string `json:"mirror_endpoints,omitempty"` + SkipMirrorEndpoints []string `json:"skip_mirror_endpoints,omitempty"` + PreferredSource string `json:"preferred_source,omitempty"` + InternalPaths []string `json:"internal_paths,omitempty"` + Shortcuts map[string]int `json:"shortcuts,omitempty"` } // GetSettings retrieves the global service settings. diff --git a/pkg/service/handlers/handlers_oauth_test.go b/pkg/service/handlers/handlers_oauth_test.go index 98ae544..47cc7b0 100644 --- a/pkg/service/handlers/handlers_oauth_test.go +++ b/pkg/service/handlers/handlers_oauth_test.go @@ -81,7 +81,7 @@ func TestHandleBoseSpotifyToken_FallbackToProxy(t *testing.T) { // Mirroring must be enabled for HandleBoseProxy to work (based on previous changes) // Actually I reverted that, so it should work regardless of MirrorEnabled now. - server.SetMirrorSettings(true, nil, "") + server.SetMirrorSettings(true, nil, nil, "") // chi.URLParam works when using chi router r := chi.NewRouter() diff --git a/pkg/service/handlers/handlers_setup.go b/pkg/service/handlers/handlers_setup.go index 400063c..74d5990 100644 --- a/pkg/service/handlers/handlers_setup.go +++ b/pkg/service/handlers/handlers_setup.go @@ -156,6 +156,7 @@ func (s *Server) HandleGetSettings(w http.ResponseWriter, _ *http.Request) { dnsBindAddr := s.dnsBindAddr mirrorEnabled := s.mirrorEnabled mirrorEndpoints := s.mirrorEndpoints + skipMirrorEndpoints := s.skipMirrorEndpoints preferredSource := s.preferredSource internalPaths := s.internalPaths redact, logBody, record := s.proxyRedact, s.proxyLogBody, s.recordEnabled @@ -166,24 +167,25 @@ func (s *Server) HandleGetSettings(w http.ResponseWriter, _ *http.Request) { dnsRunning, actualBind := s.GetDNSRunning() if err := json.NewEncoder(w).Encode(map[string]interface{}{ - "server_url": serverURL, - "https_server_url": httpsServerURL, - "discovery_interval": discoveryInterval, - "discovery_enabled": discoveryEnabled, - "dns_enabled": dnsEnabled, - "dns_running": dnsRunning, - "dns_actual_bind": actualBind, - "dns_upstream": strings.Join(dnsUpstream, ","), - "dns_bind_addr": dnsBindAddr, - "mirror_enabled": mirrorEnabled, - "mirror_endpoints": mirrorEndpoints, - "preferred_source": preferredSource, - "internal_paths": internalPaths, - "redact_logs": redact, - "log_bodies": logBody, - "record_interactions": record, - "shortcuts": shortcuts, - "spotify_configured": spotifyConfigured, + "server_url": serverURL, + "https_server_url": httpsServerURL, + "discovery_interval": discoveryInterval, + "discovery_enabled": discoveryEnabled, + "dns_enabled": dnsEnabled, + "dns_running": dnsRunning, + "dns_actual_bind": actualBind, + "dns_upstream": strings.Join(dnsUpstream, ","), + "dns_bind_addr": dnsBindAddr, + "mirror_enabled": mirrorEnabled, + "mirror_endpoints": mirrorEndpoints, + "skip_mirror_endpoints": skipMirrorEndpoints, + "preferred_source": preferredSource, + "internal_paths": internalPaths, + "redact_logs": redact, + "log_bodies": logBody, + "record_interactions": record, + "shortcuts": shortcuts, + "spotify_configured": spotifyConfigured, }); err != nil { http.Error(w, "Failed to encode response", http.StatusInternalServerError) return @@ -193,17 +195,18 @@ func (s *Server) HandleGetSettings(w http.ResponseWriter, _ *http.Request) { // HandleUpdateSettings updates the service settings. func (s *Server) HandleUpdateSettings(w http.ResponseWriter, r *http.Request) { var settings struct { - ServerURL string `json:"server_url"` - DiscoveryInterval string `json:"discovery_interval"` - DiscoveryEnabled bool `json:"discovery_enabled"` - DNSEnabled bool `json:"dns_enabled"` - DNSUpstream string `json:"dns_upstream"` - DNSBindAddr string `json:"dns_bind_addr"` - MirrorEnabled bool `json:"mirror_enabled"` - MirrorEndpoints []string `json:"mirror_endpoints"` - PreferredSource string `json:"preferred_source"` - InternalPaths []string `json:"internal_paths"` - Shortcuts map[string]int `json:"shortcuts"` + ServerURL string `json:"server_url"` + DiscoveryInterval string `json:"discovery_interval"` + DiscoveryEnabled bool `json:"discovery_enabled"` + DNSEnabled bool `json:"dns_enabled"` + DNSUpstream string `json:"dns_upstream"` + DNSBindAddr string `json:"dns_bind_addr"` + MirrorEnabled bool `json:"mirror_enabled"` + MirrorEndpoints []string `json:"mirror_endpoints"` + SkipMirrorEndpoints []string `json:"skip_mirror_endpoints"` + PreferredSource string `json:"preferred_source"` + InternalPaths []string `json:"internal_paths"` + Shortcuts map[string]int `json:"shortcuts"` } if err := json.NewDecoder(r.Body).Decode(&settings); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) @@ -249,6 +252,7 @@ func (s *Server) HandleUpdateSettings(w http.ResponseWriter, r *http.Request) { s.mirrorEnabled = settings.MirrorEnabled s.mirrorEndpoints = settings.MirrorEndpoints + s.skipMirrorEndpoints = settings.SkipMirrorEndpoints s.preferredSource = settings.PreferredSource s.internalPaths = settings.InternalPaths @@ -269,21 +273,22 @@ func (s *Server) HandleUpdateSettings(w http.ResponseWriter, r *http.Request) { log.Printf("Saving updated settings to %s/settings.json", s.ds.DataDir) err = s.ds.SaveSettings(datastore.Settings{ - ServerURL: s.serverURL, - HTTPServerURL: currentHTTPS, - RedactLogs: currentRedact, - LogBodies: currentLogBody, - RecordInteractions: currentRecord, - DiscoveryInterval: s.discoveryInterval.String(), - DiscoveryEnabled: s.discoveryEnabled, - DNSEnabled: s.dnsEnabled, - DNSUpstream: s.dnsUpstream, - DNSBindAddr: s.dnsBindAddr, - MirrorEnabled: s.mirrorEnabled, - MirrorEndpoints: s.mirrorEndpoints, - PreferredSource: s.preferredSource, - InternalPaths: s.internalPaths, - Shortcuts: s.shortcuts, + ServerURL: s.serverURL, + HTTPServerURL: currentHTTPS, + RedactLogs: currentRedact, + LogBodies: currentLogBody, + RecordInteractions: currentRecord, + DiscoveryInterval: s.discoveryInterval.String(), + DiscoveryEnabled: s.discoveryEnabled, + DNSEnabled: s.dnsEnabled, + DNSUpstream: s.dnsUpstream, + DNSBindAddr: s.dnsBindAddr, + MirrorEnabled: s.mirrorEnabled, + MirrorEndpoints: s.mirrorEndpoints, + SkipMirrorEndpoints: s.skipMirrorEndpoints, + PreferredSource: s.preferredSource, + InternalPaths: s.internalPaths, + Shortcuts: s.shortcuts, }) dnsEnabled := s.dnsEnabled diff --git a/pkg/service/handlers/mirror_loop_prevention_test.go b/pkg/service/handlers/mirror_loop_prevention_test.go index 78e6fa8..025febd 100644 --- a/pkg/service/handlers/mirror_loop_prevention_test.go +++ b/pkg/service/handlers/mirror_loop_prevention_test.go @@ -20,7 +20,7 @@ func TestMirrorMiddleware_InfiniteLoop(t *testing.T) { _ = ds.Initialize() server := NewServer(ds, nil, "http://localhost:8000", false, false, false) - server.SetMirrorSettings(true, []string{"/loop"}, "upstream") + server.SetMirrorSettings(true, []string{"/loop"}, nil, "upstream") // Create a handler that would be the "next" in the chain. // If the loop occurs, this will be called repeatedly. diff --git a/pkg/service/handlers/mirror_middleware.go b/pkg/service/handlers/mirror_middleware.go index c22b285..f0f93b0 100644 --- a/pkg/service/handlers/mirror_middleware.go +++ b/pkg/service/handlers/mirror_middleware.go @@ -26,10 +26,10 @@ import ( // MirrorMiddleware returns a middleware that mirrors specific requests to the Bose upstream. func (s *Server) MirrorMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - enabled, endpoints, preferredSource := s.getMirrorSettings() + enabled, endpoints, skipEndpoints, preferredSource := s.getMirrorSettings() isMirrorRequest := r.Header.Get("X-Mirror-Request") == "true" - if !enabled || isMirrorRequest || len(endpoints) == 0 || !s.shouldMirror(r.URL.Path, endpoints) { + if !enabled || isMirrorRequest || len(endpoints) == 0 || !s.shouldMirror(r.URL.Path, endpoints) || s.shouldSkipMirror(r.URL.Path, skipEndpoints) { next.ServeHTTP(w, r) return } @@ -64,11 +64,11 @@ func (s *Server) MirrorMiddleware(next http.Handler) http.Handler { }) } -func (s *Server) getMirrorSettings() (bool, []string, string) { +func (s *Server) getMirrorSettings() (bool, []string, []string, string) { s.mu.RLock() defer s.mu.RUnlock() - return s.mirrorEnabled, s.mirrorEndpoints, s.preferredSource + return s.mirrorEnabled, s.mirrorEndpoints, s.skipMirrorEndpoints, s.preferredSource } func (s *Server) shouldMirror(path string, endpoints []string) bool { @@ -81,6 +81,16 @@ func (s *Server) shouldMirror(path string, endpoints []string) bool { return false } +func (s *Server) shouldSkipMirror(path string, skipEndpoints []string) bool { + for _, pattern := range skipEndpoints { + if matchPattern(pattern, path) { + return true + } + } + + return false +} + func (s *Server) mirrorUpstreamPreferred(detachedCtx context.Context, w http.ResponseWriter, r *http.Request, next http.Handler, bodyBytes []byte) { log.Printf("[MIRROR] Upstream is preferred source for %s %s", r.Method, r.URL.Path) diff --git a/pkg/service/handlers/mirror_preferred_test.go b/pkg/service/handlers/mirror_preferred_test.go index c5ea28a..47939eb 100644 --- a/pkg/service/handlers/mirror_preferred_test.go +++ b/pkg/service/handlers/mirror_preferred_test.go @@ -40,7 +40,7 @@ func TestMirrorMiddleware_PreferredSource(t *testing.T) { // 3. Setup our server with MirrorMiddleware server := NewServer(ds, nil, "http://localhost:8000", false, false, false) - server.SetMirrorSettings(true, []string{"/test/local"}, "local") + server.SetMirrorSettings(true, []string{"/test/local"}, nil, "local") // We need to trick performMirror to use our mock upstream. // performMirror uses r.Host. @@ -50,7 +50,7 @@ func TestMirrorMiddleware_PreferredSource(t *testing.T) { middleware := server.MirrorMiddleware(r) t.Run("PreferredLocal", func(t *testing.T) { - server.SetMirrorSettings(true, []string{"/test/local"}, "local") + server.SetMirrorSettings(true, []string{"/test/local"}, nil, "local") req := httptest.NewRequest("GET", "/test/local", nil) req.Host = upstreamHost // So performMirror targets the mock upstream @@ -70,7 +70,7 @@ func TestMirrorMiddleware_PreferredSource(t *testing.T) { }) t.Run("PreferredUpstream", func(t *testing.T) { - server.SetMirrorSettings(true, []string{"/test/local"}, "upstream") + server.SetMirrorSettings(true, []string{"/test/local"}, nil, "upstream") req := httptest.NewRequest("GET", "/test/local", nil) req.Host = upstreamHost @@ -90,7 +90,7 @@ func TestMirrorMiddleware_PreferredSource(t *testing.T) { }) t.Run("FallbackToLocal", func(t *testing.T) { - server.SetMirrorSettings(true, []string{"/test/local"}, "upstream") + server.SetMirrorSettings(true, []string{"/test/local"}, nil, "upstream") // Use a non-existent host for mirror to trigger failure req := httptest.NewRequest("GET", "/test/local", nil) diff --git a/pkg/service/handlers/mirror_test.go b/pkg/service/handlers/mirror_test.go index 8a33d53..6a687b4 100644 --- a/pkg/service/handlers/mirror_test.go +++ b/pkg/service/handlers/mirror_test.go @@ -45,7 +45,7 @@ func TestMirroring(t *testing.T) { recorder := proxy.NewRecorder(tempDir) server.SetRecorder(recorder) server.SetRecordEnabled(true) - server.SetMirrorSettings(true, []string{"/streaming/account/*/device/*/recent"}, "local") + server.SetMirrorSettings(true, []string{"/streaming/account/*/device/*/recent"}, nil, "local") ts := httptest.NewServer(r) defer ts.Close() @@ -167,7 +167,7 @@ func TestMirroring(t *testing.T) { defer postUpstream.Close() // Setup mirroring for the POST endpoint - server.SetMirrorSettings(true, []string{"/v1/scmudc/*"}, "local") + server.SetMirrorSettings(true, []string{"/v1/scmudc/*"}, nil, "local") requestBody := `{"envelope":{"monoTime":234906,"payloadProtocolVersion":"3.1","payloadType":"scmudc","protocolVersion":"1.0","time":"2026-02-25T23:03:14.976349+00:00","uniqueId":"A81B6A536A98"},"payload":{"deviceInfo":{"boseID":"3230304","deviceID":"A81B6A536A98","deviceType":"SoundTouch 10","serialNumber":"I6332527703739342000020","softwareVersion":"27.0.6.46330.5043500 epdbuild.trunk.hepdswbld04.2022-08-04T11:20:29","systemSerialNumber":"069231P63364828AE"},"events":[{"data":{"play-state":"PAUSE_STATE"},"monoTime":234904,"time":"2026-02-25T23:03:14.973466+00:00","type":"play-state-changed"}]}}` diff --git a/pkg/service/handlers/server.go b/pkg/service/handlers/server.go index 61b6ef1..fd081ea 100644 --- a/pkg/service/handlers/server.go +++ b/pkg/service/handlers/server.go @@ -40,6 +40,7 @@ type Server struct { dnsBindAddr string mirrorEnabled bool mirrorEndpoints []string + skipMirrorEndpoints []string preferredSource string internalPaths []string shortcuts map[string]int @@ -319,12 +320,13 @@ func (s *Server) SetMgmtConfig(username, password string) { } // SetMirrorSettings sets the mirroring settings for the server. -func (s *Server) SetMirrorSettings(enabled bool, endpoints []string, preferredSource string) { +func (s *Server) SetMirrorSettings(enabled bool, endpoints, skipEndpoints []string, preferredSource string) { s.mu.Lock() defer s.mu.Unlock() s.mirrorEnabled = enabled s.mirrorEndpoints = endpoints + s.skipMirrorEndpoints = skipEndpoints s.preferredSource = preferredSource } diff --git a/pkg/service/handlers/snapshot_integrity_test.go b/pkg/service/handlers/snapshot_integrity_test.go index 0c21ed5..996f9d3 100644 --- a/pkg/service/handlers/snapshot_integrity_test.go +++ b/pkg/service/handlers/snapshot_integrity_test.go @@ -27,7 +27,7 @@ func TestSnapshotIntegrity_SelfAndMirror(t *testing.T) { recorder := proxy.NewRecorder(tempDir) s := NewServer(ds, nil, "http://localhost:8000", false, false, true) s.SetRecorder(recorder) - s.SetMirrorSettings(true, []string{"/mirror/*"}, "local") + s.SetMirrorSettings(true, []string{"/mirror/*"}, nil, "local") // Upstream mock upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -39,7 +39,7 @@ func TestSnapshotIntegrity_SelfAndMirror(t *testing.T) { defer upstream.Close() // Configure mirror to point to our mock upstream - s.SetMirrorSettings(true, []string{"/mirror/*"}, "local") + s.SetMirrorSettings(true, []string{"/mirror/*"}, nil, "local") // We need to override the host in performMirror but for tests we can just mock it via env if needed or rely on the fact that performMirror uses r.Host handler := s.SnapshotMiddleware(s.MirrorMiddleware(s.RecordMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { diff --git a/pkg/service/handlers/web/index.html b/pkg/service/handlers/web/index.html index 781ab17..cbaf711 100644 --- a/pkg/service/handlers/web/index.html +++ b/pkg/service/handlers/web/index.html @@ -228,6 +228,13 @@ style="width: 100%; max-width: 600px; margin-top: 5px; font-family: monospace;" placeholder="/streaming/account/*/device/*/recent /accounts/*/devices/*/presets/*" > + +
s.trim()) .filter((s) => s !== ""), + skip_mirror_endpoints: document + .getElementById("skip-mirror-endpoints") + .value.split("\n") + .map((s) => s.trim()) + .filter((s) => s !== ""), internal_paths: document .getElementById("internal-paths") .value.split("\n") diff --git a/pkg/service/setup/setup.go b/pkg/service/setup/setup.go index 2072ab5..2745e51 100644 --- a/pkg/service/setup/setup.go +++ b/pkg/service/setup/setup.go @@ -73,6 +73,8 @@ type MigrationSummary struct { IsMigrated bool `json:"is_migrated"` MirrorEnabled bool `json:"mirror_enabled"` MirrorEndpoints []string `json:"mirror_endpoints,omitempty"` + SkipMirrorEndpoints []string `json:"skip_mirror_endpoints,omitempty"` + PreferredSource string `json:"preferred_source,omitempty"` } // SSHClient defines the interface for SSH operations. @@ -323,6 +325,8 @@ func (m *Manager) GetMigrationSummary(deviceIP, targetURL, proxyURL string, opti if err == nil { summary.MirrorEnabled = settings.MirrorEnabled summary.MirrorEndpoints = settings.MirrorEndpoints + summary.SkipMirrorEndpoints = settings.SkipMirrorEndpoints + summary.PreferredSource = settings.PreferredSource } }