source: trunk/tssh/tssh @ 427

Last change on this file since 427 was 427, checked in by g7moreau, 4 years ago
  • Better doc
  • Property svn:keywords set to Id
File size: 12.0 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.4'
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=head2 C<-w number>
292
293Windows to open (integer, default 16)
294
295=head2 C<-o ssh_option>
296
297Option to pass to ssh
298
299=head2 C<-f>
300
301Fast, no nmap scan to eliminate sleeping computer
302
303=head2 C<-c remote_cmd>
304
305Launch the remote command on hosts and exit
306
307=head2 C<-p>
308
309Launch in parallel (only with option C<-c>) with command parallel or xargs
310
311=head2 C<-v>
312
313Verbose
314
315=head2 C<-h>
316
317Minimal help
318
319
320
321=head1 DESCRIPTION
322
323tssh can be use to launch terminal on many computer in parallel with tmux
324multiplexer and ssh.
325The tmux windows is splitted automatically.
326If you need more computers on the same windows, you can zoom in and out
327under gnome terminal with Ctrl- or Ctrl+.
328This must be done before launching tssh.
329
330On the command line, you can put C<host>, C<login@host>, C<clusterssh class>.
331A host or a class can be remove from the list with a dash append
332and force to be in this one with a plus append.
333Example with the cluster ssh config below:
334
335 tssh all team- node005 laptop04+
336
337Is equivalent to:
338
339 tssh srv-mail srv-dns srv-imap srv-web srv-proxy \\
340   node001 node002 node003 node004 \\
341   node101 node102 node103 node104 \\
342   node005 laptop04
343
344The control command for C<tmux> is C<Ctrl^b>.
345You can switch from broadcast to a local machine with C<Ctrl^b Ctrl^b>
346and move between machine with C<Ctrl^b ArrowKey>.
347
348=head1 DEPENDS
349
350On Debian, you need the package
351
352 apt-get install tmux ncurses-bin wamerican nmap
353
354C<wamerican> (or C<wfrench>...) is used to choose a random word in the file F</usr/share/dict/words>
355for each new tmux session.
356
357C<ncurses-bin> is required for the C<tput> command
358to automatically split your terminal into several small panels.
359C<nmap> is only used for dynamic DNS domain and dynamic scan.
360This is not mandatory for general use.
361
362By default, C<tssh> use C<tput> to know the number of columns and lines of your terminal.
363It takes 10 lines and 40 columns for each windows by default.
364If C<tput> is not installed, the default is 16 windows...
365
366=head1 CONFIGURATION
367
368The clusterssh config file F<~/.csshrc> is a key values file.
369The "clusters" is mandatory for clusterssh (not tssh) and define the other keys.
370Values could be computer list or other key...
371
372 clusters = all server s1 s2 s3 node n1 n2 team switch
373 all = server node team
374 server = s1 s2
375 node = n1 n2
376 s1 = srv-mail srv-dns srv-imap
377 s2 = srv-web srv-proxy
378 n1 = node001 node002 node003 node004
379 n2 = node101 node102 node103 node104
380 team = pc01 pc06 laptop04 laptop05 laptop09
381 switch = root@switch01 root@switch05 root@switch17
382
383The C<tssh> config file (F<~/.config/tssh/config.sh>) can be use change the default parameters.
384
385 #export delay_time=0.8
386 #export split_number=16
387 export dyn_domain='mycompagny.local'
388 #export ssh_option=''
389 #export fast='yes'
390 #export verbose='yes'
391
392=head2 C<delay_time>
393
394When use with C<tmux>,
395add a small delay before lauching the second shell to let C<tmux> open the window (default 0.8)
396
397=head2 C<split_number>
398
399See option C<-w>.
400
401=head2 C<dyn_domain>
402
403If computer doesn't respond on it's normal IP,
404search also to connect on it with this DNS domain.
405Could be use with DNS dynamic domain where computer register inside automatically.
406
407=head2 C<ssh_option>
408
409See option C<-o>.
410
411=head2 C<fast>
412
413See option C<-f>.
414
415=head2 C<verbose>
416
417See option C<-v>.
418
419
420
421=head1 SEE ALSO
422
423cluster-ssh, cssh, xargs, parallel, tmux
424
425Own project ressources:
426
427=over
428
429=item * L<Web site|http://servforge.legi.grenoble-inp.fr/projects/soft-trokata/wiki/SoftWare/Tssh>
430
431=item * L<Online Manual|http://servforge.legi.grenoble-inp.fr/pub/soft-trokata/tssh/tssh.html>
432
433=item * L<SVN repository|http://servforge.legi.grenoble-inp.fr/svn/soft-trokata/trunk/tssh>
434
435=back
436
437
438=head1 AUTHORS
439
440Written by Gabriel Moreau, Grenoble - France
441
442
443=head1 COPYRIGHT
444
445Copyright (C) 2014-2019, LEGI UMR 5519 / CNRS UGA G-INP, Grenoble, France
446Licence : GNU GPL version 2 or later
Note: See TracBrowser for help on using the repository browser.