source: trunk/tssh/tssh @ 425

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