diff --git a/xonssh_xxh/host_info.sh b/xonssh_xxh/host_info.sh index 2829791..7d770cd 100644 --- a/xonssh_xxh/host_info.sh +++ b/xonssh_xxh/host_info.sh @@ -1,7 +1,7 @@ #!/bin/bash xxh_home_realpath=`realpath _xxh_home_` -mkdir -p $xxh_home_realpath +mkdir -p $xxh_home_realpath $xxh_home_realpath/plugins settings_path=$xxh_home_realpath/settings.py xxh_version=`[ "$(ls -A $xxh_home_realpath)" ] && echo "0" || echo "-1"` @@ -11,7 +11,9 @@ fi echo xxh_home_realpath=$xxh_home_realpath echo xxh_version=$xxh_version +echo xxh_home_freespace=`df -k --output=avail $xxh_home_realpath | tail -n1` +echo xxh_plugins_rc=`find $xxh_home_realpath/plugins | grep xonshrc.xsh` + echo bash=`command -v bash` echo rsync=`command -v rsync` echo scp=`command -v scp` -echo xxh_home_freespace=`df -k --output=avail $xxh_home_realpath | tail -n1` \ No newline at end of file diff --git a/xonssh_xxh/settings.py b/xonssh_xxh/settings.py index 9c0a6e2..0671a3c 100644 --- a/xonssh_xxh/settings.py +++ b/xonssh_xxh/settings.py @@ -1,15 +1,11 @@ import sys, os global_settings = { - 'XXH_VERSION': '0.2.2' + 'XXH_VERSION': '0.2.5' } if __name__ == "__main__": - for e in ['XXH_HOME', 'PIP_TARGET', 'PYTHONPATH']: - if e in os.environ: - global_settings[e] = os.environ[e] - if len(sys.argv) > 1: setting_name = sys.argv[1] if setting_name in global_settings: diff --git a/xonssh_xxh/settings.xsh b/xonssh_xxh/settings.xsh new file mode 100644 index 0000000..15987a8 --- /dev/null +++ b/xonssh_xxh/settings.xsh @@ -0,0 +1,11 @@ +import sys + +sys.path.append(os.path.dirname(os.path.realpath(__file__))) +from settings import global_settings + +if __name__ == "__main__": + for e in ['XXH_HOME', 'PIP_TARGET', 'PYTHONPATH']: + if e in ${...}: + global_settings[e] = ${e} + + print(global_settings) \ No newline at end of file diff --git a/xxh b/xxh index 439fd44..c963f5b 100755 --- a/xxh +++ b/xxh @@ -21,8 +21,11 @@ portable_methods = ['appimage'] portable_methods_str = ', '.join(portable_methods) xonsh_bin_name = 'xonsh' -if os.name == 'nt': - print(f"Windows is not supported. WSL1 not recommended also. WSL2 is not tested yet.\nTry to contribution: {url_xxh_github}") +def eprint(*args, **kwargs): + print(*args, file=sys.stderr, **kwargs) + +def eeprint(*args, **kwargs): + print(*args, file=sys.stderr, **kwargs) exit(1) def xonssh(): @@ -35,7 +38,6 @@ def xonssh(): if terminal_cols < 70: return f"\n\nContribution: {url_xxh_github}\n\nPlugins: {url_xxh_plugins_search}" - l,r,s,t = (['@','-','_'][randint(0,2)], ['@','-','_'][randint(0,2)], ['_',' '][randint(0,1)], ['_',''][randint(0,1)]) return f""" @@ -49,7 +51,10 @@ def xonssh(): {' ' if not t else ''} / {'' if not t else ' '} \\________/ / {' ' if not t else ''} /_{t}__________________/ -""" # watch -n.2 xxh --help +""" # watch -n.2 xxh -h + +if os.name == 'nt': + eeprint(f"Windows is not supported. WSL1 is not recommended also. WSL2 is not tested yet.\nContribution: {url_xxh_github}") argp = argparse.ArgumentParser(description=f"The xxh is for using the xonsh shell wherever you go through the ssh. {xonssh()}", formatter_class=RawTextHelpFormatter, prefix_chars='-+') argp.add_argument('--version', '-V', action='version', version=f"xonssh-xxh/{local_xxh_version}") @@ -59,9 +64,10 @@ argp.add_argument('-i', dest='ssh_private_key', help="File from which the identi 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('+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="Delete remote xxh home and install xonsh to destination host.") -argp.add_argument('+lxh','++local-xxh-home', default=local_xxh_home_path, help=f"Local xxh home path. Default: {local_xxh_home_path}") -argp.add_argument('+hxh','++host-xxh-home', default=host_xxh_home_path, help=f"Host xxh home path. Default: {host_xxh_home_path}") +argp.add_argument('+if','++install-force', default=False, action='store_true', help="Removing the host xxh home and install xxh again.") +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('+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.") @@ -69,7 +75,8 @@ argp.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] [+m METHOD] [+v] [+vv] + [+i] [+if] [+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 ')\ .replace('number and exit', 'number and exit\n\nssh arguments:').replace('positional ', 'required ') @@ -79,13 +86,8 @@ opt = argp.parse_args() if opt.vverbose: opt.verbose = True -if not opt.destination: - print('Destination required. Try --help') - exit(1) - if opt.method not in portable_methods: - print(f'Currently supported methods: {portable_methods_str}') - exit(1) + eeprint(f'Currently supported methods: {portable_methods_str}') if 'ssh://' not in opt.destination: opt.destination = f'ssh://{opt.destination}' @@ -94,8 +96,7 @@ url = urlparse(opt.destination) host = url.hostname if not host: - print(f"Wrong distination '{host}'") - exit(1) + eeprint(f"Wrong distination '{host}'") if url.port: opt.ssh_port = url.port @@ -117,11 +118,10 @@ if opt.ssh_options: ssh_arguments += ['-o', ssh_option] if opt.verbose: - print(f'ssh arguments: {ssh_arguments}') + eprint(f'ssh arguments: {ssh_arguments}') if not which('ssh'): - print('Install OpenSSH client before using xxh: https://duckduckgo.com/?q=how+to+install+openssh+client+in+linux') - exit(1) + eeprint('Install OpenSSH client before using xxh: https://duckduckgo.com/?q=how+to+install+openssh+client+in+linux') opt.install = True if opt.install_force else opt.install @@ -133,55 +133,52 @@ package_dir_path = os.path.dirname(os.path.realpath(xonssh_xxh.__file__)) if os.path.exists(local_xxh_home_path): if not os.access(local_xxh_home_path, os.W_OK): - print(f"The local xxh home path isn't writable: {local_xxh_home_path}" ) - exit(1) + eeprint(f"The local xxh home path isn't writable: {local_xxh_home_path}" ) + elif os.path.exists(local_xxh_home_parent): if os.access(local_xxh_home_parent, os.W_OK): - print(f'Create local xxh home path: {local_xxh_home_path}') + eprint(f'Create local xxh home path: {local_xxh_home_path}') mkdir @(ssh_v) -p @(local_xxh_home_path) @(local_xxh_home_path)/plugins else: - print(f"Parent for local xxh home path isn't writable: {local_xxh_home_parent}") - exit(1) + eeprint(f"Parent for local xxh home path isn't writable: {local_xxh_home_parent}") + else: - print(f"Paths aren't writable:\n {local_xxh_home_parent}\n {local_xxh_home_path}") - exit(1) + eeprint(f"Paths aren't writable:\n {local_xxh_home_parent}\n {local_xxh_home_path}") # Fix env to avoid ssh warnings for lc in ['LC_TIME','LC_MONETARY','LC_ADDRESS','LC_IDENTIFICATION','LC_MEASUREMENT','LC_NAME','LC_NUMERIC','LC_PAPER','LC_TELEPHONE']: ${...}[lc] = "POSIX" if os.path.abspath(opt.host_xxh_home) == '/': - print("Host xxh home path {host_xxh_home} looks like /. Please check twice!") - exit(1) + eeprint("Host xxh home path {host_xxh_home} looks like /. Please check twice!") -host_info_sh = os.path.join(package_dir_path, 'host_info.sh') -host_info = $(cat @(host_info_sh) | sed @(f's|_xxh_home_|{opt.host_xxh_home}|') | ssh @(ssh_v) @(ssh_arguments) @(host) -T "bash -s" ).strip() +def get_host_info(): + host_info_sh = os.path.join(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() -if host_info == '': - print(f'Unknown answer from host when getting host info. Check your connection parameters using ordinary ssh.') - exit(1) + if opt.verbose: + eprint(f'host_info: {r}') -host_info = dict([l.split('=') for l in host_info.split('\n')]) + if r == '': + eeprint(f'Unknown answer from host when getting host info. Check your connection parameters using ordinary ssh.') -if opt.verbose: - print(f'host_info: {host_info}') + r = dict([l.split('=') for l in r.split('\n')]) + return r + +host_info = get_host_info() if host_info['xxh_home_realpath'] == '': - print(f'Unknown answer from host when getting realpath for directory {host_xxh_home}') - exit(1) + eeprint(f'Unknown answer from host when getting realpath for directory {host_xxh_home}') if host_info['xxh_version'] == '': - print(f'Unknown answer from host when getting version for directory {host_xxh_home}') - exit(1) + eeprint(f'Unknown answer from host when getting version for directory {host_xxh_home}') if host_info['scp'] == '' and host_info['rsync'] == '': - print(f"There are no rsync or scp on target host. Sad but files can't be uploaded.") - exit(1) + eeprint(f"There are no rsync or scp on target host. Sad but files can't be uploaded.") host_xxh_home = host_info['xxh_home_realpath'] host_xonsh_bin = os.path.join(host_xxh_home, xonsh_bin_name) host_xonshrc = os.path.join(host_xxh_home, 'xonshrc.xsh') -host_xonsh_plugins_rc = os.path.join(host_xxh_home, 'xxh_plugins_rc.xsh') host_xxh_version = host_info['xxh_version'] @@ -206,7 +203,7 @@ if opt.install_force == False: exit(0) elif choice == 'u': local_time = datetime.datetime.now().isoformat()[:19] - print(f"Move {host}:{host_xxh_home} to {host}:{host_xxh_home}-{local_time}") + 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" opt.install = True elif choice == 'f': @@ -215,91 +212,84 @@ if opt.install_force == False: elif choice == 'i': pass else: - print('Unknown answer') - exit(1) + eeprint('Unknown answer') if host_xxh_version == '-1': yn = input(f"{host}:{host_xxh_home} not found. Install xxh? [Y/n] ").strip().lower() if yn == 'y' or yn == '': opt.install = True else: - print('Unknown answer') - exit(1) + eeprint('Unknown answer') if opt.install: - print("\033[0;33m", end='') + eprint("\033[0;33m", end='') if opt.method == 'appimage': local_xonsh_appimage_fullpath = os.path.join(local_xxh_home_path, xonsh_bin_name) if not os.path.isfile(local_xonsh_appimage_fullpath): - print(f'First time download and save xonsh AppImage from {url_appimage}') + eprint(f'First time download and save xonsh AppImage from {url_appimage}') if which('wget'): r=![wget -q --show-progress @(url_appimage) -O @(local_xonsh_appimage_fullpath)] if r.returncode != 0: - print(f'Error while download appimage using wget: {r}') - exit(0) + eeprint(f'Error while download appimage using wget: {r}') elif which('curl'): r=![curl @(url_appimage) -o @(local_xonsh_appimage_fullpath)] if r.returncode != 0: - print(f'Error while download appimage using curl: {r}') - exit(0) + eeprint(f'Error while download appimage using curl: {r}') else: - print('Please install wget or curl and try again. Howto: https://duckduckgo.com/?q=how+to+install+wget+in+linux') - exit(1) + eeprint('Please install wget or curl and try again. Howto: https://duckduckgo.com/?q=how+to+install+wget+in+linux') chmod +x @(local_xonsh_appimage_fullpath) else: - print(f'Method "{opt.method}" is not supported now') + eprint(f'Method "{opt.method}" is not supported now') if opt.install_force: - print(f'Before upload xxh remove host directory {host}:{host_xxh_home}') + eprint(f'Before upload xxh remove host directory {host}:{host_xxh_home}') echo @(f"rm -rf {host_xxh_home}/*") | ssh @(ssh_v) @(ssh_arguments) @(host) -T "bash -s" - print(f"Install xxh to {host}:{host_xxh_home}" ) + eprint(f"Install xxh to {host}:{host_xxh_home}" ) if which('rsync') and host_info['rsync']: - print('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)/ - 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)/ + 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 elif which('scp') and host_info['scp']: - print("Upload using scp. Note: install rsync on local and remote host to increase speed.") + 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) - scp @(ssh_v) @(ssh_arguments) -r -C @([] if opt.verbose else ['-q']) @(package_dir_path)/* @(scp_host) + 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 else: - print('scp or rsync not found!') + eprint('scp or rsync not found!') plugins_fullpath = os.path.join(local_xxh_home_path, 'plugins') if os.path.exists(plugins_fullpath): - print(f'Run plugins post install on {host}') + eprint(f'Run plugins post install on {host}') scripts='' for script in sorted(glob.glob(os.path.join(plugins_fullpath, os.path.join('*','install.xsh')), recursive=True)): scripts += " && %s -i --rc %s -- %s" % (host_xonsh_bin, host_xonshrc, script.replace(local_xxh_home_path + os.sep, '')) - print(f' * {script}') + eprint(f' * {script}') if scripts: - echo @(f"cd {host_xxh_home} {scripts}" ) | ssh @(ssh_v) @(ssh_arguments) @(host) -T "bash -s" + echo @(f"cd {host_xxh_home} {scripts}" ) | ssh @(ssh_v) @(ssh_arguments) @(host) -T "bash -s" 1>&2 - print('Check xonsh') + eprint('Check xonsh') host_settings_file = os.path.join(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) ) if opt.verbose: - print(f'Check xonsh result:\n{check}') + eprint(f'Check xonsh result:\n{check}') if check == '' or 'AppImages require FUSE to run' in check: - print('Check failed. Unpack AppImage...') + eprint('Check failed. Unpack AppImage...') host_xonsh_bin_new = os.path.join(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}") + 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 = host_xonsh_bin_new - print(f'First run xonsh on {host}\033[0m') + host_info = get_host_info() -host_plugins_rc = $(ssh @(ssh_v) @(ssh_arguments) @(host) -t @(host_xonsh_bin) --no-script-cache -i --rc @(host_xonshrc) -- @(host_xonsh_plugins_rc) ) + eprint(f'First run xonsh on {host}\033[0m') -host_plugins_rc_list = host_plugins_rc.split('xxh-plugins#') -if len(host_plugins_rc_list) > 1: - host_plugins_rc_list = host_plugins_rc_list[1] +host_plugins_rc_list = host_info['xxh_plugins_rc'].split(' ') + +if opt.host_execute_file: + ssh @(ssh_v) @(ssh_arguments) @(host) -t @(host_xonsh_bin) --no-script-cache -i --rc @(host_xonshrc) -- @(opt.host_execute_file) else: - print(f'Something went wrong while getting plugins info. Host answer: {host_plugins_rc}') - exit(1) - -ssh @(ssh_v) @(ssh_arguments) @(host) -t @(host_xonsh_bin) --no-script-cache -i --rc @(host_xonshrc) @(host_plugins_rc_list) + ssh @(ssh_v) @(ssh_arguments) @(host) -t @(host_xonsh_bin) --no-script-cache -i --rc @(host_xonshrc) @(host_plugins_rc_list) \ No newline at end of file