464 lines
14 KiB
Bash
464 lines
14 KiB
Bash
#!/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=(
|
||
SOURCEPORT
|
||
BACKUPSERVER
|
||
ZSYNC
|
||
MAINTDAY
|
||
SHUTDOWN
|
||
UPDATES
|
||
SOURCEHOST
|
||
ZFSROOT
|
||
ZFSSECOND
|
||
ZFSTRGT
|
||
ZPUSHTAG
|
||
ZPUSHMINKEEP
|
||
ZPUSHKEEP
|
||
ZPUSHLABEL
|
||
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 "$SOURCEPORT" "$@"
|
||
#}
|
||
|
||
#remote_scp() {
|
||
# scp -P "$SOURCEPORT" "$@"
|
||
#}
|
||
|
||
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 "sourceport=$SOURCEPORT"
|
||
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
|
||
}
|
||
|
||
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 "$SOURCEPORT" "$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 "$SOURCEPORT" "$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 -p "$SOURCEPORT" root@"$SOURCEHOST" hostname)
|
||
fi
|
||
|
||
log "Running PBS vzdump job..."
|
||
|
||
# PBS-Storage ggf. aktivieren
|
||
log "Checking if PBS storage '$BACKUPSTORE' is enabled on $SOURCEHOST..."
|
||
if ssh -p "$SOURCEPORT" root@"$SOURCEHOST" "pvesm status | grep -w '$BACKUPSTORE' | grep -q 'disabled'"; then
|
||
log "PBS storage '$BACKUPSTORE' is disabled. Attempting to enable... sleep 10 Sekunden"
|
||
ssh -p "$SOURCEPORT" 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 -p "$SOURCEPORT" 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 -p "$SOURCEPORT" 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 -p "$SOURCEPORT" 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 "<<<local>>>" ; cat /tmp/cmk_tmp.out ) > /tmp/90000_checkpbs
|
||
scp -P "$SOURCEPORT" /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 "$SOURCEPORT" 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
|
||
send_piggyback_data_external
|
||
log "Shutting down now..."
|
||
shutdown now
|
||
else
|
||
log "No shutdown requested."
|
||
fi
|
||
}
|
||
|
||
send_piggyback_data_external() {
|
||
if [[ "${EPIGGYBACK,,}" != "yes" ]]; then
|
||
log "Externer Piggyback-Export deaktiviert."
|
||
return
|
||
fi
|
||
|
||
if [[ -z "$EPIGGYBACK_HOST" || -z "$EPIGGYBACK_PORT" ]]; then
|
||
log "EPIGGYBACK_HOST oder EPIGGYBACK_PORT nicht gesetzt – Abbruch."
|
||
return 1
|
||
fi
|
||
|
||
local combined_host="miyagi-${SOURCEHOSTNAME}-$(hostname)"
|
||
local filename="90000_${combined_host}_external"
|
||
|
||
log "Erzeuge externe Piggyback-Datei: $filename"
|
||
|
||
{
|
||
echo "<<<<${combined_host}>>>>"
|
||
/usr/bin/check_mk_agent
|
||
echo "<<<<>>>>"
|
||
} > "$filename"
|
||
|
||
if scp -P "$EPIGGYBACK_PORT" "$filename" "$EPIGGYBACK_HOST:/var/lib/check_mk_agent/spool/"; then
|
||
log "Piggyback-Daten erfolgreich an $EPIGGYBACK_HOST gesendet."
|
||
else
|
||
log "Fehler beim Senden der Piggyback-Daten an $EPIGGYBACK_HOST"
|
||
fi
|
||
|
||
rm -f "$filename"
|
||
}
|
||
|
||
send_checkzfs_output_external() {
|
||
if [[ "${ECHECKZFS,,}" == "yes" ]]; then
|
||
log "ECHECKZFS aktiviert – beginne Monitoring-Ausgabe."
|
||
else
|
||
log "ECHECKZFS ist deaktiviert – überspringe Monitoring-Ausgabe."
|
||
return
|
||
fi
|
||
|
||
local checkzfs_cmd
|
||
checkzfs_cmd=$(which checkzfs)
|
||
|
||
if [[ -z "$checkzfs_cmd" ]]; then
|
||
log "check_zfs ist nicht installiert – Abbruch."
|
||
return
|
||
fi
|
||
|
||
local config_file="/etc/bashclub/${SOURCEHOST}.conf"
|
||
|
||
if [[ ! -f "$config_file" ]]; then
|
||
log "Konfigurationsdatei $config_file nicht gefunden – check_zfs-Ausführung abgebrochen."
|
||
return
|
||
fi
|
||
|
||
# Konfiguration einlesen
|
||
log "Lese Konfiguration aus $config_file..."
|
||
source "$config_file"
|
||
|
||
# Prüfung auf deaktiviertes Monitoring in der Konfig
|
||
if [[ "${checkzfs_disabled:-0}" -eq 1 ]]; then
|
||
log "check_zfs Monitoring laut Konfiguration deaktiviert – überspringe."
|
||
return
|
||
fi
|
||
|
||
local spool_tmp="/tmp/${checkzfs_spool_maxage}_${checkzfs_prefix}"
|
||
local remote_host="$ECHECKZFS_HOST"
|
||
local remote_port="$ECHECKZFS_PORT"
|
||
|
||
echo "<<<local>>>" > "$spool_tmp"
|
||
|
||
log "Generiere check_zfs Monitoring-Ausgabe mit Prefix $checkzfs_prefix..."
|
||
|
||
$checkzfs_cmd --source "$source:$soru" \
|
||
--output checkmk \
|
||
--threshold "$checkzfs_max_age" \
|
||
--maxsnapshots "$checkzfs_max_snapshot_count" \
|
||
--prefix "$checkzfs_prefix" \
|
||
--replicafilter "^${target}" \
|
||
--filter "$snapshot_filter" >> "$spool_tmp"
|
||
|
||
if [[ -s "$spool_tmp" ]]; then
|
||
log "Übertrage Spool-Datei an $remote_host über Port $remote_port..."
|
||
scp -P "$remote_port" "$spool_tmp" root@"$remote_host":/var/lib/check_mk_agent/spool/ && \
|
||
log "Spool-Datei erfolgreich übertragen." || \
|
||
log "Fehler beim Übertragen der Spool-Datei."
|
||
rm -f "$spool_tmp"
|
||
else
|
||
log "check_zfs-Ausgabe war leer – keine Übertragung erfolgt."
|
||
rm -f "$spool_tmp"
|
||
fi
|
||
}
|
||
|
||
|
||
main() {
|
||
log "Backup-Routine startet in 60 Sekunden..."
|
||
sleep 60
|
||
log "Starting full backup routine..."
|
||
|
||
log "Versuche Hostname von $SOURCEHOST via SSH (Port $SOURCEPORT) abzurufen..."
|
||
if ! SOURCEHOSTNAME=$(ssh -p "$SOURCEPORT" -o ConnectTimeout=5 -o BatchMode=yes "$SOURCEHOST" hostname 2>/dev/null); then
|
||
log "Error: Hostname von $SOURCEHOST konnte nicht abgerufen werden – Abbruch."
|
||
exit 1
|
||
fi
|
||
log "Hostname von $SOURCEHOST: $SOURCEHOSTNAME"
|
||
|
||
set_wol_g_enabled
|
||
write_zsync_config
|
||
run_zsync
|
||
send_checkzfs_output_external
|
||
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
|