Add WSL aliases for Windows SSH and Zed
All checks were successful
Ansible Lint Check / check-ansible (push) Successful in 6s
Nix Format Check / check-format (push) Successful in 51s
Python Lint Check / check-python (push) Successful in 15s

This commit is contained in:
2025-10-23 04:20:15 +02:00
parent fb1661386b
commit 310fb92ec9
2 changed files with 95 additions and 44 deletions

View File

@@ -30,10 +30,10 @@ type LoggingConfig struct {
// SmartAlias represents a smart SSH alias configuration // SmartAlias represents a smart SSH alias configuration
type SmartAlias struct { type SmartAlias struct {
Primary string `yaml:"primary"` // SSH config host to use when local Primary string `yaml:"primary"` // SSH config host to use when local
Fallback string `yaml:"fallback"` // SSH config host to use when remote Fallback string `yaml:"fallback"` // SSH config host to use when remote
CheckHost string `yaml:"check_host"` // IP to ping for connectivity test CheckHost string `yaml:"check_host"` // IP to ping for connectivity test
Timeout string `yaml:"timeout"` // Ping timeout (default: "2s") Timeout string `yaml:"timeout"` // Ping timeout (default: "2s")
} }
// TunnelDefinition represents a tunnel configuration // TunnelDefinition represents a tunnel configuration
@@ -47,36 +47,39 @@ type TunnelDefinition struct {
// TunnelState represents runtime state of an active tunnel // TunnelState represents runtime state of an active tunnel
type TunnelState struct { type TunnelState struct {
Name string `json:"name"` Name string `json:"name"`
Source string `json:"source"` // "config" or "adhoc" Source string `json:"source"` // "config" or "adhoc"
Type string `json:"type"` // local, remote, dynamic Type string `json:"type"` // local, remote, dynamic
LocalPort int `json:"local_port"` LocalPort int `json:"local_port"`
RemoteHost string `json:"remote_host"` RemoteHost string `json:"remote_host"`
RemotePort int `json:"remote_port"` RemotePort int `json:"remote_port"`
SSHHost string `json:"ssh_host"` SSHHost string `json:"ssh_host"`
SSHHostResolved string `json:"ssh_host_resolved"` // After smart alias resolution SSHHostResolved string `json:"ssh_host_resolved"` // After smart alias resolution
PID int `json:"pid"` PID int `json:"pid"`
Status string `json:"status"` Status string `json:"status"`
StartedAt time.Time `json:"started_at"` StartedAt time.Time `json:"started_at"`
LastSeen time.Time `json:"last_seen"` LastSeen time.Time `json:"last_seen"`
CommandLine string `json:"command_line"` CommandLine string `json:"command_line"`
} }
// Config represents the YAML configuration structure // Config represents the YAML configuration structure
type Config struct { type Config struct {
Logging LoggingConfig `yaml:"logging"` Logging LoggingConfig `yaml:"logging"`
SmartAliases map[string]SmartAlias `yaml:"smart_aliases"` SmartAliases map[string]SmartAlias `yaml:"smart_aliases"`
Tunnels map[string]TunnelDefinition `yaml:"tunnels"` Tunnels map[string]TunnelDefinition `yaml:"tunnels"`
} }
const ( const (
realSSHPath = "/usr/bin/ssh" defaultSSHPath = "/usr/bin/ssh"
wslSSHPath = "ssh.exe"
wslDetectPath = "/mnt/c/Windows/System32/cmd.exe"
) )
var ( var (
configDir string configDir string
tunnelsDir string tunnelsDir string
config *Config config *Config
sshPath string // Will be set based on WSL2 detection
// Global flags // Global flags
tunnelMode bool tunnelMode bool
@@ -92,10 +95,10 @@ var (
) )
var rootCmd = &cobra.Command{ var rootCmd = &cobra.Command{
Use: "ssh", Use: "ssh",
Short: "Smart SSH utility with tunnel management", Short: "Smart SSH utility with tunnel management",
Long: "A transparent SSH wrapper that provides smart alias resolution and background tunnel management", Long: "A transparent SSH wrapper that provides smart alias resolution and background tunnel management",
Run: handleSSH, Run: handleSSH,
DisableFlagParsing: true, DisableFlagParsing: true,
} }
@@ -103,13 +106,16 @@ var tunnelCmd = &cobra.Command{
Use: "tunnel [tunnel-name]", Use: "tunnel [tunnel-name]",
Short: "Manage background SSH tunnels", Short: "Manage background SSH tunnels",
Long: "Create, list, and manage persistent SSH tunnels in the background", Long: "Create, list, and manage persistent SSH tunnels in the background",
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
handleTunnelManual(append([]string{"--tunnel"}, args...)) handleTunnelManual(append([]string{"--tunnel"}, args...))
}, },
Args: cobra.MaximumNArgs(1), Args: cobra.MaximumNArgs(1),
} }
func init() { func init() {
// Detect and set SSH path based on environment (WSL2 vs native Linux)
sshPath = detectSSHPath()
// Initialize config directory // Initialize config directory
homeDir, err := os.UserHomeDir() homeDir, err := os.UserHomeDir()
if err != nil { if err != nil {
@@ -141,6 +147,13 @@ func init() {
// Initialize logging // Initialize logging
initLogging(config.Logging) initLogging(config.Logging)
// Log SSH path detection (after logging is initialized)
if isWSL2() {
log.Debug().Str("ssh_path", sshPath).Msg("WSL2 detected, using Windows SSH")
} else {
log.Debug().Str("ssh_path", sshPath).Msg("Native Linux environment, using Linux SSH")
}
// Global flags // Global flags
rootCmd.PersistentFlags().BoolVarP(&tunnelMode, "tunnel", "T", false, "Enable tunnel mode") rootCmd.PersistentFlags().BoolVarP(&tunnelMode, "tunnel", "T", false, "Enable tunnel mode")
rootCmd.Flags().BoolVarP(&tunnelOpen, "open", "O", false, "Open a tunnel") rootCmd.Flags().BoolVarP(&tunnelOpen, "open", "O", false, "Open a tunnel")
@@ -169,6 +182,22 @@ func init() {
} }
} }
// detectSSHPath determines the correct SSH binary path based on the environment
func detectSSHPath() string {
if isWSL2() {
// In WSL2, use Windows SSH
return wslSSHPath
}
// Default to Linux SSH
return defaultSSHPath
}
// isWSL2 checks if we're running in WSL2 by looking for Windows System32
func isWSL2() bool {
_, err := os.Stat(wslDetectPath)
return err == nil
}
func main() { func main() {
// Check if this is a tunnel command first // Check if this is a tunnel command first
args := os.Args[1:] args := os.Args[1:]
@@ -563,7 +592,7 @@ func openTunnel(name string) error {
log.Debug().Strs("command", cmdArgs).Msg("Starting SSH tunnel") log.Debug().Strs("command", cmdArgs).Msg("Starting SSH tunnel")
// Start SSH process // Start SSH process
cmd := exec.Command(realSSHPath, cmdArgs[1:]...) cmd := exec.Command(sshPath, cmdArgs[1:]...)
// Capture stderr to see any SSH errors // Capture stderr to see any SSH errors
var stderr bytes.Buffer var stderr bytes.Buffer
@@ -708,7 +737,9 @@ func createAdhocTunnel() (TunnelDefinition, error) {
} }
func buildSSHCommand(tunnel TunnelDefinition, sshHost string) []string { func buildSSHCommand(tunnel TunnelDefinition, sshHost string) []string {
args := []string{"ssh", "-f", "-N"} // Use the detected SSH path basename for the command
sshBinary := filepath.Base(sshPath)
args := []string{sshBinary, "-f", "-N"}
switch tunnel.Type { switch tunnel.Type {
case "local": case "local":
@@ -1056,18 +1087,37 @@ func findSSHProcessByPort(port int) int {
// executeRealSSH executes the real SSH binary with given arguments // executeRealSSH executes the real SSH binary with given arguments
func executeRealSSH(args []string) { func executeRealSSH(args []string) {
// Check if real SSH exists log.Debug().Str("ssh_path", sshPath).Strs("args", args).Msg("Executing real SSH")
if _, err := os.Stat(realSSHPath); os.IsNotExist(err) {
log.Error().Str("path", realSSHPath).Msg("Real SSH binary not found") // In WSL2, we need to use exec.Command instead of syscall.Exec for Windows binaries
fmt.Fprintf(os.Stderr, "Error: Real SSH binary not found at %s\n", realSSHPath) if isWSL2() {
cmd := exec.Command(sshPath, args...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Run()
if err != nil {
if exitErr, ok := err.(*exec.ExitError); ok {
os.Exit(exitErr.ExitCode())
}
log.Error().Err(err).Msg("Failed to execute SSH")
fmt.Fprintf(os.Stderr, "Error executing SSH: %v\n", err)
os.Exit(1)
}
os.Exit(0)
}
// For native Linux, check if SSH exists
if _, err := os.Stat(sshPath); os.IsNotExist(err) {
log.Error().Str("path", sshPath).Msg("Real SSH binary not found")
fmt.Fprintf(os.Stderr, "Error: Real SSH binary not found at %s\n", sshPath)
os.Exit(1) os.Exit(1)
} }
log.Debug().Str("ssh_path", realSSHPath).Strs("args", args).Msg("Executing real SSH") // Execute the real SSH binary using syscall.Exec (Linux only)
// This replaces the current process (like exec in shell)
// Execute the real SSH binary err := syscall.Exec(sshPath, append([]string{"ssh"}, args...), os.Environ())
// Using syscall.Exec to replace current process (like exec in shell)
err := syscall.Exec(realSSHPath, append([]string{"ssh"}, args...), os.Environ())
if err != nil { if err != nil {
log.Error().Err(err).Msg("Failed to execute SSH") log.Error().Err(err).Msg("Failed to execute SSH")
fmt.Fprintf(os.Stderr, "Error executing SSH: %v\n", err) fmt.Fprintf(os.Stderr, "Error executing SSH: %v\n", err)

View File

@@ -137,6 +137,11 @@
bind -x '"\C-r": fzf_history_search' bind -x '"\C-r": fzf_history_search'
fi fi
# In case this is WSL, let's add various Windows executables as aliases
if [ -f "/mnt/c/Windows/System32/cmd.exe" ]; then
alias ssh-add="ssh-add.exe"
fi
# Display welcome message for interactive shells # Display welcome message for interactive shells
if [ -t 1 ]; then if [ -t 1 ]; then
command -v helloworld &> /dev/null && helloworld command -v helloworld &> /dev/null && helloworld
@@ -190,10 +195,6 @@
# Kubernetes aliases # Kubernetes aliases
"kubectl" = "minikube kubectl --"; "kubectl" = "minikube kubectl --";
# Editor aliases
"zeditor" = "${config.home.homeDirectory}/.local/bin/zed";
"zed" = "${config.home.homeDirectory}/.local/bin/zed";
# SSH alias # SSH alias
"ssh" = "${config.home.homeDirectory}/.local/bin/smart-ssh"; "ssh" = "${config.home.homeDirectory}/.local/bin/smart-ssh";