Docu review done: Fri 26 Jan 2024 04:38:40 PM CET
Table of content
General
This is an extention of ssht which you might have seen already in this documentation.
If not, it is worth to have a look at it ;)
This (sshc
) allows you to kind of the same what cssh
does but in tmux.
Why kind of, well we just started the developing on it, so a bunch of things are still not working a 100% ;)
If you are using
zsh
, we found out, that the ordering of the parameters is now allowing any kind of order. So if you want to run e.g. only a bash without any switch user (-N -s '/bin/bash' -S <server1> <server2> ...
) you have to perform it in exactly this order, otherwiese it will not work out.
By default, when tmux starts, the
synchronize-panes
is set to off. But you can use the keybindiung<prefix>
+_
(or to what ever you defined it with parameter-z
to togle it
Parameters
Parameter | Description |
---|---|
-l [ssh username] | Defines the username which is used for ssh connection like ssh -l (default: whoami ) |
-L [tmux layoutname] | Defines the layout used by tmux (default: main-vertical ) |
-s [switch user command] | Defines switch user command like su - or sudo su - or sudo -i … (default: su - ) |
-S [list of servers] | After -S we expect a list of servers, just write it like this (then tabcomletion works as well) -S host1 host2 host3 |
-N | Disables the password fetching, can be usefull if you have NOPASSWD (not recomended) in your sudors conf |
-P [absolut path to pwd bin] | Defines the path to the bin/scirpt for fetching the pwd e.g. with vault -P vault read |
-r [resize value] | Defines resize used for main-vertical and main-horizontal (default: 30) |
-z [letter] | Defines key for toggle sync panes (default: prefix> + _ ) |
* | This is a placeholder for hostnames, if you dont specify any parameter and only add hostnames it will work as well. |
-h | :help! if you konw what I mean |
Requirements
To make use of sshc
, you some applications or functions are needed in your system
tmux
- Password store with CLI access e.g.
pass
- Being able to generate a list of hosts you want to ssh and fetch passwords e.g. your
~/.ssh/config
Installation
The installation is as simple as it can be (expected that you have tmux
, pwd store and your host list already in place.
Go to a place on your system where you store your .rc
files which get sourced by your shell
and either add there a new file or copy/past the below functions into an existing one.
The function below expects that your ssh host list comes from ~/.ssh/config
, if you dont have it like that, just search for it and replace it with the commands you need.
############################################
#
# TMUX ssh root login
#
function _tmux_login_usage() {
echo "-l <local tmux socket name> # as it says"
echo "-t <tmux connection string> # <session>:<window>(.<pane>)"
echo "-s <ssh remote server> # as it says"
echo "-p <match string> # string that is shown when requesting pwd (default: 'Password:')"
echo "-P <absolut path to pwd bin> # defines the absolutpath to the binary for fetching the pwd"
echo "-h # hows this help"
return 64
}
function tmux_login() {
local optstring="l:t:s:p:P:h"
tmux_socket_name=""
tmux_connect=""
ssh_server_name=""
pwd_question_matcher="Password:"
password_bin=""
while getopts ${optstring} c; do
case ${c} in
l) tmux_socket_name="${OPTARG}" ;;
t) tmux_connect="${OPTARG}" ;;
s) ssh_server_name="${OPTARG}" ;;
p) pwd_question_matcher="${OPTARG}" ;;
P) password_bin="${OPTARG}" ;;
h) _tmux_login_usage ; return 64 ;;
*) _tmux_login_usage ; return 64 ;;
esac
done
local pw
if pw=$("${password_bin}" "${ssh_server_name}") ; then
while_counter=20
while [[ $(tmux -L "${tmux_socket_name}" capture-pane -p -t "${tmux_connect}" | grep -v "^$" | tail -n 1) != "${pwd_question_matcher}" ]]; do
if (( while_counter > 0 )); then
sleep 0.3
let while_counter-=1
else
return 1
fi
done
&>/dev/null tmux -L "${tmux_socket_name}" send-keys -t "${tmux_connect}" "$pw" C-m
&>/dev/null tmux -L "${tmux_socket_name}" send-keys -t "${tmux_connect}" "printf '\033]2;${ssh_server_name}\033\\' ; clear" C-m
else
echo "failed to get pwd for ${ssh_server_name}"
fi
}
function _sshc_compl_zsh() {
ssh_conf_hosts="$(sed -E 's/\*//g;s/ /:ssh hosts;/g' <<< ${=${${${${${(f)"$(cat ~/.ssh/config(n) /dev/null)"}##[A-Z][a-z]* }## *[A-Z][a-z]* *}%%[# ]*}%%--*}//,/ }):ssh hosts"
parameter_compl=('-l:ssh user name' '-s:specify switch user command' '-S:specify ssh target hosts (only needed if parameter are used)' '-N:disable password parsing' '-P:specify the absolutpath to bin for fetching pwds' '-h:shows help')
hosts_compl=(${(@s:;:)ssh_conf_hosts})
_describe '_sshc' parameter_compl -- hosts_compl
}
function _ssht_compl_bash() {
if [ "${#COMP_WORDS[@]}" != "2" ]; then
return
fi
local IFS=$'\n'
local suggestions=($(compgen -W "$(sed -E '/^Host +[a-zA-Z0-9]/!d;s/Host //g' ~/.ssh/config | sort -u)" -- "${COMP_WORDS[1]}"))
if [ "${#suggestions[@]}" == "1" ]; then
local onlyonesuggestion="${suggestions[0]/%\ */}"
COMPREPLY=("${onlyonesuggestion}")
else
for i in "${!suggestions[@]}"; do
suggestions[$i]="$(printf '%*s' "-$COLUMNS" "${suggestions[$i]}")"
done
COMPREPLY=("${suggestions[@]}")
fi
}
function _sshc_usage() {
echo "-l <ssh username> # default: $(whoami)"
echo "-L <tmux layoutname> # specify starting layout of panes (default: main-vertical)"
echo "-s <switch user command> # default: 'su -'"
echo "-S <list of servers> # -S server1 server2 ..."
echo "-N # set if no pwd is needed"
echo "-P <absolut path to pwd bin> # defines the absolutpath to the binary for fetching the pwd"
echo "-r <resice value> # defines resize used for main-vertical and main-horizontal (default: 30)"
echo "-z <letter> # defines key for toggle sync panes (default: <prefix> + '_')"
echo "* # hostnames for remote servers after -S"
echo ""
echo "If no parameter is used, function assumes all arguments are hostnames"
echo ""
echo "-h # hows this help"
return 64
}
function _sshc() {
ssh_user=$(whoami)
su_command="su -"
local optstring="l:L:s:S:P:z:hN"
val_args=$(sed -E 's/://g' <<<"${optstring}")
servers=""
fetch_pwd=true
password_bin="<DEFAULT PWD FETCH COMMAND>"
tmux_layout="main-vertical"
tmux_pane_resize_r="30"
tmux_sync_toggle_key="_"
window_bg_colour_inact='colour236'
window_bg_colour_act='black'
pane_border_fg_colour_act='colour51'
tmux_socket_name="sshc_root_sessions"
if grep -q -E "^-[${val_args}] *[a-zA-Z0-9_-]|^-h *$" <<<"${@}" ; then
while getopts ${optstring} c; do
case ${c} in
l) ssh_user="${OPTARG}" ;;
L) tmux_layout="${OPTARG}" ;;
s) su_command="${OPTARG}" ;;
S) servers=$(sed -E 's/^.* -S ([a-zA-Z0-9_ .-]+)( -.*){,1}/\1/g;s/ -.*//g' <<<"${@}") ;;
N) fetch_pwd=false ;;
P) password_bin="${OPTARG}" ;;
r) tmux_pane_resize_r="${OPTARG}" ;;
z) tmux_sync_toggle_key="${OPTARG}" ;;
h) _sshc_usage ; return 64 ;;
*) _sshc_usage ; return 64 ;;
esac
done
else
servers="${@}"
fi
if ( $fetch_pwd ) ; then
if ! [ -x "${password_bin}" ]; then
echo "${password_bin} is not executeable for your use or does not exist"
return 1
fi
fi
server_first=$(cut -f1 -d\ <<<"${servers}")
servers_ex_first=$(sed -e "s/^${server_first} //g" <<<"${servers}")
session_name="c_$(( ( RANDOM % 9999 ) + 1))"
window_name="$(( ( RANDOM % 9999 ) + 1 ))-$(sed -E 's/\./_/g' <<<"${servers}")"
pane_counter=0
if ( $fetch_pwd ) ; then
( &>/dev/null tmux_login -s "${server_first}" -t "${session_name}:${window_name}.${pane_counter}" -l "${tmux_socket_name}" -P "${password_bin}" & )
fi
&>/dev/null tmux -L "${tmux_socket_name}" new -s "${session_name}" -n "${window_name}" -d "TERM=rxvt ssh -t -l ${ssh_user} ${server_first} \"${su_command}\""
&>/dev/null tmux -L "${tmux_socket_name}" select-layout -t "${session_name}:${window_name}" "tiled"
&>/dev/null tmux -L "${tmux_socket_name}" set window-style "bg=${window_bg_colour_inact}"
&>/dev/null tmux -L "${tmux_socket_name}" set window-active-style "bg=${window_bg_colour_act}"
&>/dev/null tmux -L "${tmux_socket_name}" set pane-active-border-style "fg=${pane_border_fg_colour_act}"
&>/dev/null tmux -L "${tmux_socket_name}" bind-key $tmux_sync_toggle_key set-window-option synchronize-panes
&>/dev/null tmux -L "${tmux_socket_name}" setw window-status-current-format '#{?pane_synchronized,#[bg=red],}#I:#W'
&>/dev/null tmux -L "${tmux_socket_name}" setw window-status-format '#{?pane_synchronized,#[bg=red],}#I:#W'
if ! ( $fetch_pwd ) ; then
( &>/dev/null tmux -L "${tmux_socket_name}" send-keys -t "${session_name}:${window_name}" "printf '\033]2;${server_first}\033\\' ; clear" C-m &)
fi
pane_counter=1
for server in $(echo $servers_ex_first); do
&>/dev/null tmux -L "${tmux_socket_name}" select-layout -t "${session_name}:${window_name}" "tiled"
if ( $fetch_pwd ) ; then
( &>/dev/null tmux_login -s "${server}" -t "${session_name}:${window_name}.${pane_counter}" -l "${tmux_socket_name}" -P "${password_bin}" & )
fi
&>/dev/null tmux -L "${tmux_socket_name}" split-window -t "${session_name}:${window_name}" "TERM=rxvt ssh -t -l ${ssh_user} ${server} \"${su_command}\""
if ! ( $fetch_pwd ) ; then
( &>/dev/null tmux -L "${tmux_socket_name}" send-keys -t "${session_name}:${window_name}.${pane_counter}" "printf '\033]2;${server}\033\\' ; clear" C-m & )
fi
pane_counter=$((pane_counter + 1))
done
&>/dev/null tmux -L "${tmux_socket_name}" set -g pane-border-status
&>/dev/null tmux -L "${tmux_socket_name}" select-layout -t "${session_name}:${window_name}" "${tmux_layout}"
&>/dev/null tmux -L "${tmux_socket_name}" select-pane -t "${session_name}:${window_name}.0"
case "${tmux_layout}" in
"main-vertical" ) &>/dev/null tmux -L "${tmux_socket_name}" resize-pane -t "${session_name}:${window_name}.0" -R "${tmux_pane_resize_r}" ;;
"main-horizontal" ) &>/dev/null tmux -L "${tmux_socket_name}" resize-pane -t "${session_name}:${window_name}.0" -D "${tmux_pane_resize_r}" ;;
esac
&>/dev/null tmux -L "${tmux_socket_name}" attach -t "${session_name}"
}
current_shell="$(ps -o comm -p $$ | tail -1)"
alias sshc="_sshc"
if [[ "${current_shell}" == "zsh" ]]; then
compdef _sshc_compl_zsh _sshc
elif [[ "${current_shell}" == "bash" ]]; then
complete -F _ssht_compl_bash sshc
fi
As an well trained engineer, you saw ofcourse that a different tmux
config was used, ~/.tmux_ssh.conf
Why do we load an additional tmux
config?
Easy to say, this ensures that mod+b
gets unset, so if you run tmux
on the destination server you will controle the remote one and not yours.
It will also sets alertings to inform the shell/terminal about alerts, the status bar will be removed to have the full view of your terminal and the scallback buffer got increased.
Also the title is getting enabled, as the above function will replace it so that your terminal title gets the hostname from the server. This can help you for faster switching to the open connection for example.
This is what it contains:
#tmux source-file .tmux_ssh.conf
unbind C-b
#Set alert if something happens
setw -g monitor-activity on
set -g visual-activity on
# scrollback buffer n lines
set -g history-limit 99999
# enable wm window titles
set -g set-titles on
# center align the window list
set -g status off
# Enable mouse mode to avoide scroll back issues
set -g mouse off
Now the time has tome to source it to your .rc
file.
Usage
To use it, you just have to run sshc
followed either by <tab><tab>
or type the full or beginning of the hostname + tab till you have the full name displayed in your shell, or use the parameters which allow you to modify the behafoir of the script.
Guess what, now just hit the enter key ;)
Small difference to ssht is, that it starts in the background (detached) and after all commands have been executed, it will show up and you will see your tmux with splited panes.
We are still working on the bach completion, to get the parameters in there as well
Sorry, we will work on that ;)
And the nice thing is, if you time (does not matter in which pane) it will do the same actions on all the other panes as well, like cssh
.
When you close now one the connection, all other will be closed too and it will look like before you perfmored the ssht
command.
And we are done.
If you want to know how the tab completion works, have a look at