mirror of
https://github.com/kovidgoyal/kitty
synced 2026-07-27 18:51:41 +02:00
Add support for RemoteCommand option in kitten ssh
This commit is contained in:
@@ -10,7 +10,6 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/kovidgoyal/kitty"
|
|
||||||
"io"
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"maps"
|
"maps"
|
||||||
@@ -28,6 +27,8 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/kovidgoyal/kitty"
|
||||||
|
|
||||||
"github.com/kovidgoyal/go-shm"
|
"github.com/kovidgoyal/go-shm"
|
||||||
"github.com/kovidgoyal/kitty/tools/cli"
|
"github.com/kovidgoyal/kitty/tools/cli"
|
||||||
"github.com/kovidgoyal/kitty/tools/themes"
|
"github.com/kovidgoyal/kitty/tools/themes"
|
||||||
@@ -177,6 +178,7 @@ func set_askpass(hostname_for_match, uname string, overrides []string) (need_to_
|
|||||||
type connection_data struct {
|
type connection_data struct {
|
||||||
remote_args []string
|
remote_args []string
|
||||||
host_opts *Config
|
host_opts *Config
|
||||||
|
ssh_config *SSHConfig
|
||||||
hostname_for_match string
|
hostname_for_match string
|
||||||
username string
|
username string
|
||||||
echo_on bool
|
echo_on bool
|
||||||
@@ -408,6 +410,18 @@ func prepare_exec_cmd(cd *connection_data) string {
|
|||||||
return "unset KITTY_SHELL_INTEGRATION; exec \"$login_shell\" -c '" + strings.Join(args, " ") + "'"
|
return "unset KITTY_SHELL_INTEGRATION; exec \"$login_shell\" -c '" + strings.Join(args, " ") + "'"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func prepare_remote_cmd(cd *connection_data) string {
|
||||||
|
if cd.ssh_config == nil || cd.ssh_config.RemoteCommand == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
remote_command := cd.ssh_config.RemoteCommand
|
||||||
|
if cd.script_type == "py" {
|
||||||
|
return base64.RawStdEncoding.EncodeToString(utils.UnsafeStringToBytes(remote_command))
|
||||||
|
}
|
||||||
|
return remote_command
|
||||||
|
}
|
||||||
|
|
||||||
var data_shm shm.MMap
|
var data_shm shm.MMap
|
||||||
|
|
||||||
func prepare_script(script string, replacements map[string]string) string {
|
func prepare_script(script string, replacements map[string]string) string {
|
||||||
@@ -417,6 +431,9 @@ func prepare_script(script string, replacements map[string]string) string {
|
|||||||
if _, found := replacements["EXPORT_HOME_CMD"]; !found {
|
if _, found := replacements["EXPORT_HOME_CMD"]; !found {
|
||||||
replacements["EXPORT_HOME_CMD"] = ""
|
replacements["EXPORT_HOME_CMD"] = ""
|
||||||
}
|
}
|
||||||
|
if _, found := replacements["REMOTE_CMD"]; !found {
|
||||||
|
replacements["REMOTE_CMD"] = ""
|
||||||
|
}
|
||||||
keys := utils.Keys(replacements)
|
keys := utils.Keys(replacements)
|
||||||
for i, key := range keys {
|
for i, key := range keys {
|
||||||
keys[i] = "\\b" + key + "\\b"
|
keys[i] = "\\b" + key + "\\b"
|
||||||
@@ -434,6 +451,8 @@ func bootstrap_script(cd *connection_data) (err error) {
|
|||||||
if len(cd.remote_args) > 0 {
|
if len(cd.remote_args) > 0 {
|
||||||
exec_cmd = prepare_exec_cmd(cd)
|
exec_cmd = prepare_exec_cmd(cd)
|
||||||
}
|
}
|
||||||
|
remote_cmd := prepare_remote_cmd(cd)
|
||||||
|
|
||||||
pw, err := secrets.TokenHex()
|
pw, err := secrets.TokenHex()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -467,6 +486,7 @@ func bootstrap_script(cd *connection_data) (err error) {
|
|||||||
replacements := map[string]string{
|
replacements := map[string]string{
|
||||||
"EXPORT_HOME_CMD": export_home_cmd,
|
"EXPORT_HOME_CMD": export_home_cmd,
|
||||||
"EXEC_CMD": exec_cmd,
|
"EXEC_CMD": exec_cmd,
|
||||||
|
"REMOTE_CMD": remote_cmd,
|
||||||
"TEST_SCRIPT": cd.test_script,
|
"TEST_SCRIPT": cd.test_script,
|
||||||
}
|
}
|
||||||
add_bool := func(ok bool, key string) {
|
add_bool := func(ok bool, key string) {
|
||||||
@@ -600,7 +620,7 @@ func change_colors(color_scheme string) (ans string, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func run_ssh(ssh_args, server_args, found_extra_args []string) (rc int, err error) {
|
func run_ssh(ssh_args, server_args, found_extra_args []string, ssh_config *SSHConfig) (rc int, err error) {
|
||||||
go shell_integration.Data()
|
go shell_integration.Data()
|
||||||
go RelevantKittyOpts()
|
go RelevantKittyOpts()
|
||||||
defer func() {
|
defer func() {
|
||||||
@@ -610,11 +630,14 @@ func run_ssh(ssh_args, server_args, found_extra_args []string) (rc int, err erro
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
cmd := append([]string{SSHExe()}, ssh_args...)
|
cmd := append([]string{SSHExe()}, ssh_args...)
|
||||||
cd := connection_data{remote_args: server_args[1:]}
|
cd := connection_data{remote_args: server_args[1:], ssh_config: ssh_config}
|
||||||
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)
|
||||||
@@ -833,13 +856,17 @@ 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 {
|
||||||
|
return 1, err
|
||||||
|
}
|
||||||
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)
|
return run_ssh(ssh_args, server_args, found_extra_args, ssh_config)
|
||||||
}
|
}
|
||||||
|
|
||||||
func EntryPoint(parent *cli.Command) {
|
func EntryPoint(parent *cli.Command) {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
package ssh
|
package ssh
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
@@ -194,6 +195,40 @@ func ParseSSHArgs(args []string, extra_args ...string) (ssh_args []string, serve
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
text := stdout.String()
|
||||||
|
scanner := bufio.NewScanner(strings.NewReader(text))
|
||||||
|
|
||||||
|
config = &SSHConfig{}
|
||||||
|
for scanner.Scan() {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
type SSHVersion struct{ Major, Minor int }
|
type SSHVersion struct{ Major, Minor int }
|
||||||
|
|
||||||
func (self SSHVersion) SupportsAskpassRequire() bool {
|
func (self SSHVersion) SupportsAskpassRequire() bool {
|
||||||
|
|||||||
@@ -321,6 +321,11 @@ def main():
|
|||||||
os.environ.pop('KITTY_SHELL_INTEGRATION', None)
|
os.environ.pop('KITTY_SHELL_INTEGRATION', None)
|
||||||
cmd = base64.standard_b64decode(exec_cmd).decode('utf-8')
|
cmd = base64.standard_b64decode(exec_cmd).decode('utf-8')
|
||||||
exec_with_better_error(login_shell, os.path.basename(login_shell), '-c', cmd)
|
exec_with_better_error(login_shell, os.path.basename(login_shell), '-c', cmd)
|
||||||
|
remote_cmd = b'REMOTE_CMD'
|
||||||
|
if remote_cmd:
|
||||||
|
os.environ.pop('KITTY_SHELL_INTEGRATION', None)
|
||||||
|
cmd = base64.standard_b64decode(remote_cmd).decode('utf-8')
|
||||||
|
exec_with_better_error(login_shell, os.path.basename(login_shell), '-c', cmd)
|
||||||
TEST_SCRIPT # noqa
|
TEST_SCRIPT # noqa
|
||||||
if ksi and 'no-rc' not in ksi:
|
if ksi and 'no-rc' not in ksi:
|
||||||
exec_with_shell_integration()
|
exec_with_shell_integration()
|
||||||
|
|||||||
@@ -158,6 +158,8 @@ prepare_for_exec
|
|||||||
# If a command was passed to SSH execute it here
|
# If a command was passed to SSH execute it here
|
||||||
EXEC_CMD
|
EXEC_CMD
|
||||||
|
|
||||||
|
REMOTE_CMD
|
||||||
|
|
||||||
# Used in the tests
|
# Used in the tests
|
||||||
TEST_SCRIPT
|
TEST_SCRIPT
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user