tm-20180206/0000755000175000001440000000000013236275237011142 5ustar stuuserstm-20180206/LICENSE0000644000175000001440000000243413236275237012152 0ustar stuusers# Copyright (C) 2011, 2012, 2013, 2014 Joerg Jaspert # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # . # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # . # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. tm-20180206/examples/0000755000175000001440000000000013236275237012760 5ustar stuuserstm-20180206/examples/logmon.cfg0000644000175000001440000000063513236275237014740 0ustar stuuserslogmon NONE new-session -d -s SESSION -n SESSION "ssh -t host1 'cd / ; TERM=xterm sudo /some/prog/logmon -t'" split-window -d -t SESSION:TMWIN "ssh -t host2 'cd / ; TERM=xterm sudo /other/prog/logmon -t'" set-window-option -t SESSION:TMWIN automatic-rename off set-window-option -t SESSION:TMWIN allow-rename off set-window-option -t SESSION:TMWIN synchronize-pane select-layout -t SESSION:TMWIN even-horizontal tm-20180206/examples/tmux.conf0000644000175000001440000002117313236275237014630 0ustar stuusers# Ganneffs tmux config # the colors in here DO NEED a 256 colors terminal. I am using # the rxvt from package rxvt-unicode-256color # Screen like Ctrl-a for prefix unbind C-b set -g prefix ^A # And pass it through when pressing twice bind a send-prefix # Allow ^A^c to create a new window, not just ^Ac bind ^c new-window -c "#{pane_current_path}" bind c new-window -c "#{pane_current_path}" # last active window bind-key C-a last-window # Bind function keys. # -n means - no need to press ^A first. bind-key -n C-F1 select-window -t 1 bind-key -n C-F2 select-window -t 2 bind-key -n C-F3 select-window -t 3 bind-key -n C-F4 select-window -t 4 bind-key -n C-F5 select-window -t 5 bind-key -n C-F6 select-window -t 6 bind-key -n C-F7 select-window -t 7 bind-key -n C-F8 select-window -t 8 bind-key -n C-F9 select-window -t 9 bind-key -n C-F10 select-window -t 10 bind-key -n C-F11 select-window -t 11 bind-key -n C-F12 select-window -t 12 # And let Meta-number switch to the pane with that number # This drops the M-1 .. M-5 keys to switch the layout, # i just cycle through that with C-a space when I switch them. bind-key M-1 select-pane -t 1 bind-key M-2 select-pane -t 2 bind-key M-3 select-pane -t 3 bind-key M-4 select-pane -t 4 bind-key M-5 select-pane -t 5 bind-key M-6 select-pane -t 6 bind-key M-7 select-pane -t 7 bind-key M-8 select-pane -t 8 bind-key M-9 select-pane -t 9 bind-key M-0 select-pane -t 10 # vi* style pane movement bind-key h select-pane -L bind-key C-h select-pane -L bind-key j select-pane -D bind-key C-j select-pane -D # Already in use for me as kill-window #bind-key k select-pane -U #bind-key C-k select-pane -U bind-key l select-pane -R bind-key C-l select-pane -R bind-key -r "<" swap-window -t -1 bind-key -r ">" swap-window -t +1 bind-key -r H resize-pane -L 5 bind-key -r J resize-pane -D 5 bind-key -r K resize-pane -U 5 bind-key -r L resize-pane -R 5 bind-key "|" split-window -h -c "#{pane_current_path}" bind-key "-" split-window -v -c "#{pane_current_path}" # Toggle activity monitoring bind-key m setw monitor-activity # Force a window width/height. Good for stupid things like ilo. bind-key C-w setw force-width 80 bind-key C-u setw force-width 0 bind-key C-i setw force-height 0 bind-key C-h setw force-height 24 # In "multi-screen" mode, synchronized panes that is, toggle synced input bind-key C-s setw synchronize-panes # | and - for pane splitting unbind-key % # Remove default binding since we’re replacing bind-key | split-window -h # of course this looses "delete buffer" bind-key - split-window -v # open ssh to somewhere. bind-key S command-prompt -p "SSH Target: " "new-window -n %1 'exec ssh %1'" # reload the config bind-key R source-file ~/.tmux.conf \; display-message "tmux.conf reloaded!" # confirm before killing a window or the server bind-key k confirm kill-window #bind-key K confirm kill-server # open a man page in new window bind-key / command-prompt "split-window 'exec man %%'" # Pipe any output in the active pane into a file bind-key C-p pipe-pane -o 'cat >>~/tmuxoutput.#I-#P' # Less ugly key for the copy mode bind-key Escape copy-mode -u # Start window numbering at 1 set -g base-index 1 # Like base-index, but set the starting index for pane numbers. set-window-option -g pane-base-index 1 # No delay in command sequences set -s escape-time 0 # Rather than constraining window size to the maximum size of any client # connected to the *session*, constrain window size to the maximum size of any # client connected to *that window*. Much more reasonable. setw -g aggressive-resize on # Activity monitoring #setw -g monitor-activity on #set -g visual-activity on # Some default options # Show or hide the status line. set -g status on # Update the status bar every interval seconds. By default, updates # will occur every 15 seconds. A setting of zero disables redrawing at # interval. set -g status-interval 1 #### COLOUR (Solarized 256) # default statusbar colors set-option -g status-bg colour235 #base02 set-option -g status-fg colour136 #yellow set-option -g status-attr default # default window title colors set-window-option -g window-status-fg colour244 #base0 set-window-option -g window-status-bg default #set-window-option -g window-status-attr dim # active window title colors set-window-option -g window-status-current-fg colour166 #orange set-window-option -g window-status-current-bg default #set-window-option -g window-status-current-attr bright # pane border set-option -g pane-border-fg colour235 #base02 set-option -g pane-active-border-fg colour240 #base01 # message text set-option -g message-bg colour235 #base02 set-option -g message-fg colour166 #orange # pane number display set-option -g display-panes-active-colour colour33 #blue set-option -g display-panes-colour colour166 #orange # clock set-window-option -g clock-mode-colour colour64 #green # Display string to the left of the status bar. string will be passed # through strftime(3) before being used. By default, the session name # is shown. string may contain any of the following special character # sequences: # # Character pair Replaced with # #(shell-command) First line of the command's # output # #[attributes] Colour or attribute change # #H Hostname of local host # #h Hostname of local host without # the domain name # #F Current window flag # #I Current window index # #P Current pane index # #S Session name # #T Current window title # #W Current window name # ## A literal `#' # # The #(shell-command) form executes `shell-command' and # inserts the first line of its output. Note that shell # commands are only executed once at the interval specified # by the status-interval option: if the status line is # redrawn in the meantime, the previous result is used. # Shell commands are executed with the tmux global # environment set. # # The window title (#T) is the title set by the program # running within the window using the OSC title setting # sequence, for example: # # $ printf '\033]2;My Title\033\\' # # When a window is first created, its title is the # hostname. # # #[attributes] allows a comma-separated list of attributes # to be specified, these may be `fg=colour' to set the # foreground colour, `bg=colour' to set the background # colour, the name of one of the attributes (listed under # the message-attr option) to turn an attribute on, or an # attribute prefixed with `no' to turn one off, for example # nobright. Examples are: # # #(sysctl vm.loadavg) # #[fg=yellow,bold]#(apm -l)%%#[default] [#S] # # Where appropriate, special character sequences may be # prefixed with a number to specify the maximum length, for # example `#24T'. # # By default, UTF-8 in string is not interpreted, to enable # UTF-8, use the status-utf8 option. #set -g status-left "" #set -g status-right "#(uptime|awk '{print $11}')" #set -g status-right "#[fg=green,bold]%H:%M:%S" # %d-%b-%y set -g status-left '#[fg=colour14,bold]%d-%m-%y %H:%M:%S' set -g status-left-length 42 set -g status-right '#[fg=colour143,bold]#(cut -d " " -f 1-4 /proc/loadavg)#[default] #[default] #[fg=green,bold]#H#[default]' set -g status-right-length 52 # Enable utf8 set -g utf8 on # Instruct tmux to treat top-bit-set characters in the status-left and # status-right strings as UTF-8; notably, this is important for wide # characters. This option defaults to off. set -g status-utf8 on set-window-option -g window-status-format '#P###I:#W#F' set-window-option -g window-status-current-format '#P###I:#W#F' # Monitor for activity in the window. Windows with activity are # highlighted in the status line. #set-window-option -g monitor-activity on # Set the amount of time for which status line messages and other on-screen # indicators are displayed. time is in milliseconds. set -g display-time 3000 # We like zsh set -g default-command zsh # Set the number of error or information messages to save in the message # log for each client. The default is 20. set -g message-limit 100 # If on, ring the terminal bell when an activity, content or silence alert occurs. set -g bell-on-alert on # listen for activity on all windows set -g bell-action any # Set the maximum number of lines held in window history. # This setting applies only to new windows - existing window # histories are not resized and retain the limit at the point # they were created. set -g history-limit 100000 # If on, tmux captures the mouse and allows panes to be resized by # dragging on their borders. # Kills selection, so turned off. set -g mouse-resize-pane off tm-20180206/examples/ganetivms0000644000175000001440000000027713236275237014706 0ustar stuusersvm_foobar NONE LIST ssh ganetimaster sudo /usr/sbin/gnt-instance list --no-headers -o name --filter '("foo" in tags and "bar" in tags)' user@anothermachine LIST cat /home/user/morehosts.list tm-20180206/examples/ws0000644000175000001440000000011613236275237013332 0ustar stuusersworkstations NONE ws02 ws03 ws04 ws05 ws06 ws08 ws09 ws10 ws11 ws12 ws13 ws14 tm-20180206/_tm0000755000175000001440000000613113236275237011650 0ustar stuusers#compdef tm #autoload # Copyright (c) 2013 Joerg Jaspert # Author: Joerg Jaspert # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # . # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # . # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. zmodload zsh/mapfile local expl state curcontext="$curcontext" _tm_done=0 _tm_sess=0 function _tm_get_sessions { typeset -ag _tm_sessions if out=$(tmux list-sessions -F "#{session_name}" 2>/dev/null); then for sess in ${=out}; do _tm_sessions+="${sess}:Existing session ${sess}" done fi _tm_done=1 _tm_sess=$#_tm_sessions local TMDIR=${TMDIR:-"${HOME}/.tmux.d"} for file in ${TMDIR}/*; do _tm_sessions+=${file##*/}:${(f)${(f)mapfile[$file]}[1]} done } function _tm_action { typeset -a actions if [[ $_tm_sess -ne $(tmux list-sessions|wc -l) ]]; then _tm_get_sessions fi ((_tm_done)) || _tm_get_sessions actions=( 'ls:List running sessions' 's:Open ssh session to a single host' 'ms:Open ssh session to multiple hosts' '-l:List running sessions' '-s:Open ssh session to a single host' '-m:Open ssh session to multiple hosts' '-e:use existing session' ${_tm_sessions[@]} ) _describe action actions } function _tm_arguments() { case ${words[1]} in s) _arguments '::hostname:_hosts' ;; ms) _arguments -C '*:hostnames:->hosts' ;; *) echo "lala" ;; esac } function _tm() { _arguments -s\ '-n[Open new multisession even if same exists]' \ '-c+[Setup session according to TMDIR file]:_tm_sessions: ' \ ':action:_tm_action' \ ':session:->session' \ '*::arguments:_tm_arguments' case $state in hosts) _description hosts expl "hosts" _wanted hosts expl hostname _hosts && return ;; session) _tm_get_sessions ;; esac } _tm "$@" tm-20180206/README.org0000644000175000001440000002140513236275237012612 0ustar stuusers* tm - tmux manager / helper This is a medium sized shell script of mine, used to ease my day-to-day work with [[http://tmux.sourceforge.net/][tmux]]. It allows easy handling of various types of tmux sessions, as well as complex setups. ** The boring stuff, license / copyright Copyright (C) 2011, 2012, 2013, 2014 Joerg Jaspert Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: . 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. . THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * Usage As tm started as a very small wrapper around tmux, there wasn't much commandline parsing. Later on it got a getopts style interface tacked onto, so now there is a traditional and a getopts style way of using it. Personally I like the traditional one more... - Traditional :: /home/joerg/bin/tm CMD [host|$anything]... - Getopts :: /home/joerg/bin/tm [-s host] [-m hostlist] [-l] [-n] [-h] [-c config] [-e] [-r REPLACE] ** Traditional #+BEGIN_QUOTE /home/joerg/bin/tm CMD [host]... #+END_QUOTE CMD is one of + ls :: List running sessions + s :: Open ssh session to host + ms :: Open multi ssh sessions to hosts, synchronizing input - If you need to open a second session to the same set of hosts (and not just want to be dropped back into the already existing session), put a -m in front of ms, ie. as first parameter to tm. + $anything :: Either plain tmux session with name of $anything or session according to a TMDIR file ** Getopts #+BEGIN_QUOTE /home/joerg/bin/tm [-s host] [-m hostlist] [-l] [-n] [-h] [-c config] [-e] #+END_QUOTE Options: + -l :: List running sessions + -s host :: Open ssh session to host + -m hostlist :: Open multi ssh sessions to hosts, synchronizing input - Due to the way getopts works, hostlist must be enclosed in "" + -n :: Open a second session to the same set of hosts + -c config :: Setup session according to TMDIR file + -e SESSION :: Use existion session named SESSION + -r REPLACE :: Value to use for replacing in session files ** TMDIR files Each file in $TMDIR, which defaults to =~/.tmux.d/=, defines a tmux session. There are two types of files, those without an extension and those with the extension =.cfg=. The filename corresponds to the commandline =$anything= (or =-c=). *** Extensionless TMDIR files - First line :: Session name - Second line :: extra tmux commandline options - Any following line :: A hostname to open a shell with in the normal ssh syntax. (ie [user@]hostname). The [user@]hostname part can be followed by any option ssh understands. *** .cfg TMDIR files - First line :: Session name - Second line :: extra tmux commandline options - Third line :: The new-session command to use. Place NONE here if you want plain defaults, though that may mean just a shell. Otherwise put the full new-session command with all options you want here. - Any following line :: Any tmux command you can find in the tmux manpage. You should ensure that commands arrive at the right tmux session / window. To help you with this, there are some variables available which you can use, they are replaced with values right before commands are executed: - SESSION :: replaced with the session name - TMWIN :: see below for explanation of TMWIN Environment variable *** External listings of hostnames For both types of TMDIR files the hostname/command lines may start with the word LIST. Everything after it is taken as a shell command and executed as given. The output is read in line by line and added to the list of hostnames/commands already given. This feature works recursive, so be careful to not build a loop! *** Different SSH command / options The environment variable TMSSHCMD can be used to alter the default ssh command and its options used by tm globally. By default it is a plain "ssh". Inside an extensionless TMDIR file and on hosts added to the list using the LIST option described above, ssh options can be set by simply appending them, space seperated, after the hostname. So the hostlist #+BEGIN_QUOTE user@ws01 ws02 root@ws03 -v #+END_QUOTE will open 3 connections, one of which using ssh verbose output. As this may not be enough or one wants a different ssh command just for one TMDIR session, the session file recognizes SSHCMD as a token. The values given after will replace the value of TMSSHCMD for the session defined by the TMDIR file. Note: The last defined SSHCMD in the TMDIR file wins. ** Environment variables recognized by this script: - TMPDIR :: Where tmux stores its session information. DEFAULT: If unset: /tmp - TMSORT :: Should ms sort the hostnames, so it always opens the same session, no matter in which order hostnames are presented. DEFAULT: true - TMOPTS :: Extra options to give to the tmux call. Note that this ONLY affects the final tmux call to attach to the session, not to the earlier ones creating it. DEFAULT: -2 - TMDIR :: Where are session information files stored. DEFAULT: /$HOME/.tmux.d - TMWIN :: Where does your tmux starts numbering its windows? This script tries to find the information in your config, but as it only checks /$HOME/.tmux.conf it might fail. So if your window numbers start at anything different to 0, like mine do at 1, then you can set TMWIN to 1 - TMSESSHOST :: Should the hostname appear in session names? DEFAULT: true - TMSSHCMD :: Allow to globally define a custom ssh command line. This can be just the command or any option one wishes to have everywhere. DEFAULT: ssh ** Replacing of variables in session files In session files you can use the token ++TMREPLACETM++ at any point. This will be replaced by the value of the -r option (if you use getopts style) or by the LAST argument on the line if you use traditional calling. Note that with traditional calling, the argument will also be tried as a hostname, so it may not make much sense there, unless using a session file that contains solely of LIST commands. * Example usage You can find three example config files in the =examples/= subdir of this git repository. The first, =logmon.cfg=, defines a slightly more complex tmux session by giving full tmux commands. It will open a session called logmon, connect to two hosts and run some logmon program there. The tmux window will be split into two panes, their input will be synchronized, so both hosts are controlled at the same time. Additionally some window options are set, and the layout switched to evenly give both hosts window space. The second, =ws=, is an easy file. It defines a session called workstations, and simply opens a tmux window split into multiple panes connecting to a number of workstation hosts. The layout will be tiled and the input will be synchronized, so all hosts are controlled at the same time. A similar session than the above second example can be started by using #+BEGIN_SRC shell tm ms ws02 ws03 ws04 [...] #+END_SRC with the only difference that this needs more typing, so for repeated usage putting it into a file is easier. The third file, =ganetivms=, uses the syntax of the easy files, but only has one hostname defined statically (including a different username than normal) and gets most of the hostnames by first asking a /ganetimaster/ instance for machines that are tagged /foo/ and /bar/ and then adding the contents of a /morehosts.list/ file. Should /morehosts.list/ contain another *LIST* line, it would also execute it and use append its output to the hostlist. A command of #+BEGIN_SRC shell tm s user@host #+END_SRC will open a single ssh session to the given user@host. Later on repeating this command will attach to the old session. * Completion For zsh users tab completion is available. Simply copy the file =_tm= to the right place. This is more likely alpha quality completion, feel free to send patches to make it better. :) tm-20180206/tm0000755000175000001440000006550013236275237011516 0ustar stuusers#!/bin/bash # Copyright (C) 2011, 2012, 2013, 2014, 2016 Joerg Jaspert # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # . # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # . # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # Always exit on errors set -e # Undefined variables, we don't like you set -u # ERR traps are inherited by shell functions, command substitutions and # commands executed in a subshell environment. set -E ######################################################################## # The following variables can be overwritten outside the script. # # We want a useful tmpdir, so set one if it isn't already. Thats the # place where tmux puts its socket, so you want to ensure it doesn't # change under your feet - like for those with a daily-changing tmpdir # in their home... declare -r TMPDIR=${TMPDIR:-"/tmp"} # Do you want me to sort the arguments when opening an ssh/multi-ssh session? # The only use of the sorted list is for the session name, to allow you to # get the same session again no matter how you order the hosts on commandline. declare -r TMSORT=${TMSORT:-"true"} # Want some extra options given to tmux? Define TMOPTS in your environment. # Note, this is only used in the final tmux call where we actually # attach to the session! TMOPTS=${TMOPTS:-"-2"} # The following directory can hold session config for us, so you can use them # as a shortcut. declare -r TMDIR=${TMDIR:-"${HOME}/.tmux.d"} # Should we prepend the hostname to autogenerated session names? # Example: Call tm ms host1 host2. # TMSESSHOST=true -> session name is HOSTNAME_host1_host2 # TMSESSHOST=false -> session name is host1_host2 declare -r TMSESSHOST=${TMSESSHOST:-"true"} # Allow to globally define a custom ssh command line. TMSSHCMD=${TMSSHCMD:-"ssh"} # Debug output declare -r DEBUG=${DEBUG:-"false"} # Save the last argument, it may be used (traditional style) for # replacing args=$# TMREPARG=${!args} # Where does your tmux starts numbering its windows? Mine does at 1, # default for tmux is 0. We try to find it out, but if we fail, (as we # only check $HOME/.tmux.conf you can set this variable to whatever it # is for your environment. if [[ -f ${HOME}/.tmux.conf ]]; then bindex=$(grep ' base-index ' ${HOME}/.tmux.conf || echo 0 ) bindex=${bindex//* } else bindex=0 fi declare TMWIN=${TMWIN:-$bindex} unset bindex ######################################################################## # Nothing below here to configure # Should we group the session to another? Set to true if -g on # commandline GROUPSESSION=false # Should we open another session, even if we already have one with # this name? (Ie. second multisession to the same set of hosts) # This is either set by the getopts option -n or by having -n # as very first parameter after the tm command if [[ $# -ge 1 ]] && [[ "${1}" = "-n" ]]; then DOUBLENAME=true # And now get rid of it. getopts won't see it, as it was first and # we remove it - but it doesn't matter, we set it already. # getopts is only used if it appears somewhere else in the # commandline shift else DOUBLENAME=false fi # Store the first commandline parameter cmdline=${1:-""} # Get the tmux version and split it in major/minor TMUXVERS=$(tmux -V 2>/dev/null || echo "tmux 1.3") declare -r TMUXVERS=${TMUXVERS##* } declare -r TMUXMAJOR=${TMUXVERS%%.*} declare -r TMUXMINOR=${TMUXVERS##*.} # Save IFS declare -r OLDIFS=${IFS} # To save session file data TMDATA="" # Freeform .cfg file or other session file? TMSESCFG="" ######################################################################## function usage() { echo "tmux helper by Joerg Jaspert " echo "There are two ways to call it. Traditional and \"getopts\" style." echo "Traditional call as: $0 CMD [host]...[host]" echo "Getopts call as: $0 [-s host] [-m hostlist] [-k name] [-b session] [-j session] [-l] [-n] [-h] [-c config] [-e]" echo "Note that traditional and getopts can be mixed, sometimes." echo "" echo "Traditional:" echo "CMD is one of" echo " ls List running sessions" echo " s Open ssh session to host" echo " ms Open multi ssh sessions to hosts, synchronizing input" echo " To open a second session to the same set of hosts put a" echo " -n in front of ms" echo " k Kill a session. Note that this needs the exact session name" echo " as shown by tm ls" echo " b Break all panes of given session into single windows in that session" echo " j Join all windows of given session into single window in that session" echo " \$anything Either plain tmux session with name of \$anything or" echo " session according to TMDIR file" echo "" echo "Getopts style:" echo "-l List running sessions" echo "-s host Open ssh session to host" echo "-m hostlist Open multi ssh sessions to hosts, synchronizing input" echo " Due to the way getopts works, hostlist must be enclosed in \"\"" echo "-n Open a second session to the same set of hosts" echo "-g Group session - attach to an existing session, but keep seperate" echo " window control" echo "-k name Kill a session. Note that this needs the exact session name" echo " as shown by tm ls" echo "-b X Break all panes of given session into single windows in that session" echo "-j X Join all windows of given session into single window in that session" echo "-c config Setup session according to TMDIR file" echo "-e SESSION Use existion session named SESSION" echo "-r REPLACE Value to use for replacing in session files" echo "" echo "TMDIR file:" echo "Each file in \$TMDIR defines a tmux session. There are two types of files," echo "those without an extension and those with the extension \".cfg\" (no \"\")." echo "The filename corresponds to the commandline \$anything (or -c)." echo "" echo "Content of extensionless files is defined as:" echo " First line: Session name" echo " Second line: extra tmux commandline options" echo " Any following line: A hostname to open a shell with in the normal" echo " ssh syntax. (ie [user@]hostname)" echo "" echo "Content of .cfg files is defined as:" echo " First line: Session name" echo " Second line: extra tmux commandline options" echo " Third line: The new-session command to use. Place NONE here if you want plain" echo " defaults, though that may mean just a shell. Otherwise put the full" echo " new-session command with all options you want here." echo " Any following line: Any tmux command you can find in the tmux manpage." echo " You should ensure that commands arrive at the right tmux session / window." echo " To help you with this, there are some variables available which you" echo " can use, they are replaced with values right before commands are executed:" echo " SESSION - replaced with the session name" echo " TMWIN - see below for explanation of TMWIN Environment variable" echo "" echo "NOTE: Both types of files accept external listings of hostnames." echo " That is, the output of any shell command given will be used as a list" echo " of hostnames to connect to (or a set of tmux commands to run)." echo "" echo "NOTE: Session files can include the Token ++TMREPLACETM++ at any point. This" echo " will be replaced by the value of the -r option (if you use getopts style) or" echo " by the LAST argument on the line if you use traditional calling." echo " Note that with traditional calling, the argument will also be tried as a hostname," echo " so it may not make much sense there, unless using a session file that contains" echo " solely of LIST commands." echo "" echo "NOTE: Session files can include any existing environment variable at any point (but" echo " only one per line). Those get replaced during tm execution time with the actual" echo " value of the environment variable. Common usage is $HOME, but any existing var" echo " works fine." echo "" echo "Environment variables recognized by this script:" echo "TMPDIR - Where tmux stores its session information" echo " DEFAULT: If unset: /tmp" echo "TMSORT - Should ms sort the hostnames, so it always opens the same" echo " session, no matter in which order hostnames are presented" echo " DEFAULT: true" echo "TMOPTS - Extra options to give to the tmux call" echo " Note that this ONLY affects the final tmux call to attach" echo " to the session, not to the earlier ones creating it" echo " DEFAULT: -2" echo "TMDIR - Where are session information files stored" echo " DEFAULT: ${HOME}/.tmux.d" echo "TMWIN - Where does your tmux starts numbering its windows?" echo " This script tries to find the information in your config," echo " but as it only checks $HOME/.tmux.conf it might fail". echo " So if your window numbers start at anything different to 0," echo " like mine do at 1, then you can set TMWIN to 1" echo "TMSESSHOST - Should the hostname appear in session names?" echo " DEFAULT: true" echo "TMSSHCMD - Allow to globally define a custom ssh command line." echo " This can be just the command or any option one wishes to have" echo " everywhere." echo " DEFAULT: ssh" echo "DEBUG - Show debug output (remember to redirect it to a file)" echo "" exit 42 } # Simple "cleanup" of a variable, removing space and dots as we don't # want them in our tmux session name function clean_session() { local toclean=${*:-""} # Neither space nor dot nor : or " are friends in the SESSION name toclean=${toclean// /_} toclean=${toclean//:/_} toclean=${toclean//\"/} echo ${toclean//./_} } # Merge the commandline parameters (hosts) into a usable session name # for tmux function ssh_sessname() { if [[ ${TMSORT} = true ]]; then local one=$1 # get rid of first argument (s|ms), we don't want to sort this shift local sess=$(for i in $@; do echo $i; done | sort | tr '\n' ' ') sess="${one} ${sess% *}" else # no sorting wanted local sess="${*}" fi clean_session ${sess} } # Setup functions for all tmux commands function setup_command_aliases() { local command local SESNAME SESNAME="tmlscm$$" if [[ ${TMUXMAJOR} -lt 2 ]] || [[ ${TMUXMINOR} -lt 2 ]]; then # Starting tmux 2.2, this is no longer needed # Debian Bug #718777 - tmux needs a session to have lscm work tmux new-session -d -s ${SESNAME} -n "check" "sleep 3" fi for command in $(tmux list-commands|awk '{print $1}'); do eval "tm_$command() { tmux $command \"\$@\" >/dev/null; }" eval "tm_out_$command() { tmux $command \"\$@\" ; }" done if [[ ${TMUXMAJOR} -lt 2 ]] || [[ ${TMUXMINOR} -lt 2 ]]; then tmux kill-session -t ${SESNAME} || true fi } # Run a command (function) after replacing variables function do_cmd() { local cmd=$* cmd1=${cmd%% *} if [[ ${cmd1} =~ ^# ]]; then return elif [[ ${cmd1} =~ new-window ]]; then TMWIN=$(( TMWIN + 1 )) fi cmd=${cmd//SESSION/$SESSION} cmd=${cmd//TMWIN/$TMWIN} cmd=${cmd/$cmd1 /} debug $cmd1 $cmd eval tm_$cmd1 $cmd } # Use a configuration file to setup the tmux parameters/session function own_config() { if [[ ${1} =~ .cfg$ ]]; then TMSESCFG="free" fi # Set IFS to be NEWLINE only, not also space/tab, as our input files # are \n seperated (one entry per line) and lines may well have spaces. local IFS=" " # Fill an array with our config if [[ -n ${TMDATA[@]:-""} ]] && [[ ${#TMDATA[@]} -gt 0 ]]; then olddata=("${TMDATA[@]}") fi TMDATA=( $(sed -e "s/++TMREPLACETM++/${TMREPARG}/g" "${TMDIR}/$1") ) # Restore IFS IFS=${OLDIFS} SESSION=${SESSION:-$(clean_session ${TMDATA[0]})} if [ "${TMDATA[1]}" != "NONE" ]; then TMOPTS=${TMDATA[1]} fi # Seperate the lines we work with local IFS="" local -a workdata=(${TMDATA[@]:2}) IFS=${OLDIFS} # Lines (starting with line 3) may start with LIST, then we get # the list of hosts from elsewhere. So if one does, we exec the # command given, then append the output to TMDATA - while deleting # the actual line with LIST in. local TMPDATA=$(mktemp -u -p ${TMPDIR} .tmux_tm_XXXXXXXXXX) trap "rm -f ${TMPDATA}" EXIT ERR HUP INT QUIT TERM local index=0 while [[ ${index} -lt ${#workdata[@]} ]]; do if [[ "${workdata[${index}]}" =~ ^LIST\ (.*)$ ]]; then # printf -- 'workdata: %s\n' "${workdata[@]}" local cmd=${BASH_REMATCH[1]} if [[ ${cmd} =~ \$\{([0-9a-zA-Z_]+)\} ]]; then repvar=${BASH_REMATCH[1]} reptext=${!repvar} cmd=${cmd//\$\{$repvar\}/$reptext} fi echo "Line ${index}: Fetching hostnames using provided shell command '${cmd}', please stand by..." $( ${cmd} >| "${TMPDATA}" ) # Set IFS to be NEWLINE only, not also space/tab, the list may have ssh options # and what not, so \n is our seperator, not more. IFS=" " out=( $(tr -d '\r' < "${TMPDATA}" ) ) # Restore IFS IFS=${OLDIFS} workdata+=( "${out[@]}" ) unset workdata[${index}] unset out # printf -- 'workdata: %s\n' "${workdata[@]}" elif [[ "${workdata[${index}]}" =~ ^SSHCMD\ (.*)$ ]]; then TMSSHCMD=${BASH_REMATCH[1]} fi index=$(( index + 1 )) done rm -f "${TMPDATA}" trap - EXIT ERR HUP INT QUIT TERM debug "TMDATA: ${TMDATA[@]}" debug "olddata: ${olddata[@]:-''}" if [[ -n ${olddata[@]:-""} ]]; then TMDATA=( "${olddata[@]}" "${workdata[@]}" ) else TMDATA=( "${TMDATA[@]:0:2}" "${workdata[@]}" ) fi declare -r TMDATA=( "${TMDATA[@]}" ) debug "TMDATA now ${TMDATA[@]}" } # Simple overview of running sessions function list_sessions() { local IFS="" if output=$(tmux list-sessions 2>/dev/null); then echo $output else echo "No tmux sessions available" fi } # We either have a debug function that shows output, or one that # plainly returns if [[ ${DEBUG} == true ]]; then eval "debug() { >&2 echo \$* ; }" else eval "debug() { return ; }" fi setup_command_aliases ######################################################################## # MAIN work follows here # Check the first cmdline parameter, we might want to prepare something case ${cmdline} in ls) list_sessions exit 0 ;; s|ms|k) # Yay, we want ssh to a remote host - or even a multi session setup - or kill one # So we have to prepare our session name to fit in what tmux (and shell) # allow us to have. And so that we can reopen an existing session, if called # with the same hosts again. SESSION=$(ssh_sessname $@) declare -r cmdline shift ;; b) declare -r cmdline=b shift declare -r SESSION=$@ ;; j) declare -r cmdline=j shift declare -r SESSION=$@ ;; -*) while getopts "lnhgs:m:c:e:r:k:b:j:" OPTION; do case ${OPTION} in l) # ls list_sessions exit 0 ;; s) # ssh SESSION=$(ssh_sessname s ${OPTARG}) declare -r cmdline=s shift ;; k) # kill session SESSION=$(ssh_sessname s ${OPTARG}) declare -r cmdline=k shift ;; m) # ms (needs hostnames in "") SESSION=$(ssh_sessname ms ${OPTARG}) declare -r cmdline=ms shift ;; b) # b (break panes in session X, window Y) declare -r SESSION=${OPTARG} declare -r cmdline=b shift ;; j) # j (join windows in session X to first window) declare -r SESSION=${OPTARG} declare -r cmdline=j shift ;; c) # pre-defined config own_config ${OPTARG} ;; e) # existing session name SESSION=$(clean_session ${OPTARG}) shift ;; n) # new session even if same name one already exists DOUBLENAME=true ;; r) # replacement arg TMREPARG=${OPTARG} ;; g) # Group session, not simple attach declare -r GROUPSESSION=true ;; h) usage ;; esac done ;; *) # Nothing special (or something in our tmux.d) if [ $# -lt 1 ]; then SESSION=${SESSION:-""} if [[ -n "${SESSION}" ]]; then # Environment has SESSION set, wherever from. So lets # see if its an actual tmux session if ! tmux has-session -t "${SESSION}" 2>/dev/null; then # It is not. And no argument. Show usage usage fi else usage fi elif [ -r "${TMDIR}/${cmdline}" ]; then own_config ${1} else # Not a config file, so just session name. SESSION=${cmdline} fi ;; esac havesession="false" if tmux has-session -t ${SESSION} 2>/dev/null; then havesession="true" fi declare -r havesession # And now check if we would end up with a doubled session name. # If so add something "random" to the new name, like our pid. if [[ ${DOUBLENAME} == true ]] && [[ ${havesession} == true ]]; then # Session exist but we are asked to open another one, # so adjust our session name if [[ ${#TMDATA} -eq 0 ]] && [[ ${SESSION} =~ ([ms]+)_(.*) ]]; then SESSION="${BASH_REMATCH[1]}_$$_${BASH_REMATCH[2]}" else SESSION="$$_${SESSION}" fi fi if [[ ${TMSESSHOST} = true ]]; then declare -r SESSION="$(uname -n|cut -d. -f1)_${SESSION}" else declare -r SESSION fi # We only do special work if the SESSION does not already exist. if [[ ${cmdline} != k ]] && [[ ${havesession} == false ]]; then # In case we want some extra things... # Check stupid users if [ $# -lt 1 ]; then usage fi tm_pane_error="create pane failed: pane too small" case ${cmdline} in s) # The user wants to open ssh to one or more hosts do_cmd new-session -d -s ${SESSION} -n "${1}" "'${TMSSHCMD} ${1}'" # We disable any automated renaming, as that lets tmux set # the pane title to the process running in the pane. Which # means you can end up with tons of "bash". With this # disabled you will have panes named after the host. do_cmd set-window-option -t ${SESSION} automatic-rename off >/dev/null # If we have at least tmux 1.7, allow-rename works, such also disabling # any rename based on shell escape codes. if [ ${TMUXMINOR//[!0-9]/} -ge 7 ] || [ ${TMUXMAJOR//[!0-9]/} -gt 1 ]; then do_cmd set-window-option -t ${SESSION} allow-rename off >/dev/null fi shift count=2 while [ $# -gt 0 ]; do do_cmd new-window -d -t ${SESSION}:${count} -n "${1}" "${TMSSHCMD} ${1}" do_cmd set-window-option -t ${SESSION}:${count} automatic-rename off >/dev/null # If we have at least tmux 1.7, allow-rename works, such also disabling # any rename based on shell escape codes. if [ ${TMUXMINOR//[!0-9]/} -ge 7 ] || [ ${TMUXMAJOR//[!0-9]/} -gt 1 ]; then do_cmd set-window-option -t ${SESSION}:${count} allow-rename off >/dev/null fi count=$(( count + 1 )) shift done ;; ms) # We open a multisession window. That is, we tile the window as many times # as we have hosts, display them all and have the user input send to all # of them at once. do_cmd new-session -d -s ${SESSION} -n "Multisession" "'${TMSSHCMD} ${1}'" shift while [ $# -gt 0 ]; do set +e output=$(do_cmd split-window -d -t ${SESSION}:${TMWIN} "'${TMSSHCMD} ${1}'" 2>&1) ret=$? set -e if [[ ${ret} -ne 0 ]] && [[ ${output} == ${tm_pane_error} ]]; then # No more space -> have tmux redo the # layout, so all windows are evenly sized. do_cmd select-layout -t ${SESSION}:${TMWIN} main-horizontal >/dev/null # And dont shift parameter away continue fi shift done # Now synchronize them do_cmd set-window-option -t ${SESSION}:${TMWIN} synchronize-pane >/dev/null # And set a final layout which ensures they all have about the same size do_cmd select-layout -t ${SESSION}:${TMWIN} tiled >/dev/null ;; *) # Whatever string, so either a plain session or something from our tmux.d if [ -z "${TMDATA}" ]; then # the easy case, just a plain session name do_cmd new-session -d -s ${SESSION} else # data in our data array, the user wants his own config if [[ ${TMSESCFG} = free ]]; then if [[ ${TMDATA[2]} = NONE ]]; then # We have a free form config where we get the actual tmux commands # supplied by the user, so just issue them after creating the session. do_cmd new-session -d -s ${SESSION} -n "'${TMDATA[0]}'" else do_cmd ${TMDATA[2]} fi tmcount=${#TMDATA[@]} index=3 while [ ${index} -lt ${tmcount} ]; do do_cmd ${TMDATA[$index]} (( index++ )) done else # So lets start with the "first" line, before dropping into a loop do_cmd new-session -d -s ${SESSION} -n "${TMDATA[0]}" "'${TMSSHCMD} ${TMDATA[2]}'" tmcount=${#TMDATA[@]} index=3 while [ ${index} -lt ${tmcount} ]; do # List of hostnames, open a new connection per line set +e output=$(do_cmd split-window -d -t ${SESSION}:${TMWIN} "'${TMSSHCMD} ${TMDATA[$index]}'" 2>&1) set -e if [[ ${output} =~ ${tm_pane_error} ]]; then # No more space -> have tmux redo the # layout, so all windows are evenly sized. do_cmd select-layout -t ${SESSION}:${TMWIN} main-horizontal >/dev/null # And again, don't increase index continue fi (( index++ )) done # Now synchronize them do_cmd set-window-option -t ${SESSION}:${TMWIN} synchronize-pane >/dev/null # And set a final layout which ensures they all have about the same size do_cmd select-layout -t ${SESSION}:${TMWIN} tiled >/dev/null fi fi ;; esac # Build up new session, ensure we start in the first window do_cmd select-window -t ${SESSION}:${TMWIN} elif [[ ${cmdline} == k ]]; then # So we are asked to kill a session tokill=${SESSION//k_/} do_cmd kill-session -t ${tokill} exit 0 elif [[ ${cmdline} == b ]]; then # Split all panes of the given session and its window into multiple windows for pane in $(do_cmd out_list-panes -s -t${SESSION} -F'#{pane_id}'); do do_cmd break-pane -s"${pane}" 2>/dev/null || true done exit 0 elif [[ ${cmdline} == j ]]; then # Join all windows in a session into the first window for window in $(do_cmd out_list-windows -t${SESSION} -F'#{window_id}'|tail -n +2); do do_cmd join-pane -s${window} -t${SESSION}:${TMWIN} # Optimize by checking for pane too small do_cmd select-layout -t ${SESSION}:${TMWIN} tiled >/dev/null #2>/dev/null || true done # Now synchronize them do_cmd set-window-option -t ${SESSION}:${TMWIN} synchronize-pane >/dev/null exit 0 fi # If we should group our session or not if [[ ${GROUPSESSION} == true ]]; then # Grouping means opening a new session, but sharing the sessions with # another session (existing and new windows). But window control is separate. sesname="$$_${SESSION}" tmux ${TMOPTS} new-session -s ${sesname} -t ${SESSION} tmux ${TMOPTS} kill-session -t ${sesname} else # Do not group, just attach tmux ${TMOPTS} attach -t ${SESSION} fi