ssh kitten: match hostnames against both remote hostname and hostname used on the command line to connect to it
This commit is contained in:
parent
d037c0b0fc
commit
855718b179
@ -24,6 +24,8 @@ Comments can be added to the config file as lines starting with the ``#``
|
||||
character. This works only if the ``#`` character is the first character
|
||||
in the line.
|
||||
|
||||
.. _include:
|
||||
|
||||
You can include secondary config files via the :code:`include` directive. If
|
||||
you use a relative path for :code:`include`, it is resolved with respect to the
|
||||
location of the current config file. Note that environment variables are
|
||||
|
||||
@ -56,19 +56,18 @@ Additionally, you can pass config options on the command line:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
kitty +kitten ssh --kitten hostname=somehost --kitten interpreter=python ...
|
||||
kitty +kitten ssh --kitten interpreter=python servername
|
||||
|
||||
The :code:`--kitten` argument can be specified multiple times, with directives
|
||||
from :file:`ssh.conf`. These are merged with :file:`ssh.conf` as if they were
|
||||
appended to the end of that file. It is important to specify the :opt:`hostname <kitten-ssh.hostname>` as
|
||||
the first setting, otherwise the last :opt:`hostname <kitten-ssh.hostname>` from :file:`ssh.conf` will be
|
||||
used.
|
||||
appended to the end of that file. They apply only to the host being SSHed to
|
||||
by this invocation.
|
||||
|
||||
.. note::
|
||||
|
||||
Because of the poor design of SSH, any typing you do before the shell prompt
|
||||
appears may be lost. So ideally dont start typing till you see the shell
|
||||
prompt :)
|
||||
Because of limitations of the design of SSH, any typing you do before the
|
||||
shell prompt appears may be lost. So ideally dont start typing till you see
|
||||
the shell prompt 😇.
|
||||
|
||||
|
||||
A real world example
|
||||
|
||||
@ -24,11 +24,11 @@ def host_matches(pat: str, hostname: str, username: str) -> bool:
|
||||
return fnmatch.fnmatchcase(hostname, pat) and fnmatch.fnmatchcase(username, upat)
|
||||
|
||||
|
||||
def options_for_host(hostname: str, username: str, per_host_opts: Dict[str, SSHOptions]) -> SSHOptions:
|
||||
def options_for_host(hostname: str, username: str, per_host_opts: Dict[str, SSHOptions], cli_hostname: str = '', cli_uname: str = '') -> SSHOptions:
|
||||
matches = []
|
||||
for spat, opts in per_host_opts.items():
|
||||
for pat in spat.split():
|
||||
if host_matches(pat, hostname, username):
|
||||
if host_matches(pat, hostname, username) or (cli_hostname and host_matches(pat, cli_hostname, cli_uname)):
|
||||
matches.append(opts)
|
||||
if not matches:
|
||||
return SSHOptions({})
|
||||
|
||||
@ -159,12 +159,14 @@ def get_ssh_data(msg: str, request_id: str) -> Iterator[bytes]:
|
||||
raise ValueError('Incorrect password')
|
||||
if rq_id != request_id:
|
||||
raise ValueError('Incorrect request id')
|
||||
cli_hostname = env_data['cli_hostname']
|
||||
cli_uname = env_data['cli_uname']
|
||||
except Exception as e:
|
||||
traceback.print_exc()
|
||||
yield fmt_prefix(f'!{e}')
|
||||
else:
|
||||
ssh_opts = {k: SSHOptions(v) for k, v in env_data['opts'].items()}
|
||||
resolved_ssh_opts = options_for_host(hostname, username, ssh_opts)
|
||||
resolved_ssh_opts = options_for_host(hostname, username, ssh_opts, cli_hostname, cli_uname)
|
||||
resolved_ssh_opts.copy = {k: CopyInstruction(*v) for k, v in resolved_ssh_opts.copy.items()}
|
||||
try:
|
||||
data = make_tarfile(resolved_ssh_opts, env_data['env'])
|
||||
@ -205,7 +207,7 @@ def prepare_exec_cmd(remote_args: Sequence[str], is_python: bool) -> str:
|
||||
def bootstrap_script(
|
||||
script_type: str = 'sh', remote_args: Sequence[str] = (),
|
||||
ssh_opts_dict: Dict[str, Dict[str, Any]] = {},
|
||||
test_script: str = '', request_id: Optional[str] = None
|
||||
test_script: str = '', request_id: Optional[str] = None, cli_hostname: str = '', cli_uname: str = ''
|
||||
) -> str:
|
||||
if request_id is None:
|
||||
request_id = os.environ['KITTY_PID'] + '-' + os.environ['KITTY_WINDOW_ID']
|
||||
@ -214,7 +216,7 @@ def bootstrap_script(
|
||||
ans = f.read()
|
||||
pw = uuid4()
|
||||
with tempfile.NamedTemporaryFile(prefix='ssh-kitten-pw-', suffix='.json', dir=cache_dir(), delete=False) as tf:
|
||||
data = {'pw': pw, 'env': dict(os.environ), 'opts': ssh_opts_dict}
|
||||
data = {'pw': pw, 'env': dict(os.environ), 'opts': ssh_opts_dict, 'cli_hostname': cli_hostname, 'cli_uname': cli_uname}
|
||||
tf.write(json.dumps(data).encode('utf-8'))
|
||||
atexit.register(safe_remove, tf.name)
|
||||
replacements = {
|
||||
@ -224,10 +226,6 @@ def bootstrap_script(
|
||||
return prepare_script(ans, replacements)
|
||||
|
||||
|
||||
def load_script(script_type: str = 'sh', remote_args: Sequence[str] = (), ssh_opts_dict: Dict[str, Dict[str, Any]] = {}) -> str:
|
||||
return bootstrap_script(script_type, remote_args, ssh_opts_dict=ssh_opts_dict)
|
||||
|
||||
|
||||
def get_ssh_cli() -> Tuple[Set[str], Set[str]]:
|
||||
other_ssh_args: Set[str] = set()
|
||||
boolean_ssh_args: Set[str] = set()
|
||||
@ -393,11 +391,14 @@ def parse_ssh_args(args: List[str], extra_args: Tuple[str, ...] = ()) -> Tuple[L
|
||||
|
||||
|
||||
def get_remote_command(
|
||||
remote_args: List[str], hostname: str = 'localhost', interpreter: str = 'sh',
|
||||
remote_args: List[str], hostname: str = 'localhost', cli_hostname: str = '', cli_uname: str = '',
|
||||
interpreter: str = 'sh',
|
||||
ssh_opts_dict: Dict[str, Dict[str, Any]] = {}
|
||||
) -> List[str]:
|
||||
is_python = 'python' in interpreter.lower()
|
||||
sh_script = load_script(script_type='py' if is_python else 'sh', remote_args=remote_args, ssh_opts_dict=ssh_opts_dict)
|
||||
sh_script = bootstrap_script(
|
||||
script_type='py' if is_python else 'sh', remote_args=remote_args, ssh_opts_dict=ssh_opts_dict,
|
||||
cli_hostname=cli_hostname, cli_uname=cli_uname)
|
||||
return [f'{interpreter} -c {shlex.quote(sh_script)}']
|
||||
|
||||
|
||||
@ -436,12 +437,17 @@ def main(args: List[str]) -> NoReturn:
|
||||
for i, a in enumerate(found_extra_args):
|
||||
if i % 2 == 1:
|
||||
overrides.append(pat.sub(r'\1 ', a.lstrip()))
|
||||
if overrides:
|
||||
overrides.insert(0, f'hostname {uname}@{hostname_for_match}')
|
||||
so = init_config(overrides)
|
||||
sod = {k: v._asdict() for k, v in so.items()}
|
||||
cmd += get_remote_command(remote_args, hostname, options_for_host(hostname_for_match, uname, so).interpreter, sod)
|
||||
cmd += get_remote_command(remote_args, hostname, hostname_for_match, uname, options_for_host(hostname_for_match, uname, so).interpreter, sod)
|
||||
import subprocess
|
||||
with suppress(FileNotFoundError):
|
||||
raise SystemExit(subprocess.run(cmd).returncode)
|
||||
try:
|
||||
raise SystemExit(subprocess.run(cmd).returncode)
|
||||
except KeyboardInterrupt:
|
||||
raise SystemExit(1)
|
||||
raise SystemExit('Could not find the ssh executable, is it in your PATH?')
|
||||
|
||||
|
||||
|
||||
@ -29,10 +29,11 @@ The hostname the following options apply to. A glob pattern to match multiple
|
||||
hosts can be used. Multiple hostnames can also be specified separated by spaces.
|
||||
The hostname can include an optional username in the form :code:`user@host`.
|
||||
When not specified options apply to all hosts, until the
|
||||
first hostname specification is found. Note that the hostname this matches
|
||||
against is the hostname used by the remote computer, not the name you pass
|
||||
to SSH to connect to it. If you wish to include the same basic configuration for many
|
||||
different hosts, you can do so with the :code:`include` directive (see :doc:`/conf`).
|
||||
first hostname specification is found. Note that matching of hostname is done against
|
||||
both the hostname used by the remote computer, and the name you pass
|
||||
to SSH to connect to it. If either matches, it is considered a match.
|
||||
If you wish to include the same basic configuration for many
|
||||
different hosts, you can do so with the :ref:`include <include>` directive.
|
||||
''')
|
||||
|
||||
opt('+copy', '', option_type='copy', add_to_default=False, long_text=f'''
|
||||
@ -84,7 +85,7 @@ The interpreter to use on the remote host. Must be either a POSIX complaint shel
|
||||
or a python executable. If the default sh is not available for broken, using
|
||||
an alternate interpreter can be useful. Note that as the interpreter is used for
|
||||
bootstrapping, hostname specific values are matched again the hostname from the
|
||||
command line args rather than the actual remote hostname.
|
||||
command line args only.
|
||||
''')
|
||||
|
||||
opt('remote_dir', '.local/share/kitty-ssh-kitten', option_type='relative_dir', long_text='''
|
||||
|
||||
@ -49,6 +49,7 @@ def ref_map() -> Dict[str, str]:
|
||||
from kitty.actions import get_all_actions
|
||||
ref_map = {
|
||||
'layouts': f'{website_url("overview")}#layouts',
|
||||
'include': f'{website_url("conf")}#include',
|
||||
'watchers': f'{website_url("launch")}#watchers',
|
||||
'sessions': f'{website_url("overview")}#startup-sessions',
|
||||
'functional': f'{website_url("keyboard-protocol")}#functional-key-definitions',
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user