Compare commits
2 Commits
70307c7cba
...
02322c4a61
Author | SHA1 | Date | |
---|---|---|---|
02322c4a61 | |||
bde1529248 |
56
main.go
56
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 <hostname>")
|
||||
}
|
||||
|
||||
// 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.")
|
||||
}
|
@@ -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}
|
||||
}
|
||||
}
|
||||
|
@@ -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
|
||||
}
|
||||
|
||||
|
58
tui.go
58
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
|
||||
|
Reference in New Issue
Block a user