This commit is contained in:
anki-code 2020-02-25 00:09:17 +03:00
parent d08b8cea8f
commit b541752e38
4 changed files with 95 additions and 43 deletions

View file

@ -23,14 +23,15 @@ After install you can just using `xxh` command as replace `ssh` to connecting to
🗝️ The best experience you'll get when you're using [public key or ssh config](https://linuxize.com/post/using-the-ssh-config-file/#ssh-config-file-example) to authorization. In case of using password you should type it many times. We're working on reduce password typing to one in [#27](https://github.com/xonssh/xxh/issues/27).
```
$ xxh --help
usage: xxh [config name from ssh config]
$ ./xxh -h
usage: xxh <host from ~/.ssh/config>
usage: xxh [ssh arguments] [user@]host[:port] [xxh arguments]
usage: xxh [-h] [-V] [-p SSH_PORT] [-l SSH_LOGIN] [-i SSH_PRIVATE_KEY] [-o SSH_OPTION -o ...]
[user@]host[:port]
[+i] [+if] [+lxh LOCAL_XXH_HOME] [+hxh HOST_XXH_HOME] [+he HOST_EXECUTE_FILE]
[+i] [+if] [+akh] [+P PASSWORD] [+PP]
[+lxh LOCAL_XXH_HOME] [+hxh HOST_XXH_HOME] [+he HOST_EXECUTE_FILE]
[+m METHOD] [+v] [+vv]
The xxh is for using the xonsh shell wherever you go through the ssh.
@ -41,12 +42,12 @@ The xxh is for using the xonsh shell wherever you go through the ssh.
_____ / / __ \ \ / _/ https://github.com/xonssh/xxh
___ ( / / / \ \ /
\ \___/ / / / plugins
___\ /__/ / https://github.com/search?q=xxh-plugin
/ \________/ /
/___________________/
____\ /__/ / https://github.com/search?q=xxh-plugin
/ \________/ /
/____________________/
required arguments:
[user@]host[:port] Destination may be specified as [user@]host[:port] or server name from ~/.ssh/config
[user@]host[:port] Destination may be specified as [user@]host[:port] or host from ~/.ssh/config
common arguments:
-h, --help show this help message and exit
@ -61,12 +62,18 @@ ssh arguments:
xxh arguments:
+i, ++install Install xxh to destination host.
+if, ++install-force Removing the host xxh home and install xxh again.
+akh, ++add-to-known-hosts
Add new host to known hosts without asking.
+P PASSWORD, ++password PASSWORD
Password for ssh auth.
+PP, ++password-prompt
Enter password manually using prompt.
+lh LOCAL_XXH_HOME, ++local-xxh-home LOCAL_XXH_HOME
Local xxh home path. Default: ~/.xxh
+hh HOST_XXH_HOME, ++host-xxh-home HOST_XXH_HOME
Host xxh home path. Default: ~/.xxh
+he HOST_EXECUTE_FILE, ++host-execute-file HOST_EXECUTE_FILE
Execute script file placed on host and exit
Execute script file placed on host and exit.
+m METHOD, ++method METHOD
Portable method: appimage
+v, ++verbose Verbose mode.

View file

@ -1,7 +1,7 @@
import sys, os
global_settings = {
'XXH_VERSION': '0.2.12'
'XXH_VERSION': '0.3.0'
}
if __name__ == "__main__":

View file

@ -1,18 +1,19 @@
import os, sys, glob
import sys
$UPDATE_OS_ENVIRON=True
$XXH_HOME = os.path.dirname(os.path.realpath(__file__))
$PIP_TARGET = os.path.join($XXH_HOME, 'pip')
$XXH_HOME = pf"{__file__}".absolute().parent
$PIP_TARGET = $XXH_HOME / 'pip'
$PYTHONPATH = $PIP_TARGET
$PATH = [ os.path.join($PYTHONHOME, 'bin'), $XXH_HOME ] + $PATH
sys.path.append($PIP_TARGET)
$PATH = [ p"$PYTHONHOME" / 'bin', $XXH_HOME ] + $PATH
sys.path.append(str($PIP_TARGET))
sys.path.remove('') if '' in sys.path else None
aliases['pip'] = ['python','-m','pip']
aliases['xpip'] = aliases['pip']
for plugin_path in sorted(glob.glob(os.path.join($XXH_HOME, 'plugins/**'))):
if os.path.exists(os.path.join(plugin_path, 'xonshrc.xsh')):
sys.path.append(plugin_path)
for plugin_path in sorted(($XXH_HOME / 'plugins').glob('*')):
if (plugin_path / 'xonshrc.xsh').exists():
sys.path.append(str(plugin_path))
__import__('xonshrc')
del sys.modules['xonshrc']
sys.path.remove(plugin_path)
sys.path.remove(str(plugin_path))

94
xxh
View file

@ -1,6 +1,6 @@
#!/usr/bin/env xonsh
import os, sys, glob, argparse, datetime
import os, sys, argparse, datetime, getpass
from shutil import which
from sys import exit
from argparse import RawTextHelpFormatter
@ -62,20 +62,26 @@ argp.add_argument('-p', dest='ssh_port', help="Port to connect to on the remote
argp.add_argument('-l', dest='ssh_login', help="Specifies the user to log in as on the remote machine.")
argp.add_argument('-i', dest='ssh_private_key', help="File from which the identity (private key) for public key authentication is read.")
argp.add_argument('-o', dest='ssh_options', metavar='SSH_OPTION -o ...', action='append', help="SSH options are described in ssh man page. Example: -o Port=22 -o User=snail")
argp.add_argument('destination', metavar='[user@]host[:port]', help="Destination may be specified as [user@]host[:port] or server name from ~/.ssh/config")
argp.add_argument('destination', metavar='[user@]host[:port]', help="Destination may be specified as [user@]host[:port] or host from ~/.ssh/config")
argp.add_argument('+i','++install', default=False, action='store_true', help="Install xxh to destination host.")
argp.add_argument('+if','++install-force', default=False, action='store_true', help="Removing the host xxh home and install xxh again.")
argp.add_argument('+akh','++add-to-known-hosts', default=False, action='store_true', help="Add new host to known hosts without asking.")
argp.add_argument('+P','++password', help="Password for ssh auth.")
argp.add_argument('+PP','++password-prompt', default=False, action='store_true', help="Enter password manually using prompt.")
argp.add_argument('+lh','++local-xxh-home', default=local_xxh_home_path, help=f"Local xxh home path. Default: {local_xxh_home_path}")
argp.add_argument('+hh','++host-xxh-home', default=host_xxh_home_path, help=f"Host xxh home path. Default: {host_xxh_home_path}")
argp.add_argument('+he','++host-execute-file', help=f"Execute script file placed on host and exit")
argp.add_argument('+he','++host-execute-file', help=f"Execute script file placed on host and exit.")
argp.add_argument('+m','++method', default='appimage', help=f"Portable method: {portable_methods_str}")
argp.add_argument('+v','++verbose', default=False, action='store_true', help="Verbose mode.")
argp.add_argument('+vv','++vverbose', default=False, action='store_true', help="Super verbose mode.")
argp.usage = """xxh [ssh arguments] [user@]host[:port] [xxh arguments]
argp.usage = """xxh <host from ~/.ssh/config>
usage: xxh [ssh arguments] [user@]host[:port] [xxh arguments]
usage: xxh [-h] [-V] [-p SSH_PORT] [-l SSH_LOGIN] [-i SSH_PRIVATE_KEY] [-o SSH_OPTION -o ...]
[user@]host[:port]
[+i] [+if] [+lxh LOCAL_XXH_HOME] [+hxh HOST_XXH_HOME] [+he HOST_EXECUTE_FILE]
[+i] [+if] [+akh] [+P PASSWORD] [+PP]
[+lxh LOCAL_XXH_HOME] [+hxh HOST_XXH_HOME] [+he HOST_EXECUTE_FILE]
[+m METHOD] [+v] [+vv]
"""
help = argp.format_help().replace('\n +','\n\nxxh arguments:\n +',1).replace('optional ', 'common ')\
@ -104,6 +110,10 @@ if url.port:
if url.username:
opt.ssh_login = url.username
username = getpass.getuser()
if opt.ssh_login:
username = opt.ssh_login
ssh_arguments = []
if not opt.verbose:
ssh_arguments = ['-o', 'LogLevel=QUIET']
@ -123,6 +133,22 @@ if opt.verbose:
if not which('ssh'):
eeprint('Install OpenSSH client before using xxh: https://duckduckgo.com/?q=how+to+install+openssh+client+in+linux')
if not which('sshpass') and (opt.password is not None or opt.password_prompt):
eeprint('Install sshpass to using password: https://duckduckgo.com/?q=install+sshpass\n'
+ 'Note! There are a lot of security reasons for stop using password auth.')
sshpass = []
if opt.password is not None:
sshpass = ['sshpass', '-p', opt.password]
elif opt.password_prompt:
password = ''
while not password:
password = getpass.getpass(f"Enter {username}@{host}'s password: ")
sshpass = ['sshpass', '-p', password]
if sshpass != [] and opt.vverbose:
sshpass += ['-v']
opt.install = True if opt.install_force else opt.install
ssh_v = ['-v'] if opt.vverbose else []
@ -150,19 +176,37 @@ for lc in ['LC_TIME','LC_MONETARY','LC_ADDRESS','LC_IDENTIFICATION','LC_MEASUREM
if pf'{opt.host_xxh_home}' == pf'/':
eeprint("Host xxh home path {host_xxh_home} looks like /. Please check twice!")
def is_host_known(hostname):
return $(ssh-keygen -F @(hostname)) != ''
def get_host_info():
host_info_sh = package_dir_path / 'host_info.sh'
r = $(cat @(host_info_sh) | sed @(f's|_xxh_home_|{opt.host_xxh_home}|') | ssh @(ssh_v) @(ssh_arguments) @(host) -T "bash -s" ).strip()
r = $(cat @(host_info_sh) | sed @(f's|_xxh_home_|{opt.host_xxh_home}|') | @(sshpass) ssh @(ssh_v) @(ssh_arguments) @(host) -T "bash -s" ).strip()
if opt.verbose:
eprint(f'Host info:\n{r}')
if r == '':
eeprint(f'Unknown answer from host when getting host info. Check your connection parameters using ordinary ssh.')
eeprint('Empty answer from host when getting first info. Often this is a connection error.\n'
+ 'Check your connection parameters using the same command but with ssh.')
r = dict([l.split('=') for l in r.split('\n')])
return r
if opt.add_to_known_hosts:
ssh_arguments += ['-o', f'StrictHostKeyChecking=accept-new']
elif not is_host_known(host):
choice = input(f"The host {host} is unknown. What's next?\n"
+" s - [default] Stop and try to connect using ssh to check that host is right\n"
+" a - Add the host to the known hosts and continue\n"
+"S/a?")
if choice == 's' or choice.strip() == '':
print('Stopped')
exit(0)
elif choice == 'a':
ssh_arguments += ['-o', f'StrictHostKeyChecking=accept-new']
host_info = get_host_info()
host_xxh_home = host_info['xxh_home_realpath']
@ -197,11 +241,11 @@ if opt.install_force == False:
if ask:
choice = input(f"{ask} What's next? \n"
+ f"s - [default] Stop here. You'll try to connect using ordinary ssh for backup current xxh home.\n"
+ f"u - Safe update. Host xxh home will be renamed and local xxh version will be installed.\n"
+ f"f - Force install local xxh version on host. Host xxh installation will be lost.\n"
+ f"i - Ignore, cross fingers and continue the connection.\n"
+ f"S/u/f/i? ").lower()
+ " s - [default] Stop here. You'll try to connect using ordinary ssh for backup current xxh home.\n"
+ " u - Safe update. Host xxh home will be renamed and local xxh version will be installed.\n"
+ " f - Force install local xxh version on host. Host xxh installation will be lost.\n"
+ " i - Ignore, cross fingers and continue the connection.\n"
+ "S/u/f/i? ").lower()
if choice == 's' or choice.strip() == '':
print('Stopped')
@ -209,7 +253,7 @@ if opt.install_force == False:
elif choice == 'u':
local_time = datetime.datetime.now().isoformat()[:19]
eprint(f"Move {host}:{host_xxh_home} to {host}:{host_xxh_home}-{local_time}")
echo @(f"mv {host_xxh_home} {host_xxh_home}-{local_time}") | ssh @(ssh_v) @(ssh_arguments) @(host) -T "bash -s"
echo @(f"mv {host_xxh_home} {host_xxh_home}-{local_time}") | @(sshpass) ssh @(ssh_v) @(ssh_arguments) @(host) -T "bash -s"
opt.install = True
elif choice == 'f':
opt.install = True
@ -249,25 +293,25 @@ if opt.install:
if opt.install_force:
eprint(f'Remove host xxh home {host}:{host_xxh_home}')
echo @(f"rm -rf {host_xxh_home}/*") | ssh @(ssh_v) @(ssh_arguments) @(host) -T "bash -s"
echo @(f"rm -rf {host_xxh_home}/*") | @(sshpass) ssh @(ssh_v) @(ssh_arguments) @(host) -T "bash -s"
eprint(f"Install xxh to {host}:{host_xxh_home}" )
if host_xxh_version in ['dir_not_found']:
eprint(f'Create xxh home {host_xxh_home}')
echo @(f"mkdir -p {host_xxh_home}") | ssh @(ssh_v) @(ssh_arguments) @(host) -T "bash -s"
echo @(f"mkdir -p {host_xxh_home}") | @(sshpass) ssh @(ssh_v) @(ssh_arguments) @(host) -T "bash -s"
if which('rsync') and host_info['rsync']:
eprint('Upload using rsync')
rsync @(ssh_v) -e @(f"ssh {'' if ssh_v == [] else '-v'} {' '.join(ssh_arguments)}") -az --info=progress2 --include ".*" --exclude='*.pyc' @(local_xxh_home_path)/ @(host):@(host_xxh_home)/ 1>&2
rsync @(ssh_v) -e @(f"ssh {'' if ssh_v == [] else '-v'} {' '.join(ssh_arguments)}") -az --info=progress2 --include ".*" --exclude='*.pyc' @(package_dir_path)/ @(host):@(host_xxh_home)/ 1>&2
rsync @(ssh_v) -e @(f"{''.join(sshpass)} ssh {'' if ssh_v == [] else '-v'} {' '.join(ssh_arguments)}") -az --info=progress2 --include ".*" --exclude='*.pyc' @(local_xxh_home_path)/ @(host):@(host_xxh_home)/ 1>&2
rsync @(ssh_v) -e @(f"{''.join(sshpass)} ssh {'' if ssh_v == [] else '-v'} {' '.join(ssh_arguments)}") -az --info=progress2 --include ".*" --exclude='*.pyc' @(package_dir_path)/ @(host):@(host_xxh_home)/ 1>&2
elif which('scp') and host_info['scp']:
eprint("Upload using scp. Note: install rsync on local and remote host to increase speed.")
scp_host = f"{host}:{host_xxh_home}/"
scp @(ssh_v) @(ssh_arguments) -r -C @([] if opt.verbose else ['-q']) @(local_xxh_home_path)/* @(scp_host) 1>&2
scp @(ssh_v) @(ssh_arguments) -r -C @([] if opt.verbose else ['-q']) @(package_dir_path)/* @(scp_host) 1>&2
@(sshpass) scp @(ssh_v) @(ssh_arguments) -r -C @([] if opt.verbose else ['-q']) @(local_xxh_home_path)/* @(scp_host) 1>&2
@(sshpass) scp @(ssh_v) @(ssh_arguments) -r -C @([] if opt.verbose else ['-q']) @(package_dir_path)/* @(scp_host) 1>&2
else:
eprint('scp or rsync not found!')
eprint('Please install rsync or scp!')
plugins_fullpath = local_xxh_home_path / 'plugins'
if plugins_fullpath.exists():
@ -280,22 +324,22 @@ if opt.install:
eprint(f' * {script}')
if scripts:
echo @(f"cd {host_xxh_home} {scripts}" ) | ssh @(ssh_v) @(ssh_arguments) @(host) -T "bash -s" 1>&2
echo @(f"cd {host_xxh_home} {scripts}" ) | @(sshpass) ssh @(ssh_v) @(ssh_arguments) @(host) -T "bash -s" 1>&2
eprint(f'Check {opt.method}')
host_settings_file = host_xxh_home / 'settings.py'
check = $(ssh @(ssh_v) @(ssh_arguments) @(host) -t @(host_xonsh_bin) --no-script-cache -i --rc @(host_xonshrc) -- @(host_settings_file) )
check = $(@(sshpass) ssh @(ssh_v) @(ssh_arguments) @(host) -t @(host_xonsh_bin) --no-script-cache -i --rc @(host_xonshrc) -- @(host_settings_file) )
if opt.verbose:
eprint(f'Check xonsh result:\n{check}')
if check == '' or 'AppImages require FUSE to run' in check:
eprint('AppImage is not supported by host. Trying to unpack and run...')
host_xonsh_bin_new = host_xxh_home / 'xonsh-squashfs/usr/python/bin/xonsh'
ssh @(ssh_v) @(ssh_arguments) @(host) -t @(f"cd {host_xxh_home} && ./{xonsh_bin_name} --appimage-extract | grep -E 'usr/python/bin/xonsh$' && mv squashfs-root xonsh-squashfs && mv {host_xonsh_bin} {host_xonsh_bin}-disabled && ln -s {host_xonsh_bin_new}") 1>&2
host_xonsh_bin_new = host_xxh_home / 'xonsh-squashfs/usr/bin/python3'
@(sshpass) ssh @(ssh_v) @(ssh_arguments) @(host) -t @(f"cd {host_xxh_home} && ./{xonsh_bin_name} --appimage-extract | grep -E 'usr/python/bin/xonsh$' && mv squashfs-root xonsh-squashfs && mv {host_xonsh_bin} {host_xonsh_bin}-disabled && ln -s {host_xonsh_bin_new} xonsh") 1>&2
host_xonsh_bin = host_xonsh_bin_new
eprint(f'First run xonsh on {host}\033[0m')
host_execute_file = ['--', opt.host_execute_file] if opt.host_execute_file else []
ssh @(ssh_v) @(ssh_arguments) @(host) -t @(host_xonsh_bin) --no-script-cache -i --rc @(host_xonshrc) @(host_execute_file)
@(sshpass) ssh @(ssh_v) @(ssh_arguments) @(host) -t @(host_xonsh_bin) --no-script-cache -i --rc @(host_xonshrc) @(host_execute_file)