diff --git a/debug.log b/debug.log new file mode 100644 index 0000000..e69de29 diff --git a/main.go b/main.go index dc55c2a..f49d98e 100644 --- a/main.go +++ b/main.go @@ -12,6 +12,12 @@ func main() { return } + // Check for connection test mode + if len(os.Args) > 2 && os.Args[1] == "--test-connect" { + testConnection(os.Args[2]) + return + } + // Initialize the application app := NewApp() if err := app.Run(); err != nil { @@ -51,4 +57,54 @@ func testMode() { fmt.Println("") fmt.Println("To run the interactive TUI, use: ./kport") fmt.Println("Note: TUI requires a proper terminal environment") + fmt.Println("") + fmt.Println("To test connection to a specific host: ./kport --test-connect ") +} + +// testConnection tests connecting to a specific host +func testConnection(hostName string) { + fmt.Printf("Testing connection to host: %s\n", hostName) + fmt.Println("=====================================") + + // Load SSH config + config := NewSSHConfig() + if err := config.LoadConfig(); err != nil { + fmt.Printf("❌ Failed to load SSH config: %v\n", err) + return + } + + // Find the host + host, err := config.GetHostByName(hostName) + if err != nil { + fmt.Printf("❌ Host not found: %v\n", err) + return + } + + fmt.Printf("Found host configuration:\n") + fmt.Printf(" Name: %s\n", host.Name) + fmt.Printf(" Hostname: %s\n", host.Hostname) + fmt.Printf(" User: %s\n", host.User) + fmt.Printf(" Port: %s\n", host.Port) + if host.Identity != "" { + fmt.Printf(" Identity: %s\n", host.Identity) + } + fmt.Println("") + + // Test port detection + fmt.Println("Testing port detection...") + ports, err := detectRemotePorts(*host) + if err != nil { + fmt.Printf("❌ Port detection failed: %v\n", err) + fmt.Println("") + fmt.Println("This is expected if:") + fmt.Println("- The host is not reachable") + fmt.Println("- SSH keys are not set up") + fmt.Println("- SSH agent is not running") + fmt.Println("- The host doesn't exist") + } else { + fmt.Printf("✅ Port detection successful! Found %d ports: %v\n", len(ports), ports) + } + + fmt.Println("") + fmt.Println("You can still use manual port forwarding in the TUI even if port detection fails.") } \ No newline at end of file diff --git a/port_detection.go b/port_detection.go index e9e0835..dd037f1 100644 --- a/port_detection.go +++ b/port_detection.go @@ -28,10 +28,12 @@ func DetectPorts(host SSHHost) tea.Cmd { return func() tea.Msg { ports, err := detectRemotePorts(host) if err != nil { - // Instead of returning an error that quits the app, - // return an empty ports list with a message + // Log the error for debugging but don't quit the app + fmt.Fprintf(os.Stderr, "Debug: Port detection failed for %s: %v\n", host.Name, err) + // Return empty ports list so user can still use manual port forwarding return PortsDetectedMsg{Ports: []int{}} } + fmt.Fprintf(os.Stderr, "Debug: Detected %d ports on %s: %v\n", len(ports), host.Name, ports) return PortsDetectedMsg{Ports: ports} } } diff --git a/port_forwarder.go b/port_forwarder.go index 512d1ba..b6a865c 100644 --- a/port_forwarder.go +++ b/port_forwarder.go @@ -5,6 +5,7 @@ import ( "io" "net" "os" + "path/filepath" "strconv" "sync" "time" @@ -154,24 +155,32 @@ func (pf *PortForwarder) handleConnection(localConn net.Conn) { // StartPortForwarding starts port forwarding for a specific port func StartPortForwarding(host SSHHost, remotePort int) tea.Cmd { return func() tea.Msg { + fmt.Fprintf(os.Stderr, "Debug: Starting port forwarding for %s:%d\n", host.Name, remotePort) + // Find an available local port localPort, err := findAvailablePort() if err != nil { + fmt.Fprintf(os.Stderr, "Debug: Failed to find available port: %v\n", err) return ErrorMsg{Error: fmt.Errorf("failed to find available local port: %w", err)} } + fmt.Fprintf(os.Stderr, "Debug: Found available local port: %d\n", localPort) // Create SSH client client, err := createSSHClient(host) if err != nil { - return ErrorMsg{Error: fmt.Errorf("failed to create SSH client: %w", err)} + fmt.Fprintf(os.Stderr, "Debug: Failed to create SSH client: %v\n", err) + return ErrorMsg{Error: fmt.Errorf("failed to connect to %s: %w", host.Name, err)} } + fmt.Fprintf(os.Stderr, "Debug: SSH client created successfully\n") // Create and start port forwarder forwarder := NewPortForwarder(client, localPort, remotePort) if err := forwarder.Start(); err != nil { client.Close() + fmt.Fprintf(os.Stderr, "Debug: Failed to start port forwarder: %v\n", err) return ErrorMsg{Error: fmt.Errorf("failed to start port forwarding: %w", err)} } + fmt.Fprintf(os.Stderr, "Debug: Port forwarder started successfully\n") return ForwardingStartedMsg{ LocalPort: localPort, @@ -183,33 +192,43 @@ func StartPortForwarding(host SSHHost, remotePort int) tea.Cmd { // StartManualPortForwarding starts port forwarding for a manually entered port func StartManualPortForwarding(host SSHHost, portStr string) tea.Cmd { return func() tea.Msg { + fmt.Fprintf(os.Stderr, "Debug: Manual port forwarding requested for %s:%s\n", host.Name, portStr) + remotePort, err := strconv.Atoi(portStr) if err != nil { + fmt.Fprintf(os.Stderr, "Debug: Invalid port number: %s\n", portStr) return ErrorMsg{Error: fmt.Errorf("invalid port number: %s", portStr)} } if remotePort <= 0 || remotePort > 65535 { + fmt.Fprintf(os.Stderr, "Debug: Port number out of range: %d\n", remotePort) return ErrorMsg{Error: fmt.Errorf("port number must be between 1 and 65535")} } // Find an available local port localPort, err := findAvailablePort() if err != nil { + fmt.Fprintf(os.Stderr, "Debug: Failed to find available port: %v\n", err) return ErrorMsg{Error: fmt.Errorf("failed to find available local port: %w", err)} } + fmt.Fprintf(os.Stderr, "Debug: Found available local port: %d\n", localPort) // Create SSH client client, err := createSSHClient(host) if err != nil { - return ErrorMsg{Error: fmt.Errorf("failed to create SSH client: %w", err)} + fmt.Fprintf(os.Stderr, "Debug: Failed to create SSH client: %v\n", err) + return ErrorMsg{Error: fmt.Errorf("failed to connect to %s: %w", host.Name, err)} } + fmt.Fprintf(os.Stderr, "Debug: SSH client created successfully\n") // Create and start port forwarder forwarder := NewPortForwarder(client, localPort, remotePort) if err := forwarder.Start(); err != nil { client.Close() + fmt.Fprintf(os.Stderr, "Debug: Failed to start port forwarder: %v\n", err) return ErrorMsg{Error: fmt.Errorf("failed to start port forwarding: %w", err)} } + fmt.Fprintf(os.Stderr, "Debug: Port forwarder started successfully\n") return ForwardingStartedMsg{ LocalPort: localPort, @@ -224,29 +243,58 @@ func createSSHClient(host SSHHost) (*ssh.Client, error) { User: host.User, Auth: []ssh.AuthMethod{}, HostKeyCallback: ssh.InsecureIgnoreHostKey(), // In production, use proper host key verification - Timeout: 5 * time.Second, // Shorter timeout + Timeout: 10 * time.Second, // Longer timeout for better reliability } // Add key-based authentication if identity file is specified if host.Identity != "" { + fmt.Fprintf(os.Stderr, "Debug: Trying identity file: %s\n", host.Identity) key, err := loadPrivateKey(host.Identity) if err == nil { config.Auth = append(config.Auth, ssh.PublicKeys(key)) + fmt.Fprintf(os.Stderr, "Debug: Added key-based auth\n") + } else { + fmt.Fprintf(os.Stderr, "Debug: Failed to load identity file: %v\n", err) } } // Add SSH agent authentication if agentAuth, err := sshAgentAuth(); err == nil { config.Auth = append(config.Auth, agentAuth) + fmt.Fprintf(os.Stderr, "Debug: Added SSH agent auth\n") + } else { + fmt.Fprintf(os.Stderr, "Debug: SSH agent not available: %v\n", err) + } + + // Try to load default SSH keys if no specific identity is set + if host.Identity == "" { + defaultKeys := []string{"id_rsa", "id_ecdsa", "id_ed25519"} + homeDir, err := os.UserHomeDir() + if err == nil { + for _, keyName := range defaultKeys { + keyPath := filepath.Join(homeDir, ".ssh", keyName) + if key, err := loadPrivateKey(keyPath); err == nil { + config.Auth = append(config.Auth, ssh.PublicKeys(key)) + fmt.Fprintf(os.Stderr, "Debug: Added default key: %s\n", keyName) + } + } + } + } + + // If no auth methods available, provide helpful error + if len(config.Auth) == 0 { + return nil, fmt.Errorf("no SSH authentication methods available - please set up SSH keys or SSH agent") } // Connect to the remote host addr := net.JoinHostPort(host.Hostname, host.Port) + fmt.Fprintf(os.Stderr, "Debug: Connecting to %s\n", addr) client, err := ssh.Dial("tcp", addr, config) if err != nil { - return nil, fmt.Errorf("failed to connect to %s: %w", host.Name, err) + return nil, fmt.Errorf("failed to connect to %s (%s): %w", host.Name, addr, err) } + fmt.Fprintf(os.Stderr, "Debug: Successfully connected to %s\n", host.Name) return client, nil } diff --git a/tui.go b/tui.go index fd69947..8716b9d 100644 --- a/tui.go +++ b/tui.go @@ -16,6 +16,7 @@ const ( StateConnecting StateSelectPort StateManualPort + StateStartingForward StateForwarding ) @@ -75,6 +76,8 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m.updatePortSelection(msg) case StateManualPort: return m.updateManualPort(msg) + case StateStartingForward: + return m.updateStartingForward(msg) case StateForwarding: return m.updateForwarding(msg) } @@ -95,8 +98,22 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.state = StateForwarding return m, nil case ErrorMsg: - m.err = msg.Error - return m, tea.Quit + // Don't quit on errors, just show them and let user continue + m.message = fmt.Sprintf("Error: %v", msg.Error) + // Go back to appropriate state depending on current state + switch m.state { + case StateConnecting: + m.state = StateSelectPort + m.ports = []int{} // Show empty ports list + case StateStartingForward: + // Go back to port selection or manual port depending on where we came from + if len(m.ports) > 0 { + m.state = StateSelectPort + } else { + m.state = StateManualPort + } + } + return m, nil } return m, nil } @@ -163,6 +180,8 @@ func (m *Model) updatePortSelection(msg tea.KeyMsg) (tea.Model, tea.Cmd) { } case "enter", " ": m.selectedPort = m.cursor + m.state = StateStartingForward + m.message = "Starting port forwarding..." // Start port forwarding return m, StartPortForwarding(m.hosts[m.selectedHost], m.ports[m.selectedPort]) case "m": @@ -188,6 +207,8 @@ func (m *Model) updateManualPort(msg tea.KeyMsg) (tea.Model, tea.Cmd) { return m, nil case "enter": if m.manualPort != "" { + m.state = StateStartingForward + m.message = "Starting port forwarding..." // Parse and start manual port forwarding return m, StartManualPortForwarding(m.hosts[m.selectedHost], m.manualPort) } @@ -204,6 +225,20 @@ func (m *Model) updateManualPort(msg tea.KeyMsg) (tea.Model, tea.Cmd) { return m, nil } +// updateStartingForward handles the starting forward state +func (m *Model) updateStartingForward(msg tea.KeyMsg) (tea.Model, tea.Cmd) { + switch msg.String() { + case "ctrl+c", "q": + return m, tea.Quit + case "esc": + // Cancel the forwarding attempt + m.state = StateSelectPort + m.message = "" + return m, nil + } + return m, nil +} + // updateForwarding handles forwarding state func (m *Model) updateForwarding(msg tea.KeyMsg) (tea.Model, tea.Cmd) { switch msg.String() { @@ -256,6 +291,8 @@ func (m *Model) View() string { s.WriteString(m.renderPortSelection()) case StateManualPort: s.WriteString(m.renderManualPort()) + case StateStartingForward: + s.WriteString(m.renderStartingForward()) case StateForwarding: s.WriteString(m.renderForwarding()) } @@ -408,6 +445,23 @@ func (m *Model) renderManualPort() string { return s.String() } +// renderStartingForward renders the starting forward view +func (m *Model) renderStartingForward() string { + var s strings.Builder + + startingStyle := lipgloss.NewStyle(). + Foreground(lipgloss.Color("#00BFFF")). + Bold(true) + + s.WriteString(startingStyle.Render("🚀 " + m.message)) + s.WriteString("\n\n") + s.WriteString("Setting up SSH tunnel and port forwarding...\n\n") + s.WriteString("Controls:\n") + s.WriteString(" Esc: Cancel q: Quit\n") + + return s.String() +} + // renderForwarding renders the forwarding status view func (m *Model) renderForwarding() string { var s strings.Builder