#!/bin/zsh backup_server="192.168.0.162" # Сервер для резервирования inc_snapshot="manual-$(date +%Y%m%d)" # Новый создаваемый снисмок echo -ne "Обработка аргументов...\033[0K\r" if [[ $# == 1 ]]; then backup_server=$1 fi if [[ $# == 2 ]]; then backup_server=$1 inc_snapshot=$2 fi if [[ $# > 2 ]]; then echo "Ожидается 0 или 2 аргумента: backup_server или ни одного для сервера по умолчанию: $backup_server" exit fi echo "Резервирование на $backup_server" echo "Снимок файловой системы $inc_snapshot" # --------- Собираем данные ---------- echo -ne "Собираю данные...\033[0K\r" # Получить список датасетов на локальной машине local_datasets=$(zfs list -o name | grep -v -e ix-app -e boot-pool -e system-data -e jails | grep /) # Отрезаем всё лишнее, отделяем нужные данные по 4 пробелам в выводе. local_datasets=(${=local_datasets}) # Конвертирование в массив # Получить список датасетов на удалённой машине backup_datasets=$(ssh root@$backup_server zfs list -o name | grep -v -e ix-app -e boot-pool -e system-data -e jails | grep /) # Отрезаем всё лишнее, отделяем нужные данные по 4 пробелам в выводе. backup_datasets=(${=backup_datasets}) # Конвертирование в массив # Высчитать что на что резервировать declare -A work_datasets for ((i = 1; i <= ${#local_datasets}; i++)); do for ((j = 1; j <= ${#backup_datasets}; j++)); do a=$(sed -r "s/(.*)\/(.*)/\2/" <<<"$local_datasets[i]") # Определить нижний по иерархии b=$(sed -r "s/(.*)\/(.*)/\2/" <<<"$backup_datasets[j]") # тоже дял второго if [[ "$a" == "$b" ]]; then work_datasets[${local_datasets[i]}]=${backup_datasets[j]} fi done done # Словарь со списком последких снапшотов для датасетов declare -A last_loc_snaps declare -A last_bak_snaps echo -ne "Проверяю на наличие $inc_snapshot...\033[0K\r" for loc_ds bak_ds in ${(kv)work_datasets}; do # Запросить последние снимки loc_snap="$(zfs list -t snapshot -o name ${loc_ds} | grep "manual" | tail -n1 | egrep -o '@.+' )" loc_snap=${loc_snap:1} # удалить @ в начале строки bak_snap="$(ssh root@$backup_server zfs list -t snapshot -o name ${bak_ds} | grep "manual" | tail -n1 | egrep -o '@.+' )" bak_snap=${bak_snap:1} # удалить @ в начале строки # Если последний снимок совпадает с сегодняшним if [[ "$loc_snap" = "$inc_snapshot" ]]; then # Если последние снимки на обеих машинах совпадают if [[ "$loc_snap" = "$bak_snap" ]]; then echo "${loc_ds}@${loc_snap} существует на обеих машинах. $loc_ds Исключено из списка" unset "work_datasets[$loc_ds]" else # Если на удалённой машине нет такого снимка, то предложить удалить. read "del_snapshot?Датасет $loc_ds уже имеет последний снимок $inc_snapshot. Удалить? (y/N)" if [ "$del_snapshot" = "y" ]; then zfs destroy "${loc_ds}@${loc_snap}" loc_snap="$(zfs list -t snapshot -o name ${loc_ds} | grep "manual" | tail -n1 | egrep -o '@.+' )" loc_snap="${loc_snap:1}" last_loc_snaps[$loc_ds]=$loc_snap # last_bak_snaps[$bak_ds]=$bak_snap # не нужно, нигде не используется else # Если не удалять, то просто исключить из резервирования echo "$ds Исключено из списка" unset "work_datasets[$ds]" fi fi else # добавить в список снапшотов last_loc_snaps[$loc_ds]=$loc_snap # last_bak_snaps[$bak_ds]=$bak_snap # не нужно, нигде не используется fi done # --------- Запрашиваем подтверждение пользователя ---------- echo " " echo "Список резервируемых датасетов:" i=0; for loc_ds bak_ds in ${(kv)work_datasets}; do i=$((i+1)) echo "$i - $loc_ds \t ${last_loc_snaps[$loc_ds]} \t -> \t${bak_ds}" done echo echo "Инкрементный снимок:\t$inc_snapshot" # --------- Выбор датасетов ---------- read "work?Утвердите данные. В работу (y), или перечислить через пробел номера, НЕТ по умолчанию? " if [[ $work = *[[:digit:]]* ]]; then indx_list=(${=work}) # Если нет индекса в списке, то удалить из словаря для резервирования i=0; for loc_ds bak_ds in ${(kv)work_datasets}; do i=$((i+1)) if [[ ${indx_list[(r)$i]} != $i ]]; then # Если нет $i в списке $indx_list unset "work_datasets[$loc_ds]" else echo "$i - $loc_ds \t ${last_loc_snaps[$loc_ds]} \t -> \t${bak_ds}" fi done fi # Если был выведен список, то запросить запуск в работу нового списка if [[ ${#indx_list} > 0 ]]; then work="N" read "work?В работу (y/N) " fi # --------- Резервирование ---------- if [[ $work = "y" || $work = "Y" ]]; then TS0=$(date +%s) LOGFILE="log_${inc_snapshot}_backup.txt" echo "Результат работы записываю в файл $LOGFILE" # Записать в файл список резервируемых датасетов echo "Список резервируемых датасетов:" >> "${LOGFILE}" for loc_ds in ${(k)work_datasets}; do echo "$key \t ${last_loc_snaps[$loc_ds]} \t -> \t${work_datasets[$loc_ds]}" >> "${LOGFILE}" done echo exec 6>&1 # Saves stdout exec > >(tee $LOGFILE) # stdout replaced with file echo "\n--- Резервирую данные ---" for loc_ds bak_ds in ${(kv)work_datasets}; do TS1=$(date +%s) echo " * snapshot ${loc_ds}@${inc_snapshot}" zfs snapshot ${loc_ds}@${inc_snapshot}; echo " * Start sending ${loc_ds} at $(date +'%Y.%m.%d %H:%M.%S')" zfs send -V -i ${loc_ds}@${last_loc_snaps[$loc_ds]} ${loc_ds}@${inc_snapshot} | ssh "root@${backup_server}" zfs receive ${bak_ds}@${inc_snapshot} TS2=$(date +%s) echo "Закончил ${loc_ds} за: $(date -d@$(($TS2-$TS1)) -u +%H:%M:%S)" done & # Запустить резервирование в фоне exec 1>&6 6>&- # Выводить прогресс запрашивая список запущенныйх процессов while pgrep -u $USER zfs >/dev/null; do PROGR=$(ps -u | grep "send" | grep -v "grep" | sed -r "s/(.*) zfs: (.*)/\2/") echo -ne "$PROGR\033[0K\r" sleep 60 done echo -e exec > >(tee $LOGFILE) echo "\n---Все завершено за $(date -d@$(($TS2-$TS0)) -u +%H:%M.%S) ---" exec 1>&6 6>&- # Restore stdout and close file descriptor #6. fi