From 8a01f1ef8c47262afa6695970174fb1cf50e8923 Mon Sep 17 00:00:00 2001 From: pankovea Date: Fri, 8 Nov 2024 03:24:44 +0000 Subject: [PATCH 01/11] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=20README.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..f54c37c --- /dev/null +++ b/README.md @@ -0,0 +1,33 @@ +# Скрипт для резервирования данных +Скрипт используется для резервирования датасетов файловой системы zfs. +Необходим запуск от суперпользователя. +## Флаги +Используемая оболочка: zsh + +‘‘‘zsh +zfs_send - +-r|--backup-server IP адрес или доменное имя сервера для резервирования +-г|--baсkup-user Пользователь SSH на удалённом сервер (по умолчанию root) +-s|--snapshot Задать имя снимка для резервирования +-i|--incremental Режим резервирования (по умолчанию полная копия) +‘‘’ + +Ожидается что доступ по SSH на удалённую машину уже настроен + +### Режим резервирования датасета целиком +Если не использован флаг -i +Скрипт запросит список датасетов на локально машине и на удалённом сервере. Будет предлоден выбор какой датасет на какой резервировать. + +# Режим инкрементный +После полного резервирования возможно сделать резерв только изменённой части файловой системы. +Флаг -i +Скрипт сопоставит имеющиеся датасеты на локальной и удалённых машинах и предложит возможные варианты резервирования. Сопоставление производится по совпадения названий датасетов нижнего уровня. + +# После запуска +Во время резервирования данных нельзя закрывать сессию пользователя. Иначе резервирование прервётся. + +Само резервирование происходит в фоновом процессе. А в консоль выводится информация о процессе резервирования через команду '''ps -u'''. При прерывании этого процесса через Ctrl+C резервирование не прекращается. Если необходимо прервать резервирование, то нужно: + +1. '''ps -u''' узнать PID процесса zfs send + +2. '''kill 12345''' Завершить номер процесса -- 2.47.2 From 6d0056657a8431e1f30458e19dc0e0b7f61d4e40 Mon Sep 17 00:00:00 2001 From: pankovea Date: Fri, 8 Nov 2024 03:53:03 +0000 Subject: [PATCH 02/11] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B5=D0=B8=D0=BC?= =?UTF-8?q?=D0=B5=D0=BD=D0=BE=D0=B2=D0=B0=D1=82=D1=8C=20=D1=81=D0=BA=D1=80?= =?UTF-8?q?=D0=B8=D0=BF=D1=82=20=D0=B2=20zfs=5Fsend.zsh?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backup_data.zsh => zfs_send.zsh | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename backup_data.zsh => zfs_send.zsh (100%) diff --git a/backup_data.zsh b/zfs_send.zsh similarity index 100% rename from backup_data.zsh rename to zfs_send.zsh -- 2.47.2 From 7706403ff520370bd44e8324d23a48a9a3dfea8c Mon Sep 17 00:00:00 2001 From: pankovea Date: Fri, 8 Nov 2024 03:59:51 +0000 Subject: [PATCH 03/11] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=20README.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index f54c37c..aef1fbc 100644 --- a/README.md +++ b/README.md @@ -4,13 +4,13 @@ ## Флаги Используемая оболочка: zsh -‘‘‘zsh -zfs_send - +```zsh +./zfs_send.zsh -r|--backup-server IP адрес или доменное имя сервера для резервирования -г|--baсkup-user Пользователь SSH на удалённом сервер (по умолчанию root) -s|--snapshot Задать имя снимка для резервирования -i|--incremental Режим резервирования (по умолчанию полная копия) -‘‘’ +``` Ожидается что доступ по SSH на удалённую машину уже настроен @@ -26,8 +26,8 @@ zfs_send - # После запуска Во время резервирования данных нельзя закрывать сессию пользователя. Иначе резервирование прервётся. -Само резервирование происходит в фоновом процессе. А в консоль выводится информация о процессе резервирования через команду '''ps -u'''. При прерывании этого процесса через Ctrl+C резервирование не прекращается. Если необходимо прервать резервирование, то нужно: +Само резервирование происходит в фоновом процессе. А в консоль выводится информация о процессе резервирования через команду `ps -u`. При прерывании этого процесса через Ctrl+C резервирование не прекращается. Если необходимо прервать резервирование, то нужно: -1. '''ps -u''' узнать PID процесса zfs send - -2. '''kill 12345''' Завершить номер процесса +1. `ps -u` узнать PID процесса zfs sends +` +2. `kill 12345` Завершить номер процесса -- 2.47.2 From 7658041e3d49378d872c992ce331504b94f18f1b Mon Sep 17 00:00:00 2001 From: pankovea Date: Fri, 8 Nov 2024 15:45:26 +0000 Subject: [PATCH 04/11] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=20README.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 51 ++++++++++++++++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index aef1fbc..5297d7d 100644 --- a/README.md +++ b/README.md @@ -1,33 +1,46 @@ # Скрипт для резервирования данных Скрипт используется для резервирования датасетов файловой системы zfs. Необходим запуск от суперпользователя. + ## Флаги Используемая оболочка: zsh +`./zfs_send.zsh` +- `-rs | --remote-server <>` IP адрес или доменное имя сервера для резервирования (по умолчанию 192.168.0.162) +- `-ru | --remote-user <>` Пользователь SSH на удалённом сервер (по умолчанию root) +- `-s | --snapshot <>` Задать имя снимка для резервирования (по умолчанию manual-YYYYMMDD, где YMD - текущая дата, месяц и день) +- `-ld | --local-dataset <>` Резервируемый датасет (по умолчанию интерактивный режим) +- `-rd | --remote-dataset <>` Нахождение датасета на удалённой машине (по умолчанию интерактивный режим) +- `-i | --incremental` Режим резервирования (по умолчанию полная копия) +Данные по умолчанию можно отредактировать в самом скрипте в рамом начале. -```zsh -./zfs_send.zsh --r|--backup-server IP адрес или доменное имя сервера для резервирования --г|--baсkup-user Пользователь SSH на удалённом сервер (по умолчанию root) --s|--snapshot Задать имя снимка для резервирования --i|--incremental Режим резервирования (по умолчанию полная копия) -``` - -Ожидается что доступ по SSH на удалённую машину уже настроен +Ожидается, что доступ по SSH на удалённую машину уже настроен ### Режим резервирования датасета целиком -Если не использован флаг -i -Скрипт запросит список датасетов на локально машине и на удалённом сервере. Будет предлоден выбор какой датасет на какой резервировать. +Когда не использован флаг `-i` +Если не указаны `--local-dataset` и `--remote-dataset`, то скрипт запросит список датасетов на локальной машине и на удалённом сервере. Будет предлоден выбор какой датасет на какой резервировать. +Примеры: +- `./zfs_send.zsh` Полная резервная копия дата сета на сервер по умолчанию. Указать датасаты для резервирования можно в интерактивном режиме. +- `./zfs_send.zsh -rs backup.local -ld /main_pool/Documents -rd /backup` В данном случае на удалённой машине будет создан датасет `/backup/Documents`. Если такой датасет уже существует, то будет выдана ошибка и резервирование остановится. -# Режим инкрементный -После полного резервирования возможно сделать резерв только изменённой части файловой системы. -Флаг -i -Скрипт сопоставит имеющиеся датасеты на локальной и удалённых машинах и предложит возможные варианты резервирования. Сопоставление производится по совпадения названий датасетов нижнего уровня. +#### Пример вывода в процессе работы -# После запуска +### Режим инкрементный +После полного резервирования возможно сделать резерв только изменённой части файловой системы указав флаг `-i` +Если не указаны `--local-dataset` и `--remote-dataset`, то скрипт сопоставит имеющиеся датасеты на локальной и удалённых машинах и предложит возможные варианты резервирования. Сопоставление производится по совпадения названий датасетов нижнего уровня. +- `./zfs_send.zsh -rs 192.168.0.162 -i` Инкрементная резервная копия дата сета. Указать датасаты для резервирования в интерактивном режиме. +- `./zfs_send.zsh -rs 192.168.0.162 -i -ld /main_pool/Documents -rd /backup/Documents` В данном случае на удалённой машине должен существовать датасет `/backup/Documents` и у него должны сохранятся прошлые снимки файловой системы для определения новых файлов для резервирования. Если такой датасет отсутвует, то будет выдана ошибка и резервирование остановится. + +#### Пример вывода в процессе работы + +### Примеры запуска +- `./zfs_send.zsh` Полная резервная копия выбранного в интерактивном режиме дата сета на сервер по умолчанию. Будет создан снимок файловой системы с названием `manual_20241104`, где цифры - текщая дата. +- `./zfs_send.zsh -rs 192.168.0.21 --snapshot man_2405` Полная резервная копия дата сета на сервер `192.168.0.21`. Будет создан снимок файловой системы с названием `man_2405`. Датасет будет +- `./zfs_send.zsh -i` Инерментная копия на сервер по умолчанию. Датасеты будут сопоставлены автоматически. Будет предложен выбор резервируемых датасетов. + +## После запуска Во время резервирования данных нельзя закрывать сессию пользователя. Иначе резервирование прервётся. -Само резервирование происходит в фоновом процессе. А в консоль выводится информация о процессе резервирования через команду `ps -u`. При прерывании этого процесса через Ctrl+C резервирование не прекращается. Если необходимо прервать резервирование, то нужно: - +Само резервирование происходит в фоновом процессе. А в консоль выводится информация о прогрессе резервирования через команду `ps -u`. При прерывании этого процесса через `Ctrl+C` резервирование не прекращается. Если необходимо прервать резервирование, то нужно: 1. `ps -u` узнать PID процесса zfs sends -` 2. `kill 12345` Завершить номер процесса +или просто завершить сессию пользователя. \ No newline at end of file -- 2.47.2 From 2541094bdf24c5c30b35cb46d176437af873c6f1 Mon Sep 17 00:00:00 2001 From: pankovea Date: Fri, 8 Nov 2024 19:52:00 +0000 Subject: [PATCH 05/11] =?UTF-8?q?=D0=98=D0=B7=D0=BC=D0=B5=D0=BD=D0=B5?= =?UTF-8?q?=D0=BD=D1=8B=20=D0=BA=D0=BB=D1=8E=D1=87=D0=B8=20=D0=B7=D0=B0?= =?UTF-8?q?=D0=BF=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" -- 2.47.2 From 4ec02778027f11e519b78e9301eb06d88ce038bd Mon Sep 17 00:00:00 2001 From: pankovea Date: Fri, 8 Nov 2024 19:56:12 +0000 Subject: [PATCH 06/11] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=20zfs=5Fsend.zsh?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- zfs_send.zsh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zfs_send.zsh b/zfs_send.zsh index 3146163..9b711db 100755 --- a/zfs_send.zsh +++ b/zfs_send.zsh @@ -32,7 +32,7 @@ while [[ "$#" -gt 0 ]]; do echo "-s | --snapshot "; echo "-ld | --local-dataset "; echo "-rd | --remote-dataset "; - echo "-l | --log-file "; echo " --no-progress"; echo " --no-check"; echo " --stop"; -- 2.47.2 From 5b1b327e2252709e7e0e466f3198f2ef822af079 Mon Sep 17 00:00:00 2001 From: pankovea Date: Fri, 8 Nov 2024 20:02:13 +0000 Subject: [PATCH 07/11] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=20README.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 5297d7d..5a7116c 100644 --- a/README.md +++ b/README.md @@ -5,13 +5,17 @@ ## Флаги Используемая оболочка: zsh `./zfs_send.zsh` +- `-h | --help` Кратская справка по ключам - `-rs | --remote-server <>` IP адрес или доменное имя сервера для резервирования (по умолчанию 192.168.0.162) - `-ru | --remote-user <>` Пользователь SSH на удалённом сервер (по умолчанию root) -- `-s | --snapshot <>` Задать имя снимка для резервирования (по умолчанию manual-YYYYMMDD, где YMD - текущая дата, месяц и день) +- `-s | --snapshot <>` Задать имя снимка для резервирования (по умолчанию manual-YYYYMMDD, где YMD - текущая дата, месяц и день) - `-ld | --local-dataset <>` Резервируемый датасет (по умолчанию интерактивный режим) - `-rd | --remote-dataset <>` Нахождение датасета на удалённой машине (по умолчанию интерактивный режим) -- `-i | --incremental` Режим резервирования (по умолчанию полная копия) +- `-i | --incremental` Режим резервирования (по умолчанию полная копия) Данные по умолчанию можно отредактировать в самом скрипте в рамом начале. +- ` --no-progress` Не выводить прогресс +- ` --no-check` Не запрашивать подтверждения. Если нужно запускать скрипт по расписанию +- ` --stop` Остановить текщие фоновые процессы резервирования, запучщенные скриптом Ожидается, что доступ по SSH на удалённую машину уже настроен @@ -32,15 +36,13 @@ #### Пример вывода в процессе работы -### Примеры запуска +## Примеры запуска - `./zfs_send.zsh` Полная резервная копия выбранного в интерактивном режиме дата сета на сервер по умолчанию. Будет создан снимок файловой системы с названием `manual_20241104`, где цифры - текщая дата. - `./zfs_send.zsh -rs 192.168.0.21 --snapshot man_2405` Полная резервная копия дата сета на сервер `192.168.0.21`. Будет создан снимок файловой системы с названием `man_2405`. Датасет будет - `./zfs_send.zsh -i` Инерментная копия на сервер по умолчанию. Датасеты будут сопоставлены автоматически. Будет предложен выбор резервируемых датасетов. +- `./zfs_send.zsh --stop` Прервать запущенные ранее операции резервирования ## После запуска Во время резервирования данных нельзя закрывать сессию пользователя. Иначе резервирование прервётся. -Само резервирование происходит в фоновом процессе. А в консоль выводится информация о прогрессе резервирования через команду `ps -u`. При прерывании этого процесса через `Ctrl+C` резервирование не прекращается. Если необходимо прервать резервирование, то нужно: -1. `ps -u` узнать PID процесса zfs sends -2. `kill 12345` Завершить номер процесса -или просто завершить сессию пользователя. \ No newline at end of file +Само резервирование происходит в фоновом процессе. Если необходимо прервать процесс, то воспользуйтесь коммандой `./zfs_send.zsh --stop` -- 2.47.2 From 874662feb46de7604dd1cb68d4edfc519e0ed641 Mon Sep 17 00:00:00 2001 From: root Date: Thu, 27 Mar 2025 19:26:34 +0300 Subject: [PATCH 08/11] =?UTF-8?q?fix:=20=D0=B2=D1=8B=D0=B2=D0=BE=D0=B4=20?= =?UTF-8?q?=D0=BF=D1=80=D0=BE=D0=B3=D1=80=D0=B5=D1=81=D1=81=D0=B0;=20add:?= =?UTF-8?q?=20auto=20ssh=20setup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 9 ++- zfs_send.zsh | 173 +++++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 154 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 5a7116c..b125923 100644 --- a/README.md +++ b/README.md @@ -14,10 +14,17 @@ - `-i | --incremental` Режим резервирования (по умолчанию полная копия) Данные по умолчанию можно отредактировать в самом скрипте в рамом начале. - ` --no-progress` Не выводить прогресс +- ` --progress-only` Запустить только вывод прогресса, если уже запущено резервирование в флне - ` --no-check` Не запрашивать подтверждения. Если нужно запускать скрипт по расписанию - ` --stop` Остановить текщие фоновые процессы резервирования, запучщенные скриптом -Ожидается, что доступ по SSH на удалённую машину уже настроен +### Настройка доступа по ключу SSH + +Ожидается, что доступ по SSH на удалённую машину уже настроен. Доступ нужен с использованием ssh-ключа. +Если доступ по паролю, то скприт попытается создать ключ и закинуть его на сервер (по паролю) + +Если после удачной процедуры всё же не получается подсоединиться, то нужно вручную добавлять выведеный на экран публичный ключ на бэкап-сервер, +возможно через веб-гуй. ### Режим резервирования датасета целиком Когда не использован флаг `-i` diff --git a/zfs_send.zsh b/zfs_send.zsh index 9b711db..0aa0401 100755 --- a/zfs_send.zsh +++ b/zfs_send.zsh @@ -7,6 +7,7 @@ incremental=false silent=false stop=false progress=true +progress_only=false check=true LOGFILE="log_bak_${inc_snapshot}.log" unset local_dataset @@ -17,13 +18,14 @@ echo -ne "Обработка аргументов...\033[2K\r" while [[ "$#" -gt 0 ]]; do case $1 in -rs|--remote-server) backup_server="$2"; shift ;; - -гu|--remote-user) backup_user="$2"; shift ;; + -ru|--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 ;; --no-progress) progress=false ;; + --progress-only) progress_only=true ;; --no-check) check=false ;; --stop) stop=true ;; -h|--help) echo "Использование: ./zfs_send.zsh [OPTIONS]"; @@ -34,18 +36,101 @@ while [[ "$#" -gt 0 ]]; do echo "-rd | --remote-dataset "; echo "-l | --log-file "; echo " --no-progress"; + echo " --progress-only"; echo " --no-check"; echo " --stop"; - echo "-i | --incremental" ;; + echo "-i | --incremental" ; + echo "-p | --progress" ;; *) echo "Неизвестные параметры: $1"; exit 1 ;; esac shift done + +# Функция для записи сообщения в файл и на экран +echo_log () { + local text=$1 + echo $text + echo $text >> "$LOGFILE" +} + + +# Функция для определения SSH-ключа через конфигурацию +find_ssh_key() { + local key + + # Получаем IdentityFile из конфигурации + identity_files=$(ssh -G "$backup_user@$backup_server" 2>/dev/null | awk '/^IdentityFile / {print $2}') + + # Проверяем каждый IdentityFile из конфига + for file in $identity_files; do + if [[ -f "$file" ]]; then + echo "$file" + return + fi + done + + # Если ничего не найдено, ищем стандартные ключи + for candidate in ~/.ssh/id_rsa ~/.ssh/id_ecdsa ~/.ssh/id_ed25519; do + if [[ -f "$candidate" ]]; then + echo "$candidate" + return + fi + done + + # Если ключей нет, создаем новый + # echo "Создаём новый ключ..." + ssh-keygen -t ed25519 -N "" -f ~/.ssh/id_ed25519 >&2 + echo "~/.ssh/id_ed25519" +} + +# Определение ключа +key_path=$(find_ssh_key) +key_pub_path="${key_path}.pub" + + +check_ssh_connection() { + ssh -o "BatchMode=yes" -o "ConnectTimeout=5" "$backup_user@$backup_server" exit 2>/dev/null + return $? +} + + +setup_ssh_key() { + key_content=$(cat "$key_pub_path") + echo "Установка SSH-ключа на $backup_user@$backup_server..." + + # Передача публичного ключа вручную + ssh $backup_user@$backup_server "mkdir -p ~/.ssh && echo '$key_content' >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys && chmod 700 ~/.ssh" + + echo " +host $backup_server +hostname $backup_server +user $backup_user +identityFile $key_path +identitiesOnly yes +" >> ~/.ssh/config + + if [[ $? -ne 0 ]]; then + echo "Ошибка: Не удалось установить SSH-ключ. Попробуйте прописать его на сервер вручную." + echo "----------" + echo $key_content + echo "----------" + exit 1 + else + echo "SSH-ключ успешно установлен!" + fi +} + + + +# Если нужно вывести только прогресс, то пропускаем все этапы подготовки и резервирования +if [[ $progress_only = false ]]; then + + # Завершение запущенного процесса резервирования if [[ $stop = true ]]; then pids=$(ps -t $(tty | sed 's|/dev/||') -o pid,args | grep -e zfs_send.zsh -e "zfs send" | grep -v -- '--stop') # Список PIDs с командой запуска - + if [[ $pids = "" ]]; then echo "Нет запущенного процесса резервирования в этом терминале." read "srch?Икать во всех процессах системы? (y/N) " @@ -53,7 +138,7 @@ if [[ $stop = true ]]; then pids=$(ps -o pid,args | grep -e zfs_send.zsh -e "zfs send" | grep -v -- '--stop' ) fi fi - + # Подтверждение if [[ $pids = "" ]]; then echo "Нет запущенного процесса резервирования" @@ -73,14 +158,24 @@ if [[ $stop = true ]]; then exit 0 fi -echo -n "Резервирование на " + # Проверка соединения -if ssh $backup_user@$backup_server 'exit'; then; - echo "$backup_user@$backup_server ok"; +echo "Резервирование на $backup_user@$backup_server" +if check_ssh_connection; then + echo "Connection OK" else - echo "Настройте ssh и запустите скрипт повторно" - exit 1 + echo "SSH-ключ не обнаружен на сервере. Начинаем настройку..." + setup_ssh_key + if check_ssh_connection; then + echo "Connection OK" + else + echo "Ошибка: SSH-ключ не работает. Проверьте настройки." + exit 1 + fi fi + + +# ---------- echo "Снимок файловой системы $inc_snapshot" case $incremental in @@ -177,7 +272,7 @@ 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} # удалить @ в начале строки if [[ $incremental = true ]]; then - bak_snap="$(ssh $backup_user@$backup_server zfs list -t snapshot -o name ${bak_ds} | grep 'manual' | tail -n1 | egrep -o '@.+' )" + bak_snap=$(ssh $backup_user@$backup_server zfs list -t snapshot -o name ${bak_ds} | grep 'manual' | tail -n1 | egrep -o '@.+') bak_snap=${bak_snap:1} # удалить @ в начале строки fi # echo "$loc_ds @ $loc_snap -> $bak_ds @ $bak_snap" @@ -262,15 +357,10 @@ fi if [[ $work = "y" || $work = "Y" ]]; then TS0=$(date +%s) - echo $TS0 > /dev/shm/backup_time_stamp # Сохраняем отметку о времени начала + echo $TS0 > /dev/shm/backup_time_start # Сохраняем отметку о времени начала + echo $TS0 > /dev/shm/backup_time_circle # Сохраняем отметку о времени круга echo "Результат работы записываю в файл $LOGFILE" - # Функция для записи сообщения в файл и на экран - echo_log () { # $1 = Сообщение - echo $1 - echo $1 >> "$LOGFILE" - } - # Записать лог список резервируемых датасетов echo "--- $(date +'%Y.%m.%d %H:%M.%S') ---" >> "$LOGFILE" echo "Список резервируемых датасетов:" >> "$LOGFILE" @@ -286,34 +376,55 @@ if [[ $work = "y" || $work = "Y" ]]; then 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) - if [[ $progress == true ]]; then echo $TS1 > /dev/shm/backup_time_stamp; fi # Сохраняем отметку о времени начала + if [[ $progress == true ]]; then echo $TS1 > /dev/shm/backup_time_circle; 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')" case $incremental in - true) zfs send -V -i ${loc_ds}@${last_loc_snaps[$loc_ds]} ${loc_ds}@${inc_snapshot} | ssh "$backup_user@${backup_server}" zfs receive ${bak_ds}@${inc_snapshot} ;; - false) zfs send -V ${loc_ds}@${inc_snapshot} | ssh "$backup_user@${backup_server}" zfs receive ${bak_ds} ;; + true) zfs send -V -i ${loc_ds}@${last_loc_snaps[$loc_ds]} ${loc_ds}@${inc_snapshot} | ssh $backup_user@$backup_server zfs receive ${bak_ds}@${inc_snapshot} ;; + false) zfs send -V ${loc_ds}@${inc_snapshot} | ssh $backup_user@$backup_server zfs receive ${bak_ds} ;; esac TS2=$(date +%s) (( 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 & # Запустить резервирование в фоне +fi + +# Конец блока условия progress_only +fi + + +# Вывод прогресса +if [[ $progress == true ]]; then + echo "" + + pids=$(ps -t $(tty | sed 's|/dev/||') -o pid,args | grep -e zfs_send.zsh -e "zfs send" ) # Список PIDs с командой запуска + + if [[ $pids = "" ]]; then + echo "Нет запущенного процесса резервирования в этом терминале." + read "srch?Икать во всех процессах системы? (y/N) " + if [[ $srch = y ]]; then + pids=$(ps -o pid,args | grep -e zfs_send.zsh -e "zfs send" ) + fi + fi # Выводить прогресс запрашивая список запущенных процессов - if [[ $progress == true ]]; then + if [[ $pids = "" ]]; then + echo "Нет запущенного процесса резервирования" + else 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/") + progress=$(ps -u | grep "sending" | 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) + TS1=$(< /dev/shm/backup_time_circle) elapsed=$(( $(date +%s) - TS1 )) fi time_part_of_percent=$(( now - time_changed_percent_value )) # Время с последнего изменения процента @@ -335,11 +446,19 @@ if [[ $work = "y" || $work = "Y" ]]; then i_task=$(< /dev/shm/backup_i_task) done - # Удалить временные файлы состояний - 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" + if [[ -f /dev/shm/backup_time_start ]]; then + TS0=$(< /dev/shm/backup_time_start) + echo_log "\n--- Все завершено за $(date -d@$(( $(date +%s) - $TS0 )) -u '+%H:%M.%S') ---\n" + else + echo_log "\n--- Все задания завершены ---\n" + fi + + # Удалить временные файлы состояний, если они существуют + [ -f /dev/shm/backup_i_task ] && rm /dev/shm/backup_i_task + [ -f /dev/shm//dev/shm/backup_time_start ] && rm /dev/shm/backup_time_start + [ -f /dev/shm//dev/shm/backup_time_circle ] && rm /dev/shm/backup_time_circle + fi -- 2.47.2 From f55223926f6b6d831ed8448537cce37550ec990d Mon Sep 17 00:00:00 2001 From: root Date: Fri, 28 Mar 2025 00:01:47 +0300 Subject: [PATCH 09/11] =?UTF-8?q?add:=20=D0=BE=D1=82=D0=BE=D0=B1=D1=80?= =?UTF-8?q?=D0=B0=D0=B6=D0=B5=D0=BD=D0=B8=D0=B5=20=D1=81=D0=BA=D0=BE=D1=80?= =?UTF-8?q?=D0=BE=D1=81=D1=82=D0=B8=20=D0=BA=D0=BE=D0=BF=D0=B8=D1=80=D0=BE?= =?UTF-8?q?=D0=B2=D0=B0=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- zfs_send.zsh | 165 ++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 137 insertions(+), 28 deletions(-) diff --git a/zfs_send.zsh b/zfs_send.zsh index 0aa0401..78a7b30 100755 --- a/zfs_send.zsh +++ b/zfs_send.zsh @@ -1,5 +1,16 @@ #!/bin/zsh +#TODO list +# 1. Скрипт не определяет, что нужно остановиться +# 2. Возникают ошибки: +# sending main_pool/3D_Design@manual-20250327 (99%: 21.9G/21.9G) 25.6M/s date: invalid date ‘@0.’ +# date: invalid date ‘@954.’ +# sending main_pool/3D_Design@manual-20250327 (100%: 21.9G/21.9G) 25.6M/s date: invalid date ‘@-1.’ +# date: invalid date ‘@954.’ +# 3. Нужно автоматически определить, если копирование уже запущено то предложить остановить и вывести процесс резервирования +# смотреть нужно сразу на всей системе. Не будем запускать несколько параллельных резервирований. + + backup_server="192.168.0.162" # Сервер для резервирования backup_user="root" # Пользователь на сервере inc_snapshot="manual-$(date +%Y%m%d)" # Новый создаваемый снисмок @@ -123,10 +134,14 @@ identitiesOnly yes + +# ! # Если нужно вывести только прогресс, то пропускаем все этапы подготовки и резервирования if [[ $progress_only = false ]]; then + + # Завершение запущенного процесса резервирования if [[ $stop = true ]]; then pids=$(ps -t $(tty | sed 's|/dev/||') -o pid,args | grep -e zfs_send.zsh -e "zfs send" | grep -v -- '--stop') # Список PIDs с командой запуска @@ -369,6 +384,7 @@ if [[ $work = "y" || $work = "Y" ]]; then (( n_tasks++ )) echo "$key \t ${last_loc_snaps[$loc_ds]} \t -> \t${work_datasets[$loc_ds]}" >> "$LOGFILE" done + echo $n_tasks > /dev/shm/backup_n_tasks echo_log echo_log "--- Резервирую данные ---" @@ -391,74 +407,167 @@ if [[ $work = "y" || $work = "Y" ]]; then done & # Запустить резервирование в фоне fi + + +# ! # Конец блока условия progress_only fi + + # Вывод прогресса +# Это отдельный блок программы, который не имеет прямого доступа к переменным процесса в фоне. +# Обмен переменными проивится ерез временный файл if [[ $progress == true ]]; then echo "" - pids=$(ps -t $(tty | sed 's|/dev/||') -o pid,args | grep -e zfs_send.zsh -e "zfs send" ) # Список PIDs с командой запуска + pids=$(ps -t $(tty | sed 's|/dev/||') -o pid,args | grep -e zfs_send.zsh -e "zfs send" | grep -v -- 'grep' | grep -v -- 'progress-only' ) # Список PIDs с командой запуска if [[ $pids = "" ]]; then echo "Нет запущенного процесса резервирования в этом терминале." read "srch?Икать во всех процессах системы? (y/N) " if [[ $srch = y ]]; then - pids=$(ps -o pid,args | grep -e zfs_send.zsh -e "zfs send" ) + pids=$(ps a -o pid,args | grep -e zfs_send.zsh -e "zfs send" | grep -v -- 'grep' | grep -v -- 'progress-only' ) fi fi - +echo $pids # Выводить прогресс запрашивая список запущенных процессов if [[ $pids = "" ]]; then echo "Нет запущенного процесса резервирования" else echo "" + # инициализируем переменнные + TS0=$(< /dev/shm/backup_time_start) + TS1=$(< /dev/shm/backup_time_circle) + last_percent=0 - time_changed_percent_value=$(date +%s) + last_volume=0 + last_volume_time=$TS1 + speed=0 + reset_interval=10 # интревал сбрасывания скорости до нуля, в случае если нет именений + last_update_time=$TS1 # Время последнего обновления + + time_changed_percent_value=$TS1 + + n_tasks=$(< /dev/shm/backup_n_tasks) + i_task=$(< /dev/shm/backup_i_task) while [[ $i_task != $n_tasks ]]; do # выводим статус пока не завершены все задачи progress=$(ps -u | grep "sending" | grep -v "grep" | sed -r "s/(.*) zfs: (.*)/\2/") - percent=$(echo $progress | cut -d "(" -f2 | cut -d "%" -f1 ) + + # Извлекаем объем и процент + progress_part=$(echo "$progress" | grep -oP '\(\K.*(?=\))') # Извлекаем только часть в скобках + + if [[ -n $progress_part ]]; then + # Разбиваем часть на компоненты + # Процент определим по объёму + # percent=$(echo "$progress_part" | awk -F': |/' '{print $1}' | tr -d '%') + current_volume_str=$(echo "$progress_part" | awk -F': |/' '{print $2}') + total_volume_str=$(echo "$progress_part" | awk -F': |/' '{print $3}') + + # Определяем единицу для интервала сброса + unit=${current_volume_str: -1} + case $unit in + G) reset_interval=100;; + M) reset_interval=10;; + *) reset_interval=5;; + esac + + # Преобразуем в байты + current=$(echo "$current_volume_str" | awk '{sub(/[^0-9.]/,""); print $1}') + case $unit in + G) current_volume=$(( current * 1073741824 )) ;; + M) current_volume=$(( current * 1048576 )) ;; + K) current_volume=$(( current * 1024 )) ;; + *) current_volume=0 ;; + esac + + total=$(echo "$total_volume_str" | awk '{sub(/[^0-9.]/,""); print $1}') + case $unit in + G) total_volume=$(( total * 1073741824 )) ;; + M) total_volume=$(( total * 1048576 )) ;; + K) total_volume=$(( total * 1024 )) ;; + *) total_volume=0 ;; + esac + + percent=$(( current_volume * 100 / total_volume )) + else + percent=0 + current_volume=0 + fi + + # Вычисляем скорость + now=$(date +%s) + + # Если объем изменился + elapsed_from_vol_change=$((now - last_volume_time)) + if [[ $current_volume -ne $last_volume && elapsed_from_vol_change -ne 0 ]]; then + speed=$(awk "BEGIN { printf \"%.1f\", ($current_volume - $last_volume) / $elapsed_from_vol_change }") + last_volume=$current_volume + last_volume_time=$now + last_update_time=$now # Обновляем время последнего изменения + else + # Если объем не изменился, проверяем время с последнего обновления + time_since_update=$((now - last_update_time)) + if (( time_since_update >= reset_interval )); then + speed=0 # Обнуляем скорость, если нет изменений в течение интервала + last_update_time=$now # Обновляем время + fi + fi + + # Конвертируем скорость в человекочитаемый формат + if (( speed > 1073741824 )); then + speed_human=$(awk "BEGIN { printf \"%.1fG\", $speed / 1073741824 }") + elif (( speed > 1048576 )); then + speed_human=$(awk "BEGIN { printf \"%.1fM\", $speed / 1048576 }") + elif (( speed > 1024 )); then + speed_human=$(awk "BEGIN { printf \"%.1fK\", $speed / 1024 }") + else + speed_human="${speed}B" + fi + + + # Начинаем вывод + echo -n "\033[2K\r$progress $speed_human/s " + 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_circle) - elapsed=$(( $(date +%s) - TS1 )) + elapsed_total=$(( $(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 )) # Осталось времени на задачу + estimated_total=$(( 100 / percent * elapsed_total )) # Всего времени на задачу + estimated_remain=$(( estimated_total - elapsed_total - 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') " + echo -n " $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') " + echo -n " $(date -d@$estimated_remain -u '+%H:%M.%S') ост. / $(date -d@$estimated_total -u '+%H:%M.%S') " fi - sleep 1 else - echo -ne "\033[2K\r$progress " - sleep 1 - for ((i = 1; i < 3 ; i++)); do echo -n "."; sleep 1; done + for ((i = 1; i < 4 ; i++)); do echo -n "."; sleep 1; done fi - i_task=$(< /dev/shm/backup_i_task) + + sleep 1 + done + if [[ -f /dev/shm/backup_time_start ]]; then + TS0=$(< /dev/shm/backup_time_start) + echo_log "\n--- Все завершено за $(date -d@$(( $(date +%s) - $TS0 )) -u '+%H:%M.%S') ---\n" + else + echo_log "\n--- Все задания завершены ---\n" + fi + + # Удалить временные файлы состояний, если они существуют + [ -f /dev/shm/backup_i_task ] && rm /dev/shm/backup_i_task + [ -f /dev/shm//dev/shm/backup_time_start ] && rm /dev/shm/backup_time_start + [ -f /dev/shm//dev/shm/backup_time_circle ] && rm /dev/shm/backup_time_circle + fi - if [[ -f /dev/shm/backup_time_start ]]; then - TS0=$(< /dev/shm/backup_time_start) - echo_log "\n--- Все завершено за $(date -d@$(( $(date +%s) - $TS0 )) -u '+%H:%M.%S') ---\n" - else - echo_log "\n--- Все задания завершены ---\n" - fi - - # Удалить временные файлы состояний, если они существуют - [ -f /dev/shm/backup_i_task ] && rm /dev/shm/backup_i_task - [ -f /dev/shm//dev/shm/backup_time_start ] && rm /dev/shm/backup_time_start - [ -f /dev/shm//dev/shm/backup_time_circle ] && rm /dev/shm/backup_time_circle - - fi -- 2.47.2 From 1738c35abbd3705d93bf87e7927de00d7a05667e Mon Sep 17 00:00:00 2001 From: root Date: Mon, 14 Jul 2025 16:28:06 +0300 Subject: [PATCH 10/11] =?UTF-8?q?fix:=20*=20=D0=A1=D0=B5=D1=80=D0=B2=D0=B5?= =?UTF-8?q?=D1=80=20=D0=BF=D0=BE=20=D1=83=D0=BC=D0=BE=D0=BB=D1=87=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D1=8E=20120=20*=20=D0=9D=D0=B5=20=D0=B2=D1=8B?= =?UTF-8?q?=D1=85=D0=BE=D0=B4=D0=B8=D0=BB=20=D0=B8=D0=B7=20=D1=81=D0=BA?= =?UTF-8?q?=D1=80=D0=B8=D0=BF=D1=82=D0=B0=20=D0=BF=D0=BE=D1=81=D0=BB=D0=B5?= =?UTF-8?q?=20=D0=B2=D1=8B=D0=B2=D0=BE=D0=B4=D0=B0=20=D1=81=D0=BF=D1=80?= =?UTF-8?q?=D0=B0=D0=B2=D0=BA=D0=B8=20-h,=20=D0=B0=20=D0=BF=D1=80=D0=BE?= =?UTF-8?q?=D0=B4=D0=BE=D0=BB=D0=B6=D0=B0=D0=BB=20=D0=B7=D0=B0=D0=BF=D1=83?= =?UTF-8?q?=D1=81=D0=BA=20*=20=D0=A3=D1=81=D1=82=D1=80=D0=B0=D0=BD=D0=B5?= =?UTF-8?q?=D0=BD=D1=8B=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BA=D0=B8=20=D0=B2?= =?UTF-8?q?=D1=8B=D0=B2=D0=BE=D0=B4=D0=B0=20=D1=81=D1=82=D0=B0=D1=82=D0=B8?= =?UTF-8?q?=D1=81=D1=82=D0=B8=D0=BA=D0=B8=20(invalid=20date=20=E2=80=98@95?= =?UTF-8?q?4.)=20*=20=D0=92=D1=8B=D1=85=D0=BE=D0=B4=20=D0=B8=D0=B7=20?= =?UTF-8?q?=D0=B1=D0=B5=D1=81=D0=BA=D0=BE=D0=BD=D0=B5=D1=87=D0=BD=D0=BE?= =?UTF-8?q?=D0=B3=D0=BE=20=D1=86=D0=B8=D0=BA=D0=BB=D0=B0=20=D0=BF=D1=80?= =?UTF-8?q?=D0=BE=D0=B2=D0=B5=D1=80=D0=BA=D0=B8=20(=D1=82=D0=B5=D0=BF?= =?UTF-8?q?=D0=B5=D1=80=D1=8C=20=D1=87=D0=B8=D1=82=D0=B0=D0=B5=D1=82=20i?= =?UTF-8?q?=5Ftask=20=D0=BD=D0=B0=20=D0=BA=D0=B0=D0=B6=D0=B4=D0=BE=D0=B9?= =?UTF-8?q?=20=D0=B8=D1=82=D0=B5=D1=80=D1=80=D0=B0=D1=86=D0=B8=D0=B8)=20*?= =?UTF-8?q?=20=D0=9E=D1=87=D0=B8=D1=89=D0=B0=D0=B5=D1=82=20=D0=B2=D1=80?= =?UTF-8?q?=D0=B5=D0=BC=D0=B5=D0=BD=D0=BD=D1=8B=D0=B5=20=D0=BF=D0=B5=D1=80?= =?UTF-8?q?=D0=B5=D0=BC=D0=B5=D0=BD=D0=BD=D1=8B=D0=B5=20=D0=BF=D0=BE=20?= =?UTF-8?q?=D0=B7=D0=B0=D0=B2=D0=B5=D1=80=D1=88=D0=B5=D0=BD=D0=B8=D0=B8=20?= =?UTF-8?q?=D1=81=D0=BA=D1=80=D0=B8=D0=BF=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + zfs_send.zsh | 47 ++++++++++++++++++++++++++++------------------- 2 files changed, 29 insertions(+), 19 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c802e8f --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +log* diff --git a/zfs_send.zsh b/zfs_send.zsh index 78a7b30..e9a4512 100755 --- a/zfs_send.zsh +++ b/zfs_send.zsh @@ -2,16 +2,12 @@ #TODO list # 1. Скрипт не определяет, что нужно остановиться -# 2. Возникают ошибки: -# sending main_pool/3D_Design@manual-20250327 (99%: 21.9G/21.9G) 25.6M/s date: invalid date ‘@0.’ -# date: invalid date ‘@954.’ -# sending main_pool/3D_Design@manual-20250327 (100%: 21.9G/21.9G) 25.6M/s date: invalid date ‘@-1.’ -# date: invalid date ‘@954.’ -# 3. Нужно автоматически определить, если копирование уже запущено то предложить остановить и вывести процесс резервирования +# 2. Нужно автоматически определить, если копирование уже запущено то предложить остановить и вывести процесс резервирования +# Нет проверки на верность введённых параметров # смотреть нужно сразу на всей системе. Не будем запускать несколько параллельных резервирований. -backup_server="192.168.0.162" # Сервер для резервирования +backup_server="192.168.0.120" # Сервер для резервирования backup_user="root" # Пользователь на сервере inc_snapshot="manual-$(date +%Y%m%d)" # Новый создаваемый снисмок incremental=false @@ -51,7 +47,7 @@ while [[ "$#" -gt 0 ]]; do echo " --no-check"; echo " --stop"; echo "-i | --incremental" ; - echo "-p | --progress" ;; + exit 0;; *) echo "Неизвестные параметры: $1"; exit 1 ;; esac shift @@ -90,16 +86,20 @@ find_ssh_key() { done # Если ключей нет, создаем новый - # echo "Создаём новый ключ..." - ssh-keygen -t ed25519 -N "" -f ~/.ssh/id_ed25519 >&2 - echo "~/.ssh/id_ed25519" + if [ ! -f ~/.ssh/id_ed25519 ]; then + ssh-keygen -t ed25519 -N "" -f ~/.ssh/id_ed25519 + # echo "Создаём новый ключ..." + ssh-keygen -t ed25519 -N "" -f ~/.ssh/id_ed25519 >&2 + echo "~/.ssh/id_ed25519" + else + echo "Ключей для этого сервера не найдено. Ключ по умолчанию ~/.ssh/id_ed25519 уже существует. Создание нового ключа прервано" + fi } # Определение ключа key_path=$(find_ssh_key) key_pub_path="${key_path}.pub" - check_ssh_connection() { ssh -o "BatchMode=yes" -o "ConnectTimeout=5" "$backup_user@$backup_server" exit 2>/dev/null return $? @@ -451,8 +451,8 @@ echo $pids time_changed_percent_value=$TS1 n_tasks=$(< /dev/shm/backup_n_tasks) - i_task=$(< /dev/shm/backup_i_task) - while [[ $i_task != $n_tasks ]]; do # выводим статус пока не завершены все задачи + while [[ $i_task -lt $n_tasks ]]; do # выводим статус пока не завершены все задачи + i_task=$(< /dev/shm/backup_i_task) progress=$(ps -u | grep "sending" | grep -v "grep" | sed -r "s/(.*) zfs: (.*)/\2/") # Извлекаем объем и процент @@ -539,8 +539,15 @@ echo $pids elapsed_total=$(( $(date +%s) - TS1 )) fi time_part_of_percent=$(( now - time_changed_percent_value )) # Время с последнего изменения процента - estimated_total=$(( 100 / percent * elapsed_total )) # Всего времени на задачу - estimated_remain=$(( estimated_total - elapsed_total - time_part_of_percent )) # Осталось времени на задачу + # Используем awk для более точных расчётов + # estimated_total - Всего времени на задачу + # estimated_remain - Осталось времени на задачу + read estimated_total estimated_remain <<< $(awk -v p="$percent" -v t="$elapsed_total" -v tp="$time_part_of_percent" ' + BEGIN { + total = (p > 0 ? (100 / p * t) : 0); + remain = (total > 0 ? (total - t - tp) : 0); + printf("%d %d", total, remain) + }') 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 )) @@ -564,9 +571,11 @@ echo $pids fi # Удалить временные файлы состояний, если они существуют - [ -f /dev/shm/backup_i_task ] && rm /dev/shm/backup_i_task - [ -f /dev/shm//dev/shm/backup_time_start ] && rm /dev/shm/backup_time_start - [ -f /dev/shm//dev/shm/backup_time_circle ] && rm /dev/shm/backup_time_circle + [ -f /dev/shm/backup_i_task ] && rm /dev/shm/backup_i_task + [ -f /dev/shm//dev/shm/backup_time_start ] && rm /dev/shm/backup_time_start + [ -f /dev/shm//dev/shm/backup_time_circle ] && rm /dev/shm/backup_time_circle + # Обнуляем переменные после завершения задачи, чтобы избежать ошибок при повторном запуске + unset last_volume last_volume_time last_update_time last_percent time_changed_percent_value fi -- 2.47.2 From 909b5bbb49d5c66c1bb1e28b446d4342eb81536a Mon Sep 17 00:00:00 2001 From: root Date: Mon, 14 Jul 2025 17:58:04 +0300 Subject: [PATCH 11/11] =?UTF-8?q?fix:=20=D0=A4=D0=BE=D1=80=D0=BC=D0=B0?= =?UTF-8?q?=D1=82=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=20=D0=B2?= =?UTF-8?q?=D1=8B=D0=B2=D0=BE=D0=B4=D0=B0=20=D0=BF=D1=80=D0=B8=20=D0=B8?= =?UTF-8?q?=D0=BD=D0=BA=D1=80=D0=B5=D0=BC=D0=B5=D0=BD=D1=82=D0=BD=D0=BE?= =?UTF-8?q?=D0=BC=20=D0=BA=D0=BE=D0=BF=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D0=B8=20*=20=D1=83=D0=B1=D1=80=D0=B0=D0=BD=20=D0=B2?= =?UTF-8?q?=D1=8B=D0=B2=D0=BE=D0=B4=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BA=D0=B8?= =?UTF-8?q?=20=D0=BE=D1=82=D1=81=D1=83=D1=82=D0=B2=D1=83=D1=8E=D1=89=D0=B8?= =?UTF-8?q?=D1=85=20=D0=B4=D0=B0=D1=82=D0=B0=D1=81=D0=B5=D1=82=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- zfs_send.zsh | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/zfs_send.zsh b/zfs_send.zsh index e9a4512..b4df0c9 100755 --- a/zfs_send.zsh +++ b/zfs_send.zsh @@ -282,12 +282,13 @@ fi # Конец блока наличия переданных параметр # Словарь со списком последких снапшотов для датасетов declare -A last_loc_snaps echo -ne "Проверяю на наличие $inc_snapshot...\033[2K\r" +column_widths="%-45s * %-45s\n" 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="$(zfs list -t snapshot -o name ${loc_ds} 2>/dev/null | grep 'manual' | tail -n1 | egrep -o '@.+' )" loc_snap=${loc_snap:1} # удалить @ в начале строки if [[ $incremental = true ]]; then - bak_snap=$(ssh $backup_user@$backup_server zfs list -t snapshot -o name ${bak_ds} | grep 'manual' | tail -n1 | egrep -o '@.+') + bak_snap=$(ssh $backup_user@$backup_server zfs list -t snapshot -o name ${bak_ds} 2>/dev/null | grep 'manual' | tail -n1 | egrep -o '@.+') bak_snap=${bak_snap:1} # удалить @ в начале строки fi # echo "$loc_ds @ $loc_snap -> $bak_ds @ $bak_snap" @@ -295,12 +296,12 @@ for loc_ds bak_ds in ${(kv)work_datasets}; do if [[ "$loc_snap" = "$inc_snapshot" ]]; then if [[ $incremental = true ]] && [[ "$loc_snap" = "$bak_snap" ]]; then # Если последние снимки на обеих машинах совпадают в инкрементном режиме - echo "${loc_ds}@${loc_snap} * существует на обеих машинах. Исключено из списка резервируемых" + printf $column_widths "${loc_ds}@${loc_snap}" "существует на обеих машинах. Исключено из списка резервируемых" unset "work_datasets[$loc_ds]" else # Если на удалённой машине нет такого снимка, то предложить удалить. - echo "Датасет $loc_ds уже имеет последний снимок $inc_snapshot" - read "act?ok: резервировать | re: пересоздать (удаляет сразу!) | исключить из работы по умолчанию : " + printf $column_widths "${loc_ds}@${loc_snap}" "существует на исходной машине, но отсутвует на $backup_server" + read "act?ok: резервировать | re: пересоздать (удаляет сразу!!!) | по умолчанию исключить из работы : " case $act in re) zfs destroy "${loc_ds}@${loc_snap}"; loc_snap="$(zfs list -t snapshot -o name ${loc_ds} | grep 'manual' | tail -n1 | egrep -o '@.+' )"; @@ -317,6 +318,7 @@ for loc_ds bak_ds in ${(kv)work_datasets}; do last_loc_snaps[$loc_ds]=$loc_snap else if [[ $incremental = true ]]; then + printf $column_widths "${loc_ds}" "Не имеет снимков. Не возможно сделать инкрементный резерв." unset "work_datasets[$loc_ds]" fi fi -- 2.47.2