From 874662feb46de7604dd1cb68d4edfc519e0ed641 Mon Sep 17 00:00:00 2001 From: root Date: Thu, 27 Mar 2025 19:26:34 +0300 Subject: [PATCH] =?UTF-8?q?fix:=20=D0=B2=D1=8B=D0=B2=D0=BE=D0=B4=20=D0=BF?= =?UTF-8?q?=D1=80=D0=BE=D0=B3=D1=80=D0=B5=D1=81=D1=81=D0=B0;=20add:=20auto?= =?UTF-8?q?=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