247 lines
7.8 KiB
Bash
247 lines
7.8 KiB
Bash
#!/usr/bin/env bash
|
|
# Script Name: pbs-backup.v2.sh
|
|
# Beschreibung: Host-Backups zu Proxmox Backup Server (PBS) inkl. Notifications
|
|
# Aufruf: ./pbs-backup.v2.sh [--dry-run] [--no-mail] [--no-ntfy] [-q] [-h]
|
|
# Autor: Patrick Asmus
|
|
# Web: https://www.cleveradmin.de
|
|
# Git-Reposit.: https://git.techniverse.net/scriptos/pbs-backup-client-script.git
|
|
# Version: 2.0
|
|
# Datum: 22.08.2025
|
|
# Modifikation: - Ntfy-Support mit Auth implementiert
|
|
# - Logging und Ausgabe erweitert
|
|
# - Locking, Exit-Codes, externe Config, Dry-Run, robuste Fehlerbehandlung hinzugefügt
|
|
# - Konfiguration ausgelagertin eigene Konfigurationsdatei
|
|
# - Backup-OK-Datei für externes Monitoring implementiert
|
|
|
|
set -Eeuo pipefail
|
|
umask 077
|
|
|
|
# ---------- Variablen ---------- #
|
|
SCRIPT_DIR="$(cd -- "$(dirname -- "$0")" && pwd)"
|
|
CONFIG_FILE="${CONFIG_FILE:-$SCRIPT_DIR/pbs-backup.v2.conf}"
|
|
|
|
DRY_RUN=false
|
|
FORCE_NO_MAIL=false
|
|
FORCE_NO_NTFY=false
|
|
VERBOSE=true
|
|
|
|
SCRIPT_NAME="$(basename "$0")"
|
|
HOSTNAME="$(hostname)"
|
|
|
|
# ---------- Helpers ---------- #
|
|
_timestamp() { date +"%Y-%m-%d %H:%M:%S"; }
|
|
|
|
log() {
|
|
local level="$1"; shift
|
|
local msg="$*"
|
|
local line="[$(_timestamp)] [$level] $msg"
|
|
echo "$line" | tee -a "$LOGFILE"
|
|
}
|
|
|
|
die() {
|
|
log "ERROR" "$*"
|
|
exit 1
|
|
}
|
|
|
|
check_dep() {
|
|
local bin="$1" hint="${2:-}"
|
|
command -v "$bin" >/dev/null 2>&1 || die "Benötigtes Programm '$bin' fehlt. $hint"
|
|
}
|
|
|
|
bool() { [[ "${1,,}" == "true" || "$1" == "1" || "${1,,}" == "yes" ]]; }
|
|
|
|
print_usage() {
|
|
cat <<EOF
|
|
Usage: $SCRIPT_NAME [Optionen]
|
|
|
|
Optionen:
|
|
--dry-run Nur anzeigen, was ausgeführt würde (kein Backup)
|
|
--no-mail Mail-Benachrichtigung für diesen Lauf deaktivieren
|
|
--no-ntfy Ntfy-Benachrichtigung für diesen Lauf deaktivieren
|
|
-q, --quiet Weniger Ausgabe (nur WARN/ERROR)
|
|
-h, --help Diese Hilfe
|
|
|
|
Hinweis:
|
|
Alle variablen Einstellungen kommen aus der Config:
|
|
$CONFIG_FILE
|
|
Standardmäßig wird eine Datei 'pbs-backup.v2.conf' im selben Verzeichnis wie das Script erwartet.
|
|
EOF
|
|
}
|
|
|
|
# ---------- CLI ---------- #
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--dry-run) DRY_RUN=true; shift ;;
|
|
--no-mail) FORCE_NO_MAIL=true; shift ;;
|
|
--no-ntfy) FORCE_NO_NTFY=true; shift ;;
|
|
-q|--quiet) VERBOSE=false; shift ;;
|
|
-h|--help) print_usage; exit 0 ;;
|
|
*) die "Unbekannte Option: $1 (siehe --help)" ;;
|
|
esac
|
|
done
|
|
|
|
# ---------- Config laden ---------- #
|
|
[[ -f "$CONFIG_FILE" ]] || die "Config nicht gefunden: $CONFIG_FILE"
|
|
source "$CONFIG_FILE"
|
|
|
|
[[ -n "${PBS_REPOSITORY:-}" ]] || die "PBS_REPOSITORY ist nicht gesetzt (in $CONFIG_FILE)."
|
|
|
|
if ! declare -p INCLUDE_DIRS >/dev/null 2>&1; then
|
|
die "INCLUDE_DIRS ist nicht gesetzt (Array) in $CONFIG_FILE."
|
|
fi
|
|
[[ "${#INCLUDE_DIRS[@]}" -gt 0 ]] || die "INCLUDE_DIRS ist leer in $CONFIG_FILE."
|
|
|
|
[[ -n "${LOGFILE:-}" ]] || die "LOGFILE ist nicht gesetzt (in $CONFIG_FILE)."
|
|
[[ -n "${LOCKFILE:-}" ]] || die "LOCKFILE ist nicht gesetzt (in $CONFIG_FILE)."
|
|
|
|
HEALTHCHECK_OK_FILE="${HEALTHCHECK_OK_FILE:-}"
|
|
|
|
$FORCE_NO_MAIL && MAIL_ENABLED=false
|
|
$FORCE_NO_NTFY && NTFY_ENABLED=false
|
|
$VERBOSE || exec >/dev/null 2>&1
|
|
|
|
RUNLOG="$(mktemp)"
|
|
_cleanup() { rm -f "$RUNLOG"; }
|
|
trap _cleanup EXIT
|
|
|
|
# ---------- Lock & Log ---------- #
|
|
if ! touch "$LOGFILE" 2>/dev/null; then
|
|
LOGFILE="/tmp/pbs-backup.v2.log"
|
|
touch "$LOGFILE" || die "Kann Logfile nicht anlegen: $LOGFILE"
|
|
fi
|
|
|
|
mkdir -p "$(dirname "$LOCKFILE")"
|
|
exec 9>"$LOCKFILE"
|
|
flock -n 9 || die "Es läuft bereits ein anderer Backup-Prozess (Lock: $LOCKFILE)"
|
|
|
|
log "INFO" "Starte $SCRIPT_NAME auf Host $HOSTNAME"
|
|
log "INFO" "Config: $CONFIG_FILE"
|
|
log "INFO" "Logfile: $LOGFILE"
|
|
|
|
check_dep "proxmox-backup-client" "Bitte PBS-Client installieren."
|
|
if bool "${MAIL_ENABLED:-}"; then
|
|
check_dep "${MAIL_CMD:-mail}" "Mail-/msmtp-Client nicht gefunden? Setze MAIL_CMD in $CONFIG_FILE."
|
|
fi
|
|
if bool "${NTFY_ENABLED:-}"; then
|
|
check_dep "curl" "curl wird für Ntfy benötigt."
|
|
fi
|
|
|
|
# ---------- Notifications ---------- #
|
|
send_mail() {
|
|
bool "${MAIL_ENABLED:-}" || return 0
|
|
local subject="$1"; shift
|
|
local body="$*"
|
|
[[ -n "${MAIL_RECIPIENTS:-}" ]] || { log "WARN" "MAIL_RECIPIENTS leer, Mail wird übersprungen."; return 0; }
|
|
local mail_cmd="${MAIL_CMD:-mail}"
|
|
printf "%s\n" "$body" | $mail_cmd -s "$subject" $MAIL_RECIPIENTS || log "ERROR" "Mail-Versand fehlgeschlagen."
|
|
}
|
|
|
|
send_ntfy() {
|
|
bool "${NTFY_ENABLED:-}" || return 0
|
|
local title="$1" priority="$2" body="$3"
|
|
[[ -n "${NTFY_TOPIC_URL:-}" ]] || { log "WARN" "NTFY_TOPIC_URL leer, Ntfy wird übersprungen."; return 0; }
|
|
local -a CURL_ARGS=( -sS -X POST "$NTFY_TOPIC_URL" -H "Title: $title" -H "Priority: $priority" --data-binary "$body" )
|
|
[[ -n "${NTFY_AUTH_TOKEN:-}" ]] && CURL_ARGS+=( -H "Authorization: Bearer $NTFY_AUTH_TOKEN" )
|
|
curl "${CURL_ARGS[@]}" >/dev/null || log "ERROR" "Ntfy-Versand fehlgeschlagen."
|
|
}
|
|
|
|
# ---------- Backup ---------- #
|
|
run_backup() {
|
|
local cmd=("proxmox-backup-client" "backup" "--repository" "$PBS_REPOSITORY")
|
|
|
|
for dir in "${INCLUDE_DIRS[@]}"; do
|
|
local base; base="$(basename "$dir")"
|
|
cmd+=("${base}.pxar:${dir}")
|
|
done
|
|
|
|
local cmd_pretty; cmd_pretty="$(printf "%q " "${cmd[@]}")"
|
|
log "INFO" "Backup-Command: ${cmd_pretty} # PBS_PASSWORD via env"
|
|
|
|
if $DRY_RUN; then
|
|
log "INFO" "DRY-RUN aktiv: Es wird nichts ausgeführt."
|
|
return 0
|
|
fi
|
|
|
|
{
|
|
echo "===== $(_timestamp) | BEGIN BACKUP RUN ====="
|
|
echo "Host: $HOSTNAME"
|
|
echo "Repository: $PBS_REPOSITORY"
|
|
echo "Quellen:"
|
|
printf " - %s\n" "${INCLUDE_DIRS[@]}"
|
|
echo "------------------------------------------------"
|
|
} | tee -a "$LOGFILE" | tee -a "$RUNLOG"
|
|
|
|
if (
|
|
[[ -n "${PBS_API:-}" ]] && export PBS_PASSWORD="$PBS_API"
|
|
[[ -n "${PBS_FINGERPRINT:-}" ]] && export PBS_FINGERPRINT
|
|
"${cmd[@]}"
|
|
) 2>&1 | tee -a "$LOGFILE" | tee -a "$RUNLOG"
|
|
then
|
|
echo "===== $(_timestamp) | END BACKUP RUN (OK) =====" | tee -a "$LOGFILE" | tee -a "$RUNLOG"
|
|
return 0
|
|
else
|
|
echo "===== $(_timestamp) | END BACKUP RUN (FAILED) =====" | tee -a "$LOGFILE" | tee -a "$RUNLOG"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# ---------- Healthcheck ---------- #
|
|
write_health_ok() {
|
|
[[ -n "$HEALTHCHECK_OK_FILE" ]] || return 0
|
|
mkdir -p "$(dirname "$HEALTHCHECK_OK_FILE")" || true
|
|
cat > "$HEALTHCHECK_OK_FILE" <<EOF
|
|
status=OK
|
|
timestamp=$(date -Iseconds)
|
|
host=$HOSTNAME
|
|
repo=$PBS_REPOSITORY
|
|
duration_s=$1
|
|
EOF
|
|
chmod 0644 "$HEALTHCHECK_OK_FILE" || true
|
|
}
|
|
|
|
remove_health_ok() {
|
|
[[ -n "$HEALTHCHECK_OK_FILE" ]] || return 0
|
|
rm -f "$HEALTHCHECK_OK_FILE" || true
|
|
}
|
|
|
|
# ---------- Main ---------- #
|
|
START_EPOCH="$(date +%s)"
|
|
STATUS="unknown"
|
|
EXIT_CODE=1
|
|
NTFY_TAIL_LINES="${NTFY_TAIL_LINES:-200}"
|
|
|
|
if run_backup; then
|
|
STATUS="success"
|
|
log "INFO" "Backup erfolgreich abgeschlossen."
|
|
if bool "${MAIL_NOTIFY_ON_SUCCESS:-}"; then
|
|
send_mail "${MAIL_SUBJECT_SUCCESS:-" ${HOSTNAME} | Backup erfolgreich abgeschlossen"}" \
|
|
"$(printf "Das Backup wurde erfolgreich abgeschlossen.\n\n--- Letzte %s Zeilen ---\n%s" "$NTFY_TAIL_LINES" "$(tail -n "$NTFY_TAIL_LINES" "$RUNLOG")")"
|
|
fi
|
|
if bool "${NTFY_NOTIFY_ON_SUCCESS:-}"; then
|
|
send_ntfy "${NTFY_TITLE_SUCCESS:-"Backup erfolgreich: ${HOSTNAME}"}" \
|
|
"${NTFY_PRIORITY_SUCCESS:-3}" \
|
|
"$(tail -n "$NTFY_TAIL_LINES" "$RUNLOG")"
|
|
fi
|
|
EXIT_CODE=0
|
|
else
|
|
STATUS="failure"
|
|
log "ERROR" "Fehler beim Backup."
|
|
send_mail "${MAIL_SUBJECT_FAILURE:-" ${HOSTNAME} | Fehler beim Backup"}" \
|
|
"$(printf "Es ist ein Fehler beim Backup aufgetreten.\n\n--- Letzte %s Zeilen ---\n%s" "${NTFY_TAIL_LINES}" "$(tail -n "$NTFY_TAIL_LINES" "$RUNLOG")")"
|
|
send_ntfy "${NTFY_TITLE_FAILURE:-"Backup FEHLGESCHLAGEN: ${HOSTNAME}"}" \
|
|
"${NTFY_PRIORITY_FAILURE:-5}" \
|
|
"$(tail -n "$NTFY_TAIL_LINES" "$RUNLOG")"
|
|
EXIT_CODE=1
|
|
fi
|
|
|
|
DURATION="$(( $(date +%s) - START_EPOCH ))"
|
|
log "INFO" "Ende ($STATUS) | Dauer: ${DURATION}s"
|
|
|
|
if [[ "$EXIT_CODE" -eq 0 ]]; then
|
|
write_health_ok "$DURATION"
|
|
else
|
|
remove_health_ok
|
|
fi
|
|
|
|
exit "$EXIT_CODE"
|