14 Commits

Author SHA1 Message Date
patrick a15a7bad54 2.0 2025-07-24 12:52:46 +02:00
patrick 849b5e9570 miyagi-backup.sh aktualisiert 2025-07-17 11:11:11 +02:00
patrick 1221a6cf23 config.example aktualisiert
REPLEXCLUDE=$BACKUPEXCLUDE
2025-07-15 15:13:32 +02:00
patrick 0db8fee019 config.example aktualisiert
PBSPORT
2025-07-15 01:49:56 +02:00
patrick 564939053a miyagi-check.sh aktualisiert 2025-07-15 01:49:11 +02:00
patrick 127431689a miyagi-convert.sh aktualisiert
fix BACKUPEXCLUDE
2025-07-15 00:01:56 +02:00
patrick f999ff6a87 miyagi-check.sh aktualisiert 2025-07-14 18:19:37 +02:00
patrick 8e121cb75c miyagi-convert.sh aktualisiert 2025-07-14 17:49:44 +02:00
patrick 2329aa7377 miyag-convert.sh hinzugefügt 2025-07-08 23:56:52 +02:00
patrick 7eb712fae4 miyagi-backup.sh aktualisiert 2025-07-02 15:55:49 +02:00
patrick 93780dad1b miyagi-check.sh aktualisiert 2025-07-02 15:43:47 +02:00
patrick 802b7b8c63 config.example aktualisiert 2025-07-02 15:07:17 +02:00
patrick e6842f1de8 miyagi-backup.sh aktualisiert 2025-06-30 14:55:07 +02:00
patrick 25220beae2 miyagi-backup.sh aktualisiert
ssh -p "$SSHPORT""$SOURCEHOST" hostname)
2025-06-30 13:37:35 +02:00
4 changed files with 492 additions and 344 deletions
+3 -2
View File
@@ -4,7 +4,7 @@ UPDATES='yes' # Proxmox VE und PBS Updates nach dem Lauf
SHUTDOWN='no' # System nach Ausführung herunterfahren? SHUTDOWN='no' # System nach Ausführung herunterfahren?
# Quelle (Proxmox VE System, das gesichert wird) # Quelle (Proxmox VE System, das gesichert wird)
SSHPORT='22' # SSH-Port, normalerweise 22 SOURCEPORT='22' # SSH-Port, normalerweise 22
SOURCEHOST='192.168.50.200' # IP des Quell-Proxmox-Servers SOURCEHOST='192.168.50.200' # IP des Quell-Proxmox-Servers
# Replikation (ZFS) # Replikation (ZFS)
@@ -24,10 +24,11 @@ ZPUSHFILTER='hourly|daily|weekly|monthly' # Weitere Filter (leer lassen oder Mus
BACKUPSERVER='no' # Backup via PBS aktivieren? BACKUPSERVER='no' # Backup via PBS aktivieren?
MAINTDAY='7' # Wartungstag (1=Mo, 7=So) MAINTDAY='7' # Wartungstag (1=Mo, 7=So)
PBSHOST='192.168.50.199' # IP des Proxmox Backup Servers PBSHOST='192.168.50.199' # IP des Proxmox Backup Servers
PBSPORT='22'
BACKUPSTORE='backup' # Datastore auf Quell-Proxmox BACKUPSTORE='backup' # Datastore auf Quell-Proxmox
BACKUPSTOREPBS='backup' # Datastore auf PBS BACKUPSTOREPBS='backup' # Datastore auf PBS
BACKUPEXCLUDE='10,3252,3253,3254' # VM/CT-IDs, die vom Backup ausgeschlossen sind BACKUPEXCLUDE='10,3252,3253,3254' # VM/CT-IDs, die vom Backup ausgeschlossen sind
REPLEXCLUDE="$BACKUPEXCLUDE" # Diese auch von Replikation ausschließen REPLEXCLUDE=$BACKUPEXCLUDE # Diese auch von Replikation ausschließen
# Zusätzliche Monitoring-Ziele # Zusätzliche Monitoring-Ziele
# External Piggyback Host # External Piggyback Host
+337 -316
View File
@@ -7,401 +7,423 @@ SCRIPT_NAME=$(basename "$0")
LOGFILE="/var/log/${SCRIPT_NAME%.sh}.log" LOGFILE="/var/log/${SCRIPT_NAME%.sh}.log"
log() { log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOGFILE" echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOGFILE"
} }
usage() { usage() {
echo "Usage:" echo "Usage:"
echo " $0 -c /path/to/config # Full backup run" echo " $0 -c /path/to/config # Full backup run"
echo " $0 [function] # Run individual function" echo " $0 [function] # Run individual function"
echo " $0 help # Show available functions" echo " $0 help # Show available functions"
exit 1 exit 1
} }
CONFIG_FILE="" CONFIG_FILE=""
while getopts "c:" opt; do while getopts "c:" opt; do
case "$opt" in case "$opt" in
c) CONFIG_FILE="$OPTARG" ;; c) CONFIG_FILE="$OPTARG" ;;
*) usage ;; *) usage ;;
esac esac
done done
# Bei Einzelaufruf muss Konfiguration geladen sein
shift $((OPTIND - 1)) shift $((OPTIND - 1))
if [[ -n "${CONFIG_FILE:-}" ]]; then if [[ -n "${CONFIG_FILE:-}" ]]; then
if [[ ! -f "$CONFIG_FILE" ]]; then if [[ ! -f "$CONFIG_FILE" ]]; then
log "ERROR: Configuration file not found: $CONFIG_FILE" 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 exit 1
fi fi
source "$CONFIG_FILE" if ! bash -n "$CONFIG_FILE"; then
log "Syntax error in configuration file $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
PBSHOST
BACKUPSTORE
BACKUPSTOREPBS
BACKUPEXCLUDE
REPLEXCLUDE
)
MISSING_VARS=() REQUIRED_VARS=(
for var in "${REQUIRED_VARS[@]}"; do SOURCEPORT BACKUPSERVER ZSYNC MAINTDAY SHUTDOWN UPDATES
#if [[ -z "${!var:-}" ]]; then SOURCEHOST ZFSROOT ZFSSECOND ZFSTRGT ZPUSHTAG ZPUSHMINKEEP ZPUSHKEEP ZPUSHLABEL
if ! declare -p "$var" &>/dev/null || [[ -z "${!var}" ]]; then PBSHOST BACKUPSTORE BACKUPSTOREPBS BACKUPEXCLUDE REPLEXCLUDE
MISSING_VARS+=("$var") )
fi
done
if [[ "${#MISSING_VARS[@]}" -ne 0 ]]; then MISSING_VARS=()
log "Fehlende Konfigurationsvariablen:" for var in "${REQUIRED_VARS[@]}"; do
for var in "${MISSING_VARS[@]}"; do if [[ -z "${!var:-}" ]]; then
log " - $var" MISSING_VARS+=("$var")
done
log "Breche ab bitte Konfigurationsdatei prüfen."
exit 1
else
log "Alle erforderlichen Konfigurationsvariablen sind gesetzt."
fi 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 fi
# Funktionen rssh() { ssh -p "$SOURCEPORT" "$@"; }
remote_ssh() { rscp() { scp -P "$SOURCEPORT" "$@"; }
ssh -p "$SSHPORT" "$@"
}
remote_scp() { get_sourcehostname() {
scp -P "$SSHPORT" "$@" if [[ -z "${SOURCEHOSTNAME:-}" ]]; then
log "SOURCEHOSTNAME is empty, retrieving via SSH from $SOURCEHOST..."
SOURCEHOSTNAME=$(rssh "$SOURCEHOST" hostname)
log "Detected SOURCEHOSTNAME: $SOURCEHOSTNAME"
fi
} }
set_wol_g_enabled() { set_wol_g_enabled() {
log "Pruefe, ob ethtool installiert ist..." log "Checking if ethtool is installed..."
if ! command -v ethtool >/dev/null 2>&1; then if ! command -v ethtool >/dev/null 2>&1; then
log "ethtool ist nicht installiert, versuche Installation..." log "ethtool is not installed, attempting installation..."
apt update && apt install -y ethtool || { apt update && apt install -y ethtool || {
log "Fehler: ethtool konnte nicht installiert werden." log "Error: ethtool could not be installed."
return 1 return 1
} }
else else
log "ethtool ist bereits installiert." log "ethtool is already installed."
fi fi
log "Pruefe und setze Wake-on-LAN (WOL) auf 'g' nur bei Interfaces mit statischer IP..." 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 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 if [[ -e "/sys/class/net/$iface/device" ]]; then
log "Bearbeite physisches Interface: $iface" log "Processing physical interface: $iface"
# Aktuellen WOL-Status pruefen current_wol=$(ethtool "$iface" 2>/dev/null | awk '/Wake-on:/ {print $2}')
current_wol=$(ethtool "$iface" 2>/dev/null | awk '/Wake-on:/ {print $2}') if [[ "$current_wol" != "g" ]]; then
if [[ "$current_wol" != "g" ]]; then log "Setting WOL to 'g' for $iface..."
log "Setze WOL auf 'g' fuer $iface..." ethtool -s "$iface" wol g || log "Error setting WOL on $iface"
ethtool -s "$iface" wol g || log "Fehler beim Setzen von WOL auf $iface" else
else log "WOL already set to 'g' for $iface"
log "WOL ist bereits korrekt auf 'g' fuer $iface" fi
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 -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
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..."
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
sed -i "/^\s*iface\s\+$iface\s\+inet\s\+static/a \ post-up ethtool -s $iface wol g" /etc/network/interfaces else
else log "WOL command already present in static block for $iface."
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 fi
done else
log "No static entry found for $iface, no changes made."
fi
fi
done
} }
write_zsync_config() { write_zsync_config() {
local conf_file="/etc/bashclub/$SOURCEHOST.conf" get_sourcehostname
log "Writing zsync config to $conf_file" local conf_file="/etc/bashclub/$SOURCEHOST.conf"
{ log "Writing zsync config to $conf_file"
echo "target=$ZFSTRGT" {
echo "source=root@$SOURCEHOST" echo "target=$ZFSTRGT"
echo "sshport=$SSHPORT" echo "source=root@$SOURCEHOST"
echo "tag=$ZPUSHTAG" echo "sshport=$SOURCEPORT"
echo "snapshot_filter=\"$ZPUSHFILTER\"" echo "tag=$ZPUSHTAG"
echo "min_keep=$ZPUSHMINKEEP" echo "snapshot_filter=\"$ZPUSHFILTER\""
echo "zfs_auto_snapshot_keep=$ZPUSHKEEP" echo "min_keep=$ZPUSHMINKEEP"
echo "zfs_auto_snapshot_label=$ZPUSHLABEL" echo "zfs_auto_snapshot_keep=$ZPUSHKEEP"
echo "zfs_auto_snapshot_engine=internal" echo "zfs_auto_snapshot_label=$ZPUSHLABEL"
echo "checkzfs_disabled=0" echo "zfs_auto_snapshot_engine=internal"
echo "checkzfs_local=0" echo "checkzfs_disabled=0"
echo "checkzfs_prefix=miyagi-$SOURCEHOSTNAME-$(hostname)-$ZPUSHTAG" echo "checkzfs_local=0"
echo "checkzfs_max_age=1500,2000" echo "checkzfs_prefix=miyagi-$SOURCEHOSTNAME-$(hostname)-$ZPUSHTAG"
echo "checkzfs_max_snapshot_count=180,200" echo "checkzfs_max_age=1500,2000"
echo "checkzfs_spool=1" echo "checkzfs_max_snapshot_count=180,200"
echo "checkzfs_spool_maxage=90000" echo "checkzfs_spool=1"
} > "$conf_file" echo "checkzfs_spool_maxage=90000"
} > "$conf_file"
} }
run_zsync() { run_zsync() {
if [[ "$ZSYNC" != "no" ]]; then if [[ "$ZSYNC" != "no" ]]; then
/usr/bin/bashclub-zsync -c "/etc/bashclub/$SOURCEHOST.conf" /usr/bin/bashclub-zsync -c "/etc/bashclub/$SOURCEHOST.conf"
else else
log "Zsync is disabled" log "Zsync is disabled."
fi fi
} }
run_remote_updates() { run_remote_updates() {
if [[ "$UPDATES" == "yes" ]]; then if [[ "${UPDATES,,}" == "yes" ]]; then
ssh "$PBSHOST" apt update && apt dist-upgrade -y log "Running updates on local system..."
apt update && apt dist-upgrade -y || log "Error during local updates"
if [[ "${BACKUPSERVER,,}" == "yes" ]]; then
log "Running updates on PBS host ($PBSHOST)..."
ssh root@"$PBSHOST" apt update && ssh root@"$PBSHOST" apt dist-upgrade -y || {
log "Error during updates on $PBSHOST"
}
else else
log "Remote updates disabled" log "PBS updates skipped (BACKUPSERVER=$BACKUPSERVER)"
fi fi
else
log "Updates disabled (UPDATES=$UPDATES)"
fi
} }
run_remote_updates() { send_piggyback() {
if [[ "$UPDATES" == "yes" ]]; then get_sourcehostname
ssh "$PBSHOST" apt update && apt dist-upgrade -y local combined_host="miyagi-${SOURCEHOSTNAME}-$(hostname)"
else local filename="90000_${combined_host}"
log "Remote updates disabled"
fi
}
send_piggyback_data() { log "Reminder: Add a host named ${combined_host} in CMK (without Agent, Piggyback enabled)!"
# Falls SOURCEHOSTNAME leer ist, ueber SSH vom Zielhost ermitteln log "Creating piggyback file: $filename"
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}" echo "<<<<${combined_host}>>>>"
/usr/bin/check_mk_agent
echo "<<<<>>>>"
} > "$filename"
log "Do nott forget to add a Host in CMK named: ${combined_host} (without Agent, Piggyback enabled)!" if rscp "$filename" "$SOURCEHOST:/var/lib/check_mk_agent/spool/"; then
log "Creating piggyback file: $filename" log "Piggyback data successfully sent to $SOURCEHOST"
else
log "ERROR: Failed to send piggyback data to $SOURCEHOST"
fi
{ rm -f "$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() { run_pbs_backup() {
if [[ -z "${SOURCEHOSTNAME:-}" ]]; then if [[ "${BACKUPSERVER,,}" != "yes" ]]; then
SOURCEHOSTNAME=$(ssh "$SOURCEHOST" hostname) log "PBS Backup übersprungen: BACKUPSERVER='$BACKUPSERVER' (muss 'yes' sein)"
fi return 0
fi
log "Running PBS vzdump job..." get_sourcehostname
log "Starte PBS Backup auf Host: $SOURCEHOST"
# PBS-Storage ggf. aktivieren # PBS-Storage prüfen und ggf. aktivieren
log "Checking if PBS storage '$BACKUPSTORE' is enabled on $SOURCEHOST..." log "Prüfe, ob PBS-Storage '$BACKUPSTORE' auf $SOURCEHOST aktiviert ist..."
if ssh root@"$SOURCEHOST" "pvesm status | grep -w '$BACKUPSTORE' | grep -q 'disabled'"; then if rssh root@"$SOURCEHOST" "pvesm status | grep -w '$BACKUPSTORE' | grep -q 'disabled'"; then
log "PBS storage '$BACKUPSTORE' is disabled. Attempting to enable...sleep 10 Sekunden" log "PBS-Storage '$BACKUPSTORE' ist deaktiviert aktiviere temporär und warte 10 Sekunden..."
ssh root@"$SOURCEHOST" "pvesm set '$BACKUPSTORE' --disable 0 && sleep 10" rssh root@"$SOURCEHOST" "pvesm set '$BACKUPSTORE' --disable 0 && sleep 10"
pbs_enabled_by_script=true 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 root@"$SOURCEHOST" 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 root@"$SOURCEHOST" vzdump \
--node "$SOURCEHOSTNAME" --storage "$BACKUPSTORE" \
--exclude "$BACKUPEXCLUDE" --mode snapshot --all 1 \
--notes-template '{{guestname}}'; then
log "Fallback-vzdump erfolgreich."
vzdump_success=true
else else
log "PBS storage '$BACKUPSTORE' is already enabled." log "FEHLER: vzdump fehlgeschlagen auch im Fallback."
pbs_enabled_by_script=false
fi fi
fi
vzdump_success=false # 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 root@"$SOURCEHOST" "pvesm set '$BACKUPSTORE' --disable 1"
fi
# Hauptversuch mit --pbs-change-detection-mode # Monitoring-Ausgabe
if ssh root@"$SOURCEHOST" vzdump --pbs-change-detection-mode metadata \ if [[ "$vzdump_success" == true ]]; then
--node "$SOURCEHOSTNAME" --storage "$BACKUPSTORE" \ echo "0 DailyPBS - Daily Backup erfolgreich" > /tmp/cmk_tmp.out
--exclude "$BACKUPEXCLUDE" --mode snapshot --all 1 \ else
--notes-template '{{guestname}}'; then echo "2 DailyPBS - Daily Backup FEHLGESCHLAGEN" > /tmp/cmk_tmp.out
fi
log "vzdump with change-detection-mode succeeded" {
vzdump_success=true echo "<<<local>>>"
cat /tmp/cmk_tmp.out
} > /tmp/90000_checkpbs
else rscp /tmp/90000_checkpbs root@"$SOURCEHOST":/var/lib/check_mk_agent/spool \
log "Fallback: vzdump with change-detection-mode failed, trying without it..." || log "Fehler beim Übertragen des Monitoring-Outputs via SCP"
if ssh root@"$SOURCEHOST" vzdump \ rm -f /tmp/cmk_tmp.out /tmp/90000_checkpbs
--node "$SOURCEHOSTNAME" --storage "$BACKUPSTORE" \ #write_pbs_status
--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 "<<<local>>>" ; 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() { run_maintenance() {
if [[ "$(date +%u)" == "$MAINTDAY" ]]; then if [[ "${BACKUPSERVER,,}" != "yes" ]]; then
log "Running maintenance..." log "PBS Backup wird übersprungen (BACKUPSERVER=$BACKUPSERVER)"
PRUNEJOB=$(ssh "$PBSHOST" proxmox-backup-manager prune-job list --output-format json-pretty | grep -m 1 "id" | cut -d'"' -f4) return
ssh root@"$PBSHOST" proxmox-backup-manager prune-job run "$PRUNEJOB" fi
ssh root@"$PBSHOST" proxmox-backup-manager garbage-collection start "$BACKUPSTOREPBS"
ssh root@"$PBSHOST" proxmox-backup-manager verify backup if [[ "$(date +%u)" == "$MAINTDAY" ]]; then
else log "Running maintenance..."
log "No maintenance scheduled for today." PRUNEJOB=$(ssh "$PBSHOST" proxmox-backup-manager prune-job list --output-format json-pretty | grep -m 1 "id" | cut -d'"' -f4)
fi 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() { run_scrub_stop() {
ssh -p "$SSHPORT" root@"$SOURCEHOST" 'for pool in $(zpool list -H -o name); do local mode="$1" # "local" oder "remote"
echo "Stoppe Scrub auf Pool: $pool" local ssh_cmd=()
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() { if [[ "$mode" == "remote" ]]; then
ssh_cmd=(ssh -p "$SOURCEPORT" root@"$SOURCEHOST")
fi
"${ssh_cmd[@]}" bash -c '
for pool in $(zpool list -H -o name); do for pool in $(zpool list -H -o name); do
echo "Stoppe Scrub auf Pool: $pool" echo "Stopping scrub on pool: $pool"
if zpool status "$pool" | grep -q "scrub in progress"; then if zpool status "$pool" | grep -q "scrub in progress"; then
if zpool scrub -s "$pool"; then if zpool scrub -s "$pool"; then
echo "Scrub auf $pool gestoppt" echo "Scrub stopped on $pool"
else
echo "Fehler beim Stoppen des Scrubs auf $pool"
fi
else else
echo "Kein aktiver Scrub auf $pool" echo "Error stopping scrub on $pool"
fi fi
else
echo "No active scrub on $pool"
fi
done done
'
} }
shutdown_if_requested() { shutdown_now() {
log "SHUTDOWN-Variable: '${SHUTDOWN:-nicht gesetzt}'" if [[ "${SHUTDOWN,,}" == "yes" ]]; then
#if [[ "${SHUTDOWN,,}" == "yes" ]]; then send_piggyback
if [[ "$(echo "$SHUTDOWN" | tr '[:upper:]' '[:lower:]')" == "yes" ]]; then send_piggyback_external
send_piggyback_data send_checkzfs_external
send_piggyback_data_external log "Shutting down now..."
log "Shutting down now..." shutdown now
shutdown now else
else log "No shutdown requested."
log "No shutdown requested." fi
fi
} }
send_piggyback_data_external() { send_piggyback_external() {
if [[ "${EPIGGYBACK,,}" != "yes" ]]; then if [[ "${EPIGGYBACK,,}" != "yes" ]]; then
log "Externer Piggyback-Export deaktiviert." log "Externer Piggyback-Export deaktiviert."
return return
fi fi
get_sourcehostname
if [[ -z "$EPIGGYBACK_HOST" || -z "$EPIGGYBACK_PORT" ]]; then log "Ermittelter SOURCEHOSTNAME: $SOURCEHOSTNAME"
log "EPIGGYBACK_HOST oder EPIGGYBACK_PORT nicht gesetzt Abbruch."
return 1
fi
local combined_host="miyagi-${SOURCEHOSTNAME}-$(hostname)" local combined_host="miyagi-${SOURCEHOSTNAME}-$(hostname)"
local filename="90000_${combined_host}_external" local spool_file="90000_${combined_host}_external"
local temp_dir
temp_dir=$(mktemp -d)
log "Erzeuge externe Piggyback-Datei: $filename" log "Erzeuge temporäre Piggyback-Datei: $temp_dir/$spool_file"
{ {
echo "<<<<${combined_host}>>>>" echo "<<<<${combined_host}>>>>"
/usr/bin/check_mk_agent /usr/bin/check_mk_agent
echo "<<<<>>>>" echo "<<<<>>>>"
} > "$filename" } > "$temp_dir/$spool_file"
if scp -P "$EPIGGYBACK_PORT" "$filename" "$EPIGGYBACK_HOST:/var/lib/check_mk_agent/spool/"; then if scp -P "$EPIGGYBACK_PORT" "$temp_dir/$spool_file" "$EPIGGYBACK_HOST:/var/lib/check_mk_agent/spool/"; then
log "Piggyback-Daten erfolgreich an $EPIGGYBACK_HOST gesendet." log "Piggyback-Daten erfolgreich an $EPIGGYBACK_HOST gesendet: $spool_file"
else else
log "Fehler beim Senden der Piggyback-Daten an $EPIGGYBACK_HOST" log "Fehler beim Übertragen der Piggyback-Daten an $EPIGGYBACK_HOST"
fi rm -rf "$temp_dir"
rm -f "$filename"
}
send_checkzfs_output_external() {
if [[ "${ECHECKZFS,,}" != "yes" ]]; then
log "Externer check_zfs-Export deaktiviert."
return
fi
if [[ -z "$ECHECKZFS_HOST" || -z "$ECHECKZFS_PORT" ]]; then
log "ECHECKZFS_HOST oder ECHECKZFS_PORT nicht gesetzt Abbruch."
return 1 return 1
fi fi
local checkfile="/tmp/90000_checkzfs_external" rm -rf "$temp_dir"
return 0
log "Führe check_zfs aus und schreibe Output nach $checkfile"
/usr/lib/nagios/plugins/check_zfs > "$checkfile" 2>&1 || log "check_zfs Befehl fehlgeschlagen"
log "Sende check_zfs-Daten an $ECHECKZFS_HOST..."
if scp -P "$ECHECKZFS_PORT" "$checkfile" "$ECHECKZFS_HOST:/var/lib/check_mk_agent/spool/"; then
log "Check_zfs-Daten erfolgreich an $ECHECKZFS_HOST gesendet."
else
log "Fehler beim Senden der Check_zfs-Daten an $ECHECKZFS_HOST"
fi
rm -f "$checkfile"
} }
send_checkzfs_external() {
if [[ "${ECHECKZFS,,}" != "yes" ]]; then
log "Externer Piggyback-Export deaktiviert."
return
fi
local config="/etc/bashclub/${SOURCEHOST}.conf"
if [[ ! -f "$config" ]]; then
log "Konfigurationsdatei fehlt: $config"
return 1
fi
source "$config"
main() { if [[ "${ECHECKZFS,,}" != "yes" ]]; then
log "Externer Check-ZFS deaktiviert (ECHECKZFS=$ECHECKZFS)"
return
fi
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 < <(ssh -p "$SOURCEPORT" "$SOURCEHOST" "zfs get -H -o name,value,source -t filesystem,volume $ZPUSHTAG")
filter="#${filter%|}"
log "Generierter ZFS-Filter: $filter"
local combined="miyagi-${SOURCEHOSTNAME}-$(hostname)"
local spoolfile="/tmp/${combined}_checkzfs_external"
local spooldest="90000_${combined}_checkzfs_external"
log "Führe checkzfs aus..."
{
echo "<<<local>>>"
"$checkzfs_cmd" \
--source root@"${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" root@"$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"
}
# Main execution:
if [[ $# -eq 0 ]]; then
if [[ -n "$CONFIG_FILE" ]]; then
log "Backup-Routine startet in 60 Sekunden..." log "Backup-Routine startet in 60 Sekunden..."
sleep 60 sleep 60
log "Starting full backup routine..." #log "Starting full backup routine..."
log "Running full backup using configuration file: $CONFIG_FILE"
SOURCEHOSTNAME=$(ssh "$SOURCEHOST" hostname)
set_wol_g_enabled
write_zsync_config write_zsync_config
run_zsync run_zsync
send_checkzfs_output_external
run_scrub_stop_local
run_scrub_stop_src
if [[ "${BACKUPSERVER,,}" == "yes" ]]; then if [[ "${BACKUPSERVER,,}" == "yes" ]]; then
log "BACKUPSERVER ist aktiviert, führe Backup aus..." log "BACKUPSERVER ist aktiviert, führe Backup aus..."
run_maintenance run_maintenance
@@ -409,24 +431,23 @@ main() {
else else
log "BACKUPSERVER ist nicht aktiviert (BACKUPSERVER=$BACKUPSERVER), überspringe Backup." log "BACKUPSERVER ist nicht aktiviert (BACKUPSERVER=$BACKUPSERVER), überspringe Backup."
fi fi
run_remote_updates run_remote_updates
run_updates shutdown_now
else
shutdown_if_requested usage
fi
}
# 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 else
main case "$1" in
help)
declare -F | awk '{print $3}' | grep -v "^_" | grep -v "^main$"
;;
*)
if declare -F "$1" >/dev/null 2>&1; then
"$@"
else
log "Function $1 not found."
usage
fi
;;
esac
fi fi
+43 -26
View File
@@ -2,6 +2,7 @@
set -euo pipefail set -euo pipefail
IFS=$'\n\t' IFS=$'\n\t'
PERMITROOT_YES_HOSTS=()
LOG() { LOG() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"
} }
@@ -9,39 +10,31 @@ LOG() {
CONFIG_FILE="${1:-}" CONFIG_FILE="${1:-}"
if [[ -z "$CONFIG_FILE" ]]; then if [[ -z "$CONFIG_FILE" ]]; then
LOG "Keine Konfigurationsdatei übergeben." LOG "Keine Konfigurationsdatei übergeben."
echo "Usage: $0 /pfad/zur/config" echo "Usage: $0 /pfad/zur/config"
exit 1 exit 1
fi fi
if [[ ! -f "$CONFIG_FILE" ]]; then if [[ ! -f "$CONFIG_FILE" ]]; then
LOG "Konfigurationsdatei nicht gefunden: $CONFIG_FILE" LOG "Konfigurationsdatei nicht gefunden: $CONFIG_FILE"
exit 1 exit 1
fi fi
if ! bash -n "$CONFIG_FILE"; then if ! bash -n "$CONFIG_FILE"; then
LOG "Syntaxfehler in der Konfigurationsdatei!" LOG "Syntaxfehler in der Konfigurationsdatei!"
exit 1 exit 1
fi fi
source "$CONFIG_FILE" source "$CONFIG_FILE"
REQUIRED_VARS=( REQUIRED_VARS=(
SSHPORT SOURCEPORT
BACKUPSERVER BACKUPSERVER
ZSYNC ZSYNC
MAINTDAY MAINTDAY
SHUTDOWN SHUTDOWN
UPDATES UPDATES
SOURCEHOST SOURCEHOST
ZFSROOT
ZFSSECOND
ZFSTRGT
ZPUSHTAG
ZPUSHMINKEEP
ZPUSHKEEP
ZPUSHLABEL
ZPUSHFILTER
PBSHOST PBSHOST
BACKUPSTORE BACKUPSTORE
BACKUPSTOREPBS BACKUPSTOREPBS
@@ -96,7 +89,7 @@ esac
check_ssh_connection() { check_ssh_connection() {
local host=$1 local host=$1
LOG "Prüfe SSH-Verbindung zu $host ..." LOG "Prüfe SSH-Verbindung zu $host ..."
if ssh -p "$SSHPORT" -o BatchMode=yes -o ConnectTimeout=5 "$host" "echo OK" 2>/dev/null | grep -q OK; then if ssh -p "$SOURCEPORT" -o BatchMode=yes -o ConnectTimeout=5 "$host" "echo OK" 2>/dev/null | grep -q OK; then
LOG " SSH-Verbindung zu $host erfolgreich." LOG " SSH-Verbindung zu $host erfolgreich."
return 0 return 0
else else
@@ -113,20 +106,19 @@ check_and_copy_ssh_key() {
LOG " Lokaler SSH-Public-Key ($keyfile) nicht gefunden!" LOG " Lokaler SSH-Public-Key ($keyfile) nicht gefunden!"
return 1 return 1
fi fi
local pubkey local pubkey
pubkey=$(<"$keyfile") pubkey=$(<"$keyfile")
LOG " Prüfe, ob SSH-Key auf $host autorisiert ist ..." LOG " Prüfe, ob SSH-Key auf $host autorisiert ist ..."
if ssh -p "$SSHPORT" "$host" "grep -qF '$pubkey' ~/.ssh/authorized_keys" 2>/dev/null; then if ssh -p "$SOURCEPORT" "$host" "grep -qF '$pubkey' ~/.ssh/authorized_keys" 2>/dev/null; then
LOG " SSH-Key ist bereits auf $host hinterlegt." LOG " SSH-Key ist bereits auf $host hinterlegt."
else else
LOG " SSH-Key nicht auf $host vorhanden." LOG " SSH-Key nicht auf $host vorhanden."
read -rp " Möchtest du den SSH-Key jetzt via ssh-copy-id übertragen? [j/N] " ans read -rp " Möchtest du den SSH-Key jetzt via ssh-copy-id übertragen? [j/N] " ans
if [[ "$ans" =~ ^[JjYy]$ ]]; then if [[ "$ans" =~ ^[JjYy]$ ]]; then
ssh-copy-id -p "$SSHPORT" "$host" ssh-copy-id -p "$SOURCEPORT" "$host"
else else
LOG " SSH-Key nicht übertragen." LOG " SSH-Key nicht übertragen."
fi fi
@@ -138,7 +130,7 @@ check_sshd_config_recommendation() {
LOG " Prüfe sshd_config auf $host bzgl. 'PermitRootLogin'..." LOG " Prüfe sshd_config auf $host bzgl. 'PermitRootLogin'..."
local current_setting local current_setting
current_setting=$(ssh -p "$SSHPORT" "$host" "grep -i '^PermitRootLogin' /etc/ssh/sshd_config" 2>/dev/null || echo "") current_setting=$(ssh -p "$SOURCEPORT" "$host" "grep -i '^PermitRootLogin' /etc/ssh/sshd_config" 2>/dev/null || echo "")
if [[ -z "$current_setting" ]]; then if [[ -z "$current_setting" ]]; then
LOG " Keine explizite 'PermitRootLogin'-Einstellung gefunden." LOG " Keine explizite 'PermitRootLogin'-Einstellung gefunden."
@@ -154,31 +146,56 @@ check_sshd_config_recommendation() {
check_pveversion() { check_pveversion() {
local host=$1 local host=$1
LOG "Prüfe PVE-Version auf $host ..." LOG "Prüfe PVE-Version auf $host ..."
if ssh -p "$SSHPORT" "$host" "command -v pveversion >/dev/null"; then if ssh -p "$SOURCEPORT" "$host" "command -v pveversion >/dev/null"; then
ssh -p "$SSHPORT" "$host" "pveversion" | while read -r line; do ssh -p "$SOURCEPORT" "$host" "pveversion" | while read -r line; do
LOG " $host: $line" LOG " $host: $line"
done done
else else
LOG " 'pveversion' ist auf $host nicht verfügbar kein Proxmox?" LOG " 'pveversion' ist auf $host nicht verfügbar kein Proxmox?"
fi fi
} }
check_pbs_version() {
local host=$1
local port=$2
LOG "Prüfe PBS-Version auf $host ..."
if ssh -p "$port" "$host" "command -v proxmox-backup-manager >/dev/null"; then
ssh -p "$port" "$host" "proxmox-backup-manager version" | while read -r line; do
LOG " $host: $line"
done
else
LOG " 'proxmox-backup-manager' ist auf $host nicht verfügbar kein PBS?"
fi
}
run_host_check() { run_host_check() {
local host=$1 local host=$1
local type=${2:-pve}
local port=$SOURCEPORT
local pbsport=$PBSPORT
LOG "" LOG ""
LOG "=== Prüfung für Host: $host ===" LOG "=== Prüfung für Host: $host (Typ: $type) ==="
if check_ssh_connection "$host"; then
if check_ssh_connection "$host" "$port"; then
check_and_copy_ssh_key "$host" check_and_copy_ssh_key "$host"
check_sshd_config_recommendation "$host" check_sshd_config_recommendation "$host"
check_pveversion "$host"
if [[ "$type" == "pve" ]]; then
check_pveversion "$host" "$port"
elif [[ "$type" == "pbs" ]]; then
check_pbs_version "$host" "$pbsport"
else
LOG " Unbekannter Host-Typ: $type"
fi
fi fi
echo "" echo ""
} }
run_host_check "$SOURCEHOST"
run_host_check "$SOURCEHOST" pve
if [[ "$BACKUPSERVER" == "yes" ]]; then if [[ "$BACKUPSERVER" == "yes" ]]; then
run_host_check "$PBSHOST" run_host_check "$PBSHOST" pbs
else else
LOG " BACKUPSERVER ist deaktiviert PBSHOST wird übersprungen." LOG " BACKUPSERVER ist deaktiviert PBSHOST wird übersprungen."
fi fi
@@ -195,7 +212,7 @@ if [[ ${#PERMITROOT_YES_HOSTS[@]} -gt 0 ]]; then
if [[ "$change_ans" =~ ^[JjYy]$ ]]; then if [[ "$change_ans" =~ ^[JjYy]$ ]]; then
for h in "${PERMITROOT_YES_HOSTS[@]}"; do for h in "${PERMITROOT_YES_HOSTS[@]}"; do
echo "Ändere sshd_config auf $h ..." echo "Ändere sshd_config auf $h ..."
ssh -p "$SSHPORT" "$h" "sed -i 's/^PermitRootLogin.*/PermitRootLogin prohibit-password/' /etc/ssh/sshd_config && systemctl reload sshd && echo '✅ sshd auf $h neu geladen.' || echo '❌ Fehler bei $h'" ssh -p "$SOURCEPORT" "$h" "sed -i 's/^PermitRootLogin.*/PermitRootLogin prohibit-password/' /etc/ssh/sshd_config && systemctl reload sshd && echo '✅ sshd auf $h neu geladen.' || echo '❌ Fehler bei $h'"
done done
else else
echo " Änderung von sshd_config übersprungen." echo " Änderung von sshd_config übersprungen."
+109
View File
@@ -0,0 +1,109 @@
#!/bin/bash
set -euo pipefail
IFS=$'\n\t'
log() {
echo "[INFO] $*"
}
error_exit() {
echo "[ERROR] $*" >&2
exit 1
}
sanitize_value() {
echo "$1" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//'
}
load_config() {
local config_file="$1"
if [[ ! -f "$config_file" ]]; then
error_exit "Konfigurationsdatei nicht gefunden: $config_file"
fi
log "Lade und bereinige Konfigurationsdatei: $config_file"
while IFS='=' read -r key value; do
# nur gültige Variablennamen parsen
if [[ "$key" =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]]; then
# Kommentar nach Wert entfernen
value="${value%%#*}"
value="$(sanitize_value "$value")"
# Variable setzen (für den write_new_config Zugriff)
eval "$key=\"$value\""
fi
done < "$config_file"
}
write_new_config() {
local out_file="$1"
cat > "$out_file" <<EOF
#Edit all Variables for best Experience
UPDATES='${UPDATES}' # Proxmox VE und PBS Updates nach dem Lauf
SHUTDOWN='${SHUTDOWN}' # System nach Ausführung herunterfahren?
# Quelle (Proxmox VE System, das gesichert wird)
SOURCEPORT='${SOURCEPORT}' # SSH-Port, normalerweise 22
SOURCEHOST='${SOURCEHOST}' # IP des Quell-Proxmox-Servers
# Replikation (ZFS)
ZFSROOT='${ZFSROOT}' # Erstes Dataset vom Quellsystem
ZFSSECOND='${ZFSSECOND}' # Optional zweites Dataset
ZFSTRGT='${ZFSTRGT}' # Zielpfad für Replikation
# ZFS Zsync Replikation
ZSYNC='${ZSYNC}' # ZSYNC aktivieren (ja/nein)
ZPUSHTAG='${ZPUSHTAG}' # Benutzer-Tag für ZFS
ZPUSHMINKEEP='${ZPUSHMINKEEP}' # Mindestens zu behaltende Snapshots
ZPUSHKEEP='${ZPUSHKEEP}' # Snapshots mit dem Tag, die behalten werden
ZPUSHLABEL='${ZPUSHLABEL}' # Suffix für Snapshot-Autoengine
ZPUSHFILTER='${ZPUSHFILTER}' # Weitere Filter (leer lassen oder Muster wie daily| weekly etc.)
# Backup mit Proxmox Backup Server
BACKUPSERVER='${BACKUPSERVER}' # Backup via PBS aktivieren?
MAINTDAY='${MAINTDAY}' # Wartungstag (1=Mo, 7=So)
PBSHOST='${PBSHOST}' # IP des Proxmox Backup Servers
BACKUPSTORE='${BACKUPSTORE}' # Datastore auf Quell-Proxmox
BACKUPSTOREPBS='${BACKUPSTOREPBS}' # Datastore auf PBS
BACKUPEXCLUDE='${BACKUPEXCLUDE}' # VM/CT-IDs, die vom Backup ausgeschlossen sind
REPLEXCLUDE=\$BACKUPEXCLUDE # Diese auch von Replikation ausschließen
# Zusätzliche Monitoring-Ziele
# External Piggyback Host
EPIGGYBACK='${EPIGGYBACK}' # Piggyback-Daten an Monitoring-Ziel senden?
EPIGGYBACK_PORT='${EPIGGYBACK_PORT}' # SSH-Port für EPIGGYBACK_HOST
EPIGGYBACK_HOST='${EPIGGYBACK_HOST}' # Monitoring-Zielhost für Piggyback
# External Checkzfs Host
ECHECKZFS='${ECHECKZFS}' # check_zfs-Output an Monitoring-Ziel senden?
ECHECKZFS_PORT='${ECHECKZFS_PORT}' # SSH-Port für ECHECKZFS_HOST
ECHECKZFS_HOST='${ECHECKZFS_HOST}' # Monitoring-Zielhost für check_zfs
EOF
}
if [[ $# -ne 1 ]]; then
echo "Usage: $0 <configfile>"
exit 1
fi
ORIGINAL="$1"
read -rp "Soll die aktuelle Datei als Backup gesichert werden (umbenennen)? (ja/nein): " RESPONSE
RESPONSE="${RESPONSE,,}"
if [[ "$RESPONSE" == "ja" || "$RESPONSE" == "j" ]]; then
BACKUPFILE="${ORIGINAL}.bak"
mv "$ORIGINAL" "$BACKUPFILE"
log "Originaldatei wurde umbenannt in: $BACKUPFILE"
CONFIG_TO_READ="$BACKUPFILE"
else
log "Keine Sicherung der Originaldatei durchgeführt. Original bleibt unverändert."
CONFIG_TO_READ="$ORIGINAL"
fi
NEWFILE="${ORIGINAL}.convert"
load_config "$CONFIG_TO_READ"
write_new_config "$NEWFILE"
log "Neue Konfiguration geschrieben in: $NEWFILE"