fix(setup): prevent and clean up corrupted rc.local with cat error message

This commit is contained in:
Tobias Gesellchen
2026-02-16 18:20:16 +01:00
parent 7337296ae9
commit 95f5e9c831
2 changed files with 140 additions and 28 deletions
+46 -28
View File
@@ -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)
}
}
}
+94
View File
@@ -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)
}