#!/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} * существует на обеих машинах. Исключено из списка резервируемых" 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="backup_${inc_snapshot}.log" echo "Результат работы записываю в файл $LOGFILE" # Записать в файл список резервируемых датасетов echo "--- $(date +'%Y.%m.%d %H:%M.%S') ---" >> "${LOGFILE}" echo "Список резервируемых датасетов:" >> "${LOGFILE}" n_tasks=0 for loc_ds in ${(k)work_datasets}; do n_tasks=$((n_tasks+1)) echo "$key \t ${last_loc_snaps[$loc_ds]} \t -> \t${work_datasets[$loc_ds]}" >> "${LOGFILE}" done echo exec 6>&1 # Saves stdout exec >&1 >>$LOGFILE 2>&1 # Вывод на экран и в файл echo "\n--- Резервирую данные ---" i_task=0 # Выполненных зачач 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) i_task=$((i_task+1)) echo "------ готово за: $(date -d@$(($TS2-$TS1)) -u '+%H:%M:%S') " done & # Запустить резервирование в фоне exec 1>&6 # Restore stdout. Не писать в файл лога отследивание процента # Выводить прогресс запрашивая список запущенныйх процессов while [[ $i_task != $n_tasks ]]; do PROGR=$(ps -u | grep "send" | grep -v "grep" | sed -r "s/(.*) zfs: (.*)/\2/") echo -ne "$PROGR" dots_per_min=10 for ((i = 0; i < dots_per_min; i++)); do echo -n "."; sleep ((60 / dots_per_min)); done echo -e "\r"; done exec >&1 >>$LOGFILE 2>&1 # Вывод на экран и в файл echo "\n--- Все завершено за $(date -d@$(($TS2-$TS0)) -u '+%H:%M.%S') ---" exec 1>&6 6>&- # Restore stdout and close file descriptor #6. # sed "s/\r/\n/g" "${LOGFILE}" > "${LOGFILE}" # Заменить все возвраты каретки на символы переноса лог файле fi