#!/usr/bin/env bash # # zrepl_last_snapshot_age.sh # # Показывает, сколько минут назад был создан последний снапшот # (по умолчанию с префиксом "zrepl_") на резервной (приёмной) стороне. # # Использование: # ./zrepl_last_snapshot_age.sh <датасет> [<датасет2> ...] # ./zrepl_last_snapshot_age.sh -r <датасет> # рекурсивно по всем дочерним # ./zrepl_last_snapshot_age.sh -p zrepl_ -r chunk_9XG1J8T0/nomad # # Опции: # -r рекурсивно проверять все дочерние датасеты # -p PREFIX фильтровать снапшоты по префиксу имени (по умолчанию "zrepl_") # -w MINUTES задать порог в минутах: если снапшот старше — exit code 1 # (удобно для мониторинга / cron) # -q тихий режим: не печатать построчный вывод, только итог # # Коды возврата: # 0 — всё ок (снапшот найден, и если задан -w, он не старше порога) # 1 — снапшот найден, но старше порога -w # 2 — снапшот не найден вообще (для датасета/выборки) # 3 — ошибка использования / zfs недоступен set -euo pipefail PREFIX="zrepl_" RECURSIVE=0 WARN_MINUTES="" QUIET=0 usage() { grep '^#' "$0" | sed -e '1d' -e 's/^# \{0,1\}//' exit 3 } while getopts ":rp:w:qh" opt; do case "$opt" in r) RECURSIVE=1 ;; p) PREFIX="$OPTARG" ;; w) WARN_MINUTES="$OPTARG" ;; q) QUIET=1 ;; h) usage ;; *) usage ;; esac done shift $((OPTIND - 1)) if [[ $# -lt 1 ]]; then echo "Ошибка: укажите хотя бы один датасет." >&2 usage fi if ! command -v zfs >/dev/null 2>&1; then echo "Ошибка: команда zfs не найдена." >&2 exit 3 fi now_epoch=$(date +%s) overall_newest_epoch=0 overall_newest_name="" any_found=0 for dataset in "$@"; do zfs_opts=(-H -p -o name,creation -t snapshot -s creation) if [[ $RECURSIVE -eq 1 ]]; then zfs_opts+=(-r) else zfs_opts+=(-d 1) fi # -H без заголовков, -p значения в "машинном" виде (creation -> epoch seconds) mapfile -t lines < <(zfs list "${zfs_opts[@]}" "$dataset" 2>/dev/null | grep "@${PREFIX}" || true) if [[ ${#lines[@]} -eq 0 ]]; then if [[ $QUIET -eq 0 ]]; then echo "[$dataset] снапшотов с префиксом '${PREFIX}' не найдено" fi continue fi # последняя строка — самый свежий снапшот (сортировка -s creation) last_line="${lines[-1]}" snap_name="${last_line%%$'\t'*}" snap_epoch="${last_line##*$'\t'}" age_seconds=$(( now_epoch - snap_epoch )) age_minutes=$(( age_seconds / 60 )) any_found=1 if (( snap_epoch > overall_newest_epoch )); then overall_newest_epoch=$snap_epoch overall_newest_name=$snap_name fi if [[ $QUIET -eq 0 ]]; then printf "[%s] последний снапшот: %s (%d мин. назад)\n" \ "$dataset" "$snap_name" "$age_minutes" fi done if [[ $any_found -eq 0 ]]; then echo "Ни одного снапшота с префиксом '${PREFIX}' не найдено ни в одном из указанных датасетов." >&2 exit 2 fi overall_age_minutes=$(( (now_epoch - overall_newest_epoch) / 60 )) echo "---" printf "Самый свежий снапшот в выборке: %s (%d мин. назад)\n" \ "$overall_newest_name" "$overall_age_minutes" if [[ -n "$WARN_MINUTES" ]]; then if (( overall_age_minutes > WARN_MINUTES )); then echo "ВНИМАНИЕ: последний снапшот старше порога ${WARN_MINUTES} мин." >&2 exit 1 fi fi exit 0