fix: вывод прогресса; add: auto ssh setup

This commit is contained in:
root 2025-03-27 19:26:34 +03:00
parent 5b1b327e22
commit 874662feb4
2 changed files with 154 additions and 28 deletions

View File

@ -14,10 +14,17 @@
- `-i | --incremental` Режим резервирования (по умолчанию полная копия) - `-i | --incremental` Режим резервирования (по умолчанию полная копия)
Данные по умолчанию можно отредактировать в самом скрипте в рамом начале. Данные по умолчанию можно отредактировать в самом скрипте в рамом начале.
- ` --no-progress` Не выводить прогресс - ` --no-progress` Не выводить прогресс
- ` --progress-only` Запустить только вывод прогресса, если уже запущено резервирование в флне
- ` --no-check` Не запрашивать подтверждения. Если нужно запускать скрипт по расписанию - ` --no-check` Не запрашивать подтверждения. Если нужно запускать скрипт по расписанию
- ` --stop` Остановить текщие фоновые процессы резервирования, запучщенные скриптом - ` --stop` Остановить текщие фоновые процессы резервирования, запучщенные скриптом
Ожидается, что доступ по SSH на удалённую машину уже настроен ### Настройка доступа по ключу SSH
Ожидается, что доступ по SSH на удалённую машину уже настроен. Доступ нужен с использованием ssh-ключа.
Если доступ по паролю, то скприт попытается создать ключ и закинуть его на сервер (по паролю)
Если после удачной процедуры всё же не получается подсоединиться, то нужно вручную добавлять выведеный на экран публичный ключ на бэкап-сервер,
возможно через веб-гуй.
### Режим резервирования датасета целиком ### Режим резервирования датасета целиком
Когда не использован флаг `-i` Когда не использован флаг `-i`

View File

