mirror of
https://github.com/xxh/xxh
synced 2025-02-17 12:58:27 +00:00
commit
91c82944c3
6 changed files with 447 additions and 281 deletions
|
@ -16,8 +16,6 @@ python3 -m pip install --upgrade xonssh-xxh
|
||||||
```
|
```
|
||||||
🔁 After install you can just using `xxh` command as replace `ssh` to connecting to the host because `xxh` has seamless support of basic `ssh` command arguments.
|
🔁 After install you can just using `xxh` command as replace `ssh` to connecting to the host because `xxh` has seamless support of basic `ssh` command arguments.
|
||||||
|
|
||||||
🗝️ The best if you're using [ssh config with private keys](https://linuxize.com/post/using-the-ssh-config-file/#ssh-config-file-example) to authorization. In case of using password to avoid password typing many times use `+PP` or `+P pwd` options.
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
```
|
```
|
||||||
$ ./xxh -h
|
$ ./xxh -h
|
||||||
|
|
3
setup.py
3
setup.py
|
@ -13,7 +13,8 @@ setuptools.setup(
|
||||||
},
|
},
|
||||||
python_requires='>=3.6',
|
python_requires='>=3.6',
|
||||||
install_requires=[
|
install_requires=[
|
||||||
'xonsh >= 0.9.13'
|
'xonsh >= 0.9.13',
|
||||||
|
'pexpect >= 4.8.0'
|
||||||
],
|
],
|
||||||
platforms='Unix-like',
|
platforms='Unix-like',
|
||||||
scripts=['xxh'],
|
scripts=['xxh'],
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
xxh_home_realpath=`realpath _xxh_home_`
|
xxh_home_realpath=`realpath -m _xxh_home_`
|
||||||
xxh_plugins_path=$xxh_home_realpath/plugins
|
xxh_plugins_path=$xxh_home_realpath/plugins
|
||||||
|
|
||||||
xxh_version='dir_not_found'
|
xxh_version='dir_not_found'
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import sys, os
|
import sys, os
|
||||||
|
|
||||||
global_settings = {
|
global_settings = {
|
||||||
'XXH_VERSION': '0.3.0'
|
'XXH_VERSION': '0.3.1'
|
||||||
}
|
}
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
$UPDATE_OS_ENVIRON=True
|
$UPDATE_OS_ENVIRON=True
|
||||||
|
del $LS_COLORS # https://github.com/xonsh/xonsh/issues/3055
|
||||||
$XXH_HOME = pf"{__file__}".absolute().parent
|
$XXH_HOME = pf"{__file__}".absolute().parent
|
||||||
$PIP_TARGET = $XXH_HOME / 'pip'
|
$PIP_TARGET = $XXH_HOME / 'pip'
|
||||||
$PYTHONPATH = $PIP_TARGET
|
$PYTHONPATH = $PIP_TARGET
|
||||||
|
|
717
xxh
717
xxh
|
@ -1,6 +1,6 @@
|
||||||
#!/usr/bin/env xonsh
|
#!/usr/bin/env xonsh
|
||||||
|
|
||||||
import os, sys, argparse, datetime, getpass
|
import os, sys, argparse, datetime, re, getpass, pexpect
|
||||||
from shutil import which
|
from shutil import which
|
||||||
from sys import exit
|
from sys import exit
|
||||||
from argparse import RawTextHelpFormatter
|
from argparse import RawTextHelpFormatter
|
||||||
|
@ -11,16 +11,6 @@ sys.path.append(str(pf"{__file__}".absolute().parent))
|
||||||
import xonssh_xxh
|
import xonssh_xxh
|
||||||
from xonssh_xxh.settings import global_settings
|
from xonssh_xxh.settings import global_settings
|
||||||
|
|
||||||
url_xxh_github = 'https://github.com/xonssh/xxh'
|
|
||||||
url_xxh_plugins_search = 'https://github.com/search?q=xxh-plugin'
|
|
||||||
url_appimage = 'https://github.com/niess/linuxdeploy-plugin-python/releases/download/continuous/xonsh-x86_64.AppImage'
|
|
||||||
local_xxh_version = global_settings['XXH_VERSION']
|
|
||||||
local_xxh_home_path = '~/.xxh'
|
|
||||||
host_xxh_home_path = '~/.xxh'
|
|
||||||
portable_methods = ['appimage']
|
|
||||||
portable_methods_str = ', '.join(portable_methods)
|
|
||||||
xonsh_bin_name = 'xonsh'
|
|
||||||
|
|
||||||
def eprint(*args, **kwargs):
|
def eprint(*args, **kwargs):
|
||||||
print(*args, file=sys.stderr, **kwargs)
|
print(*args, file=sys.stderr, **kwargs)
|
||||||
|
|
||||||
|
@ -28,300 +18,477 @@ def eeprint(*args, **kwargs):
|
||||||
print(*args, file=sys.stderr, **kwargs)
|
print(*args, file=sys.stderr, **kwargs)
|
||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
def xonssh():
|
class Xxh:
|
||||||
try:
|
def __init__(self):
|
||||||
terminal = os.get_terminal_size()
|
self.package_dir_path = pf"{xonssh_xxh.__file__}".parent
|
||||||
terminal_cols = terminal.columns
|
self.url_xxh_github = 'https://github.com/xonssh/xxh'
|
||||||
except:
|
self.url_xxh_plugins_search = 'https://github.com/search?q=xxh-plugin'
|
||||||
terminal_cols=70
|
self.url_appimage = 'https://github.com/niess/linuxdeploy-plugin-python/releases/download/continuous/xonsh-x86_64.AppImage'
|
||||||
|
self.local_xxh_version = global_settings['XXH_VERSION']
|
||||||
|
self.local_xxh_home = '~/.xxh'
|
||||||
|
self.host_xxh_home = '~/.xxh'
|
||||||
|
self.url = None
|
||||||
|
self.portable_methods = ['appimage']
|
||||||
|
self.portable_methods_str = ', '.join(self.portable_methods)
|
||||||
|
self.xonsh_bin_name = 'xonsh'
|
||||||
|
self.ssh_arguments = []
|
||||||
|
self.ssh_arg_v = []
|
||||||
|
self.sshpass = []
|
||||||
|
self.use_pexpect = True
|
||||||
|
self._password = None
|
||||||
|
self._verbose = False
|
||||||
|
self._vverbose = False
|
||||||
|
self._method = 'appimage'
|
||||||
|
|
||||||
if terminal_cols < 70:
|
def snail(self):
|
||||||
return f"\n\nContribution: {url_xxh_github}\n\nPlugins: {url_xxh_plugins_search}"
|
try:
|
||||||
|
terminal = os.get_terminal_size()
|
||||||
|
terminal_cols = terminal.columns
|
||||||
|
except:
|
||||||
|
terminal_cols=70
|
||||||
|
|
||||||
l,r,s,t = (['@','-','_'][randint(0,2)], ['@','-','_'][randint(0,2)], ['_',' '][randint(0,1)], ['_',''][randint(0,1)])
|
if terminal_cols < 70:
|
||||||
return f"""
|
return f"\n\nContribution: {self.url_xxh_github}\n\nPlugins: {self.url_xxh_plugins_search}"
|
||||||
|
|
||||||
{s}___ __________ {l} {r}
|
l,r,s,t = (['@','-','_'][randint(0,2)], ['@','-','_'][randint(0,2)], ['_',' '][randint(0,1)], ['_',''][randint(0,1)])
|
||||||
{s}_____ / \\ \\__/
|
return f"\n" \
|
||||||
{s}___ / ______ \\ / \\ contribution
|
+f" {s}___ __________ {l} {r}\n" \
|
||||||
{s}____ / / __ \\ \\ / _/ {url_xxh_github}
|
+f" {s}_____ / \\ \\__/\n" \
|
||||||
{s}__ ( / / / \\ \\ /
|
+f" {s}___ / ______ \\ / \\ contribution\n" \
|
||||||
\\ \\___/ / / / plugins
|
+f" {s}____ / / __ \\ \\ / _/ {self.url_xxh_github}\n" \
|
||||||
{' ' if not t else ''} _{t}__\\ /__/ / {url_xxh_plugins_search}
|
+f" {s}__ ( / / / \\ \\ /\n" \
|
||||||
{' ' if not t else ''} / {'' if not t else ' '} \\________/ /
|
+f" \\ \\___/ / / / plugins\n" \
|
||||||
{' ' if not t else ''} /_{t}__________________/
|
+f"{' ' if not t else ''} _{t}__\\ /__/ / {self.url_xxh_plugins_search}\n" \
|
||||||
|
+f"{' ' if not t else ''} / {'' if not t else ' '} \\________/ /\n" \
|
||||||
|
+f"{' ' if not t else ''} /_{t}__________________/\n" \
|
||||||
|
+f"" # d2F0Y2ggLW4uMiB4eGggLWg
|
||||||
|
|
||||||
""" # watch -n.2 xxh -h
|
def pssh(self, cmd, accept_host=None, host_password=None, key_password=None):
|
||||||
|
if self.password:
|
||||||
|
host_password = self.password
|
||||||
|
|
||||||
if os.name == 'nt':
|
if self.vverbose:
|
||||||
eeprint(f"Windows is not supported. WSL1 is not recommended also. WSL2 is not tested yet.\nContribution: {url_xxh_github}")
|
eprint('Try pexpect command: '+cmd)
|
||||||
|
|
||||||
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='-+')
|
sess = pexpect.spawn(cmd)
|
||||||
argp.add_argument('--version', '-V', action='version', version=f"xonssh-xxh/{local_xxh_version}")
|
user_host_accept = None
|
||||||
argp.add_argument('-p', dest='ssh_port', help="Port to connect to on the remote host.")
|
user_host_password = None
|
||||||
argp.add_argument('-l', dest='ssh_login', help="Specifies the user to log in as on the remote machine.")
|
user_key_password = None
|
||||||
argp.add_argument('-i', dest='ssh_private_key', help="File from which the identity (private key) for public key authentication is read.")
|
patterns = ['Are you sure you want to continue connecting.*', "Please type 'yes' or 'no':",
|
||||||
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")
|
'Enter passphrase for key.*', 'password:', pexpect.EOF, '[$#~]', 'Last login.*']
|
||||||
argp.add_argument('destination', metavar='[user@]host[:port]', help="Destination may be specified as [user@]host[:port] or host from ~/.ssh/config")
|
while True:
|
||||||
argp.add_argument('+i','++install', default=False, action='store_true', help="Install xxh to destination host.")
|
try:
|
||||||
argp.add_argument('+if','++install-force', default=False, action='store_true', help="Removing the host xxh home and install xxh again.")
|
i = sess.expect(patterns, timeout=3)
|
||||||
argp.add_argument('+P','++password', help="Password for ssh auth.")
|
except:
|
||||||
argp.add_argument('+PP','++password-prompt', default=False, action='store_true', help="Enter password manually using prompt.")
|
if self.vverbose:
|
||||||
argp.add_argument('+lh','++local-xxh-home', default=local_xxh_home_path, help=f"Local xxh home path. Default: {local_xxh_home_path}")
|
print('Unknown answer details:')
|
||||||
argp.add_argument('+hh','++host-xxh-home', default=host_xxh_home_path, help=f"Host xxh home path. Default: {host_xxh_home_path}")
|
print(sess)
|
||||||
argp.add_argument('+he','++host-execute-file', help=f"Execute script file placed on host and exit.")
|
print('Unknown answer from host')
|
||||||
argp.add_argument('+m','++method', default='appimage', help=f"Portable method: {portable_methods_str}")
|
return {}
|
||||||
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 <host from ~/.ssh/config>
|
|
||||||
|
|
||||||
usage: xxh [ssh arguments] [user@]host[:port] [xxh arguments]
|
if self.vverbose:
|
||||||
|
eprint(f'Pexpect caught pattern: {patterns[i]}')
|
||||||
|
|
||||||
usage: xxh [-h] [-V] [-p SSH_PORT] [-l SSH_LOGIN] [-i SSH_PRIVATE_KEY] [-o SSH_OPTION -o ...]
|
if i in [0,1]:
|
||||||
[user@]host[:port]
|
# Expected:
|
||||||
[+i] [+if] [+P PASSWORD] [+PP]
|
# The authenticity of host '<...>' can't be established.
|
||||||
[+lxh LOCAL_XXH_HOME] [+hxh HOST_XXH_HOME] [+he HOST_EXECUTE_FILE]
|
# ECDSA key fingerprint is <...>
|
||||||
[+m METHOD] [+v] [+vv]
|
# Are you sure you want to continue connecting (yes/no)?
|
||||||
"""
|
print('* '+(sess.before + sess.after).decode("utf-8"), end='')
|
||||||
help = argp.format_help().replace('\n +','\n\nxxh arguments:\n +',1).replace('optional ', 'common ')\
|
if accept_host is None:
|
||||||
.replace('number and exit', 'number and exit\n\nssh arguments:').replace('positional ', 'required ')
|
user_host_accept = input()
|
||||||
argp.format_help = lambda: help
|
sess.sendline(user_host_accept)
|
||||||
opt = argp.parse_args()
|
if user_host_accept == 'yes':
|
||||||
|
user_host_accept = True
|
||||||
|
elif user_host_accept == 'no':
|
||||||
|
user_host_accept = False
|
||||||
|
else:
|
||||||
|
user_host_accept = None
|
||||||
|
elif accept_host:
|
||||||
|
sess.sendline('yes')
|
||||||
|
else:
|
||||||
|
sess.sendline('no')
|
||||||
|
|
||||||
if opt.vverbose:
|
if i == 2:
|
||||||
opt.verbose = True
|
# Expected:
|
||||||
|
# Enter passphrase for key '<keyfile>':
|
||||||
|
if key_password is None:
|
||||||
|
user_key_password = getpass.getpass(prompt='* '+(sess.before + sess.after).decode("utf-8")+' ')
|
||||||
|
sess.sendline(user_key_password)
|
||||||
|
else:
|
||||||
|
sess.sendline(key_password)
|
||||||
|
|
||||||
if opt.method not in portable_methods:
|
if i == 3:
|
||||||
eeprint(f'Currently supported methods: {portable_methods_str}')
|
# Expected:
|
||||||
|
# <host>`s password:
|
||||||
|
if host_password is None:
|
||||||
|
user_host_password = getpass.getpass(prompt='* '+(sess.before + sess.after).decode("utf-8")+' ')
|
||||||
|
sess.sendline(user_host_password)
|
||||||
|
else:
|
||||||
|
sess.sendline(host_password)
|
||||||
|
|
||||||
if 'ssh://' not in opt.destination:
|
if i == 4:
|
||||||
opt.destination = f'ssh://{opt.destination}'
|
# Getting result
|
||||||
|
output = sess.before.decode("utf-8")
|
||||||
|
output = re.sub('\r\nConnection to (.*) closed.\r\r\n', '', output)
|
||||||
|
output = output[:-3] if output.endswith('\r\r\n') else output
|
||||||
|
output = output[3:] if output.startswith(' \r\n') else output
|
||||||
|
result = {
|
||||||
|
'user_host_accept': user_host_accept,
|
||||||
|
'user_host_password':user_host_password,
|
||||||
|
'user_key_password':user_key_password,
|
||||||
|
'output':output
|
||||||
|
}
|
||||||
|
|
||||||
url = urlparse(opt.destination)
|
return result
|
||||||
host = url.hostname
|
|
||||||
|
|
||||||
if not host:
|
if i == [5,6]:
|
||||||
eeprint(f"Wrong distination '{host}'")
|
# Prompt
|
||||||
|
print(sess.before.decode("utf-8"))
|
||||||
|
sess.interact()
|
||||||
|
|
||||||
if url.port:
|
result = {
|
||||||
opt.ssh_port = url.port
|
'user_host_accept': user_host_accept,
|
||||||
|
'user_host_password':user_host_password,
|
||||||
|
'user_key_password':user_key_password
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
|
||||||
if url.username:
|
return {}
|
||||||
opt.ssh_login = url.username
|
|
||||||
|
|
||||||
username = getpass.getuser()
|
@property
|
||||||
if opt.ssh_login:
|
def password(self):
|
||||||
username = opt.ssh_login
|
return self._password
|
||||||
|
|
||||||
ssh_arguments = ['-o', 'StrictHostKeyChecking=accept-new']
|
@password.setter
|
||||||
if not opt.verbose:
|
def password(self, password):
|
||||||
ssh_arguments += ['-o', 'LogLevel=QUIET']
|
self._password = password
|
||||||
if opt.ssh_port:
|
if password:
|
||||||
ssh_arguments += ['-o', f'Port={opt.ssh_port}']
|
if not which('sshpass'):
|
||||||
if opt.ssh_private_key:
|
eeprint('Install sshpass to using password: https://duckduckgo.com/?q=install+sshpass\n'
|
||||||
ssh_arguments += ['-o', f'IdentityFile={opt.ssh_private_key}']
|
+ 'Note! There are a lot of security reasons to stop using password auth.')
|
||||||
if opt.ssh_login:
|
verbose = '-v' if '-v' in self.sshpass else []
|
||||||
ssh_arguments += ['-o', f'User={opt.ssh_login}']
|
self.sshpass = ['sshpass', '-p', password] + verbose
|
||||||
if opt.ssh_options:
|
|
||||||
for ssh_option in opt.ssh_options:
|
|
||||||
ssh_arguments += ['-o', ssh_option]
|
|
||||||
|
|
||||||
if opt.verbose:
|
|
||||||
eprint(f'ssh arguments: {ssh_arguments}')
|
|
||||||
|
|
||||||
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 []
|
|
||||||
|
|
||||||
local_xxh_home_path = pf"{opt.local_xxh_home}"
|
|
||||||
local_xxh_home_parent = local_xxh_home_path.parent
|
|
||||||
package_dir_path = pf"{xonssh_xxh.__file__}".parent
|
|
||||||
|
|
||||||
if local_xxh_home_path.exists():
|
|
||||||
if not os.access(local_xxh_home_path, os.W_OK):
|
|
||||||
eeprint(f"The local xxh home path isn't writable: {local_xxh_home_path}" )
|
|
||||||
elif local_xxh_home_parent.exists():
|
|
||||||
if os.access(local_xxh_home_parent, os.W_OK):
|
|
||||||
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:
|
|
||||||
eeprint(f"Parent for local xxh home path isn't writable: {local_xxh_home_parent}")
|
|
||||||
else:
|
|
||||||
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 pf'{opt.host_xxh_home}' == pf'/':
|
|
||||||
eeprint("Host xxh home path {host_xxh_home} looks like /. Please check twice!")
|
|
||||||
|
|
||||||
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}|') | @(sshpass) ssh @(ssh_v) @(ssh_arguments) @(host) -T "bash -s" ).strip()
|
|
||||||
|
|
||||||
if opt.verbose:
|
|
||||||
eprint(f'Host info:\n{r}')
|
|
||||||
|
|
||||||
if r == '':
|
|
||||||
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
|
|
||||||
|
|
||||||
host_info = get_host_info()
|
|
||||||
|
|
||||||
host_xxh_home = host_info['xxh_home_realpath']
|
|
||||||
host_xxh_version = host_info['xxh_version']
|
|
||||||
|
|
||||||
if host_xxh_home == '':
|
|
||||||
eeprint(f'Unknown answer from host when getting realpath for directory {host_xxh_home}')
|
|
||||||
|
|
||||||
if host_xxh_version == '':
|
|
||||||
eeprint(f'Unknown answer from host when getting version for directory {host_xxh_home}')
|
|
||||||
|
|
||||||
host_xxh_home = pf"{host_xxh_home}"
|
|
||||||
|
|
||||||
if host_info['xxh_home_writable'] == '0' and host_info['xxh_parent_home_writable'] == '0':
|
|
||||||
yn = input(f"{host}:{host_xxh_home} is not writable. Continue? [y/n] ").strip().lower()
|
|
||||||
if yn != 'y':
|
|
||||||
eeprint('Stopped')
|
|
||||||
|
|
||||||
if host_info['scp'] == '' and host_info['rsync'] == '':
|
|
||||||
eeprint(f"There are no rsync or scp on target host. Sad but files can't be uploaded.")
|
|
||||||
|
|
||||||
host_xonsh_bin = host_xxh_home / xonsh_bin_name
|
|
||||||
host_xonshrc = host_xxh_home / 'xonshrc.xsh'
|
|
||||||
|
|
||||||
if opt.install_force == False:
|
|
||||||
# Check version
|
|
||||||
ask = False
|
|
||||||
if host_xxh_version == 'version_not_found':
|
|
||||||
ask = f'Host xxh home is not empty but something went wrong while getting host xxh version.'
|
|
||||||
elif host_xxh_version not in ['dir_not_found','dir_empty'] and host_xxh_version != local_xxh_version:
|
|
||||||
ask = f"Local xxh version '{local_xxh_version}' is not equal host xxh version '{host_xxh_version}'."
|
|
||||||
|
|
||||||
if ask:
|
|
||||||
choice = input(f"{ask} What's next? \n"
|
|
||||||
+ " 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')
|
|
||||||
exit(0)
|
|
||||||
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}") | @(sshpass) ssh @(ssh_v) @(ssh_arguments) @(host) -T "bash -s"
|
|
||||||
opt.install = True
|
|
||||||
elif choice == 'f':
|
|
||||||
opt.install = True
|
|
||||||
opt.install_force = True
|
|
||||||
elif choice == 'i':
|
|
||||||
pass
|
|
||||||
else:
|
else:
|
||||||
eeprint('Unknown answer')
|
self.sshpass = []
|
||||||
|
|
||||||
if host_xxh_version in ['dir_not_found','dir_empty'] and opt.install_force == False:
|
@property
|
||||||
yn = input(f"{host}:{host_xxh_home} not found. Install xxh? [Y/n] ").strip().lower()
|
def method(self):
|
||||||
if yn == 'y' or yn == '':
|
return self._method
|
||||||
opt.install = True
|
|
||||||
else:
|
|
||||||
eeprint('Unknown answer')
|
|
||||||
|
|
||||||
if opt.install:
|
@method.setter
|
||||||
eprint("\033[0;33m", end='')
|
def method(self, value):
|
||||||
if opt.method == 'appimage':
|
if value not in self.portable_methods:
|
||||||
local_xonsh_appimage_fullpath = local_xxh_home_path / xonsh_bin_name
|
eeprint(f'Currently supported methods: {self.portable_methods_str}')
|
||||||
if not local_xonsh_appimage_fullpath.is_file():
|
self._method = value
|
||||||
eprint(f'First time download and save xonsh AppImage from {url_appimage}')
|
|
||||||
if which('wget'):
|
@property
|
||||||
r=![wget -q --show-progress @(url_appimage) -O @(local_xonsh_appimage_fullpath)]
|
def verbose(self):
|
||||||
if r.returncode != 0:
|
return self._verbose
|
||||||
eeprint(f'Error while download appimage using wget: {r}')
|
|
||||||
elif which('curl'):
|
@verbose.setter
|
||||||
r=![curl @(url_appimage) -o @(local_xonsh_appimage_fullpath)]
|
def verbose(self, value):
|
||||||
if r.returncode != 0:
|
self._verbose = value
|
||||||
eeprint(f'Error while download appimage using curl: {r}')
|
if not self._verbose:
|
||||||
|
self.vverbose=False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def vverbose(self):
|
||||||
|
return self._vverbose
|
||||||
|
|
||||||
|
@vverbose.setter
|
||||||
|
def vverbose(self, value):
|
||||||
|
self._vverbose = value
|
||||||
|
if self._vverbose:
|
||||||
|
self.verbose = True
|
||||||
|
self.ssh_arg_v = ['-v']
|
||||||
|
if self.sshpass and ['-v'] not in self.sshpass:
|
||||||
|
self.sshpass += ['-v']
|
||||||
|
else:
|
||||||
|
self.ssh_arg_v = []
|
||||||
|
if '-v' in self.sshpass:
|
||||||
|
self.sshpass.remove('-v')
|
||||||
|
|
||||||
|
def parse_destination(self, destination):
|
||||||
|
destination = f'ssh://{destination}' if 'ssh://' not in destination else destination
|
||||||
|
url = urlparse(destination)
|
||||||
|
return url
|
||||||
|
|
||||||
|
def get_host_info(self):
|
||||||
|
host = self.url.hostname
|
||||||
|
host_info_sh = self.package_dir_path / 'host_info.sh'
|
||||||
|
if self.use_pexpect:
|
||||||
|
cmd = "bash -c 'cat {host_info_sh} | sed \"s|_xxh_home_|{host_xxh_home}|\" | ssh {ssh_v} {ssh_arguments} {host} -T \"bash -s\"'".format(
|
||||||
|
host_info_sh=host_info_sh, host_xxh_home=self.host_xxh_home, ssh_v=('' if not self.ssh_arg_v else '-v'), ssh_arguments=' '.join(self.ssh_arguments), host=host)
|
||||||
|
pr = self.pssh(cmd)
|
||||||
|
|
||||||
|
if pr == {}:
|
||||||
|
eeprint('Unexpected result. Try again with +v')
|
||||||
|
|
||||||
|
if self.verbose:
|
||||||
|
eprint('Pexpect result:')
|
||||||
|
eprint(pr)
|
||||||
|
|
||||||
|
if pr['user_host_password'] is not None:
|
||||||
|
self.password = pr['user_host_password']
|
||||||
|
|
||||||
|
r = pr['output']
|
||||||
|
else:
|
||||||
|
r = $(cat @(host_info_sh) | sed @(f's|_xxh_home_|{self.host_xxh_home}|') | @(self.sshpass) ssh @(self.ssh_arg_v) @(self.ssh_arguments) @(host) -T "bash -s" ).strip()
|
||||||
|
|
||||||
|
if self.verbose:
|
||||||
|
eprint(f'Host info:\n{r}')
|
||||||
|
|
||||||
|
if r == '':
|
||||||
|
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.replace('\r','').split('\n') if l.strip() != '' and '=' in l])
|
||||||
|
|
||||||
|
return r
|
||||||
|
|
||||||
|
def main(self):
|
||||||
|
argp = argparse.ArgumentParser(description=f"The xxh is for using the xonsh shell wherever you go through the ssh. {self.snail()}", formatter_class=RawTextHelpFormatter, prefix_chars='-+')
|
||||||
|
argp.add_argument('--version', '-V', action='version', version=f"xonssh-xxh/{self.local_xxh_version}")
|
||||||
|
argp.add_argument('-p', dest='ssh_port', help="Port to connect to on the remote host.")
|
||||||
|
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 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('+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=self.local_xxh_home, help=f"Local xxh home path. Default: {self.local_xxh_home}")
|
||||||
|
argp.add_argument('+hh','++host-xxh-home', default=self.host_xxh_home, help=f"Host xxh home path. Default: {self.host_xxh_home}")
|
||||||
|
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: {self.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 <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] [+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 ')\
|
||||||
|
.replace('number and exit', 'number and exit\n\nssh arguments:').replace('positional ', 'required ')
|
||||||
|
argp.format_help = lambda: help
|
||||||
|
opt = argp.parse_args()
|
||||||
|
|
||||||
|
self.verbose = opt.verbose
|
||||||
|
self.vverbose = opt.vverbose
|
||||||
|
self.method = opt.method
|
||||||
|
self.url = url = self.parse_destination(opt.destination)
|
||||||
|
|
||||||
|
username = getpass.getuser()
|
||||||
|
host = url.hostname
|
||||||
|
if not host:
|
||||||
|
eeprint(f"Wrong distination '{host}'")
|
||||||
|
if url.port:
|
||||||
|
opt.ssh_port = url.port
|
||||||
|
if url.username:
|
||||||
|
opt.ssh_login = url.username
|
||||||
|
if opt.ssh_login:
|
||||||
|
username = opt.ssh_login
|
||||||
|
|
||||||
|
self.ssh_arguments = ['-o', 'StrictHostKeyChecking=accept-new']
|
||||||
|
if not self.verbose:
|
||||||
|
self.ssh_arguments += ['-o', 'LogLevel=QUIET']
|
||||||
|
if opt.ssh_port:
|
||||||
|
self.ssh_arguments += ['-o', f'Port={opt.ssh_port}']
|
||||||
|
if opt.ssh_private_key:
|
||||||
|
self.ssh_arguments += ['-o', f'IdentityFile={opt.ssh_private_key}']
|
||||||
|
if opt.ssh_login:
|
||||||
|
self.ssh_arguments += ['-o', f'User={opt.ssh_login}']
|
||||||
|
if opt.ssh_options:
|
||||||
|
for ssh_option in opt.ssh_options:
|
||||||
|
self.ssh_arguments += ['-o', ssh_option]
|
||||||
|
|
||||||
|
if self.verbose:
|
||||||
|
eprint(f'ssh arguments: {self.ssh_arguments}')
|
||||||
|
|
||||||
|
if opt.password is not None:
|
||||||
|
self.password = opt.password
|
||||||
|
elif opt.password_prompt:
|
||||||
|
password = ''
|
||||||
|
while not password:
|
||||||
|
password = getpass.getpass(f"Enter {username}@{host}'s password: ")
|
||||||
|
self.password = password
|
||||||
|
|
||||||
|
opt.install = True if opt.install_force else opt.install
|
||||||
|
|
||||||
|
self.local_xxh_home = pf"{opt.local_xxh_home}"
|
||||||
|
local_xxh_home_parent = self.local_xxh_home.parent
|
||||||
|
|
||||||
|
if self.local_xxh_home.exists():
|
||||||
|
if not os.access(self.local_xxh_home, os.W_OK):
|
||||||
|
eeprint(f"The local xxh home path isn't writable: {self.local_xxh_home}" )
|
||||||
|
elif local_xxh_home_parent.exists():
|
||||||
|
if os.access(local_xxh_home_parent, os.W_OK):
|
||||||
|
eprint(f'Create local xxh home path: {self.local_xxh_home}')
|
||||||
|
mkdir @(self.ssh_arg_v) -p @(self.local_xxh_home) @(self.local_xxh_home / 'plugins')
|
||||||
else:
|
else:
|
||||||
eeprint('Please install wget or curl and try again. Howto: https://duckduckgo.com/?q=how+to+install+wget+in+linux')
|
eeprint(f"Parent for local xxh home path isn't writable: {local_xxh_home_parent}")
|
||||||
|
else:
|
||||||
|
eeprint(f"Paths aren't writable:\n {local_xxh_home_parent}\n {self.local_xxh_home}")
|
||||||
|
|
||||||
chmod +x @(local_xonsh_appimage_fullpath)
|
# Fix env to avoid ssh warnings
|
||||||
else:
|
for lc in ['LC_TIME','LC_MONETARY','LC_ADDRESS','LC_IDENTIFICATION','LC_MEASUREMENT','LC_NAME','LC_NUMERIC','LC_PAPER','LC_TELEPHONE']:
|
||||||
eprint(f'Method "{opt.method}" is not supported now')
|
${...}[lc] = "POSIX"
|
||||||
|
|
||||||
if opt.install_force:
|
if pf'{opt.host_xxh_home}' == pf'/':
|
||||||
eprint(f'Remove host xxh home {host}:{host_xxh_home}')
|
eeprint("Host xxh home path {host_xxh_home} looks like /. Please check twice!")
|
||||||
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}" )
|
host_info = self.get_host_info()
|
||||||
|
|
||||||
if host_xxh_version in ['dir_not_found']:
|
if not host_info:
|
||||||
eprint(f'Create xxh home {host_xxh_home}')
|
eeprint(f'Unknown answer from host when getting info')
|
||||||
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"{''.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}/"
|
|
||||||
@(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('Please install rsync or scp!')
|
|
||||||
|
|
||||||
plugins_fullpath = local_xxh_home_path / 'plugins'
|
if 'xxh_home_realpath' not in host_info or host_info['xxh_home_realpath'] == '':
|
||||||
if plugins_fullpath.exists():
|
eeprint(f'Unknown answer from host when getting realpath for directory {host_xxh_home}')
|
||||||
plugin_post_installs = sorted(plugins_fullpath.glob('*/post_install.xsh'))
|
|
||||||
if len(plugin_post_installs) > 0:
|
|
||||||
eprint(f'Run plugins post install on {host}')
|
|
||||||
scripts=''
|
|
||||||
for script in plugin_post_installs:
|
|
||||||
scripts += " && %s -i --rc %s -- %s" % (host_xonsh_bin, host_xonshrc, str(script).replace(str(local_xxh_home_path)+'/', ''))
|
|
||||||
eprint(f' * {script}')
|
|
||||||
|
|
||||||
if scripts:
|
if 'xxh_version' not in host_info or host_info['xxh_version'] == '':
|
||||||
echo @(f"cd {host_xxh_home} {scripts}" ) | @(sshpass) ssh @(ssh_v) @(ssh_arguments) @(host) -T "bash -s" 1>&2
|
eeprint(f'Unknown answer from host when getting version for directory {host_xxh_home}')
|
||||||
|
|
||||||
eprint(f'Check {opt.method}')
|
host_xxh_home = host_info['xxh_home_realpath']
|
||||||
host_settings_file = host_xxh_home / 'settings.py'
|
host_xxh_home = pf"{host_xxh_home}"
|
||||||
check = $(@(sshpass) ssh @(ssh_v) @(ssh_arguments) @(host) -t @(host_xonsh_bin) --no-script-cache -i --rc @(host_xonshrc) -- @(host_settings_file) )
|
host_xxh_version = host_info['xxh_version']
|
||||||
|
|
||||||
if opt.verbose:
|
if host_info['xxh_home_writable'] == '0' and host_info['xxh_parent_home_writable'] == '0':
|
||||||
eprint(f'Check xonsh result:\n{check}')
|
yn = input(f"{host}:{host_xxh_home} is not writable. Continue? [y/n] ").strip().lower()
|
||||||
|
if yn != 'y':
|
||||||
|
eeprint('Stopped')
|
||||||
|
|
||||||
if check == '' or 'AppImages require FUSE to run' in check:
|
if host_info['scp'] == '' and host_info['rsync'] == '':
|
||||||
eprint('AppImage is not supported by host. Trying to unpack and run...')
|
eeprint(f"There are no rsync or scp on target host. Sad but files can't be uploaded.")
|
||||||
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_xonsh_bin = host_xxh_home / self.xonsh_bin_name
|
||||||
|
host_xonshrc = host_xxh_home / 'xonshrc.xsh'
|
||||||
|
|
||||||
host_execute_file = ['--', opt.host_execute_file] if opt.host_execute_file else []
|
if opt.install_force == False:
|
||||||
@(sshpass) ssh @(ssh_v) @(ssh_arguments) @(host) -t @(host_xonsh_bin) --no-script-cache -i --rc @(host_xonshrc) @(host_execute_file)
|
# Check version
|
||||||
|
ask = False
|
||||||
|
if host_xxh_version == 'version_not_found':
|
||||||
|
ask = f'Host xxh home is not empty but something went wrong while getting host xxh version.'
|
||||||
|
elif host_xxh_version not in ['dir_not_found','dir_empty'] and host_xxh_version != self.local_xxh_version:
|
||||||
|
ask = f"Local xxh version '{self.local_xxh_version}' is not equal host xxh version '{host_xxh_version}'."
|
||||||
|
|
||||||
|
if ask:
|
||||||
|
choice = input(f"{ask} What's next? \n"
|
||||||
|
+ " 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')
|
||||||
|
exit(0)
|
||||||
|
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}") | @(self.sshpass) ssh @(self.ssh_arg_v) @(self.ssh_arguments) @(host) -T "bash -s"
|
||||||
|
opt.install = True
|
||||||
|
elif choice == 'f':
|
||||||
|
opt.install = True
|
||||||
|
opt.install_force = True
|
||||||
|
elif choice == 'i':
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
eeprint('Unknown answer')
|
||||||
|
|
||||||
|
if host_xxh_version in ['dir_not_found','dir_empty'] and opt.install_force == False:
|
||||||
|
yn = input(f"{host}:{host_xxh_home} not found. Install xxh? [Y/n] ").strip().lower()
|
||||||
|
if yn == 'y' or yn == '':
|
||||||
|
opt.install = True
|
||||||
|
else:
|
||||||
|
eeprint('Unknown answer')
|
||||||
|
|
||||||
|
if opt.install:
|
||||||
|
eprint("\033[0;33m", end='')
|
||||||
|
if opt.method == 'appimage':
|
||||||
|
local_xonsh_appimage_fullpath = self.local_xxh_home / self.xonsh_bin_name
|
||||||
|
if not local_xonsh_appimage_fullpath.is_file():
|
||||||
|
eprint(f'First time download and save xonsh AppImage from {self.url_appimage}')
|
||||||
|
if which('wget'):
|
||||||
|
r=![wget -q --show-progress @(self.url_appimage) -O @(local_xonsh_appimage_fullpath)]
|
||||||
|
if r.returncode != 0:
|
||||||
|
eeprint(f'Error while download appimage using wget: {r}')
|
||||||
|
elif which('curl'):
|
||||||
|
r=![curl @(self.url_appimage) -o @(local_xonsh_appimage_fullpath)]
|
||||||
|
if r.returncode != 0:
|
||||||
|
eeprint(f'Error while download appimage using curl: {r}')
|
||||||
|
else:
|
||||||
|
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:
|
||||||
|
eprint(f'Method "{opt.method}" is not supported now')
|
||||||
|
|
||||||
|
if opt.install_force:
|
||||||
|
eprint(f'Remove host xxh home {host}:{host_xxh_home}')
|
||||||
|
echo @(f"rm -rf {host_xxh_home}/*") | @(self.sshpass) ssh @(self.ssh_arg_v) @(self.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}") | @(self.sshpass) ssh @(self.ssh_arg_v) @(self.ssh_arguments) @(host) -T "bash -s"
|
||||||
|
|
||||||
|
if which('rsync') and host_info['rsync']:
|
||||||
|
eprint('Upload using rsync')
|
||||||
|
rsync @(self.ssh_arg_v) -e @(f"{''.join(self.sshpass)} ssh {'' if self.ssh_arg_v == [] else '-v'} {' '.join(self.ssh_arguments)}") -az --info=progress2 --include ".*" --exclude='*.pyc' @(self.local_xxh_home)/ @(host):@(host_xxh_home)/ 1>&2
|
||||||
|
rsync @(self.ssh_arg_v) -e @(f"{''.join(self.sshpass)} ssh {'' if self.ssh_arg_v == [] else '-v'} {' '.join(self.ssh_arguments)}") -az --info=progress2 --include ".*" --exclude='*.pyc' @(self.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}/"
|
||||||
|
@(self.sshpass) scp @(self.ssh_arg_v) @(self.ssh_arguments) -r -C @([] if self.vverbose else ['-q']) @(self.local_xxh_home)/* @(scp_host) 1>&2
|
||||||
|
@(self.sshpass) scp @(self.ssh_arg_v) @(self.ssh_arguments) -r -C @([] if self.vverbose else ['-q']) @(self.package_dir_path)/* @(scp_host) 1>&2
|
||||||
|
else:
|
||||||
|
eprint('Please install rsync or scp!')
|
||||||
|
|
||||||
|
plugins_fullpath = self.local_xxh_home / 'plugins'
|
||||||
|
if plugins_fullpath.exists():
|
||||||
|
plugin_post_installs = sorted(plugins_fullpath.glob('*/post_install.xsh'))
|
||||||
|
if len(plugin_post_installs) > 0:
|
||||||
|
eprint(f'Run plugins post install on {host}')
|
||||||
|
scripts=''
|
||||||
|
for script in plugin_post_installs:
|
||||||
|
scripts += " && %s -i --rc %s -- %s" % (host_xonsh_bin, host_xonshrc, str(script).replace(str(self.local_xxh_home)+'/', ''))
|
||||||
|
eprint(f' * {script}')
|
||||||
|
|
||||||
|
if scripts:
|
||||||
|
echo @(f"cd {host_xxh_home} {scripts}" ) | @(self.sshpass) ssh @(self.ssh_arg_v) @(self.ssh_arguments) @(host) -T "bash -s" 1>&2
|
||||||
|
|
||||||
|
eprint(f'Check {opt.method}')
|
||||||
|
host_settings_file = host_xxh_home / 'settings.py'
|
||||||
|
check = $(@(self.sshpass) ssh @(self.ssh_arg_v) @(self.ssh_arguments) @(host) -t @(host_xonsh_bin) --no-script-cache -i --rc @(host_xonshrc) -- @(host_settings_file) )
|
||||||
|
|
||||||
|
if self.vverbose:
|
||||||
|
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/bin/python3'
|
||||||
|
@(self.sshpass) ssh @(self.ssh_arg_v) @(self.ssh_arguments) @(host) -t @(f"cd {host_xxh_home} && ./{self.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 []
|
||||||
|
@(self.sshpass) ssh @(self.ssh_arg_v) @(self.ssh_arguments) @(host) -t @(host_xonsh_bin) --no-script-cache -i --rc @(host_xonshrc) @(host_execute_file)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
if os.name == 'nt':
|
||||||
|
eeprint(f"Windows is not supported. WSL1 is not recommended also. WSL2 is not tested yet.\nContribution: {self.url_xxh_github}")
|
||||||
|
if not which('ssh'):
|
||||||
|
eeprint('Install OpenSSH client before using xxh: https://duckduckgo.com/?q=how+to+install+openssh+client+in+linux')
|
||||||
|
|
||||||
|
xxh = Xxh()
|
||||||
|
xxh.main()
|
Loading…
Add table
Reference in a new issue