mirror of
https://github.com/xxh/xxh
synced 2024-11-27 06:00:21 +00:00
commit
735031b7bf
5 changed files with 59 additions and 132 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,3 +1,4 @@
|
|||
xxh-shell-*
|
||||
squashfs-root
|
||||
*.pyc
|
||||
*.out
|
||||
|
|
36
README.md
36
README.md
|
@ -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.
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import sys, os
|
||||
|
||||
global_settings = {
|
||||
'XXH_VERSION': '0.3.6'
|
||||
'XXH_VERSION': '0.4.0'
|
||||
}
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
@ -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
112
xxh
|
@ -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':
|
||||
|
|
Loading…
Reference in a new issue