pankovea-patch-1 #1
307
zfs_send.zsh
307
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 <ip/dns>";
|
||||
echo "-гu | --remote-user <user>";
|
||||
echo "-s | --snapshot <snapshot_name>";
|
||||
echo "-ld | --local-dataset <local/dataset>";
|
||||
echo "-rd | --remote-dataset <remote/dataset>";
|
||||
echo "-l | --log-file <path/filename";
|
||||
echo " --no-progress";
|
||||
echo " --no-check";
|
||||
echo " --stop";
|
||||
echo "-i | --incremental" ;;
|
||||
*) echo "Неизвестные параметры: $1"; exit 1 ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
# Завершение запущенного процесса резервирования
|
||||
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) "
|
||||
if [[ $srch = y ]]; then
|
||||
pids=$(ps -o pid,args | grep -e zfs_send.zsh -e "zfs send" | grep -v -- '--stop' )
|
||||
fi
|
||||
fi
|
||||
|
||||
# Подтверждение
|
||||
if [[ $pids = "" ]]; then
|
||||
echo "Нет запущенного процесса резервирования"
|
||||
else
|
||||
echo "Будут остановлены следующие процессы:"
|
||||
echo $pids
|
||||
read "work? Продолжать (y/N)"
|
||||
if [[ $work = y ]]; then
|
||||
pids=$(echo $pids | awk '{print $1}')
|
||||
kill $pids
|
||||
echo "ok"
|
||||
else
|
||||
echo "Операция прервана"
|
||||
fi
|
||||
fi
|
||||
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo -n "Резервирование на "
|
||||
# Проверка соединения
|
||||
if ssh $backup_user@$backup_server 'exit'; then;
|
||||
echo "$backup_user@$backup_server ok";
|
||||
else
|
||||
echo "Настройте ssh и запустите скрипт повторно"
|
||||
exit 1
|
||||
echo "Настройте ssh и запустите скрипт повторно"
|
||||
exit 1
|
||||
fi
|
||||
echo "Снимок файловой системы $inc_snapshot"
|
||||
|
||||
@ -33,6 +88,8 @@ case $incremental in
|
||||
false) echo "Режим полной копии";;
|
||||
esac
|
||||
|
||||
echo "Дополнительные опции: -h|--help"
|
||||
|
||||
# --------- Собираем данные ----------
|
||||
|
||||
echo -ne "Собираю данные...\033[2K\r"
|
||||
@ -45,67 +102,71 @@ backup_datasets=(${=backup_datasets}) # Конвертирование в мас
|
||||
|
||||
declare -A work_datasets # Словарь ключ = резервируемый локальный датасет, значение = имя датасета на удалённом сервере
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
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"
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user