@ -7,6 +7,7 @@ incremental=false
silent=false silent=false
stop=false stop=false
progress=true progress=true
progress_only=false
check=true check=true
LOGFILE="log_bak_${inc_snapshot}.log" LOGFILE="log_bak_${inc_snapshot}.log"
unset local_dataset unset local_dataset
@ -17,13 +18,14 @@ echo -ne "Обработка аргументов...\033[2K\r"
while [[ "$#" -gt 0 ]]; do while [[ "$#" -gt 0 ]]; do
case $1 in case $1 in
-rs|--remote-server) backup_server="$2"; shift ;; -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 ;; -s|--snapshot) inc_snapshot="$2"; shift ;;
-ld|--local-dataset) local_dataset="$2"; shift ;; -ld|--local-dataset) local_dataset="$2"; shift ;;
-rd|--remote-dataset) remote_dataset="$2"; shift ;; -rd|--remote-dataset) remote_dataset="$2"; shift ;;
-l|--log-file) LOGFILE="$2"; shift ;; -l|--log-file) LOGFILE="$2"; shift ;;
-i|--incremental) incremental=true ;; -i|--incremental) incremental=true ;;
--no-progress) progress=false ;; --no-progress) progress=false ;;
--progress-only) progress_only=true ;;
--no-check) check=false ;; --no-check) check=false ;;
--stop) stop=true ;; --stop) stop=true ;;
-h|--help) echo "Использование: ./zfs_send.zsh [OPTIONS]"; -h|--help) echo "Использование: ./zfs_send.zsh [OPTIONS]";
@ -34,18 +36,101 @@ while [[ "$#" -gt 0 ]]; do
echo "-rd | --remote-dataset <remote/dataset>"; echo "-rd | --remote-dataset <remote/dataset>";
echo "-l | --log-file <path/filename>"; echo "-l | --log-file <path/filename>";
echo " --no-progress"; echo " --no-progress";
echo " --progress-only";
echo " --no-check"; echo " --no-check";
echo " --stop"; echo " --stop";
echo "-i | --incremental" ;; echo "-i | --incremental" ;
echo "-p | --progress" ;;
*) echo "Неизвестные параметры: $1"; exit 1 ;; *) echo "Неизвестные параметры: $1"; exit 1 ;;
esac esac
shift shift
done 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 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 с командой запуска 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 if [[ $pids = "" ]]; then
echo "Нет запущенного процесса резервирования в этом терминале." echo "Нет запущенного процесса резервирования в этом терминале."
read "srch?Икать во всех процессах системы? (y/N) " 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' ) pids=$(ps -o pid,args | grep -e zfs_send.zsh -e "zfs send" | grep -v -- '--stop' )
fi fi
fi fi
# Подтверждение # Подтверждение
if [[ $pids = "" ]]; then if [[ $pids = "" ]]; then
echo "Нет запущенного процесса резервирования" echo "Нет запущенного процесса резервирования"
@ -73,14 +158,24 @@ if [[ $stop = true ]]; then
exit 0 exit 0
fi fi
echo -n "Резервирование на "
# Проверка соединения # Проверка соединения
if ssh $backup_user@$backup_server 'exit'; then; echo "Резервирование на $backup_user@$backup_server"
echo "$backup_user@$backup_server ok"; if check_ssh_connection; then
echo "Connection OK"
else else
echo "Настройте ssh и запустите скрипт повторно" echo "SSH-ключ не обнаружен на сервере. Начинаем настройку..."
exit 1 setup_ssh_key
if check_ssh_connection; then
echo "Connection OK"
else
echo "Ошибка: SSH-ключ не работает. Проверьте настройки."
exit 1
fi
fi fi
# ----------
echo "Снимок файловой системы $inc_snapshot" echo "Снимок файловой системы $inc_snapshot"
case $incremental in 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="$(zfs list -t snapshot -o name ${loc_ds} | grep 'manual' | tail -n1 | egrep -o '@.+' )"
loc_snap=${loc_snap:1} # удалить @ в начале строки loc_snap=${loc_snap:1} # удалить @ в начале строки
if [[ $incremental = true ]]; then 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} # удалить @ в начале строки bak_snap=${bak_snap:1} # удалить @ в начале строки
fi fi
# echo "$loc_ds @ $loc_snap -> $bak_ds @ $bak_snap" # echo "$loc_ds @ $loc_snap -> $bak_ds @ $bak_snap"
@ -262,15 +357,10 @@ fi
if [[ $work = "y" || $work = "Y" ]]; then if [[ $work = "y" || $work = "Y" ]]; then
TS0=$(date +%s) 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 "Результат работы записываю в файл $LOGFILE"
# Функция для записи сообщения в файл и на экран
echo_log () { # $1 = Сообщение
echo $1
echo $1 >> "$LOGFILE"
}
# Записать лог список резервируемых датасетов # Записать лог список резервируемых датасетов
echo "--- $(date +'%Y.%m.%d %H:%M.%S') ---" >> "$LOGFILE" echo "--- $(date +'%Y.%m.%d %H:%M.%S') ---" >> "$LOGFILE"
echo "Список резервируемых датасетов:" >> "$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 # Сохраняем количество готовых задач в память if [[ $progress == true ]]; then echo $i_task > /dev/shm/backup_i_task; fi # Сохраняем количество готовых задач в память
for loc_ds bak_ds in ${(kv)work_datasets}; do for loc_ds bak_ds in ${(kv)work_datasets}; do
TS1=$(date +%s) 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}" echo_log " * snapshot ${loc_ds}@${inc_snapshot}"
zfs snapshot ${loc_ds}@${inc_snapshot}; #FIXME выдаёт ошибку если мы оставили существующий снимок zfs snapshot ${loc_ds}@${inc_snapshot}; #FIXME выдаёт ошибку если мы оставили существующий снимок
echo_log " * Start sending ${loc_ds} at $(date +'%Y.%m.%d %H:%M.%S')" echo_log " * Start sending ${loc_ds} at $(date +'%Y.%m.%d %H:%M.%S')"
case $incremental in 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} ;; 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} ;; false) zfs send -V ${loc_ds}@${inc_snapshot} | ssh $backup_user@$backup_server zfs receive ${bak_ds} ;;
esac esac
TS2=$(date +%s) TS2=$(date +%s)
(( i_task++ )) (( i_task++ ))
if [[ $progress == true ]]; then echo $i_task > /dev/shm/backup_i_task; fi if [[ $progress == true ]]; then echo $i_task > /dev/shm/backup_i_task; fi
echo_log "--- готово за: $(date -d@$(($TS2-$TS1)) -u '+%H:%M.%S') ---" echo_log "--- готово за: $(date -d@$(($TS2-$TS1)) -u '+%H:%M.%S') ---"
done & # Запустить резервирование в фоне 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 "" echo ""
last_percent=0 last_percent=0
time_changed_percent_value=$(date +%s) time_changed_percent_value=$(date +%s)
while [[ $i_task != $n_tasks ]]; do # выводим статус пока не завершены все задачи 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 ) percent=$(echo $progress | cut -d "(" -f2 | cut -d "%" -f1 )
if (( percent > 0 )); then if (( percent > 0 )); then
now=$(date +%s) now=$(date +%s)
if (( percent != last_percent )); then if (( percent != last_percent )); then
time_changed_percent_value=$now time_changed_percent_value=$now
last_percent=$percent last_percent=$percent
TS1=$(< /dev/shm/backup_time_stamp) TS1=$(< /dev/shm/backup_time_circle)
elapsed=$(( $(date +%s) - TS1 )) elapsed=$(( $(date +%s) - TS1 ))
fi fi
time_part_of_percent=$(( now - time_changed_percent_value )) # Время с последнего изменения процента 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) i_task=$(< /dev/shm/backup_i_task)
done done
# Удалить временные файлы состояний
rm /dev/shm/backup_i_task
rm /dev/shm/backup_time_stamp
fi 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 fi