6 Commits
Author SHA1 Message Date
patrickandClaude Sonnet 5 4d050cbedf dev: nur v3-Inhalt auf Root-Ebene
Legacy-Root-Dateien (miyagi-check.sh, miyagi-convert.sh, README,
testing/), sowie v1-alt/v2-alt Referenzkopien und VERGLEICH.md aus dev
entfernt. v3/config.example, v3/DEVLOG.md, v3/hooks.d, v3/miyagi-backup.sh,
v3/miyagi-install.sh, v3/miyagi-update.sh auf Root-Ebene verschoben.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NqeQQpk62QnLSwzQ3JumkM
2026-09-09 12:51:40 +02:00
patrickandClaude Sonnet 5 14019487b3 v3: set_dynamic_route() FreeBSD-tauglich machen
set_dynamic_route() unterstuetzt jetzt Linux (ip route) und FreeBSD
(route), erkennt SOURCEHOST als bereits aufgeloeste IP-Adresse, nutzt
lokale statt globale CURRENT_IP-Variable.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NqeQQpk62QnLSwzQ3JumkM
2026-09-09 12:48:49 +02:00
sysops 0141cdc4c7 miyagi-check.sh: Emoji aus LOG-Meldungen entfernen 2026-07-14 13:40:30 +02:00
sysops cbef884db8 Root: neue Hilfsskripte, config.example erweitern, DEVLOG einchecken
miyagi-check.sh_new (erweiterte Prüfliste inkl. Piggyback/CheckZFS-Hosts),
miyagi-convert.sh, miyagi-install.sh neu. config.example: DYNROUTE/DDNS_GATEWAY/
DDNS_SERVER/REBOOT ergänzt, ungenutzte ZFSROOT/ZFSSECOND entfernt.
2026-07-14 13:40:04 +02:00
sysops d83c3113f8 testing/: bisherige Testversion inkl. Erweiterungen einchecken
Erstmalig getrackt (war bisher lokal ungetrackt). Enthält gegenüber dem
Root-Stand: PBSPORT, DYNROUTE/DDNS-Konfig, zfs_replace()/wait_replace()
für Resilver-Abwarten vor Shutdown, BatchMode SSH, write_zsync_config()-
Anpassungen (Snapshot-Label-Präfix, checkzfs_spool_maxage).
2026-07-14 13:39:51 +02:00
sysops 1619f22f18 v3: konsolidierte miyagi-backup Version aus v1-alt/v2-alt
Neue Arbeitskopie v3/ mit Fixes: LOGFILE-Scope-Bug, PBSPORT-Quoting,
detect_host_type()/OS-Erkennung, FreeBSD-Unterstützung, Pre-Shutdown-Hooks,
write_pbs_status(), run_remote_updates() aufgeteilt in lokale/PBS-Updates,
disable_pve_notifications(). v1-alt/v2-alt als unveränderte Referenzkopien
der bisherigen Root- bzw. testing/-Stände beigelegt.
2026-07-14 13:39:28 +02:00
9 changed files with 941 additions and 779 deletions

No files matched your search

