From a9f0571e0debcd6312a1f7967bb00b0ae19fea51 Mon Sep 17 00:00:00 2001 From: russian mafia <1+lobanov_v@noreply.localhost> Date: Fri, 25 Sep 2026 09:59:38 +0000 Subject: [PATCH] =?UTF-8?q?=D0=97=D0=B0=D0=B3=D1=80=D1=83=D0=B7=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=20=D1=84=D0=B0=D0=B9=D0=BB=D1=8B=20=D0=B2=20=C2=AB?= =?UTF-8?q?/=C2=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- urbackup_zabbix.sh | 417 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 417 insertions(+) create mode 100644 urbackup_zabbix.sh diff --git a/urbackup_zabbix.sh b/urbackup_zabbix.sh new file mode 100644 index 0000000..1a93b10 --- /dev/null +++ b/urbackup_zabbix.sh @@ -0,0 +1,417 @@ +#!/usr/bin/env bash + +set -u +PATH=/usr/sbin:/usr/bin:/sbin:/bin +export PATH + +die() { + echo "ZBX_NOTSUPPORTED" + exit 1 +} + +BASE="${2:-}" + +[ -n "$BASE" ] || die +[ -d "$BASE" ] || die + +BASE="${BASE%/}" + +# +# Convert: +# +# 260924-0949 +# 260924-0949_Image_C +# +# to Unix timestamp. +# +name_to_epoch() { + local n="$1" + local yy mm dd hh mi year + + # Remove Image suffix + n="${n%%_Image_*}" + + if [[ "$n" =~ ^([0-9]{2})([0-9]{2})([0-9]{2})-([0-9]{2})([0-9]{2})$ ]]; then + + yy="${BASH_REMATCH[1]}" + mm="${BASH_REMATCH[2]}" + dd="${BASH_REMATCH[3]}" + hh="${BASH_REMATCH[4]}" + mi="${BASH_REMATCH[5]}" + + if (( 10#$yy >= 70 )); then + year="19$yy" + else + year="20$yy" + fi + + date -d "${year}-${mm}-${dd} ${hh}:${mi}:00" +%s 2>/dev/null + return + fi + + echo 0 +} + +valid_client() { + [[ -n "$1" ]] || return 1 + [[ "$1" != "." ]] || return 1 + [[ "$1" != ".." ]] || return 1 + [[ "$1" != *"/"* ]] || return 1 +} + +client_dir() { + local client="$1" + + valid_client "$client" || die + + printf '%s/%s\n' "$BASE" "$client" +} + +# +# File backup: +# +# 260924-0936 +# +is_file_backup() { + local d="$1" + local n="${1##*/}" + + [[ "$n" =~ ^[0-9]{6}-[0-9]{4}$ ]] || return 1 + [ -d "$d" ] || return 1 + + return 0 +} + +# +# Image backup: +# +# 260924-0949_Image_C +# +# Must also contain a non-empty image file (.vhdz, .vhd or .raw). +# +is_image_backup() { + local d="$1" + local n="${1##*/}" + local vhdz + + [[ "$n" =~ ^[0-9]{6}-[0-9]{4}_Image_.*$ ]] || return 1 + [ -d "$d" ] || return 1 + + # Ищет любые типы виртуальных дисков, которые создает UrBackup + vhdz="$(find "$d" \ + -maxdepth 1 \ + -type f \ + \( -name '*.vhdz' -o -name '*.vhd' -o -name '*.raw' \) \ + -size +0c \ + -print -quit 2>/dev/null)" + + [ -n "$vhdz" ] +} + +# +# Find latest backup timestamp. +# +latest_epoch() { + + local dir="$1" + local kind="$2" + + local best=0 + local d + local n + local e + + [ -d "$dir" ] || { + echo 0 + return + } + + while IFS= read -r -d '' d; do + + n="${d##*/}" + + case "$kind" in + + file) + is_file_backup "$d" || continue + ;; + + image) + is_image_backup "$d" || continue + ;; + + *) + echo 0 + return + ;; + + esac + + e="$(name_to_epoch "$n")" + + if (( e > best )); then + best="$e" + fi + + done < <( + find "$dir" \ + -mindepth 1 \ + -maxdepth 1 \ + -type d \ + -print0 2>/dev/null + ) + + echo "$best" +} + +# +# Determine whether directory is a real UrBackup client. +# +is_client_dir() { + + local dir="$1" + local d + + while IFS= read -r -d '' d; do + + if is_file_backup "$d"; then + return 0 + fi + + if is_image_backup "$d"; then + return 0 + fi + + done < <( + find "$dir" \ + -mindepth 1 \ + -maxdepth 1 \ + -type d \ + -print0 2>/dev/null + ) + + return 1 +} + +# +# MAIN +# + +case "${1:-}" in + + discovery) + + printf '{"data":[' + + first=1 + + while IFS= read -r -d '' d; do + + client="${d##*/}" + + valid_client "$client" || continue + + is_client_dir "$d" || continue + + [ "$first" -eq 1 ] || printf ',' + + esc="$(printf '%s' "$client" | \ + sed 's/\\/\\\\/g; s/"/\\"/g')" + + printf '{"{#CLIENT}":"%s"}' "$esc" + + first=0 + + done < <( + find "$BASE" \ + -mindepth 1 \ + -maxdepth 1 \ + -type d \ + -print0 2>/dev/null | + sort -z + ) + + printf ']}\n' + + ;; + + last_ts) + + [ "$#" -eq 4 ] || die + + client="$3" + kind="$4" + + latest_epoch \ + "$(client_dir "$client")" \ + "$kind" + + ;; + + last_age) + + [ "$#" -eq 4 ] || die + + client="$3" + kind="$4" + + ts="$( + latest_epoch \ + "$(client_dir "$client")" \ + "$kind" + )" + + if [ "$ts" -le 0 ]; then + + # No backup found. + echo 999999999 + + else + + now="$(date +%s)" + + age=$((now - ts)) + + (( age < 0 )) && age=0 + + echo "$age" + + fi + + ;; + + count) + + [ "$#" -eq 4 ] || die + + client="$3" + kind="$4" + + dir="$(client_dir "$client")" + + count=0 + + while IFS= read -r -d '' d; do + + case "$kind" in + + file) + is_file_backup "$d" || continue + ;; + + image) + is_image_backup "$d" || continue + ;; + + all) + + if is_file_backup "$d"; then + : + elif is_image_backup "$d"; then + : + else + continue + fi + + ;; + + *) + die + ;; + + esac + + count=$((count + 1)) + + done < <( + find "$dir" \ + -mindepth 1 \ + -maxdepth 1 \ + -type d \ + -print0 2>/dev/null + ) + + echo "$count" + + ;; + + image_size) + + [ "$#" -eq 3 ] || die + + client="$3" + + dir="$(client_dir "$client")" + + ts="$(latest_epoch "$dir" image)" + + [ "$ts" -gt 0 ] || { + echo 0 + exit + } + + best="" + + while IFS= read -r -d '' d; do + + is_image_backup "$d" || continue + + n="${d##*/}" + + e="$(name_to_epoch "$n")" + + [ "$e" -eq "$ts" ] || continue + + best="$d" + + break + + done < <( + find "$dir" \ + -mindepth 1 \ + -maxdepth 1 \ + -type d \ + -print0 2>/dev/null + ) + + [ -n "$best" ] || { + echo 0 + exit + } + + # Ищет размер для любых форматов образов (.vhdz, .vhd, .raw) + vhdz="$( + find "$best" \ + -maxdepth 1 \ + -type f \ + \( -name '*.vhdz' -o -name '*.vhd' -o -name '*.raw' \) \ + -size +0c \ + -print -quit 2>/dev/null + )" + + [ -n "$vhdz" ] || { + echo 0 + exit + } + + stat -c '%s' "$vhdz" 2>/dev/null || echo 0 + + ;; + + disk_free_pct) + + df -P "$BASE" 2>/dev/null | + awk 'NR==2 { + gsub(/%/,"",$5); + print 100-$5 + }' + + ;; + + *) + + die + + ;; + +esac