package cmd import ( "fmt" "os" "os/exec" "path/filepath" "strconv" "strings" "syscall" "github.com/spf13/cobra" ) var debugCmd = &cobra.Command{ Use: "debug", Short: "Run diagnostics on SSH tunnels", Long: `Run diagnostic checks on your SSH tunnel setup, including: - SSH client availability - Tunnel directory integrity - Recorded tunnels status - Active SSH processes verification`, Run: func(cmd *cobra.Command, args []string) { runDebugCommand() }, } func init() { rootCmd.AddCommand(debugCmd) } // runDebugCommand handles the debug subcommand logic func runDebugCommand() { fmt.Println("SSH Tunnel Manager Diagnostics") fmt.Println("==============================") // Check SSH client availability fmt.Println("\n1. Checking SSH client:") checkSSHClient() // Check tunnel directory fmt.Println("\n2. Checking tunnel directory:") checkTunnelDirectory() // Check recorded tunnels fmt.Println("\n3. Checking recorded tunnels:") checkRecordedTunnels() // Check active SSH processes fmt.Println("\n4. Checking active SSH processes:") checkActiveSSHProcesses() } func checkSSHClient() { path, err := exec.LookPath("ssh") if err != nil { fmt.Printf(" ❌ SSH client not found in PATH: %v\n", err) return } fmt.Printf(" ✅ SSH client found at: %s\n", path) // Get SSH version cmd := exec.Command("ssh", "-V") output, err := cmd.CombinedOutput() if err != nil { fmt.Printf(" ⚠️ Could not determine SSH version: %v\n", err) return } fmt.Printf(" ✅ SSH version info: %s\n", strings.TrimSpace(string(output))) } func checkTunnelDirectory() { homeDir, err := os.UserHomeDir() if err != nil { fmt.Printf(" ❌ Could not determine home directory: %v\n", err) return } tunnelPath := filepath.Join(homeDir, tunnelDir) info, err := os.Stat(tunnelPath) if os.IsNotExist(err) { fmt.Printf(" ⚠️ Tunnel directory does not exist: %s\n", tunnelPath) return } else if err != nil { fmt.Printf(" ❌ Error accessing tunnel directory: %v\n", err) return } fmt.Printf(" ✅ Tunnel directory exists: %s\n", tunnelPath) fmt.Printf(" ✅ Permissions: %s\n", info.Mode().String()) entries, err := os.ReadDir(tunnelPath) if err != nil { fmt.Printf(" ❌ Could not read tunnel directory: %v\n", err) return } fmt.Printf(" ✅ Directory contains %d entries\n", len(entries)) } func checkRecordedTunnels() { tunnels, err := getTunnels() if err != nil { fmt.Printf(" ❌ Error reading tunnels: %v\n", err) return } if len(tunnels) == 0 { fmt.Printf(" ℹ️ No recorded tunnels found\n") return } fmt.Printf(" ✅ Found %d recorded tunnels\n", len(tunnels)) for i, t := range tunnels { fmt.Printf("\n Tunnel #%d (ID: %d):\n", i+1, t.ID) fmt.Printf(" Local port: %d\n", t.LocalPort) fmt.Printf(" Remote: %s:%d\n", t.RemoteHost, t.RemotePort) fmt.Printf(" Server: %s\n", t.SSHServer) fmt.Printf(" PID: %d\n", t.PID) // Check if process exists process, err := os.FindProcess(t.PID) if err != nil { fmt.Printf(" ❌ Process not found: %v\n", err) continue } // Try to send signal 0 to check if process exists err = process.Signal(syscall.Signal(0)) if err != nil { fmt.Printf(" ❌ Process not running: %v\n", err) } else { fmt.Printf(" ✅ Process is running\n") // Check if it's actually an SSH process isSSH := checkIfSSHProcess(t.PID) if isSSH { fmt.Printf(" ✅ Process is an SSH process\n") } else { fmt.Printf(" ⚠️ Process is not an SSH process!\n") } } } } func checkActiveSSHProcesses() { // Try using ps to find SSH processes cmd := exec.Command("ps", "-eo", "pid,command") output, err := cmd.Output() if err != nil { fmt.Printf(" ❌ Could not list processes: %v\n", err) return } lines := strings.Split(string(output), "\n") sshProcesses := []string{} for _, line := range lines { if strings.Contains(line, "ssh") && strings.Contains(line, "-L") { sshProcesses = append(sshProcesses, strings.TrimSpace(line)) } } if len(sshProcesses) == 0 { fmt.Printf(" ℹ️ No SSH tunnel processes found\n") return } fmt.Printf(" ✅ Found %d SSH tunnel processes:\n", len(sshProcesses)) for _, proc := range sshProcesses { fmt.Printf(" %s\n", proc) // Extract PID fields := strings.Fields(proc) if len(fields) > 0 { pid, err := strconv.Atoi(fields[0]) if err == nil { // Check if this process is in our records found := false tunnels, _ := getTunnels() for _, t := range tunnels { if t.PID == pid { fmt.Printf(" ✅ This process is tracked as tunnel ID %d\n", t.ID) found = true break } } if !found { fmt.Printf(" ⚠️ This process is not tracked by the tunnel manager\n") } } } } } func verifyTunnelConnectivity(t Tunnel) error { // Try to connect to the local port to verify the tunnel is working cmd := exec.Command("nc", "-z", "-w", "1", "localhost", strconv.Itoa(t.LocalPort)) err := cmd.Run() if err != nil { return fmt.Errorf("could not connect to local port %d: %v", t.LocalPort, err) } return nil }