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 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='yes' # DYNroute ja oder nein? DYNROUTE='no' #Dynroute setzen?
DDNS_GATEWAY='192.168.66.1' # DYNDNS Gateway DDNS_GATEWAY='192.168.66.1' # Gateway
DDNS_SERVER='1.1.1.1' #DNS Server extern DDNS_SERVER='1.1.1.1' # Externer DNS Server
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)
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 # ZFS Zsync Replikation
ZSYNC='yes' # ZSYNC aktivieren (ja/nein) 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 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-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.) 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' # SSH-Port PBS PBSPORT='22'
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
BACKUPEXCLUDE='10,3252,3253,3254' # VM/CT-IDs, die vom Backup ausgeschlossen sind PBS_NAMESPACE='none' # Namespace auf PBS, setzen wenn verfügbar sonst none!
REPLEXCLUDE="$BACKUPEXCLUDE" # Diese auch von Replikation ausschließen BACKUPEXCLUDE='9999,99988' # VM/CT-IDs, die vom Backup ausgeschlossen sind
REPLEXCLUDE=$BACKUPEXCLUDE # Diese auch von Replikation ausschließen
# Zusätzliche Monitoring-Ziele # 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' # 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_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
@@ -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 set -uo pipefail
IFS=$'\n\t' IFS=$'\n\t'
@@ -6,24 +6,6 @@ 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"
} }
@@ -58,15 +40,29 @@ 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 ZFSTRGT ZPUSHTAG ZPUSHMINKEEP ZPUSHKEEP ZPUSHLABEL SOURCEHOST ZFSROOT ZFSSECOND ZFSTRGT ZPUSHTAG ZPUSHMINKEEP ZPUSHKEEP ZPUSHLABEL
PBSHOST PBSPORT BACKUPSTORE BACKUPSTOREPBS BACKUPEXCLUDE REPLEXCLUDE DYNROUTE REBOOT PBSHOST BACKUPSTORE BACKUPSTOREPBS BACKUPEXCLUDE REPLEXCLUDE DYNROUTE REBOOT DDNS_GATEWAY
) )
if [[ "${DYNROUTE,,}" == "yes" ]]; then if [[ "${DYNROUTE,,}" == "yes" ]]; then
REQUIRED_VARS+=(DDNS_GATEWAY DDNS_SERVER) REQUIRED_VARS+=(DDNS_GATEWAY)
fi fi
MISSING_VARS=() MISSING_VARS=()
for var in "${REQUIRED_VARS[@]}"; do for var in "${REQUIRED_VARS[@]}"; do
@@ -87,29 +83,13 @@ if [[ -n "${CONFIG_FILE:-}" ]]; then
fi fi
fi fi
SOURCEUSER="${SOURCEUSER:-root}" rssh() { ssh -p "$SOURCEPORT" "$@"; }
PBSUSER="${PBSUSER:-root}" rscp() { scp -P "$SOURCEPORT" "$@"; }
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 hostname) SOURCEHOSTNAME=$(rssh "$SOURCEHOST" hostname)
log "Detected SOURCEHOSTNAME: $SOURCEHOSTNAME" log "Detected SOURCEHOSTNAME: $SOURCEHOSTNAME"
fi fi
} }
@@ -147,11 +127,6 @@ 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
@@ -198,21 +173,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=${SOURCEUSER}@${SOURCEHOST}" echo "source=root@$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=zfs-auto-snap_$ZPUSHLABEL" echo "zfs_auto_snapshot_label=\"$ZPUSHLABEL\""
echo "zfs_auto_snapshot_engine=internal" echo "zfs_auto_snapshot_engine=\"internal\""
echo "checkzfs_disabled=0" echo "checkzfs_disabled=$CHECKZFS_DISABLED"
echo "checkzfs_local=0" echo "checkzfs_local=$CHECKZFS_LOCAL"
echo "checkzfs_prefix=miyagi-$SOURCEHOSTNAME-$(hostname)-$ZPUSHTAG" echo "checkzfs_prefix=miyagi-$SOURCEHOSTNAME-$(hostname)-$ZPUSHTAG"
echo "checkzfs_max_age=1500,2000" echo "checkzfs_max_age=$CHECKZFS_MAX_AGE"
echo "checkzfs_max_snapshot_count=180,200" echo "checkzfs_max_snapshot_count=$CHECKZFS_MAX_SNAPSHOT_COUNT"
echo "checkzfs_spool=1" echo "checkzfs_spool=$CHECKZFS_SPOOL"
echo "checkzfs_spool_maxage=100000" echo "checkzfs_spool_maxage=$CHECKZFS_SPOOL_MAXAGE"
} > "$conf_file" } > "$conf_file"
} }
@@ -225,132 +200,83 @@ 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
if [[ "$OS_TYPE" == "freebsd" ]]; then log "Running updates on local system..."
log "FreeBSD erkannt – lokales apt-Update wird übersprungen (apt nicht verfügbar)."
else TIMESTAMP=$(date +"%Y-%m-%d_%H-%M-%S")
run_local_apt_updates SNAPSHOT_TAG="pve-update-via-miyagi"
disable_pve_notifications 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 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
@@ -359,7 +285,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="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 "Reminder: Add a host named ${combined_host} in CMK (without Agent, Piggyback enabled)!"
log "Creating piggyback file: $filename" log "Creating piggyback file: $filename"
@@ -370,8 +296,10 @@ send_piggyback() {
echo "<<<<>>>>" echo "<<<<>>>>"
} > "$filename" } > "$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" 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
@@ -382,18 +310,14 @@ 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 ! 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" log "Error clearing PBS LOG on $PBSHOST"
fi fi
} }
run_pve_log_clear() { vzdump_success=false
log "Running clear LOG on PVE host ($PBSHOST)..." pbs_enabled_by_script=false
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
@@ -404,67 +328,152 @@ 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"
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 else
log "PBS-Storage '$BACKUPSTORE' ist bereits aktiviert." log "PBS-Storage '$BACKUPSTORE' ist bereits aktiviert."
pbs_enabled_by_script=false
fi fi
vzdump_success=false # ------------------------------------------------------------
# PBS Namespace setzen
# ------------------------------------------------------------
PBS_NS_ARG=()
# Versuch: vzdump mit Change Detection if [[ -n "$PBS_NAMESPACE" && "${PBS_NAMESPACE,,}" != "none" ]]; then
## Eintrag muss in /etc/vzdump.conf entries-max 100000000 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..." log "Starte vzdump mit Change Detection..."
if rssh vzdump --pbs-change-detection-mode metadata \
--node "$SOURCEHOSTNAME" --storage "$BACKUPSTORE" \ if rssh root@"$SOURCEHOST" \
--exclude "$BACKUPEXCLUDE" --mode snapshot --all 1 \ vzdump --pbs-change-detection-mode metadata \
"${PBS_NS_ARG[@]}" \
--node "$SOURCEHOSTNAME" \
--storage "$BACKUPSTORE" \
--exclude "$BACKUPEXCLUDE" \
--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 \ # ----------------------------------------------------------
--node "$SOURCEHOSTNAME" --storage "$BACKUPSTORE" \ # Fallback ohne Change Detection
--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
# ------------------------------------------------------------
# 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
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 # 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/1200000_checkpbs } > "/tmp/${pbs_spool_file}"
rscp /tmp/1200000_checkpbs "${SOURCEUSER}@${SOURCEHOST}":/var/lib/check_mk_agent/spool \ if rscp "/tmp/${pbs_spool_file}" root@"$SOURCEHOST":"${SPOOL_DIR}"; then
|| log "Fehler beim Übertragen des Monitoring-Outputs via SCP" 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 #write_pbs_status
# ------------------------------------------------------------
# Rückgabestatus
# ------------------------------------------------------------
if [[ "$vzdump_success" == true ]]; then
return 0
else
return 1
fi
} }
run_maintenance() { run_maintenance() {
@@ -475,10 +484,10 @@ run_maintenance() {
if [[ "$(date +%u)" == "$MAINTDAY" ]]; then if [[ "$(date +%u)" == "$MAINTDAY" ]]; then
log "Running maintenance..." log "Running maintenance..."
PRUNEJOB=$(rpbssh proxmox-backup-manager prune-job list --output-format json-pretty | grep -m 1 "id" | cut -d'"' -f4) PRUNEJOB=$(ssh -p $PBSPORT "$PBSHOST" 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" ssh -p $PBSPORT root@"$PBSHOST" proxmox-backup-manager prune-job run "$PRUNEJOB"
rpbssh proxmox-backup-manager garbage-collection start "$BACKUPSTOREPBS" ssh -p $PBSPORT root@"$PBSHOST" proxmox-backup-manager garbage-collection start "$BACKUPSTOREPBS"
rpbssh proxmox-backup-manager verify backup ssh -p $PBSPORT root@"$PBSHOST" proxmox-backup-manager verify backup
else else
log "No maintenance scheduled for today." log "No maintenance scheduled for today."
fi fi
@@ -489,7 +498,7 @@ run_scrub_stop() {
local ssh_cmd=() local ssh_cmd=()
if [[ "$mode" == "remote" ]]; then 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 fi
"${ssh_cmd[@]}" bash -c ' "${ssh_cmd[@]}" bash -c '
@@ -509,8 +518,8 @@ run_scrub_stop() {
} }
write_pbs_status() { write_pbs_status() {
local spool_file="/var/lib/check_mk_agent/spool/90000_checkpbs_local" local spool_file="${SPOOL_DIR}/${SPOOL_PREFIX_PBS}_checkpbs_local"
local repo="${PBS_REPO_USER}@${PBSHOST}:${BACKUPSTOREPBS}" local repo="$BACKUPSTOREPBS"
local tmpfile local tmpfile
tmpfile=$(mktemp) tmpfile=$(mktemp)
@@ -519,11 +528,6 @@ 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
@@ -548,8 +552,8 @@ write_pbs_status() {
fi fi
fi fi
# Backup-Daten extrahieren (direkt vom Skript-Host gegen PBSHOST, mit PBS_PASSWORD authentifiziert) # Backup-Daten extrahieren
PBS_PASSWORD="$PBS_PASSWORD" proxmox-backup-client list --repository "$repo" --output-format json 2>/dev/null | \ rssh root@"$SOURCEHOST" 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"
@@ -568,7 +572,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=$(epoch_to_str "$backup_time") timestamp=$(date -d "@$backup_time" "+%Y-%m-%d %H:%M:%S")
if (( age < warn_threshold )); then if (( age < warn_threshold )); then
status=0 status=0
@@ -593,27 +597,6 @@ 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
@@ -626,7 +609,6 @@ 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
@@ -640,11 +622,12 @@ 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="100000_${combined_host}_piggyback_external" local spool_file="${SPOOL_PREFIX_PIGGYBACK_EXTERNAL}_${combined_host}_external"
local temp_dir local temp_dir
temp_dir=$(mktemp -d) temp_dir=$(mktemp -d)
@@ -656,8 +639,10 @@ send_piggyback_external() {
echo "<<<<>>>>" echo "<<<<>>>>"
} > "$temp_dir/$spool_file" } > "$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" 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"
@@ -668,70 +653,9 @@ 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 CheckZFS deaktiviert." log "Externer Piggyback-Export deaktiviert."
return return
fi fi
local config="/etc/bashclub/${SOURCEHOST}.conf" local config="/etc/bashclub/${SOURCEHOST}.conf"
@@ -741,6 +665,11 @@ 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)}"
@@ -758,20 +687,20 @@ send_checkzfs_external() {
elif [[ "$source" == "local" && "$value" == "all" ]]; then elif [[ "$source" == "local" && "$value" == "all" ]]; then
filter+="${name}|" filter+="${name}|"
fi 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%|}" 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="100000_${combined}_checkzfs_external" local spooldest="${SPOOL_PREFIX_CHECKZFS_EXTERNAL}_${combined}_checkzfs_external"
log "Führe checkzfs aus..." log "Führe checkzfs aus..."
{ {
echo "<<<local>>>" echo "<<<local>>>"
"$checkzfs_cmd" \ "$checkzfs_cmd" \
--source ${SOURCEUSER}@"${SOURCEHOST}:${SOURCEPORT}" \ --source root@"${SOURCEHOST}:${SOURCEPORT}" \
--filter "$filter" \ --filter "$filter" \
--replicafilter "^${ZFSTRGT}" \ --replicafilter "^${ZFSTRGT}" \
--prefix "$checkzfs_prefix" \ --prefix "$checkzfs_prefix" \
@@ -780,8 +709,10 @@ send_checkzfs_external() {
--output checkmk --output checkmk
} > "$spoolfile" } > "$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" 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
@@ -881,8 +812,7 @@ if [[ $# -eq 0 ]]; then
run_pbs_backup run_pbs_backup
run_maintenance run_maintenance
run_pbs_log_clear run_pbs_log_clear
run_pve_log_clear else
else
log "BACKUPSERVER ist nicht aktiviert (BACKUPSERVER=$BACKUPSERVER), überspringe Backup." log "BACKUPSERVER ist nicht aktiviert (BACKUPSERVER=$BACKUPSERVER), überspringe Backup."
fi fi
run_remote_updates run_remote_updates
@@ -898,23 +828,12 @@ 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
@@ -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 #!/bin/bash
# miyagi-backup Installer v3 # Author: (C) 2025 Patrick Perlbach <patrick@perlbach24.de>
set -uo pipefail # --- Konfiguration und Initiale Variablen ---
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 [OPTIONS] Usage: $0 -c <config> [--yes] [--dry-run]
-c <file> Konfigurationsdatei laden (für SSH-Check + ZFS-Tagging) -c <file> Pfad zur Konfigurationsdatei (Pflicht)
--yes Keine Rückfragen --yes Automatische Ausführung ohne Rückfrage
--dry-run Zeigt nur was gemacht würde --dry-run Zeigt nur, was gemacht würde
--dev Installiere von Gitea (dev) statt GitHub (stable) --help Diese Hilfe anzeigen
--help Diese Hilfe
EOF EOF
exit 0 exit 1
}
run_or_echo() {
if [[ "$DRY_RUN" == "1" ]]; then
echo "[DRY-RUN] $*"
else
eval "$@"
fi
} }
parse_args() { parse_args() {
@@ -44,250 +32,224 @@ 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 ;;
*) die "Unbekannter Parameter: $1" ;; *) echo "[ERROR] Unbekannter Parameter: $1"; usage ;;
esac esac
done done
if [[ -n "$CONFIG_FILE" ]]; then [[ -z "$CONFIG_FILE" || ! -f "$CONFIG_FILE" ]] && {
[[ ! -f "$CONFIG_FILE" ]] && die "Konfigurationsdatei nicht gefunden: $CONFIG_FILE" echo "[ERROR] Konfigurationsdatei fehlt oder ungültig!"
bash -n "$CONFIG_FILE" || die "Syntaxfehler in $CONFIG_FILE" exit 1
source "$CONFIG_FILE" }
SOURCEUSER="${SOURCEUSER:-root}"
fi
}
run() { source "$CONFIG_FILE"
if [[ "$DRY_RUN" -eq 1 ]]; then
echo "[DRY-RUN] $*"
else
"$@"
fi
} }
confirm() { confirm() {
[[ "$FORCE_YES" -eq 1 ]] && return 0 if [[ "$FORCE_YES" -eq 1 ]]; then
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 oder n eingeben." ;; *) echo "Bitte j (ja) oder n (nein) eingeben." ;;
esac esac
done done
} }
ensure_dialog() {
if ! command -v dialog &>/dev/null; then install_cmk() {
info "Installiere dialog..." echo "[INFO] Lade cmk.deb herunter..."
run apt-get install -y -qq dialog || die "dialog konnte nicht installiert werden" run_or_echo "curl -fsSL \"$CMK_URL\" -o \"$CMK_FILE\""
fi
echo "[INFO] Installiere cmk.deb..."
run_or_echo "sudo apt install -y \"$CMK_FILE\""
} }
get_base_url() { install_fix_interface_names() {
[[ "$DEV_MODE" -eq 1 ]] && echo "$GITEA_BASE" || echo "$GITHUB_BASE" 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_scripts() {
echo "[INFO] Lade Miyagi-Backup-Skripte..."
install_miyagi() { run_or_echo "wget -O miyagi-backup.sh \"$MIYAGI_BACKUP_URL\""
local base run_or_echo "wget -O miyagi-check.sh \"$MIYAGI_CHECK_URL\""
base="$(get_base_url)" run_or_echo "wget -O miyagi-convert.sh \"$MIYAGI_CONVERT_URL\""
local source_label run_or_echo "mkdir -p /root/miyagi-backup"
[[ "$DEV_MODE" -eq 1 ]] && source_label="Gitea (dev)" || source_label="GitHub (stable)" 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"
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_checkzfs() { install_checkmk_plugins() {
info "Installiere checkzfs..." echo "[INFO] Installiere disk_smart_info Plugin..."
run curl -fsSL "$CHECKZFS_URL" -o /usr/local/bin/checkzfs run_or_echo "curl -fsSL \"$DISK_SMART_INFO_URL\" -o /usr/lib/check_mk_agent/plugins/disk_smart_info"
run chmod +x /usr/local/bin/checkzfs run_or_echo "chmod +x /usr/lib/check_mk_agent/plugins/disk_smart_info"
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() { install_cleansnaps() {
info "Installiere cleansnaps..." echo "[INFO] Installiere cleansnaps Skript..."
run curl -fsSL "$CLEANSNAPS_URL" -o /usr/local/bin/cleansnaps run_or_echo "wget -O /usr/local/bin/cleansnaps \"$CLEANSNAPS_URL\""
run chmod +x /usr/local/bin/cleansnaps run_or_echo "chmod +x /usr/local/bin/cleansnaps"
ok "cleansnaps installiert"
} }
install_disk_smart_info() { install_check_snapshot_age() {
info "Installiere disk_smart_info..." echo "[INFO] Installiere check-snapshot-age..."
run mkdir -p /usr/lib/check_mk_agent/plugins run_or_echo "wget -O /usr/local/bin/check-snapshot-age \"$CHECK_SNAPSHOT_AGE_URL\""
run curl -fsSL "$DISK_SMART_INFO_URL" -o /usr/lib/check_mk_agent/plugins/disk_smart_info run_or_echo "chmod +x /usr/local/bin/check-snapshot-age"
run chmod +x /usr/lib/check_mk_agent/plugins/disk_smart_info
ok "disk_smart_info installiert"
} }
install_ethtool() { install_checkzfs() {
info "Installiere ethtool..." echo "[INFO] Installiere checkzfs..."
run apt-get install -y -qq ethtool run_or_echo "wget -O /usr/local/bin/checkzfs \"$CHECKZFS_URL\""
ok "ethtool installiert" run_or_echo "chmod +x /usr/local/bin/checkzfs"
} }
check_ssh() { check_ssh() {
[[ -z "${SOURCEHOST:-}" ]] && warn "Kein SOURCEHOST in Config – SSH-Check übersprungen" && return echo "[INFO] Teste SSH-Verbindung zu $SOURCEHOST ..."
info "Teste SSH-Verbindung zu ${SOURCEUSER}@${SOURCEHOST}:${SOURCEPORT}..."
if ssh -p "${SOURCEPORT:-22}" -o BatchMode=yes -o ConnectTimeout=5 \ if ssh -p "$SOURCEPORT" -o BatchMode=yes -o ConnectTimeout=5 root@"$SOURCEHOST" "echo Verbindung erfolgreich" 2>/dev/null; then
"${SOURCEUSER}@${SOURCEHOST}" "echo ok" &>/dev/null; then echo "[OK] SSH-Verbindung über Schlüssel funktioniert."
ok "SSH-Verbindung via Schlüssel funktioniert"
else else
warn "Keine schlüsselbasierte SSH-Verbindung möglich" echo "[WARN] Keine Schlüsselbasierte SSH-Verbindung möglich oder abgelehnt."
if confirm "SSH-Schlüssel jetzt auf ${SOURCEHOST} kopieren (ssh-copy-id)?"; then
run ssh-copy-id -p "${SOURCEPORT:-22}" "${SOURCEUSER}@${SOURCEHOST}" echo "[HINWEIS] Eine Passwortabfrage erfolgt jetzt – dies ist normal."
ok "Schlüssel kopiert – bitte Installer erneut starten zum Verifizieren" if ssh -p "$SOURCEPORT" root@"$SOURCEHOST" "echo Verbindung erfolgreich"; then
exit 0 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
fi fi
} }
select_zfs_pools() { 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_HOSTNAME=$(hostname -s)
local local_hostname remote_hostname tag_key tag_value REMOTE_HOSTNAME=$(ssh -p "$SOURCEPORT" root@"$SOURCEHOST" "hostname -s")
local_hostname="$(hostname -s)" TAG_KEY="bashclub:miyagi-${REMOTE_HOSTNAME}-${LOCAL_HOSTNAME}"
remote_hostname="$(ssh -p "${SOURCEPORT:-22}" "${SOURCEUSER}@${SOURCEHOST}" "hostname -s" 2>/dev/null)" \ TAG_VALUE="subvols"
|| { warn "SSH-Verbindung fehlgeschlagen – ZFS-Tagging übersprungen"; return; }
tag_key="bashclub:miyagi-${remote_hostname}-${local_hostname}" # Hole alle relevanten Pool-Namen
tag_value="subvols" mapfile -t pools < <(ssh -p "$SOURCEPORT" root@"$SOURCEHOST" \
"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
warn "Keine geeigneten ZFS-Datasets gefunden" echo "[WARN] Keine geeigneten 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:-22}" "${SOURCEUSER}@${SOURCEHOST}" \ tag=$(ssh -p "$SOURCEPORT" root@"$SOURCEHOST" zfs get -H -o value "$TAG_KEY" "$pool" 2>/dev/null)
zfs get -H -o value "$tag_key" "$pool" 2>/dev/null)" if [[ "$tag" == "$TAG_VALUE" ]]; then
[[ "$tag" == "$tag_value" ]] && status="on" || status="off" status="on"
options+=("$pool" "" "$status") tagged_pools+=("$pool")
else
status="off"
fi
options+=("$pool" "$([[ "$status" == "on" ]] && echo 'TAGGED' || echo 'untagged')" "$status")
done done
local selected local selected
selected=$(dialog --clear \ selected=$(whiptail --title "ZFS Pools auswählen" \
--backtitle "miyagi-backup Installer" \ --checklist "Wähle Datasets zum Taggen aus:" 20 78 12 \
--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
[[ $? -ne 0 ]] && info "ZFS-Tagging abgebrochen" && return if [[ $? -ne 0 ]]; then
echo "[INFO] Auswahl abgebrochen."
return
fi
local selected_array=() 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 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
[[ "$sel" == "\"$pool\"" || "$sel" == "$pool" ]] && is_selected=1 && break if [[ "$sel" == "$pool" ]]; then
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
info "Tagge $pool" echo " → Tagge $pool"
run ssh -p "${SOURCEPORT:-22}" "${SOURCEUSER}@${SOURCEHOST}" \ run_or_echo ssh -p "$SOURCEPORT" root@"$SOURCEHOST" zfs set "$TAG_KEY=$TAG_VALUE" "$pool"
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
info "Entferne Tag von $pool" echo " → Entferne Tag von $pool"
run ssh -p "${SOURCEPORT:-22}" "${SOURCEUSER}@${SOURCEHOST}" \ run_or_echo ssh -p "$SOURCEPORT" root@"$SOURCEHOST" zfs inherit "$TAG_KEY" "$pool"
zfs inherit "$tag_key" "$pool"
fi fi
done done
ok "ZFS-Tagging abgeschlossen"
} }
# ── Hauptmenü ─────────────────────────────────────────────────────────────────
main() { 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 "" 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-backup Installer v3 ║" MIYAGI_CONVERT_URL="https://gitea.perlbach24.de/scripte/miyagi-backup/raw/branch/main/miyagi-convert.sh"
echo " ╚══════════════════════════════════════╝"
echo ""
local choices DISK_SMART_INFO_URL="https://raw.githubusercontent.com/bashclub/checkmk-smart/main/disk_smart_info.py"
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
[[ $? -ne 0 ]] && echo "Abgebrochen." && exit 0 CLEANSNAPS_URL="https://raw.githubusercontent.com/bashclub/zfs-housekeeping/refs/heads/main/cleansnaps.sh"
[[ -z "$choices" ]] && echo "Nichts ausgewählt." && exit 0
[[ "$choices" == *'"miyagi"'* || "$choices" == *'miyagi'* ]] && install_miyagi CHECK_SNAPSHOT_AGE_URL="https://gitea.perlbach24.de/scripte/check-zfs-replication/raw/branch/main/check-snapshot-age"
[[ "$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
echo "" CHECKZFS_URL="https://gitea.perlbach24.de/scripte/check-zfs-replication/raw/branch/main/checkzfs.py"
ok "Installation abgeschlossen."
echo "" 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 "$@" parse_args "$@"
main 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