47 Commits
Author SHA1 Message Date
patrickandClaude Sonnet 5 428348939b Spool-Dateien: Namen/Prefixe als Config-Variablen, Auto-Cleanup alter Dateien
Verzeichnis und Prefixe (PBS, Piggyback lokal/extern, checkzfs-extern)
jetzt als SPOOL_* Variablen im Config-Block, per Config-Datei
überschreibbar (aktuelle Defaults: PBS=120000, Piggyback=100000).

Nach jedem Spool-Transfer räumt ein find+mtime-Aufruf auf dem
Zielhost alte/verwaiste Varianten auf (SPOOL_CLEANUP_DAYS, Default 7),
damit sich Spool-Dateien nicht mehr unbegrenzt ansammeln.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 16:21:48 +02:00
patrickandClaude Sonnet 5 86f2ef6196 checkzfs-Defaults als überschreibbare Config-Variablen statt hartkodiert
write_zsync_config() nutzte fixe Literale (checkzfs_max_age=1500,2000
etc.). Jetzt als CHECKZFS_* Variablen mit Default via ${VAR:=...} direkt
nach dem Config-Sourcing gesetzt, per Config-Datei überschreibbar.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 14:39:23 +02:00
patrickandClaude Sonnet 5 08230c4aca Fix: verwaisten Monitoring-Block in run_pbs_backup() zurückverschoben
Block (Zeile 437-459) lag außerhalb jeder Funktion, öffnende Klammer
fehlte -> unerwartetes '}' bei Zeile 459, Skript komplett unparsebar
(bash -n schlug fehl, Updater übernahm kaputte Version).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 14:25:11 +02:00
patrick 1cda5e5599 miyagi-backup.sh aktualisiert 2026-09-15 10:10:55 +02:00
patrick d76eadfb57 miyagi-backup.sh aktualisiert 2026-08-13 22:14:15 +02:00
patrick 39417576b0 config.example aktualisiert
PBS_NAMESPACE='none' add
2026-07-23 16:24:28 +02:00
patrick 7cf6f15779 miyagi-backup.sh aktualisiert
PBSNAMESPACE add
2026-07-23 16:03:19 +02:00
patrick 11fdda1ef6 miyagi-backup.sh aktualisiert
dyndns route fix
2026-07-16 14:32:15 +02:00
patrick 0b52e87330 miyagi-backup.sh aktualisiert
fix 
    echo "zfs_auto_snapshot_label=\"$ZPUSHLABEL\""
    echo "zfs_auto_snapshot_engine=\"internal\""
2026-07-15 11:54:30 +02:00
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
patrick 2954647d87 miyagi-backup.sh aktualisiert
extra monitoring hosts
2025-06-29 20:52:26 +02:00
patrick 0efa02e0c2 config.example aktualisiert
extra monitoring hosts
2025-06-29 20:50:29 +02:00
9 changed files with 780 additions and 942 deletions

No files matched your search

