diff --git a/pkg/service/setup/setup.go b/pkg/service/setup/setup.go index a26e529..5b788dd 100644 --- a/pkg/service/setup/setup.go +++ b/pkg/service/setup/setup.go @@ -1097,7 +1097,10 @@ func (m *Manager) migrateViaResolvConf(deviceIP, targetURL string) (string, erro hookMarker := "/mnt/nv/aftertouch.resolv.conf" // Check if rc.local exists and read it - currentRcLocal, _ := client.Run(fmt.Sprintf("cat %s", rcLocalPath)) + currentRcLocal, rcErr := client.Run(fmt.Sprintf("cat %s", rcLocalPath)) + if rcErr != nil { + currentRcLocal = "" + } patchLogic := fmt.Sprintf(` # Aftertouch DNS hook: prioritizes our custom nameserver if it exists @@ -1109,6 +1112,11 @@ fi if !strings.Contains(currentRcLocal, hookMarker) { newRcLocal := currentRcLocal + // Remove "cat: can't open..." error message if it was accidentally saved in the file + if strings.Contains(newRcLocal, "cat: can't open") { + newRcLocal = "" + } + if !strings.HasPrefix(newRcLocal, "#!/bin/sh") { newRcLocal = "#!/bin/sh\n" + strings.TrimPrefix(newRcLocal, "#!/bin/sh") } @@ -1270,36 +1278,46 @@ func (m *Manager) revertAftertouchHook(client SSHClient, rwCmd string) string { _, _ = client.Run(fmt.Sprintf("rm %s", aftertouchConfPath)) } - if currentRcLocal, err := client.Run(fmt.Sprintf("cat %s", rcLocalPath)); err == nil && (strings.Contains(currentRcLocal, aftertouchConfPath) || strings.Contains(currentRcLocal, "# Aftertouch DNS hook")) { - logs += fmt.Sprintf("Removing Aftertouch hook logic from %s\n", rcLocalPath) - fmt.Printf("Removing Aftertouch hook logic from %s\n", rcLocalPath) + if currentRcLocal, err := client.Run(fmt.Sprintf("cat %s", rcLocalPath)); err == nil { + // Remove "cat: can't open..." error message if it was accidentally saved in the file + if strings.Contains(currentRcLocal, "cat: can't open") { + logs += fmt.Sprintf("Removing corrupted %s\n", rcLocalPath) + _, _ = client.Run(fmt.Sprintf("rm %s", rcLocalPath)) - // Simple removal: filter out lines between the marker and the 'fi' - lines := strings.Split(currentRcLocal, "\n") - - var newLines []string - - skip := false - - for _, line := range lines { - if strings.Contains(line, "# Aftertouch DNS hook") { - skip = true - continue - } - - if skip && strings.TrimSpace(line) == "fi" { - skip = false - continue - } - - if !skip { - newLines = append(newLines, line) - } + return logs } - newRcLocal := strings.Join(newLines, "\n") - if err := client.UploadContent([]byte(newRcLocal), rcLocalPath); err != nil { - fmt.Printf("Warning: failed to update %s: %v\n", rcLocalPath, err) + if strings.Contains(currentRcLocal, aftertouchConfPath) || strings.Contains(currentRcLocal, "# Aftertouch DNS hook") { + logs += fmt.Sprintf("Removing Aftertouch hook logic from %s\n", rcLocalPath) + fmt.Printf("Removing Aftertouch hook logic from %s\n", rcLocalPath) + + // Simple removal: filter out lines between the marker and the 'fi' + lines := strings.Split(currentRcLocal, "\n") + + var newLines []string + + skip := false + + for _, line := range lines { + if strings.Contains(line, "# Aftertouch DNS hook") { + skip = true + continue + } + + if skip && strings.TrimSpace(line) == "fi" { + skip = false + continue + } + + if !skip { + newLines = append(newLines, line) + } + } + + newRcLocal := strings.Join(newLines, "\n") + if err := client.UploadContent([]byte(newRcLocal), rcLocalPath); err != nil { + fmt.Printf("Warning: failed to update %s: %v\n", rcLocalPath, err) + } } } diff --git a/pkg/service/setup/setup_test.go b/pkg/service/setup/setup_test.go index e20eec8..76e08ef 100644 --- a/pkg/service/setup/setup_test.go +++ b/pkg/service/setup/setup_test.go @@ -808,6 +808,47 @@ func TestRevertMigration(t *testing.T) { } } +func TestRevertMigration_CorruptedRcLocal(t *testing.T) { + m := NewManager("http://localhost:8000", nil, nil) + + runCalls := []string{} + m.NewSSH = func(host string) SSHClient { + return &mockSSH{ + runFunc: func(command string) (string, error) { + runCalls = append(runCalls, command) + if command == "cat /mnt/nv/rc.local" { + return "cat: can't open '/mnt/nv/rc.local': No such file or directory", nil + } + if strings.HasPrefix(command, "[ -f") { + if strings.Contains(command, ".original") { + if strings.Contains(command, "SoundTouchSdkPrivateCfg.xml") { + return "", nil // Pretend XML backup exists to satisfy RevertMigration + } + return "", fmt.Errorf("not found") + } + } + return "", nil + }, + } + } + + _, err := m.RevertMigration("192.168.1.10") + if err != nil { + t.Fatalf("RevertMigration failed: %v", err) + } + + foundRmRcLocal := false + for _, call := range runCalls { + if call == "rm /mnt/nv/rc.local" { + foundRmRcLocal = true + break + } + } + if !foundRmRcLocal { + t.Errorf("Expected corrupted rc.local to be removed") + } +} + func TestRevertMigration_NoBackup(t *testing.T) { m := NewManager("http://localhost:8000", nil, nil) @@ -1113,6 +1154,59 @@ func TestMigrateViaResolvConf(t *testing.T) { } } +func TestMigrateViaResolvConf_CorruptedRcLocal(t *testing.T) { + tempDir, err := os.MkdirTemp("", "setup-test-resolv-corrupted") + if err != nil { + t.Fatalf("Failed to create temp dir: %v", err) + } + defer os.RemoveAll(tempDir) + + cm := certmanager.NewCertificateManager(filepath.Join(tempDir, "certs")) + if err := cm.EnsureCA(); err != nil { + t.Fatalf("Failed to ensure CA: %v", err) + } + + m := NewManager("http://192.168.1.100:8000", nil, cm) + + uploads := make(map[string]string) + + m.NewSSH = func(host string) SSHClient { + return &mockSSH{ + runFunc: func(command string) (string, error) { + if command == "cat /mnt/nv/rc.local" { + // Simulate corrupted file containing error message + return "cat: can't open '/mnt/nv/rc.local': No such file or directory", nil + } + if strings.HasPrefix(command, "[ -f") { + return "", fmt.Errorf("file not found") + } + return "", nil + }, + uploadContentFunc: func(content []byte, remotePath string) error { + uploads[remotePath] = string(content) + return nil + }, + } + } + + _, err = m.migrateViaResolvConf("192.168.1.10", "http://192.168.1.100:8000") + if err != nil { + t.Fatalf("migrateViaResolvConf failed: %v", err) + } + + // Verify uploads - rc.local should have been sanitized and only contain shebang and hook + rcLocal := uploads["/mnt/nv/rc.local"] + if strings.Contains(rcLocal, "cat: can't open") { + t.Errorf("rc.local still contains corrupted content: %s", rcLocal) + } + if !strings.HasPrefix(rcLocal, "#!/bin/sh") { + t.Errorf("rc.local missing shebang: %s", rcLocal) + } + if !strings.Contains(rcLocal, "/mnt/nv/aftertouch.resolv.conf") { + t.Errorf("rc.local missing hook logic: %s", rcLocal) + } +} + func contains(s, substr string) bool { return strings.Contains(s, substr) }