diff --git a/kittens/ssh/main.py b/kittens/ssh/main.py index 99376c261..d10b69dcb 100644 --- a/kittens/ssh/main.py +++ b/kittens/ssh/main.py @@ -96,6 +96,8 @@ def make_tarfile(ssh_opts: SSHOptions, base_env: Dict[str, str]) -> bytes: env.update(ssh_opts.env) env['KITTY_SHELL_INTEGRATION'] = ksi or DELETE_ENV_VAR env['KITTY_SSH_KITTEN_DATA_DIR'] = ssh_opts.remote_dir + if ssh_opts.login_shell: + env['KITTY_LOGIN_SHELL'] = ssh_opts.login_shell env_script = serialize_env(env, base_env) buf = io.BytesIO() with tarfile.open(mode='w:bz2', fileobj=buf, encoding='utf-8') as tf: @@ -173,7 +175,7 @@ def prepare_script(ans: str, replacements: Dict[str, str]) -> str: atexit.register(safe_remove, tf.name) replacements['DATA_PASSWORD'] = pw replacements['PASSWORD_FILENAME'] = os.path.basename(tf.name) - for k in ('EXEC_CMD', 'OVERRIDE_LOGIN_SHELL'): + for k in ('EXEC_CMD',): replacements[k] = replacements.get(k, '') def sub(m: 're.Match[str]') -> str: diff --git a/kittens/ssh/options/definition.py b/kittens/ssh/options/definition.py index d86298684..9efb5e94b 100644 --- a/kittens/ssh/options/definition.py +++ b/kittens/ssh/options/definition.py @@ -91,5 +91,8 @@ for details on how this setting works. The special value :code:`inherit` means use the setting from kitty.conf. This setting is useful for overriding integration on a per-host basis.''') +opt('login_shell', '', long_text=''' +The login shell to execute on the remote host. By default, the remote user account's +login shell is used.''') egr() # }}} diff --git a/kittens/ssh/options/parse.py b/kittens/ssh/options/parse.py index c6a79ac86..507698dde 100644 --- a/kittens/ssh/options/parse.py +++ b/kittens/ssh/options/parse.py @@ -21,6 +21,9 @@ class Parser: def interpreter(self, val: str, ans: typing.Dict[str, typing.Any]) -> None: ans['interpreter'] = str(val) + def login_shell(self, val: str, ans: typing.Dict[str, typing.Any]) -> None: + ans['login_shell'] = str(val) + def remote_dir(self, val: str, ans: typing.Dict[str, typing.Any]) -> None: ans['remote_dir'] = relative_dir(val) diff --git a/kittens/ssh/options/types.py b/kittens/ssh/options/types.py index 94efcde36..5c9dd6998 100644 --- a/kittens/ssh/options/types.py +++ b/kittens/ssh/options/types.py @@ -5,12 +5,19 @@ import kittens.ssh.copy option_names = ( # {{{ - 'copy', 'env', 'hostname', 'interpreter', 'remote_dir', 'shell_integration') # }}} + 'copy', + 'env', + 'hostname', + 'interpreter', + 'login_shell', + 'remote_dir', + 'shell_integration') # }}} class Options: hostname: str = '*' interpreter: str = 'sh' + login_shell: str = '' remote_dir: str = '.local/share/kitty-ssh-kitten' shell_integration: str = 'inherit' copy: typing.Dict[str, kittens.ssh.copy.CopyInstruction] = {} diff --git a/kitty_tests/ssh.py b/kitty_tests/ssh.py index 250d60b01..688ed9550 100644 --- a/kitty_tests/ssh.py +++ b/kitty_tests/ssh.py @@ -84,7 +84,7 @@ print(' '.join(map(str, buf)))'''), lines=13, cols=77) @property @lru_cache() def all_possible_sh(self): - return tuple(sh for sh in ('dash', 'zsh', 'bash', 'posh', 'sh') if shutil.which(sh)) + return tuple(filter(shutil.which, ('dash', 'zsh', 'bash', 'posh', 'sh'))) def test_ssh_copy(self): simple_data = 'rkjlhfwf9whoaa' @@ -188,6 +188,9 @@ copy --exclude */w.* d1 if login_shell == 'bash': pty.send_cmd_to_child('echo $HISTFILE') pty.wait_till(lambda: '/.bash_history' in pty.screen_contents()) + elif login_shell == 'zsh': + pty.send_cmd_to_child('echo "login_shell=$ZSH_NAME"') + pty.wait_till(lambda: 'login_shell=zsh' in pty.screen_contents()) # check that turning off shell integration works if ok_login_shell in ('bash', 'zsh'): for val in ('', 'no-rc', 'enabled no-rc'): @@ -197,7 +200,6 @@ copy --exclude */w.* d1 def check_bootstrap(self, sh, home_dir, login_shell='', SHELL_INTEGRATION_VALUE='enabled', extra_exec='', pre_data='', ssh_opts=None): script = bootstrap_script( EXEC_CMD=f'echo "UNTAR_DONE"; {extra_exec}', - OVERRIDE_LOGIN_SHELL=login_shell, ) env = basic_shell_env(home_dir) # Avoid generating unneeded completion scripts @@ -205,6 +207,9 @@ copy --exclude */w.* d1 # prevent newuser-install from running open(os.path.join(home_dir, '.zshrc'), 'w').close() options = {'shell_integration': shell_integration(SHELL_INTEGRATION_VALUE or 'disabled')} + if login_shell: + ssh_opts = ssh_opts or {} + ssh_opts['login_shell'] = login_shell pty = self.create_pty(f'{sh} -c {shlex.quote(script)}', cwd=home_dir, env=env, options=options, ssh_opts=ssh_opts) if pre_data: pty.write_buf = pre_data.encode('utf-8') diff --git a/shell-integration/ssh/bootstrap.sh b/shell-integration/ssh/bootstrap.sh index d137bf084..1761cec47 100644 --- a/shell-integration/ssh/bootstrap.sh +++ b/shell-integration/ssh/bootstrap.sh @@ -245,9 +245,9 @@ execute_with_python() { return 1; } -LOGIN_SHELL="OVERRIDE_LOGIN_SHELL" -if [ -n "$LOGIN_SHELL" ]; then - login_shell="$LOGIN_SHELL" +if [ -n "$KITTY_LOGIN_SHELL" ]; then + login_shell="$KITTY_LOGIN_SHELL" + unset KITTY_LOGIN_SHELL else using_getent || using_id || using_python || using_passwd || die "Could not detect login shell"; fi