#!/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
