Merge pull request #42 from xxh/0.4.0

0.4.0
This commit is contained in:
anki-code 2020-03-04 18:46:01 +03:00 committed by GitHub
commit 735031b7bf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 59 additions and 132 deletions

1
.gitignore vendored
View file

@ -1,3 +1,4 @@
xxh-shell-*
squashfs-root
*.pyc
*.out

View file

@ -1,17 +1,17 @@
<p align="center">You chosen a favorite command shell and spend hours to adjust it, to stuff it with aliases, shortcuts and colors. But when you move from local to remote host using ssh you lose it all. <b>The mission of xxh</b> is to allow you to use your favorite shell with your aliases, shortcuts and color theme wherever you go through the ssh. We use Python-powered <a href="https://xon.sh">xonsh shell</a>.</p>
<p align="center">You chosen a favorite command shell and spend hours to adjust it, to stuff it with aliases, shortcuts and colors. But when you move from local to remote host using ssh you lose it all. <b>The mission of xxh</b> is to allow you to use your favorite shell with your aliases, shortcuts and color theme wherever you go through the ssh.</p>
<p align="center">
<a href="https://pypi.org/project/xonssh-xxh/" target="_blank" alt="PyPI Latest Release"><img src="https://img.shields.io/pypi/v/xonssh-xxh.svg"></a>
<a href="https://asciinema.org/a/osSEzqnmH9pMYEZibNe2K7ZL7" target="_blank"><img alt="asciinema demo" src="https://img.shields.io/badge/demo-asciinema-grass"></a>
<a href="#plugins" target="_blank"><img alt="asciinema demo" src="https://img.shields.io/badge/extensions-plugins-yellow"></a>
<a href="https://gitter.im/xonssh-xxh/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge" target="_blank"><img alt="Chat on Gitter" src="https://badges.gitter.im/xonssh-xxh/community.svg"></a>
<img alt="PyPI - License" src="https://img.shields.io/pypi/l/xonssh-xxh">
<a href="https://pypi.org/project/xonssh-xxh/" target="_blank"><img src="https://img.shields.io/pypi/v/xonssh-xxh.svg" alt="[release]"></a>
<a href="https://asciinema.org/a/osSEzqnmH9pMYEZibNe2K7ZL7" target="_blank"><img alt="[asciinema demo]" src="https://img.shields.io/badge/demo-asciinema-grass"></a>
<a href="#plugins" target="_blank"><img alt="[plugins]" src="https://img.shields.io/badge/extensions-plugins-yellow"></a>
<a href="https://gitter.im/xonssh-xxh/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge" target="_blank"><img alt="[gitter chat]" src="https://badges.gitter.im/xonssh-xxh/community.svg"></a>
<img alt="[BSD license]" src="https://img.shields.io/pypi/l/xonssh-xxh">
</p>
## Install or update
```
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.
## Usage
```
@ -39,28 +39,20 @@ The xxh is for using the xonsh shell wherever you go through the ssh.
/____________________/
```
## Supported shells
Currently supported Python-powered [xonsh shell](https://github.com/xxh/xxh-shell-xonsh-appimage).
Experimental: to use another shell you can create your `xxh-shell` entrypoint like in [xxh-shell-xonsh-appimage](https://github.com/xxh/xxh-shell-xonsh-appimage) and add `+s` argument to your `xxh` command.
## Plugins
**xxh plugin** is the set of xsh scripts which will be run when you'll use xxh. You can create xxh plugin with your lovely aliases, tools or color theme and xxh will bring them to your ssh sessions.
🔎 [Search xxh plugins on Github](https://github.com/search?q=xxh-plugin&type=Repositories) or [Bitbucket](https://bitbucket.org/repo/all?name=xxh-plugin)
💡 [Create xxh plugin](https://github.com/xxh/xxh-plugin-xonsh-sample)
📌 [xxh-plugin-xonsh-pipe-liner](https://github.com/xxh/xxh-plugin-xonsh-pipe-liner) — processing the lines easy with python and classic shell pipes
📌 [xxh-plugin-xonsh-theme-bar](https://github.com/xxh/xxh-plugin-xonsh-theme-bar) — theme to stay focused
📌 [xxh-plugin-xonsh-autojump](https://github.com/xxh/xxh-plugin-xonsh-autojump) — save time on moving thru directories
🔎 [Search xxh plugins on Github](https://github.com/search?q=xxh-plugin&type=Repositories) or [Bitbucket](https://bitbucket.org/repo/all?name=xxh-plugin) or 💡 [Create xxh plugin](https://github.com/xxh/xxh-plugin-xonsh-sample)
## Notes
### Using python, pip and [xontribs](https://xon.sh/xontribs.html)
The xxh is using pip and python from `xonsh.AppImage` by default. You can update pip (`pip install --upgrade pip`) and install packages ordinally: `pip install --upgrade pandas`. The packages will appear in host xxh home `~/.xxh/pip` by default.
To install [xontribs](https://xon.sh/xontribs.html) in xxh session use `xpip install <package>`. Never use `pip` to install xontribs ([details](https://github.com/xonsh/xonsh/issues/3463)).
### How it works?
When you run `xxh myhost` command xxh download portable xonsh and store locally to future use. Then if it needed xxh upload the portable xonsh, init scripts and plugins to the host. Finally xxh make ssh connection to the host and run portable xonsh shell without any system installs and affection on the target host.

View file

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

View file

@ -1,40 +0,0 @@
import sys, argparse
del $LS_COLORS # https://github.com/xonsh/xonsh/issues/3055
$UPDATE_OS_ENVIRON=True
$XXH_HOME = pf"{__file__}".absolute().parent
$PIP_TARGET = $XXH_HOME / 'pip'
$PIP_XONTRIB_TARGET = $PIP_TARGET / 'xontrib'
if not $PIP_XONTRIB_TARGET.exists():
mkdir -p @($PIP_XONTRIB_TARGET)
$PYTHONPATH = $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']
def _xxh_pip(args): # https://github.com/xonsh/xonsh/issues/3463
if args and 'install' in args and '-h' not in args and '--help' not in args:
print('\033[0;33mRun xpip in xontrib safe mode\033[0m')
pip_xontrib_tmp = $PIP_XONTRIB_TARGET.parent / 'xontrib-safe'
mv @($PIP_XONTRIB_TARGET) @(pip_xontrib_tmp)
pip @(args)
mkdir -p @($PIP_XONTRIB_TARGET)
if list(pip_xontrib_tmp.glob('*')):
bash -c $(echo mv @(pip_xontrib_tmp / '*') @($PIP_XONTRIB_TARGET))
rm -r @(pip_xontrib_tmp)
else:
pip @(args)
aliases['xpip'] = _xxh_pip
del _xxh_pip
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(str(plugin_path))

112
xxh
View file

@ -23,14 +23,14 @@ class Xxh:
self.package_dir_path = pf"{xonssh_xxh.__file__}".parent
self.url_xxh_github = 'https://github.com/xxh/xxh'
self.url_xxh_plugins_search = 'https://github.com/search?q=xxh-plugin'
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.shells = ['xxh-shell-xonsh-appimage']
self.shells_str = ', '.join(self.shells)
self._shell = 'xxh-shell-xonsh-appimage'
self.shell_source = 'https://github.com/xxh/xxh-shell-xonsh-appimage.git'
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 = []
@ -38,7 +38,6 @@ class Xxh:
self._password = None
self._verbose = False
self._vverbose = False
self._method = 'appimage'
def snail(self):
try:
@ -173,14 +172,14 @@ class Xxh:
self.sshpass = []
@property
def method(self):
return self._method
def shell(self):
return self._shell
@method.setter
def method(self, value):
if value not in self.portable_methods:
eeprint(f'Currently supported methods: {self.portable_methods_str}')
self._method = value
@shell.setter
def shell(self, value):
if value not in self.shells:
eeprint(f'Currently supported shells: {self.shells_str}')
self._shell = value
@property
def verbose(self):
@ -262,7 +261,8 @@ class Xxh:
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('+s','++shell', default=self.shell, help=f"xxh shell: {self.shells_str}")
argp.add_argument('+ss','++shell-source', default=self.shell_source, help=f"(future) Custom source of xxh-shell: git url or local path")
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>
@ -273,7 +273,7 @@ class Xxh:
[user@]host[:port]
[+i] [+if] [+P PASSWORD] [+PP]
[+lxh LOCAL_XXH_HOME] [+hxh HOST_XXH_HOME] [+he HOST_EXECUTE_FILE]
[+m METHOD] [+v] [+vv]
[+s SHELL] [+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 ')
@ -282,7 +282,8 @@ class Xxh:
self.verbose = opt.verbose
self.vverbose = opt.vverbose
self.method = opt.method
self.shell = opt.shell
self.shell_source = opt.shell_source
self.url = url = self.parse_destination(opt.destination)
username = getpass.getuser()
@ -329,14 +330,13 @@ class Xxh:
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:
if not os.access(local_xxh_home_parent, os.W_OK):
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}")
mkdir @(self.ssh_arg_v) -p @(self.local_xxh_home) @(self.local_xxh_home / 'plugins') @(self.local_xxh_home / 'shells')
# 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"
@ -368,9 +368,6 @@ class Xxh:
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 / self.xonsh_bin_name
host_xonshrc = host_xxh_home / 'xonshrc.xsh'
if opt.install_force == False:
# Check version
ask = False
@ -412,77 +409,54 @@ class Xxh:
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')
eprint(f"Install xxh to {host}:{host_xxh_home}" )
shells_dir = self.local_xxh_home / 'shells'
shells = sorted(shells_dir.glob('*'))
shell_dir = shells_dir / f'{self.shell}'
if not shell_dir.exists():
eprint(f'First time download {self.shell} shell from {self.shell_source}')
if self.shell_source[:6] in ['http:/', 'https:'] and 'git' in self.shell_source:
git clone -q --depth 1 @(self.shell_source) @(shells_dir / self.shell)
elif fp'{self.shell_source}'.exists():
cp -r @(self.shell_source) @(shells_dir)
else:
eeprint(f'Unknown shell source: {self.shell_source}')
shell_build_dir = shell_dir / 'build'
if not shell_build_dir.exists():
eprint(f"First time build {self.shell}")
xonsh @(shell_build_dir.parent / 'build.xsh')
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"
# TODO: check shells list, shell dir
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
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' @(shell_build_dir)/ @(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
@(self.sshpass) scp @(self.ssh_arg_v) @(self.ssh_arguments) -r -C @([] if self.vverbose else ['-q']) @(shell_build_dir)/* @(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)
host_execute_file = ['-f', opt.host_execute_file] if opt.host_execute_file else []
@(self.sshpass) ssh @(self.ssh_arg_v) @(self.ssh_arguments) @(host) -t bash @(str(host_xxh_home)+'/entrypoint.sh') @(host_execute_file)
if __name__ == '__main__':
if os.name == 'nt':