From b86367483f09546700a0ef94b689f682e116ecc9 Mon Sep 17 00:00:00 2001 From: zhaolei Date: Tue, 14 Apr 2026 14:29:18 +0800 Subject: [PATCH] Asynchronously read ssh configuration --- kittens/ssh/main.go | 25 ++++++++++------- kittens/ssh/utils.go | 64 +++++++++++++++++++++++++++++--------------- 2 files changed, 57 insertions(+), 32 deletions(-) diff --git a/kittens/ssh/main.go b/kittens/ssh/main.go index 729ebdd10..f1d9c4892 100644 --- a/kittens/ssh/main.go +++ b/kittens/ssh/main.go @@ -6,6 +6,7 @@ import ( "archive/tar" "bytes" "compress/gzip" + "context" "encoding/base64" "encoding/json" "errors" @@ -620,7 +621,7 @@ func change_colors(color_scheme string) (ans string, err error) { return } -func run_ssh(ssh_args, server_args, found_extra_args []string, ssh_config *SSHConfig) (rc int, err error) { +func run_ssh(ssh_args, server_args, found_extra_args []string, ssh_config_channel <-chan *SSHConfig) (rc int, err error) { go shell_integration.Data() go RelevantKittyOpts() defer func() { @@ -630,14 +631,11 @@ func run_ssh(ssh_args, server_args, found_extra_args []string, ssh_config *SSHCo } }() cmd := append([]string{SSHExe()}, ssh_args...) - cd := connection_data{remote_args: server_args[1:], ssh_config: ssh_config} + cd := connection_data{remote_args: server_args[1:]} hostname := server_args[0] if len(cd.remote_args) == 0 { cmd = append(cmd, "-t") } - if cd.ssh_config != nil && cd.ssh_config.RemoteCommand != "" { - cmd = append(cmd, "-o", "RemoteCommand=none") - } insertion_point := len(cmd) cmd = append(cmd, "--", hostname) uname, hostname_for_match := get_destination(hostname) @@ -780,6 +778,12 @@ func run_ssh(ssh_args, server_args, found_extra_args []string, ssh_config *SSHCo } } defer cleanup() + // Receive ssh config + ssh_config := <-ssh_config_channel + if ssh_config != nil && ssh_config.RemoteCommand != "" { + cmd = slices.Insert(cmd, insertion_point, "-o", "RemoteCommand=none") + } + cd.ssh_config = ssh_config err = get_remote_command(&cd) if err != nil { return 1, err @@ -856,17 +860,18 @@ func main(cmd *cli.Command, o *Options, args []string) (rc int, err error) { if passthrough { return 1, unix.Exec(SSHExe(), utils.Concat([]string{"ssh"}, ssh_args, server_args), os.Environ()) } - ssh_config, err := LoadSSHConfig(server_args[0]) - if err != nil { - return 1, err - } + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + ssh_config_channel := ReadSSHConfig(ctx, server_args[0]) + if os.Getenv("KITTY_WINDOW_ID") == "" || os.Getenv("KITTY_PID") == "" { return 1, fmt.Errorf("The SSH kitten is meant to run inside a kitty window") } if !tty.IsTerminal(os.Stdin.Fd()) { return 1, fmt.Errorf("The SSH kitten is meant for interactive use only, STDIN must be a terminal") } - return run_ssh(ssh_args, server_args, found_extra_args, ssh_config) + return run_ssh(ssh_args, server_args, found_extra_args, ssh_config_channel) } func EntryPoint(parent *cli.Command) { diff --git a/kittens/ssh/utils.go b/kittens/ssh/utils.go index f5c7a89f7..f7bf674af 100644 --- a/kittens/ssh/utils.go +++ b/kittens/ssh/utils.go @@ -5,6 +5,7 @@ package ssh import ( "bufio" "bytes" + "context" "fmt" "os/exec" "regexp" @@ -199,34 +200,53 @@ type SSHConfig struct { RemoteCommand string } -func LoadSSHConfig(hostname string) (config *SSHConfig, err error) { - cmd_args := []string{SSHExe(), hostname, "-G"} - cmd := exec.Command(cmd_args[0], cmd_args[1:]...) - var stdout, stderr bytes.Buffer - cmd.Stdout = &stdout - cmd.Stderr = &stderr - _ = cmd.Run() +// ReadSSHConfig Asynchronously read ssh configuration +func ReadSSHConfig(ctx context.Context, hostname string) <-chan *SSHConfig { + ch := make(chan *SSHConfig, 1) - text := stdout.String() - scanner := bufio.NewScanner(strings.NewReader(text)) + go func() { + defer close(ch) - config = &SSHConfig{} - for scanner.Scan() { - line := scanner.Text() - i := strings.IndexByte(line, ' ') - if i <= 0 { - continue + cmd_args := []string{SSHExe(), hostname, "-G"} + cmd := exec.CommandContext(ctx, cmd_args[0], cmd_args[1:]...) + var stdout, stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr + if err := cmd.Run(); err != nil { + return } - key, val := line[:i], line[i+1:] - switch key { - case "remotecommand": - if val != "none" { - config.RemoteCommand = val + text := stdout.String() + scanner := bufio.NewScanner(strings.NewReader(text)) + + config := &SSHConfig{} + for scanner.Scan() { + select { + case <-ctx.Done(): + return + default: + } + line := scanner.Text() + i := strings.IndexByte(line, ' ') + if i <= 0 { + continue + } + + key, val := line[:i], line[i+1:] + switch key { + case "remotecommand": + if val != "none" { + config.RemoteCommand = val + } } } - } - return config, nil + select { + case <-ctx.Done(): + return + case ch <- config: + } + }() + return ch } type SSHVersion struct{ Major, Minor int }