#!/bin/bash set -uo pipefail IFS=$'\n\t' PATH="/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin" SCRIPT_NAME=$(basename "$0") LOGFILE="/var/log/${SCRIPT_NAME%.sh}.log" log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOGFILE" } usage() { echo "Usage:" echo " $0 -c /path/to/config # Full backup run" echo " $0 [function] # Run individual function" echo " $0 help # Show available functions" exit 1 } CONFIG_FILE="" while getopts "c:" opt; do case "$opt" in c) CONFIG_FILE="$OPTARG" ;; *) usage ;; esac done # Bei Einzelaufruf muss Konfiguration geladen sein shift $((OPTIND - 1)) if [[ -n "${CONFIG_FILE:-}" ]]; then if [[ ! -f "$CONFIG_FILE" ]]; then log "ERROR: Configuration file not found: $CONFIG_FILE" exit 1 fi if ! bash -n "$CONFIG_FILE"; then log "Syntaxfehler in Konfigurationsdatei $CONFIG_FILE" exit 1 fi source "$CONFIG_FILE" # ========================== # Konfigurationsprüfung # ========================== REQUIRED_VARS=( SSHPORT BACKUPSERVER ZSYNC MAINTDAY SHUTDOWN UPDATES SOURCEHOST ZFSROOT ZFSSECOND ZFSTRGT ZPUSHTAG ZPUSHMINKEEP ZPUSHKEEP ZPUSHLABEL ZPUSHFILTER PBSHOST BACKUPSTORE BACKUPSTOREPBS BACKUPEXCLUDE REPLEXCLUDE ) MISSING_VARS=() for var in "${REQUIRED_VARS[@]}"; do #if [[ -z "${!var:-}" ]]; then if ! declare -p "$var" &>/dev/null || [[ -z "${!var}" ]]; then MISSING_VARS+=("$var") fi done if [[ "${#MISSING_VARS[@]}" -ne 0 ]]; then log "Fehlende Konfigurationsvariablen:" for var in "${MISSING_VARS[@]}"; do log " - $var" done log "Breche ab – bitte Konfigurationsdatei prüfen." exit 1 else log "Alle erforderlichen Konfigurationsvariablen sind gesetzt." fi fi # Funktionen remote_ssh() { ssh -p "$SSHPORT" "$@" } remote_scp() { scp -P "$SSHPORT" "$@" } set_wol_g_enabled() { log "Pruefe, ob ethtool installiert ist..." if ! command -v ethtool >/dev/null 2>&1; then log "ethtool ist nicht installiert, versuche Installation..." apt update && apt install -y ethtool || { log "Fehler: ethtool konnte nicht installiert werden." return 1 } else log "ethtool ist bereits installiert." fi log "Pruefe und setze Wake-on-LAN (WOL) auf 'g' nur bei Interfaces mit statischer IP..." for iface in $(ls /sys/class/net | grep -vE '^(lo|tap|vmbr|veth|br|docker|bond|wl)'); do if [[ -e "/sys/class/net/$iface/device" ]]; then log "Bearbeite physisches Interface: $iface" # Aktuellen WOL-Status pruefen current_wol=$(ethtool "$iface" 2>/dev/null | awk '/Wake-on:/ {print $2}') if [[ "$current_wol" != "g" ]]; then log "Setze WOL auf 'g' fuer $iface..." ethtool -s "$iface" wol g || log "Fehler beim Setzen von WOL auf $iface" else log "WOL ist bereits korrekt auf 'g' fuer $iface" fi # Pruefen, ob ein 'iface $iface inet static' Eintrag existiert if grep -qE "^\s*iface\s+$iface\s+inet\s+static" /etc/network/interfaces; then if ! grep -A 5 -E "^\s*iface\s+$iface\s+inet\s+static" /etc/network/interfaces | grep -q "post-up ethtool -s $iface wol g"; then log "Ergaenze WOL-Befehl im statischen Block fuer $iface..." sed -i "/^\s*iface\s\+$iface\s\+inet\s\+static/a \ post-up ethtool -s $iface wol g" /etc/network/interfaces else log "WOL-Befehl fuer $iface ist bereits im statischen Block vorhanden." fi else log "Kein statischer Eintrag fuer $iface gefunden, keine Aenderung vorgenommen." fi fi done } write_zsync_config() { local conf_file="/etc/bashclub/$SOURCEHOST.conf" log "Writing zsync config to $conf_file" { echo "target=$ZFSTRGT" echo "source=root@$SOURCEHOST" echo "sshport=$SSHPORT" echo "tag=$ZPUSHTAG" echo "snapshot_filter=$ZPUSHFILTER" echo "min_keep=$ZPUSHMINKEEP" echo "zfs_auto_snapshot_keep=$ZPUSHKEEP" echo "zfs_auto_snapshot_label=$ZPUSHLABEL" echo "zfs_auto_snapshot_engine=internal" echo "checkzfs_disabled=0" echo "checkzfs_local=0" echo "checkzfs_prefix=miyagi-$SOURCEHOSTNAME-$(hostname)-$ZPUSHTAG" echo "checkzfs_max_age=1500,2000" echo "checkzfs_max_snapshot_count=180,200" echo "checkzfs_spool=1" echo "checkzfs_spool_maxage=90000" } > "$conf_file" } run_zsync() { if [[ "$ZSYNC" != "no" ]]; then /usr/bin/bashclub-zsync -c "/etc/bashclub/$SOURCEHOST.conf" else log "Zsync is disabled" fi } run_remote_updates() { if [[ "$UPDATES" == "yes" ]]; then ssh "$PBSHOST" apt update && apt dist-upgrade -y else log "Remote updates disabled" fi } run_remote_updates() { if [[ "$UPDATES" == "yes" ]]; then ssh "$PBSHOST" apt update && apt dist-upgrade -y else log "Remote updates disabled" fi } send_piggyback_data() { # Falls SOURCEHOSTNAME leer ist, ueber SSH vom Zielhost ermitteln if [[ -z "${SOURCEHOSTNAME:-}" ]]; then log "SOURCEHOSTNAME is empty retrieving via SSH from $SOURCEHOST..." SOURCEHOSTNAME=$(ssh -p "$SSHPORT" "$SOURCEHOST" hostname) log "Detected SOURCEHOSTNAME: $SOURCEHOSTNAME" fi local combined_host="miyagi-${SOURCEHOSTNAME}-$(hostname)" local filename="90000_${combined_host}" log "Do nott forget to add a Host in CMK named: ${combined_host} (without Agent, Piggyback enabled)!" log "Creating piggyback file: $filename" { echo "<<<<${combined_host}>>>>" /usr/bin/check_mk_agent echo "<<<<>>>>" } > "$filename" if scp -P "$SSHPORT" "$filename" "$SOURCEHOST:/var/lib/check_mk_agent/spool/"; then log "Piggyback data successfully sent to $SOURCEHOST" else log "ERROR: Failed to send piggyback data to $SOURCEHOST" fi } run_pbs_backup() { if [[ -z "${SOURCEHOSTNAME:-}" ]]; then SOURCEHOSTNAME=$(ssh "$SOURCEHOST" hostname) fi log "Running PBS vzdump job..." # PBS-Storage ggf. aktivieren log "Checking if PBS storage '$BACKUPSTORE' is enabled on $SOURCEHOST..." if ssh root@"$SOURCEHOST" "pvesm status | grep -w '$BACKUPSTORE' | grep -q 'disabled'"; then log "PBS storage '$BACKUPSTORE' is disabled. Attempting to enable...sleep 10 Sekunden" ssh root@"$SOURCEHOST" "pvesm set '$BACKUPSTORE' --disable 0 && sleep 10" pbs_enabled_by_script=true else log "PBS storage '$BACKUPSTORE' is already enabled." pbs_enabled_by_script=false fi vzdump_success=false # Hauptversuch mit --pbs-change-detection-mode if ssh root@"$SOURCEHOST" vzdump --pbs-change-detection-mode metadata \ --node "$SOURCEHOSTNAME" --storage "$BACKUPSTORE" \ --exclude "$BACKUPEXCLUDE" --mode snapshot --all 1 \ --notes-template '{{guestname}}'; then log "vzdump with change-detection-mode succeeded" vzdump_success=true else log "Fallback: vzdump with change-detection-mode failed, trying without it..." if ssh root@"$SOURCEHOST" vzdump \ --node "$SOURCEHOSTNAME" --storage "$BACKUPSTORE" \ --exclude "$BACKUPEXCLUDE" --mode snapshot --all 1 \ --notes-template '{{guestname}}'; then log "Fallback vzdump succeeded" vzdump_success=true else log "ERROR: vzdump failed even after fallback" fi fi # PBS-Storage wieder deaktivieren, wenn zuvor aktiviert und erfolgreich if [[ "$vzdump_success" == true && "$pbs_enabled_by_script" == true ]]; then log "Disabling PBS storage '$BACKUPSTORE' again on $SOURCEHOST..." ssh root@"$SOURCEHOST" "pvesm set '$BACKUPSTORE' --disable 1" fi # Monitoring-Output für Checkmk if [[ "$vzdump_success" == true ]]; then echo "0 DailyPBS - Daily Backup" > /tmp/cmk_tmp.out else echo "2 DailyPBS - Daily Backup FAILED" > /tmp/cmk_tmp.out fi ( echo "<<>>" ; cat /tmp/cmk_tmp.out ) > /tmp/90000_checkpbs scp /tmp/90000_checkpbs root@"$SOURCEHOST":/var/lib/check_mk_agent/spool || log "Fehler beim SCP des Monitoring-Outputs" } run_maintenance() { if [[ "$(date +%u)" == "$MAINTDAY" ]]; then log "Running maintenance..." PRUNEJOB=$(ssh "$PBSHOST" proxmox-backup-manager prune-job list --output-format json-pretty | grep -m 1 "id" | cut -d'"' -f4) ssh root@"$PBSHOST" proxmox-backup-manager prune-job run "$PRUNEJOB" ssh root@"$PBSHOST" proxmox-backup-manager garbage-collection start "$BACKUPSTOREPBS" ssh root@"$PBSHOST" proxmox-backup-manager verify backup else log "No maintenance scheduled for today." fi } run_scrub_stop_src() { ssh -p "$SSHPORT" root@"$SOURCEHOST" 'for pool in $(zpool list -H -o name); do echo "Stoppe Scrub auf Pool: $pool" if zpool status "$pool" | grep -q "scrub in progress"; then if zpool scrub -s "$pool"; then echo "Scrub auf $pool gestoppt" else echo "Fehler beim Stoppen des Scrubs auf $pool" fi else echo "Kein aktiver Scrub auf $pool" fi done' } run_scrub_stop_local() { for pool in $(zpool list -H -o name); do echo "Stoppe Scrub auf Pool: $pool" if zpool status "$pool" | grep -q "scrub in progress"; then if zpool scrub -s "$pool"; then echo "Scrub auf $pool gestoppt" else echo "Fehler beim Stoppen des Scrubs auf $pool" fi else echo "Kein aktiver Scrub auf $pool" fi done } shutdown_if_requested() { log "SHUTDOWN-Variable: '${SHUTDOWN:-nicht gesetzt}'" #if [[ "${SHUTDOWN,,}" == "yes" ]]; then if [[ "$(echo "$SHUTDOWN" | tr '[:upper:]' '[:lower:]')" == "yes" ]]; then send_piggyback_data log "Shutting down now..." shutdown now else log "No shutdown requested." fi } main() { log "Backup-Routine startet in 60 Sekunden..." sleep 60 log "Starting full backup routine..." SOURCEHOSTNAME=$(ssh "$SOURCEHOST" hostname) set_wol_g_enabled write_zsync_config run_zsync run_scrub_stop_local run_scrub_stop_src if [[ "${BACKUPSERVER,,}" == "yes" ]]; then log "BACKUPSERVER ist aktiviert, führe Backup aus..." run_maintenance run_pbs_backup else log "BACKUPSERVER ist nicht aktiviert (BACKUPSERVER=$BACKUPSERVER), überspringe Backup." fi run_remote_updates run_updates shutdown_if_requested } # Funktionsbasierter Aufruf if [[ "${1:-}" == "help" ]]; then echo "Verfuegbare Funktionen:" declare -F | awk '{print " - " $3}' | grep -v "^ - _" exit 0 elif [[ "${1:-}" =~ ^[a-zA-Z0-9_]+$ && "$(type -t "$1")" == "function" ]]; then FUNC="$1" shift "$FUNC" "$@" exit 0 else main fi