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
+}