Asynchronously read ssh configuration

This commit is contained in:
zhaolei
2026-04-14 14:29:18 +08:00
parent 9b02a59c4e
commit b86367483f
2 changed files with 57 additions and 32 deletions

View File

@@ -6,6 +6,7 @@ import (
"archive/tar" "archive/tar"
"bytes" "bytes"
"compress/gzip" "compress/gzip"
"context"
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"errors" "errors"
@@ -620,7 +621,7 @@ func change_colors(color_scheme string) (ans string, err error) {
return 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 shell_integration.Data()
go RelevantKittyOpts() go RelevantKittyOpts()
defer func() { 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...) 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] hostname := server_args[0]
if len(cd.remote_args) == 0 { if len(cd.remote_args) == 0 {
cmd = append(cmd, "-t") cmd = append(cmd, "-t")
} }
if cd.ssh_config != nil && cd.ssh_config.RemoteCommand != "" {
cmd = append(cmd, "-o", "RemoteCommand=none")
}
insertion_point := len(cmd) insertion_point := len(cmd)
cmd = append(cmd, "--", hostname) cmd = append(cmd, "--", hostname)
uname, hostname_for_match := get_destination(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() 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) err = get_remote_command(&cd)
if err != nil { if err != nil {
return 1, err return 1, err
@@ -856,17 +860,18 @@ func main(cmd *cli.Command, o *Options, args []string) (rc int, err error) {
if passthrough { if passthrough {
return 1, unix.Exec(SSHExe(), utils.Concat([]string{"ssh"}, ssh_args, server_args), os.Environ()) 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 { ctx, cancel := context.WithCancel(context.Background())
return 1, err defer cancel()
} ssh_config_channel := ReadSSHConfig(ctx, server_args[0])
if os.Getenv("KITTY_WINDOW_ID") == "" || os.Getenv("KITTY_PID") == "" { 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") return 1, fmt.Errorf("The SSH kitten is meant to run inside a kitty window")
} }
if !tty.IsTerminal(os.Stdin.Fd()) { 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 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) { func EntryPoint(parent *cli.Command) {

View File

@@ -5,6 +5,7 @@ package ssh
import ( import (
"bufio" "bufio"
"bytes" "bytes"
"context"
"fmt" "fmt"
"os/exec" "os/exec"
"regexp" "regexp"
@@ -199,34 +200,53 @@ type SSHConfig struct {
RemoteCommand string RemoteCommand string
} }
func LoadSSHConfig(hostname string) (config *SSHConfig, err error) { // ReadSSHConfig Asynchronously read ssh configuration
cmd_args := []string{SSHExe(), hostname, "-G"} func ReadSSHConfig(ctx context.Context, hostname string) <-chan *SSHConfig {
cmd := exec.Command(cmd_args[0], cmd_args[1:]...) ch := make(chan *SSHConfig, 1)
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
_ = cmd.Run()
text := stdout.String() go func() {
scanner := bufio.NewScanner(strings.NewReader(text)) defer close(ch)
config = &SSHConfig{} cmd_args := []string{SSHExe(), hostname, "-G"}
for scanner.Scan() { cmd := exec.CommandContext(ctx, cmd_args[0], cmd_args[1:]...)
line := scanner.Text() var stdout, stderr bytes.Buffer
i := strings.IndexByte(line, ' ') cmd.Stdout = &stdout
if i <= 0 { cmd.Stderr = &stderr
continue if err := cmd.Run(); err != nil {
return
} }
key, val := line[:i], line[i+1:] text := stdout.String()
switch key { scanner := bufio.NewScanner(strings.NewReader(text))
case "remotecommand":
if val != "none" { config := &SSHConfig{}
config.RemoteCommand = val 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
}
} }
} }
} select {
return config, nil case <-ctx.Done():
return
case ch <- config:
}
}()
return ch
} }
type SSHVersion struct{ Major, Minor int } type SSHVersion struct{ Major, Minor int }