207 lines
5.1 KiB
Go
207 lines
5.1 KiB
Go
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
|
||
} |