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

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

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

No files matched your search

+195
View File
@@ -0,0 +1,195 @@
# v3 – Dev Log
## 2026-06-19 00:56 – 00:58 (1m)
**Beschreibung:** Claude Code Session
**Projekt:** miyagi-backup
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- config.example | 2 +-
---
## 2026-06-19 01:01 – 01:05 (4m)
**Beschreibung:** Claude Code Session
**Projekt:** v3
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- config.example | 2 +-
---
## 2026-06-19 01:06 – 01:07 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** v3
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- config.example | 2 +-
---
## 2026-06-19 01:26 – 01:28 (2m)
**Beschreibung:** Claude Code Session
**Projekt:** v3
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- config.example | 2 +-
---
## 2026-06-19 01:32 – 01:33 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** v3
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- config.example | 2 +-
---
## 2026-06-19 01:40 – 01:42 (2m)
**Beschreibung:** Claude Code Session
**Projekt:** v3
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- config.example | 2 +-
---
## 2026-06-19 01:44 – 01:47 (2m)
**Beschreibung:** Claude Code Session
**Projekt:** v3
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- config.example | 2 +-
---
## 2026-06-19 01:48 – 01:48 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** v3
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- config.example | 2 +-
---
## 2026-06-19 01:49 – 02:08 (18m)
**Beschreibung:** Claude Code Session
**Projekt:** v3
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- config.example | 2 +-
---
## 2026-06-19 02:08 – 02:08 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** v3
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- config.example | 2 +-
---
## 2026-06-19 02:09 – 02:10 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** v3
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- config.example | 2 +-
---
## 2026-06-19 02:11 – 02:15 (3m)
**Beschreibung:** Claude Code Session
**Projekt:** v3
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- config.example | 2 +-
---
## 2026-06-19 02:16 – 02:16 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** v3
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- config.example | 2 +-
---
## 2026-07-07 – Pre-Shutdown-Hooks + Installpfade
**Feature:** `run_pre_shutdown_hooks()` eingebaut, aufgerufen in `shutdown_now()` nach Piggyback/CheckZFS, vor 60s-Sleep.
**Hook-Verzeichnis:** `/etc/miyagi-backup/hooks.d/pre-shutdown/`
- Verzeichnis fehlt oder leer → still übersprungen
- Nur `*.sh` mit `chmod +x` werden ausgeführt
- Alphabetisch sortiert
- Hook-Fehler → WARNUNG im Log, Shutdown läuft weiter
**Installpfade festgelegt:**
- Skripte: `/opt/miyagi-backup/`
- Konfigs: `/etc/miyagi-backup/`
- Hooks: `/etc/miyagi-backup/hooks.d/pre-shutdown/`
- Symlink: `/usr/local/bin/miyagi-backup` → `/opt/miyagi-backup/miyagi-backup.sh`
---
## 2026-07-16 14:13 – 14:13 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** v3
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- miyagi-check.sh | 100 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
---
## 2026-07-16 14:16 – 14:48 (32m)
**Beschreibung:** Claude Code Session
**Projekt:** v3
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- miyagi-check.sh | 100 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
---
## 2026-07-16 14:49 – 14:51 (1m)
**Beschreibung:** Claude Code Session
**Projekt:** v3
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- miyagi-check.sh | 100 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
---
-27
View File
@@ -1,27 +0,0 @@
# Beispielhafte Aufrufe
Befehl Wirkung
./miyagi-backup.sh -c /etc/miyagi.conf Führt den vollständigen Backup-Prozess aus
./miyagi-backup.sh help Listet alle verfügbaren Funktionen
./miyagi-backup.sh run_updates Führt nur Updates lokal aus
./miyagi-backup.sh run_pbs_backup Führt nur das vzdump-PBS-Backup aus
./miyagi-backup.sh run_maintenance Nur Wartung (Prune + GC)
./miyagi-backup.sh shutdown_if_requested Prüft ob Shutdown nötig ist
####
# miyagi-check.sh
Dieses Skript überprüft eine Bash-basierte Konfigurationsdatei für ein Backup-System:
## Funktionen:
- Validiert alle benötigten Variablen
- Testet SSH-Verbindungen zu `SOURCEHOST` und optional `PBSHOST`
- Prüft und installiert bei Bedarf den lokalen SSH-Key (`~/.ssh/id_rsa.pub`)
- Erkennt unsichere `PermitRootLogin yes` Einstellungen
- Bietet automatische Umstellung auf `prohibit-password` an
## Nutzung:
```bash
./miyagi-check.sh /pfad/zur/config
+25 -4
View File
@@ -1,15 +1,18 @@
#Edit all Variables for best Experience #Edit all Variables for best Experience
SSHPORT='22' # SSH-Port, normalerweise 22
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?
DDNS_GATEWAY='192.168.66.1' # DYNDNS Gateway
DDNS_SERVER='1.1.1.1' #DNS Server extern
REBOOT='no' # Reboot nach Update?
# Quelle (Proxmox VE System, das gesichert wird) # Quelle (Proxmox VE System, das gesichert wird)
SOURCEHOST='192.168.50.200' # IP des Quell-Proxmox-Servers SOURCEPORT='22' # SSH-Port, normalerweise 22
SOURCEHOST='192.168.50.200' # IP des Quell-Proxmox-Servers
SOURCEUSER='root' # SSH-User für SOURCEHOST
# Replikation (ZFS) # Replikation (ZFS)
ZFSROOT='rpool/data' # Erstes Dataset vom Quellsystem
ZFSSECOND='rpool-hdd/data' # Optional zweites Dataset
ZFSTRGT='rpool-ssd1/repl/pve200' # Zielpfad für Replikation ZFSTRGT='rpool-ssd1/repl/pve200' # Zielpfad für Replikation
# ZFS Zsync Replikation # ZFS Zsync Replikation
@@ -24,7 +27,25 @@ ZPUSHFILTER='hourly|daily|weekly|monthly' # Weitere Filter (leer lassen oder Mus
BACKUPSERVER='no' # Backup via PBS aktivieren? BACKUPSERVER='no' # Backup via PBS aktivieren?
MAINTDAY='7' # Wartungstag (1=Mo, 7=So) MAINTDAY='7' # Wartungstag (1=Mo, 7=So)
PBSHOST='192.168.50.199' # IP des Proxmox Backup Servers PBSHOST='192.168.50.199' # IP des Proxmox Backup Servers
PBSPORT='22' # SSH-Port PBS
PBSUSER='root' # SSH-User für PBSHOST
PBSBACKUP_STATUS='86400' # Warnung ab 24h (86400s)
PBS_REPO_USER='root@pam' # PBS-User oder API-Token-ID (z.B. 'user@pbs!tokenname') für write_pbs_status
PBS_PASSWORD='' # Passwort oder API-Token-Secret zu PBS_REPO_USER (write_pbs_status)
BACKUPSTORE='backup' # Datastore auf Quell-Proxmox BACKUPSTORE='backup' # Datastore auf Quell-Proxmox
BACKUPSTOREPBS='backup' # Datastore auf PBS BACKUPSTOREPBS='backup' # Datastore auf PBS
BACKUPEXCLUDE='10,3252,3253,3254' # VM/CT-IDs, die vom Backup ausgeschlossen sind BACKUPEXCLUDE='10,3252,3253,3254' # VM/CT-IDs, die vom Backup ausgeschlossen sind
REPLEXCLUDE="$BACKUPEXCLUDE" # Diese auch von Replikation ausschließen REPLEXCLUDE="$BACKUPEXCLUDE" # Diese auch von Replikation ausschließen
# Zusätzliche Monitoring-Ziele
# 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_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
+34
View File
@@ -0,0 +1,34 @@
#!/usr/bin/env bash
# Beispiel Pre-Shutdown-Hook für miyagi-backup
#
# Ablage: /etc/miyagi-backup/hooks.d/pre-shutdown/
# Ausführung: chmod +x <hookname>.sh
# Reihenfolge: alphabetisch (01-foo.sh, 02-bar.sh, ...)
# Fehlerverhalten: Exit != 0 erzeugt WARNUNG im Log, Shutdown läuft weiter
#
# Dieser Hook wird vor dem 60s-Countdown in shutdown_now() ausgeführt.
# Hinweis: eigene sleep-Aufrufe addieren sich zum 60s-Countdown dazu.
# ── Beispiel 1: Monitoring-Agent vor Shutdown reporten ───────────────────────
#
# if [ -x /usr/local/bin/patchmon-agent ]; then
# /usr/local/bin/patchmon-agent report
# fi
# ── Beispiel 2: CheckMK-Agent aktualisieren ───────────────────────────────────
#
# if [ -x /usr/bin/cmk-agent-update ]; then
# /usr/bin/cmk-agent-update
# fi
# ── Beispiel 3: Dienst neu starten (nur wenn installiert) ────────────────────
#
# if systemctl list-unit-files wazuh-agent.service &>/dev/null; then
# systemctl restart wazuh-agent.service
# fi
# ── Beispiel 4: Eigenes Skript aufrufen ───────────────────────────────────────
#
# /opt/myscript/notify-shutdown.sh
exit 0
+829 -277
View File
File diff suppressed because it is too large. Load diff
-203
View File
@@ -1,203 +0,0 @@
#!/bin/bash
set -euo pipefail
IFS=$'\n\t'
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=(
SSHPORT
BACKUPSERVER
ZSYNC
MAINTDAY
SHUTDOWN
UPDATES
SOURCEHOST
ZFSROOT
ZFSSECOND
ZFSTRGT
ZPUSHTAG
ZPUSHMINKEEP
ZPUSHKEEP
ZPUSHLABEL
ZPUSHFILTER
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 "$SSHPORT" -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 "$SSHPORT" "$host" "grep -qF '$pubkey' ~/.ssh/authorized_keys" 2>/dev/null; then
LOG " SSH-Key ist bereits auf $host hinterlegt."
else
LOG " SSH-Key nicht auf $host vorhanden."
read -rp " Möchtest du den SSH-Key jetzt via ssh-copy-id übertragen? [j/N] " ans
if [[ "$ans" =~ ^[JjYy]$ ]]; then
ssh-copy-id -p "$SSHPORT" "$host"
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 "$SSHPORT" "$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 "$SSHPORT" "$host" "command -v pveversion >/dev/null"; then
ssh -p "$SSHPORT" "$host" "pveversion" | while read -r line; do
LOG " $host: $line"
done
else
LOG " 'pveversion' ist auf $host nicht verfügbar – kein Proxmox?"
fi
}
run_host_check() {
local host=$1
LOG ""
LOG "=== Prüfung für Host: $host ==="
if check_ssh_connection "$host"; then
check_and_copy_ssh_key "$host"
check_sshd_config_recommendation "$host"
check_pveversion "$host"
fi
echo ""
}
run_host_check "$SOURCEHOST"
if [[ "$BACKUPSERVER" == "yes" ]]; then
run_host_check "$PBSHOST"
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 "$SSHPORT" "$h" "sed -i 's/^PermitRootLogin.*/PermitRootLogin prohibit-password/' /etc/ssh/sshd_config && systemctl reload sshd && echo '✅ sshd auf $h neu geladen.' || echo '❌ Fehler bei $h'"
done
else
echo " Änderung von sshd_config übersprungen."
fi
fi
+293
View File
@@ -0,0 +1,293 @@
#!/usr/bin/env bash
# miyagi-backup Installer v3
set -uo pipefail
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; }
usage() {
cat <<EOF
Usage: $0 [OPTIONS]
-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
EOF
exit 0
}
parse_args() {
while [[ $# -gt 0 ]]; do
case "$1" in
-c) CONFIG_FILE="$2"; shift 2 ;;
--yes) FORCE_YES=1; shift ;;
--dry-run) DRY_RUN=1; shift ;;
--dev) DEV_MODE=1; shift ;;
--help) usage ;;
*) die "Unbekannter Parameter: $1" ;;
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
}
run() {
if [[ "$DRY_RUN" -eq 1 ]]; then
echo "[DRY-RUN] $*"
else
"$@"
fi
}
confirm() {
[[ "$FORCE_YES" -eq 1 ]] && return 0
while true; do
read -rp "$1 [j/N]: " yn
case "$yn" in
[JjYy]*) return 0 ;;
[Nn]*|"") return 1 ;;
*) echo "Bitte j oder n 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
}
get_base_url() {
[[ "$DEV_MODE" -eq 1 ]] && echo "$GITEA_BASE" || echo "$GITHUB_BASE"
}
# ── 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_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_cleansnaps() {
info "Installiere cleansnaps..."
run curl -fsSL "$CLEANSNAPS_URL" -o /usr/local/bin/cleansnaps
run chmod +x /usr/local/bin/cleansnaps
ok "cleansnaps installiert"
}
install_disk_smart_info() {
info "Installiere disk_smart_info..."
run mkdir -p /usr/lib/check_mk_agent/plugins
run curl -fsSL "$DISK_SMART_INFO_URL" -o /usr/lib/check_mk_agent/plugins/disk_smart_info
run chmod +x /usr/lib/check_mk_agent/plugins/disk_smart_info
ok "disk_smart_info installiert"
}
install_ethtool() {
info "Installiere ethtool..."
run apt-get install -y -qq ethtool
ok "ethtool installiert"
}
check_ssh() {
[[ -z "${SOURCEHOST:-}" ]] && warn "Kein SOURCEHOST in Config – SSH-Check übersprungen" && return
info "Teste SSH-Verbindung zu ${SOURCEUSER}@${SOURCEHOST}:${SOURCEPORT}..."
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"
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
fi
fi
}
select_zfs_pools() {
[[ -z "${SOURCEHOST:-}" ]] && warn "Kein SOURCEHOST – ZFS-Tagging übersprungen" && return
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; }
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 | \
grep -E '/(vm|subvol)-' | \
grep -viE 'repl|replica' | \
sed 's:/[^/]*$::' | \
sort -u)
if [[ ${#pools[@]} -eq 0 ]]; then
warn "Keine geeigneten ZFS-Datasets gefunden"
return
fi
local options=()
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")
done
local selected
selected=$(dialog --clear \
--backtitle "miyagi-backup Installer" \
--title "ZFS Pools taggen" \
--checklist "Datasets für Replikation auswählen:" 20 78 12 \
"${options[@]}" \
3>&1 1>&2 2>&3)
clear
[[ $? -ne 0 ]] && info "ZFS-Tagging abgebrochen" && return
local selected_array=()
read -ra selected_array <<< "$selected"
for pool in "${pools[@]}"; do
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
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"
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"
fi
done
ok "ZFS-Tagging abgeschlossen"
}
# ── Hauptmenü ─────────────────────────────────────────────────────────────────
main() {
[[ $EUID -ne 0 ]] && die "Bitte als root ausführen"
ensure_dialog
echo ""
echo " ╔══════════════════════════════════════╗"
echo " ║ miyagi-backup Installer v3 ║"
echo " ╚══════════════════════════════════════╝"
echo ""
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
[[ $? -ne 0 ]] && echo "Abgebrochen." && exit 0
[[ -z "$choices" ]] && echo "Nichts ausgewählt." && exit 0
[[ "$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
echo ""
ok "Installation abgeschlossen."
echo ""
}
parse_args "$@"
main
+168
View File
@@ -0,0 +1,168 @@
#!/usr/bin/env bash
# miyagi-backup Updater v3
set -uo pipefail
DEV_MODE=0
INSTALL_DIR="/opt/miyagi-backup"
CONFIG_DIR="/etc/miyagi-backup"
HOOKS_DIR="${CONFIG_DIR}/hooks.d/pre-shutdown"
SYMLINK="/usr/local/bin/miyagi-backup"
GITHUB_BASE="https://raw.githubusercontent.com/bashclub/miyagi-backup/main"
GITEA_BASE="https://gitea.perlbach24.de/scripte/miyagi-backup/raw/branch/main"
CHECKZFS_URL="https://gitea.perlbach24.de/scripte/check-zfs-replication/raw/branch/main/checkzfs.py"
CHECK_SNAPSHOT_AGE_URL="https://gitea.perlbach24.de/scripte/check-zfs-replication/raw/branch/main/check-snapshot-age"
CLEANSNAPS_URL="https://raw.githubusercontent.com/bashclub/zfs-housekeeping/refs/heads/main/cleansnaps.sh"
DISK_SMART_INFO_URL="https://raw.githubusercontent.com/bashclub/checkmk-smart/main/disk_smart_info.py"
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; NC='\033[0m'
ok() { echo -e "${GREEN}[OK]${NC} $*"; }
info() { echo -e "${BLUE}[..]${NC} $*"; }
warn() { echo -e "${YELLOW}[!!]${NC} $*"; }
die() { echo -e "${RED}[ERR]${NC} $*" >&2; exit 1; }
usage() {
cat <<EOF
Usage: $0 [--dev]
--dev Update von Gitea (dev) statt GitHub (stable)
--help Diese Hilfe
EOF
exit 0
}
parse_args() {
while [[ $# -gt 0 ]]; do
case "$1" in
--dev) DEV_MODE=1; shift ;;
--help) usage ;;
*) die "Unbekannter Parameter: $1" ;;
esac
done
}
get_base_url() {
[[ "$DEV_MODE" -eq 1 ]] && echo "$GITEA_BASE" || echo "$GITHUB_BASE"
}
# ── Self-Update ───────────────────────────────────────────────────────────────
self_update() {
local base
base="$(get_base_url)"
local self="${INSTALL_DIR}/miyagi-update.sh"
if [[ -f "$self" ]] && command -v curl >/dev/null; then
curl -fsSL "${base}/miyagi-update.sh" -o "${self}.new" 2>/dev/null \
&& mv "${self}.new" "$self" \
&& chmod +x "$self" \
&& info "Updater aktualisiert" \
|| rm -f "${self}.new"
fi
}
# ── miyagi-backup.sh updaten ─────────────────────────────────────────────────
update_miyagi() {
local base
base="$(get_base_url)"
local source_label
[[ "$DEV_MODE" -eq 1 ]] && source_label="Gitea (dev)" || source_label="GitHub (stable)"
info "Aktualisiere miyagi-backup.sh von $source_label..."
if [[ ! -d "$INSTALL_DIR" ]]; then
warn "Installverzeichnis $INSTALL_DIR fehlt – bitte zuerst miyagi-install.sh ausführen"
return 1
fi
curl -fsSL "${base}/miyagi-backup.sh" -o "${INSTALL_DIR}/miyagi-backup.sh.new" || {
warn "Download fehlgeschlagen – miyagi-backup.sh bleibt unverändert"
return 1
}
mv "${INSTALL_DIR}/miyagi-backup.sh.new" "${INSTALL_DIR}/miyagi-backup.sh"
chmod +x "${INSTALL_DIR}/miyagi-backup.sh"
# Symlink sicherstellen
ln -sf "${INSTALL_DIR}/miyagi-backup.sh" "$SYMLINK"
# config.example aktualisieren (nie .conf überschreiben)
curl -fsSL "${base}/config.example" -o "${CONFIG_DIR}/config.example.new" 2>/dev/null \
&& mv "${CONFIG_DIR}/config.example.new" "${CONFIG_DIR}/config.example" \
|| warn "config.example konnte nicht aktualisiert werden"
# Hooks-Verzeichnis sicherstellen
mkdir -p "$HOOKS_DIR"
# Example-Hook aktualisieren (nicht ausführbar = bleibt inaktiv)
curl -fsSL "${base}/hooks.d/pre-shutdown/example-hook.sh" \
-o "${HOOKS_DIR}/example-hook.sh" 2>/dev/null \
&& info "example-hook.sh aktualisiert" \
|| warn "example-hook.sh konnte nicht aktualisiert werden"
ok "miyagi-backup.sh aktualisiert"
}
# ── Abhängigkeiten updaten (nur wenn bereits installiert) ─────────────────────
update_dep() {
local name="$1"
local url="$2"
local target="$3"
if [[ ! -f "$target" ]]; then
info "$name nicht installiert – übersprungen (install mit miyagi-install.sh)"
return 0
fi
info "Aktualisiere $name..."
curl -fsSL "$url" -o "${target}.new" 2>/dev/null || {
warn "$name Download fehlgeschlagen – bleibt unverändert"
rm -f "${target}.new"
return 1
}
mv "${target}.new" "$target"
chmod +x "$target"
ok "$name aktualisiert"
}
# ── Status-Zusammenfassung ────────────────────────────────────────────────────
print_status() {
echo ""
echo " ┌──────────────────────────────────────────┐"
printf " │ %-40s│\n" "miyagi-backup $([ -f "${INSTALL_DIR}/miyagi-backup.sh" ] && echo "✓ installiert" || echo "✗ fehlt")"
printf " │ %-40s│\n" "checkzfs $(command -v checkzfs &>/dev/null && echo "✓" || echo "✗ nicht installiert")"
printf " │ %-40s│\n" "check-snap-age $(command -v check-snapshot-age &>/dev/null && echo "✓" || echo "✗ nicht installiert")"
printf " │ %-40s│\n" "cleansnaps $(command -v cleansnaps &>/dev/null && echo "✓" || echo "✗ nicht installiert")"
printf " │ %-40s│\n" "ethtool $(command -v ethtool &>/dev/null && echo "✓" || echo "✗ nicht installiert")"
echo " └──────────────────────────────────────────┘"
echo ""
}
# ── Main ──────────────────────────────────────────────────────────────────────
main() {
[[ $EUID -ne 0 ]] && die "Bitte als root ausführen"
echo ""
echo " ╔══════════════════════════════════════╗"
echo " ║ miyagi-backup Updater v3 ║"
echo " ╚══════════════════════════════════════╝"
echo ""
self_update
update_miyagi
update_dep "checkzfs" "$CHECKZFS_URL" "/usr/local/bin/checkzfs"
update_dep "check-snapshot-age" "$CHECK_SNAPSHOT_AGE_URL" "/usr/local/bin/check-snapshot-age"
update_dep "cleansnaps" "$CLEANSNAPS_URL" "/usr/local/bin/cleansnaps"
update_dep "disk_smart_info" "$DISK_SMART_INFO_URL" "/usr/lib/check_mk_agent/plugins/disk_smart_info"
print_status
ok "Update abgeschlossen."
}
parse_args "$@"
main