source: trunk/tssh/tssh

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