├── LICENSE.md ├── README.md └── tmux-cssh /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright 2019 John Hopper, john.hopper@jpserver.net 2 | 3 | Copyright 2018 Dennis Hafemann, dennis.hafemann@gmail.com 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tmux-cssh 2 | _TMUX-C(luster)-SSH_ 3 | 4 | ## Description 5 | 6 | _tmux_ is a _terminal multiplexer_, like e.g. _screen_, which gives you a possibility to use multiple virtual terminal session within one real terminal session. _tmux-cssh_ (tmux-cluster-ssh) sets a comfortable and easy to use functionality, clustering and synchronizing virtual _tmux_-sessions, on top of _tmux_. No need for a x-server or x-forwarding. _tmux-cssh_ works just with _tmux_ and in an low-level terminal-environment, like most server do. 7 | 8 | ## Dependencies / Installation 9 | 10 | ``` 11 | $ apt-cache search --names-only tmux 12 | tmux - Terminal-Multiplexer 13 | ``` 14 | 15 | ... under _debian_-based systems: 16 | ``` 17 | $ sudo apt-get install tmux 18 | ``` 19 | 20 | ## Usage / Example 21 | 22 | First, take a look at the help- and syntax-texts: 23 | ``` 24 | $ tmux-cssh --help 25 | ``` 26 | 27 | You can connect to a single server, with a single connection-data: 28 | 29 | ``` 30 | $ tmux-cssh -sc my-user-name@my-own-server 31 | ``` 32 | 33 | You can connect multiple server, with different connection-data: 34 | ``` 35 | $ tmux-cssh -sc my-user-name@my-own-server -sc second_user@second_server 36 | ``` 37 | 38 | You can connect to multiple server, with a single connection-data: 39 | ``` 40 | $ tmux-cssh -u my-user-name -sc my-own-server -sc second_server 41 | ``` 42 | 43 | You can connect to multiple server, the short way: 44 | ``` 45 | $ tmux-cssh -u my-user-name my_server 1.2.3.4 11.22.33.44 my_second_server my_third_server my_and_so_on_server 46 | ``` 47 | 48 | You can load predefined parameters settings from your user-home-settings-file ~/.tmux-cssh. 49 | ``` 50 | $ tmux-cssh -cs [dev\|test\|productive]_servers 51 | ``` 52 | 53 | If you want to just open your multiple server in a grid, but don't want every keystrokes shared around you can add the parameter for "don't synchronize panes". 54 | ``` 55 | $ tmux-cssh -cs [dev\|test\|productive]_servers -ds 56 | ``` 57 | 58 | It is possible to use different tmux layouts, default is tiled: 59 | ``` 60 | $ tmux-cssh -tl even-vertical -u my-user-name my_server 1.2.3.4 11.22.33.44 my_second_server my_third_server my_and_so_on_server 61 | ``` 62 | 63 | 64 | ## User-Home-Settings-file ~/.tmux-cssh 65 | 66 | This file is located in the user home directory, from _tmux-cssh_-calling user, and includes predefined parameters in a single line. 67 | 68 | ``` 69 | dev_servers:-sc 10.10.1.1 70 | test_servers:-sc 10.20.1.1 -sc 10.20.1.2 71 | productive_servers:-sc 10.30.1.1 -sc 10.30.1.2 -sc 10.30.1.3 72 | ``` 73 | 74 | Each line can be analysed into to values _key_ and _parameters_, seperated by a colon (:). 75 | 76 | `[key]:[parameters]` 77 | 78 | Using the parameters _-cs|--config-setting_ the _key_ name is search via a single string or a (grep valid) regular expression. 79 | 80 | ``` 81 | $ tmux-cssh -cs [dev\|test\|productive]_servers 82 | ... or ... 83 | $ tmux-cssh -cs server[1-9] 84 | ``` 85 | 86 | ### Multiple -cs 87 | 88 | With support of multiple -cs parameters you can now be more flexible. 89 | 90 | ``` 91 | clients:-sc 1.1.1.1 -sc 1.1.1.2 92 | servers:-sc 10.10.10.1 -sc 10.10.10.2 93 | all:-cs clients -cs servers 94 | ``` 95 | 96 | ``` 97 | $ tmux-cssh -cs clients 98 | $ tmux-cssh -cs servers 99 | $ tmux-cssh -cs clients -cs servers 100 | $ tmux-cssh -cs all 101 | ``` 102 | 103 | ## Combine parameters 104 | 105 | _tmux-cssh_ adds all given parameters to it's environment before calling the final _tmux_-session. So that means all parameters can be combined in each way. 106 | 107 | **Call** 108 | 109 | ``` 110 | $ tmux-cssh -f /tmp/temp-server-hosts -cs "fixed_dev_server\|auth_user" 111 | ``` 112 | 113 | **Host file -f /tmp/temp-server-hosts** 114 | 115 | ``` 116 | 10.10.1.1 117 | dev_server_1 118 | ``` 119 | 120 | **Config-Settings name fix_dev_server in ~/.tmux-cssh** 121 | 122 | ``` 123 | auth_user:-u me_as_an_auth_user 124 | fix_dev_server:10.10.1.10 10.10.1.20 10.10.1.30 125 | ``` 126 | 127 | **Finally tmux-cssh works like if you called ...** 128 | 129 | ``` 130 | $ tmux-cssh -u me_as_an_auth_user -sc 10.10.1.1 -sc dev_server_1 -sc 10.10.1.10 -sc 10.10.1.20 -sc 10.10.1.30 131 | ``` 132 | 133 | -------------------------------------------------------------------------------- /tmux-cssh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | THIS_BASENAME=$(basename "$0") 4 | TMUX_SESSION_NAME="tmux-cssh" 5 | HOSTS="" 6 | USER="" 7 | IDENTITY="" 8 | FILENAME="" 9 | SSH_COMMAND="ssh" 10 | SSH_ARGS="" 11 | SYNCHRONIZE_PANES="true" 12 | TMUX_LAYOUT="tiled" 13 | QUIET="false" 14 | 15 | # Config-file in home directory 16 | CONFIG_FILENAME=".tmux-cssh" 17 | USE_CONFIG_FILENAME="$(echo ~)/${CONFIG_FILENAME}" 18 | CONFIG_SETTING_NAME="" 19 | 20 | # Outputs script syntax and help 21 | syntax() { 22 | echo "Syntax: ${THIS_BASENAME} [-h|-u [user]|-c|--certificate|-i|--identity [path to certificate-/identity-file]|-sc [ssh-server-connect-string]|-sa [additional ssh arguments]|-ss,-csc [ssh-command]|-ts [session-name]|-ns|-tl [tmux-layout]|-set|-q|-f [filename]|-cs [name of config-parameters to use]|[-dc,-ds][-cf config-file]]" 23 | echo 24 | 25 | echo "-h | --help This help." 26 | echo 27 | 28 | echo "-u | --user User to use." 29 | echo 30 | 31 | echo "-c | --certificate" 32 | echo "-i | --identity Path to ssh key (identity/-certificate-file) to use." 33 | echo 34 | 35 | echo "-sc | --ssh-server-connect-string SSH-connection-string, multiple." 36 | echo 37 | 38 | echo "-sa | --ssh_args SSH connection arguments, used on every session." 39 | echo 40 | 41 | echo "-ss, -csc |" 42 | echo " --ssh_command, --custom-cssh-command SSH command to use." 43 | echo 44 | 45 | echo "-ts | --tmux-session-name Alternative tmux-session-name, default: ${THIS_BASENAME}" 46 | echo 47 | 48 | echo "-tl | --tmuxlayout Use different layouts (tiled, even-vertical... ) see in man tmux for more information" 49 | echo 50 | 51 | echo "-sw | --splitwindow Create a new Window after the max number of Panes is reached. e.g. -sw 6" 52 | echo 53 | 54 | echo "-set | --set-epoch-time Like -ts, but quickly set the current epoch time, overwrites -ts" 55 | echo 56 | 57 | echo "-ns | --new-session Initializes a new session, like -ts [name]." 58 | echo 59 | 60 | echo "-q | --quiet Quiet-mode." 61 | echo 62 | 63 | echo "-f | --filename Filename of textfile to get -sc connection-strings from, line separated, multiple." 64 | echo 65 | 66 | echo "-cs | --config-setting Name of config-settings which should be gotten from config-file '${USE_CONFIG_FILENAME}'. This can be a grep-regular expression to find the name(s), multiple." 67 | echo 68 | 69 | echo "-dc | --dont-clusterize Don't clusterize ..." 70 | echo "-ds | --dont-synchronize Don't synchronize keyboard in panes, default: tmux should synchronize." 71 | echo 72 | 73 | echo "-cf | --config-file Config-file to use, (Default ${USE_CONFIG_FILENAME}), Designed for option to switch between multiples config-files via one tmux-cssh call." 74 | echo 75 | 76 | echo "* Other arguments will be interpreted as '-sc'." 77 | echo 78 | 79 | echo "* For a maybe newer version of ${THIS_BASENAME} take a look on https://github.com/zinic/tmux-cssh" 80 | echo 81 | } 82 | 83 | # Output given parameters if not in quiet-mode 84 | output() { 85 | if [ "${QUIET}" != "true" ]; then 86 | echo "$@" 87 | fi 88 | } 89 | 90 | # Analyse given parameter into environment 91 | analyseParameters() { 92 | # Walk through parameters 93 | while [ $# -gt 0 ]; do 94 | case "$1" in 95 | -h|--help) syntax; exit;; 96 | -u|--user) USER="$2"; shift;; 97 | -c|--certificate|-i|--identity) IDENTITY="$2"; shift;; 98 | -sc|--ssh-server-connect-string) 99 | if [ "$2" != "" ]; then 100 | if [ -z "$HOSTS" ]; then 101 | HOSTS="$2" 102 | else 103 | HOSTS="${HOSTS},$2" 104 | fi 105 | fi 106 | shift;; 107 | -sa|--ssh_args) SSH_ARGS="${SSH_ARGS} $2"; shift;; 108 | -ss|-csc|--ssh_command|--custom-ssh-command) SSH_COMMAND="$2"; shift;; 109 | -ts|--tmux-session-name) TMUX_SESSION_NAME="$2"; shift;; 110 | -tl|--tmuxlayout) 111 | case "$2" in 112 | h|H) TMUX_LAYOUT="even-horizontal";; 113 | v|V) TMUX_LAYOUT="even-vertical";; 114 | t|T) TMUX_LAYOUT="tiled";; 115 | *) TMUX_LAYOUT="$2";; 116 | esac 117 | shift;; 118 | -sw|--splitwindow) SPLIT_NUM="$2"; shift;; 119 | -set|--set-epoch-time) TMUX_SESSION_NAME="$(date +"%s")";; 120 | -ns|--new-session) TMUX_SESSION_NAME="${TMUX_SESSION_NAME}_$(date +%s)";; 121 | -q|-quiet) QUIET="true";; 122 | -f|--filename) FILENAME="${FILENAME} $2"; shift;; 123 | -cs|--config-setting) 124 | get_parameters_from_config_settings_name "$2" 125 | shift;; 126 | -dc|--dont-clusterize|-ds|--dont-synchronize) SYNCHRONIZE_PANES="false";; 127 | -cf|--config-file) USE_CONFIG_FILENAME="$2"; shift;; 128 | *) 129 | if [ "$1" != "" ]; then 130 | if [ -z "$HOSTS" ]; then 131 | HOSTS="$1" 132 | else 133 | HOSTS="${HOSTS},$1" 134 | fi 135 | fi 136 | ;; 137 | esac 138 | 139 | shift 140 | done 141 | } 142 | 143 | get_parameters_from_config_settings_name() { 144 | configSettingName=$1 145 | 146 | # Check if a config-settings name is given 147 | if [ "${configSettingName}" != "" ]; then 148 | # If home-config-file available 149 | if [ -f "${USE_CONFIG_FILENAME}" ]; then 150 | output "* Searching for config setting name '${configSettingName}' ..." 151 | 152 | # Read every line from config-settings file 153 | analyseParametersString="" 154 | 155 | while read -r configSettingsRow; do 156 | # Seperate Name and parameters 157 | configSettingsName=$(echo "${configSettingsRow}" | cut -d ":" -f 1) 158 | configSettingsParameters=$(echo "${configSettingsRow}" | cut -d ":" -f 2-) 159 | 160 | # Match name against given settings-name 161 | matched=$(echo "${configSettingsName}" | grep "^${configSettingName}$") 162 | 163 | # Matched ? 164 | if [ "${matched}" != "" ]; then 165 | output "* Matched config-settings '${matched}', analysing parameters, ${configSettingsParameters} ..." 166 | analyseParametersString="${analyseParametersString} ${configSettingsParameters}" 167 | fi 168 | done < "${USE_CONFIG_FILENAME}" 169 | 170 | # Analyse parameters string 171 | if [ "${analyseParametersString}" != "" ]; then 172 | analyseParameters "${analyseParametersString}" 173 | fi 174 | else 175 | output "No config-file '${USE_CONFIG_FILENAME}' available." 176 | fi 177 | fi 178 | } 179 | 180 | # Set synchronization for given tmux-session-names 181 | # Parameter: $1 = true if tmux should synchronize panes 182 | # $2 = TMUX-Session-Name 183 | synchronizePanes() { 184 | # Set pane synchronisation 185 | if [ "$1" = "true" ]; then 186 | syncValue='on' 187 | else 188 | syncValue='off' 189 | fi 190 | 191 | tmux set-window-option -t "$2" synchronize-panes ${syncValue} 192 | } 193 | 194 | # Check if tmux is available 195 | if [ -z "$(which tmux)" ]; then 196 | echo "${THIS_BASENAME}" 197 | echo 198 | 199 | echo "TMUX is not avaiable." 200 | echo 201 | 202 | exit; 203 | fi 204 | 205 | # Check main parameters 206 | analyseParameters "$@" 207 | 208 | # Check if SSH-Command is available 209 | if [ -z "$(which "${SSH_COMMAND}")" ]; then 210 | echo "SSH-Command '${SSH_COMMAND}' not found via 'which'." 211 | 212 | exit 213 | fi 214 | 215 | # Check if filenames with connection-strings are given 216 | if [ "$FILENAME" != "" ]; then 217 | output "* Reading file '${FILENAME}' with host-connection-string." 218 | 219 | # Walk through all given filenames 220 | for filename in $FILENAME; do 221 | # Walk through all available connection strings 222 | while read -r connHost; do 223 | output "* Adding host '${connHost}'" 224 | 225 | # Add connection string to currently set hosts 226 | HOSTS="$HOSTS $connHost" 227 | done < "${filename}" 228 | done 229 | fi 230 | 231 | # Check if tmux-session is available 232 | if tmux ls 2> /dev/null | grep -wq "${TMUX_SESSION_NAME}"; then 233 | # Setup synchronizing panes 234 | synchronizePanes ${SYNCHRONIZE_PANES} "${TMUX_SESSION_NAME}" 235 | 236 | # Attach to available tmux-session 237 | tmux attach -t "${TMUX_SESSION_NAME}" 238 | exit 239 | fi 240 | 241 | # Hosts available ? 242 | if [ -z "${HOSTS}" ]; then 243 | output "* Hosts not given." 244 | syntax 245 | 246 | exit 247 | fi 248 | 249 | initTmuxCall="true" 250 | 251 | # Walk through hosts 252 | HOSTS=$(echo "$HOSTS" | tr , ' ') 253 | 254 | for host in ${HOSTS}; do 255 | connectString="" 256 | hostname="" 257 | port="" 258 | 259 | # Separate host and port, if given 260 | if echo "${host}" | grep -q ':'; then 261 | # Remove port from string 262 | hostname=$(echo "$host" | cut -d: -f1) 263 | connectString=${hostname} 264 | 265 | # Remove host from string 266 | port=$(echo "$host" | cut -d: -f2) 267 | else 268 | connectString=${host} 269 | fi 270 | 271 | # Add user-part 272 | if [ "${USER}" != "" ]; then 273 | connectString="${USER}@${connectString}" 274 | fi 275 | 276 | # Port 277 | if [ "${port}" != "" ]; then 278 | connectString="-p ${port} ${connectString}" 279 | fi 280 | 281 | # Add identity-part 282 | if [ "${IDENTITY}" != "" ]; then 283 | connectString="-i ${IDENTITY} ${connectString}" 284 | fi 285 | 286 | # Add ssh_args-part 287 | if [ "${SSH_ARGS}" != "" ]; then 288 | connectString="${SSH_ARGS} ${connectString}" 289 | fi 290 | 291 | # Finalize connect-string 292 | connectString="${SSH_COMMAND} ${connectString}" 293 | 294 | TSN="$(echo "${TMUX_SESSION_NAME}"|cut -d: -f1)" 295 | # Output 296 | output "* Connecting '${connectString}'" 297 | 298 | # First Call, inits the tmux-session 299 | if [ "${initTmuxCall}" = "true" ]; then 300 | tmux new-session -d -s "${TMUX_SESSION_NAME}" "${connectString}" 301 | [ "$SPLIT_NUM" ] && SPLIT_COUNT=1 302 | # If our initial ssh connection has failed, do not mark initTmuxCall as false since tmux will have aborted. 303 | # We'll need to try to start a new tmux session for the next host. 304 | if tmux ls 2> /dev/null | grep -q "${TMUX_SESSION_NAME}"; then 305 | initTmuxCall="false" 306 | fi 307 | else 308 | if [ "$SPLIT_NUM" ] && [ $(( SPLIT_COUNT % SPLIT_NUM )) -eq 0 ]; then 309 | tmux new-window -t "${TSN}" "${connectString}" && \ 310 | tmux select-layout -t "${TMUX_SESSION_NAME}" "${TMUX_LAYOUT}" 311 | TSW="$(tmux lsp -s -t "${TSN}" -F\#I |tail -n 1)" 312 | TMUX_SESSION_NAME="${TSN}:${TSW}" 313 | else 314 | tmux split-window -t "${TMUX_SESSION_NAME}" "${connectString}" && \ 315 | tmux select-layout -t "${TMUX_SESSION_NAME}" "${TMUX_LAYOUT}" 316 | fi 317 | SPLIT_COUNT=$(( SPLIT_COUNT+1 )) 318 | synchronizePanes ${SYNCHRONIZE_PANES} "${TMUX_SESSION_NAME}" 319 | fi 320 | done 321 | 322 | # If no session was able to start, say so and just exit. 323 | if ! tmux ls 2> /dev/null | grep -q "${TSN}"; then 324 | echo "All connections have failed." 325 | exit 1 326 | fi 327 | 328 | # Setup synchronizing panes 329 | synchronizePanes ${SYNCHRONIZE_PANES} "${TMUX_SESSION_NAME}" 330 | 331 | # Attach to tmux session 332 | tmux attach-session -t "${TMUX_SESSION_NAME}" 333 | --------------------------------------------------------------------------------