diff --git a/provision-contest/ansible/roles/clusterssh/files/tmux-cssh b/provision-contest/ansible/roles/clusterssh/files/tmux-cssh new file mode 100644 index 00000000..41862edf --- /dev/null +++ b/provision-contest/ansible/roles/clusterssh/files/tmux-cssh @@ -0,0 +1,130 @@ +#!/bin/sh + +hosts="" +ssh_options="" +tmux_name="cssh" +tmux_attach_current_session="false" + +usage() { + echo "Usage: $0 [options] host [host ...]" >&2 + echo "" >&2 + echo "Spawns multiple synchronized SSH sessions inside a tmux session." >&2 + echo "" >&2 + echo "Options:" >&2 + echo " -h Show help" >&2 + echo " -c Use the current tmux session and just spawn a new window instead" >&2 + echo " -n Name of the tmux session or window (default: cssh)" >&2 + echo " -o Additional SSH arguments" >&2 +} + +while [ $# -ne 0 ]; do + case $1 in + -n) + shift + if [ $# -eq 0 ]; then + usage + exit 2 + fi + tmux_name="$1" + shift + ;; + -c) + tmux_attach_current_session="true" + shift + ;; + -o) + shift + if [ $# -eq 0 ]; then + usage + exit 2 + fi + ssh_options="$1" + shift + ;; + -h) + usage + exit 0 + ;; + -*) + usage + exit 2 + ;; + *) + hosts="${hosts}${hosts:+ }$1" + shift + ;; + esac +done + +if [ -z "${hosts}" ]; then + usage + exit 2 +fi + +# Find a name for a new session +n=0 +while tmux has-session -t "${tmux_name}-${n}" 2>/dev/null; do n=$((n + 1)); done +tmux_session="${tmux_name}-${n}" + +if [ "${tmux_attach_current_session}" = "true" ]; then + tmux_session="$(tmux display-message -p '#S')" + # Find a name for a new window + n=0 + while tmux list-windows -F "#W" | grep -q "${tmux_name}-${n}" 2>/dev/null; do n=$((n + 1)); done + tmux_window="${tmux_name}-${n}" + tmux_window_options="-n ${tmux_window}" +fi + +# If host doesn't look like a DNS name, it may be a CSSH cluster +if ! echo "${hosts}" | grep -q '[. ]'; then + for cfg in "${HOME}/.clusterssh/clusters" /etc/clusters; do + if [ -r "${cfg}" ]; then + h="$(sed -n "s/^${hosts} //p" <"${cfg}")" + if [ -n "${h}" ]; then + hosts="${h}" + break + fi + fi + # If there was no corresponding cluster name, + # just assume we have an unqualified domain name + done +fi + +# Open a new session and split into new panes for each SSH session +for host in ${hosts}; do + if ! tmux has-session -t "${tmux_session}" 2>/dev/null; then + tmux new-session -s "${tmux_session}" -d "ssh ${ssh_options} ${host}" + elif [ "${tmux_attach_current_session}" = "true" ]; then + if ! tmux list-windows -F "#W" | grep -q "${tmux_window}" >/dev/null; then + # shellcheck disable=SC2086 + tmux new-window ${tmux_window_options} "ssh ${ssh_options} ${host}" + else + tmux split-window -t "${tmux_window}" -d "ssh ${ssh_options} ${host}" + # We have to reset the layout after each new pane otherwise the panes + # quickly become too small to spawn any more + tmux select-layout -t "${tmux_session}" tiled + fi + else + tmux split-window -t "${tmux_session}" -d "ssh ${ssh_options} ${host}" + # We have to reset the layout after each new pane otherwise the panes + # quickly become too small to spawn any more + tmux select-layout -t "${tmux_session}" tiled + fi +done + +# Synchronize panes by default +if [ "${tmux_attach_current_session}" = "true" ]; then + tmux set-window-option -t "${tmux_window}" synchronize-panes on +else + tmux set-window-option -t "${tmux_session}" synchronize-panes on +fi + +if [ -n "${TMUX}" ]; then + # We are in a tmux, just switch to the new session + tmux switch-client -t "${tmux_session}" +else + # We are NOT in a tmux, attach to the new session + tmux attach-session -t "${tmux_session}" +fi + +exit 0 diff --git a/provision-contest/ansible/roles/clusterssh/tasks/main.yml b/provision-contest/ansible/roles/clusterssh/tasks/main.yml index 585432da..1ae21350 100644 --- a/provision-contest/ansible/roles/clusterssh/tasks/main.yml +++ b/provision-contest/ansible/roles/clusterssh/tasks/main.yml @@ -22,3 +22,46 @@ src: clusters.j2 dest: /home/domjudge/.clusterssh/clusters mode: 0644 + +- name: Install tmux-cssh + when: not WF_RESTRICTED_NETWORK + get_url: + url: https://raw.githubusercontent.com/peikk0/tmux-cssh/master/tmux-cssh + dest: /usr/local/bin/tmux-cssh + mode: 0755 + owner: root + group: root + +- name: Install tmux-cssh + when: WF_RESTRICTED_NETWORK + copy: + src: tmux-cssh + dest: /usr/local/bin/tmux-cssh + mode: 0755 + owner: root + group: root + +- name: Set tmux-cssh shorthands + template: + owner: root + group: root + mode: 0755 + src: tmux-cluster.sh.j2 + dest: /usr/local/bin/{{ item.short }} + loop: + - short: ssh-js + group: wfinal-judgehost + - short: ssh-jsanalyst + group: analyst-judgehost + - short: ssh-jsall + group: judgehost + - short: ssh-dom + group: wfinal-domserver + - short: ssh-domanalyst + group: analyst-domserver + - short: ssh-domall + group: domserver + - short: ssh-all + group: onprem + - short: ssh-admin + group: admin diff --git a/provision-contest/ansible/roles/clusterssh/templates/tmux-cluster.sh.j2 b/provision-contest/ansible/roles/clusterssh/templates/tmux-cluster.sh.j2 new file mode 100644 index 00000000..a995a600 --- /dev/null +++ b/provision-contest/ansible/roles/clusterssh/templates/tmux-cluster.sh.j2 @@ -0,0 +1,3 @@ +#!/usr/bin/sh + +tmux-cssh {% for host in groups[item.group] %}{{ host }} {% endfor %}