+195
View File
@@ -0,0 +1,195 @@
# v3 – Dev Log
## 2026-06-19 00:56 – 00:58 (1m)
**Beschreibung:** Claude Code Session
**Projekt:** miyagi-backup
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- config.example | 2 +-
---
## 2026-06-19 01:01 – 01:05 (4m)
**Beschreibung:** Claude Code Session
**Projekt:** v3
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- config.example | 2 +-
---
## 2026-06-19 01:06 – 01:07 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** v3
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- config.example | 2 +-
---
## 2026-06-19 01:26 – 01:28 (2m)
**Beschreibung:** Claude Code Session
**Projekt:** v3
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- config.example | 2 +-
---
## 2026-06-19 01:32 – 01:33 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** v3
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- config.example | 2 +-
---
## 2026-06-19 01:40 – 01:42 (2m)
**Beschreibung:** Claude Code Session
**Projekt:** v3
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- config.example | 2 +-
---
## 2026-06-19 01:44 – 01:47 (2m)
**Beschreibung:** Claude Code Session
**Projekt:** v3
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- config.example | 2 +-
---
## 2026-06-19 01:48 – 01:48 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** v3
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- config.example | 2 +-
---
## 2026-06-19 01:49 – 02:08 (18m)
**Beschreibung:** Claude Code Session
**Projekt:** v3
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- config.example | 2 +-
---
## 2026-06-19 02:08 – 02:08 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** v3
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- config.example | 2 +-
---
## 2026-06-19 02:09 – 02:10 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** v3
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- config.example | 2 +-
---
## 2026-06-19 02:11 – 02:15 (3m)
**Beschreibung:** Claude Code Session
**Projekt:** v3
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- config.example | 2 +-
---
## 2026-06-19 02:16 – 02:16 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** v3
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- config.example | 2 +-
---
## 2026-07-07 – Pre-Shutdown-Hooks + Installpfade
**Feature:** `run_pre_shutdown_hooks()` eingebaut, aufgerufen in `shutdown_now()` nach Piggyback/CheckZFS, vor 60s-Sleep.
**Hook-Verzeichnis:** `/etc/miyagi-backup/hooks.d/pre-shutdown/`
- Verzeichnis fehlt oder leer → still übersprungen
- Nur `*.sh` mit `chmod +x` werden ausgeführt
- Alphabetisch sortiert
- Hook-Fehler → WARNUNG im Log, Shutdown läuft weiter
**Installpfade festgelegt:**
- Skripte: `/opt/miyagi-backup/`
- Konfigs: `/etc/miyagi-backup/`
- Hooks: `/etc/miyagi-backup/hooks.d/pre-shutdown/`
- Symlink: `/usr/local/bin/miyagi-backup` → `/opt/miyagi-backup/miyagi-backup.sh`
---
## 2026-07-16 14:13 – 14:13 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** v3
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- miyagi-check.sh | 100 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
---
## 2026-07-16 14:16 – 14:48 (32m)
**Beschreibung:** Claude Code Session
**Projekt:** v3
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- miyagi-check.sh | 100 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
---
## 2026-07-16 14:49 – 14:51 (1m)
**Beschreibung:** Claude Code Session
**Projekt:** v3
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- miyagi-check.sh | 100 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
---
-29
View File
@@ -1,29 +0,0 @@
# 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
```
####
# miyagi-check.sh
Dieses Skript überprüft eine Bash-basierte Konfigurationsdatei für ein Backup-System:
## Funktionen:
- Validiert alle benötigten Variablen
- Testet SSH-Verbindungen zu `SOURCEHOST` und optional `PBSHOST`
- Prüft und installiert bei Bedarf den lokalen SSH-Key (`~/.ssh/id_rsa.pub`)
- Erkennt unsichere `PermitRootLogin yes` Einstellungen
- Bietet automatische Umstellung auf `prohibit-password` an
## Nutzung:
```bash
./miyagi-check.sh /pfad/zur/config
```
+18 -14
View File
@@ -2,46 +2,50 @@
UPDATES='yes' # Proxmox VE und PBS Updates nach dem Lauf UPDATES='yes' # Proxmox VE und PBS Updates nach dem Lauf
SHUTDOWN='no' # System nach Ausführung herunterfahren? SHUTDOWN='no' # System nach Ausführung herunterfahren?
DYNROUTE='no' #Dynroute setzen? DYNROUTE='yes' # DYNroute ja oder nein?
DDNS_GATEWAY='192.168.66.1' # Gateway DDNS_GATEWAY='192.168.66.1' # DYNDNS Gateway
DDNS_SERVER='1.1.1.1' # Externer DNS Server DDNS_SERVER='1.1.1.1' #DNS Server extern
REBOOT='no' # Reboot nach Update? REBOOT='no' # Reboot nach Update?
# Quelle (Proxmox VE System, das gesichert wird) # Quelle (Proxmox VE System, das gesichert wird)
SOURCEPORT='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
SOURCEUSER='root' # SSH-User für SOURCEHOST
# Replikation (ZFS) # Replikation (ZFS)
ZFSROOT='rpool/data' # Erstes Dataset vom Quellsystem ZFSTRGT='rpool-ssd1/repl/pve200' # Zielpfad für Replikation
ZFSSECOND='rpool-hdd/data' # Optional zweites Dataset
ZFSTRGT='rpool/repl/pve200' # Zielpfad für Replikation
# ZFS Zsync Replikation # ZFS Zsync Replikation
ZSYNC='yes' # ZSYNC aktivieren (ja/nein) ZSYNC='yes' # ZSYNC aktivieren (ja/nein)
ZPUSHTAG='bashclub:zsync' # Benutzer-Tag für ZFS ZPUSHTAG='bashclub:zsync-198-ssd' # Benutzer-Tag für ZFS
ZPUSHMINKEEP='3' # Mindestens zu behaltende Snapshots ZPUSHMINKEEP='3' # Mindestens zu behaltende Snapshots
ZPUSHKEEP='14' # Snapshots mit dem Tag, die behalten werden ZPUSHKEEP='14' # Snapshots mit dem Tag, die behalten werden
ZPUSHLABEL='zsync' # Suffix für Snapshot-Autoengine ZPUSHLABEL='zsync-rz' # Suffix für Snapshot-Autoengine
ZPUSHFILTER='hourly|daily|weekly|monthly' # Weitere Filter (leer lassen oder Muster wie daily| weekly etc.) ZPUSHFILTER='hourly|daily|weekly|monthly' # Weitere Filter (leer lassen oder Muster wie daily| weekly etc.)
# Backup mit Proxmox Backup Server # Backup mit Proxmox Backup Server
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' PBSPORT='22' # SSH-Port PBS
PBSUSER='root' # SSH-User für PBSHOST
PBSBACKUP_STATUS='86400' # Warnung ab 24h (86400s)
PBS_REPO_USER='root@pam' # PBS-User oder API-Token-ID (z.B. 'user@pbs!tokenname') für write_pbs_status
PBS_PASSWORD='' # Passwort oder API-Token-Secret zu PBS_REPO_USER (write_pbs_status)
BACKUPSTORE='backup' # Datastore auf Quell-Proxmox BACKUPSTORE='backup' # Datastore auf Quell-Proxmox
BACKUPSTOREPBS='backup' # Datastore auf PBS BACKUPSTOREPBS='backup' # Datastore auf PBS
PBS_NAMESPACE='none' # Namespace auf PBS, setzen wenn verfügbar sonst none! BACKUPEXCLUDE='10,3252,3253,3254' # VM/CT-IDs, die vom Backup ausgeschlossen sind
BACKUPEXCLUDE='9999,99988' # 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
EPIGGYBACK='no' # Piggyback-Daten an Monitoring-Ziel senden? EPIGGYBACK='no' # Piggyback-Daten an Monitoring-Ziel senden?
EPIGGYBACK_PORT='222' # SSH-Port für EPIGGYBACK_HOST EPIGGYBACK_PORT='222' # SSH-Port für EPIGGYBACK_HOST
EPIGGYBACK_HOST='192.168.66.10' # Monitoring-Zielhost für Piggyback EPIGGYBACK_HOST='192.168.66.10' # Monitoring-Zielhost für Piggyback
EPIGGYBACK_USER='root' # SSH-User für EPIGGYBACK_HOST
# External Checkzfs Host # External Checkzfs Host
ECHECKZFS='no' # check_zfs-Output an Monitoring-Ziel senden? ECHECKZFS='no' # CheckZFS-Daten an externen Monitoring-Host senden?
ECHECKZFS_PORT='2222' # SSH-Port für ECHECKZFS_HOST ECHECKZFS_PORT='2222' # SSH-Port für ECHECKZFS_HOST
ECHECKZFS_HOST='192.168.50.210' # Monitoring-Zielhost für check_zfs ECHECKZFS_HOST='192.168.50.210' # Monitoring-Zielhost für check_zfs
ECHECKZFS_USER='root' # SSH-User für ECHECKZFS_HOST
+34
View File
@@ -0,0 +1,34 @@
#!/usr/bin/env bash
# Beispiel Pre-Shutdown-Hook für miyagi-backup
#
# Ablage: /etc/miyagi-backup/hooks.d/pre-shutdown/
# Ausführung: chmod +x <hookname>.sh
# Reihenfolge: alphabetisch (01-foo.sh, 02-bar.sh, ...)
# Fehlerverhalten: Exit != 0 erzeugt WARNUNG im Log, Shutdown läuft weiter
#
# Dieser Hook wird vor dem 60s-Countdown in shutdown_now() ausgeführt.
# Hinweis: eigene sleep-Aufrufe addieren sich zum 60s-Countdown dazu.
# ── Beispiel 1: Monitoring-Agent vor Shutdown reporten ───────────────────────
#
# if [ -x /usr/local/bin/patchmon-agent ]; then
# /usr/local/bin/patchmon-agent report
# fi
# ── Beispiel 2: CheckMK-Agent aktualisieren ───────────────────────────────────
#
# if [ -x /usr/bin/cmk-agent-update ]; then
# /usr/bin/cmk-agent-update
# fi
# ── Beispiel 3: Dienst neu starten (nur wenn installiert) ────────────────────
#
# if systemctl list-unit-files wazuh-agent.service &>/dev/null; then
# systemctl restart wazuh-agent.service
# fi
# ── Beispiel 4: Eigenes Skript aufrufen ───────────────────────────────────────
#
# /opt/myscript/notify-shutdown.sh
exit 0
+328 -247
View File
@@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
set -uo pipefail set -uo pipefail
IFS=$'\n\t' IFS=$'\n\t'
@@ -6,6 +6,24 @@ PATH="/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin"
SCRIPT_NAME=$(basename "$0") SCRIPT_NAME=$(basename "$0")
LOGFILE="/var/log/${SCRIPT_NAME%.sh}.log" LOGFILE="/var/log/${SCRIPT_NAME%.sh}.log"
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() { log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOGFILE" echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOGFILE"
} }
@@ -40,29 +58,15 @@ if [[ -n "${CONFIG_FILE:-}" ]]; then
source "$CONFIG_FILE" source "$CONFIG_FILE"
: "${CHECKZFS_DISABLED:=0}"
: "${CHECKZFS_LOCAL:=0}"
: "${CHECKZFS_MAX_AGE:=1500,2000}"
: "${CHECKZFS_MAX_SNAPSHOT_COUNT:=180,200}"
: "${CHECKZFS_SPOOL:=1}"
: "${CHECKZFS_SPOOL_MAXAGE:=90000}"
: "${SPOOL_DIR:=/var/lib/check_mk_agent/spool}"
: "${SPOOL_PREFIX_PBS:=120000}"
: "${SPOOL_PREFIX_PIGGYBACK:=100000}"
: "${SPOOL_PREFIX_PIGGYBACK_EXTERNAL:=100000}"
: "${SPOOL_PREFIX_CHECKZFS_EXTERNAL:=120000}"
: "${SPOOL_CLEANUP_DAYS:=7}"
REQUIRED_VARS=( REQUIRED_VARS=(
SOURCEPORT BACKUPSERVER ZSYNC MAINTDAY SHUTDOWN UPDATES SOURCEPORT BACKUPSERVER ZSYNC MAINTDAY SHUTDOWN UPDATES
SOURCEHOST ZFSROOT ZFSSECOND ZFSTRGT ZPUSHTAG ZPUSHMINKEEP ZPUSHKEEP ZPUSHLABEL SOURCEHOST ZFSTRGT ZPUSHTAG ZPUSHMINKEEP ZPUSHKEEP ZPUSHLABEL
PBSHOST BACKUPSTORE BACKUPSTOREPBS BACKUPEXCLUDE REPLEXCLUDE DYNROUTE REBOOT DDNS_GATEWAY PBSHOST PBSPORT BACKUPSTORE BACKUPSTOREPBS BACKUPEXCLUDE REPLEXCLUDE DYNROUTE REBOOT
) )
if [[ "${DYNROUTE,,}" == "yes" ]]; then if [[ "${DYNROUTE,,}" == "yes" ]]; then
REQUIRED_VARS+=(DDNS_GATEWAY) REQUIRED_VARS+=(DDNS_GATEWAY DDNS_SERVER)
fi fi
MISSING_VARS=() MISSING_VARS=()
for var in "${REQUIRED_VARS[@]}"; do for var in "${REQUIRED_VARS[@]}"; do
@@ -83,13 +87,29 @@ fi
fi fi
fi fi
rssh() { ssh -p "$SOURCEPORT" "$@"; } SOURCEUSER="${SOURCEUSER:-root}"
rscp() { scp -P "$SOURCEPORT" "$@"; } PBSUSER="${PBSUSER:-root}"
EPIGGYBACK_USER="${EPIGGYBACK_USER:-root}"
ECHECKZFS_USER="${ECHECKZFS_USER:-root}"
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() { get_sourcehostname() {
if [[ -z "${SOURCEHOSTNAME:-}" ]]; then if [[ -z "${SOURCEHOSTNAME:-}" ]]; then
log "SOURCEHOSTNAME is empty, retrieving via SSH from $SOURCEHOST..." log "SOURCEHOSTNAME is empty, retrieving via SSH from $SOURCEHOST..."
SOURCEHOSTNAME=$(rssh "$SOURCEHOST" hostname) SOURCEHOSTNAME=$(rssh hostname)
log "Detected SOURCEHOSTNAME: $SOURCEHOSTNAME" log "Detected SOURCEHOSTNAME: $SOURCEHOSTNAME"
fi fi
} }
@@ -127,6 +147,11 @@ wait_replace() {
} }
set_wol_g_enabled() { 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..." log "Checking if ethtool is installed..."
if ! command -v ethtool >/dev/null 2>&1; then if ! command -v ethtool >/dev/null 2>&1; then
@@ -173,21 +198,21 @@ write_zsync_config() {
log "Writing zsync config to $conf_file" log "Writing zsync config to $conf_file"
{ {
echo "target=$ZFSTRGT" echo "target=$ZFSTRGT"
echo "source=root@$SOURCEHOST" echo "source=${SOURCEUSER}@${SOURCEHOST}"
echo "sshport=$SOURCEPORT" echo "sshport=$SOURCEPORT"
echo "tag=$ZPUSHTAG" echo "tag=$ZPUSHTAG"
echo "snapshot_filter=\"$ZPUSHFILTER\"" echo "snapshot_filter=\"$ZPUSHFILTER\""
echo "min_keep=$ZPUSHMINKEEP" echo "min_keep=$ZPUSHMINKEEP"
echo "zfs_auto_snapshot_keep=$ZPUSHKEEP" echo "zfs_auto_snapshot_keep=$ZPUSHKEEP"
echo "zfs_auto_snapshot_label=\"$ZPUSHLABEL\"" echo "zfs_auto_snapshot_label=zfs-auto-snap_$ZPUSHLABEL"
echo "zfs_auto_snapshot_engine=\"internal\"" echo "zfs_auto_snapshot_engine=internal"
echo "checkzfs_disabled=$CHECKZFS_DISABLED" echo "checkzfs_disabled=0"
echo "checkzfs_local=$CHECKZFS_LOCAL" echo "checkzfs_local=0"
echo "checkzfs_prefix=miyagi-$SOURCEHOSTNAME-$(hostname)-$ZPUSHTAG" echo "checkzfs_prefix=miyagi-$SOURCEHOSTNAME-$(hostname)-$ZPUSHTAG"
echo "checkzfs_max_age=$CHECKZFS_MAX_AGE" echo "checkzfs_max_age=1500,2000"
echo "checkzfs_max_snapshot_count=$CHECKZFS_MAX_SNAPSHOT_COUNT" echo "checkzfs_max_snapshot_count=180,200"
echo "checkzfs_spool=$CHECKZFS_SPOOL" echo "checkzfs_spool=1"
echo "checkzfs_spool_maxage=$CHECKZFS_SPOOL_MAXAGE" echo "checkzfs_spool_maxage=100000"
} > "$conf_file" } > "$conf_file"
} }
@@ -200,83 +225,132 @@ run_zsync() {
fi fi
} }
disable_pve_notifications() {
if [[ "$(hostname -I 2>/dev/null)" == *"$SOURCEHOST"* || "$(hostname)" == "$SOURCEHOST" ]]; then
log "Lokaler Host ist SOURCEHOST – Notification-Deaktivierung wird hier übersprungen."
return
fi
if [[ "$(detect_host_type)" == "pve" ]]; then
log "PVE erkannt – deaktiviere Notification-Target mail-to-root und default-matcher (keine lokalen Mails an root)."
pvesh set /cluster/notifications/endpoints/sendmail/mail-to-root --disable 1 >/dev/null 2>&1 \
|| log "WARN: Notification-Target mail-to-root konnte nicht deaktiviert werden."
pvesh set /cluster/notifications/matchers/default-matcher --disable 1 >/dev/null 2>&1 \
|| log "WARN: Notification-Matcher default-matcher konnte nicht deaktiviert werden."
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() { run_remote_updates() {
if [[ "${UPDATES,,}" == "yes" ]]; then if [[ "${UPDATES,,}" == "yes" ]]; then
log "Running updates on local system..." if [[ "$OS_TYPE" == "freebsd" ]]; then
log "FreeBSD erkannt – lokales apt-Update wird übersprungen (apt nicht verfügbar)."
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 else
log_msg "INFO" "Update abgeschlossen. Kein Neustart erforderlich." run_local_apt_updates
fi disable_pve_notifications
# 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 fi
# PBS-Host-Update läuft unabhängig vom lokalen Update-Ergebnis/Skript-Host-OS
run_pbs_apt_updates
else else
log "Updates disabled (UPDATES=$UPDATES)" log "Updates disabled (UPDATES=$UPDATES)"
fi fi
@@ -285,7 +359,7 @@ run_remote_updates() {
send_piggyback() { send_piggyback() {
get_sourcehostname get_sourcehostname
local combined_host="miyagi-${SOURCEHOSTNAME}-$(hostname)" local combined_host="miyagi-${SOURCEHOSTNAME}-$(hostname)"
local filename="${SPOOL_PREFIX_PIGGYBACK}_${combined_host}" local filename="100000_${combined_host}"
log "Reminder: Add a host named ${combined_host} in CMK (without Agent, Piggyback enabled)!" log "Reminder: Add a host named ${combined_host} in CMK (without Agent, Piggyback enabled)!"
log "Creating piggyback file: $filename" log "Creating piggyback file: $filename"
@@ -296,10 +370,8 @@ send_piggyback() {
echo "<<<<>>>>" echo "<<<<>>>>"
} > "$filename" } > "$filename"
if rscp "$filename" "$SOURCEHOST:${SPOOL_DIR}/"; then if rscp "$filename" "${SOURCEUSER}@${SOURCEHOST}:/var/lib/check_mk_agent/spool/"; then
log "Piggyback data successfully sent to $SOURCEHOST" log "Piggyback data successfully sent to $SOURCEHOST"
rssh root@"$SOURCEHOST" "find '${SPOOL_DIR}' -maxdepth 1 -name '*_${combined_host}' -not -name '$(basename "$filename")' -mtime +${SPOOL_CLEANUP_DAYS} -delete" \
|| log "WARNUNG: Aufräumen alter Piggyback-Spool-Dateien auf $SOURCEHOST fehlgeschlagen"
else else
log "ERROR: Failed to send piggyback data to $SOURCEHOST" log "ERROR: Failed to send piggyback data to $SOURCEHOST"
fi fi
@@ -310,14 +382,18 @@ send_piggyback() {
run_pbs_log_clear() { run_pbs_log_clear() {
log "Running clear LOG on PBS host ($PBSHOST)..." log "Running clear LOG on PBS host ($PBSHOST)..."
if ! ssh -p "$PBSPORT" root@"$PBSHOST" \ if ! rpbssh find /var/log/proxmox-backup/tasks/ -type f -mtime +60 -delete; then
find /var/log/proxmox-backup/tasks/ -type f -mtime +60 -delete; then
log "Error clearing PBS LOG on $PBSHOST" log "Error clearing PBS LOG on $PBSHOST"
fi fi
} }
vzdump_success=false run_pve_log_clear() {
pbs_enabled_by_script=false log "Running clear LOG on PVE host ($PBSHOST)..."
if ! rssh find /var/log/vzdump -type f -mtime +30 -delete; then
log "Error clearing PVE LOG on $SOURCEHOST"
fi
}
run_pbs_backup() { run_pbs_backup() {
if [[ "${BACKUPSERVER,,}" != "yes" ]]; then if [[ "${BACKUPSERVER,,}" != "yes" ]]; then
@@ -328,152 +404,67 @@ run_pbs_backup() {
get_sourcehostname get_sourcehostname
log "Starte PBS Backup auf Host: $SOURCEHOST" log "Starte PBS Backup auf Host: $SOURCEHOST"
# ------------------------------------------------------------ # PBS-Storage prüfen und ggf. aktivieren
# PBS-Storage prüfen und ggf. temporär aktivieren
# ------------------------------------------------------------
pbs_enabled_by_script=false
log "Prüfe, ob PBS-Storage '$BACKUPSTORE' auf $SOURCEHOST aktiviert ist..." log "Prüfe, ob PBS-Storage '$BACKUPSTORE' auf $SOURCEHOST aktiviert ist..."
if rssh "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' ist deaktiviert – aktiviere temporär und warte 10 Sekunden..." log "PBS-Storage '$BACKUPSTORE' ist deaktiviert – aktiviere temporär und warte 10 Sekunden..."
rssh "pvesm set '$BACKUPSTORE' --disable 0 && sleep 10"
if rssh root@"$SOURCEHOST" \ pbs_enabled_by_script=true
"pvesm set '$BACKUPSTORE' --disable 0 && sleep 10"; then
pbs_enabled_by_script=true
log "PBS-Storage '$BACKUPSTORE' wurde erfolgreich aktiviert."
else
log "FEHLER: PBS-Storage '$BACKUPSTORE' konnte nicht aktiviert werden."
vzdump_success=false
return 1
fi
else else
log "PBS-Storage '$BACKUPSTORE' ist bereits aktiviert." log "PBS-Storage '$BACKUPSTORE' ist bereits aktiviert."
pbs_enabled_by_script=false
fi fi
# ------------------------------------------------------------
# PBS Namespace setzen
# ------------------------------------------------------------
PBS_NS_ARG=()
if [[ -n "$PBS_NAMESPACE" && "${PBS_NAMESPACE,,}" != "none" ]]; then
PBS_NS_ARG=(--pbs-namespace "$PBS_NAMESPACE")
log "PBS Namespace aktiviert: $PBS_NAMESPACE"
else
log "PBS Namespace nicht gesetzt – Backup läuft im Standard-Datastore"
fi
# ------------------------------------------------------------
# Backup-Status zurücksetzen
# ------------------------------------------------------------
vzdump_success=false vzdump_success=false
# ------------------------------------------------------------
# vzdump mit Change Detection
# ------------------------------------------------------------
log "Starte vzdump mit Change Detection..."
if rssh root@"$SOURCEHOST" \ # Versuch: vzdump mit Change Detection
vzdump --pbs-change-detection-mode metadata \ ## Eintrag muss in /etc/vzdump.conf entries-max 100000000
"${PBS_NS_ARG[@]}" \ log "Starte vzdump mit Change Detection..."
--node "$SOURCEHOSTNAME" \ if rssh vzdump --pbs-change-detection-mode metadata \
--storage "$BACKUPSTORE" \ --node "$SOURCEHOSTNAME" --storage "$BACKUPSTORE" \
--exclude "$BACKUPEXCLUDE" \ --exclude "$BACKUPEXCLUDE" --mode snapshot --all 1 \
--mode snapshot \
--all 1 \
--notes-template '{{guestname}}'; then --notes-template '{{guestname}}'; then
log "vzdump (Change Detection) erfolgreich." log "vzdump (Change Detection) erfolgreich."
vzdump_success=true vzdump_success=true
else else
log "vzdump (Change Detection) fehlgeschlagen – versuche Fallback ohne Change Detection..." log "vzdump (Change Detection) fehlgeschlagen – versuche Fallback ohne Change Detection..."
# ---------------------------------------------------------- if rssh vzdump \
# Fallback ohne Change Detection --node "$SOURCEHOSTNAME" --storage "$BACKUPSTORE" \
# ---------------------------------------------------------- --exclude "$BACKUPEXCLUDE" --mode snapshot --all 1 \
if rssh root@"$SOURCEHOST" \
vzdump \
"${PBS_NS_ARG[@]}" \
--node "$SOURCEHOSTNAME" \
--storage "$BACKUPSTORE" \
--exclude "$BACKUPEXCLUDE" \
--mode snapshot \
--all 1 \
--notes-template '{{guestname}}'; then --notes-template '{{guestname}}'; then
log "Fallback-vzdump erfolgreich." log "Fallback-vzdump erfolgreich."
vzdump_success=true vzdump_success=true
else else
log "FEHLER: vzdump fehlgeschlagen – auch im Fallback." log "FEHLER: vzdump fehlgeschlagen – auch im Fallback."
vzdump_success=false
fi fi
fi fi
# ------------------------------------------------------------
# PBS-Storage nach erfolgreichem Backup wieder deaktivieren # PBS-Storage ggf. wieder deaktivieren
#
# WICHTIG:
# Nur deaktivieren, wenn dieses Script den Storage
# vorher selbst aktiviert hat.
# ------------------------------------------------------------
if [[ "$vzdump_success" == true && "$pbs_enabled_by_script" == true ]]; then if [[ "$vzdump_success" == true && "$pbs_enabled_by_script" == true ]]; then
log "Deaktiviere temporär aktiviertes PBS-Storage '$BACKUPSTORE' auf $SOURCEHOST..." log "Deaktiviere temporär aktiviertes PBS-Storage '$BACKUPSTORE' auf $SOURCEHOST..."
rssh "pvesm set '$BACKUPSTORE' --disable 1"
if rssh root@"$SOURCEHOST" \
"pvesm set '$BACKUPSTORE' --disable 1"; then
log "PBS-Storage '$BACKUPSTORE' wurde wieder deaktiviert."
else
log "WARNUNG: PBS-Storage '$BACKUPSTORE' konnte nicht wieder deaktiviert werden."
fi
fi fi
# ------------------------------------------------------------
# Monitoring-Ausgabe # Monitoring-Ausgabe
# ------------------------------------------------------------
if [[ "$vzdump_success" == true ]]; then if [[ "$vzdump_success" == true ]]; then
echo "0 DailyPBS - Daily Backup erfolgreich" > /tmp/cmk_tmp.out echo "0 DailyPBS - Daily Backup erfolgreich" > /tmp/cmk_tmp.out
else else
echo "2 DailyPBS - Daily Backup FEHLGESCHLAGEN" > /tmp/cmk_tmp.out echo "2 DailyPBS - Daily Backup FEHLGESCHLAGEN" > /tmp/cmk_tmp.out
fi fi
local pbs_spool_file="${SPOOL_PREFIX_PBS}_checkpbs"
{ {
echo "<<<local>>>" echo "<<<local>>>"
cat /tmp/cmk_tmp.out cat /tmp/cmk_tmp.out
} > "/tmp/${pbs_spool_file}" } > /tmp/1200000_checkpbs
if rscp "/tmp/${pbs_spool_file}" root@"$SOURCEHOST":"${SPOOL_DIR}"; then rscp /tmp/1200000_checkpbs "${SOURCEUSER}@${SOURCEHOST}":/var/lib/check_mk_agent/spool \
rssh root@"$SOURCEHOST" "find '${SPOOL_DIR}' -maxdepth 1 -name '*_checkpbs' -not -name '${pbs_spool_file}' -mtime +${SPOOL_CLEANUP_DAYS} -delete" \ || log "Fehler beim Übertragen des Monitoring-Outputs via SCP"
|| log "WARNUNG: Aufräumen alter PBS-Spool-Dateien auf $SOURCEHOST fehlgeschlagen"
else
log "Fehler beim Übertragen des Monitoring-Outputs via SCP"
fi
rm -f /tmp/cmk_tmp.out "/tmp/${pbs_spool_file}"
rm -f /tmp/cmk_tmp.out /tmp/1200000_checkpbs
#write_pbs_status #write_pbs_status
# ------------------------------------------------------------
# Rückgabestatus
# ------------------------------------------------------------
if [[ "$vzdump_success" == true ]]; then
return 0
else
return 1
fi
} }
run_maintenance() { run_maintenance() {
@@ -484,10 +475,10 @@ run_maintenance() {
if [[ "$(date +%u)" == "$MAINTDAY" ]]; then if [[ "$(date +%u)" == "$MAINTDAY" ]]; then
log "Running maintenance..." log "Running maintenance..."
PRUNEJOB=$(ssh -p $PBSPORT "$PBSHOST" proxmox-backup-manager prune-job list --output-format json-pretty | grep -m 1 "id" | cut -d'"' -f4) PRUNEJOB=$(rpbssh 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" rpbssh proxmox-backup-manager prune-job run "$PRUNEJOB"
ssh -p $PBSPORT root@"$PBSHOST" proxmox-backup-manager garbage-collection start "$BACKUPSTOREPBS" rpbssh proxmox-backup-manager garbage-collection start "$BACKUPSTOREPBS"
ssh -p $PBSPORT root@"$PBSHOST" proxmox-backup-manager verify backup rpbssh proxmox-backup-manager verify backup
else else
log "No maintenance scheduled for today." log "No maintenance scheduled for today."
fi fi
@@ -498,7 +489,7 @@ run_scrub_stop() {
local ssh_cmd=() local ssh_cmd=()
if [[ "$mode" == "remote" ]]; then if [[ "$mode" == "remote" ]]; then
ssh_cmd=(ssh -p "$SOURCEPORT" root@"$SOURCEHOST") ssh_cmd=(ssh -o BatchMode=yes -o ConnectTimeout=10 -p "$SOURCEPORT" "${SOURCEUSER}@${SOURCEHOST}")
fi fi
"${ssh_cmd[@]}" bash -c ' "${ssh_cmd[@]}" bash -c '
@@ -518,8 +509,8 @@ run_scrub_stop() {
} }
write_pbs_status() { write_pbs_status() {
local spool_file="${SPOOL_DIR}/${SPOOL_PREFIX_PBS}_checkpbs_local" local spool_file="/var/lib/check_mk_agent/spool/90000_checkpbs_local"
local repo="$BACKUPSTOREPBS" local repo="${PBS_REPO_USER}@${PBSHOST}:${BACKUPSTOREPBS}"
local tmpfile local tmpfile
tmpfile=$(mktemp) tmpfile=$(mktemp)
@@ -528,6 +519,11 @@ write_pbs_status() {
return return
fi fi
if [[ -z "${PBS_REPO_USER:-}" || -z "${PBS_PASSWORD:-}" ]]; then
log "PBS_REPO_USER oder PBS_PASSWORD nicht gesetzt – PBS-Status-Ausgabe übersprungen."
return 1
fi
echo "<<<local>>>" > "$tmpfile" echo "<<<local>>>" > "$tmpfile"
local now local now
@@ -552,8 +548,8 @@ write_pbs_status() {
fi fi
fi fi
# Backup-Daten extrahieren # Backup-Daten extrahieren (direkt vom Skript-Host gegen PBSHOST, mit PBS_PASSWORD authentifiziert)
rssh root@"$SOURCEHOST" proxmox-backup-client list --repository "$repo" --output-format json 2>/dev/null | \ PBS_PASSWORD="$PBS_PASSWORD" proxmox-backup-client list --repository "$repo" --output-format json 2>/dev/null | \
grep -E '"backup-id"|"backup-type"|"backup-time"' | \ grep -E '"backup-id"|"backup-type"|"backup-time"' | \
sed -E 's/[",]//g; s/^ *//' > "$tmpfile.json" sed -E 's/[",]//g; s/^ *//' > "$tmpfile.json"
@@ -572,7 +568,7 @@ write_pbs_status() {
if [[ -n "$backup_type" && -n "$backup_id" && -n "$backup_time" ]]; then if [[ -n "$backup_type" && -n "$backup_id" && -n "$backup_time" ]]; then
local age status timestamp msg local age status timestamp msg
age=$((now - backup_time)) age=$((now - backup_time))
timestamp=$(date -d "@$backup_time" "+%Y-%m-%d %H:%M:%S") timestamp=$(epoch_to_str "$backup_time")
if (( age < warn_threshold )); then if (( age < warn_threshold )); then
status=0 status=0
@@ -597,6 +593,27 @@ write_pbs_status() {
log "PBS Backup Status lokal geschrieben: $spool_file" log "PBS Backup Status lokal geschrieben: $spool_file"
} }
run_pre_shutdown_hooks() {
local hook_dir="/etc/miyagi-backup/hooks.d/pre-shutdown"
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 Pre-Shutdown-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() { shutdown_now() {
if [[ "${SHUTDOWN,,}" == "yes" ]]; then if [[ "${SHUTDOWN,,}" == "yes" ]]; then
if zfs_replace; then if zfs_replace; then
@@ -609,6 +626,7 @@ shutdown_now() {
send_piggyback send_piggyback
send_piggyback_external send_piggyback_external
send_checkzfs_external send_checkzfs_external
run_pre_shutdown_hooks
log "Shutting down now...in 60sec" log "Shutting down now...in 60sec"
sleep 60 sleep 60
shutdown now shutdown now
@@ -622,12 +640,11 @@ send_piggyback_external() {
log "Externer Piggyback-Export deaktiviert." log "Externer Piggyback-Export deaktiviert."
return return
fi fi
write_zsync_config
get_sourcehostname get_sourcehostname
log "Ermittelter SOURCEHOSTNAME: $SOURCEHOSTNAME" log "Ermittelter SOURCEHOSTNAME: $SOURCEHOSTNAME"
local combined_host="miyagi-${SOURCEHOSTNAME}-$(hostname)" local combined_host="miyagi-${SOURCEHOSTNAME}-$(hostname)"
local spool_file="${SPOOL_PREFIX_PIGGYBACK_EXTERNAL}_${combined_host}_external" local spool_file="100000_${combined_host}_piggyback_external"
local temp_dir local temp_dir
temp_dir=$(mktemp -d) temp_dir=$(mktemp -d)
@@ -639,10 +656,8 @@ send_piggyback_external() {
echo "<<<<>>>>" echo "<<<<>>>>"
} > "$temp_dir/$spool_file" } > "$temp_dir/$spool_file"
if scp -P "$EPIGGYBACK_PORT" "$temp_dir/$spool_file" "$EPIGGYBACK_HOST:${SPOOL_DIR}/"; then 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" log "Piggyback-Daten erfolgreich an $EPIGGYBACK_HOST gesendet: $spool_file"
ssh -p "$EPIGGYBACK_PORT" "$EPIGGYBACK_HOST" "find '${SPOOL_DIR}' -maxdepth 1 -name '*_${combined_host}_external' -not -name '${spool_file}' -mtime +${SPOOL_CLEANUP_DAYS} -delete" \
|| log "WARNUNG: Aufräumen alter Piggyback-Spool-Dateien auf $EPIGGYBACK_HOST fehlgeschlagen"
else else
log "Fehler beim Übertragen der Piggyback-Daten an $EPIGGYBACK_HOST" log "Fehler beim Übertragen der Piggyback-Daten an $EPIGGYBACK_HOST"
rm -rf "$temp_dir" rm -rf "$temp_dir"
@@ -653,9 +668,70 @@ send_piggyback_external() {
return 0 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:-1500,2000}"
local checkzfs_max_snapshot_count="${checkzfs_max_snapshot_count:-180,200}"
# 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="100000_${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() { send_checkzfs_external() {
if [[ "${ECHECKZFS,,}" != "yes" ]]; then if [[ "${ECHECKZFS,,}" != "yes" ]]; then
log "Externer Piggyback-Export deaktiviert." log "Externer CheckZFS deaktiviert."
return return
fi fi
local config="/etc/bashclub/${SOURCEHOST}.conf" local config="/etc/bashclub/${SOURCEHOST}.conf"
@@ -665,11 +741,6 @@ send_checkzfs_external() {
fi fi
source "$config" source "$config"
if [[ "${ECHECKZFS,,}" != "yes" ]]; then
log "Externer Check-ZFS deaktiviert (ECHECKZFS=$ECHECKZFS)"
return
fi
write_zsync_config
get_sourcehostname # Setzt SOURCEHOSTNAME, wenn nicht vorhanden get_sourcehostname # Setzt SOURCEHOSTNAME, wenn nicht vorhanden
local checkzfs_cmd="${checkzfs_cmd:-$(command -v checkzfs)}" local checkzfs_cmd="${checkzfs_cmd:-$(command -v checkzfs)}"
@@ -687,20 +758,20 @@ send_checkzfs_external() {
elif [[ "$source" == "local" && "$value" == "all" ]]; then elif [[ "$source" == "local" && "$value" == "all" ]]; then
filter+="${name}|" filter+="${name}|"
fi fi
done < <(ssh -p "$SOURCEPORT" "$SOURCEHOST" "zfs get -H -o name,value,source -t filesystem,volume $ZPUSHTAG") done < <(rssh "zfs get -H -o name,value,source -t filesystem,volume $ZPUSHTAG")
filter="#${filter%|}" filter="#${filter%|}"
log "Generierter ZFS-Filter: $filter" log "Generierter ZFS-Filter: $filter"
local combined="miyagi-${SOURCEHOSTNAME}-$(hostname)-${ZPUSHTAG}" local combined="miyagi-${SOURCEHOSTNAME}-$(hostname)-${ZPUSHTAG}"
local spoolfile="/tmp/${combined}_checkzfs_external" local spoolfile="/tmp/${combined}_checkzfs_external"
local spooldest="${SPOOL_PREFIX_CHECKZFS_EXTERNAL}_${combined}_checkzfs_external" local spooldest="100000_${combined}_checkzfs_external"
log "Führe checkzfs aus..." log "Führe checkzfs aus..."
{ {
echo "<<<local>>>" echo "<<<local>>>"
"$checkzfs_cmd" \ "$checkzfs_cmd" \
--source root@"${SOURCEHOST}:${SOURCEPORT}" \ --source ${SOURCEUSER}@"${SOURCEHOST}:${SOURCEPORT}" \
--filter "$filter" \ --filter "$filter" \
--replicafilter "^${ZFSTRGT}" \ --replicafilter "^${ZFSTRGT}" \
--prefix "$checkzfs_prefix" \ --prefix "$checkzfs_prefix" \
@@ -709,10 +780,8 @@ send_checkzfs_external() {
--output checkmk --output checkmk
} > "$spoolfile" } > "$spoolfile"
if scp -P "$ECHECKZFS_PORT" "$spoolfile" root@"$ECHECKZFS_HOST":"${SPOOL_DIR}/${spooldest}"; then 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" log "Spool-Datei erfolgreich übertragen an $ECHECKZFS_HOST:$spooldest"
ssh -p "$ECHECKZFS_PORT" root@"$ECHECKZFS_HOST" "find '${SPOOL_DIR}' -maxdepth 1 -name 'miyagi-${SOURCEHOSTNAME}-$(hostname)-*_checkzfs_external' -not -name '${spooldest}' -mtime +${SPOOL_CLEANUP_DAYS} -delete" \
|| log "WARNUNG: Aufräumen alter checkzfs-Spool-Dateien auf $ECHECKZFS_HOST fehlgeschlagen"
else else
log "Fehler beim Übertragen der Spool-Datei an $ECHECKZFS_HOST" log "Fehler beim Übertragen der Spool-Datei an $ECHECKZFS_HOST"
fi fi
@@ -812,7 +881,8 @@ if [[ $# -eq 0 ]]; then
run_pbs_backup run_pbs_backup
run_maintenance run_maintenance
run_pbs_log_clear run_pbs_log_clear
else run_pve_log_clear
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
@@ -828,12 +898,23 @@ if [[ $# -eq 0 ]]; then
usage usage
fi fi
else 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 case "$1" in
help) help)
declare -F | awk '{print $3}' | grep -v "^_" | grep -v "^main$" declare -F | awk '{print $3}' | grep -v "^_" | grep -v "^main$"
;; ;;
*) *)
if declare -F "$1" >/dev/null 2>&1; then 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 else
log "Function $1 not found." log "Function $1 not found."
-220
View File
@@ -1,220 +0,0 @@
#!/bin/bash
set -euo pipefail
IFS=$'\n\t'
PERMITROOT_YES_HOSTS=()
LOG() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"
}
CONFIG_FILE="${1:-}"
if [[ -z "$CONFIG_FILE" ]]; then
LOG "Keine Konfigurationsdatei übergeben."
echo "Usage: $0 /pfad/zur/config"
exit 1
fi
if [[ ! -f "$CONFIG_FILE" ]]; then
LOG "Konfigurationsdatei nicht gefunden: $CONFIG_FILE"
exit 1
fi
if ! bash -n "$CONFIG_FILE"; then
LOG "Syntaxfehler in der Konfigurationsdatei!"
exit 1
fi
source "$CONFIG_FILE"
REQUIRED_VARS=(
SOURCEPORT
BACKUPSERVER
ZSYNC
MAINTDAY
SHUTDOWN
UPDATES
SOURCEHOST
PBSHOST
BACKUPSTORE
BACKUPSTOREPBS
BACKUPEXCLUDE
REPLEXCLUDE
)
MISSING=()
for var in "${REQUIRED_VARS[@]}"; do
if ! declare -p "$var" &>/dev/null || [[ -z "${!var}" ]]; then
MISSING+=("$var")
fi
done
if [[ ${#MISSING[@]} -gt 0 ]]; then
LOG " Fehlende Variablen in Konfiguration:"
for v in "${MISSING[@]}"; do echo " - $v"; done
exit 1
else
LOG " Alle Variablen gesetzt."
fi
if [[ ${#MISSING[@]} -gt 0 ]]; then
LOG " Fehlende Variablen in Konfiguration:"
for v in "${MISSING[@]}"; do echo " - $v"; done
exit 1
else
LOG " Alle Variablen gesetzt."
fi
# Prüfung, ob SHUTDOWN auch tatsächlich in der Datei definiert wurde
if ! grep -qE '^\s*SHUTDOWN\s*=' "$CONFIG_FILE"; then
LOG "Hinweis: Die Variable 'SHUTDOWN' ist zwar gesetzt, aber nicht direkt in der Konfigurationsdatei '$CONFIG_FILE' definiert."
LOG " → Bitte prüfen, ob dies gewollt ist oder von einer übergeordneten Quelle stammt."
fi
# Anzeigen, ob SHUTDOWN aktiv ist
LOG "SHUTDOWN-Status: ${SHUTDOWN:-nicht gesetzt}"
case "${SHUTDOWN,,}" in
yes)
LOG "Nach dem Backup wird das System heruntergefahren."
;;
no)
LOG "Kein automatischer Shutdown nach dem Backup."
;;
*)
LOG "Unbekannter SHUTDOWN-Wert: '${SHUTDOWN}' , erwartet: 'yes' oder 'no'"
;;
esac
check_ssh_connection() {
local host=$1
LOG "Prüfe SSH-Verbindung zu $host ..."
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
LOG " SSH-Verbindung zu $host fehlgeschlagen!"
return 1
fi
}
check_and_copy_ssh_key() {
local host=$1
local keyfile="$HOME/.ssh/id_rsa.pub"
if [[ ! -f "$keyfile" ]]; then
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 "$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 "$SOURCEPORT" "$host"
else
LOG " SSH-Key nicht übertragen."
fi
fi
}
check_sshd_config_recommendation() {
local host=$1
LOG " Prüfe sshd_config auf $host bzgl. 'PermitRootLogin'..."
local current_setting
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."
elif echo "$current_setting" | grep -qi "PermitRootLogin yes"; then
LOG " Aktuell ist PermitRootLogin=YES erlaubt auf $host"
LOG " Empfehlung: auf 'prohibit-password' umstellen."
PERMITROOT_YES_HOSTS+=("$host")
else
LOG " PermitRootLogin-Einstellung ist: $current_setting"
fi
}
check_pveversion() {
local host=$1
LOG "Prüfe PVE-Version auf $host ..."
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 (Typ: $type) ==="
if check_ssh_connection "$host" "$port"; then
check_and_copy_ssh_key "$host"
check_sshd_config_recommendation "$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" pve
if [[ "$BACKUPSERVER" == "yes" ]]; then
run_host_check "$PBSHOST" pbs
else
LOG " BACKUPSERVER ist deaktiviert PBSHOST wird übersprungen."
fi
# NACHLAUF: Frage zur automatischen Änderung von PermitRootLogin
if [[ ${#PERMITROOT_YES_HOSTS[@]} -gt 0 ]]; then
echo ""
echo " Die folgenden Hosts erlauben derzeit root-Login per Passwort:"
for h in "${PERMITROOT_YES_HOSTS[@]}"; do
echo " - $h"
done
read -rp " Möchtest du PermitRootLogin auf diesen Hosts auf 'prohibit-password' setzen und sshd neustarten? [j/N] " change_ans
if [[ "$change_ans" =~ ^[JjYy]$ ]]; then
for h in "${PERMITROOT_YES_HOSTS[@]}"; do
echo "Ändere sshd_config auf $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."
fi
fi
-109
View File
@@ -1,109 +0,0 @@
#!/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"
+198 -160
View File
@@ -1,29 +1,41 @@
#!/bin/bash #!/usr/bin/env bash
# Author: (C) 2025 Patrick Perlbach <patrick@perlbach24.de> # miyagi-backup Installer v3
# --- Konfiguration und Initiale Variablen --- set -uo pipefail
FORCE_YES=0 FORCE_YES=0
DRY_RUN=0 DRY_RUN=0
DEV_MODE=0
CONFIG_FILE="" CONFIG_FILE=""
INSTALL_DIR="/opt/miyagi-backup"
CONFIG_DIR="/etc/miyagi-backup"
HOOKS_DIR="${CONFIG_DIR}/hooks.d/pre-shutdown"
SYMLINK="/usr/local/bin/miyagi-backup"
GITHUB_BASE="https://raw.githubusercontent.com/bashclub/miyagi-backup/main"
GITEA_BASE="https://gitea.perlbach24.de/scripte/miyagi-backup/raw/branch/main"
CHECKZFS_URL="https://gitea.perlbach24.de/scripte/check-zfs-replication/raw/branch/main/checkzfs.py"
CHECK_SNAPSHOT_AGE_URL="https://gitea.perlbach24.de/scripte/check-zfs-replication/raw/branch/main/check-snapshot-age"
CLEANSNAPS_URL="https://raw.githubusercontent.com/bashclub/zfs-housekeeping/refs/heads/main/cleansnaps.sh"
DISK_SMART_INFO_URL="https://raw.githubusercontent.com/bashclub/checkmk-smart/main/disk_smart_info.py"
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; NC='\033[0m'
ok() { echo -e "${GREEN}[OK]${NC} $*"; }
info() { echo -e "${BLUE}[..]${NC} $*"; }
warn() { echo -e "${YELLOW}[!!]${NC} $*"; }
die() { echo -e "${RED}[ERR]${NC} $*" >&2; exit 1; }
# --- Hilfsfunktionen ---
usage() { usage() {
cat <<EOF cat <<EOF
Usage: $0 -c <config> [--yes] [--dry-run] Usage: $0 [OPTIONS]
-c <file> Pfad zur Konfigurationsdatei (Pflicht) -c <file> Konfigurationsdatei laden (für SSH-Check + ZFS-Tagging)
--yes Automatische Ausführung ohne Rückfrage --yes Keine Rückfragen
--dry-run Zeigt nur, was gemacht würde --dry-run Zeigt nur was gemacht würde
--help Diese Hilfe anzeigen --dev Installiere von Gitea (dev) statt GitHub (stable)
--help Diese Hilfe
EOF EOF
exit 1 exit 0
}
run_or_echo() {
if [[ "$DRY_RUN" == "1" ]]; then
echo "[DRY-RUN] $*"
else
eval "$@"
fi
} }
parse_args() { parse_args() {
@@ -32,224 +44,250 @@ parse_args() {
-c) CONFIG_FILE="$2"; shift 2 ;; -c) CONFIG_FILE="$2"; shift 2 ;;
--yes) FORCE_YES=1; shift ;; --yes) FORCE_YES=1; shift ;;
--dry-run) DRY_RUN=1; shift ;; --dry-run) DRY_RUN=1; shift ;;
--dev) DEV_MODE=1; shift ;;
--help) usage ;; --help) usage ;;
*) echo "[ERROR] Unbekannter Parameter: $1"; usage ;; *) die "Unbekannter Parameter: $1" ;;
esac esac
done done
[[ -z "$CONFIG_FILE" || ! -f "$CONFIG_FILE" ]] && { if [[ -n "$CONFIG_FILE" ]]; then
echo "[ERROR] Konfigurationsdatei fehlt oder ungültig!" [[ ! -f "$CONFIG_FILE" ]] && die "Konfigurationsdatei nicht gefunden: $CONFIG_FILE"
exit 1 bash -n "$CONFIG_FILE" || die "Syntaxfehler in $CONFIG_FILE"
} source "$CONFIG_FILE"
SOURCEUSER="${SOURCEUSER:-root}"
fi
}
source "$CONFIG_FILE" run() {
if [[ "$DRY_RUN" -eq 1 ]]; then
echo "[DRY-RUN] $*"
else
"$@"
fi
} }
confirm() { confirm() {
if [[ "$FORCE_YES" -eq 1 ]]; then [[ "$FORCE_YES" -eq 1 ]] && return 0
return 0
fi
while true; do while true; do
read -rp "$1 [j/N]: " yn read -rp "$1 [j/N]: " yn
case $yn in case "$yn" in
[JjYy]*) return 0 ;; [JjYy]*) return 0 ;;
[Nn]*|"") return 1 ;; [Nn]*|"") return 1 ;;
*) echo "Bitte j (ja) oder n (nein) eingeben." ;; *) echo "Bitte j oder n eingeben." ;;
esac esac
done done
} }
ensure_dialog() {
install_cmk() { if ! command -v dialog &>/dev/null; then
echo "[INFO] Lade cmk.deb herunter..." info "Installiere dialog..."
run_or_echo "curl -fsSL \"$CMK_URL\" -o \"$CMK_FILE\"" run apt-get install -y -qq dialog || die "dialog konnte nicht installiert werden"
fi
echo "[INFO] Installiere cmk.deb..."
run_or_echo "sudo apt install -y \"$CMK_FILE\""
} }
install_fix_interface_names() { get_base_url() {
echo "[INFO] Installiere Fix für Interface-Namen..." [[ "$DEV_MODE" -eq 1 ]] && echo "$GITEA_BASE" || echo "$GITHUB_BASE"
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() { # ── Installationsschritte ─────────────────────────────────────────────────────
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() { install_miyagi() {
echo "[INFO] Installiere disk_smart_info Plugin..." local base
run_or_echo "curl -fsSL \"$DISK_SMART_INFO_URL\" -o /usr/lib/check_mk_agent/plugins/disk_smart_info" base="$(get_base_url)"
run_or_echo "chmod +x /usr/lib/check_mk_agent/plugins/disk_smart_info" local source_label
} [[ "$DEV_MODE" -eq 1 ]] && source_label="Gitea (dev)" || source_label="GitHub (stable)"
install_cleansnaps() { info "Installiere miyagi-backup von $source_label..."
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() { run mkdir -p "$INSTALL_DIR"
echo "[INFO] Installiere check-snapshot-age..." run mkdir -p "$CONFIG_DIR"
run_or_echo "wget -O /usr/local/bin/check-snapshot-age \"$CHECK_SNAPSHOT_AGE_URL\"" run mkdir -p "$HOOKS_DIR"
run_or_echo "chmod +x /usr/local/bin/check-snapshot-age"
run curl -fsSL "${base}/miyagi-backup.sh" -o "${INSTALL_DIR}/miyagi-backup.sh"
run chmod +x "${INSTALL_DIR}/miyagi-backup.sh"
# config.example nur holen, nie bestehende Config überschreiben
run curl -fsSL "${base}/config.example" -o "${CONFIG_DIR}/config.example"
# Example-Hook kopieren (nicht ausführbar = inaktiv bis User chmod +x setzt)
run curl -fsSL "${base}/hooks.d/pre-shutdown/example-hook.sh" \
-o "${HOOKS_DIR}/example-hook.sh"
# Symlink
run ln -sf "${INSTALL_DIR}/miyagi-backup.sh" "$SYMLINK"
ok "miyagi-backup installiert: ${INSTALL_DIR}/miyagi-backup.sh"
ok "Symlink: $SYMLINK"
ok "Config-Verzeichnis: $CONFIG_DIR"
ok "Hook-Verzeichnis: $HOOKS_DIR"
} }
install_checkzfs() { install_checkzfs() {
echo "[INFO] Installiere checkzfs..." info "Installiere checkzfs..."
run_or_echo "wget -O /usr/local/bin/checkzfs \"$CHECKZFS_URL\"" run curl -fsSL "$CHECKZFS_URL" -o /usr/local/bin/checkzfs
run_or_echo "chmod +x /usr/local/bin/checkzfs" run chmod +x /usr/local/bin/checkzfs
ok "checkzfs installiert"
}
install_check_snapshot_age() {
info "Installiere check-snapshot-age..."
run curl -fsSL "$CHECK_SNAPSHOT_AGE_URL" -o /usr/local/bin/check-snapshot-age
run chmod +x /usr/local/bin/check-snapshot-age
ok "check-snapshot-age installiert"
}
install_cleansnaps() {
info "Installiere cleansnaps..."
run curl -fsSL "$CLEANSNAPS_URL" -o /usr/local/bin/cleansnaps
run chmod +x /usr/local/bin/cleansnaps
ok "cleansnaps installiert"
}
install_disk_smart_info() {
info "Installiere disk_smart_info..."
run mkdir -p /usr/lib/check_mk_agent/plugins
run curl -fsSL "$DISK_SMART_INFO_URL" -o /usr/lib/check_mk_agent/plugins/disk_smart_info
run chmod +x /usr/lib/check_mk_agent/plugins/disk_smart_info
ok "disk_smart_info installiert"
}
install_ethtool() {
info "Installiere ethtool..."
run apt-get install -y -qq ethtool
ok "ethtool installiert"
} }
check_ssh() { check_ssh() {
echo "[INFO] Teste SSH-Verbindung zu $SOURCEHOST ..." [[ -z "${SOURCEHOST:-}" ]] && warn "Kein SOURCEHOST in Config – SSH-Check übersprungen" && return
info "Teste SSH-Verbindung zu ${SOURCEUSER}@${SOURCEHOST}:${SOURCEPORT}..."
if ssh -p "$SOURCEPORT" -o BatchMode=yes -o ConnectTimeout=5 root@"$SOURCEHOST" "echo Verbindung erfolgreich" 2>/dev/null; then if ssh -p "${SOURCEPORT:-22}" -o BatchMode=yes -o ConnectTimeout=5 \
echo "[OK] SSH-Verbindung über Schlüssel funktioniert." "${SOURCEUSER}@${SOURCEHOST}" "echo ok" &>/dev/null; then
ok "SSH-Verbindung via Schlüssel funktioniert"
else else
echo "[WARN] Keine Schlüsselbasierte SSH-Verbindung möglich oder abgelehnt." warn "Keine schlüsselbasierte SSH-Verbindung möglich"
if confirm "SSH-Schlüssel jetzt auf ${SOURCEHOST} kopieren (ssh-copy-id)?"; then
echo "[HINWEIS] Eine Passwortabfrage erfolgt jetzt – dies ist normal." run ssh-copy-id -p "${SOURCEPORT:-22}" "${SOURCEUSER}@${SOURCEHOST}"
if ssh -p "$SOURCEPORT" root@"$SOURCEHOST" "echo Verbindung erfolgreich"; then ok "Schlüssel kopiert – bitte Installer erneut starten zum Verifizieren"
echo "[OK] SSH-Verbindung per Passwort erfolgreich." exit 0
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
fi fi
} }
select_zfs_pools() { select_zfs_pools() {
echo "[INFO] Lade ZFS-Datasets vom Remote-Host..." [[ -z "${SOURCEHOST:-}" ]] && warn "Kein SOURCEHOST – ZFS-Tagging übersprungen" && return
LOCAL_HOSTNAME=$(hostname -s) info "Lade ZFS-Datasets von ${SOURCEHOST}..."
REMOTE_HOSTNAME=$(ssh -p "$SOURCEPORT" root@"$SOURCEHOST" "hostname -s") local local_hostname remote_hostname tag_key tag_value
TAG_KEY="bashclub:miyagi-${REMOTE_HOSTNAME}-${LOCAL_HOSTNAME}" local_hostname="$(hostname -s)"
TAG_VALUE="subvols" remote_hostname="$(ssh -p "${SOURCEPORT:-22}" "${SOURCEUSER}@${SOURCEHOST}" "hostname -s" 2>/dev/null)" \
|| { warn "SSH-Verbindung fehlgeschlagen – ZFS-Tagging übersprungen"; return; }
# Hole alle relevanten Pool-Namen tag_key="bashclub:miyagi-${remote_hostname}-${local_hostname}"
mapfile -t pools < <(ssh -p "$SOURCEPORT" root@"$SOURCEHOST" \ tag_value="subvols"
"zfs list -H -o name" | \
mapfile -t pools < <(ssh -p "${SOURCEPORT:-22}" "${SOURCEUSER}@${SOURCEHOST}" \
"zfs list -H -o name" 2>/dev/null | \
grep -E '/(vm|subvol)-' | \ grep -E '/(vm|subvol)-' | \
grep -viE 'repl|replica' | \ grep -viE 'repl|replica' | \
sed 's:/[^/]*$::' | \ sed 's:/[^/]*$::' | \
sort -u) sort -u)
if [[ ${#pools[@]} -eq 0 ]]; then if [[ ${#pools[@]} -eq 0 ]]; then
echo "[WARN] Keine geeigneten Datasets gefunden!" warn "Keine geeigneten ZFS-Datasets gefunden"
return return
fi fi
local options=() local options=()
local tagged_pools=()
for pool in "${pools[@]}"; do for pool in "${pools[@]}"; do
local tag status local tag status
tag=$(ssh -p "$SOURCEPORT" root@"$SOURCEHOST" zfs get -H -o value "$TAG_KEY" "$pool" 2>/dev/null) tag="$(ssh -p "${SOURCEPORT:-22}" "${SOURCEUSER}@${SOURCEHOST}" \
if [[ "$tag" == "$TAG_VALUE" ]]; then zfs get -H -o value "$tag_key" "$pool" 2>/dev/null)"
status="on" [[ "$tag" == "$tag_value" ]] && status="on" || status="off"
tagged_pools+=("$pool") options+=("$pool" "" "$status")
else
status="off"
fi
options+=("$pool" "$([[ "$status" == "on" ]] && echo 'TAGGED' || echo 'untagged')" "$status")
done done
local selected local selected
selected=$(whiptail --title "ZFS Pools auswählen" \ selected=$(dialog --clear \
--checklist "Wähle Datasets zum Taggen aus:" 20 78 12 \ --backtitle "miyagi-backup Installer" \
--title "ZFS Pools taggen" \
--checklist "Datasets für Replikation auswählen:" 20 78 12 \
"${options[@]}" \ "${options[@]}" \
3>&1 1>&2 2>&3) 3>&1 1>&2 2>&3)
clear
if [[ $? -ne 0 ]]; then [[ $? -ne 0 ]] && info "ZFS-Tagging abgebrochen" && return
echo "[INFO] Auswahl abgebrochen."
return
fi
local selected_array=() local selected_array=()
eval "selected_array=($selected)" read -ra selected_array <<< "$selected"
echo "[INFO] Verarbeite Tagging für ausgewählte Pools:" for pool in "${pools[@]}"; do
for ((i = 0; i < ${#options[@]}; i += 3)); do
local pool="${options[i]}"
local prev_status="${options[i+2]}"
local is_selected=0 local is_selected=0
local prev_status="off"
for ((i=0; i<${#options[@]}; i+=3)); do
[[ "${options[i]}" == "$pool" ]] && prev_status="${options[i+2]}" && break
done
for sel in "${selected_array[@]}"; do for sel in "${selected_array[@]}"; do
if [[ "$sel" == "$pool" ]]; then [[ "$sel" == "\"$pool\"" || "$sel" == "$pool" ]] && is_selected=1 && break
is_selected=1
break
fi
done done
if [[ "$is_selected" -eq 1 && "$prev_status" == "off" ]]; then if [[ "$is_selected" -eq 1 && "$prev_status" == "off" ]]; then
echo " → Tagge $pool" info "Tagge $pool"
run_or_echo ssh -p "$SOURCEPORT" root@"$SOURCEHOST" zfs set "$TAG_KEY=$TAG_VALUE" "$pool" run ssh -p "${SOURCEPORT:-22}" "${SOURCEUSER}@${SOURCEHOST}" \
zfs set "${tag_key}=${tag_value}" "$pool"
elif [[ "$is_selected" -eq 0 && "$prev_status" == "on" ]]; then elif [[ "$is_selected" -eq 0 && "$prev_status" == "on" ]]; then
echo " → Entferne Tag von $pool" info "Entferne Tag von $pool"
run_or_echo ssh -p "$SOURCEPORT" root@"$SOURCEHOST" zfs inherit "$TAG_KEY" "$pool" run ssh -p "${SOURCEPORT:-22}" "${SOURCEUSER}@${SOURCEHOST}" \
zfs inherit "$tag_key" "$pool"
fi fi
done done
ok "ZFS-Tagging abgeschlossen"
} }
# ── Hauptmenü ─────────────────────────────────────────────────────────────────
main() { main() {
# URLs und Dateipfade als Variablen, ggf. in Kopfbereich auslagern [[ $EUID -ne 0 ]] && die "Bitte als root ausführen"
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" ensure_dialog
MIYAGI_BACKUP_URL="https://gitea.perlbach24.de/scripte/miyagi-backup/raw/branch/main/miyagi-backup.sh" echo ""
MIYAGI_CHECK_URL="https://gitea.perlbach24.de/scripte/miyagi-backup/raw/branch/main/miyagi-check.sh" echo " ╔══════════════════════════════════════╗"
MIYAGI_CONVERT_URL="https://gitea.perlbach24.de/scripte/miyagi-backup/raw/branch/main/miyagi-convert.sh" echo " ║ miyagi-backup Installer v3 ║"
echo " ╚══════════════════════════════════════╝"
echo ""
DISK_SMART_INFO_URL="https://raw.githubusercontent.com/bashclub/checkmk-smart/main/disk_smart_info.py" local choices
choices=$(dialog --clear \
--backtitle "miyagi-backup Installer v3" \
--title "Komponenten auswählen" \
--checklist "Was soll installiert werden?" 22 65 12 \
"miyagi" "miyagi-backup.sh + Verzeichnisse + Symlink" on \
"checkzfs" "checkzfs (ZFS-Replikationsprüfung)" on \
"snapage" "check-snapshot-age" on \
"cleansnaps" "cleansnaps (ZFS-Snapshot-Housekeeping)" on \
"smartinfo" "disk_smart_info (CheckMK Plugin)" on \
"ethtool" "ethtool (WOL-Unterstützung)" off \
"ssh" "SSH-Verbindung prüfen / Schlüssel kopieren" on \
"zfstag" "ZFS-Pools taggen (benötigt Config -c)" off \
3>&1 1>&2 2>&3)
clear
CLEANSNAPS_URL="https://raw.githubusercontent.com/bashclub/zfs-housekeeping/refs/heads/main/cleansnaps.sh" [[ $? -ne 0 ]] && echo "Abgebrochen." && exit 0
[[ -z "$choices" ]] && echo "Nichts ausgewählt." && exit 0
CHECK_SNAPSHOT_AGE_URL="https://gitea.perlbach24.de/scripte/check-zfs-replication/raw/branch/main/check-snapshot-age" [[ "$choices" == *'"miyagi"'* || "$choices" == *'miyagi'* ]] && install_miyagi
[[ "$choices" == *'"checkzfs"'* || "$choices" == *'checkzfs'* ]] && install_checkzfs
[[ "$choices" == *'"snapage"'* || "$choices" == *'snapage'* ]] && install_check_snapshot_age
[[ "$choices" == *'"cleansnaps"'* || "$choices" == *'cleansnaps'* ]] && install_cleansnaps
[[ "$choices" == *'"smartinfo"'* || "$choices" == *'smartinfo'* ]] && install_disk_smart_info
[[ "$choices" == *'"ethtool"'* || "$choices" == *'ethtool'* ]] && install_ethtool
[[ "$choices" == *'"ssh"'* || "$choices" == *'ssh'* ]] && check_ssh
[[ "$choices" == *'"zfstag"'* || "$choices" == *'zfstag'* ]] && select_zfs_pools
CHECKZFS_URL="https://gitea.perlbach24.de/scripte/check-zfs-replication/raw/branch/main/checkzfs.py" echo ""
ok "Installation abgeschlossen."
echo "[INFO] Starte Installation mit Konfig: $CONFIG_FILE" echo ""
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 "$@" parse_args "$@"
main main
+168
View File
@@ -0,0 +1,168 @@
#!/usr/bin/env bash
# miyagi-backup Updater v3
set -uo pipefail
DEV_MODE=0
INSTALL_DIR="/opt/miyagi-backup"
CONFIG_DIR="/etc/miyagi-backup"
HOOKS_DIR="${CONFIG_DIR}/hooks.d/pre-shutdown"
SYMLINK="/usr/local/bin/miyagi-backup"
GITHUB_BASE="https://raw.githubusercontent.com/bashclub/miyagi-backup/main"
GITEA_BASE="https://gitea.perlbach24.de/scripte/miyagi-backup/raw/branch/main"
CHECKZFS_URL="https://gitea.perlbach24.de/scripte/check-zfs-replication/raw/branch/main/checkzfs.py"
CHECK_SNAPSHOT_AGE_URL="https://gitea.perlbach24.de/scripte/check-zfs-replication/raw/branch/main/check-snapshot-age"
CLEANSNAPS_URL="https://raw.githubusercontent.com/bashclub/zfs-housekeeping/refs/heads/main/cleansnaps.sh"
DISK_SMART_INFO_URL="https://raw.githubusercontent.com/bashclub/checkmk-smart/main/disk_smart_info.py"
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; NC='\033[0m'
ok() { echo -e "${GREEN}[OK]${NC} $*"; }
info() { echo -e "${BLUE}[..]${NC} $*"; }
warn() { echo -e "${YELLOW}[!!]${NC} $*"; }
die() { echo -e "${RED}[ERR]${NC} $*" >&2; exit 1; }
usage() {
cat <<EOF
Usage: $0 [--dev]
--dev Update von Gitea (dev) statt GitHub (stable)
--help Diese Hilfe
EOF
exit 0
}
parse_args() {
while [[ $# -gt 0 ]]; do
case "$1" in
--dev) DEV_MODE=1; shift ;;
--help) usage ;;
*) die "Unbekannter Parameter: $1" ;;
esac
done
}
get_base_url() {
[[ "$DEV_MODE" -eq 1 ]] && echo "$GITEA_BASE" || echo "$GITHUB_BASE"
}
# ── Self-Update ───────────────────────────────────────────────────────────────
self_update() {
local base
base="$(get_base_url)"
local self="${INSTALL_DIR}/miyagi-update.sh"
if [[ -f "$self" ]] && command -v curl >/dev/null; then
curl -fsSL "${base}/miyagi-update.sh" -o "${self}.new" 2>/dev/null \
&& mv "${self}.new" "$self" \
&& chmod +x "$self" \
&& info "Updater aktualisiert" \
|| rm -f "${self}.new"
fi
}
# ── miyagi-backup.sh updaten ─────────────────────────────────────────────────
update_miyagi() {
local base
base="$(get_base_url)"
local source_label
[[ "$DEV_MODE" -eq 1 ]] && source_label="Gitea (dev)" || source_label="GitHub (stable)"
info "Aktualisiere miyagi-backup.sh von $source_label..."
if [[ ! -d "$INSTALL_DIR" ]]; then
warn "Installverzeichnis $INSTALL_DIR fehlt – bitte zuerst miyagi-install.sh ausführen"
return 1
fi
curl -fsSL "${base}/miyagi-backup.sh" -o "${INSTALL_DIR}/miyagi-backup.sh.new" || {
warn "Download fehlgeschlagen – miyagi-backup.sh bleibt unverändert"
return 1
}
mv "${INSTALL_DIR}/miyagi-backup.sh.new" "${INSTALL_DIR}/miyagi-backup.sh"
chmod +x "${INSTALL_DIR}/miyagi-backup.sh"
# Symlink sicherstellen
ln -sf "${INSTALL_DIR}/miyagi-backup.sh" "$SYMLINK"
# config.example aktualisieren (nie .conf überschreiben)
curl -fsSL "${base}/config.example" -o "${CONFIG_DIR}/config.example.new" 2>/dev/null \
&& mv "${CONFIG_DIR}/config.example.new" "${CONFIG_DIR}/config.example" \
|| warn "config.example konnte nicht aktualisiert werden"
# Hooks-Verzeichnis sicherstellen
mkdir -p "$HOOKS_DIR"
# Example-Hook aktualisieren (nicht ausführbar = bleibt inaktiv)
curl -fsSL "${base}/hooks.d/pre-shutdown/example-hook.sh" \
-o "${HOOKS_DIR}/example-hook.sh" 2>/dev/null \
&& info "example-hook.sh aktualisiert" \
|| warn "example-hook.sh konnte nicht aktualisiert werden"
ok "miyagi-backup.sh aktualisiert"
}
# ── Abhängigkeiten updaten (nur wenn bereits installiert) ─────────────────────
update_dep() {
local name="$1"
local url="$2"
local target="$3"
if [[ ! -f "$target" ]]; then
info "$name nicht installiert – übersprungen (install mit miyagi-install.sh)"
return 0
fi
info "Aktualisiere $name..."
curl -fsSL "$url" -o "${target}.new" 2>/dev/null || {
warn "$name Download fehlgeschlagen – bleibt unverändert"
rm -f "${target}.new"
return 1
}
mv "${target}.new" "$target"
chmod +x "$target"
ok "$name aktualisiert"
}
# ── Status-Zusammenfassung ────────────────────────────────────────────────────
print_status() {
echo ""
echo " ┌──────────────────────────────────────────┐"
printf " │ %-40s│\n" "miyagi-backup $([ -f "${INSTALL_DIR}/miyagi-backup.sh" ] && echo "✓ installiert" || echo "✗ fehlt")"
printf " │ %-40s│\n" "checkzfs $(command -v checkzfs &>/dev/null && echo "✓" || echo "✗ nicht installiert")"
printf " │ %-40s│\n" "check-snap-age $(command -v check-snapshot-age &>/dev/null && echo "✓" || echo "✗ nicht installiert")"
printf " │ %-40s│\n" "cleansnaps $(command -v cleansnaps &>/dev/null && echo "✓" || echo "✗ nicht installiert")"
printf " │ %-40s│\n" "ethtool $(command -v ethtool &>/dev/null && echo "✓" || echo "✗ nicht installiert")"
echo " └──────────────────────────────────────────┘"
echo ""
}
# ── Main ──────────────────────────────────────────────────────────────────────
main() {
[[ $EUID -ne 0 ]] && die "Bitte als root ausführen"
echo ""
echo " ╔══════════════════════════════════════╗"
echo " ║ miyagi-backup Updater v3 ║"
echo " ╚══════════════════════════════════════╝"
echo ""
self_update
update_miyagi
update_dep "checkzfs" "$CHECKZFS_URL" "/usr/local/bin/checkzfs"
update_dep "check-snapshot-age" "$CHECK_SNAPSHOT_AGE_URL" "/usr/local/bin/check-snapshot-age"
update_dep "cleansnaps" "$CLEANSNAPS_URL" "/usr/local/bin/cleansnaps"
update_dep "disk_smart_info" "$DISK_SMART_INFO_URL" "/usr/lib/check_mk_agent/plugins/disk_smart_info"
print_status
ok "Update abgeschlossen."
}
parse_args "$@"
main