-195
View File
@@ -1,195 +0,0 @@
# 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
@@ -0,0 +1,29 @@
# 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
```
+15 -19
View File
@@ -2,50 +2,46 @@
UPDATES='yes' # Proxmox VE und PBS Updates nach dem Lauf
SHUTDOWN='no' # System nach Ausführung herunterfahren?
DYNROUTE='yes' # DYNroute ja oder nein?
DDNS_GATEWAY='192.168.66.1' # DYNDNS Gateway
DDNS_SERVER='1.1.1.1' #DNS Server extern
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)
SOURCEPORT='22' # SSH-Port, normalerweise 22
SOURCEHOST='192.168.50.200' # IP des Quell-Proxmox-Servers
SOURCEUSER='root' # SSH-User für SOURCEHOST
SOURCEHOST='192.168.50.200' # IP des Quell-Proxmox-Servers
# Replikation (ZFS)
ZFSTRGT='rpool-ssd1/repl/pve200' # Zielpfad für Replikation
ZFSROOT='rpool/data' # Erstes Dataset vom Quellsystem
ZFSSECOND='rpool-hdd/data' # Optional zweites Dataset
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' # 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)
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
PBS_NAMESPACE='none' # Namespace auf PBS, setzen wenn verfügbar sonst none!
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
EPIGGYBACK='no' # Piggyback-Daten an Monitoring-Ziel senden?
EPIGGYBACK_PORT='222' # SSH-Port für EPIGGYBACK_HOST
EPIGGYBACK_HOST='192.168.66.10' # Monitoring-Zielhost für Piggyback
EPIGGYBACK_USER='root' # SSH-User für EPIGGYBACK_HOST
# External Checkzfs Host
ECHECKZFS='no' # CheckZFS-Daten an externen Monitoring-Host senden?
ECHECKZFS='no' # check_zfs-Output an Monitoring-Ziel senden?
ECHECKZFS_PORT='2222' # SSH-Port für ECHECKZFS_HOST
ECHECKZFS_HOST='192.168.50.210' # Monitoring-Zielhost für check_zfs
ECHECKZFS_USER='root' # SSH-User für ECHECKZFS_HOST
ECHECKZFS_HOST='192.168.50.210' # Monitoring-Zielhost für check_zfs
-34
View File
@@ -1,34 +0,0 @@
#!/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
+250 -331
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env bash
#!/bin/bash
set -uo pipefail
IFS=$'\n\t'
@@ -6,24 +6,6 @@ PATH="/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin"
SCRIPT_NAME=$(basename "$0")
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() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOGFILE"
}
@@ -58,15 +40,29 @@ if [[ -n "${CONFIG_FILE:-}" ]]; then
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=(
SOURCEPORT BACKUPSERVER ZSYNC MAINTDAY SHUTDOWN UPDATES
SOURCEHOST ZFSTRGT ZPUSHTAG ZPUSHMINKEEP ZPUSHKEEP ZPUSHLABEL
PBSHOST PBSPORT BACKUPSTORE BACKUPSTOREPBS BACKUPEXCLUDE REPLEXCLUDE DYNROUTE REBOOT
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 DDNS_SERVER)
fi
if [[ "${DYNROUTE,,}" == "yes" ]]; then
REQUIRED_VARS+=(DDNS_GATEWAY)
fi
MISSING_VARS=()
for var in "${REQUIRED_VARS[@]}"; do
@@ -87,29 +83,13 @@ if [[ -n "${CONFIG_FILE:-}" ]]; then
fi
fi
SOURCEUSER="${SOURCEUSER:-root}"
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
}
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 hostname)
SOURCEHOSTNAME=$(rssh "$SOURCEHOST" hostname)
log "Detected SOURCEHOSTNAME: $SOURCEHOSTNAME"
fi
}
@@ -147,11 +127,6 @@ wait_replace() {
}
set_wol_g_enabled() {
if [[ "$OS_TYPE" == "freebsd" ]]; then
log "FreeBSD erkannt – set_wol_g_enabled() wird übersprungen (ethtool/apt nicht verfügbar)."
return 0
fi
log "Checking if ethtool is installed..."
if ! command -v ethtool >/dev/null 2>&1; then
@@ -198,21 +173,21 @@ write_zsync_config() {
log "Writing zsync config to $conf_file"
{
echo "target=$ZFSTRGT"
echo "source=${SOURCEUSER}@${SOURCEHOST}"
echo "source=root@$SOURCEHOST"
echo "sshport=$SOURCEPORT"
echo "tag=$ZPUSHTAG"
echo "snapshot_filter=\"$ZPUSHFILTER\""
echo "min_keep=$ZPUSHMINKEEP"
echo "zfs_auto_snapshot_keep=$ZPUSHKEEP"
echo "zfs_auto_snapshot_label=zfs-auto-snap_$ZPUSHLABEL"
echo "zfs_auto_snapshot_engine=internal"
echo "checkzfs_disabled=0"
echo "checkzfs_local=0"
echo "zfs_auto_snapshot_label=\"$ZPUSHLABEL\""
echo "zfs_auto_snapshot_engine=\"internal\""
echo "checkzfs_disabled=$CHECKZFS_DISABLED"
echo "checkzfs_local=$CHECKZFS_LOCAL"
echo "checkzfs_prefix=miyagi-$SOURCEHOSTNAME-$(hostname)-$ZPUSHTAG"
echo "checkzfs_max_age=1500,2000"
echo "checkzfs_max_snapshot_count=180,200"
echo "checkzfs_spool=1"
echo "checkzfs_spool_maxage=100000"
echo "checkzfs_max_age=$CHECKZFS_MAX_AGE"
echo "checkzfs_max_snapshot_count=$CHECKZFS_MAX_SNAPSHOT_COUNT"
echo "checkzfs_spool=$CHECKZFS_SPOOL"
echo "checkzfs_spool_maxage=$CHECKZFS_SPOOL_MAXAGE"
} > "$conf_file"
}
@@ -225,132 +200,83 @@ run_zsync() {
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() {
if [[ "${UPDATES,,}" == "yes" ]]; then
if [[ "$OS_TYPE" == "freebsd" ]]; then
log "FreeBSD erkannt – lokales apt-Update wird übersprungen (apt nicht verfügbar)."
else
run_local_apt_updates
disable_pve_notifications
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_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
# PBS-Host-Update läuft unabhängig vom lokalen Update-Ergebnis/Skript-Host-OS
run_pbs_apt_updates
else
log "Updates disabled (UPDATES=$UPDATES)"
fi
@@ -359,7 +285,7 @@ run_remote_updates() {
send_piggyback() {
get_sourcehostname
local combined_host="miyagi-${SOURCEHOSTNAME}-$(hostname)"
local filename="100000_${combined_host}"
local filename="${SPOOL_PREFIX_PIGGYBACK}_${combined_host}"
log "Reminder: Add a host named ${combined_host} in CMK (without Agent, Piggyback enabled)!"
log "Creating piggyback file: $filename"
@@ -370,8 +296,10 @@ send_piggyback() {
echo "<<<<>>>>"
} > "$filename"
if rscp "$filename" "${SOURCEUSER}@${SOURCEHOST}:/var/lib/check_mk_agent/spool/"; then
if rscp "$filename" "$SOURCEHOST:${SPOOL_DIR}/"; then
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
log "ERROR: Failed to send piggyback data to $SOURCEHOST"
fi
@@ -382,18 +310,14 @@ send_piggyback() {
run_pbs_log_clear() {
log "Running clear LOG on PBS host ($PBSHOST)..."
if ! rpbssh find /var/log/proxmox-backup/tasks/ -type f -mtime +60 -delete; then
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_pve_log_clear() {
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
}
vzdump_success=false
pbs_enabled_by_script=false
run_pbs_backup() {
if [[ "${BACKUPSERVER,,}" != "yes" ]]; then
@@ -404,67 +328,152 @@ run_pbs_backup() {
get_sourcehostname
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..."
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..."
rssh "pvesm set '$BACKUPSTORE' --disable 0 && sleep 10"
pbs_enabled_by_script=true
if rssh root@"$SOURCEHOST" \
"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
log "PBS-Storage '$BACKUPSTORE' ist bereits aktiviert."
pbs_enabled_by_script=false
fi
vzdump_success=false
# ------------------------------------------------------------
# PBS Namespace setzen
# ------------------------------------------------------------
PBS_NS_ARG=()
# Versuch: vzdump mit Change Detection
## Eintrag muss in /etc/vzdump.conf entries-max 100000000
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 mit Change Detection
# ------------------------------------------------------------
log "Starte vzdump mit Change Detection..."
if rssh vzdump --pbs-change-detection-mode metadata \
--node "$SOURCEHOSTNAME" --storage "$BACKUPSTORE" \
--exclude "$BACKUPEXCLUDE" --mode snapshot --all 1 \
if rssh root@"$SOURCEHOST" \
vzdump --pbs-change-detection-mode metadata \
"${PBS_NS_ARG[@]}" \
--node "$SOURCEHOSTNAME" \
--storage "$BACKUPSTORE" \
--exclude "$BACKUPEXCLUDE" \
--mode snapshot \
--all 1 \
--notes-template '{{guestname}}'; then
log "vzdump (Change Detection) erfolgreich."
vzdump_success=true
else
log "vzdump (Change Detection) fehlgeschlagen – versuche Fallback ohne Change Detection..."
if rssh vzdump \
--node "$SOURCEHOSTNAME" --storage "$BACKUPSTORE" \
--exclude "$BACKUPEXCLUDE" --mode snapshot --all 1 \
# ----------------------------------------------------------
# Fallback ohne Change Detection
# ----------------------------------------------------------
if rssh root@"$SOURCEHOST" \
vzdump \
"${PBS_NS_ARG[@]}" \
--node "$SOURCEHOSTNAME" \
--storage "$BACKUPSTORE" \
--exclude "$BACKUPEXCLUDE" \
--mode snapshot \
--all 1 \
--notes-template '{{guestname}}'; then
log "Fallback-vzdump erfolgreich."
vzdump_success=true
else
log "FEHLER: vzdump fehlgeschlagen – auch im Fallback."
vzdump_success=false
fi
fi
# ------------------------------------------------------------
# PBS-Storage nach erfolgreichem Backup wieder deaktivieren
#
# WICHTIG:
# Nur deaktivieren, wenn dieses Script den Storage
# vorher selbst aktiviert hat.
# ------------------------------------------------------------
if [[ "$vzdump_success" == true && "$pbs_enabled_by_script" == true ]]; then
log "Deaktiviere temporär aktiviertes PBS-Storage '$BACKUPSTORE' auf $SOURCEHOST..."
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
# PBS-Storage ggf. wieder deaktivieren
if [[ "$vzdump_success" == true && "$pbs_enabled_by_script" == true ]]; then
log "Deaktiviere temporär aktiviertes PBS-Storage '$BACKUPSTORE' auf $SOURCEHOST..."
rssh "pvesm set '$BACKUPSTORE' --disable 1"
fi
# ------------------------------------------------------------
# Monitoring-Ausgabe
# ------------------------------------------------------------
if [[ "$vzdump_success" == true ]]; then
echo "0 DailyPBS - Daily Backup erfolgreich" > /tmp/cmk_tmp.out
else
echo "2 DailyPBS - Daily Backup FEHLGESCHLAGEN" > /tmp/cmk_tmp.out
fi
local pbs_spool_file="${SPOOL_PREFIX_PBS}_checkpbs"
{
echo "<<<local>>>"
cat /tmp/cmk_tmp.out
} > /tmp/1200000_checkpbs
} > "/tmp/${pbs_spool_file}"
rscp /tmp/1200000_checkpbs "${SOURCEUSER}@${SOURCEHOST}":/var/lib/check_mk_agent/spool \
|| log "Fehler beim Übertragen des Monitoring-Outputs via SCP"
if rscp "/tmp/${pbs_spool_file}" root@"$SOURCEHOST":"${SPOOL_DIR}"; then
rssh root@"$SOURCEHOST" "find '${SPOOL_DIR}' -maxdepth 1 -name '*_checkpbs' -not -name '${pbs_spool_file}' -mtime +${SPOOL_CLEANUP_DAYS} -delete" \
|| 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
# ------------------------------------------------------------
# Rückgabestatus
# ------------------------------------------------------------
if [[ "$vzdump_success" == true ]]; then
return 0
else
return 1
fi
}
run_maintenance() {
@@ -475,10 +484,10 @@ run_maintenance() {
if [[ "$(date +%u)" == "$MAINTDAY" ]]; then
log "Running maintenance..."
PRUNEJOB=$(rpbssh proxmox-backup-manager prune-job list --output-format json-pretty | grep -m 1 "id" | cut -d'"' -f4)
rpbssh proxmox-backup-manager prune-job run "$PRUNEJOB"
rpbssh proxmox-backup-manager garbage-collection start "$BACKUPSTOREPBS"
rpbssh proxmox-backup-manager verify backup
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
@@ -489,7 +498,7 @@ run_scrub_stop() {
local ssh_cmd=()
if [[ "$mode" == "remote" ]]; then
ssh_cmd=(ssh -o BatchMode=yes -o ConnectTimeout=10 -p "$SOURCEPORT" "${SOURCEUSER}@${SOURCEHOST}")
ssh_cmd=(ssh -p "$SOURCEPORT" root@"$SOURCEHOST")
fi
"${ssh_cmd[@]}" bash -c '
@@ -509,8 +518,8 @@ run_scrub_stop() {
}
write_pbs_status() {
local spool_file="/var/lib/check_mk_agent/spool/90000_checkpbs_local"
local repo="${PBS_REPO_USER}@${PBSHOST}:${BACKUPSTOREPBS}"
local spool_file="${SPOOL_DIR}/${SPOOL_PREFIX_PBS}_checkpbs_local"
local repo="$BACKUPSTOREPBS"
local tmpfile
tmpfile=$(mktemp)
@@ -519,11 +528,6 @@ write_pbs_status() {
return
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"
local now
@@ -548,8 +552,8 @@ write_pbs_status() {
fi
fi
# Backup-Daten extrahieren (direkt vom Skript-Host gegen PBSHOST, mit PBS_PASSWORD authentifiziert)
PBS_PASSWORD="$PBS_PASSWORD" proxmox-backup-client list --repository "$repo" --output-format json 2>/dev/null | \
# 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"
@@ -568,7 +572,7 @@ write_pbs_status() {
if [[ -n "$backup_type" && -n "$backup_id" && -n "$backup_time" ]]; then
local age status timestamp msg
age=$((now - backup_time))
timestamp=$(epoch_to_str "$backup_time")
timestamp=$(date -d "@$backup_time" "+%Y-%m-%d %H:%M:%S")
if (( age < warn_threshold )); then
status=0
@@ -593,27 +597,6 @@ write_pbs_status() {
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() {
if [[ "${SHUTDOWN,,}" == "yes" ]]; then
if zfs_replace; then
@@ -626,7 +609,6 @@ shutdown_now() {
send_piggyback
send_piggyback_external
send_checkzfs_external
run_pre_shutdown_hooks
log "Shutting down now...in 60sec"
sleep 60
shutdown now
@@ -640,11 +622,12 @@ send_piggyback_external() {
log "Externer Piggyback-Export deaktiviert."
return
fi
write_zsync_config
get_sourcehostname
log "Ermittelter SOURCEHOSTNAME: $SOURCEHOSTNAME"
local combined_host="miyagi-${SOURCEHOSTNAME}-$(hostname)"
local spool_file="100000_${combined_host}_piggyback_external"
local spool_file="${SPOOL_PREFIX_PIGGYBACK_EXTERNAL}_${combined_host}_external"
local temp_dir
temp_dir=$(mktemp -d)
@@ -656,8 +639,10 @@ send_piggyback_external() {
echo "<<<<>>>>"
} > "$temp_dir/$spool_file"
if scp -P "$EPIGGYBACK_PORT" "$temp_dir/$spool_file" "${EPIGGYBACK_USER}@${EPIGGYBACK_HOST}:/var/lib/check_mk_agent/spool/"; then
if scp -P "$EPIGGYBACK_PORT" "$temp_dir/$spool_file" "$EPIGGYBACK_HOST:${SPOOL_DIR}/"; then
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
log "Fehler beim Übertragen der Piggyback-Daten an $EPIGGYBACK_HOST"
rm -rf "$temp_dir"
@@ -668,70 +653,9 @@ send_piggyback_external() {
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() {
if [[ "${ECHECKZFS,,}" != "yes" ]]; then
log "Externer CheckZFS deaktiviert."
if [[ "${ECHECKZFS,,}" != "yes" ]]; then
log "Externer Piggyback-Export deaktiviert."
return
fi
local config="/etc/bashclub/${SOURCEHOST}.conf"
@@ -741,6 +665,11 @@ send_checkzfs_external() {
fi
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
local checkzfs_cmd="${checkzfs_cmd:-$(command -v checkzfs)}"
@@ -758,20 +687,20 @@ send_checkzfs_external() {
elif [[ "$source" == "local" && "$value" == "all" ]]; then
filter+="${name}|"
fi
done < <(rssh "zfs get -H -o name,value,source -t filesystem,volume $ZPUSHTAG")
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="100000_${combined}_checkzfs_external"
local spooldest="${SPOOL_PREFIX_CHECKZFS_EXTERNAL}_${combined}_checkzfs_external"
log "Führe checkzfs aus..."
{
echo "<<<local>>>"
"$checkzfs_cmd" \
--source ${SOURCEUSER}@"${SOURCEHOST}:${SOURCEPORT}" \
--source root@"${SOURCEHOST}:${SOURCEPORT}" \
--filter "$filter" \
--replicafilter "^${ZFSTRGT}" \
--prefix "$checkzfs_prefix" \
@@ -780,8 +709,10 @@ send_checkzfs_external() {
--output checkmk
} > "$spoolfile"
if scp -P "$ECHECKZFS_PORT" "$spoolfile" ${ECHECKZFS_USER}@"$ECHECKZFS_HOST":/var/lib/check_mk_agent/spool/"$spooldest"; then
if scp -P "$ECHECKZFS_PORT" "$spoolfile" root@"$ECHECKZFS_HOST":"${SPOOL_DIR}/${spooldest}"; then
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
log "Fehler beim Übertragen der Spool-Datei an $ECHECKZFS_HOST"
fi
@@ -881,8 +812,7 @@ if [[ $# -eq 0 ]]; then
run_pbs_backup
run_maintenance
run_pbs_log_clear
run_pve_log_clear
else
else
log "BACKUPSERVER ist nicht aktiviert (BACKUPSERVER=$BACKUPSERVER), überspringe Backup."
fi
run_remote_updates
@@ -898,23 +828,12 @@ if [[ $# -eq 0 ]]; then
usage
fi
else
# Funktionen die keine Config brauchen
CONFIG_FREE_FUNCTIONS=(help detect_os detect_host_type zfs_replace wait_replace set_wol_g_enabled run_scrub_stop)
case "$1" in
help)
declare -F | awk '{print $3}' | grep -v "^_" | grep -v "^main$"
;;
*)
if declare -F "$1" >/dev/null 2>&1; then
config_required=true
for fn in "${CONFIG_FREE_FUNCTIONS[@]}"; do
[[ "$1" == "$fn" ]] && config_required=false && break
done
if [[ "$config_required" == true && -z "${SOURCEHOST:-}" ]]; then
log "Funktion '$1' benötigt eine Config. Aufruf: $0 -c /etc/miyagi-backup/backup.conf $1"
exit 1
fi
"$@"
else
log "Function $1 not found."
+220
View File
@@ -0,0 +1,220 @@
#!/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
@@ -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"
+157 -195
View File
@@ -1,41 +1,29 @@
#!/usr/bin/env bash
# miyagi-backup Installer v3
set -uo pipefail
#!/bin/bash
# Author: (C) 2025 Patrick Perlbach <patrick@perlbach24.de>
# --- Konfiguration und Initiale Variablen ---
FORCE_YES=0
DRY_RUN=0
DEV_MODE=0
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() {
cat <<EOF
Usage: $0 [OPTIONS]
Usage: $0 -c <config> [--yes] [--dry-run]
-c <file> Konfigurationsdatei laden (für SSH-Check + ZFS-Tagging)
--yes Keine Rückfragen
--dry-run Zeigt nur was gemacht würde
--dev Installiere von Gitea (dev) statt GitHub (stable)
--help Diese Hilfe
-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 0
exit 1
}
run_or_echo() {
if [[ "$DRY_RUN" == "1" ]]; then
echo "[DRY-RUN] $*"
else
eval "$@"
fi
}
parse_args() {
@@ -44,250 +32,224 @@ parse_args() {
-c) CONFIG_FILE="$2"; shift 2 ;;
--yes) FORCE_YES=1; shift ;;
--dry-run) DRY_RUN=1; shift ;;
--dev) DEV_MODE=1; shift ;;
--help) usage ;;
*) die "Unbekannter Parameter: $1" ;;
*) echo "[ERROR] Unbekannter Parameter: $1"; usage ;;
esac
done
if [[ -n "$CONFIG_FILE" ]]; then
[[ ! -f "$CONFIG_FILE" ]] && die "Konfigurationsdatei nicht gefunden: $CONFIG_FILE"
bash -n "$CONFIG_FILE" || die "Syntaxfehler in $CONFIG_FILE"
source "$CONFIG_FILE"
SOURCEUSER="${SOURCEUSER:-root}"
fi
}
[[ -z "$CONFIG_FILE" || ! -f "$CONFIG_FILE" ]] && {
echo "[ERROR] Konfigurationsdatei fehlt oder ungültig!"
exit 1
}
run() {
if [[ "$DRY_RUN" -eq 1 ]]; then
echo "[DRY-RUN] $*"
else
"$@"
fi
source "$CONFIG_FILE"
}
confirm() {
[[ "$FORCE_YES" -eq 1 ]] && return 0
if [[ "$FORCE_YES" -eq 1 ]]; then
return 0
fi
while true; do
read -rp "$1 [j/N]: " yn
case "$yn" in
case $yn in
[JjYy]*) return 0 ;;
[Nn]*|"") return 1 ;;
*) echo "Bitte j oder n eingeben." ;;
*) echo "Bitte j (ja) oder n (nein) eingeben." ;;
esac
done
}
ensure_dialog() {
if ! command -v dialog &>/dev/null; then
info "Installiere dialog..."
run apt-get install -y -qq dialog || die "dialog konnte nicht installiert werden"
fi
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\""
}
get_base_url() {
[[ "$DEV_MODE" -eq 1 ]] && echo "$GITEA_BASE" || echo "$GITHUB_BASE"
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"
}
# ── Installationsschritte ─────────────────────────────────────────────────────
install_miyagi() {
local base
base="$(get_base_url)"
local source_label
[[ "$DEV_MODE" -eq 1 ]] && source_label="Gitea (dev)" || source_label="GitHub (stable)"
info "Installiere miyagi-backup von $source_label..."
run mkdir -p "$INSTALL_DIR"
run mkdir -p "$CONFIG_DIR"
run mkdir -p "$HOOKS_DIR"
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_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_checkzfs() {
info "Installiere checkzfs..."
run curl -fsSL "$CHECKZFS_URL" -o /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_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() {
info "Installiere cleansnaps..."
run curl -fsSL "$CLEANSNAPS_URL" -o /usr/local/bin/cleansnaps
run chmod +x /usr/local/bin/cleansnaps
ok "cleansnaps installiert"
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_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_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_ethtool() {
info "Installiere ethtool..."
run apt-get install -y -qq ethtool
ok "ethtool installiert"
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() {
[[ -z "${SOURCEHOST:-}" ]] && warn "Kein SOURCEHOST in Config – SSH-Check übersprungen" && return
info "Teste SSH-Verbindung zu ${SOURCEUSER}@${SOURCEHOST}:${SOURCEPORT}..."
echo "[INFO] Teste SSH-Verbindung zu $SOURCEHOST ..."
if ssh -p "${SOURCEPORT:-22}" -o BatchMode=yes -o ConnectTimeout=5 \
"${SOURCEUSER}@${SOURCEHOST}" "echo ok" &>/dev/null; then
ok "SSH-Verbindung via Schlüssel funktioniert"
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
warn "Keine schlüsselbasierte SSH-Verbindung möglich"
if confirm "SSH-Schlüssel jetzt auf ${SOURCEHOST} kopieren (ssh-copy-id)?"; then
run ssh-copy-id -p "${SOURCEPORT:-22}" "${SOURCEUSER}@${SOURCEHOST}"
ok "Schlüssel kopiert – bitte Installer erneut starten zum Verifizieren"
exit 0
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() {
[[ -z "${SOURCEHOST:-}" ]] && warn "Kein SOURCEHOST – ZFS-Tagging übersprungen" && return
echo "[INFO] Lade ZFS-Datasets vom Remote-Host..."
info "Lade ZFS-Datasets von ${SOURCEHOST}..."
local local_hostname remote_hostname tag_key tag_value
local_hostname="$(hostname -s)"
remote_hostname="$(ssh -p "${SOURCEPORT:-22}" "${SOURCEUSER}@${SOURCEHOST}" "hostname -s" 2>/dev/null)" \
|| { warn "SSH-Verbindung fehlgeschlagen – ZFS-Tagging übersprungen"; return; }
LOCAL_HOSTNAME=$(hostname -s)
REMOTE_HOSTNAME=$(ssh -p "$SOURCEPORT" root@"$SOURCEHOST" "hostname -s")
TAG_KEY="bashclub:miyagi-${REMOTE_HOSTNAME}-${LOCAL_HOSTNAME}"
TAG_VALUE="subvols"
tag_key="bashclub:miyagi-${remote_hostname}-${local_hostname}"
tag_value="subvols"
mapfile -t pools < <(ssh -p "${SOURCEPORT:-22}" "${SOURCEUSER}@${SOURCEHOST}" \
"zfs list -H -o name" 2>/dev/null | \
# 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
warn "Keine geeigneten ZFS-Datasets gefunden"
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:-22}" "${SOURCEUSER}@${SOURCEHOST}" \
zfs get -H -o value "$tag_key" "$pool" 2>/dev/null)"
[[ "$tag" == "$tag_value" ]] && status="on" || status="off"
options+=("$pool" "" "$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=$(dialog --clear \
--backtitle "miyagi-backup Installer" \
--title "ZFS Pools taggen" \
--checklist "Datasets für Replikation auswählen:" 20 78 12 \
selected=$(whiptail --title "ZFS Pools auswählen" \
--checklist "Wähle Datasets zum Taggen aus:" 20 78 12 \
"${options[@]}" \
3>&1 1>&2 2>&3)
clear
[[ $? -ne 0 ]] && info "ZFS-Tagging abgebrochen" && return
if [[ $? -ne 0 ]]; then
echo "[INFO] Auswahl abgebrochen."
return
fi
local selected_array=()
read -ra selected_array <<< "$selected"
eval "selected_array=($selected)"
for pool in "${pools[@]}"; do
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
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
[[ "$sel" == "\"$pool\"" || "$sel" == "$pool" ]] && is_selected=1 && break
if [[ "$sel" == "$pool" ]]; then
is_selected=1
break
fi
done
if [[ "$is_selected" -eq 1 && "$prev_status" == "off" ]]; then
info "Tagge $pool"
run ssh -p "${SOURCEPORT:-22}" "${SOURCEUSER}@${SOURCEHOST}" \
zfs set "${tag_key}=${tag_value}" "$pool"
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
info "Entferne Tag von $pool"
run ssh -p "${SOURCEPORT:-22}" "${SOURCEUSER}@${SOURCEHOST}" \
zfs inherit "$tag_key" "$pool"
echo " → Entferne Tag von $pool"
run_or_echo ssh -p "$SOURCEPORT" root@"$SOURCEHOST" zfs inherit "$TAG_KEY" "$pool"
fi
done
ok "ZFS-Tagging abgeschlossen"
}
# ── Hauptmenü ─────────────────────────────────────────────────────────────────
main() {
[[ $EUID -ne 0 ]] && die "Bitte als root ausführen"
# 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"
ensure_dialog
FIX_INTERFACE_NAMES_URL="https://raw.githubusercontent.com/bashclub/trmm-scripts/refs/heads/main/debian-fix-interface-names.sh"
echo ""
echo " ╔══════════════════════════════════════╗"
echo " ║ miyagi-backup Installer v3 ║"
echo " ╚══════════════════════════════════════╝"
echo ""
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"
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
DISK_SMART_INFO_URL="https://raw.githubusercontent.com/bashclub/checkmk-smart/main/disk_smart_info.py"
[[ $? -ne 0 ]] && echo "Abgebrochen." && exit 0
[[ -z "$choices" ]] && echo "Nichts ausgewählt." && exit 0
CLEANSNAPS_URL="https://raw.githubusercontent.com/bashclub/zfs-housekeeping/refs/heads/main/cleansnaps.sh"
[[ "$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
CHECK_SNAPSHOT_AGE_URL="https://gitea.perlbach24.de/scripte/check-zfs-replication/raw/branch/main/check-snapshot-age"
echo ""
ok "Installation abgeschlossen."
echo ""
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
-168
View File
@@ -1,168 +0,0 @@
#!/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