ad.backup.zfs

Yohn Y. 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 +}