#!/bin/bash # pve-host-backup.sh # Sichert die Konfiguration eines Proxmox-VE-Hosts in ein datiertes tar.gz. # Keine Nutzdaten: VMs und Container gehoeren in den Proxmox Backup Server. set -uo pipefail ########## Konfiguration ########## DEST="/mnt/pve/NAS03/backup-host/" # Zielverzeichnis, MUSS ausserhalb des Hosts liegen KEEP=14 # so viele Archive aufbewahren LOGFILE="/var/log/pve-host-backup.log" ################################### HOST="$(hostname)" STAMP="$(date +%Y-%m-%d_%H-%M)" OUT="${DEST}/${HOST}_${STAMP}.tar.gz" STATE="/var/tmp/pve-host-state" # Alles ab hier landet gleichzeitig auf der Konsole und im Logfile exec > >(tee -a "$LOGFILE") 2>&1 log() { printf '%s %-5s %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$1" "$2"; } info() { log "INFO" "$1"; } warn() { log "WARN" "$1"; } die() { log "ERROR" "$1"; log "INFO" "=== Backup abgebrochen ==="; exit 1; } cleanup() { rm -rf "$STATE"; } trap cleanup EXIT info "=== Backup gestartet auf ${HOST} ===" # --- Vorbedingungen ------------------------------------------------------- [ "$(id -u)" -eq 0 ] || die "Das Skript muss als root laufen." mkdir -p "$DEST" || die "Zielverzeichnis ${DEST} konnte nicht angelegt werden." info "Backup-Ziel: ${DEST}" # --- Zustandsinformationen einsammeln ------------------------------------- info "Sammle Zustandsinformationen." rm -rf "$STATE" mkdir -p "$STATE" pveversion -v > "$STATE/pveversion.txt" 2>/dev/null pvesm status > "$STATE/pvesm-status.txt" 2>/dev/null apt-mark showmanual > "$STATE/pkg-manual.txt" 2>/dev/null dpkg --get-selections > "$STATE/pkg-selections.txt" 2>/dev/null lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINT,SERIAL > "$STATE/lsblk.txt" 2>/dev/null blkid > "$STATE/blkid.txt" 2>/dev/null ip -br address > "$STATE/ip-address.txt" 2>/dev/null if command -v zpool >/dev/null 2>&1; then zpool status > "$STATE/zpool-status.txt" 2>/dev/null zpool list -v > "$STATE/zpool-list.txt" 2>/dev/null fi if [ -f /proc/mdstat ]; then cat /proc/mdstat > "$STATE/mdstat.txt" mdadm --detail --scan > "$STATE/mdadm-detail.txt" 2>/dev/null fi # --- Zu sichernde Pfade --------------------------------------------------- PATHS=( /etc/pve /var/lib/pve-cluster/config.db /etc/network/interfaces /etc/network/interfaces.d /etc/hostname /etc/hosts /etc/resolv.conf /etc/fstab /etc/ssh /etc/apt /etc/default/grub /etc/kernel/cmdline /etc/modules /etc/modprobe.d /etc/systemd/system /etc/systemd/network /etc/cron.d /etc/crontab /var/spool/cron /etc/passwd /etc/shadow /etc/group "$STATE" ) # Nur existierende Pfade uebergeben, sonst laermt tar im Log. # Fuehrender Slash wird entfernt und stattdessen mit "-C /" gearbeitet, # damit tar die "Removing leading /"-Meldungen gar nicht erst ausgibt. REL=() SKIPPED=() for p in "${PATHS[@]}"; do if [ -e "$p" ]; then REL+=("${p#/}") else SKIPPED+=("$p") fi done [ ${#SKIPPED[@]} -gt 0 ] && info "Nicht vorhanden, uebersprungen: ${SKIPPED[*]}" [ ${#REL[@]} -eq 0 ] && die "Keiner der konfigurierten Pfade existiert." # --- Archiv erstellen ----------------------------------------------------- info "Erstelle Archiv: ${OUT}" tar czf "$OUT" -C / --ignore-failed-read --warning=no-file-changed -- "${REL[@]}" rc=$? if [ $rc -eq 1 ]; then warn "tar meldet Warnungen (Dateien haben sich waehrend des Laufs geaendert)." elif [ $rc -gt 1 ]; then die "tar ist mit Exit-Code ${rc} fehlgeschlagen." fi chmod 600 "$OUT" # --- Archiv pruefen ------------------------------------------------------- if tar tzf "$OUT" >/dev/null 2>&1; then COUNT=$(tar tzf "$OUT" 2>/dev/null | wc -l) SIZE=$(du -h "$OUT" | cut -f1) info "Archiv OK: ${SIZE}, ${COUNT} Eintraege." else die "Archiv ${OUT} ist nicht lesbar. Backup unbrauchbar." fi # --- Alte Archive aufraeumen ---------------------------------------------- OLD=$(ls -1t "${DEST}/${HOST}_"*.tar.gz 2>/dev/null | tail -n +$((KEEP + 1))) if [ -n "$OLD" ]; then echo "$OLD" | while read -r f; do rm -f "$f" && info "Geloescht (aelter als die letzten ${KEEP}): $(basename "$f")" done else info "Keine alten Archive zu loeschen (Aufbewahrung: ${KEEP})." fi info "=== Backup erfolgreich abgeschlossen ===" exit 0