Skip to content

Instantly share code, notes, and snippets.

@tree-s
Last active August 21, 2024 00:10
Show Gist options
  • Save tree-s/4cab05dfe10694da39feb63955613293 to your computer and use it in GitHub Desktop.
Save tree-s/4cab05dfe10694da39feb63955613293 to your computer and use it in GitHub Desktop.
Resurrect tmux session backup/restore
#!/bin/bash
#######################
# Usage
usage() {
echo "USAGE: ${0} -b | -r [backup_number]"
exit 1
}
#######################
# Config Variables
max_backups=12
ignore_session_list="hosts"
backup_dir=~/.resurrect/tmux_snapshot
export PATH="${PATH}:/usr/local/bin"
#######################
# Use prompt to get state of pane.
mimic_state() {
prompt='^\<.*@.*:'
prompt_line=$(grep $(eval echo ${prompt}) ${backup_dir}/${pane_full} | tail -n 1)
host=$(printf "${prompt_line}" | cut -d@ -f2 | cut -d: -f1)
working_dir=$(printf "${prompt_line}" | cut -d: -f2 | cut -d\> -f1)
if [ "${host}" != "$(hostname)" ] && [ "${host}" != "" ] ;then
tmux send-keys -t ${pane_full} "ssh ${host}
" #carriage return from previous line
echo "SSH'd to ${host} in ${pane_full}. Sleeping 5 sec..."
sleep 5
fi
tmux send-keys -t ${pane_full} "cd ${working_dir}
" #carriage return from previous line
}
#######################
# Code
if [ ! ${1} ] ;then
usage
fi
while [ ${1} ] ;do
case ${1} in
-b)
task=backup
shift 1
;;
-r)
task=restore
if [ ${2} ] ;then
backup_number=${2}
shift 2
else
shift 1
fi
;;
*)
usage
;;
esac
done
if [ "${task}" = 'backup' ] ;then
##Rotate previous backups.
i=${max_backups}
while [[ ${i} != 0 ]] ;do
if [ -d ${backup_dir}.${i} ] ;then
if [[ ${i} = ${max_backups} ]] ;then
rm -r ${backup_dir}.${i}
else
mv ${backup_dir}.${i} ${backup_dir}.$((${i}+1))
fi
fi
i=$((${i}-1))
done
if [ -d ${backup_dir} ] ;then
mv ${backup_dir} ${backup_dir}.1
fi
##Dump copy from all windows in all available tmux sessions.
if [ ! -d ${backup_dir} ] ;then
mkdir -p ${backup_dir}
fi
for session in $(tmux list-sessions | awk -F: '{print $1}') ;do
for ignore_session in ${ignore_session_list} ;do
if [ "${session}" = "${ignore_session}" ] ;then
continue 2
fi
done
session_size=$(tmux list-sessions | awk -F'[' '/^'${session}': / {print $2}' | awk -F']' '{print $1}')
echo "${session_size}" >${backup_dir}/${session}.size
for window in $(tmux list-windows -t ${session} | awk -F: '{print $1}') ;do
for pane in $(tmux list-panes -t ${session}:${window} | awk -F: '{print $1}') ;do
##backup pane layout
if [ ${pane} -gt 0 ] ;then
tmux list-windows -t ${session} | awk -F'\\[layout ' '/^'${window}':/ {print $2}' | awk '{print $1}' | sed 's/\]$//' >${backup_dir}/${session}:${window}.layout
fi
pane_full=${session}:${window}.${pane}
##figure out the editing mode so we can select text in history
mode_keys=$(tmux show-window-options -g | awk '/^mode-keys/ {print $2}')
if [ "${mode_keys}" = 'emacs' ] ;then
tmux copy-mode -t ${session}:${window}.${pane} \; send-keys -t ${session}:${window}.${pane} 'M-<' 'C-Space' 'M->' 'C-e' 'M-w' \; save-buffer -b 0 ${backup_dir}/${pane_full} \; delete-buffer -b 0
elif [ "${mode_keys}" = 'vi' ] ;then
tmux copy-mode -t ${session}:${window}.${pane} \; send-keys -t ${session}:${window}.${pane} g Space G '$' Enter \; save-buffer -b 0 ${backup_dir}/${pane_full} \; delete-buffer -b 0
fi
if [ ! -s ${backup_dir}/${pane_full} ] ;then
rm ${backup_dir}/${pane_full}
fi
done
done
done
elif [ "${task}" = 'restore' ] ;then
##Allow for base-index configuration
base_index=0
if [ -f ~/.tmux.conf ] ;then
if grep -v ^# ~/.tmux.conf | egrep 'base-index +[0-9]' >/dev/null ;then
base_index=$(grep -v ^# ~/.tmux.conf | egrep 'base-index +[0-9]' | awk -F'base-index ' '{print $2}')
fi
fi
##Check for specified number backup. If none, then use latest.
if [ "${backup_number}" != '' ] ;then
backup_dir=${backup_dir}.${backup_number}
fi
##Restore proper number of sessions, windows, and panes
for session in $(ls ${backup_dir} | grep -v '\.size$' | cut -d: -f1 | sort -du) ;do
window_list=$(ls ${backup_dir} | awk -F: '/^'${session}':/ {print $2}' | cut -d\. -f1 | sort -nu)
window_list_rev=$(echo "${window_list}" | sort -nr)
num_windows=$(echo ${window_list} | wc -w)
##create session, and first window.
echo "* Creating new session, \"${session}\"..."
session_width=$(cut -d x -f 1 ${backup_dir}/${session}.size)
session_height=$(($(cut -d x -f 2 ${backup_dir}/${session}.size)+1))
tmux new-session -d -s ${session} -x ${session_width} -y ${session_height}
##create additional windows if # of windows is > 1
if [ ${num_windows} -gt 1 ] ;then
echo "* Creating $((${num_windows}-1)) additional windows for session, \"${session}\"..."
i=1
while [ ${i} -lt ${num_windows} ] ;do
tmux new-window -d -a -t ${session}:${base_index}
i=$((${i}+1))
done
fi
##re-number the windows to reflect backed-up window number
echo "* Re-numbering windows to reflect original scheme:"
#always get $i to 1 regardless of base-index
i=$((${num_windows}+(${base_index}-1)))
for window in ${window_list_rev} ;do
if [ ${i} -ne ${window} ] ;then
echo "${session}:${i} -> ${session}:${window}"
tmux move-window -d -s ${session}:${i} -t ${session}:${window}
fi
i=$((${i}-1))
done
##if windows had multiple panes, populate with proper number of panes
for window in ${window_list} ;do
pane_list=$(ls ${backup_dir} | grep -v 'layout$' | awk -F\. '/^'${session}':'${window}'\./ {print $2}')
num_panes=$(echo ${pane_list} | wc -w)
if [ ${num_panes} -gt 1 ] ;then
echo "* Adding panes to window, \"${session}:${window}\"."
i=1
while [ ${i} -lt ${num_panes} ] ;do
tmux split-window -d -v -t ${session}:${window}.0
rval=${?}
if [ ${rval} -gt 0 ] ;then
echo "Error on split-window with \"${session}:${window}\", compensating by re-arranging panes and trying again."
tmux select-layout -t ${session}:${window} tiled
tmux split-window -d -v -t ${session}:${window}.0
fi
i=$((${i}+1))
done
echo "Applying saved layout to panes in window, \"${session}:${window}\"."
layout=$(cat ${backup_dir}/${session}:${window}.layout)
tmux select-layout -t ${session}:${window} "${layout}"
for pane in ${pane_list} ;do
pane_full="${session}:${window}.${pane}"
tmux send-keys -t ${pane_full} "cat ${backup_dir}/${pane_full}
" #carriage return from previous line
mimic_state
done
else
pane_full="${session}:${window}.0"
tmux send-keys -t ${pane_full} "grep -v -e 'strings you dont want to see' -e 'another string you dont want to see' ${backup_dir}/${pane_full} | cat -s
" #carriage return from previous line
mimic_state
fi
done
done
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment