------------------------------------------------------------------------------- Creating a program wrapper around SSH... ------------------------------------------------------------------------------- SSH Command wrapper -- Why do it? Creating a wrapper around the ssh lets you do a lot of weird and wonderful things, some of which are not obvious until you have a wrapper to play with and can adjust to suit your needs. * Look up the username to use when connecting to a specific host. For example from your list of 'accounts' you already have for file distribution. * Expand hostnames using short personal 'host_aliases'. Yes this can be done using the SSH config file, but with a wrapper you can lookup and replace the hostname using a aliases file, or accounts list. I can then even use that short name in commands that use the ssh wrapper For example: rsync ... backup If "rsync" is using a ssh wrapper (see below) then backup can be looked up and not hard coded. For example: unison ALT_HOME_HOSTNAME If "unison" calls your ssh wrapper script it can replace the hostname with a environment variable (as I do) or other means. I use this to allow "unison" to be launched from either side of the two hosts being synchronized, and still have it handle things (archive files etc) correctly! The hosts may even move around (laptops) on the network. * Resolve using your own list of DNS domains, rather than the system one. That is expand "xyzzy" to mean "xyzzy.domain.you.usually.work.in", if such a host exists in your domain. * Map a hostname to DHCP assigned IP's on your home network. Where you don't actually have a local DNS server running. EG: When you have no DNS on your home network. * Handle an accounts that 'moves' (different IP, and DHCP hostname) This is often handled by Dynamic DNS servers, but that generally costs money. * Set the SSH key correctly for a to use a 'mobile' account. (see "info/apps/ssh.txt" for details) * Add or remove problematic SSH options from commands that call SSH. For example: terminal, Control Pipeline, options that "ansible" uses. * Run the SSH command in a separate Terminal Window. Open a xterm, running the command in it, with a sleep delay at the end. * Jump through a firewall using a separate ssh command on the firewall. That is ssh gets it keys from the firewall host (not a proxy jump). * Start a VNC server, with SSH port forwards, to forward X window display. * Launch or reconnect to a "tmux" or "screen" session (if present). * Transparently run the command as root via "sudo" on the remote server. This is something "ansible" does all the time! * Setup a directories on the remote system before running the supplied remote command, shell, or transferring files. * Transfer and Run a initialisation file, before starting terminal. This can perform a HUGH amount of tasks on the remote system before passing an interactive command to the user, or running the given remote command. All of it without anything being pre-setup on the remote system. See "Better Remote Scripting" in "info/apps/ssh_remote_commands.txt" Also "sshrc" on GitHub https://github.com/IngoHeimbach/sshrc/ I use this system to set up a minimal HOME directory, and environment, on a remote machine, if not already present, then adjusting the 'HOME' setting to use it, before doing whatever was requested. That way I don't 'share' my home setup with other people who may also be using the same account! I have done ALL the above and more at various times and for various reasons, using my own ssh wrapper script. ------------------------------------------------------------------------------- Calling your SSH Wrapper from another program Many programs call SSH direct, and getting them to call you SSH wrapper instead can be tricky, but practically all commands do provide some why to do it. For example I have a "r" ssh wrapper I want these commands to use... scp You must use an command line option (aargh)... scp -Sr ... rsync This can be used as a "scp" replacement in many situations. set environment variable RSYNC_RSH="r -x" rsync option '-e' rsync -e 'r -x' unison In ".unison/default.prf" sshcmd = r sshargs = -q -x cssh In ".clusterssh/config" ssh=r ssh_args= -x -o ConnectTimeout=5 multixterm Use multixterm -xc "r %n" hostname... git set an environment variable (global or before the command) GIT_SSH="r -x" git ... vim netrw (file explorer) Have it use the "rsync" command (set above)... vim rsync://hostname/ OR for vim scp://hostname/... In ".vimrc" configuration, redefine scp command to use. let g:netrw_list_cmd="r USEPORT HOSTNAME ls -Fa1" let g:netrw_scp_cmd="scp -Sr -q" ------------------------------------------------------------------------------- Parsing SSH options (for ssh wrappers) This is the biggest problem in making a SSH Wrapper script work. It is not always needed. For example, the script could be a simple 'add your own special options', though that is typically easier to do via ".ssh/config" file, based on hostname, unless you don't want to do it globally but just for that program calling ssh. I found ssh wrapper script particularly useful for "unison" letting me set the host I'm syncing against with environment variable so I can initiate the "unison" synchronization from either host, or to hosts that may move around (laptops moving between work and home), while using the correct the saved 'file lists' unison creates based on hostname. That is unison uses a 'simple hostname' while the ssh wrapper call replaces that name with the correct network name. If you want to make a wrapper work, you may need to parse at least some of the SSH command line into... SSH Options, Username/Hostname, Remote_Command This can be tricky, as some commands can call SSH in strange and odd ways. Many for example, do not include a '--' option, but relay on the 'hostname' as to mark the end of options (which isn't the case). Then again an old version of "rsync" actually included options AFTER the destination hostname, before the rsync command to run on the remote server. Also usernames can be given as a separate "-l" option (for "rsh" backward compatibility) or as part of the destination as "user@hostname". That is username can be supplied both as an option and as part of the destination (or even BOTH at the same time, which I have made use of!). If you are simply collecting options (to pass on) you can use this, which deals with most ssh arguments known at this time. =======8<-------- ssh_args=() while (( $# > 0 )); do case "$1" in -[1246AaCfgKkMNnqsTtVvXxYy]* ) # SSH option(s) without argument ssh_args+=("$1") ;; -[bcDEeFIiLlmOopQRSWw] ) # SSH option with argument that follows if (( $# == 0 )); then echo "$0: Missing argument for '$1'"; exit 10 fi ssh_args+=("$1" "$2"); shift ;; ;; -[bcDEeFIiLlmOopQRSWw]* ) # SSH option with appended arg ssh_args+=("$1") ;; --) # End of options if [[ -z $destination ]]; then destination="$2"; shift; fi shift; break ;; -*) # unknown option! echo "$0: Unknown Option '$1'"; exit 10 ;; *) # destination or remote command if [[ -z $destination ]]; then destination="$1"; shift; fi break ;; esac shift done if [[ -z $destination ]]; then echo "ERROR: no destination host found" fi printf "ssh arguments:"; printf " %q" "$(ssh_args[@])"; printf "\n" printf "destination: %s\n" "$destination" printf "ssh command:"; printf " %q" "$@"; printf "\n" # Exec to ssh (with whatever changes you make)... # ssh "$(ssh_args[@])" "$destination" "$@" =======8<-------- With the above you can replace or remove specific ssh options. Add your own ssh options, modify 'username' and adjust the '$destination', and modify the command that is run to suit. If you really need to parse the SSH options further, so as find and replace one that is giving you problems. Then you may have to deal with a large variety of complications. Note the '-o' has a very wide variety of possibilities. Examples.. -v -vv -vvv -fN -tt -oOPT -oOPT=arg -oOPT\ arg $'-oOPT\targ' -o OPT -o OPT=arg -o 'OPT arg' -o $'OPT\targ' However in practice calling programs rarely make options that complex and generally keep them separate for better debugging. So start simply and fix your option handling and substitution as you need. --- Example: Replace destination with appropriate acct=$(find_acct ${DEBUG:+'-d'} "$destination") This is a separate script "find_acct", to parse the given destination into the username and FQDN network name of the account I have on that destination server (username is expanded or preserved if given, which it normally isn't). EG: Expand "a@hobbit" into "anthony@us-prod-seet-hobbit.example.com" This also replaces special host aliases like 'backup' or 'UNISON_ALT_HOME' appropriately for things like "unison". See file "info/apps/unison.txt". --- Example: Modify ssh based on a specific username # at top of options loop... -l*) user="${1:2}"; [[ $user ]] || { user="$2"; shift; } ;; I can then test for a special username using... if [[ $user = 'b' || $destination = b@* ]]; then # Do something special! # # In my case double jump SSH via a bastion server, # to a 'root' account on the given hostname. # # Complex as I also add a BIG 'sshrc' script into the ssh command, # to remotely setup my environment (bash aliases etc) on the server! # fi --- Example, add an option to run ssh in a xterm... That is I added my own special argument '-xterm' that re-runs the wrapper within the xterm window. =======8<-------- # At top before main loop xterm_opts=( "$@" ) # in option loop... # ... +xterm) NO_XTERM=true ;; # Internal option, ignore "-xterm" launcher option -xterm) # run the command in a separate xterm window if [[ -z $NO_XTERM ]]; then # if we not in the new xterm already [[ $DEBUG ]] && echo >&2 "Launching Xterm Window" exec xterm -g "${XTERMGEOM:-80x50}" -T xterm \ -e $0 +xterm "${xterm_opts[@]}" & # re-run in the xterm exit 0 fi ;; # ... =======8<-------- -------------------------------------------------------------------------------