diff --git a/kittens/ssh/main.py b/kittens/ssh/main.py index 2e39a51e2..79a5f5199 100644 --- a/kittens/ssh/main.py +++ b/kittens/ssh/main.py @@ -113,6 +113,8 @@ def make_tarfile(ssh_opts: SSHOptions, base_env: Dict[str, str]) -> bytes: env['KITTY_SSH_KITTEN_DATA_DIR'] = ssh_opts.remote_dir if ssh_opts.login_shell: env['KITTY_LOGIN_SHELL'] = ssh_opts.login_shell + if ssh_opts.cwd: + env['KITTY_LOGIN_CWD'] = ssh_opts.cwd env_script = serialize_env(env, base_env) buf = io.BytesIO() with tarfile.open(mode='w:bz2', fileobj=buf, encoding='utf-8') as tf: diff --git a/kittens/ssh/options/definition.py b/kittens/ssh/options/definition.py index 69b50650a..34bcabe0e 100644 --- a/kittens/ssh/options/definition.py +++ b/kittens/ssh/options/definition.py @@ -71,6 +71,14 @@ are processed alphabetically. The special value :code:`_kitty_copy_env_var_` will cause the value of the variable to be copied from the local machine. ''') + +opt('cwd', '', long_text=''' +The working directory on the remote host to change to. Env vars in this +value are expanded. The default is empty so no changing is done, which +usually means the home directory is used. +''') + + opt('interpreter', 'sh', long_text=''' The interpreter to use on the remote host. Must be either a POSIX complaint shell or a python executable. If the default sh is not available for broken, using diff --git a/kittens/ssh/options/parse.py b/kittens/ssh/options/parse.py index 507698dde..61a1b8183 100644 --- a/kittens/ssh/options/parse.py +++ b/kittens/ssh/options/parse.py @@ -11,6 +11,9 @@ class Parser: for k, v in copy(val, ans["copy"]): ans["copy"][k] = v + def cwd(self, val: str, ans: typing.Dict[str, typing.Any]) -> None: + ans['cwd'] = str(val) + def env(self, val: str, ans: typing.Dict[str, typing.Any]) -> None: for k, v in env(val, ans["env"]): ans["env"][k] = v diff --git a/kittens/ssh/options/types.py b/kittens/ssh/options/types.py index a8c8320ff..0b86c640b 100644 --- a/kittens/ssh/options/types.py +++ b/kittens/ssh/options/types.py @@ -6,6 +6,7 @@ import kittens.ssh.copy option_names = ( # {{{ 'copy', + 'cwd', 'env', 'hostname', 'interpreter', @@ -15,6 +16,7 @@ option_names = ( # {{{ class Options: + cwd: str = '' hostname: str = '*' interpreter: str = 'sh' login_shell: str = '' diff --git a/kitty_tests/ssh.py b/kitty_tests/ssh.py index aaf158a8d..4130546d5 100644 --- a/kitty_tests/ssh.py +++ b/kitty_tests/ssh.py @@ -142,9 +142,10 @@ copy --exclude */w.* d1 tset = '$A-$(echo no)-`echo no2` "something"' for sh in self.all_possible_sh: with self.subTest(sh=sh), tempfile.TemporaryDirectory() as tdir: + os.mkdir(os.path.join(tdir, 'cwd')) pty = self.check_bootstrap( - sh, tdir, test_script='env; exit 0', SHELL_INTEGRATION_VALUE='', - ssh_opts={'env': { + sh, tdir, test_script='env; pwd; exit 0', SHELL_INTEGRATION_VALUE='', + ssh_opts={'cwd': '$HOME/cwd', 'env': { 'A': 'AAA', 'TSET': tset, 'COLORTERM': DELETE_ENV_VAR, @@ -152,6 +153,7 @@ copy --exclude */w.* d1 ) pty.wait_till(lambda: 'TSET={}'.format(tset.replace('$A', 'AAA')) in pty.screen_contents()) self.assertNotIn('COLORTERM', pty.screen_contents()) + pty.wait_till(lambda: '/cwd' in pty.screen_contents()) def test_ssh_leading_data(self): script = 'echo "ld:$leading_data"; exit 0' diff --git a/shell-integration/ssh/bootstrap.py b/shell-integration/ssh/bootstrap.py index d4f9f9b2a..f84b6ca84 100644 --- a/shell-integration/ssh/bootstrap.py +++ b/shell-integration/ssh/bootstrap.py @@ -230,6 +230,9 @@ def main(): get_data() finally: cleanup() + cwd = os.environ.pop('KITTY_LOGIN_CWD', '') + if cwd: + os.chdir(cwd) ksi = frozenset(filter(None, os.environ.get('KITTY_SHELL_INTEGRATION', '').split())) exec_cmd = b'EXEC_CMD' if exec_cmd: diff --git a/shell-integration/ssh/bootstrap.sh b/shell-integration/ssh/bootstrap.sh index af54e20e5..16f0be0ed 100644 --- a/shell-integration/ssh/bootstrap.sh +++ b/shell-integration/ssh/bootstrap.sh @@ -86,8 +86,8 @@ hostname="$HOSTNAME" # ensure $USER is set [ -z "$USER" ] && USER="$(command whoami 2> /dev/null)" -# ask for the SSH data leading_data="" +login_cwd="" init_tty && trap "cleanup_on_bootstrap_exit" EXIT [ "$tty_ok" = "y" ] && dcs_to_kitty "ssh" "id="REQUEST_ID":hostname="$hostname":pwfile="PASSWORD_FILENAME":user="$USER":pw="DATA_PASSWORD"" @@ -129,13 +129,15 @@ untar_and_read_env() { read_n_bytes_from_tty "$1" | command base64 -d | command tar xpjf - -C "$tdir" data_file="$tdir/data.sh" [ -f "$data_file" ] && . "$data_file" + [ -z "$KITTY_SSH_KITTEN_DATA_DIR" ] && die "Failed to read SSH data from tty" data_dir="$HOME/$KITTY_SSH_KITTEN_DATA_DIR" + unset KITTY_SSH_KITTEN_DATA_DIR + login_cwd="$KITTY_LOGIN_CWD" + unset KITTY_LOGIN_CWD compile_terminfo "$tdir/home" mv_files_and_dirs "$tdir/home" "$HOME" [ -e "$tdir/root" ] && mv_files_and_dirs "$tdir/root" "" command rm -rf "$tdir" - [ -z "KITTY_SSH_KITTEN_DATA_DIR" ] && die "Failed to read SSH data from tty" - unset KITTY_SSH_KITTEN_DATA_DIR } read_record() { @@ -160,6 +162,7 @@ get_data() { } if [ "$tty_ok" = "y" ]; then + # ask for the SSH data get_data cleanup_on_bootstrap_exit if [ -n "$leading_data" ]; then @@ -252,6 +255,7 @@ else using_getent || using_id || using_python || using_passwd || die "Could not detect login shell" fi shell_name=$(command basename $login_shell) +[ -n "$login_cwd" ] && cd "$login_cwd" # If a command was passed to SSH execute it here EXEC_CMD