ad.backup.zfs
2026-05-17
0:00bb6c16b4b6 tip Browse Files
.. init . По сути архивация имеющегося самопиского решания для памяти.
README.md bin/zfsbackup.cron.sh bin/zfsbackup.sh lib/log.sh lib/main.sh
1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/README.md Sun May 17 17:44:29 2026 +0300 1.3 @@ -0,0 +1,6 @@ 1.4 +Что это 1.5 +======= 1.6 + 1.7 +Моя ранняя попытка организовать некоторый вариант архивирования средствами ZFS с нуливой ценой. 1.8 +Решение вполне успешно борется с глупостями оператора машины по удалению различных файлов. 1.9 +Сейчас подобных решений миллион.
2.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 2.2 +++ b/bin/zfsbackup.cron.sh Sun May 17 17:44:29 2026 +0300 2.3 @@ -0,0 +1,81 @@ 2.4 +#!/bin/sh 2.5 +# devel.a0fs.net: zfsbackup.bin.cron - v0.1 by awgur 2.6 +# --- 2.7 +# Файл должен запускаться через crond. Здесь содержится вся логика, 2.8 +# выполняющая функции резервирования 2.9 + 2.10 +PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin 2.11 +export PATH 2.12 + 2.13 +dir_root="$(dirname "$(dirname "$(readlink -f "$0")")")" 2.14 +dir_lib="${dir_root}/lib" # Директория с библиотеками 2.15 + 2.16 +arg_timeMark="$(date '+%Y%m%d-%H%M%S')" 2.17 +arg_timeDate="$(date '+%Y%m%d')" 2.18 +arg_timeHour="$(date '+%H')" 2.19 + 2.20 +sys_backupCnt_d=90 2.21 +sys_backupCnt_h=48 2.22 + 2.23 +. "${dir_lib}/main.sh" 2.24 + 2.25 +worker () { 2.26 + # Исполнительная процедура. Содержит логику работы с определённым набором данных ZFS 2.27 + # Выполняя функции обработки 2.28 + local ds="$1" 2.29 + local lastSnap # Последняя резервная копия 2.30 + local cnt # Сконфигурированое количество резервных копий 2.31 + local cnt_default # Количество резервных копий по умолчанию 2.32 + local backupType # Тип резервного копирования 2.33 + local curSnap # Имя текущего снимка 2.34 + local zfs_ret # Код возврата комманды zfs 2.35 + local lp="cron.worker():" # префикс для системы логирования, 2.36 + # выносим отдельно, так как логов будем писать много 2.37 + if ! [ "$ds" ] ; then 2.38 + return 1 2.39 + fi 2.40 + 2.41 + if zfs_isBackup "$ds" ; then 2.42 + log "$lp Working with '$ds'" 2.43 + lastSnap=$(zfs_getLastSnap "$ds") 2.44 + if ! svc_date_isToday "${lastSnap}" ; then 2.45 + backupType="${sys_backupType_d}" 2.46 + cnt_default="${sys_backupCnt_d}" 2.47 + elif ! svc_date_isThisHour "${lastSnap}" ; then 2.48 + backupType="${sys_backupType_h}" 2.49 + cnt_default="${sys_backupCnt_h}" 2.50 + else 2.51 + return 0 2.52 + fi 2.53 + 2.54 + cnt=$(zfs_getProp "$ds" "${backupType}") 2.55 + if ! [ "$cnt" ] ; then 2.56 + cnt="${cnt_default}" 2.57 + fi 2.58 + 2.59 + if [ "$cnt" -eq 0 -a "${backupType}" = "${sys_backupType_d}" ] ; then 2.60 + backupType="${sys_backupType_h}" 2.61 + cnt=$(zfs_getProp "$ds" "${sys_backupType_h}") 2.62 + if ! [ "$cnt" ] ; then 2.63 + cnt="${sys_backupCnt_h}" 2.64 + fi 2.65 + fi 2.66 + 2.67 + 2.68 + if [ "$cnt" -gt 0 ]; then 2.69 + curSnap="$(svc_makeName "$ds" "${backupType}" "${sys_date_now}")" 2.70 + log "$lp Make snapshot '$curSnap'" 2.71 + { 2.72 + zfs snapshot "${curSnap}" 2>&1 2.73 + zfs_ret=$? 2.74 + if [ "$zfs_ret" -eq 0 ] ; then 2.75 + zfs_clean "$ds" "${backupType}" "${cnt}" 2.76 + else 2.77 + log_err "$lp Make snapshot '$curSnap' fail with return code '$zfs_ret'" 2.78 + fi 2.79 + } 2>&1 | log_err 2.80 + fi 2.81 + fi 2.82 +} 2.83 + 2.84 +zfs list -Ho name | while read fs ; do worker "$fs"; done 2.85 \ No newline at end of file
3.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 3.2 +++ b/bin/zfsbackup.sh Sun May 17 17:44:29 2026 +0300 3.3 @@ -0,0 +1,122 @@ 3.4 +#!/bin/sh 3.5 +# devel.a0fs.net: zfsbackup.bin.main - v0.1 by awgur 3.6 +# --- 3.7 +# Файл, содержащий скрипт управления системой резервного копирования 3.8 + 3.9 +PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin 3.10 +export PATH 3.11 + 3.12 +dir_root="$(dirname "$(dirname "$(readlink -f "$0")")")" 3.13 +dir_lib="${dir_root}/lib" # Директория с библиотеками 3.14 + 3.15 +. "${dir_lib}/main.sh" 3.16 + 3.17 +usage () { 3.18 + # Вывод справки по аргементам скрипта 3.19 + cat << EOF 3.20 +$0 ZFS_Dataset_name команда [аргументы] 3.21 + Настройка резервного копирования наборов данных (dataset) ZFS 3.22 + Комманды: 3.23 + enable : Включение копий для текущего набора данных 3.24 + : и вложенных в него наборов, если у них эта настройка 3.25 + : не заданна отдельно 3.26 + disable : Отключение резервного копирования для набора данных ZFS 3.27 + : и вложенных в него наборов, если у них эта насройка 3.28 + : не заданна отдельно 3.29 + set backup_type number : Установить количество хранимых копий для 3.30 + : заданного типа копирования. 3.31 + : Поддерживаются типы (backup_type): 3.32 + : - h - почасовое копирование, делается раз в час, в основном 3.33 + : в начале каждого часа 3.34 + : - d - ежедневное копирование, делается раз в день, в основном 3.35 + : в 00 часов 00 минут по локальному времени системы 3.36 + : number - Число резервных копий - неотрицательное целое число. 3.37 + : Если число равно 0 данный тип резервного копирования 3.38 + : для набора отключается. 3.39 +EOF 3.40 +} 3.41 + 3.42 +err_usr () { 3.43 + # Возбуждение исключения, с аварийным завершением работы скрипта 3.44 + # и выдачей сообщения на терминал. 3.45 + # Аргументы: 3.46 + # [exit_code] : Код завершения, по-умолчанию 1 3.47 + # message : Сообщение пользователю 3.48 + local exit_code="$1" 3.49 + local msg="$2" 3.50 + 3.51 + if ! [ "$msg" ] ; then 3.52 + msg="$exit_code" 3.53 + exit_code=1 3.54 + fi 3.55 + 3.56 + echo "ERROR: $msg" 3.57 + usage 3.58 + exit "${exit_code}" 3.59 +} 3.60 + 3.61 +cmd_eanble() { 3.62 + # Обработчик комманды включения резервного попирования 3.63 + zfs_setProp "$arg_ds" "enable" "yes" 3.64 +} 3.65 + 3.66 +cmd_disable() { 3.67 + # Обработчик комманды отключения резервного попирования 3.68 + zfs_setProp "$arg_ds" "enable" "no" 3.69 +} 3.70 + 3.71 +cmd_set() { 3.72 + # Обработчик комманды на установку количества резервных копий 3.73 + # аргументы: 3.74 + # backup_type : Тип резервного копирования 3.75 + # count : Коилчество копий, целое положительное число 3.76 + local backupType 3.77 + case "$1" in 3.78 + d ) 3.79 + backupType="${sys_backupType_d}" 3.80 + ;; 3.81 + h ) 3.82 + backupType="${sys_backupType_h}" 3.83 + ;; 3.84 + * ) 3.85 + err_usr 2 "Не вреный тип резервного копирования, для которого устанавливается значение: '$1'" 3.86 + ;; 3.87 + esac 3.88 + 3.89 + if ! svc_isNum "$2" ; then 3.90 + err_usr 3 "Число резервных копий не является неотрицательным числом: '$2'" 3.91 + fi 3.92 + 3.93 + zfs_setProp "$arg_ds" "$backupType" $2 3.94 +} 3.95 + 3.96 +# === 3.97 +# Проверяем в порядке ли аргументы 3.98 +# - Присутствует ли набор данных 3.99 +if ! zfs list -Ho name "$1" > /dev/null 2>&1 ; then 3.100 + err_usr "Набор данных '$1' не существует" 3.101 +fi 3.102 +arg_ds="$1" 3.103 +shift 3.104 + 3.105 +# - Проверка комманд 3.106 +arg_cmd="$1" 3.107 +shift 3.108 + 3.109 +case "$arg_cmd" in 3.110 + enable ) 3.111 + cmd_eanble 3.112 + ;; 3.113 + disable ) 3.114 + cmd_disable 3.115 + ;; 3.116 + set ) 3.117 + cmd_set "$@" 3.118 + ;; 3.119 + * ) 3.120 + err_usr 4 "Комманда не распознана: '$cmd'" 3.121 + ;; 3.122 +esac 3.123 + 3.124 + 3.125 +
4.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 4.2 +++ b/lib/log.sh Sun May 17 17:44:29 2026 +0300 4.3 @@ -0,0 +1,47 @@ 4.4 +#!/bin/sh 4.5 +# devel.a0fs.net: zfsbackup.lib.log - v0.1 by awgur 4.6 +# --- 4.7 +# Файл содержит процедуры и функции организации логирования и обработки ошибок 4.8 + 4.9 +sys_log () { 4.10 + # Процедура логирования, принимает аргументы logger, и всего лишь добавляет тег 4.11 + # к логам 4.12 + logger -t net.a0fs.zfsbackup "$@" 4.13 +} 4.14 + 4.15 +log_err () { 4.16 + # Организация логирования ошибок 4.17 + sys_log -p user.err "$@" 4.18 +} 4.19 + 4.20 +log () { 4.21 + # Логирование с уровнеи "Информация" 4.22 + sys_log -p user.info "$@" 4.23 +} 4.24 + 4.25 +log_n () { 4.26 + # Логирование с уровнем "Уведомление" 4.27 + sys_log -p user.notice "$@" 4.28 +} 4.29 + 4.30 +log_crit () { 4.31 + # Логирование критической ошибки, не дающей возможность продолжать процесс 4.32 + sys_log -p user.crit "$@" 4.33 +} 4.34 + 4.35 +err () { 4.36 + # Возбуждение исключения (выход из скрипта с выдачей сообщения в лог) 4.37 + # Аргументы: 4.38 + # [ret_code]: Код возврата скрипта 4.39 + # message: Сообщение в журнал 4.40 + local code="$1" 4.41 + local msg="$2" 4.42 + 4.43 + if ! [ "$msg" ] ; then 4.44 + msg="$code" 4.45 + code=1 4.46 + fi 4.47 + 4.48 + log_crit "$msg" 4.49 + exit "$code" 4.50 +} 4.51 \ No newline at end of file
5.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 5.2 +++ b/lib/main.sh Sun May 17 17:44:29 2026 +0300 5.3 @@ -0,0 +1,164 @@ 5.4 +#!/bin/sh 5.5 +# devel.a0fs.net: zfsbackup.lib.log - v0.1 by awgur 5.6 +# --- 5.7 +# Файл содержит процедуры и функции организации определения и исполнения 5.8 +# заданий на резервное копирование наборов данных ZFS 5.9 + 5.10 +# Определяем корень иерархии файлов относительно файла задания $dir_root/task/$task_name 5.11 +# при условии что данная переменная не определена в вызывающем скрипте 5.12 +dir_root="${dir_root:-$(dirname "$(dirname "$(readlink -f "$0")")")}" 5.13 +dir_lib="${dir_lib:-${dir_root}/lib}" # Директория с библиотеками 5.14 + 5.15 +. "${dir_lib}/log.sh" 5.16 + 5.17 +sys_propNamePrefix="net.a0fs.zfsbackup" # префикс для свойств набора данных ZFS, 5.18 + # в которые будут сохранятся параметры 5.19 + 5.20 +sys_backupType_d="daily" 5.21 +sys_backupType_h="hourly" 5.22 +sys_date_now="$(date '+%Y%m%d-%H%M%S')" 5.23 +sys_date_today="$(date '+%Y%m%d')" 5.24 +sys_date_thisHour="$(date '+%Y%m%d-%H')" 5.25 + 5.26 +svc_isNum () { 5.27 + # Проверяет, является ли аргумент числом 5.28 + echo "$1" | grep -vE '^[[:space:]]*$' | sed 's/[[:space:]]//g' | tr -d '\n\r' | grep -E '^[0-9]+$' > /dev/null 2>&1 5.29 +} 5.30 + 5.31 +svc_makeName () { 5.32 + # Конструктор имени снимка. Для однотипного именования по всему скрипту 5.33 + # Аргументы: 5.34 + # dataset_name : Имя набора данных, для которого получаем снимок 5.35 + # backup_type : Имя типа резервного копирования 5.36 + # timeMark : Метка времени 5.37 + if ! [ "$1" -a "$2" -a "$3" ] ; then 5.38 + log_err "svc_makeName(): Not enough arguments (dataset_name='$1', backup_type='$2', timeMark='$3')" 5.39 + return 1 5.40 + fi 5.41 + echo "$1@${sys_propNamePrefix}_${3}_${2}" 5.42 +} 5.43 + 5.44 +svc_date_getSnapTime () { 5.45 + # Получение временной метки из имени снимка 5.46 + local buf 5.47 + if ! [ "$1" ] ; then 5.48 + return 1 5.49 + fi 5.50 + 5.51 + echo "$1" | sed "s/.*@${sys_propNamePrefix}_//" | cut -f 1 -d '_' 5.52 +} 5.53 + 5.54 +svc_date_isToday () { 5.55 + # Проверка, сделан ли снимок набора данных в день проверки 5.56 + local buf 5.57 + if ! [ "$1" ] ; then 5.58 + return 1 5.59 + fi 5.60 + 5.61 + svc_date_getSnapTime "$1" | grep -E "^${sys_date_today}" >/dev/null 2>&1 5.62 +} 5.63 + 5.64 +svc_date_isThisHour () { 5.65 + # Проверка, сделан ли снимок набора данных в час проверки 5.66 + local buf 5.67 + if ! [ "$1" ] ; then 5.68 + return 1 5.69 + fi 5.70 + 5.71 + svc_date_getSnapTime "$1" | grep -E "^${sys_date_thisHour}" >/dev/null 2>&1 5.72 +} 5.73 + 5.74 +zfs_getSnaps () { 5.75 + # Получить список снимков для данного в аргументах набора. 5.76 + # ВНИМАНИЕ! Снимки сортируются в обратном порядке 5.77 + # Аргументы: 5.78 + # dataset_name : Имя набора, для которго получаем снимки 5.79 + # backup_type : Имя типа резервного копирования 5.80 + if ! [ "$1" -a "$2" ] ; then 5.81 + log_err "zfs_getSnaps(): Dataset or backup type not set: dataset='$1' backup_type='$2'" 5.82 + return 1 5.83 + fi 5.84 + 5.85 + zfs list -Ho name -d 1 -r -t snapshot "$1" \ 5.86 + | grep -E "@${sys_propNamePrefix}_[0-9]{8}-[0-9]{6}_${2}\$" \ 5.87 + | sort -r 5.88 +} 5.89 + 5.90 +zfs_getLastSnap () { 5.91 + # Получить последний снимок созданный системой резервного копирования 5.92 + # Аргументы: 5.93 + # dataset_name : Имя набора, для которого получаем снимок 5.94 + if ! [ "$1" ] ; then 5.95 + log_err "zfs_getLastSnap(): Dataset not set" 5.96 + return 1 5.97 + fi 5.98 + 5.99 + zfs list -Ho name -d 1 -r -t snapshot "$1" \ 5.100 + | grep -E "@${sys_propNamePrefix}_[0-9]{8}-[0-9]{6}_" \ 5.101 + | sort -r | head -n 1 5.102 +} 5.103 + 5.104 +zfs_getProp () { 5.105 + # Получить значение свойства из zfs, относящиеся к системе резервного копирования 5.106 + # Аргументы: 5.107 + # dataset_name : Имя набора данных 5.108 + # prop_name : Имя свойства 5.109 + local res 5.110 + 5.111 + if ! [ "$1" -a "$2" ] ; then 5.112 + return 1 5.113 + fi 5.114 + res=$(zfs get -Hp -o value "${sys_propNamePrefix}:$2" "$1") 5.115 + 5.116 + if [ "${res}" = "-" ] ; then 5.117 + res="" 5.118 + fi 5.119 + echo "$res" 5.120 +} 5.121 + 5.122 +zfs_setProp () { 5.123 + # Установить значение свойства в zfs, относящиеся к системе резервного копирования 5.124 + # Аргументы: 5.125 + # dataset_name : Имя набора данных 5.126 + # prop_name : Имя свойства 5.127 + # prop_value : Значение свойства 5.128 + if ! [ "$1" -a "$2" -a "$3" ] ; then 5.129 + return 1 5.130 + fi 5.131 + 5.132 + zfs set "${sys_propNamePrefix}:$2"="$3" "$1" 5.133 + log "zfs_setProp(): Property changed for '$1': user='$(whoami 2>/dev/null)' uid='$(id -u)' property='$2' value='$3'" 5.134 +} 5.135 + 5.136 +zfs_isBackup () { 5.137 + # Необходимо ли резервное копирование для данного набора данных 5.138 + # Аргументы: 5.139 + # dataset_name : Имя исследуемого набора данных 5.140 + case "$(zfs_getProp "$1" "enable")" in 5.141 + yes|y ) 5.142 + return 0 5.143 + ;; 5.144 + * ) 5.145 + return 1 5.146 + ;; 5.147 + esac 5.148 +} 5.149 + 5.150 +zfs_clean () { 5.151 + # Удаление устаревших резервных копий 5.152 + # Аргументы: 5.153 + # dataset_name : Набор данных для которого производится операция 5.154 + # backup_type : Тип резервной копии 5.155 + # count : Количество копий, которые необходимо сохранить 5.156 + local cnt 5.157 + 5.158 + if ! svc_isNum "$3" ; then 5.159 + log_err "zfs_clean(): Wrong backup count: %3 (dataset_name='$1', backup_type='$2')" 5.160 + return 1 5.161 + fi 5.162 + cnt="$(( $cnt + 1 ))" 5.163 + 5.164 + zfs_getSnaps "$1" "$2" \ 5.165 + | tail -n "+${cnt}" | xargs -n 1 zfs destroy -v \ 5.166 + | awk "{print \"$1: \" \$0 }" | log 5.167 +}