source: trunk/tssh/tssh @ 424

Last change on this file since 424 was 424, checked in by g7moreau, 4 years ago
  • Update doc
  • Property svn:keywords set to Id
File size: 11.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
13VERSION='0.2.1'
14
15function usage() {
16   cat <<END_USAGE
17tssh - tmux cluster ssh
18
19 tssh [-w number] [-f] [-v] [-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 remote_command=''
34export ssh_option=''
35export split_number=16
36if which tput > /dev/null
37then
38   export split_number=$(( ($(tput  lines)/ 10) * ($(tput  cols)/ 40) ))
39fi
40
41export dyn_domain=''
42if [ -e "${HOME}/.config/tssh/config.sh" ]
43then
44   . "${HOME}/.config/tssh/config.sh"
45elif [ -e "${HOME}/.tsshrc" ]
46then
47   . "${HOME}/.tsshrc"
48fi
49
50# get options
51if [ $# -eq 0 ]; then usage; exit 1; fi 
52while getopts "w:o:c:fvph" options
53do
54   case ${options} in
55      w)
56         if echo ${OPTARG} | egrep -q '^[[:digit:]]+$' && [ ${OPTARG} -gt 0 ]
57         then
58            export split_number=${OPTARG}
59         else
60            usage
61            exit 2
62         fi
63         ;;
64      c)
65         if echo ${OPTARG} | egrep -q '[[:alpha:]]'
66         then
67            export remote_command=${OPTARG}
68         else
69            usage
70            exit 2
71         fi         
72         ;;
73      p)
74         export parallel='yes'
75         ;;
76      o)
77         if echo ${OPTARG} | egrep -q '[[:alpha:][:digit:]]'
78         then
79            export ssh_option=${OPTARG}
80         else
81            usage
82            exit 2
83         fi
84         ;;
85      f)
86         export fast='yes'
87         ;;
88      v)
89         export verbose='yes'
90         ;;
91      h|*)
92         usage
93         exit 3
94         ;;
95   esac
96done
97shift $((OPTIND - 1))
98[[ $1 = "--" ]] && shift
99
100cd /tmp/
101export base_path=$(mktemp -d tssh.XXXXXX)
102touch "/tmp/${base_path}/master"
103touch "/tmp/${base_path}/master-"
104touch "/tmp/${base_path}/master+"
105touch "/tmp/${base_path}/master--"
106touch "/tmp/${base_path}/master++"
107
108get_host_list () {
109   local cluster
110   local default_mode=''
111
112   # set local mode
113   if echo $1 | grep -- ^--mode=
114   then
115      default_mode=$(echo $1 | grep -- ^--mode= | cut -f 2 -d '=')
116      shift
117   fi
118
119   for host in $*
120   do
121      local mode=${default_mode}
122      local last_char="${host: -1}"
123      if [ "${last_char}" == "-" -o "${last_char}" == "+" ]
124      then
125         mode="${last_char}"
126         host="${host:0:${#host}-1}"
127      fi
128
129      # short host without login part if any
130      local justhost=${host#*@}
131     
132      cluster=$(grep "^${justhost}\b" ${HOME}/.csshrc | cut -f 2 -d '=' | sed -e 's/^[[:space:]]*//;')
133      if [ "${cluster}" == "" ]
134      then
135         # just a host to scan and add
136         if [ "${fast}" != 'yes' -a "${mode}" != '-' ]
137         then
138            # test if exists host
139            if host ${justhost} | grep -q 'not found'
140            then
141               [ "${verbose}" == 'yes' ] && echo "Warning: ${justhost} does not exists"
142               continue
143            fi
144            if ! nmap -p 22 -sT -PN ${justhost} | grep -q '\bopen\b'
145            then
146               if host ${justhost}.${dyn_domain} | grep -q 'not found' || ! nmap -p 22 -sT -PN ${justhost}.${dyn_domain} | grep -q '\bopen\b'
147               then
148                  [ "${verbose}" == 'yes' ] && echo "Warning: ${justhost} is down"
149                  continue
150               else
151                  [ "${verbose}" == 'yes' ] && echo "Warning: remove ssh key of ${justhost}.${dyn_domain}"
152                  host=${justhost}.${dyn_domain}
153                  ssh-keygen -q -R $(LANG=C host ${justhost} | awk '{print $4}')
154               fi
155            fi
156         fi
157         [ "${verbose}" == 'yes' ] && echo "Warning: add ${host} on list with mode ${mode}"
158         echo "${host}" >> "/tmp/${base_path}/master${mode}"
159      else
160         # cluster, jump in a recursive mode
161         [ "${verbose}" == 'yes' ] && echo "Warning: recursive call for cluster ${justhost} (${cluster}), with mode ${mode}"
162         cluster=$(get_host_list --mode=${mode} "${cluster}")
163      fi
164   done
165   }
166declare -fx get_host_list
167
168get_host_list $@
169cat "/tmp/${base_path}/master+" >> "/tmp/${base_path}/master"
170for f in $(grep . "/tmp/${base_path}/master-")
171do
172   egrep "^${f}$" "/tmp/${base_path}/master+" && continue
173   echo "${f}" >> "/tmp/${base_path}/master--"
174done
175for f in $(grep . "/tmp/${base_path}/master")
176do
177   egrep "^${f}$" "/tmp/${base_path}/master--" && continue
178   echo "${f}" >> "/tmp/${base_path}/master++"
179done
180
181if [ "${parallel}" == 'yes' -a -n "${remote_command}" ]
182then
183   if parallel --version 2> /dev/null | grep -q ^'GNU parallel'
184   then
185      sort -u "/tmp/${base_path}/master++" | parallel -j ${split_number} -I {} -- "ssh ${ssh_option} {} ${remote_command}"
186   else
187      sort -u "/tmp/${base_path}/master++" | xargs -r -n 1 -P ${split_number} -I {} -- ssh ${ssh_option} {} "${remote_command}"
188   fi
189
190   # Clean temporary folder
191   [ -d "/tmp/${base_path}" ] && rm -rf "/tmp/${base_path}"
192   
193   exit 0
194fi
195
196
197# split master list in paquet of split_number computer
198sort -u "/tmp/${base_path}/master++" | split -l ${split_number} - /tmp/${base_path}/__splitted_
199
200# wait is needed by time tmux session open and ssh time connection
201tempo=0.8
202first_tempo=0
203other_tempo=0
204if [ -n "${remote_command}" ]
205then
206   # add tempo after remote command
207   other_tempo=${tempo}
208   first_tempo="${tempo} ${other_tempo}"
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_tempo}"
226   # wait ${tempo} second to let new session start...
227   sleep ${tempo}
228
229   for (( i=1 ; i < ${#host[@]} ; i++))
230   do
231      # wait ${tempo} needed in case of ${remote_command}
232      tmux splitw -t $session "ssh ${ssh_option} ${host[$i]} ${remote_command}; sleep ${other_tempo}"
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] [-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 split_number=16
374 export dyn_domain='mycompagny.local'
375 #export ssh_option=''
376 #export fast='yes'
377 #export verbose='yes'
378
379=head1 SEE ALSO
380
381cluster-ssh, cssh, xargs, parallel, tmux
382
383=head1 AUTHORS
384
385Written by Gabriel Moreau, Grenoble - France
386
387=head1 COPYRIGHT
388
389Copyright (C) 2014-2019, LEGI UMR 5519 / CNRS UGA G-INP, Grenoble, France
390Licence : GNU GPL version 2 or later
Note: See TracBrowser for help on using the repository browser.