diff --git a/cmd/soundtouch-service/main.go b/cmd/soundtouch-service/main.go index 7ecd1a5..34a6f39 100644 --- a/cmd/soundtouch-service/main.go +++ b/cmd/soundtouch-service/main.go @@ -165,6 +165,7 @@ func main() { r.Get("/summary/{deviceIP}", server.HandleGetMigrationSummary) r.Post("/migrate/{deviceIP}", server.HandleMigrateDevice) r.Post("/ensure-remote-services/{deviceIP}", server.HandleEnsureRemoteServices) + r.Post("/remove-remote-services/{deviceIP}", server.HandleRemoveRemoteServices) r.Post("/backup/{deviceIP}", server.HandleBackupConfig) r.Get("/proxy-settings", server.HandleGetProxySettings) r.Post("/proxy-settings", server.HandleUpdateProxySettings) diff --git a/pkg/service/handlers/handlers_setup.go b/pkg/service/handlers/handlers_setup.go index 518ecd3..ef01f95 100644 --- a/pkg/service/handlers/handlers_setup.go +++ b/pkg/service/handlers/handlers_setup.go @@ -190,6 +190,41 @@ func (s *Server) HandleEnsureRemoteServices(w http.ResponseWriter, r *http.Reque } } +// HandleRemoveRemoteServices removes remote services configuration from a device. +func (s *Server) HandleRemoveRemoteServices(w http.ResponseWriter, r *http.Request) { + deviceIP := chi.URLParam(r, "deviceIP") + if deviceIP == "" { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusBadRequest) + + if err := json.NewEncoder(w).Encode(map[string]interface{}{"ok": false, "message": "Device IP is required"}); err != nil { + http.Error(w, "Failed to encode response", http.StatusInternalServerError) + return + } + + return + } + + if err := s.sm.RemoveRemoteServices(deviceIP); err != nil { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusInternalServerError) + + if encodeErr := json.NewEncoder(w).Encode(map[string]interface{}{"ok": false, "message": err.Error()}); encodeErr != nil { + http.Error(w, "Failed to encode response", http.StatusInternalServerError) + return + } + + return + } + + w.Header().Set("Content-Type", "application/json") + + if err := json.NewEncoder(w).Encode(map[string]interface{}{"ok": true, "message": "Remote services removed"}); err != nil { + http.Error(w, "Failed to encode response", http.StatusInternalServerError) + return + } +} + // HandleBackupConfig creates a backup of the device configuration. func (s *Server) HandleBackupConfig(w http.ResponseWriter, r *http.Request) { deviceIP := chi.URLParam(r, "deviceIP") diff --git a/pkg/service/handlers/index.html b/pkg/service/handlers/index.html index cc088db..f481def 100644 --- a/pkg/service/handlers/index.html +++ b/pkg/service/handlers/index.html @@ -119,7 +119,8 @@
- + +
@@ -346,6 +347,10 @@ remoteBtn.onclick = () => ensureRemoteServices(ip); remoteBtn.disabled = !summary.ssh_success; + const removeRemoteBtn = document.getElementById('remove-remote-btn'); + removeRemoteBtn.onclick = () => removeRemoteServices(ip); + removeRemoteBtn.disabled = !summary.ssh_success || !summary.remote_services_enabled; + const backupBtn = document.getElementById('backup-config-btn'); backupBtn.onclick = () => backupConfig(ip); backupBtn.disabled = !summary.ssh_success || !!summary.original_config; @@ -438,6 +443,38 @@ } } + async function removeRemoteServices(ip) { + if (!ip) { + alert('Please enter a valid IP address.'); + return; + } + if (!confirm('Are you sure you want to remove remote services from ' + ip + '?')) { + return; + } + const summaryDiv = document.getElementById('migration-summary'); + summaryDiv.style.display = 'none'; + + const statusDiv = document.getElementById('status'); + statusDiv.style.display = 'block'; + statusDiv.style.backgroundColor = '#ffffcc'; + statusDiv.innerHTML = 'Removing remote services for ' + ip + '...'; + + try { + const response = await fetch('/setup/remove-remote-services/' + ip, { method: 'POST' }); + const result = await response.json(); + if (result.ok) { + statusDiv.style.backgroundColor = '#ccffcc'; + statusDiv.innerHTML = 'Successfully removed remote services from ' + ip + '.'; + } else { + statusDiv.style.backgroundColor = '#ffcccc'; + statusDiv.innerHTML = 'Failed to remove remote services for ' + ip + ': ' + (result.message || 'Unknown error'); + } + } catch (error) { + statusDiv.style.backgroundColor = '#ffcccc'; + statusDiv.innerHTML = 'Error removing remote services for ' + ip + ': ' + error; + } + } + async function backupConfig(ip) { if (!ip) { alert('Please enter a valid IP address.'); diff --git a/pkg/service/handlers/main_test.go b/pkg/service/handlers/main_test.go index d84a0aa..0db217a 100644 --- a/pkg/service/handlers/main_test.go +++ b/pkg/service/handlers/main_test.go @@ -48,6 +48,8 @@ func setupRouter(targetURL string, ds *datastore.DataStore) (*chi.Mux, *Server) r.Route("/setup", func(r chi.Router) { r.Get("/proxy-settings", server.HandleGetProxySettings) r.Post("/proxy-settings", server.HandleUpdateProxySettings) + r.Post("/ensure-remote-services/{deviceIP}", server.HandleEnsureRemoteServices) + r.Post("/remove-remote-services/{deviceIP}", server.HandleRemoveRemoteServices) }) r.NotFound(func(w http.ResponseWriter, r *http.Request) { diff --git a/pkg/service/setup/setup.go b/pkg/service/setup/setup.go index f32f750..ec9ef35 100644 --- a/pkg/service/setup/setup.go +++ b/pkg/service/setup/setup.go @@ -449,3 +449,35 @@ func (m *Manager) EnsureRemoteServices(deviceIP string) error { return fmt.Errorf("failed to enable remote services in any of the locations: %v", locations) } + +// RemoveRemoteServices removes remote services from the device by deleting the known remote_services files. +func (m *Manager) RemoveRemoteServices(deviceIP string) error { + client := ssh.NewClient(deviceIP) + rwCmd := "(rw || mount -o remount,rw /)" + + locations := []string{ + "/etc/remote_services", + "/mnt/nv/remote_services", + "/tmp/remote_services", + } + + var errors []error + + for _, loc := range locations { + // Try to make filesystem writable and remove the file + _, err := client.Run(fmt.Sprintf("%s && rm -f %s", rwCmd, loc)) + if err != nil { + // If rw && rm failed, try just rm (e.g. for /tmp) + _, err = client.Run(fmt.Sprintf("rm -f %s", loc)) + if err != nil { + errors = append(errors, fmt.Errorf("failed to remove %s: %w", loc, err)) + } + } + } + + if len(errors) == len(locations) { + return fmt.Errorf("failed to remove remote services from any location: %v", errors) + } + + return nil +}