36 Commits

Author SHA1 Message Date
patrick 2fa79afd66 miyagi-backup.sh aktualisiert 2026-02-04 17:09:40 +01:00
patrick 15e86400f0 miyagi-backup.sh aktualisiert 2026-02-04 00:51:31 +01:00
patrick 1166a9c03e config.example aktualisiert 2026-01-07 11:46:23 +01:00
patrick c519aac1d9 config.example aktualisiert 2026-01-07 11:44:55 +01:00
patrick effc9646be miyagi-backup.sh aktualisiert 2026-01-07 11:44:25 +01:00
patrick e2960c19b4 miyagi-backup.sh aktualisiert
add function run_pbs_log_clear, last 60 days
2025-12-11 01:19:43 +01:00
patrick 7d42984bfa miyagi-backup.sh aktualisiert
PBS PORT
2025-11-26 13:26:37 +01:00
patrick f0890c61ff config.example aktualisiert 2025-11-26 13:09:03 +01:00
patrick eab882aa44 config.example aktualisiert 2025-11-26 13:08:47 +01:00
patrick 87eb3307cb miyagi-backup.sh aktualisiert 2025-08-22 23:01:50 +02:00
patrick 067fe8f1af v2.05
fix piggyback_external in main
2025-08-22 23:00:55 +02:00
patrick e2f64a4491 config.example aktualisiert 2025-08-22 16:33:16 +02:00
patrick ca8c2854a9 v2.04 2025-08-22 00:05:43 +02:00
patrick 3fbb50e2bb config.example aktualisiert 2025-08-19 01:12:13 +02:00
patrick f7b9acd73b README.md aktualisiert 2025-08-11 12:03:51 +02:00
patrick 76a081468d README.md aktualisiert 2025-08-06 01:24:11 +02:00
patrick 75534137ff miyagi-backup.sh aktualisiert 2025-08-04 00:13:17 +02:00
patrick beb53968a4 miyagi-backup.sh aktualisiert 2025-08-02 01:09:12 +02:00
patrick dcae51da9f v2.03
wait replace
2025-08-02 00:50:14 +02:00
patrick 87c2d13069 v2.02
shutdown stop by zpool replace
2025-08-02 00:36:40 +02:00
patrick 602cfe21b0 Dateien nach "/" hochladen 2025-07-30 01:31:44 +02:00
patrick 7d3e53b42a v2.01
Press ENTER to continue.
2025-07-26 02:01:58 +02:00
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
6 changed files with 1022 additions and 350 deletions
+3 -1
View File
@@ -1,13 +1,14 @@
# Beispielhafte Aufrufe
Befehl Wirkung
```
./miyagi-backup.sh -c /etc/miyagi.conf Führt den vollständigen Backup-Prozess aus
./miyagi-backup.sh help Listet alle verfügbaren Funktionen
./miyagi-backup.sh run_updates Führt nur Updates lokal aus
./miyagi-backup.sh run_pbs_backup Führt nur das vzdump-PBS-Backup aus
./miyagi-backup.sh run_maintenance Nur Wartung (Prune + GC)
./miyagi-backup.sh shutdown_if_requested Prüft ob Shutdown nötig ist
```
####
@@ -25,3 +26,4 @@ Dieses Skript überprüft eine Bash-basierte Konfigurationsdatei für ein Backup
## Nutzung:
```bash
./miyagi-check.sh /pfad/zur/config
```
+11 -6
View File
@@ -2,32 +2,37 @@
UPDATES='yes' # Proxmox VE und PBS Updates nach dem Lauf
SHUTDOWN='no' # System nach Ausführung herunterfahren?
DYNROUTE='no' #Dynroute setzen?
DDNS_GATEWAY='192.168.66.1' # Gateway
DDNS_SERVER='1.1.1.1' # Externer DNS Server
REBOOT='no' # Reboot nach Update?
# 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
# Replikation (ZFS)
ZFSROOT='rpool/data' # Erstes Dataset vom Quellsystem
ZFSSECOND='rpool-hdd/data' # Optional zweites Dataset
ZFSTRGT='rpool-ssd1/repl/pve200' # Zielpfad für Replikation
ZFSTRGT='rpool/repl/pve200' # Zielpfad für Replikation
# ZFS Zsync Replikation
ZSYNC='yes' # ZSYNC aktivieren (ja/nein)
ZPUSHTAG='bashclub:zsync-198-ssd' # Benutzer-Tag für ZFS
ZPUSHTAG='bashclub:zsync' # Benutzer-Tag für ZFS
ZPUSHMINKEEP='3' # Mindestens zu behaltende Snapshots
ZPUSHKEEP='14' # Snapshots mit dem Tag, die behalten werden
ZPUSHLABEL='zsync-rz' # Suffix für Snapshot-Autoengine
ZPUSHLABEL='zsync' # Suffix für Snapshot-Autoengine
ZPUSHFILTER='hourly|daily|weekly|monthly' # Weitere Filter (leer lassen oder Muster wie daily| weekly etc.)
# Backup mit Proxmox Backup Server
BACKUPSERVER='no' # Backup via PBS aktivieren?
MAINTDAY='7' # Wartungstag (1=Mo, 7=So)
PBSHOST='192.168.50.199' # IP des Proxmox Backup Servers
PBSPORT='22'
BACKUPSTORE='backup' # Datastore auf Quell-Proxmox
BACKUPSTOREPBS='backup' # Datastore auf PBS
BACKUPEXCLUDE='10,3252,3253,3254' # VM/CT-IDs, die vom Backup ausgeschlossen sind
REPLEXCLUDE="$BACKUPEXCLUDE" # Diese auch von Replikation ausschließen
BACKUPEXCLUDE='9999,99988' # VM/CT-IDs, die vom Backup ausgeschlossen sind
REPLEXCLUDE=$BACKUPEXCLUDE # Diese auch von Replikation ausschließen
# Zusätzliche Monitoring-Ziele
# External Piggyback Host
+479 -195
View File
@@ -25,8 +25,6 @@ while getopts "c:" opt; do
*) usage ;;
esac
done
# Bei Einzelaufruf muss Konfiguration geladen sein
shift $((OPTIND - 1))
if [[ -n "${CONFIG_FILE:-}" ]]; then
@@ -35,117 +33,134 @@ if [[ -n "${CONFIG_FILE:-}" ]]; then
exit 1
fi
if ! bash -n "$CONFIG_FILE"; then
log "Syntaxfehler in Konfigurationsdatei $CONFIG_FILE"
if ! bash -n "$CONFIG_FILE"; then
log "Syntax error in configuration file $CONFIG_FILE"
exit 1
fi
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
SOURCEPORT BACKUPSERVER ZSYNC MAINTDAY SHUTDOWN UPDATES
SOURCEHOST ZFSROOT ZFSSECOND ZFSTRGT ZPUSHTAG ZPUSHMINKEEP ZPUSHKEEP ZPUSHLABEL
PBSHOST BACKUPSTORE BACKUPSTOREPBS BACKUPEXCLUDE REPLEXCLUDE DYNROUTE REBOOT DDNS_GATEWAY
)
if [[ "${DYNROUTE,,}" == "yes" ]]; then
REQUIRED_VARS+=(DDNS_GATEWAY)
fi
MISSING_VARS=()
for var in "${REQUIRED_VARS[@]}"; do
#if [[ -z "${!var:-}" ]]; then
if ! declare -p "$var" &>/dev/null || [[ -z "${!var}" ]]; then
if [[ -z "${!var:-}" ]]; then
MISSING_VARS+=("$var")
fi
done
if [[ "${#MISSING_VARS[@]}" -ne 0 ]]; then
log "Fehlende Konfigurationsvariablen:"
if [[ "${#MISSING_VARS[@]}" -gt 0 ]]; then
log "Missing configuration variables:"
for var in "${MISSING_VARS[@]}"; do
log " - $var"
done
log "Breche ab bitte Konfigurationsdatei prüfen."
log "Aborting — please check your configuration file."
exit 1
else
log "Alle erforderlichen Konfigurationsvariablen sind gesetzt."
log "All required configuration variables are set."
fi
fi
# Funktionen
remote_ssh() {
ssh -p "$SSHPORT" "$@"
rssh() { ssh -p "$SOURCEPORT" "$@"; }
rscp() { scp -P "$SOURCEPORT" "$@"; }
get_sourcehostname() {
if [[ -z "${SOURCEHOSTNAME:-}" ]]; then
log "SOURCEHOSTNAME is empty, retrieving via SSH from $SOURCEHOST..."
SOURCEHOSTNAME=$(rssh "$SOURCEHOST" hostname)
log "Detected SOURCEHOSTNAME: $SOURCEHOSTNAME"
fi
}
remote_scp() {
scp -P "$SSHPORT" "$@"
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() {
log "Pruefe, ob ethtool installiert ist..."
log "Checking if ethtool is installed..."
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 || {
log "Fehler: ethtool konnte nicht installiert werden."
log "Error: ethtool could not be installed."
return 1
}
else
log "ethtool ist bereits installiert."
log "ethtool is already installed."
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
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}')
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"
log "Setting WOL to 'g' for $iface..."
ethtool -s "$iface" wol g || log "Error setting WOL on $iface"
else
log "WOL ist bereits korrekt auf 'g' fuer $iface"
log "WOL already set to 'g' for $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..."
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-Befehl fuer $iface ist bereits im statischen Block vorhanden."
log "WOL command already present in static block for $iface."
fi
else
log "Kein statischer Eintrag fuer $iface gefunden, keine Aenderung vorgenommen."
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=root@$SOURCEHOST"
echo "sshport=$SSHPORT"
echo "sshport=$SOURCEPORT"
echo "tag=$ZPUSHTAG"
echo "snapshot_filter=\"$ZPUSHFILTER\""
echo "min_keep=$ZPUSHMINKEEP"
@@ -163,41 +178,102 @@ write_zsync_config() {
}
run_zsync() {
write_zsync_config
if [[ "$ZSYNC" != "no" ]]; then
/usr/bin/bashclub-zsync -c "/etc/bashclub/$SOURCEHOST.conf"
else
log "Zsync is disabled"
log "Zsync is disabled."
fi
}
run_remote_updates() {
if [[ "$UPDATES" == "yes" ]]; then
ssh "$PBSHOST" apt update && apt dist-upgrade -y
if [[ "${UPDATES,,}" == "yes" ]]; then
log "Running updates on local system..."
TIMESTAMP=$(date +"%Y-%m-%d_%H-%M-%S")
SNAPSHOT_TAG="pve-update-via-miyagi"
MAX_SNAPSHOTS=5
ZFS_DATASETS=("rpool/ROOT" "rpool/pveconf")
REBOOT_FLAG="${REBOOT^^}"
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" >> "$LOGFILE"
}
# Prüfen, ob Updates verfügbar sind
if ! apt update | tee -a "$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
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=$(echo "$old_snaps" | wc -l)
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 "$LOGFILE"; then
log_msg "ERROR" "Fehler während dist-upgrade"
return 1
fi
log_msg "INFO" "Starte autoremove"
apt autoremove -y | tee -a "$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 "Remote updates disabled"
log_msg "INFO" "Update abgeschlossen. Kein Neustart erforderlich."
fi
# PBS-Host-Update
if [[ "${BACKUPSERVER,,}" == "yes" ]]; then
log "Running updates on PBS host ($PBSHOST)..."
ssh -p $PBSPORT root@"$PBSHOST" apt update && ssh -p $PBSPORT root@"$PBSHOST" apt dist-upgrade -y || {
log "Error during updates on $PBSHOST"
}
else
log "PBS updates skipped (BACKUPSERVER=$BACKUPSERVER)"
fi
else
log "Updates disabled (UPDATES=$UPDATES)"
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
send_piggyback() {
get_sourcehostname
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 "Reminder: Add a host named ${combined_host} in CMK (without Agent, Piggyback enabled)!"
log "Creating piggyback file: $filename"
{
@@ -206,227 +282,435 @@ send_piggyback_data() {
echo "<<<<>>>>"
} > "$filename"
if scp -P "$SSHPORT" "$filename" "$SOURCEHOST:/var/lib/check_mk_agent/spool/"; then
if rscp "$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
rm -f "$filename"
}
run_pbs_log_clear() {
log "Running clear LOG on PBS host ($PBSHOST)..."
if ! ssh -p "$PBSPORT" root@"$PBSHOST" \
find /var/log/proxmox-backup/tasks/ -type f -mtime +60 -delete; then
log "Error clearing PBS LOG on $PBSHOST"
fi
}
run_pbs_backup() {
if [[ -z "${SOURCEHOSTNAME:-}" ]]; then
SOURCEHOSTNAME=$(ssh "$SOURCEHOST" hostname)
if [[ "${BACKUPSERVER,,}" != "yes" ]]; then
log "PBS Backup übersprungen: BACKUPSERVER='$BACKUPSERVER' (muss 'yes' sein)"
return 0
fi
log "Running PBS vzdump job..."
get_sourcehostname
log "Starte PBS Backup auf Host: $SOURCEHOST"
# 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-Storage prüfen und ggf. aktivieren
log "Prüfe, ob PBS-Storage '$BACKUPSTORE' auf $SOURCEHOST aktiviert ist..."
if rssh root@"$SOURCEHOST" "pvesm status | grep -w '$BACKUPSTORE' | grep -q 'disabled'"; then
log "PBS-Storage '$BACKUPSTORE' ist deaktiviert aktiviere temporär und warte 10 Sekunden..."
rssh root@"$SOURCEHOST" "pvesm set '$BACKUPSTORE' --disable 0 && sleep 10"
pbs_enabled_by_script=true
else
log "PBS storage '$BACKUPSTORE' is already enabled."
log "PBS-Storage '$BACKUPSTORE' ist bereits aktiviert."
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 \
# 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 with change-detection-mode succeeded"
log "vzdump (Change Detection) erfolgreich."
vzdump_success=true
else
log "Fallback: vzdump with change-detection-mode failed, trying without it..."
log "vzdump (Change Detection) fehlgeschlagen versuche Fallback ohne Change Detection..."
if ssh root@"$SOURCEHOST" vzdump \
if rssh root@"$SOURCEHOST" vzdump \
--node "$SOURCEHOSTNAME" --storage "$BACKUPSTORE" \
--exclude "$BACKUPEXCLUDE" --mode snapshot --all 1 \
--notes-template '{{guestname}}'; then
log "Fallback vzdump succeeded"
log "Fallback-vzdump erfolgreich."
vzdump_success=true
else
log "ERROR: vzdump failed even after fallback"
log "FEHLER: vzdump fehlgeschlagen auch im Fallback."
fi
fi
# PBS-Storage wieder deaktivieren, wenn zuvor aktiviert und erfolgreich
# PBS-Storage ggf. wieder deaktivieren
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"
log "Deaktiviere temporär aktiviertes PBS-Storage '$BACKUPSTORE' auf $SOURCEHOST..."
rssh root@"$SOURCEHOST" "pvesm set '$BACKUPSTORE' --disable 1"
fi
# Monitoring-Output für Checkmk
# Monitoring-Ausgabe
if [[ "$vzdump_success" == true ]]; then
echo "0 DailyPBS - Daily Backup" > /tmp/cmk_tmp.out
echo "0 DailyPBS - Daily Backup erfolgreich" > /tmp/cmk_tmp.out
else
echo "2 DailyPBS - Daily Backup FAILED" > /tmp/cmk_tmp.out
echo "2 DailyPBS - Daily Backup FEHLGESCHLAGEN" > /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"
{
echo "<<<local>>>"
cat /tmp/cmk_tmp.out
} > /tmp/90000_checkpbs
rscp /tmp/90000_checkpbs root@"$SOURCEHOST":/var/lib/check_mk_agent/spool \
|| log "Fehler beim Übertragen des Monitoring-Outputs via SCP"
rm -f /tmp/cmk_tmp.out /tmp/90000_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=$(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
PRUNEJOB=$(ssh -p $PBSPORT "$PBSHOST" proxmox-backup-manager prune-job list --output-format json-pretty | grep -m 1 "id" | cut -d'"' -f4)
ssh -p $PBSPORT root@"$PBSHOST" proxmox-backup-manager prune-job run "$PRUNEJOB"
ssh -p $PBSPORT root@"$PBSHOST" proxmox-backup-manager garbage-collection start "$BACKUPSTOREPBS"
ssh -p $PBSPORT 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 mode="$1" # "local" oder "remote"
local ssh_cmd=()
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
echo "Stoppe Scrub auf Pool: $pool"
echo "Stopping scrub on pool: $pool"
if zpool status "$pool" | grep -q "scrub in progress"; 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"
echo "Error stopping scrub on $pool"
fi
else
echo "Kein aktiver Scrub auf $pool"
echo "No active scrub on $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..."
write_pbs_status() {
local spool_file="/var/lib/check_mk_agent/spool/90000_checkpbs_local"
local repo="$BACKUPSTOREPBS"
local tmpfile
tmpfile=$(mktemp)
if ! command -v proxmox-backup-client >/dev/null 2>&1; then
log "proxmox-backup-client nicht installiert PBS-Status-Ausgabe übersprungen."
return
fi
echo "<<<local>>>" > "$tmpfile"
local now
now=$(date +%s)
# === Schwellenwert(e) parsen ===
local warn_threshold=0
local crit_threshold=86400 # Default kritisch ab 24h
if [[ -n "${PBSBACKUP_STATUS:-}" ]]; then
IFS=',' read -r t1 t2 <<< "$PBSBACKUP_STATUS"
if [[ "$t1" =~ ^[0-9]+$ && -z "$t2" ]]; then
# Nur ein Wert vorhanden
crit_threshold=$t1
log "Verwende einfachen Schwellenwert: CRIT=${crit_threshold}s"
elif [[ "$t1" =~ ^[0-9]+$ && "$t2" =~ ^[0-9]+$ ]]; then
warn_threshold=$t1
crit_threshold=$t2
log "Verwende gestaffelte Schwellenwerte: WARN=${warn_threshold}s, CRIT=${crit_threshold}s"
else
log "WARNUNG: Ungültiges PBSBACKUP_STATUS-Format Fallback: CRIT=${crit_threshold}s"
fi
fi
# Backup-Daten extrahieren
rssh root@"$SOURCEHOST" proxmox-backup-client list --repository "$repo" --output-format json 2>/dev/null | \
grep -E '"backup-id"|"backup-type"|"backup-time"' | \
sed -E 's/[",]//g; s/^ *//' > "$tmpfile.json"
local backup_type="" backup_id="" backup_time=""
while read -r line; do
case "$line" in
backup-type:*)
backup_type="${line#*: }"
;;
backup-id:*)
backup_id="${line#*: }"
;;
backup-time:*)
backup_time="${line#*: }"
if [[ -n "$backup_type" && -n "$backup_id" && -n "$backup_time" ]]; then
local age status timestamp msg
age=$((now - backup_time))
timestamp=$(date -d "@$backup_time" "+%Y-%m-%d %H:%M:%S")
if (( age < warn_threshold )); then
status=0
elif (( age < crit_threshold )); then
status=1
else
status=2
fi
msg="$backup_type/$backup_id last backup $timestamp (age: $((age / 3600))h)"
echo "$status PBS_${backup_type}_${backup_id} - $msg" >> "$tmpfile"
# Reset
backup_type="" backup_id="" backup_time=""
fi
;;
esac
done < "$tmpfile.json"
rm -f "$tmpfile.json"
mv "$tmpfile" "$spool_file"
log "PBS Backup Status lokal geschrieben: $spool_file"
}
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
log "Shutting down now...in 60sec"
sleep 60
shutdown now
else
log "No shutdown requested."
fi
}
send_piggyback_data_external() {
send_piggyback_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
write_zsync_config
get_sourcehostname
log "Ermittelter SOURCEHOSTNAME: $SOURCEHOSTNAME"
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}>>>>"
/usr/bin/check_mk_agent
echo "<<<<>>>>"
} > "$filename"
} > "$temp_dir/$spool_file"
if scp -P "$EPIGGYBACK_PORT" "$filename" "$EPIGGYBACK_HOST:/var/lib/check_mk_agent/spool/"; then
log "Piggyback-Daten erfolgreich an $EPIGGYBACK_HOST gesendet."
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: $spool_file"
else
log "Fehler beim Senden der Piggyback-Daten an $EPIGGYBACK_HOST"
fi
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."
log "Fehler beim Übertragen der Piggyback-Daten an $EPIGGYBACK_HOST"
rm -rf "$temp_dir"
return 1
fi
local checkfile="/tmp/90000_checkzfs_external"
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"
rm -rf "$temp_dir"
return 0
}
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() {
log "Backup-Routine startet in 60 Sekunden..."
if [[ "${ECHECKZFS,,}" != "yes" ]]; then
log "Externer Check-ZFS deaktiviert (ECHECKZFS=$ECHECKZFS)"
return
fi
write_zsync_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 < <(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)-${ZPUSHTAG}"
local spoolfile="/tmp/${combined}_checkzfs_external"
local spooldest="120000_${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"
}
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
log "Starting full backup routine..."
fi
}
set_dynamic_route() {
local ddns_hostname="$SOURCEHOST"
local gateway="$DDNS_GATEWAY"
local dns_server="$DDNS_SERVER"
SOURCEHOSTNAME=$(ssh "$SOURCEHOST" hostname)
if [[ -z "$ddns_hostname" || -z "$gateway" ]]; then
log "Fehler: SOURCEHOST oder DDNS_GATEWAY nicht gesetzt"
return 1
fi
log "Setze temporäre Route zu DNS-Server $dns_server via $gateway..."
if ! ip route | grep -q "^$dns_server"; then
ip route add "$dns_server" via "$gateway" \
&& log "Route zu $dns_server via $gateway gesetzt" \
|| log "Fehler beim Setzen der DNS-Route"
else
log "Route zu $dns_server bereits vorhanden"
fi
log "Löse IP von $ddns_hostname über DNS $dns_server..."
CURRENT_IP=$(dig +short @"$dns_server" "$ddns_hostname")
if [[ -z "$CURRENT_IP" ]]; then
log "Fehler: Konnte IP für $ddns_hostname nicht auflösen"
return 1
fi
log "Ermittelte IP: $CURRENT_IP setze Route via $gateway..."
if ! ip route | grep -q "^$CURRENT_IP"; then
ip route add "$CURRENT_IP" via "$gateway" \
&& log "Route erfolgreich gesetzt: $CURRENT_IP via $gateway" \
|| log "Fehler beim Setzen der Route zu $CURRENT_IP"
else
log "Route zu $CURRENT_IP bereits vorhanden"
fi
}
# 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
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
run_maintenance
run_pbs_log_clear
else
log "BACKUPSERVER ist nicht aktiviert (BACKUPSERVER=$BACKUPSERVER), überspringe Backup."
fi
run_remote_updates
run_updates
if [[ "${EPIGGYBACK,,}" == "yes" ]]; then
send_piggyback_external
fi
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
if [[ "${ECHECKZFS,,}" == "yes" ]]; then
send_checkzfs_external
fi
shutdown_now
else
usage
fi
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
+43 -26
View File
@@ -2,6 +2,7 @@
set -euo pipefail
IFS=$'\n\t'
PERMITROOT_YES_HOSTS=()
LOG() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"
}
@@ -9,39 +10,31 @@ LOG() {
CONFIG_FILE="${1:-}"
if [[ -z "$CONFIG_FILE" ]]; then
LOG "Keine Konfigurationsdatei übergeben."
LOG "Keine Konfigurationsdatei übergeben."
echo "Usage: $0 /pfad/zur/config"
exit 1
fi
if [[ ! -f "$CONFIG_FILE" ]]; then
LOG "Konfigurationsdatei nicht gefunden: $CONFIG_FILE"
LOG "Konfigurationsdatei nicht gefunden: $CONFIG_FILE"
exit 1
fi
if ! bash -n "$CONFIG_FILE"; then
LOG "Syntaxfehler in der Konfigurationsdatei!"
LOG "Syntaxfehler in der Konfigurationsdatei!"
exit 1
fi
source "$CONFIG_FILE"
REQUIRED_VARS=(
SSHPORT
SOURCEPORT
BACKUPSERVER
ZSYNC
MAINTDAY
SHUTDOWN
UPDATES
SOURCEHOST
ZFSROOT
ZFSSECOND
ZFSTRGT
ZPUSHTAG
ZPUSHMINKEEP
ZPUSHKEEP
ZPUSHLABEL
ZPUSHFILTER
PBSHOST
BACKUPSTORE
BACKUPSTOREPBS
@@ -96,7 +89,7 @@ esac
check_ssh_connection() {
local host=$1
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."
return 0
else
@@ -113,20 +106,19 @@ check_and_copy_ssh_key() {
LOG " Lokaler SSH-Public-Key ($keyfile) nicht gefunden!"
return 1
fi
local pubkey
pubkey=$(<"$keyfile")
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."
else
LOG " SSH-Key nicht auf $host vorhanden."
read -rp " Möchtest du den SSH-Key jetzt via ssh-copy-id übertragen? [j/N] " ans
if [[ "$ans" =~ ^[JjYy]$ ]]; then
ssh-copy-id -p "$SSHPORT" "$host"
ssh-copy-id -p "$SOURCEPORT" "$host"
else
LOG " SSH-Key nicht übertragen."
fi
@@ -138,7 +130,7 @@ check_sshd_config_recommendation() {
LOG " Prüfe sshd_config auf $host bzgl. 'PermitRootLogin'..."
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
LOG " Keine explizite 'PermitRootLogin'-Einstellung gefunden."
@@ -154,31 +146,56 @@ check_sshd_config_recommendation() {
check_pveversion() {
local host=$1
LOG "Prüfe PVE-Version auf $host ..."
if ssh -p "$SSHPORT" "$host" "command -v pveversion >/dev/null"; then
ssh -p "$SSHPORT" "$host" "pveversion" | while read -r line; do
if ssh -p "$SOURCEPORT" "$host" "command -v pveversion >/dev/null"; then
ssh -p "$SOURCEPORT" "$host" "pveversion" | while read -r line; do
LOG " $host: $line"
done
else
LOG " 'pveversion' ist auf $host nicht verfügbar kein Proxmox?"
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() {
local host=$1
local type=${2:-pve}
local port=$SOURCEPORT
local pbsport=$PBSPORT
LOG ""
LOG "=== Prüfung für Host: $host ==="
if check_ssh_connection "$host"; then
LOG "=== Prüfung für Host: $host (Typ: $type) ==="
if check_ssh_connection "$host" "$port"; then
check_and_copy_ssh_key "$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
echo ""
}
run_host_check "$SOURCEHOST"
run_host_check "$SOURCEHOST" pve
if [[ "$BACKUPSERVER" == "yes" ]]; then
run_host_check "$PBSHOST"
run_host_check "$PBSHOST" pbs
else
LOG " BACKUPSERVER ist deaktiviert PBSHOST wird übersprungen."
fi
@@ -195,7 +212,7 @@ if [[ ${#PERMITROOT_YES_HOSTS[@]} -gt 0 ]]; then
if [[ "$change_ans" =~ ^[JjYy]$ ]]; then
for h in "${PERMITROOT_YES_HOSTS[@]}"; do
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
else
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"
+255
View File
@@ -0,0 +1,255 @@
#!/bin/bash
# Author: (C) 2025 Patrick Perlbach <patrick@perlbach24.de>
# --- Konfiguration und Initiale Variablen ---
FORCE_YES=0
DRY_RUN=0
CONFIG_FILE=""
# --- Hilfsfunktionen ---
usage() {
cat <<EOF
Usage: $0 -c <config> [--yes] [--dry-run]
-c <file> Pfad zur Konfigurationsdatei (Pflicht)
--yes Automatische Ausführung ohne Rückfrage
--dry-run Zeigt nur, was gemacht würde
--help Diese Hilfe anzeigen
EOF
exit 1
}
run_or_echo() {
if [[ "$DRY_RUN" == "1" ]]; then
echo "[DRY-RUN] $*"
else
eval "$@"
fi
}
parse_args() {
while [[ $# -gt 0 ]]; do
case "$1" in
-c) CONFIG_FILE="$2"; shift 2 ;;
--yes) FORCE_YES=1; shift ;;
--dry-run) DRY_RUN=1; shift ;;
--help) usage ;;
*) echo "[ERROR] Unbekannter Parameter: $1"; usage ;;
esac
done
[[ -z "$CONFIG_FILE" || ! -f "$CONFIG_FILE" ]] && {
echo "[ERROR] Konfigurationsdatei fehlt oder ungültig!"
exit 1
}
source "$CONFIG_FILE"
}
confirm() {
if [[ "$FORCE_YES" -eq 1 ]]; then
return 0
fi
while true; do
read -rp "$1 [j/N]: " yn
case $yn in
[JjYy]*) return 0 ;;
[Nn]*|"") return 1 ;;
*) echo "Bitte j (ja) oder n (nein) eingeben." ;;
esac
done
}
install_cmk() {
echo "[INFO] Lade cmk.deb herunter..."
run_or_echo "curl -fsSL \"$CMK_URL\" -o \"$CMK_FILE\""
echo "[INFO] Installiere cmk.deb..."
run_or_echo "sudo apt install -y \"$CMK_FILE\""
}
install_fix_interface_names() {
echo "[INFO] Installiere Fix für Interface-Namen..."
run_or_echo "wget -O /tmp/debian-fix-interface-names.sh \"$FIX_INTERFACE_NAMES_URL\""
run_or_echo "chmod +x /tmp/debian-fix-interface-names.sh"
run_or_echo "bash /tmp/debian-fix-interface-names.sh"
}
install_miyagi_scripts() {
echo "[INFO] Lade Miyagi-Backup-Skripte..."
run_or_echo "wget -O miyagi-backup.sh \"$MIYAGI_BACKUP_URL\""
run_or_echo "wget -O miyagi-check.sh \"$MIYAGI_CHECK_URL\""
run_or_echo "wget -O miyagi-convert.sh \"$MIYAGI_CONVERT_URL\""
run_or_echo "mkdir -p /root/miyagi-backup"
run_or_echo "mv miyagi-backup.sh miyagi-check.sh miyagi-convert.sh /root/miyagi-backup/"
run_or_echo "chmod +x /root/miyagi-backup/*.sh"
}
install_checkmk_plugins() {
echo "[INFO] Installiere disk_smart_info Plugin..."
run_or_echo "curl -fsSL \"$DISK_SMART_INFO_URL\" -o /usr/lib/check_mk_agent/plugins/disk_smart_info"
run_or_echo "chmod +x /usr/lib/check_mk_agent/plugins/disk_smart_info"
}
install_cleansnaps() {
echo "[INFO] Installiere cleansnaps Skript..."
run_or_echo "wget -O /usr/local/bin/cleansnaps \"$CLEANSNAPS_URL\""
run_or_echo "chmod +x /usr/local/bin/cleansnaps"
}
install_check_snapshot_age() {
echo "[INFO] Installiere check-snapshot-age..."
run_or_echo "wget -O /usr/local/bin/check-snapshot-age \"$CHECK_SNAPSHOT_AGE_URL\""
run_or_echo "chmod +x /usr/local/bin/check-snapshot-age"
}
install_checkzfs() {
echo "[INFO] Installiere checkzfs..."
run_or_echo "wget -O /usr/local/bin/checkzfs \"$CHECKZFS_URL\""
run_or_echo "chmod +x /usr/local/bin/checkzfs"
}
check_ssh() {
echo "[INFO] Teste SSH-Verbindung zu $SOURCEHOST ..."
if ssh -p "$SOURCEPORT" -o BatchMode=yes -o ConnectTimeout=5 root@"$SOURCEHOST" "echo Verbindung erfolgreich" 2>/dev/null; then
echo "[OK] SSH-Verbindung über Schlüssel funktioniert."
else
echo "[WARN] Keine Schlüsselbasierte SSH-Verbindung möglich oder abgelehnt."
echo "[HINWEIS] Eine Passwortabfrage erfolgt jetzt dies ist normal."
if ssh -p "$SOURCEPORT" root@"$SOURCEHOST" "echo Verbindung erfolgreich"; then
echo "[OK] SSH-Verbindung per Passwort erfolgreich."
if confirm "Möchtest du den lokalen SSH-Schlüssel auf $SOURCEHOST kopieren (per ssh-copy-id)?"; then
run_or_echo "ssh-copy-id -p \"$SOURCEPORT\" root@\"$SOURCEHOST\""
echo "[INFO] Bitte erneut starten, um die Schlüsselverbindung zu nutzen."
exit 0
else
echo "[HINWEIS] Es wird weiterhin Passwortabfrage benötigt."
fi
else
echo "[FEHLER] SSH-Verbindung nicht möglich bitte Zugang prüfen!"
exit 1
fi
fi
}
select_zfs_pools() {
echo "[INFO] Lade ZFS-Datasets vom Remote-Host..."
LOCAL_HOSTNAME=$(hostname -s)
REMOTE_HOSTNAME=$(ssh -p "$SOURCEPORT" root@"$SOURCEHOST" "hostname -s")
TAG_KEY="bashclub:miyagi-${REMOTE_HOSTNAME}-${LOCAL_HOSTNAME}"
TAG_VALUE="subvols"
# Hole alle relevanten Pool-Namen
mapfile -t pools < <(ssh -p "$SOURCEPORT" root@"$SOURCEHOST" \
"zfs list -H -o name" | \
grep -E '/(vm|subvol)-' | \
grep -viE 'repl|replica' | \
sed 's:/[^/]*$::' | \
sort -u)
if [[ ${#pools[@]} -eq 0 ]]; then
echo "[WARN] Keine geeigneten Datasets gefunden!"
return
fi
local options=()
local tagged_pools=()
for pool in "${pools[@]}"; do
local tag status
tag=$(ssh -p "$SOURCEPORT" root@"$SOURCEHOST" zfs get -H -o value "$TAG_KEY" "$pool" 2>/dev/null)
if [[ "$tag" == "$TAG_VALUE" ]]; then
status="on"
tagged_pools+=("$pool")
else
status="off"
fi
options+=("$pool" "$([[ "$status" == "on" ]] && echo 'TAGGED' || echo 'untagged')" "$status")
done
local selected
selected=$(whiptail --title "ZFS Pools auswählen" \
--checklist "Wähle Datasets zum Taggen aus:" 20 78 12 \
"${options[@]}" \
3>&1 1>&2 2>&3)
if [[ $? -ne 0 ]]; then
echo "[INFO] Auswahl abgebrochen."
return
fi
local selected_array=()
eval "selected_array=($selected)"
echo "[INFO] Verarbeite Tagging für ausgewählte Pools:"
for ((i = 0; i < ${#options[@]}; i += 3)); do
local pool="${options[i]}"
local prev_status="${options[i+2]}"
local is_selected=0
for sel in "${selected_array[@]}"; do
if [[ "$sel" == "$pool" ]]; then
is_selected=1
break
fi
done
if [[ "$is_selected" -eq 1 && "$prev_status" == "off" ]]; then
echo " → Tagge $pool"
run_or_echo ssh -p "$SOURCEPORT" root@"$SOURCEHOST" zfs set "$TAG_KEY=$TAG_VALUE" "$pool"
elif [[ "$is_selected" -eq 0 && "$prev_status" == "on" ]]; then
echo " → Entferne Tag von $pool"
run_or_echo ssh -p "$SOURCEPORT" root@"$SOURCEHOST" zfs inherit "$TAG_KEY" "$pool"
fi
done
}
main() {
# URLs und Dateipfade als Variablen, ggf. in Kopfbereich auslagern
CMK_URL="https://nc.sysops.de/index.php/s/YofRT5LBfX5ZDQs/download/cmk.deb"
CMK_FILE="/tmp/cmk.deb"
FIX_INTERFACE_NAMES_URL="https://raw.githubusercontent.com/bashclub/trmm-scripts/refs/heads/main/debian-fix-interface-names.sh"
MIYAGI_BACKUP_URL="https://gitea.perlbach24.de/scripte/miyagi-backup/raw/branch/main/miyagi-backup.sh"
MIYAGI_CHECK_URL="https://gitea.perlbach24.de/scripte/miyagi-backup/raw/branch/main/miyagi-check.sh"
MIYAGI_CONVERT_URL="https://gitea.perlbach24.de/scripte/miyagi-backup/raw/branch/main/miyagi-convert.sh"
DISK_SMART_INFO_URL="https://raw.githubusercontent.com/bashclub/checkmk-smart/main/disk_smart_info.py"
CLEANSNAPS_URL="https://raw.githubusercontent.com/bashclub/zfs-housekeeping/refs/heads/main/cleansnaps.sh"
CHECK_SNAPSHOT_AGE_URL="https://gitea.perlbach24.de/scripte/check-zfs-replication/raw/branch/main/check-snapshot-age"
CHECKZFS_URL="https://gitea.perlbach24.de/scripte/check-zfs-replication/raw/branch/main/checkzfs.py"
echo "[INFO] Starte Installation mit Konfig: $CONFIG_FILE"
install_cmk
install_fix_interface_names
install_miyagi_scripts
install_checkmk_plugins
install_cleansnaps
install_check_snapshot_age
install_checkzfs
check_ssh
select_zfs_pools
echo "[OK] Installation abgeschlossen."
}
# Start
parse_args "$@"
main