From 2541094bdf24c5c30b35cb46d176437af873c6f1 Mon Sep 17 00:00:00 2001 From: pankovea Date: Fri, 8 Nov 2024 19:52:00 +0000 Subject: [PATCH] =?UTF-8?q?=D0=98=D0=B7=D0=BC=D0=B5=D0=BD=D0=B5=D0=BD?= =?UTF-8?q?=D1=8B=20=D0=BA=D0=BB=D1=8E=D1=87=D0=B8=20=D0=B7=D0=B0=D0=BF?= =?UTF-8?q?=D1=83=D1=81=D0=BA=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Изменены -rs | --remote-server -гu | --remote-user -ld | --local-dataset -rd | --remote-dataset Добавлены: -l | --log-file --no-progress --no-check --stop #FIXME работоспособность не протестирована --- zfs_send.zsh | 307 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 186 insertions(+), 121 deletions(-) diff --git a/zfs_send.zsh b/zfs_send.zsh index a70a3a2..3146163 100755 --- a/zfs_send.zsh +++ b/zfs_send.zsh @@ -4,27 +4,82 @@ backup_server="192.168.0.162" # Сервер для резервирования backup_user="root" # Пользователь на сервере inc_snapshot="manual-$(date +%Y%m%d)" # Новый создаваемый снисмок incremental=false +silent=false +stop=false +progress=true +check=true +LOGFILE="log_bak_${inc_snapshot}.log" +unset local_dataset +unset remote_dataset echo -ne "Обработка аргументов...\033[2K\r" while [[ "$#" -gt 0 ]]; do case $1 in - -r|--backup-server) backup_server="$2"; shift ;; - -г|--baсkup-user) backup_user="$2"; shift ;; - -s|--snapshot) inc_snapshot="$2"; shift ;; + -rs|--remote-server) backup_server="$2"; shift ;; + -гu|--remote-user) backup_user="$2"; shift ;; + -s|--snapshot) inc_snapshot="$2"; shift ;; + -ld|--local-dataset) local_dataset="$2"; shift ;; + -rd|--remote-dataset) remote_dataset="$2"; shift ;; + -l|--log-file) LOGFILE="$2"; shift ;; -i|--incremental) incremental=true ;; - *) echo "Неизвестные параметры: $1"; exit 1 ;; - esac - shift + --no-progress) progress=false ;; + --no-check) check=false ;; + --stop) stop=true ;; + -h|--help) echo "Использование: ./zfs_send.zsh [OPTIONS]"; + echo "-rs | --remote-server "; + echo "-гu | --remote-user "; + echo "-s | --snapshot "; + echo "-ld | --local-dataset "; + echo "-rd | --remote-dataset "; + echo "-l | --log-file ${#backup_datasets} ? ${#local_datasets} : ${#backup_datasets} )) # Максимальное количество элементов - printf $column_widths "№" "Локальные датасеты" "№" "На $backup_server" - printf $column_widths "----" "------------------" "----" "-------------------" - # echo "№ - Локальные\t\t№ - Удалённые" - for ((i = 1; i <= $i_max; i++)); do - if [[ -v local_datasets[i] ]]; then i1=$i; else; i1=""; fi - if [[ -v backup_datasets[i] ]]; then i2=$i; else; i2=""; fi - printf $column_widths "$i1" "${local_datasets[i]}" "$i2" "${backup_datasets[i]}" - done - - wrong_enter=true - while $wrong_enter = true ; do - read "loc_ds_indx_list?Локальные индексы через пробел (0=выход; all=режим исключения): " - if [[ $loc_ds_indx_list = "all" ]]; then - loc_ds_indx_list=({1..${#local_datasets}}) - break - fi - if [[ $loc_ds_indx_list = *[[:digit:]]* ]]; then - loc_ds_indx_list=(${=loc_ds_indx_list}) # Convert to list - for i in $loc_ds_indx_list; do - if [[ $i = 0 ]]; then exit; fi - if (( $i < 1 )) || (( $i > ${#local_datasets} )); then - echo "$i ! Выход за диапазон" - wrong_enter=true - break - else - wrong_enter=false +# Если заданы local_dataset & remote_dataset +if [[ -v local_dataset && -v remote_dataset ]]; then + work_datasets[$local_dataset]=$remote_dataset +else # Если нет, то вычислить + if [[ $incremental = true ]]; then + # Высчитать что на что резервировать + 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 - fi - done + done + else # Если не инкрементная копия, то запросить меcто для резервирования + # Вывести на экран запрос + column_widths="| %-4s%-35s | %-4s%-35s\n" + i_max=$(( ${#local_datasets} > ${#backup_datasets} ? ${#local_datasets} : ${#backup_datasets} )) # Максимальное количество элементов + printf $column_widths "№" "Локальные датасеты" "№" "На $backup_server" + printf $column_widths "----" "------------------" "----" "-------------------" + # echo "№ - Локальные\t\t№ - Удалённые" + for ((i = 1; i <= $i_max; i++)); do + if [[ -v local_datasets[i] ]]; then i1=$i; else; i1=""; fi + if [[ -v backup_datasets[i] ]]; then i2=$i; else; i2=""; fi + printf $column_widths "$i1" "${local_datasets[i]}" "$i2" "${backup_datasets[i]}" + done - echo "Введите номер датасета в котором создать кописю исходного (- исключить, 0 выйти)" - for i in $loc_ds_indx_list; do - read "bak_ds_indx?$i - ${local_datasets[i]} -> " - case $bak_ds_indx in - 0) exit;; - -) ;; - *) if (( $bak_ds_indx >= 1 )) && (( $bak_ds_indx <= ${#backup_datasets} )); then - loc_name_ds=$(sed -r "s/(.*)\/(.*)/\2/" <<<"$local_datasets[$i]") # Определить нижний по иерархии - bak_ds="$bak_ds/$loc_name_ds" # Добавить имя создаваемого датасета - work_datasets[$local_datasets[$i]]=$bak_ds - else; exit 1; fi ;; - esac - done -fi # Конец блока запроса места резервирования + wrong_enter=true + while $wrong_enter = true ; do + read "loc_ds_indx_list?Локальные индексы через пробел (0=выход; all=режим исключения): " + if [[ $loc_ds_indx_list = "all" ]]; then + loc_ds_indx_list=({1..${#local_datasets}}) + break + fi + if [[ $loc_ds_indx_list = *[[:digit:]]* ]]; then + loc_ds_indx_list=(${=loc_ds_indx_list}) # Convert to list + for i in $loc_ds_indx_list; do + if [[ $i = 0 ]]; then exit; fi + if (( $i < 1 )) || (( $i > ${#local_datasets} )); then + echo "$i ! Выход за диапазон" + wrong_enter=true + break + else + wrong_enter=false + fi + done + fi + done + echo "Введите номер датасета в котором создать кописю исходного (- исключить, 0 выйти)" + for i in $loc_ds_indx_list; do + read "bak_ds_indx?$i - ${local_datasets[i]} -> " + case $bak_ds_indx in + 0) exit;; + -) ;; + *) if (( $bak_ds_indx >= 1 )) && (( $bak_ds_indx <= ${#backup_datasets} )); then + loc_name_ds=$(sed -r "s/(.*)\/(.*)/\2/" <<<"$local_datasets[$i]") # Определить нижний по иерархии + bak_ds="$bak_ds/$loc_name_ds" # Добавить имя создаваемого датасета + work_datasets[$local_datasets[$i]]=$bak_ds + else; exit 1; fi ;; + esac + done + fi # Конец блока запроса места резервирования +fi # Конец блока наличия переданных параметров датасетов в скрипт # Словарь со списком последких снапшотов для датасетов @@ -173,33 +234,35 @@ fi # --------- Выбор датасетов ---------- -read 'work?Утвердите данные. "y" в работу | "1 2.." выбрать через пробел | НЕТ по умолчанию : ' -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 [[ $check = true ]]; then + read 'work?Утвердите данные. "y" в работу | "1 2.." выбрать через пробел | НЕТ по умолчанию : ' + 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) " + # Если был выведен список, то запросить запуск в работу нового списка + if [[ ${#indx_list} > 0 ]]; then + work="N" + read "work?В работу (y/N) " + fi +else + work="y" fi - # --------- Резервирование ---------- if [[ $work = "y" || $work = "Y" ]]; then TS0=$(date +%s) echo $TS0 > /dev/shm/backup_time_stamp # Сохраняем отметку о времени начала - LOGFILE="log_bak_${inc_snapshot}.log" echo "Результат работы записываю в файл $LOGFILE" # Функция для записи сообщения в файл и на экран @@ -220,10 +283,10 @@ if [[ $work = "y" || $work = "Y" ]]; then echo_log echo_log "--- Резервирую данные ---" i_task=0 # Выполненных зачач - echo $i_task > /dev/shm/backup_i_task # Сохраняем количество готовых задач в память + if [[ $progress == true ]]; then echo $i_task > /dev/shm/backup_i_task; fi # Сохраняем количество готовых задач в память for loc_ds bak_ds in ${(kv)work_datasets}; do TS1=$(date +%s) - echo $TS1 > /dev/shm/backup_time_stamp # Сохраняем отметку о времени начала + if [[ $progress == true ]]; then echo $TS1 > /dev/shm/backup_time_stamp; fi # Сохраняем отметку о времени начала echo_log " * snapshot ${loc_ds}@${inc_snapshot}" zfs snapshot ${loc_ds}@${inc_snapshot}; #FIXME выдаёт ошибку если мы оставили существующий снимок echo_log " * Start sending ${loc_ds} at $(date +'%Y.%m.%d %H:%M.%S')" @@ -233,47 +296,49 @@ if [[ $work = "y" || $work = "Y" ]]; then esac TS2=$(date +%s) (( i_task++ )) - echo $i_task > /dev/shm/backup_i_task + if [[ $progress == true ]]; then echo $i_task > /dev/shm/backup_i_task; fi echo_log "--- готово за: $(date -d@$(($TS2-$TS1)) -u '+%H:%M.%S') ---" done & # Запустить резервирование в фоне - # Выводить прогресс запрашивая список запущенныйх процессов - echo "" - last_percent=0 - time_changed_percent_value=$(date +%s) - while [[ $i_task != $n_tasks ]]; do # выводим статус пока не завершены все задачи - progress=$(ps -u | grep "send" | grep -v "grep" | sed -r "s/(.*) zfs: (.*)/\2/") - percent=$(echo $progress | cut -d "(" -f2 | cut -d "%" -f1 ) - if (( percent > 0 )); then - now=$(date +%s) - if (( percent != last_percent )); then - time_changed_percent_value=$now - last_percent=$percent - TS1=$(< /dev/shm/backup_time_stamp) - elapsed=$(( $(date +%s) - TS1 )) - fi - time_part_of_percent=$(( now - time_changed_percent_value )) # Время с последнего изменения процента - estimated_total=$(( 100 / percent * elapsed )) # Всего времени на задачу - estimated_remain=$(( estimated_total - elapsed - time_part_of_percent )) # Осталось времени на задачу - if (( estimated_remain >= 86400 )) || (( estimated_total >= 86400 )); then # Если осталось более чем сутки, то отобразить дни - estimated_remain_days=$(( estimated_remain / 86400 )); estimated_remain_time=$(( estimated_remain % 86400 )) - estimated_total_days=$(( estimated_total / 86400 )); estimated_total_time=$(( estimated_total % 86400 )) - echo -n "\033[2K\r$progress $estimated_remain_days д. $(date -d@$estimated_remain_time -u '+%H:%M.%S') ост. / $estimated_total_days д. $(date -d@$estimated_total_time -u '+%H:%M.%S') " + # Выводить прогресс запрашивая список запущенных процессов + if [[ $progress == true ]]; then + echo "" + last_percent=0 + time_changed_percent_value=$(date +%s) + while [[ $i_task != $n_tasks ]]; do # выводим статус пока не завершены все задачи + progress=$(ps -u | grep "send" | grep -v "grep" | sed -r "s/(.*) zfs: (.*)/\2/") + percent=$(echo $progress | cut -d "(" -f2 | cut -d "%" -f1 ) + if (( percent > 0 )); then + now=$(date +%s) + if (( percent != last_percent )); then + time_changed_percent_value=$now + last_percent=$percent + TS1=$(< /dev/shm/backup_time_stamp) + elapsed=$(( $(date +%s) - TS1 )) + fi + time_part_of_percent=$(( now - time_changed_percent_value )) # Время с последнего изменения процента + estimated_total=$(( 100 / percent * elapsed )) # Всего времени на задачу + estimated_remain=$(( estimated_total - elapsed - time_part_of_percent )) # Осталось времени на задачу + if (( estimated_remain >= 86400 )) || (( estimated_total >= 86400 )); then # Если осталось более чем сутки, то отобразить дни + estimated_remain_days=$(( estimated_remain / 86400 )); estimated_remain_time=$(( estimated_remain % 86400 )) + estimated_total_days=$(( estimated_total / 86400 )); estimated_total_time=$(( estimated_total % 86400 )) + echo -n "\033[2K\r$progress $estimated_remain_days д. $(date -d@$estimated_remain_time -u '+%H:%M.%S') ост. / $estimated_total_days д. $(date -d@$estimated_total_time -u '+%H:%M.%S') " + else + echo -n "\033[2K\r$progress $(date -d@$estimated_remain -u '+%H:%M.%S') ост. / $(date -d@$estimated_total -u '+%H:%M.%S') " + fi + sleep 1 else - echo -n "\033[2K\r$progress $(date -d@$estimated_remain -u '+%H:%M.%S') ост. / $(date -d@$estimated_total -u '+%H:%M.%S') " + echo -ne "\033[2K\r$progress " + sleep 1 + for ((i = 1; i < 3 ; i++)); do echo -n "."; sleep 1; done fi - sleep 1 - else - echo -ne "\033[2K\r$progress " - sleep 1 - for ((i = 1; i < 3 ; i++)); do echo -n "."; sleep 1; done - fi - i_task=$(< /dev/shm/backup_i_task) - done + i_task=$(< /dev/shm/backup_i_task) + done - # Удалить временные файлы состояний - rm /dev/shm/backup_i_task - rm /dev/shm/backup_time_stamp + # Удалить временные файлы состояний + rm /dev/shm/backup_i_task + rm /dev/shm/backup_time_stamp + fi echo_log "\n--- Все завершено за $(date -d@$(( $(date +%s) - $TS0 )) -u '+%H:%M.%S') ---\n"