Add WSL aliases for Windows SSH and Zed
This commit is contained in:
@@ -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)
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user