Files

841 lines
26 KiB
Bash
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
set -uo pipefail
IFS=$'\n\t'
PATH="/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin"
SCRIPT_NAME=$(basename "$0")
SCRIPT_DIR=$(cd -- "$(dirname -- "$0")" &>/dev/null && pwd)
LOGFILE="/var/log/${SCRIPT_NAME%.sh}.log"
# checkzfs-Defaults (in config.example überschreibbar, hier zentral statt an mehreren Stellen im Skript)
CHECKZFS_MAX_AGE="${CHECKZFS_MAX_AGE:-1500,2000}"
CHECKZFS_MAX_SNAPSHOT_COUNT="${CHECKZFS_MAX_SNAPSHOT_COUNT:-180,200}"
CHECKZFS_SPOOL_MAXAGE="${CHECKZFS_SPOOL_MAXAGE:-100000}"
CHECKZFS_DISABLED="${CHECKZFS_DISABLED:-0}"
CHECKZFS_LOCAL="${CHECKZFS_LOCAL:-0}"
CHECKZFS_SPOOL="${CHECKZFS_SPOOL:-1}"
# Check_MK Spool-Staleness (Sekunden, Dateiname-Prefix) – zentral statt an jeder Stelle hartkodiert
SPOOL_MAXAGE_PIGGYBACK="${SPOOL_MAXAGE_PIGGYBACK:-100000}"
SPOOL_MAXAGE_CHECKZFS="${SPOOL_MAXAGE_CHECKZFS:-100000}"
SPOOL_MAXAGE_CHECKPBS="${SPOOL_MAXAGE_CHECKPBS:-1200000}"
SPOOL_MAXAGE_PBS_STATUS="${SPOOL_MAXAGE_PBS_STATUS:-90000}"
detect_os() {
case "$(uname -s)" in
Linux) echo "linux" ;;
FreeBSD) echo "freebsd" ;;
*) echo "unknown" ;;
esac
}
OS_TYPE="$(detect_os)"
epoch_to_str() {
local epoch="$1"
if [[ "$OS_TYPE" == "freebsd" ]]; then
date -r "$epoch" "+%Y-%m-%d %H:%M:%S"
else
date -d "@$epoch" "+%Y-%m-%d %H:%M:%S"
fi
}
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
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 "Syntax error in configuration file $CONFIG_FILE"
exit 1
fi
source "$CONFIG_FILE"
REQUIRED_VARS=(
SOURCEPORT BACKUPSERVER ZSYNC MAINTDAY SHUTDOWN UPDATES
SOURCEHOST ZFSTRGT ZPUSHTAG ZPUSHMINKEEP ZPUSHKEEP ZPUSHLABEL
PBSHOST PBSPORT BACKUPSTORE BACKUPSTOREPBS BACKUPEXCLUDE REPLEXCLUDE DYNROUTE REBOOT
)
if [[ "${DYNROUTE,,}" == "yes" ]]; then
REQUIRED_VARS+=(DDNS_GATEWAY DDNS_SERVER)
fi
MISSING_VARS=()
for var in "${REQUIRED_VARS[@]}"; do
if [[ -z "${!var:-}" ]]; then
MISSING_VARS+=("$var")
fi
done
if [[ "${#MISSING_VARS[@]}" -gt 0 ]]; then
log "Missing configuration variables:"
for var in "${MISSING_VARS[@]}"; do
log " - $var"
done
log "Aborting — please check your configuration file."
exit 1
else
log "All required configuration variables are set."
fi
fi
SOURCEUSER="${SOURCEUSER:-root}"
PBSUSER="${PBSUSER:-root}"
EPIGGYBACK_USER="${EPIGGYBACK_USER:-root}"
ECHECKZFS_USER="${ECHECKZFS_USER:-root}"
# Für hooks.d/-Scripts verfügbar machen (laufen als eigener Prozess via `bash`)
export SOURCEHOST SOURCEPORT SOURCEUSER PBSHOST PBSPORT PBSUSER BACKUPSTOREPBS
rssh() { ssh -o BatchMode=yes -o ConnectTimeout=10 -p "$SOURCEPORT" "${SOURCEUSER}@${SOURCEHOST}" "$@"; }
rscp() { scp -P "$SOURCEPORT" "$@"; }
rpbssh() { ssh -o BatchMode=yes -o ConnectTimeout=10 -p "$PBSPORT" "${PBSUSER}@${PBSHOST}" "$@"; }
detect_host_type() {
if command -v pveversion >/dev/null 2>&1; then
echo "pve"
elif command -v proxmox-backup-server >/dev/null 2>&1; then
echo "pbs"
else
echo "unknown"
fi
}
get_sourcehostname() {
if [[ -z "${SOURCEHOSTNAME:-}" ]]; then
log "SOURCEHOSTNAME is empty, retrieving via SSH from $SOURCEHOST..."
SOURCEHOSTNAME=$(rssh hostname)
log "Detected SOURCEHOSTNAME: $SOURCEHOSTNAME"
fi
}
zfs_replace() {
local status
status=$(zpool status)
if echo "$status" | grep -q 'scan: resilver in progress'; then
return 0
fi
if echo "$status" | grep -qE 'replacing-[0-9]'; then
return 0
fi
return 1
}
wait_replace() {
local interval_seconds=300 # Alle 5 Minuten prüfen
local waited_minutes=0
log "ZFS Replace-Vorgang erkannt – warte unbegrenzt auf Abschluss..."
while true; do
if ! zfs_replace; then
log "Replace abgeschlossen nach $waited_minutes Minuten – fahre jetzt herunter."
return 0
fi
log "Replace läuft noch – erneut prüfen in $((interval_seconds/60)) Minuten..."
sleep "$interval_seconds"
((waited_minutes+=interval_seconds/60))
done
}
set_wol_g_enabled() {
if [[ "$OS_TYPE" == "freebsd" ]]; then
log "FreeBSD erkannt – set_wol_g_enabled() wird übersprungen (ethtool/apt nicht verfügbar)."
return 0
fi
log "Checking if ethtool is installed..."
if ! command -v ethtool >/dev/null 2>&1; then
log "ethtool is not installed, attempting installation..."
apt update && apt install -y ethtool || {
log "Error: ethtool could not be installed."
return 1
}
else
log "ethtool is already installed."
fi
log "Setting Wake-on-LAN (WOL) to 'g' on interfaces with static 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 "Processing physical interface: $iface"
current_wol=$(ethtool "$iface" 2>/dev/null | awk '/Wake-on:/ {print $2}')
if [[ "$current_wol" != "g" ]]; then
log "Setting WOL to 'g' for $iface..."
ethtool -s "$iface" wol g || log "Error setting WOL on $iface"
else
log "WOL already set to 'g' for $iface"
fi
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 "Adding WOL command in static block for $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 command already present in static block for $iface."
fi
else
log "No static entry found for $iface, no changes made."
fi
fi
done
}
write_zsync_config() {
get_sourcehostname
local conf_file="/etc/bashclub/$SOURCEHOST.conf"
log "Writing zsync config to $conf_file"
{
echo "target=$ZFSTRGT"
echo "source=${SOURCEUSER}@${SOURCEHOST}"
echo "sshport=$SOURCEPORT"
echo "tag=$ZPUSHTAG"
echo "snapshot_filter=\"$ZPUSHFILTER\""
echo "min_keep=$ZPUSHMINKEEP"
echo "zfs_auto_snapshot_keep=$ZPUSHKEEP"
echo "zfs_auto_snapshot_label=zfs-auto-snap_$ZPUSHLABEL"
echo "zfs_auto_snapshot_engine=internal"
echo "checkzfs_disabled=$CHECKZFS_DISABLED"
echo "checkzfs_local=$CHECKZFS_LOCAL"
echo "checkzfs_prefix=miyagi-$SOURCEHOSTNAME-$(hostname)-$ZPUSHTAG"
echo "checkzfs_max_age=$CHECKZFS_MAX_AGE"
echo "checkzfs_max_snapshot_count=$CHECKZFS_MAX_SNAPSHOT_COUNT"
echo "checkzfs_spool=$CHECKZFS_SPOOL"
echo "checkzfs_spool_maxage=$CHECKZFS_SPOOL_MAXAGE"
} > "$conf_file"
}
run_zsync() {
write_zsync_config
if [[ "$ZSYNC" != "no" ]]; then
/usr/bin/bashclub-zsync -c "/etc/bashclub/$SOURCEHOST.conf"
else
log "Zsync is disabled."
fi
}
run_local_apt_updates() {
log "Running updates on local system..."
local TIMESTAMP
TIMESTAMP=$(date +"%Y-%m-%d_%H-%M-%S")
local SNAPSHOT_TAG="pve-update-via-miyagi"
local MAX_SNAPSHOTS=5
local REBOOT_FLAG="${REBOOT^^}"
local UPDATE_LOGFILE="/var/log/proxmox_update.log"
log_msg() {
local level="$1"
local message="$2"
local timestamp
timestamp=$(date +"%Y-%m-%d %H:%M:%S")
echo "[$timestamp] $message"
echo "[$timestamp] [$level] $message" >> "$UPDATE_LOGFILE"
}
local HOST_TYPE
HOST_TYPE=$(detect_host_type)
local ZFS_DATASETS=()
case "$HOST_TYPE" in
pve)
log_msg "INFO" "Systemtyp erkannt: PVE"
ZFS_DATASETS=("rpool/ROOT/pve-1" "rpool/pveconf")
;;
pbs)
log_msg "INFO" "Systemtyp erkannt: PBS"
ZFS_DATASETS=("rpool/ROOT/pbs-1")
;;
*)
log_msg "WARN" "Systemtyp (PVE/PBS) konnte nicht erkannt werden – ueberspringe ZFS-Snapshots."
;;
esac
# Prüfen, ob Updates verfügbar sind
if ! apt update | tee -a "$UPDATE_LOGFILE" | grep -qi "upgradable"; then
log_msg "INFO" "Keine Updates verfügbar."
return
fi
log_msg "INFO" "Updates verfügbar. Erstelle Snapshots."
for dataset in "${ZFS_DATASETS[@]}"; do
if ! zfs list -H -o name "$dataset" >/dev/null 2>&1; then
log_msg "WARN" "Dataset $dataset existiert nicht – übersprungen."
continue
fi
snapshot="${dataset}@${SNAPSHOT_TAG}-${TIMESTAMP}"
log_msg "INFO" "Erstelle Snapshot: $snapshot"
zfs snapshot "$snapshot"
# Alte Snapshots bereinigen
log_msg "INFO" "Bereinige alte Snapshots in $dataset"
old_snaps=$(zfs list -t snapshot -o name -s creation | grep "^${dataset}@${SNAPSHOT_TAG}-")
snap_count=$(grep -c . <<< "$old_snaps")
if (( snap_count > MAX_SNAPSHOTS )); then
snaps_to_delete=$(echo "$old_snaps" | head -n $((snap_count - MAX_SNAPSHOTS)))
while IFS= read -r snap; do
log_msg "INFO" "Lösche alten Snapshot: $snap"
zfs destroy "$snap"
done <<< "$snaps_to_delete"
fi
done
# System-Upgrade
log_msg "INFO" "Starte dist-upgrade"
if ! apt dist-upgrade -y | tee -a "$UPDATE_LOGFILE"; then
log_msg "ERROR" "Fehler während dist-upgrade"
return 1
fi
log_msg "INFO" "Starte autoremove"
apt autoremove -y | tee -a "$UPDATE_LOGFILE"
# Kernel-Update prüfen
if apt list --upgradable 2>/dev/null | grep -q "linux-image-"; then
log_msg "WARN" "Kernel-Update erkannt. Neustart empfohlen."
if [[ "$REBOOT_FLAG" == "YES" ]]; then
log_msg "INFO" "REBOOT=YES erkannt. System wird neugestartet."
reboot
fi
else
log_msg "INFO" "Update abgeschlossen. Kein Neustart erforderlich."
fi
}
run_pbs_apt_updates() {
if [[ "${BACKUPSERVER,,}" == "yes" ]]; then
log "Running updates on PBS host ($PBSHOST)..."
rpbssh apt update && rpbssh apt dist-upgrade -y || {
log "Error during updates on $PBSHOST"
}
else
log "PBS updates skipped (BACKUPSERVER=$BACKUPSERVER)"
fi
}
run_remote_updates() {
if [[ "${UPDATES,,}" == "yes" ]]; then
if [[ "$OS_TYPE" == "freebsd" ]]; then
log "FreeBSD erkannt – lokales apt-Update wird übersprungen (apt nicht verfügbar)."
else
run_local_apt_updates
fi
# PBS-Host-Update läuft unabhängig vom lokalen Update-Ergebnis/Skript-Host-OS
run_pbs_apt_updates
run_hooks post-update
else
log "Updates disabled (UPDATES=$UPDATES)"
fi
}
send_piggyback() {
get_sourcehostname
local combined_host="miyagi-${SOURCEHOSTNAME}-$(hostname)"
local filename="${SPOOL_MAXAGE_PIGGYBACK}_${combined_host}"
log "Reminder: Add a host named ${combined_host} in CMK (without Agent, Piggyback enabled)!"
log "Creating piggyback file: $filename"
{
echo "<<<<${combined_host}>>>>"
/usr/bin/check_mk_agent
echo "<<<<>>>>"
} > "$filename"
if rscp "$filename" "${SOURCEUSER}@${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
rm -f "$filename"
}
run_pbs_backup() {
if [[ "${BACKUPSERVER,,}" != "yes" ]]; then
log "PBS Backup übersprungen: BACKUPSERVER='$BACKUPSERVER' (muss 'yes' sein)"
return 0
fi
get_sourcehostname
log "Starte PBS Backup auf Host: $SOURCEHOST"
# PBS-Storage prüfen und ggf. aktivieren
log "Prüfe, ob PBS-Storage '$BACKUPSTORE' auf $SOURCEHOST aktiviert ist..."
if rssh "pvesm status | grep -w '$BACKUPSTORE' | grep -q 'disabled'"; then
log "PBS-Storage '$BACKUPSTORE' ist deaktiviert – aktiviere temporär und warte 10 Sekunden..."
rssh "pvesm set '$BACKUPSTORE' --disable 0 && sleep 10"
pbs_enabled_by_script=true
else
log "PBS-Storage '$BACKUPSTORE' ist bereits aktiviert."
pbs_enabled_by_script=false
fi
vzdump_success=false
# Versuch: vzdump mit Change Detection
## Eintrag muss in /etc/vzdump.conf entries-max 100000000
log "Starte vzdump mit Change Detection..."
if rssh vzdump --pbs-change-detection-mode metadata \
--node "$SOURCEHOSTNAME" --storage "$BACKUPSTORE" \
--exclude "$BACKUPEXCLUDE" --mode snapshot --all 1 \
--notes-template '{{guestname}}'; then
log "vzdump (Change Detection) erfolgreich."
vzdump_success=true
else
log "vzdump (Change Detection) fehlgeschlagen – versuche Fallback ohne Change Detection..."
if rssh vzdump \
--node "$SOURCEHOSTNAME" --storage "$BACKUPSTORE" \
--exclude "$BACKUPEXCLUDE" --mode snapshot --all 1 \
--notes-template '{{guestname}}'; then
log "Fallback-vzdump erfolgreich."
vzdump_success=true
else
log "FEHLER: vzdump fehlgeschlagen – auch im Fallback."
fi
fi
# PBS-Storage ggf. wieder deaktivieren
if [[ "$vzdump_success" == true && "$pbs_enabled_by_script" == true ]]; then
log "Deaktiviere temporär aktiviertes PBS-Storage '$BACKUPSTORE' auf $SOURCEHOST..."
rssh "pvesm set '$BACKUPSTORE' --disable 1"
fi
# Monitoring-Ausgabe
if [[ "$vzdump_success" == true ]]; then
echo "0 DailyPBS - Daily Backup erfolgreich" > /tmp/cmk_tmp.out
else
echo "2 DailyPBS - Daily Backup FEHLGESCHLAGEN" > /tmp/cmk_tmp.out
fi
{
echo "<<<local>>>"
cat /tmp/cmk_tmp.out
} > "/tmp/${SPOOL_MAXAGE_CHECKPBS}_checkpbs"
rscp "/tmp/${SPOOL_MAXAGE_CHECKPBS}_checkpbs" "${SOURCEUSER}@${SOURCEHOST}":/var/lib/check_mk_agent/spool \
|| log "Fehler beim Übertragen des Monitoring-Outputs via SCP"
rm -f /tmp/cmk_tmp.out "/tmp/${SPOOL_MAXAGE_CHECKPBS}_checkpbs"
#write_pbs_status
}
run_maintenance() {
if [[ "${BACKUPSERVER,,}" != "yes" ]]; then
log "PBS Backup wird übersprungen (BACKUPSERVER=$BACKUPSERVER)"
return
fi
if [[ "$(date +%u)" == "$MAINTDAY" ]]; then
log "Running maintenance..."
PRUNEJOB=$(rpbssh proxmox-backup-manager prune-job list --output-format json-pretty | grep -m 1 "id" | cut -d'"' -f4)
rpbssh proxmox-backup-manager prune-job run "$PRUNEJOB"
rpbssh proxmox-backup-manager garbage-collection start "$BACKUPSTOREPBS"
rpbssh proxmox-backup-manager verify backup
else
log "No maintenance scheduled for today."
fi
}
run_scrub_stop() {
local mode="$1" # "local" oder "remote"
local ssh_cmd=()
if [[ "$mode" == "remote" ]]; then
ssh_cmd=(ssh -o BatchMode=yes -o ConnectTimeout=10 -p "$SOURCEPORT" "${SOURCEUSER}@${SOURCEHOST}")
fi
"${ssh_cmd[@]}" bash -c '
for pool in $(zpool list -H -o name); do
echo "Stopping scrub on pool: $pool"
if zpool status "$pool" | grep -q "scrub in progress"; then
if zpool scrub -s "$pool"; then
echo "Scrub stopped on $pool"
else
echo "Error stopping scrub on $pool"
fi
else
echo "No active scrub on $pool"
fi
done
'
}
write_pbs_status() {
local lib_script="$SCRIPT_DIR/lib/pbs-status.sh"
if [[ ! -x "$lib_script" ]]; then
log "FEHLER: $lib_script nicht gefunden oder nicht ausführbar."
return 1
fi
PBSHOST="$PBSHOST" BACKUPSTOREPBS="$BACKUPSTOREPBS" \
PBS_REPO_USER="${PBS_REPO_USER:-}" PBS_PASSWORD="${PBS_PASSWORD:-}" \
PBSBACKUP_STATUS="${PBSBACKUP_STATUS:-}" \
SPOOL_MAXAGE_PBS_STATUS="$SPOOL_MAXAGE_PBS_STATUS" \
"$lib_script" | tee -a "$LOGFILE"
}
run_hooks() {
local stage="$1"
local hook_dir="/etc/miyagi-backup/hooks.d/$stage"
if [[ ! -d "$hook_dir" ]]; then
return 0
fi
local hooks=()
while IFS= read -r -d '' f; do
hooks+=("$f")
done < <(find "$hook_dir" -maxdepth 1 -name "*.sh" -executable -print0 | sort -z)
if [[ ${#hooks[@]} -eq 0 ]]; then
return 0
fi
log "Führe $stage-Hooks aus ($hook_dir)..."
for hook in "${hooks[@]}"; do
log "Hook: $hook"
if ! bash "$hook"; then
log "WARNUNG: Hook $hook fehlgeschlagen (Exit $?) – fahre fort"
fi
done
}
shutdown_now() {
if [[ "${SHUTDOWN,,}" == "yes" ]]; then
if zfs_replace; then
log "ZFS Replace-Vorgang erkannt – warte bis zum Abschluss..."
if ! wait_replace; then
log "Shutdown abgebrochen – Replace ist nach max. Wartezeit noch nicht abgeschlossen."
return
fi
fi
send_piggyback
send_piggyback_external
send_checkzfs_external
run_hooks pre-shutdown
log "Shutting down now...in 60sec"
sleep 60
shutdown now
else
log "No shutdown requested."
fi
}
send_piggyback_external() {
if [[ "${EPIGGYBACK,,}" != "yes" ]]; then
log "Externer Piggyback-Export deaktiviert."
return
fi
get_sourcehostname
log "Ermittelter SOURCEHOSTNAME: $SOURCEHOSTNAME"
local combined_host="miyagi-${SOURCEHOSTNAME}-$(hostname)"
local spool_file="${SPOOL_MAXAGE_PIGGYBACK}_${combined_host}_piggyback_external"
local temp_dir
temp_dir=$(mktemp -d)
log "Erzeuge temporäre Piggyback-Datei: $temp_dir/$spool_file"
{
echo "<<<<${combined_host}>>>>"
/usr/bin/check_mk_agent
echo "<<<<>>>>"
} > "$temp_dir/$spool_file"
if scp -P "$EPIGGYBACK_PORT" "$temp_dir/$spool_file" "${EPIGGYBACK_USER}@${EPIGGYBACK_HOST}:/var/lib/check_mk_agent/spool/"; then
log "Piggyback-Daten erfolgreich an $EPIGGYBACK_HOST gesendet: $spool_file"
else
log "Fehler beim Übertragen der Piggyback-Daten an $EPIGGYBACK_HOST"
rm -rf "$temp_dir"
return 1
fi
rm -rf "$temp_dir"
return 0
}
send_checkzfs() {
if [[ "${ZSYNC,,}" != "yes" ]]; then
log "send_checkzfs: ZSYNC nicht aktiv – übersprungen"
return 0
fi
get_sourcehostname
local checkzfs_cmd
checkzfs_cmd="$(command -v checkzfs || true)"
if [[ -z "$checkzfs_cmd" || ! -x "$checkzfs_cmd" ]]; then
log "send_checkzfs: checkzfs nicht gefunden – übersprungen"
return 0
fi
# Defaults für checkzfs-Parameter (können via zsync-Config überschrieben werden)
local checkzfs_prefix="${checkzfs_prefix:-miyagi-${SOURCEHOSTNAME}-$(hostname)-${ZPUSHTAG}}"
local checkzfs_max_age="${checkzfs_max_age:-$CHECKZFS_MAX_AGE}"
local checkzfs_max_snapshot_count="${checkzfs_max_snapshot_count:-$CHECKZFS_MAX_SNAPSHOT_COUNT}"
# Optional zsync-Config laden falls vorhanden (überschreibt Defaults)
local zsync_config="/etc/bashclub/${SOURCEHOST}.conf"
[[ -f "$zsync_config" ]] && source "$zsync_config"
local filter=""
while IFS=$'\t' read -r name value src; do
if [[ "$src" == "local" && "$value" == "subvols" ]]; then
filter+="${name}/|"
elif [[ "$src" == "local" && "$value" == "all" ]]; then
filter+="${name}|"
fi
done < <(rssh "zfs get -H -o name,value,source -t filesystem,volume $ZPUSHTAG")
filter="#${filter%|}"
log "Generierter ZFS-Filter: $filter"
local combined="miyagi-${SOURCEHOSTNAME}-$(hostname)-${ZPUSHTAG}"
local spoolfile="/tmp/${combined}_checkzfs"
local spooldest="${SPOOL_MAXAGE_CHECKZFS}_${combined}_checkzfs"
log "Führe checkzfs aus..."
{
echo "<<<local>>>"
"$checkzfs_cmd" \
--source "${SOURCEUSER}@${SOURCEHOST}:${SOURCEPORT}" \
--filter "$filter" \
--replicafilter "^${ZFSTRGT}" \
--prefix "$checkzfs_prefix" \
--threshold "$checkzfs_max_age" \
--maxsnapshots "$checkzfs_max_snapshot_count" \
--output checkmk
} > "$spoolfile"
if rscp "$spoolfile" "${SOURCEUSER}@${SOURCEHOST}:/var/lib/check_mk_agent/spool/${spooldest}"; then
log "CheckZFS-Daten an $SOURCEHOST gesendet: $spooldest"
else
log "FEHLER: CheckZFS-Daten konnten nicht übertragen werden"
fi
rm -f "$spoolfile"
}
send_checkzfs_external() {
if [[ "${ECHECKZFS,,}" != "yes" ]]; then
log "Externer CheckZFS deaktiviert."
return
fi
local config="/etc/bashclub/${SOURCEHOST}.conf"
if [[ ! -f "$config" ]]; then
log "Konfigurationsdatei fehlt: $config"
return 1
fi
source "$config"
get_sourcehostname # Setzt SOURCEHOSTNAME, wenn nicht vorhanden
local checkzfs_cmd="${checkzfs_cmd:-$(command -v checkzfs)}"
if [[ -z "$checkzfs_cmd" || ! -x "$checkzfs_cmd" ]]; then
log "checkzfs nicht gefunden oder nicht ausführbar – Abbruch."
return 1
fi
log "Verwende checkzfs: $checkzfs_cmd"
# Filter anhand ZFS-Tags auf dem SOURCEHOST aufbauen
local filter=""
while IFS=$'\t' read -r name value source; do
if [[ "$source" == "local" && "$value" == "subvols" ]]; then
filter+="${name}/|"
elif [[ "$source" == "local" && "$value" == "all" ]]; then
filter+="${name}|"
fi
done < <(rssh "zfs get -H -o name,value,source -t filesystem,volume $ZPUSHTAG")
filter="#${filter%|}"
log "Generierter ZFS-Filter: $filter"
local combined="miyagi-${SOURCEHOSTNAME}-$(hostname)-${ZPUSHTAG}"
local spoolfile="/tmp/${combined}_checkzfs_external"
local spooldest="${SPOOL_MAXAGE_CHECKZFS}_${combined}_checkzfs_external"
log "Führe checkzfs aus..."
{
echo "<<<local>>>"
"$checkzfs_cmd" \
--source ${SOURCEUSER}@"${SOURCEHOST}:${SOURCEPORT}" \
--filter "$filter" \
--replicafilter "^${ZFSTRGT}" \
--prefix "$checkzfs_prefix" \
--threshold "$checkzfs_max_age" \
--maxsnapshots "$checkzfs_max_snapshot_count" \
--output checkmk
} > "$spoolfile"
if scp -P "$ECHECKZFS_PORT" "$spoolfile" ${ECHECKZFS_USER}@"$ECHECKZFS_HOST":/var/lib/check_mk_agent/spool/"$spooldest"; then
log "Spool-Datei erfolgreich übertragen an $ECHECKZFS_HOST:$spooldest"
else
log "Fehler beim Übertragen der Spool-Datei an $ECHECKZFS_HOST"
fi
rm -f "$spoolfile"
}
wait() {
echo
echo "###########################################"
echo "# Das Backup startet in 60 Sekunden... #"
echo "# Drücke [Enter], um sofort fortzufahren, #"
echo "# oder warte, um automatisch zu starten. #"
echo "###########################################"
read -t 60 -p "Fortfahren (Enter drücken) oder warten... " input
if [[ $? -eq 0 ]]; then
log "Press ENTER to continue."
else
log "No press ENTER, wait for 60 Sec.."
sleep 60
fi
}
set_dynamic_route() {
local target="$SOURCEHOST"
local gateway="$DDNS_GATEWAY"
local dns_server="$DDNS_SERVER"
local current_ip
if [[ -z "$target" || -z "$gateway" ]]; then
log "Fehler: SOURCEHOST oder DDNS_GATEWAY nicht gesetzt"
return 1
fi
# Ist SOURCEHOST bereits eine IPv4- oder IPv6-Adresse?
if [[ "$target" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ || "$target" == *:* ]]; then
current_ip="$target"
log "SOURCEHOST ist bereits eine IP-Adresse: $current_ip"
else
log "Setze temporäre Route zu DNS-Server $dns_server via $gateway..."
case "$(uname -s)" in
Linux)
ip route | grep -q "^$dns_server" || \
ip route add "$dns_server" via "$gateway"
;;
FreeBSD)
route -n get "$dns_server" >/dev/null 2>&1 || \
route add -host "$dns_server" "$gateway"
;;
esac
log "Löse IP von $target über DNS $dns_server..."
current_ip=$(dig +short @"$dns_server" "$target" | awk 'NF {print; exit}')
if [[ -z "$current_ip" ]]; then
log "Fehler: Konnte IP für $target nicht auflösen"
return 1
fi
fi
log "Ermittelte IP: $current_ip – setze Route via $gateway..."
case "$(uname -s)" in
Linux)
ip route | grep -q "^$current_ip" || \
ip route add "$current_ip" via "$gateway"
;;
FreeBSD)
route -n get "$current_ip" >/dev/null 2>&1 || \
route add -host "$current_ip" "$gateway"
;;
*)
log "Nicht unterstütztes Betriebssystem: $(uname -s)"
return 1
;;
esac
log "Route erfolgreich gesetzt: $current_ip via $gateway"
}
# Main execution:
if [[ $# -eq 0 ]]; then
if [[ -n "$CONFIG_FILE" ]]; then
wait
log "Running full backup using configuration file: $CONFIG_FILE"
if [[ "${DYNROUTE,,}" == "yes" ]]; then
log "DYNROUTE ist aktiviert – setze dynamische Route für $SOURCEHOST..."
set_dynamic_route
else
log "DYNROUTE ist deaktiviert oder nicht gesetzt."
fi
write_zsync_config
run_zsync
if [[ "${BACKUPSERVER,,}" == "yes" ]]; then
log "BACKUPSERVER ist aktiviert, führe Backup aus..."
run_pbs_backup
run_maintenance
run_hooks post-backup
else
log "BACKUPSERVER ist nicht aktiviert (BACKUPSERVER=$BACKUPSERVER), überspringe Backup."
fi
run_remote_updates
if [[ "${EPIGGYBACK,,}" == "yes" ]]; then
send_piggyback_external
fi
if [[ "${ECHECKZFS,,}" == "yes" ]]; then
send_checkzfs_external
fi
shutdown_now
else
usage
fi
else
# Funktionen die keine Config brauchen
CONFIG_FREE_FUNCTIONS=(help detect_os detect_host_type zfs_replace wait_replace set_wol_g_enabled run_scrub_stop)
case "$1" in
help)
declare -F | awk '{print $3}' | grep -v "^_" | grep -v "^main$"
;;
*)
if declare -F "$1" >/dev/null 2>&1; then
config_required=true
for fn in "${CONFIG_FREE_FUNCTIONS[@]}"; do
[[ "$1" == "$fn" ]] && config_required=false && break
done
if [[ "$config_required" == true && -z "${SOURCEHOST:-}" ]]; then
log "Funktion '$1' benötigt eine Config. Aufruf: $0 -c /etc/miyagi-backup/backup.conf $1"
exit 1
fi
"$@"
else
log "Function $1 not found."
usage
fi
;;
esac
fi