Last active
August 21, 2024 00:10
-
-
Save tree-s/4cab05dfe10694da39feb63955613293 to your computer and use it in GitHub Desktop.
Resurrect tmux session backup/restore
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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