Implement an option to control the installation of the kitty bootstrap script

This commit is contained in:
Kovid Goyal 2022-04-03 20:30:16 +05:30
parent a2d1140229
commit c07f164154
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
7 changed files with 66 additions and 3 deletions

View File

@ -156,6 +156,8 @@ def make_tarfile(ssh_opts: SSHOptions, base_env: Dict[str, str], compression: st
env['KITTY_LOGIN_SHELL'] = ssh_opts.login_shell env['KITTY_LOGIN_SHELL'] = ssh_opts.login_shell
if ssh_opts.cwd: if ssh_opts.cwd:
env['KITTY_LOGIN_CWD'] = ssh_opts.cwd env['KITTY_LOGIN_CWD'] = ssh_opts.cwd
if ssh_opts.remote_kitty != 'no':
env['KITTY_REMOTE'] = ssh_opts.remote_kitty
env_script = serialize_env(env, base_env) env_script = serialize_env(env, base_env)
buf = io.BytesIO() buf = io.BytesIO()
with tarfile.open(mode=f'w:{compression}', fileobj=buf, encoding='utf-8') as tf: with tarfile.open(mode=f'w:{compression}', fileobj=buf, encoding='utf-8') as tf:
@ -171,6 +173,7 @@ def make_tarfile(ssh_opts: SSHOptions, base_env: Dict[str, str], compression: st
f'{arcname}/ssh/*', # bootstrap files are sent as command line args f'{arcname}/ssh/*', # bootstrap files are sent as command line args
f'{arcname}/zsh/kitty.zsh', # present for legacy compat not needed by ssh kitten f'{arcname}/zsh/kitty.zsh', # present for legacy compat not needed by ssh kitten
)) ))
if ssh_opts.remote_kitty != 'no':
arcname = 'home/' + rd + '/kitty' arcname = 'home/' + rd + '/kitty'
add_data_as_file(tf, arcname + '/version', str_version.encode('ascii')) add_data_as_file(tf, arcname + '/version', str_version.encode('ascii'))
tf.add(shell_integration_dir + '/ssh/kitty', arcname=arcname + '/bin/kitty', filter=normalize_tarinfo) tf.add(shell_integration_dir + '/ssh/kitty', arcname=arcname + '/bin/kitty', filter=normalize_tarinfo)

View File

@ -102,6 +102,19 @@ 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 value are expanded. The default is empty so no changing is done, which
usually means the home directory is used. usually means the home directory is used.
''') ''')
opt('remote_kitty', 'if-needed', choices=('if-needed', 'no', 'yes'), long_text='''
Make kitty available on the remote server. Useful to run kittens such as the
icat kitten to display images or the transfer file kitten to transfer files.
Only works if the remote server has an architecture for which pre-compiled
kitty binaries are available. Note that kitty is not actually copied to the
remote server, instead a small bootstrap script is copied which will download
and run kitty when kitty is first executed on the remote server. A value of
:code:`needed` means kitty is installed only if not already present in the
system-wide PATH. A value of :code:`yes` means that kitty is installed even if
already present, and the installed kitty takes precedence. Finally, :code:`no`
means no kitty is installed on the remote machine.
''')
egr() # }}} egr() # }}}
agr('ssh', 'SSH configuration') # {{{ agr('ssh', 'SSH configuration') # {{{

View File

@ -38,6 +38,14 @@ class Parser:
def remote_dir(self, val: str, ans: typing.Dict[str, typing.Any]) -> None: def remote_dir(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
ans['remote_dir'] = relative_dir(val) ans['remote_dir'] = relative_dir(val)
def remote_kitty(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
val = val.lower()
if val not in self.choices_for_remote_kitty:
raise ValueError(f"The value {val} is not a valid choice for remote_kitty")
ans["remote_kitty"] = val
choices_for_remote_kitty = frozenset(('if-needed', 'no', 'yes'))
def share_connections(self, val: str, ans: typing.Dict[str, typing.Any]) -> None: def share_connections(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
ans['share_connections'] = to_bool(val) ans['share_connections'] = to_bool(val)

View File

@ -5,8 +5,10 @@ import kittens.ssh.copy
if typing.TYPE_CHECKING: if typing.TYPE_CHECKING:
choices_for_askpass = typing.Literal['unless-set', 'ssh', 'native'] choices_for_askpass = typing.Literal['unless-set', 'ssh', 'native']
choices_for_remote_kitty = typing.Literal['if-needed', 'no', 'yes']
else: else:
choices_for_askpass = str choices_for_askpass = str
choices_for_remote_kitty = str
option_names = ( # {{{ option_names = ( # {{{
'askpass', 'askpass',
@ -17,6 +19,7 @@ option_names = ( # {{{
'interpreter', 'interpreter',
'login_shell', 'login_shell',
'remote_dir', 'remote_dir',
'remote_kitty',
'share_connections', 'share_connections',
'shell_integration') # }}} 'shell_integration') # }}}
@ -28,6 +31,7 @@ class Options:
interpreter: str = 'sh' interpreter: str = 'sh'
login_shell: str = '' login_shell: str = ''
remote_dir: str = '.local/share/kitty-ssh-kitten' remote_dir: str = '.local/share/kitty-ssh-kitten'
remote_kitty: choices_for_remote_kitty = 'if-needed'
share_connections: bool = True share_connections: bool = True
shell_integration: str = 'inherited' shell_integration: str = 'inherited'
copy: typing.Dict[str, kittens.ssh.copy.CopyInstruction] = {} copy: typing.Dict[str, kittens.ssh.copy.CopyInstruction] = {}

View File

@ -169,6 +169,24 @@ fi' > "$sh_script"
exec "$login_shell" exec "$login_shell"
} }
install_kitty_bootstrap() {
case "$(command uname)" in
Linux) ;;
Darwin) ;;
*) return ;;
esac
kitty_exists="n"
command -v kitty 2> /dev/null > /dev/null && kitty_exists="y"
if [ "$kitty_remote" = "yes" -o "$kitty_remote-$kitty_exists" = "if-needed-n" ]; then
kitty_dir="$data_dir/kitty/bin"
if [ "$kitty_exists" = "y" ]; then
export PATH="$kitty_dir:$PATH"
else
export PATH="$PATH:$kitty_dir"
fi
fi
}
prepare_for_exec() { prepare_for_exec() {
if [ -n "$leading_data" ]; then if [ -n "$leading_data" ]; then
# clear current line as it might have things echoed on it from leading_data # clear current line as it might have things echoed on it from leading_data
@ -177,6 +195,7 @@ prepare_for_exec() {
printf "\r\033[K" > /dev/tty printf "\r\033[K" > /dev/tty
fi fi
[ -f "$HOME/.terminfo/kitty.terminfo" ] || die "Incomplete extraction of ssh data" [ -f "$HOME/.terminfo/kitty.terminfo" ] || die "Incomplete extraction of ssh data"
install_kitty_bootstrap
[ -n "$login_shell" ] || using_getent || using_id || using_python || using_perl || using_passwd || using_shell_env || login_shell="sh" [ -n "$login_shell" ] || using_getent || using_id || using_python || using_perl || using_passwd || using_shell_env || login_shell="sh"
shell_name=$(command basename $login_shell) shell_name=$(command basename $login_shell)

View File

@ -258,6 +258,19 @@ def exec_with_shell_integration():
exec_bash_with_integration() exec_bash_with_integration()
def install_kitty_bootstrap():
kitty_remote = os.environ.pop('KITTY_REMOTE', '')
if os.uname().sysname not in ('Linux', 'Darwin'):
return
kitty_exists = shutil.which('kitty')
if kitty_remote == 'yes' or (kitty_remote == 'if-needed' and not kitty_exists):
kitty_dir = os.path.join(data_dir, 'kitty', 'bin')
if kitty_exists:
os.environ['PATH'] = kitty_dir + os.pathsep + os.environ['PATH']
else:
os.environ['PATH'] = os.environ['PATH'] + os.pathsep + kitty_dir
def main(): def main():
global tty_file_obj, login_shell global tty_file_obj, login_shell
# the value of O_CLOEXEC below is on macOS which is most likely to not have # the value of O_CLOEXEC below is on macOS which is most likely to not have
@ -271,6 +284,7 @@ def main():
finally: finally:
cleanup() cleanup()
cwd = os.environ.pop('KITTY_LOGIN_CWD', '') cwd = os.environ.pop('KITTY_LOGIN_CWD', '')
install_kitty_bootstrap()
if cwd: if cwd:
os.chdir(cwd) os.chdir(cwd)
ksi = frozenset(filter(None, os.environ.get('KITTY_SHELL_INTEGRATION', '').split())) ksi = frozenset(filter(None, os.environ.get('KITTY_SHELL_INTEGRATION', '').split()))

View File

@ -106,6 +106,8 @@ untar_and_read_env() {
unset KITTY_LOGIN_SHELL unset KITTY_LOGIN_SHELL
login_cwd="$KITTY_LOGIN_CWD" login_cwd="$KITTY_LOGIN_CWD"
unset KITTY_LOGIN_CWD unset KITTY_LOGIN_CWD
kitty_remote="$KITTY_REMOTE"
unset KITTY_REMOTE
compile_terminfo "$tdir/home" compile_terminfo "$tdir/home"
mv_files_and_dirs "$tdir/home" "$HOME" mv_files_and_dirs "$tdir/home" "$HOME"
[ -e "$tdir/root" ] && mv_files_and_dirs "$tdir/root" "" [ -e "$tdir/root" ] && mv_files_and_dirs "$tdir/root" ""