Skip to content

Commit 9b02a59

Browse files
committed
Add support for RemoteCommand option in kitten ssh
1 parent c60ed85 commit 9b02a59

4 files changed

Lines changed: 73 additions & 4 deletions

File tree

kittens/ssh/main.go

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import (
1010
"encoding/json"
1111
"errors"
1212
"fmt"
13-
"github.com/kovidgoyal/kitty"
1413
"io"
1514
"io/fs"
1615
"maps"
@@ -28,6 +27,8 @@ import (
2827
"syscall"
2928
"time"
3029

30+
"github.com/kovidgoyal/kitty"
31+
3132
"github.com/kovidgoyal/go-shm"
3233
"github.com/kovidgoyal/kitty/tools/cli"
3334
"github.com/kovidgoyal/kitty/tools/themes"
@@ -177,6 +178,7 @@ func set_askpass(hostname_for_match, uname string, overrides []string) (need_to_
177178
type connection_data struct {
178179
remote_args []string
179180
host_opts *Config
181+
ssh_config *SSHConfig
180182
hostname_for_match string
181183
username string
182184
echo_on bool
@@ -408,6 +410,18 @@ func prepare_exec_cmd(cd *connection_data) string {
408410
return "unset KITTY_SHELL_INTEGRATION; exec \"$login_shell\" -c '" + strings.Join(args, " ") + "'"
409411
}
410412

413+
func prepare_remote_cmd(cd *connection_data) string {
414+
if cd.ssh_config == nil || cd.ssh_config.RemoteCommand == "" {
415+
return ""
416+
}
417+
418+
remote_command := cd.ssh_config.RemoteCommand
419+
if cd.script_type == "py" {
420+
return base64.RawStdEncoding.EncodeToString(utils.UnsafeStringToBytes(remote_command))
421+
}
422+
return remote_command
423+
}
424+
411425
var data_shm shm.MMap
412426

413427
func prepare_script(script string, replacements map[string]string) string {
@@ -417,6 +431,9 @@ func prepare_script(script string, replacements map[string]string) string {
417431
if _, found := replacements["EXPORT_HOME_CMD"]; !found {
418432
replacements["EXPORT_HOME_CMD"] = ""
419433
}
434+
if _, found := replacements["REMOTE_CMD"]; !found {
435+
replacements["REMOTE_CMD"] = ""
436+
}
420437
keys := utils.Keys(replacements)
421438
for i, key := range keys {
422439
keys[i] = "\\b" + key + "\\b"
@@ -434,6 +451,8 @@ func bootstrap_script(cd *connection_data) (err error) {
434451
if len(cd.remote_args) > 0 {
435452
exec_cmd = prepare_exec_cmd(cd)
436453
}
454+
remote_cmd := prepare_remote_cmd(cd)
455+
437456
pw, err := secrets.TokenHex()
438457
if err != nil {
439458
return err
@@ -467,6 +486,7 @@ func bootstrap_script(cd *connection_data) (err error) {
467486
replacements := map[string]string{
468487
"EXPORT_HOME_CMD": export_home_cmd,
469488
"EXEC_CMD": exec_cmd,
489+
"REMOTE_CMD": remote_cmd,
470490
"TEST_SCRIPT": cd.test_script,
471491
}
472492
add_bool := func(ok bool, key string) {
@@ -600,7 +620,7 @@ func change_colors(color_scheme string) (ans string, err error) {
600620
return
601621
}
602622

603-
func run_ssh(ssh_args, server_args, found_extra_args []string) (rc int, err error) {
623+
func run_ssh(ssh_args, server_args, found_extra_args []string, ssh_config *SSHConfig) (rc int, err error) {
604624
go shell_integration.Data()
605625
go RelevantKittyOpts()
606626
defer func() {
@@ -610,11 +630,14 @@ func run_ssh(ssh_args, server_args, found_extra_args []string) (rc int, err erro
610630
}
611631
}()
612632
cmd := append([]string{SSHExe()}, ssh_args...)
613-
cd := connection_data{remote_args: server_args[1:]}
633+
cd := connection_data{remote_args: server_args[1:], ssh_config: ssh_config}
614634
hostname := server_args[0]
615635
if len(cd.remote_args) == 0 {
616636
cmd = append(cmd, "-t")
617637
}
638+
if cd.ssh_config != nil && cd.ssh_config.RemoteCommand != "" {
639+
cmd = append(cmd, "-o", "RemoteCommand=none")
640+
}
618641
insertion_point := len(cmd)
619642
cmd = append(cmd, "--", hostname)
620643
uname, hostname_for_match := get_destination(hostname)
@@ -833,13 +856,17 @@ func main(cmd *cli.Command, o *Options, args []string) (rc int, err error) {
833856
if passthrough {
834857
return 1, unix.Exec(SSHExe(), utils.Concat([]string{"ssh"}, ssh_args, server_args), os.Environ())
835858
}
859+
ssh_config, err := LoadSSHConfig(server_args[0])
860+
if err != nil {
861+
return 1, err
862+
}
836863
if os.Getenv("KITTY_WINDOW_ID") == "" || os.Getenv("KITTY_PID") == "" {
837864
return 1, fmt.Errorf("The SSH kitten is meant to run inside a kitty window")
838865
}
839866
if !tty.IsTerminal(os.Stdin.Fd()) {
840867
return 1, fmt.Errorf("The SSH kitten is meant for interactive use only, STDIN must be a terminal")
841868
}
842-
return run_ssh(ssh_args, server_args, found_extra_args)
869+
return run_ssh(ssh_args, server_args, found_extra_args, ssh_config)
843870
}
844871

845872
func EntryPoint(parent *cli.Command) {

kittens/ssh/utils.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
package ssh
44

55
import (
6+
"bufio"
67
"bytes"
78
"fmt"
89
"os/exec"
@@ -194,6 +195,40 @@ func ParseSSHArgs(args []string, extra_args ...string) (ssh_args []string, serve
194195
return
195196
}
196197

198+
type SSHConfig struct {
199+
RemoteCommand string
200+
}
201+
202+
func LoadSSHConfig(hostname string) (config *SSHConfig, err error) {
203+
cmd_args := []string{SSHExe(), hostname, "-G"}
204+
cmd := exec.Command(cmd_args[0], cmd_args[1:]...)
205+
var stdout, stderr bytes.Buffer
206+
cmd.Stdout = &stdout
207+
cmd.Stderr = &stderr
208+
_ = cmd.Run()
209+
210+
text := stdout.String()
211+
scanner := bufio.NewScanner(strings.NewReader(text))
212+
213+
config = &SSHConfig{}
214+
for scanner.Scan() {
215+
line := scanner.Text()
216+
i := strings.IndexByte(line, ' ')
217+
if i <= 0 {
218+
continue
219+
}
220+
221+
key, val := line[:i], line[i+1:]
222+
switch key {
223+
case "remotecommand":
224+
if val != "none" {
225+
config.RemoteCommand = val
226+
}
227+
}
228+
}
229+
return config, nil
230+
}
231+
197232
type SSHVersion struct{ Major, Minor int }
198233

199234
func (self SSHVersion) SupportsAskpassRequire() bool {

shell-integration/ssh/bootstrap.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,11 @@ def main():
321321
os.environ.pop('KITTY_SHELL_INTEGRATION', None)
322322
cmd = base64.standard_b64decode(exec_cmd).decode('utf-8')
323323
exec_with_better_error(login_shell, os.path.basename(login_shell), '-c', cmd)
324+
remote_cmd = b'REMOTE_CMD'
325+
if remote_cmd:
326+
os.environ.pop('KITTY_SHELL_INTEGRATION', None)
327+
cmd = base64.standard_b64decode(remote_cmd).decode('utf-8')
328+
exec_with_better_error(login_shell, os.path.basename(login_shell), '-c', cmd)
324329
TEST_SCRIPT # noqa
325330
if ksi and 'no-rc' not in ksi:
326331
exec_with_shell_integration()

shell-integration/ssh/bootstrap.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,8 @@ prepare_for_exec
158158
# If a command was passed to SSH execute it here
159159
EXEC_CMD
160160

161+
REMOTE_CMD
162+
161163
# Used in the tests
162164
TEST_SCRIPT
163165

0 commit comments

Comments
 (0)