source: trunk/tssh/tssh @ 416

Last change on this file since 416 was 416, checked in by g7moreau, 4 years ago
  • Add support for parallel and xargs
File size: 10.1 KB
Line 
1#!/bin/bash
2#
3# 2014/03/26 Gabriel Moreau <Gabriel Moreau(A)univ-grenoble-alpes.fr> - Initial release
4#
5# From http://hd-recording.at/dokuwiki/doku.php?id=linux:tmux#tssh
6
7# Clean when Ctrl^C
8trap '[ -n "${base_path}" -a -d "/tmp/${base_path}" ] && rm -rf "/tmp/${base_path}"; exit 4;' QUIT INT TERM
9
10export PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin
11export LANG=C
12
13
14function usage() {
15   cat <<END_USAGE
16NAME
17   $(basename $0) - tmux cluster ssh
18
19SYNOPSIS
20   $0 [-w number] [-f] [-v] [-c remote_cmd] [-o ssh_option] <host1> <host2> <clusterssh class>... <hostM>- <hostN>+
21
22OPTIONS
23   -w windows     windows to open (integer, default 16)
24   -o ssh_option  option to pass to ssh
25   -f             fast, no nmap scan to eliminate sleeping computer
26   -v             verbose
27   -c remote_cmd  launch the remote command on hosts and exit
28   -p             launch in parallel (only with -c) with command parallel or xargs
29   -h             help
30
31DESCRIPTION
32   tssh can be use to launch terminal on many computer in parallel with tmux
33   multiplexer and ssh.
34   The tmux windows is splitted automatically.
35   If you need more computers on the same windows, you can zoom in and out
36   under gnome terminal with Ctrl- or Ctrl+.
37   This must be done before launching tssh.
38   
39   On the command line, you can put host, login@host, clusterssh class.
40   A host or a class can be remove from the list with a dash append
41   and force to be in this one with a plus append.
42   Example with the cluster ssh config below:
43   
44    tssh all team- node005 laptop04+
45
46   Is equivalent to:
47 
48    tssh srv-mail srv-dns srv-imap srv-web srv-proxy \\
49      node001 node002 node003 node004 \\
50      node101 node102 node103 node104 \\
51      node005 laptop04
52
53   The control command for tmux is Ctrl^b.
54   You can switch from broadcast to a local machine with Ctrl^b Ctrl^b
55   and move between machine with Ctrl^b ArrowKey.
56
57DEPENDS
58   On Debian, you need the package
59
60    apt-get install tmux ncurses-bin wamerican nmap
61
62   wamerican (or wfrench...) is used to choose a random word in the file /usr/share/dict/words
63   for each new tmux session.
64
65   ncurses-bin is required for the tput command
66   to automatically split your terminal into several small panels.
67   nmap is only used for dynamic DNS domain and dynamic scan.
68   This is not mandatory for general use.
69
70   By default, tssh use tput to know the number of columns and lines of your terminal.
71   It takes 10 lines and 40 columns for each windows by default.
72   If tput is not installed, the default is 16 windows...
73
74CONFIGURATION
75   The clusterssh config file ~/.csshrc is a key values file.
76   The "clusters" is mandatory for clusterssh (not tssh) and define the other keys.
77   Values could be computer list or other key...
78   
79    clusters = all server s1 s2 s3 node n1 n2 team switch
80    all = server node team
81    server = s1 s2
82    node = n1 n2
83    s1 = srv-mail srv-dns srv-imap
84    s2 = srv-web srv-proxy
85    n1 = node001 node002 node003 node004
86    n2 = node101 node102 node103 node104
87    team = pc01 pc06 laptop04 laptop05 laptop09
88    switch = root@switch01 root@switch05 root@switch17
89
90   The tssh config file (~/.tsshrc) can be use change the default parameters.
91   
92    #export split_number=16
93    export dyn_domain='mycompagny.local'
94    #export ssh_option=''
95    #export fast='yes'
96    #export verbose='yes'
97
98AUTHOR
99   Gabriel Moreau
100
101COPYRIGHT
102   Copyright (C) 2014-2019, LEGI UMR 5519 / CNRS UGA G-INP, Grenoble, France
103   Licence : GNU GPL version 2 or later
104END_USAGE
105   }
106
107export remote_command=''
108export ssh_option=''
109export split_number=16
110if which tput > /dev/null
111then
112   export split_number=$(( ($(tput  lines)/ 10) * ($(tput  cols)/ 40) ))
113fi
114
115export dyn_domain=''
116if [ -e "${HOME}/.tsshrc" ]
117then
118   . "${HOME}/.tsshrc"
119fi
120
121# get options
122if [ $# -eq 0 ]; then usage; exit 1; fi 
123while getopts "w:o:c:fvph" options
124do
125   case ${options} in
126      w)
127         if echo ${OPTARG} | egrep -q '^[[:digit:]]+$' && [ ${OPTARG} -gt 0 ]
128         then
129            export split_number=${OPTARG}
130         else
131            usage
132            exit 2
133         fi
134         ;;
135      c)
136         if echo ${OPTARG} | egrep -q '[[:alpha:]]'
137         then
138            export remote_command=${OPTARG}
139         else
140            usage
141            exit 2
142         fi         
143         ;;
144      p)
145         export parallel='yes'
146         ;;
147      o)
148         if echo ${OPTARG} | egrep -q '[[:alpha:][:digit:]]'
149         then
150            export ssh_option=${OPTARG}
151         else
152            usage
153            exit 2
154         fi
155         ;;
156      f)
157         export fast='yes'
158         ;;
159      v)
160         export verbose='yes'
161         ;;
162      h|*)
163         usage
164         exit 3
165         ;;
166   esac
167done
168shift $((OPTIND - 1))
169[[ $1 = "--" ]] && shift
170
171cd /tmp/
172export base_path=$(mktemp -d tssh.XXXXXX)
173touch "/tmp/${base_path}/master"
174touch "/tmp/${base_path}/master-"
175touch "/tmp/${base_path}/master+"
176touch "/tmp/${base_path}/master--"
177touch "/tmp/${base_path}/master++"
178
179get_host_list () {
180   local cluster
181   local default_mode=''
182
183   # set local mode
184   if echo $1 | grep -- ^--mode=
185   then
186      default_mode=$(echo $1 | grep -- ^--mode= | cut -f 2 -d '=')
187      shift
188   fi
189
190   for host in $*
191   do
192      local mode=${default_mode}
193      local last_char="${host: -1}"
194      if [ "${last_char}" == "-" -o "${last_char}" == "+" ]
195      then
196         mode="${last_char}"
197         host="${host:0:${#host}-1}"
198      fi
199
200      # short host without login part if any
201      local justhost=${host#*@}
202     
203      cluster=$(grep "^${justhost}\b" ${HOME}/.csshrc | cut -f 2 -d '=' | sed -e 's/^[[:space:]]*//;')
204      if [ "${cluster}" == "" ]
205      then
206         # just a host to scan and add
207         if [ "${fast}" != 'yes' -a "${mode}" != '-' ]
208         then
209            # test if exists host
210            if host ${justhost} | grep -q 'not found'
211            then
212               [ "${verbose}" == 'yes' ] && echo "Warning: ${justhost} does not exists"
213               continue
214            fi
215            if ! nmap -p 22 -sT -PN ${justhost} | grep -q '\bopen\b'
216            then
217               if host ${justhost}.${dyn_domain} | grep -q 'not found' || ! nmap -p 22 -sT -PN ${justhost}.${dyn_domain} | grep -q '\bopen\b'
218               then
219                  [ "${verbose}" == 'yes' ] && echo "Warning: ${justhost} is down"
220                  continue
221               else
222                  [ "${verbose}" == 'yes' ] && echo "Warning: remove ssh key of ${justhost}.${dyn_domain}"
223                  host=${justhost}.${dyn_domain}
224                  ssh-keygen -q -R $(LANG=C host ${justhost} | awk '{print $4}')
225               fi
226            fi
227         fi
228         [ "${verbose}" == 'yes' ] && echo "Warning: add ${host} on list with mode ${mode}"
229         echo "${host}" >> "/tmp/${base_path}/master${mode}"
230      else
231         # cluster, jump in a recursive mode
232         [ "${verbose}" == 'yes' ] && echo "Warning: recursive call for cluster ${justhost} (${cluster}), with mode ${mode}"
233         cluster=$(get_host_list --mode=${mode} "${cluster}")
234      fi
235   done
236   }
237declare -fx get_host_list
238
239get_host_list $@
240cat "/tmp/${base_path}/master+" >> "/tmp/${base_path}/master"
241for f in $(grep . "/tmp/${base_path}/master-")
242do
243   egrep "^${f}$" "/tmp/${base_path}/master+" && continue
244   echo "${f}" >> "/tmp/${base_path}/master--"
245done
246for f in $(grep . "/tmp/${base_path}/master")
247do
248   egrep "^${f}$" "/tmp/${base_path}/master--" && continue
249   echo "${f}" >> "/tmp/${base_path}/master++"
250done
251
252if [ "${parallel}" == 'yes' -a -n "${remote_command}" ]
253then
254   if which parallel > /dev/null
255   then
256      sort -u "/tmp/${base_path}/master++" | parallel -j ${split_number} "ssh ${ssh_option} {} ${remote_command}"
257   else
258      sort -u "/tmp/${base_path}/master++" | xargs -n 1 -P ${split_number} -I{} ssh ${ssh_option} {} "${remote_command}"
259   fi
260
261   # Clean temporary folder
262   [ -d "/tmp/${base_path}" ] && rm -rf "/tmp/${base_path}"
263   
264   exit 0
265fi
266
267
268# split master list in paquet of split_number computer
269sort -u "/tmp/${base_path}/master++" | split -l ${split_number} - /tmp/${base_path}/__splitted_
270
271# wait is needed by time tmux session open and ssh time connection
272tempo=0.8
273first_tempo=0
274other_tempo=0
275if [ -n "${remote_command}" ]
276then
277   # add tempo after remote command
278   other_tempo=${tempo}
279   first_tempo="${tempo} ${other_tempo}"
280fi
281
282# loop on each split windows
283for f in $(ls -1 /tmp/${base_path}/ | grep ^__splitted_)
284do
285   if [ "${verbose}" == 'yes' ]
286   then
287      echo "Info: next hosts to be splitted"
288      cat "/tmp/${base_path}/${f}" | sed -e 's/^/  /;'
289      sleep 5
290   fi
291
292   session=$(shuf -n 1 /usr/share/dict/words | tr -cd "[:alpha:]")
293
294   IFS=$'\n' host=($(cat "/tmp/${base_path}/${f}"))
295
296   tmux -2 new-session -d -s $session "ssh ${ssh_option} ${host[0]} ${remote_command}; sleep ${first_tempo}"
297   # wait ${tempo} second to let new session start...
298   sleep ${tempo}
299
300   for (( i=1 ; i < ${#host[@]} ; i++))
301   do
302      # wait ${tempo} needed in case of ${remote_command}
303      tmux splitw -t $session "ssh ${ssh_option} ${host[$i]} ${remote_command}; sleep ${other_tempo}"
304      tmux select-layout tiled
305   done
306
307   tmux set-window-option synchronize-panes on  > /dev/null
308   tmux set-window-option -g utf8 on            > /dev/null
309   tmux set -g default-terminal screen-256color > /dev/null
310   #tmux set-option -g set-clipboard on
311 
312   # Sane scrolling
313   #tmux set -g mode-mouse on
314   #tmux set -g mouse-resize-pane on
315   #tmux set -g mouse-select-pane on
316   #tmux set -g mouse-select-window on
317 
318   #set -g terminal-overrides 'xterm*:smcup@:rmcup@'
319 
320   # toggle mouse mode to allow mouse copy/paste
321   # set mouse on with prefix m
322   tmux bind m \
323      set -g mode-mouse on \; \
324      set -g mouse-select-pane on \; \
325      display 'Mouse: ON' > /dev/null
326      # set -g mouse-resize-pane on \; \
327      #set -g mouse-select-window on \; \
328   # set mouse off with prefix M
329   tmux bind M \
330      set -g mode-mouse off \; \
331      set -g mouse-select-pane off \; \
332      display 'Mouse: OFF' > /dev/null
333      #set -g mouse-resize-pane off \; \
334      #set -g mouse-select-window off \; \
335   # toggle Broadcast
336   tmux bind b set-window-option synchronize-panes
337
338   tmux attach -t $session
339done
340
341# Clean temporary folder
342[ -d "/tmp/${base_path}" ] && rm -rf "/tmp/${base_path}"
Note: See TracBrowser for help on using the repository browser.