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
This commit is contained in:
1 parent
14019487b3
commit
4d050cbedf
32 files changed
+1062
-7393
No files matched your search
@@ -1,17 +1,6 @@
|
||||
# miyagi-backup – Dev Log
|
||||
# v3 – Dev Log
|
||||
|
||||
## 2026-06-19 00:08 – 00:27 (18m)
|
||||
**Beschreibung:** Claude Code Session
|
||||
**Projekt:** testing
|
||||
|
||||
### Commits
|
||||
Keine Commits in dieser Session.
|
||||
|
||||
### Geänderte Dateien
|
||||
- config.example | 2 +-
|
||||
|
||||
---
|
||||
## 2026-06-24 12:17 – 12:18 (0m)
|
||||
## 2026-06-19 00:56 – 00:58 (1m)
|
||||
**Beschreibung:** Claude Code Session
|
||||
**Projekt:** miyagi-backup
|
||||
|
||||
@@ -22,9 +11,9 @@ Keine Commits in dieser Session.
|
||||
- config.example | 2 +-
|
||||
|
||||
---
|
||||
## 2026-06-24 12:32 – 12:32 (0m)
|
||||
## 2026-06-19 01:01 – 01:05 (4m)
|
||||
**Beschreibung:** Claude Code Session
|
||||
**Projekt:** miyagi-backup
|
||||
**Projekt:** v3
|
||||
|
||||
### Commits
|
||||
Keine Commits in dieser Session.
|
||||
@@ -33,9 +22,9 @@ Keine Commits in dieser Session.
|
||||
- config.example | 2 +-
|
||||
|
||||
---
|
||||
## 2026-07-07 15:23 – 15:23 (0m)
|
||||
## 2026-06-19 01:06 – 01:07 (0m)
|
||||
**Beschreibung:** Claude Code Session
|
||||
**Projekt:** miyagi-backup
|
||||
**Projekt:** v3
|
||||
|
||||
### Commits
|
||||
Keine Commits in dieser Session.
|
||||
@@ -44,9 +33,9 @@ Keine Commits in dieser Session.
|
||||
- config.example | 2 +-
|
||||
|
||||
---
|
||||
## 2026-07-07 15:25 – 15:25 (0m)
|
||||
## 2026-06-19 01:26 – 01:28 (2m)
|
||||
**Beschreibung:** Claude Code Session
|
||||
**Projekt:** miyagi-backup
|
||||
**Projekt:** v3
|
||||
|
||||
### Commits
|
||||
Keine Commits in dieser Session.
|
||||
@@ -55,9 +44,9 @@ Keine Commits in dieser Session.
|
||||
- config.example | 2 +-
|
||||
|
||||
---
|
||||
## 2026-07-07 15:28 – 15:28 (0m)
|
||||
## 2026-06-19 01:32 – 01:33 (0m)
|
||||
**Beschreibung:** Claude Code Session
|
||||
**Projekt:** miyagi-backup
|
||||
**Projekt:** v3
|
||||
|
||||
### Commits
|
||||
Keine Commits in dieser Session.
|
||||
@@ -66,9 +55,9 @@ Keine Commits in dieser Session.
|
||||
- config.example | 2 +-
|
||||
|
||||
---
|
||||
## 2026-07-07 15:29 – 15:29 (0m)
|
||||
## 2026-06-19 01:40 – 01:42 (2m)
|
||||
**Beschreibung:** Claude Code Session
|
||||
**Projekt:** miyagi-backup
|
||||
**Projekt:** v3
|
||||
|
||||
### Commits
|
||||
Keine Commits in dieser Session.
|
||||
@@ -77,9 +66,9 @@ Keine Commits in dieser Session.
|
||||
- config.example | 2 +-
|
||||
|
||||
---
|
||||
## 2026-07-07 15:31 – 15:31 (0m)
|
||||
## 2026-06-19 01:44 – 01:47 (2m)
|
||||
**Beschreibung:** Claude Code Session
|
||||
**Projekt:** miyagi-backup
|
||||
**Projekt:** v3
|
||||
|
||||
### Commits
|
||||
Keine Commits in dieser Session.
|
||||
@@ -88,9 +77,9 @@ Keine Commits in dieser Session.
|
||||
- config.example | 2 +-
|
||||
|
||||
---
|
||||
## 2026-07-07 15:32 – 15:32 (0m)
|
||||
## 2026-06-19 01:48 – 01:48 (0m)
|
||||
**Beschreibung:** Claude Code Session
|
||||
**Projekt:** miyagi-backup
|
||||
**Projekt:** v3
|
||||
|
||||
### Commits
|
||||
Keine Commits in dieser Session.
|
||||
@@ -99,9 +88,9 @@ Keine Commits in dieser Session.
|
||||
- config.example | 2 +-
|
||||
|
||||
---
|
||||
## 2026-07-07 15:33 – 15:33 (0m)
|
||||
## 2026-06-19 01:49 – 02:08 (18m)
|
||||
**Beschreibung:** Claude Code Session
|
||||
**Projekt:** miyagi-backup
|
||||
**Projekt:** v3
|
||||
|
||||
### Commits
|
||||
Keine Commits in dieser Session.
|
||||
@@ -110,9 +99,9 @@ Keine Commits in dieser Session.
|
||||
- config.example | 2 +-
|
||||
|
||||
---
|
||||
## 2026-07-07 15:36 – 15:37 (1m)
|
||||
## 2026-06-19 02:08 – 02:08 (0m)
|
||||
**Beschreibung:** Claude Code Session
|
||||
**Projekt:** miyagi-backup
|
||||
**Projekt:** v3
|
||||
|
||||
### Commits
|
||||
Keine Commits in dieser Session.
|
||||
@@ -121,9 +110,9 @@ Keine Commits in dieser Session.
|
||||
- config.example | 2 +-
|
||||
|
||||
---
|
||||
## 2026-07-07 15:38 – 15:39 (0m)
|
||||
## 2026-06-19 02:09 – 02:10 (0m)
|
||||
**Beschreibung:** Claude Code Session
|
||||
**Projekt:** miyagi-backup
|
||||
**Projekt:** v3
|
||||
|
||||
### Commits
|
||||
Keine Commits in dieser Session.
|
||||
@@ -132,9 +121,9 @@ Keine Commits in dieser Session.
|
||||
- config.example | 2 +-
|
||||
|
||||
---
|
||||
## 2026-07-07 15:39 – 15:40 (0m)
|
||||
## 2026-06-19 02:11 – 02:15 (3m)
|
||||
**Beschreibung:** Claude Code Session
|
||||
**Projekt:** miyagi-backup
|
||||
**Projekt:** v3
|
||||
|
||||
### Commits
|
||||
Keine Commits in dieser Session.
|
||||
@@ -143,9 +132,9 @@ Keine Commits in dieser Session.
|
||||
- config.example | 2 +-
|
||||
|
||||
---
|
||||
## 2026-07-07 15:40 – 15:41 (0m)
|
||||
## 2026-06-19 02:16 – 02:16 (0m)
|
||||
**Beschreibung:** Claude Code Session
|
||||
**Projekt:** miyagi-backup
|
||||
**Projekt:** v3
|
||||
|
||||
### Commits
|
||||
Keine Commits in dieser Session.
|
||||
@@ -154,562 +143,26 @@ Keine Commits in dieser Session.
|
||||
- config.example | 2 +-
|
||||
|
||||
---
|
||||
## 2026-07-07 15:43 – 15:43 (0m)
|
||||
**Beschreibung:** Claude Code Session
|
||||
**Projekt:** miyagi-backup
|
||||
## 2026-07-07 – Pre-Shutdown-Hooks + Installpfade
|
||||
|
||||
### Commits
|
||||
Keine Commits in dieser Session.
|
||||
**Feature:** `run_pre_shutdown_hooks()` eingebaut, aufgerufen in `shutdown_now()` nach Piggyback/CheckZFS, vor 60s-Sleep.
|
||||
|
||||
### Geänderte Dateien
|
||||
- config.example | 2 +-
|
||||
**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-07 15:45 – 15:45 (0m)
|
||||
## 2026-07-16 14:13 – 14:13 (0m)
|
||||
**Beschreibung:** Claude Code Session
|
||||
**Projekt:** miyagi-backup
|
||||
|
||||
### Commits
|
||||
Keine Commits in dieser Session.
|
||||
|
||||
### Geänderte Dateien
|
||||
- config.example | 2 +-
|
||||
|
||||
---
|
||||
## 2026-07-07 15:45 – 15:46 (0m)
|
||||
**Beschreibung:** Claude Code Session
|
||||
**Projekt:** miyagi-backup
|
||||
|
||||
### Commits
|
||||
Keine Commits in dieser Session.
|
||||
|
||||
### Geänderte Dateien
|
||||
- config.example | 2 +-
|
||||
|
||||
---
|
||||
## 2026-07-07 15:47 – 15:48 (0m)
|
||||
**Beschreibung:** Claude Code Session
|
||||
**Projekt:** miyagi-backup
|
||||
|
||||
### Commits
|
||||
Keine Commits in dieser Session.
|
||||
|
||||
### Geänderte Dateien
|
||||
- config.example | 2 +-
|
||||
|
||||
---
|
||||
## 2026-07-07 15:48 – 15:49 (0m)
|
||||
**Beschreibung:** Claude Code Session
|
||||
**Projekt:** miyagi-backup
|
||||
|
||||
### Commits
|
||||
Keine Commits in dieser Session.
|
||||
|
||||
### Geänderte Dateien
|
||||
- config.example | 2 +-
|
||||
|
||||
---
|
||||
## 2026-07-07 15:49 – 15:51 (1m)
|
||||
**Beschreibung:** Claude Code Session
|
||||
**Projekt:** miyagi-backup
|
||||
|
||||
### Commits
|
||||
Keine Commits in dieser Session.
|
||||
|
||||
### Geänderte Dateien
|
||||
- config.example | 2 +-
|
||||
|
||||
---
|
||||
## 2026-07-07 15:52 – 15:53 (1m)
|
||||
**Beschreibung:** Claude Code Session
|
||||
**Projekt:** miyagi-backup
|
||||
|
||||
### Commits
|
||||
Keine Commits in dieser Session.
|
||||
|
||||
### Geänderte Dateien
|
||||
- config.example | 2 +-
|
||||
|
||||
---
|
||||
## 2026-07-07 15:55 – 15:55 (0m)
|
||||
**Beschreibung:** Claude Code Session
|
||||
**Projekt:** miyagi-backup
|
||||
|
||||
### Commits
|
||||
Keine Commits in dieser Session.
|
||||
|
||||
### Geänderte Dateien
|
||||
- config.example | 2 +-
|
||||
|
||||
---
|
||||
## 2026-07-07 15:57 – 15:59 (1m)
|
||||
**Beschreibung:** Claude Code Session
|
||||
**Projekt:** miyagi-backup
|
||||
|
||||
### Commits
|
||||
Keine Commits in dieser Session.
|
||||
|
||||
### Geänderte Dateien
|
||||
- config.example | 2 +-
|
||||
|
||||
---
|
||||
## 2026-07-07 16:00 – 16:00 (0m)
|
||||
**Beschreibung:** Claude Code Session
|
||||
**Projekt:** miyagi-backup
|
||||
|
||||
### Commits
|
||||
Keine Commits in dieser Session.
|
||||
|
||||
### Geänderte Dateien
|
||||
- config.example | 2 +-
|
||||
|
||||
---
|
||||
## 2026-07-07 16:01 – 16:01 (0m)
|
||||
**Beschreibung:** Claude Code Session
|
||||
**Projekt:** miyagi-backup
|
||||
|
||||
### Commits
|
||||
Keine Commits in dieser Session.
|
||||
|
||||
### Geänderte Dateien
|
||||
- config.example | 2 +-
|
||||
|
||||
---
|
||||
## 2026-07-07 16:05 – 16:06 (0m)
|
||||
**Beschreibung:** Claude Code Session
|
||||
**Projekt:** miyagi-backup
|
||||
|
||||
### Commits
|
||||
Keine Commits in dieser Session.
|
||||
|
||||
### Geänderte Dateien
|
||||
- config.example | 2 +-
|
||||
|
||||
---
|
||||
## 2026-07-07 16:18 – 16:18 (0m)
|
||||
**Beschreibung:** Claude Code Session
|
||||
**Projekt:** miyagi-backup
|
||||
|
||||
### Commits
|
||||
Keine Commits in dieser Session.
|
||||
|
||||
### Geänderte Dateien
|
||||
- config.example | 2 +-
|
||||
|
||||
---
|
||||
## 2026-07-07 16:20 – 16:20 (0m)
|
||||
**Beschreibung:** Claude Code Session
|
||||
**Projekt:** miyagi-backup
|
||||
|
||||
### Commits
|
||||
Keine Commits in dieser Session.
|
||||
|
||||
### Geänderte Dateien
|
||||
- config.example | 2 +-
|
||||
|
||||
---
|
||||
## 2026-07-07 16:22 – 16:23 (0m)
|
||||
**Beschreibung:** Claude Code Session
|
||||
**Projekt:** miyagi-backup
|
||||
|
||||
### Commits
|
||||
Keine Commits in dieser Session.
|
||||
|
||||
### Geänderte Dateien
|
||||
- config.example | 2 +-
|
||||
|
||||
---
|
||||
## 2026-07-07 16:23 – 16:25 (1m)
|
||||
**Beschreibung:** Claude Code Session
|
||||
**Projekt:** miyagi-backup
|
||||
|
||||
### Commits
|
||||
Keine Commits in dieser Session.
|
||||
|
||||
### Geänderte Dateien
|
||||
- config.example | 2 +-
|
||||
|
||||
---
|
||||
## 2026-07-07 16:51 – 16:51 (0m)
|
||||
**Beschreibung:** Claude Code Session
|
||||
**Projekt:** miyagi-backup
|
||||
|
||||
### Commits
|
||||
Keine Commits in dieser Session.
|
||||
|
||||
### Geänderte Dateien
|
||||
- config.example | 2 +-
|
||||
|
||||
---
|
||||
## 2026-07-07 16:51 – 16:52 (0m)
|
||||
**Beschreibung:** Claude Code Session
|
||||
**Projekt:** miyagi-backup
|
||||
|
||||
### Commits
|
||||
Keine Commits in dieser Session.
|
||||
|
||||
### Geänderte Dateien
|
||||
- config.example | 2 +-
|
||||
|
||||
---
|
||||
## 2026-07-07 16:56 – 16:57 (0m)
|
||||
**Beschreibung:** Claude Code Session
|
||||
**Projekt:** miyagi-backup
|
||||
|
||||
### Commits
|
||||
Keine Commits in dieser Session.
|
||||
|
||||
### Geänderte Dateien
|
||||
- config.example | 2 +-
|
||||
|
||||
---
|
||||
## 2026-07-07 16:57 – 16:58 (0m)
|
||||
**Beschreibung:** Claude Code Session
|
||||
**Projekt:** miyagi-backup
|
||||
|
||||
### Commits
|
||||
Keine Commits in dieser Session.
|
||||
|
||||
### Geänderte Dateien
|
||||
- config.example | 2 +-
|
||||
|
||||
---
|
||||
## 2026-07-07 16:59 – 16:59 (0m)
|
||||
**Beschreibung:** Claude Code Session
|
||||
**Projekt:** miyagi-backup
|
||||
|
||||
### Commits
|
||||
Keine Commits in dieser Session.
|
||||
|
||||
### Geänderte Dateien
|
||||
- config.example | 2 +-
|
||||
|
||||
---
|
||||
## 2026-07-07 17:00 – 17:01 (0m)
|
||||
**Beschreibung:** Claude Code Session
|
||||
**Projekt:** miyagi-backup
|
||||
|
||||
### Commits
|
||||
Keine Commits in dieser Session.
|
||||
|
||||
### Geänderte Dateien
|
||||
- config.example | 2 +-
|
||||
|
||||
---
|
||||
## 2026-07-07 17:01 – 17:01 (0m)
|
||||
**Beschreibung:** Claude Code Session
|
||||
**Projekt:** miyagi-backup
|
||||
|
||||
### Commits
|
||||
Keine Commits in dieser Session.
|
||||
|
||||
### Geänderte Dateien
|
||||
- config.example | 2 +-
|
||||
|
||||
---
|
||||
## 2026-07-07 17:02 – 17:02 (0m)
|
||||
**Beschreibung:** Claude Code Session
|
||||
**Projekt:** miyagi-backup
|
||||
|
||||
### Commits
|
||||
Keine Commits in dieser Session.
|
||||
|
||||
### Geänderte Dateien
|
||||
- config.example | 2 +-
|
||||
|
||||
---
|
||||
## 2026-07-07 17:02 – 17:07 (5m)
|
||||
**Beschreibung:** Claude Code Session
|
||||
**Projekt:** miyagi-backup
|
||||
|
||||
### Commits
|
||||
Keine Commits in dieser Session.
|
||||
|
||||
### Geänderte Dateien
|
||||
- config.example | 2 +-
|
||||
|
||||
---
|
||||
## 2026-07-07 17:41 – 17:41 (0m)
|
||||
**Beschreibung:** Claude Code Session
|
||||
**Projekt:** miyagi-backup
|
||||
|
||||
### Commits
|
||||
Keine Commits in dieser Session.
|
||||
|
||||
### Geänderte Dateien
|
||||
- config.example | 2 +-
|
||||
|
||||
---
|
||||
## 2026-07-07 17:43 – 17:43 (0m)
|
||||
**Beschreibung:** Claude Code Session
|
||||
**Projekt:** miyagi-backup
|
||||
|
||||
### Commits
|
||||
Keine Commits in dieser Session.
|
||||
|
||||
### Geänderte Dateien
|
||||
- config.example | 2 +-
|
||||
|
||||
---
|
||||
## 2026-07-07 17:56 – 17:56 (0m)
|
||||
**Beschreibung:** Claude Code Session
|
||||
**Projekt:** miyagi-backup
|
||||
|
||||
### Commits
|
||||
Keine Commits in dieser Session.
|
||||
|
||||
### Geänderte Dateien
|
||||
- config.example | 2 +-
|
||||
|
||||
---
|
||||
## 2026-07-07 18:06 – 18:06 (0m)
|
||||
**Beschreibung:** Claude Code Session
|
||||
**Projekt:** miyagi-backup
|
||||
|
||||
### Commits
|
||||
Keine Commits in dieser Session.
|
||||
|
||||
### Geänderte Dateien
|
||||
- config.example | 2 +-
|
||||
|
||||
---
|
||||
## 2026-07-07 18:07 – 18:08 (0m)
|
||||
**Beschreibung:** Claude Code Session
|
||||
**Projekt:** miyagi-backup
|
||||
|
||||
### Commits
|
||||
Keine Commits in dieser Session.
|
||||
|
||||
### Geänderte Dateien
|
||||
- config.example | 2 +-
|
||||
|
||||
---
|
||||
## 2026-07-07 18:15 – 18:15 (0m)
|
||||
**Beschreibung:** Claude Code Session
|
||||
**Projekt:** miyagi-backup
|
||||
|
||||
### Commits
|
||||
Keine Commits in dieser Session.
|
||||
|
||||
### Geänderte Dateien
|
||||
- config.example | 2 +-
|
||||
|
||||
---
|
||||
## 2026-07-07 22:16 – 22:17 (1m)
|
||||
**Beschreibung:** Claude Code Session
|
||||
**Projekt:** miyagi-backup
|
||||
|
||||
### Commits
|
||||
Keine Commits in dieser Session.
|
||||
|
||||
### Geänderte Dateien
|
||||
- config.example | 2 +-
|
||||
|
||||
---
|
||||
## 2026-07-07 22:17 – 22:18 (0m)
|
||||
**Beschreibung:** Claude Code Session
|
||||
**Projekt:** miyagi-backup
|
||||
|
||||
### Commits
|
||||
Keine Commits in dieser Session.
|
||||
|
||||
### Geänderte Dateien
|
||||
- config.example | 2 +-
|
||||
|
||||
---
|
||||
## 2026-07-07 22:18 – 22:18 (0m)
|
||||
**Beschreibung:** Claude Code Session
|
||||
**Projekt:** miyagi-backup
|
||||
|
||||
### Commits
|
||||
Keine Commits in dieser Session.
|
||||
|
||||
### Geänderte Dateien
|
||||
- config.example | 2 +-
|
||||
|
||||
---
|
||||
## 2026-07-07 22:19 – 22:20 (0m)
|
||||
**Beschreibung:** Claude Code Session
|
||||
**Projekt:** miyagi-backup
|
||||
|
||||
### Commits
|
||||
Keine Commits in dieser Session.
|
||||
|
||||
### Geänderte Dateien
|
||||
- config.example | 2 +-
|
||||
|
||||
---
|
||||
## 2026-07-07 22:20 – 22:21 (0m)
|
||||
**Beschreibung:** Claude Code Session
|
||||
**Projekt:** miyagi-backup
|
||||
|
||||
### Commits
|
||||
Keine Commits in dieser Session.
|
||||
|
||||
### Geänderte Dateien
|
||||
- config.example | 2 +-
|
||||
|
||||
---
|
||||
## 2026-07-07 23:04 – 23:04 (0m)
|
||||
**Beschreibung:** Claude Code Session
|
||||
**Projekt:** miyagi-backup
|
||||
|
||||
### Commits
|
||||
Keine Commits in dieser Session.
|
||||
|
||||
### Geänderte Dateien
|
||||
- config.example | 2 +-
|
||||
|
||||
---
|
||||
## 2026-07-12 23:45 – 09:25 (9h 39m)
|
||||
**Beschreibung:** Claude Code Session
|
||||
**Projekt:** wireguard-ui-multi
|
||||
|
||||
### Commits
|
||||
Keine Commits in dieser Session.
|
||||
|
||||
### Geänderte Dateien
|
||||
- config.example | 2 +-
|
||||
|
||||
---
|
||||
## 2026-07-13 09:25 – 09:26 (0m)
|
||||
**Beschreibung:** Claude Code Session
|
||||
**Projekt:** miyagi-backup
|
||||
|
||||
### Commits
|
||||
Keine Commits in dieser Session.
|
||||
|
||||
### Geänderte Dateien
|
||||
- config.example | 2 +-
|
||||
|
||||
---
|
||||
## 2026-07-13 09:26 – 09:28 (1m)
|
||||
**Beschreibung:** Claude Code Session
|
||||
**Projekt:** miyagi-backup
|
||||
|
||||
### Commits
|
||||
Keine Commits in dieser Session.
|
||||
|
||||
### Geänderte Dateien
|
||||
- config.example | 2 +-
|
||||
|
||||
---
|
||||
## 2026-07-13 09:28 – 09:59 (30m)
|
||||
**Beschreibung:** Claude Code Session
|
||||
**Projekt:** miyagi-backup
|
||||
|
||||
### Commits
|
||||
Keine Commits in dieser Session.
|
||||
|
||||
### Geänderte Dateien
|
||||
- config.example | 2 +-
|
||||
|
||||
---
|
||||
## 2026-07-13 10:00 – 10:00 (0m)
|
||||
**Beschreibung:** Claude Code Session
|
||||
**Projekt:** miyagi-backup
|
||||
|
||||
### Commits
|
||||
Keine Commits in dieser Session.
|
||||
|
||||
### Geänderte Dateien
|
||||
- config.example | 2 +-
|
||||
|
||||
---
|
||||
## 2026-07-13 10:29 – 10:30 (0m)
|
||||
**Beschreibung:** Claude Code Session
|
||||
**Projekt:** miyagi-backup
|
||||
|
||||
### Commits
|
||||
Keine Commits in dieser Session.
|
||||
|
||||
### Geänderte Dateien
|
||||
- config.example | 2 +-
|
||||
|
||||
---
|
||||
## 2026-07-13 10:30 – 10:30 (0m)
|
||||
**Beschreibung:** Claude Code Session
|
||||
**Projekt:** miyagi-backup
|
||||
|
||||
### Commits
|
||||
Keine Commits in dieser Session.
|
||||
|
||||
### Geänderte Dateien
|
||||
- config.example | 2 +-
|
||||
|
||||
---
|
||||
## 2026-07-13 10:31 – 10:31 (0m)
|
||||
**Beschreibung:** Claude Code Session
|
||||
**Projekt:** miyagi-backup
|
||||
|
||||
### Commits
|
||||
Keine Commits in dieser Session.
|
||||
|
||||
### Geänderte Dateien
|
||||
- config.example | 2 +-
|
||||
|
||||
---
|
||||
## 2026-07-13 10:32 – 10:37 (4m)
|
||||
**Beschreibung:** Claude Code Session
|
||||
**Projekt:** miyagi-backup
|
||||
|
||||
### Commits
|
||||
Keine Commits in dieser Session.
|
||||
|
||||
### Geänderte Dateien
|
||||
- config.example | 2 +-
|
||||
|
||||
---
|
||||
## 2026-07-13 10:39 – 10:39 (0m)
|
||||
**Beschreibung:** Claude Code Session
|
||||
**Projekt:** miyagi-backup
|
||||
|
||||
### Commits
|
||||
Keine Commits in dieser Session.
|
||||
|
||||
### Geänderte Dateien
|
||||
- config.example | 2 +-
|
||||
|
||||
---
|
||||
## 2026-07-13 12:00 – 12:00 (0m)
|
||||
**Beschreibung:** Claude Code Session
|
||||
**Projekt:** miyagi-backup
|
||||
|
||||
### Commits
|
||||
Keine Commits in dieser Session.
|
||||
|
||||
### Geänderte Dateien
|
||||
- config.example | 2 +-
|
||||
|
||||
---
|
||||
## 2026-07-13 12:00 – 12:01 (0m)
|
||||
**Beschreibung:** Claude Code Session
|
||||
**Projekt:** miyagi-backup
|
||||
|
||||
### Commits
|
||||
Keine Commits in dieser Session.
|
||||
|
||||
### Geänderte Dateien
|
||||
- config.example | 2 +-
|
||||
|
||||
---
|
||||
## 2026-07-14 09:40 – 13:40 (4h 00m)
|
||||
**Beschreibung:** Claude Code Session
|
||||
**Projekt:** miyagi-backup
|
||||
|
||||
### Commits
|
||||
- 0141cdc miyagi-check.sh: Emoji aus LOG-Meldungen entfernen
|
||||
- cbef884 Root: neue Hilfsskripte, config.example erweitern, DEVLOG einchecken
|
||||
- d83c311 testing/: bisherige Testversion inkl. Erweiterungen einchecken
|
||||
- 1619f22 v3: konsolidierte miyagi-backup Version aus v1-alt/v2-alt
|
||||
|
||||
### Geänderte Dateien
|
||||
- miyagi-check.sh | 100 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
|
||||
|
||||
---
|
||||
## 2026-07-14 13:41 – 13:41 (0m)
|
||||
**Beschreibung:** Claude Code Session
|
||||
**Projekt:** miyagi-backup
|
||||
**Projekt:** v3
|
||||
|
||||
### Commits
|
||||
Keine Commits in dieser Session.
|
||||
@@ -718,9 +171,9 @@ Keine Commits in dieser Session.
|
||||
- miyagi-check.sh | 100 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
|
||||
|
||||
---
|
||||
## 2026-07-14 15:20 – 15:20 (0m)
|
||||
## 2026-07-16 14:16 – 14:48 (32m)
|
||||
**Beschreibung:** Claude Code Session
|
||||
**Projekt:** miyagi-backup
|
||||
**Projekt:** v3
|
||||
|
||||
### Commits
|
||||
Keine Commits in dieser Session.
|
||||
@@ -729,31 +182,9 @@ Keine Commits in dieser Session.
|
||||
- miyagi-check.sh | 100 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
|
||||
|
||||
---
|
||||
## 2026-07-14 23:59 – 00:01 (2m)
|
||||
## 2026-07-16 14:49 – 14:51 (1m)
|
||||
**Beschreibung:** Claude Code Session
|
||||
**Projekt:** archivdms
|
||||
|
||||
### Commits
|
||||
Keine Commits in dieser Session.
|
||||
|
||||
### Geänderte Dateien
|
||||
- miyagi-check.sh | 100 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
|
||||
|
||||
---
|
||||
## 2026-09-08 20:16 – 20:23 (6m)
|
||||
**Beschreibung:** Claude Code Session
|
||||
**Projekt:** asb-material
|
||||
|
||||
### Commits
|
||||
Keine Commits in dieser Session.
|
||||
|
||||
### Geänderte Dateien
|
||||
- miyagi-check.sh | 100 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
|
||||
|
||||
---
|
||||
## 2026-09-09 12:45 – 12:45 (0m)
|
||||
**Beschreibung:** Claude Code Session
|
||||
**Projekt:** miyagi-backup
|
||||
**Projekt:** v3
|
||||
|
||||
### Commits
|
||||
Keine Commits in dieser Session.
|
||||
|
||||
@@ -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
|
||||
+10
-3
@@ -9,7 +9,8 @@ REBOOT='no' # Reboot nach Update?
|
||||
|
||||
# Quelle (Proxmox VE System, das gesichert wird)
|
||||
SOURCEPORT='22' # SSH-Port, normalerweise 22
|
||||
SOURCEHOST='192.168.50.200' # IP des Quell-Proxmox-Servers
|
||||
SOURCEHOST='192.168.50.200' # IP des Quell-Proxmox-Servers
|
||||
SOURCEUSER='root' # SSH-User für SOURCEHOST
|
||||
|
||||
# Replikation (ZFS)
|
||||
ZFSTRGT='rpool-ssd1/repl/pve200' # Zielpfad für Replikation
|
||||
@@ -26,8 +27,11 @@ ZPUSHFILTER='hourly|daily|weekly|monthly' # Weitere Filter (leer lassen oder Mus
|
||||
BACKUPSERVER='no' # Backup via PBS aktivieren?
|
||||
MAINTDAY='7' # Wartungstag (1=Mo, 7=So)
|
||||
PBSHOST='192.168.50.199' # IP des Proxmox Backup Servers
|
||||
PBSPORT='22' ##testing##
|
||||
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
|
||||
BACKUPSTOREPBS='backup' # Datastore auf PBS
|
||||
BACKUPEXCLUDE='10,3252,3253,3254' # VM/CT-IDs, die vom Backup ausgeschlossen sind
|
||||
@@ -38,7 +42,10 @@ REPLEXCLUDE="$BACKUPEXCLUDE" # Diese auch von Replikation ausschlie
|
||||
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_HOST='192.168.50.210' # Monitoring-Zielhost für check_zfs
|
||||
ECHECKZFS_USER='root' # SSH-User für ECHECKZFS_HOST
|
||||
File renamed without changes.
+809
-373
File diff suppressed because it is too large.
Load diff
-263
@@ -1,263 +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=(
|
||||
SOURCEPORT
|
||||
BACKUPSERVER
|
||||
ZSYNC
|
||||
MAINTDAY
|
||||
SHUTDOWN
|
||||
UPDATES
|
||||
SOURCEHOST
|
||||
EPIGGYBACK
|
||||
EPIGGYBACK_PORT
|
||||
EPIGGYBACK_HOST
|
||||
ECHECKZFS
|
||||
ECHECKZFS_PORT
|
||||
ECHECKZFS_HOST
|
||||
ZFSROOT
|
||||
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"
|
||||
local port="$2"
|
||||
LOG "Prüfe SSH-Verbindung zu $host:$port ..."
|
||||
if ssh -p "$port" -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
|
||||
}
|
||||
|
||||
run_host_check() {
|
||||
local host=$1
|
||||
local port=$2
|
||||
local do_pve_check=${3:-yes} # Standard: ja
|
||||
|
||||
LOG ""
|
||||
LOG "=== Prüfung für Host: $host:$port ==="
|
||||
if check_ssh_connection "$host" "$port"; then
|
||||
check_and_copy_ssh_key "$host" "$port"
|
||||
check_sshd_config_recommendation "$host" "$port"
|
||||
|
||||
if [[ "$do_pve_check" == "yes" ]]; then
|
||||
check_pveversion "$host" "$port"
|
||||
else
|
||||
LOG " PVE-Version-Check für $host übersprungen (nicht PVE)."
|
||||
fi
|
||||
fi
|
||||
echo ""
|
||||
}
|
||||
|
||||
|
||||
run_host_check "$SOURCEHOST"
|
||||
|
||||
if [[ "$BACKUPSERVER" == "yes" ]]; then
|
||||
run_host_check "$PBSHOST"
|
||||
else
|
||||
LOG " BACKUPSERVER ist deaktiviert PBSHOST wird übersprungen."
|
||||
fi
|
||||
|
||||
run_pbs_check
|
||||
|
||||
if [[ "${EPIGGYBACK,,}" == "yes" ]]; then
|
||||
run_host_check "$EPIGGYBACK_HOST"
|
||||
else
|
||||
LOG " Externer Piggyback-Host deaktiviert – überspringe."
|
||||
fi
|
||||
|
||||
if [[ "${ECHECKZFS,,}" == "yes" ]]; then
|
||||
run_host_check "$ECHECKZFS_HOST"
|
||||
else
|
||||
LOG " Externer Checkzfs-Host deaktiviert – überspringe."
|
||||
fi
|
||||
|
||||
|
||||
check_pbs_version() {
|
||||
local host=$1
|
||||
LOG "Prüfe PBS-Version auf $host ..."
|
||||
if ssh -p 22 "$host" "command -v proxmox-backup-server >/dev/null"; then
|
||||
ssh -p 22 "$host" "proxmox-backup-server version" | while read -r line; do
|
||||
LOG " $host: $line"
|
||||
done
|
||||
else
|
||||
LOG " 'proxmox-backup-server' ist auf $host nicht verfügbar – kein PBS?"
|
||||
fi
|
||||
}
|
||||
|
||||
run_pbs_check() {
|
||||
if [[ "${BACKUPSERVER,,}" != "yes" ]]; then
|
||||
LOG " BACKUPSERVER ist deaktiviert – PBSHOST wird übersprungen."
|
||||
return
|
||||
fi
|
||||
|
||||
local host=$PBSHOST
|
||||
LOG ""
|
||||
LOG "=== Prüfung für PBS-Host: $host ==="
|
||||
|
||||
if check_ssh_connection "$host"; then
|
||||
check_and_copy_ssh_key "$host"
|
||||
check_sshd_config_recommendation "$host"
|
||||
check_pbs_version "$host"
|
||||
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
|
||||
@@ -1,220 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
IFS=$'\n\t'
|
||||
|
||||
PERMITROOT_YES_HOSTS=()
|
||||
LOG() {
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"
|
||||
}
|
||||
|
||||
CONFIG_FILE="${1:-}"
|
||||
|
||||
if [[ -z "$CONFIG_FILE" ]]; then
|
||||
LOG "Keine Konfigurationsdatei übergeben."
|
||||
echo "Usage: $0 /pfad/zur/config"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -f "$CONFIG_FILE" ]]; then
|
||||
LOG "Konfigurationsdatei nicht gefunden: $CONFIG_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! bash -n "$CONFIG_FILE"; then
|
||||
LOG "Syntaxfehler in der Konfigurationsdatei!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
source "$CONFIG_FILE"
|
||||
|
||||
REQUIRED_VARS=(
|
||||
SOURCEPORT
|
||||
BACKUPSERVER
|
||||
ZSYNC
|
||||
MAINTDAY
|
||||
SHUTDOWN
|
||||
UPDATES
|
||||
SOURCEHOST
|
||||
PBSHOST
|
||||
BACKUPSTORE
|
||||
BACKUPSTOREPBS
|
||||
BACKUPEXCLUDE
|
||||
REPLEXCLUDE
|
||||
)
|
||||
|
||||
MISSING=()
|
||||
for var in "${REQUIRED_VARS[@]}"; do
|
||||
if ! declare -p "$var" &>/dev/null || [[ -z "${!var}" ]]; then
|
||||
MISSING+=("$var")
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ ${#MISSING[@]} -gt 0 ]]; then
|
||||
LOG " Fehlende Variablen in Konfiguration:"
|
||||
for v in "${MISSING[@]}"; do echo " - $v"; done
|
||||
exit 1
|
||||
else
|
||||
LOG " Alle Variablen gesetzt."
|
||||
fi
|
||||
|
||||
if [[ ${#MISSING[@]} -gt 0 ]]; then
|
||||
LOG " Fehlende Variablen in Konfiguration:"
|
||||
for v in "${MISSING[@]}"; do echo " - $v"; done
|
||||
exit 1
|
||||
else
|
||||
LOG " Alle Variablen gesetzt."
|
||||
fi
|
||||
|
||||
# Prüfung, ob SHUTDOWN auch tatsächlich in der Datei definiert wurde
|
||||
if ! grep -qE '^\s*SHUTDOWN\s*=' "$CONFIG_FILE"; then
|
||||
LOG "Hinweis: Die Variable 'SHUTDOWN' ist zwar gesetzt, aber nicht direkt in der Konfigurationsdatei '$CONFIG_FILE' definiert."
|
||||
LOG " → Bitte prüfen, ob dies gewollt ist oder von einer übergeordneten Quelle stammt."
|
||||
fi
|
||||
|
||||
# Anzeigen, ob SHUTDOWN aktiv ist
|
||||
LOG "SHUTDOWN-Status: ${SHUTDOWN:-nicht gesetzt}"
|
||||
|
||||
case "${SHUTDOWN,,}" in
|
||||
yes)
|
||||
LOG "Nach dem Backup wird das System heruntergefahren."
|
||||
;;
|
||||
no)
|
||||
LOG "Kein automatischer Shutdown nach dem Backup."
|
||||
;;
|
||||
*)
|
||||
LOG "Unbekannter SHUTDOWN-Wert: '${SHUTDOWN}' , erwartet: 'yes' oder 'no'"
|
||||
;;
|
||||
esac
|
||||
|
||||
check_ssh_connection() {
|
||||
local host=$1
|
||||
LOG "Prüfe SSH-Verbindung zu $host ..."
|
||||
if ssh -p "$SOURCEPORT" -o BatchMode=yes -o ConnectTimeout=5 "$host" "echo OK" 2>/dev/null | grep -q OK; then
|
||||
LOG " SSH-Verbindung zu $host erfolgreich."
|
||||
return 0
|
||||
else
|
||||
LOG " SSH-Verbindung zu $host fehlgeschlagen!"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
check_and_copy_ssh_key() {
|
||||
local host=$1
|
||||
local keyfile="$HOME/.ssh/id_rsa.pub"
|
||||
|
||||
if [[ ! -f "$keyfile" ]]; then
|
||||
LOG " Lokaler SSH-Public-Key ($keyfile) nicht gefunden!"
|
||||
return 1
|
||||
fi
|
||||
local pubkey
|
||||
pubkey=$(<"$keyfile")
|
||||
|
||||
LOG " Prüfe, ob SSH-Key auf $host autorisiert ist ..."
|
||||
|
||||
if ssh -p "$SOURCEPORT" "$host" "grep -qF '$pubkey' ~/.ssh/authorized_keys" 2>/dev/null; then
|
||||
LOG " SSH-Key ist bereits auf $host hinterlegt."
|
||||
else
|
||||
LOG " SSH-Key nicht auf $host vorhanden."
|
||||
|
||||
read -rp " Möchtest du den SSH-Key jetzt via ssh-copy-id übertragen? [j/N] " ans
|
||||
if [[ "$ans" =~ ^[JjYy]$ ]]; then
|
||||
ssh-copy-id -p "$SOURCEPORT" "$host"
|
||||
else
|
||||
LOG " SSH-Key nicht übertragen."
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
check_sshd_config_recommendation() {
|
||||
local host=$1
|
||||
LOG " Prüfe sshd_config auf $host bzgl. 'PermitRootLogin'..."
|
||||
|
||||
local current_setting
|
||||
current_setting=$(ssh -p "$SOURCEPORT" "$host" "grep -i '^PermitRootLogin' /etc/ssh/sshd_config" 2>/dev/null || echo "")
|
||||
|
||||
if [[ -z "$current_setting" ]]; then
|
||||
LOG " Keine explizite 'PermitRootLogin'-Einstellung gefunden."
|
||||
elif echo "$current_setting" | grep -qi "PermitRootLogin yes"; then
|
||||
LOG " Aktuell ist PermitRootLogin=YES erlaubt auf $host"
|
||||
LOG " Empfehlung: auf 'prohibit-password' umstellen."
|
||||
PERMITROOT_YES_HOSTS+=("$host")
|
||||
else
|
||||
LOG " PermitRootLogin-Einstellung ist: $current_setting"
|
||||
fi
|
||||
}
|
||||
|
||||
check_pveversion() {
|
||||
local host=$1
|
||||
LOG "Prüfe PVE-Version auf $host ..."
|
||||
if ssh -p "$SOURCEPORT" "$host" "command -v pveversion >/dev/null"; then
|
||||
ssh -p "$SOURCEPORT" "$host" "pveversion" | while read -r line; do
|
||||
LOG " $host: $line"
|
||||
done
|
||||
else
|
||||
LOG " 'pveversion' ist auf $host nicht verfügbar – kein Proxmox?"
|
||||
fi
|
||||
}
|
||||
check_pbs_version() {
|
||||
local host=$1
|
||||
local port=$2
|
||||
LOG "Prüfe PBS-Version auf $host ..."
|
||||
if ssh -p "$port" "$host" "command -v proxmox-backup-manager >/dev/null"; then
|
||||
ssh -p "$port" "$host" "proxmox-backup-manager version" | while read -r line; do
|
||||
LOG " $host: $line"
|
||||
done
|
||||
else
|
||||
LOG " 'proxmox-backup-manager' ist auf $host nicht verfügbar – kein PBS?"
|
||||
fi
|
||||
}
|
||||
run_host_check() {
|
||||
local host=$1
|
||||
local type=${2:-pve}
|
||||
local port=$SOURCEPORT
|
||||
local pbsport=$PBSPORT
|
||||
|
||||
LOG ""
|
||||
LOG "=== Prüfung für Host: $host (Typ: $type) ==="
|
||||
|
||||
if check_ssh_connection "$host" "$port"; then
|
||||
check_and_copy_ssh_key "$host"
|
||||
check_sshd_config_recommendation "$host"
|
||||
|
||||
if [[ "$type" == "pve" ]]; then
|
||||
check_pveversion "$host" "$port"
|
||||
elif [[ "$type" == "pbs" ]]; then
|
||||
check_pbs_version "$host" "$pbsport"
|
||||
else
|
||||
LOG " Unbekannter Host-Typ: $type"
|
||||
fi
|
||||
fi
|
||||
echo ""
|
||||
}
|
||||
|
||||
|
||||
|
||||
run_host_check "$SOURCEHOST" pve
|
||||
|
||||
if [[ "$BACKUPSERVER" == "yes" ]]; then
|
||||
run_host_check "$PBSHOST" pbs
|
||||
else
|
||||
LOG " BACKUPSERVER ist deaktiviert PBSHOST wird übersprungen."
|
||||
fi
|
||||
|
||||
# NACHLAUF: Frage zur automatischen Änderung von PermitRootLogin
|
||||
if [[ ${#PERMITROOT_YES_HOSTS[@]} -gt 0 ]]; then
|
||||
echo ""
|
||||
echo " Die folgenden Hosts erlauben derzeit root-Login per Passwort:"
|
||||
for h in "${PERMITROOT_YES_HOSTS[@]}"; do
|
||||
echo " - $h"
|
||||
done
|
||||
|
||||
read -rp " Möchtest du PermitRootLogin auf diesen Hosts auf 'prohibit-password' setzen und sshd neustarten? [j/N] " change_ans
|
||||
if [[ "$change_ans" =~ ^[JjYy]$ ]]; then
|
||||
for h in "${PERMITROOT_YES_HOSTS[@]}"; do
|
||||
echo "Ändere sshd_config auf $h ..."
|
||||
ssh -p "$SOURCEPORT" "$h" "sed -i 's/^PermitRootLogin.*/PermitRootLogin prohibit-password/' /etc/ssh/sshd_config && systemctl reload sshd && echo '✅ sshd auf $h neu geladen.' || echo '❌ Fehler bei $h'"
|
||||
done
|
||||
else
|
||||
echo " Änderung von sshd_config übersprungen."
|
||||
fi
|
||||
fi
|
||||
@@ -1,109 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
IFS=$'\n\t'
|
||||
|
||||
log() {
|
||||
echo "[INFO] $*"
|
||||
}
|
||||
|
||||
error_exit() {
|
||||
echo "[ERROR] $*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
sanitize_value() {
|
||||
echo "$1" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//'
|
||||
}
|
||||
|
||||
load_config() {
|
||||
local config_file="$1"
|
||||
if [[ ! -f "$config_file" ]]; then
|
||||
error_exit "Konfigurationsdatei nicht gefunden: $config_file"
|
||||
fi
|
||||
|
||||
log "Lade und bereinige Konfigurationsdatei: $config_file"
|
||||
while IFS='=' read -r key value; do
|
||||
# nur gültige Variablennamen parsen
|
||||
if [[ "$key" =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]]; then
|
||||
# Kommentar nach Wert entfernen
|
||||
value="${value%%#*}"
|
||||
value="$(sanitize_value "$value")"
|
||||
# Variable setzen (für den write_new_config Zugriff)
|
||||
eval "$key=\"$value\""
|
||||
fi
|
||||
done < "$config_file"
|
||||
}
|
||||
|
||||
write_new_config() {
|
||||
local out_file="$1"
|
||||
|
||||
cat > "$out_file" <<EOF
|
||||
#Edit all Variables for best Experience
|
||||
|
||||
UPDATES='${UPDATES}' # Proxmox VE und PBS Updates nach dem Lauf
|
||||
SHUTDOWN='${SHUTDOWN}' # System nach Ausführung herunterfahren?
|
||||
|
||||
# Quelle (Proxmox VE System, das gesichert wird)
|
||||
SOURCEPORT='${SSHPORT}' # SSH-Port, normalerweise 22
|
||||
SOURCEHOST='${SOURCEHOST}' # IP des Quell-Proxmox-Servers
|
||||
|
||||
# Replikation (ZFS)
|
||||
ZFSROOT='${ZFSROOT}' # Erstes Dataset vom Quellsystem
|
||||
ZFSSECOND='${ZFSSECOND}' # Optional zweites Dataset
|
||||
ZFSTRGT='${ZFSTRGT}' # Zielpfad für Replikation
|
||||
|
||||
# ZFS Zsync Replikation
|
||||
ZSYNC='${ZSYNC}' # ZSYNC aktivieren (ja/nein)
|
||||
ZPUSHTAG='${ZPUSHTAG}' # Benutzer-Tag für ZFS
|
||||
ZPUSHMINKEEP='${ZPUSHMINKEEP}' # Mindestens zu behaltende Snapshots
|
||||
ZPUSHKEEP='${ZPUSHKEEP}' # Snapshots mit dem Tag, die behalten werden
|
||||
ZPUSHLABEL='${ZPUSHLABEL}' # Suffix für Snapshot-Autoengine
|
||||
ZPUSHFILTER='${ZPUSHFILTER}' # Weitere Filter (leer lassen oder Muster wie daily| weekly etc.)
|
||||
|
||||
# Backup mit Proxmox Backup Server
|
||||
BACKUPSERVER='${BACKUPSERVER}' # Backup via PBS aktivieren?
|
||||
MAINTDAY='${MAINTDAY}' # Wartungstag (1=Mo, 7=So)
|
||||
PBSHOST='${PBSHOST}' # IP des Proxmox Backup Servers
|
||||
BACKUPSTORE='${BACKUPSTORE}' # Datastore auf Quell-Proxmox
|
||||
BACKUPSTOREPBS='${BACKUPSTOREPBS}' # Datastore auf PBS
|
||||
BACKUPEXCLUDE='${BACKUPEXCLUDE}' # VM/CT-IDs, die vom Backup ausgeschlossen sind
|
||||
REPLEXCLUDE=\$BACKUPEXCLUDE # Diese auch von Replikation ausschließen
|
||||
|
||||
# Zusätzliche Monitoring-Ziele
|
||||
# External Piggyback Host
|
||||
EPIGGYBACK='${EPIGGYBACK}' # Piggyback-Daten an Monitoring-Ziel senden?
|
||||
EPIGGYBACK_PORT='${EPIGGYBACK_PORT}' # SSH-Port für EPIGGYBACK_HOST
|
||||
EPIGGYBACK_HOST='${EPIGGYBACK_HOST}' # Monitoring-Zielhost für Piggyback
|
||||
|
||||
# External Checkzfs Host
|
||||
ECHECKZFS='${ECHECKZFS}' # check_zfs-Output an Monitoring-Ziel senden?
|
||||
ECHECKZFS_PORT='${ECHECKZFS_PORT}' # SSH-Port für ECHECKZFS_HOST
|
||||
ECHECKZFS_HOST='${ECHECKZFS_HOST}' # Monitoring-Zielhost für check_zfs
|
||||
EOF
|
||||
}
|
||||
|
||||
|
||||
if [[ $# -ne 1 ]]; then
|
||||
echo "Usage: $0 <configfile>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ORIGINAL="$1"
|
||||
|
||||
read -rp "Soll die aktuelle Datei als Backup gesichert werden (umbenennen)? (ja/nein): " RESPONSE
|
||||
RESPONSE="${RESPONSE,,}"
|
||||
|
||||
if [[ "$RESPONSE" == "ja" || "$RESPONSE" == "j" ]]; then
|
||||
BACKUPFILE="${ORIGINAL}.bak"
|
||||
mv "$ORIGINAL" "$BACKUPFILE"
|
||||
log "Originaldatei wurde umbenannt in: $BACKUPFILE"
|
||||
CONFIG_TO_READ="$BACKUPFILE"
|
||||
else
|
||||
log "Keine Sicherung der Originaldatei durchgeführt. Original bleibt unverändert."
|
||||
CONFIG_TO_READ="$ORIGINAL"
|
||||
fi
|
||||
|
||||
NEWFILE="${ORIGINAL}.convert"
|
||||
load_config "$CONFIG_TO_READ"
|
||||
write_new_config "$NEWFILE"
|
||||
log "Neue Konfiguration geschrieben in: $NEWFILE"
|
||||
+198
-160
@@ -1,29 +1,41 @@
|
||||
#!/bin/bash
|
||||
# Author: (C) 2025 Patrick Perlbach <patrick@perlbach24.de>
|
||||
# --- Konfiguration und Initiale Variablen ---
|
||||
#!/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; }
|
||||
|
||||
# --- Hilfsfunktionen ---
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage: $0 -c <config> [--yes] [--dry-run]
|
||||
Usage: $0 [OPTIONS]
|
||||
|
||||
-c <file> Pfad zur Konfigurationsdatei (Pflicht)
|
||||
--yes Automatische Ausführung ohne Rückfrage
|
||||
--dry-run Zeigt nur, was gemacht würde
|
||||
--help Diese Hilfe anzeigen
|
||||
-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 1
|
||||
}
|
||||
|
||||
run_or_echo() {
|
||||
if [[ "$DRY_RUN" == "1" ]]; then
|
||||
echo "[DRY-RUN] $*"
|
||||
else
|
||||
eval "$@"
|
||||
fi
|
||||
exit 0
|
||||
}
|
||||
|
||||
parse_args() {
|
||||
@@ -32,224 +44,250 @@ parse_args() {
|
||||
-c) CONFIG_FILE="$2"; shift 2 ;;
|
||||
--yes) FORCE_YES=1; shift ;;
|
||||
--dry-run) DRY_RUN=1; shift ;;
|
||||
--dev) DEV_MODE=1; shift ;;
|
||||
--help) usage ;;
|
||||
*) echo "[ERROR] Unbekannter Parameter: $1"; usage ;;
|
||||
*) die "Unbekannter Parameter: $1" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
[[ -z "$CONFIG_FILE" || ! -f "$CONFIG_FILE" ]] && {
|
||||
echo "[ERROR] Konfigurationsdatei fehlt oder ungültig!"
|
||||
exit 1
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
source "$CONFIG_FILE"
|
||||
run() {
|
||||
if [[ "$DRY_RUN" -eq 1 ]]; then
|
||||
echo "[DRY-RUN] $*"
|
||||
else
|
||||
"$@"
|
||||
fi
|
||||
}
|
||||
|
||||
confirm() {
|
||||
if [[ "$FORCE_YES" -eq 1 ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
[[ "$FORCE_YES" -eq 1 ]] && return 0
|
||||
while true; do
|
||||
read -rp "$1 [j/N]: " yn
|
||||
case $yn in
|
||||
case "$yn" in
|
||||
[JjYy]*) return 0 ;;
|
||||
[Nn]*|"") return 1 ;;
|
||||
*) echo "Bitte j (ja) oder n (nein) eingeben." ;;
|
||||
*) echo "Bitte j oder n eingeben." ;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
|
||||
install_cmk() {
|
||||
echo "[INFO] Lade cmk.deb herunter..."
|
||||
run_or_echo "curl -fsSL \"$CMK_URL\" -o \"$CMK_FILE\""
|
||||
|
||||
echo "[INFO] Installiere cmk.deb..."
|
||||
run_or_echo "sudo apt install -y \"$CMK_FILE\""
|
||||
ensure_dialog() {
|
||||
if ! command -v dialog &>/dev/null; then
|
||||
info "Installiere dialog..."
|
||||
run apt-get install -y -qq dialog || die "dialog konnte nicht installiert werden"
|
||||
fi
|
||||
}
|
||||
|
||||
install_fix_interface_names() {
|
||||
echo "[INFO] Installiere Fix für Interface-Namen..."
|
||||
run_or_echo "wget -O /tmp/debian-fix-interface-names.sh \"$FIX_INTERFACE_NAMES_URL\""
|
||||
run_or_echo "chmod +x /tmp/debian-fix-interface-names.sh"
|
||||
run_or_echo "bash /tmp/debian-fix-interface-names.sh"
|
||||
get_base_url() {
|
||||
[[ "$DEV_MODE" -eq 1 ]] && echo "$GITEA_BASE" || echo "$GITHUB_BASE"
|
||||
}
|
||||
|
||||
install_miyagi_scripts() {
|
||||
echo "[INFO] Lade Miyagi-Backup-Skripte..."
|
||||
run_or_echo "wget -O miyagi-backup.sh \"$MIYAGI_BACKUP_URL\""
|
||||
run_or_echo "wget -O miyagi-check.sh \"$MIYAGI_CHECK_URL\""
|
||||
run_or_echo "wget -O miyagi-convert.sh \"$MIYAGI_CONVERT_URL\""
|
||||
run_or_echo "mkdir -p /root/miyagi-backup"
|
||||
run_or_echo "mv miyagi-backup.sh miyagi-check.sh miyagi-convert.sh /root/miyagi-backup/"
|
||||
run_or_echo "chmod +x /root/miyagi-backup/*.sh"
|
||||
}
|
||||
# ── Installationsschritte ─────────────────────────────────────────────────────
|
||||
|
||||
install_checkmk_plugins() {
|
||||
echo "[INFO] Installiere disk_smart_info Plugin..."
|
||||
run_or_echo "curl -fsSL \"$DISK_SMART_INFO_URL\" -o /usr/lib/check_mk_agent/plugins/disk_smart_info"
|
||||
run_or_echo "chmod +x /usr/lib/check_mk_agent/plugins/disk_smart_info"
|
||||
}
|
||||
install_miyagi() {
|
||||
local base
|
||||
base="$(get_base_url)"
|
||||
local source_label
|
||||
[[ "$DEV_MODE" -eq 1 ]] && source_label="Gitea (dev)" || source_label="GitHub (stable)"
|
||||
|
||||
install_cleansnaps() {
|
||||
echo "[INFO] Installiere cleansnaps Skript..."
|
||||
run_or_echo "wget -O /usr/local/bin/cleansnaps \"$CLEANSNAPS_URL\""
|
||||
run_or_echo "chmod +x /usr/local/bin/cleansnaps"
|
||||
}
|
||||
info "Installiere miyagi-backup von $source_label..."
|
||||
|
||||
install_check_snapshot_age() {
|
||||
echo "[INFO] Installiere check-snapshot-age..."
|
||||
run_or_echo "wget -O /usr/local/bin/check-snapshot-age \"$CHECK_SNAPSHOT_AGE_URL\""
|
||||
run_or_echo "chmod +x /usr/local/bin/check-snapshot-age"
|
||||
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() {
|
||||
echo "[INFO] Installiere checkzfs..."
|
||||
run_or_echo "wget -O /usr/local/bin/checkzfs \"$CHECKZFS_URL\""
|
||||
run_or_echo "chmod +x /usr/local/bin/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() {
|
||||
echo "[INFO] Teste SSH-Verbindung zu $SOURCEHOST ..."
|
||||
[[ -z "${SOURCEHOST:-}" ]] && warn "Kein SOURCEHOST in Config – SSH-Check übersprungen" && return
|
||||
info "Teste SSH-Verbindung zu ${SOURCEUSER}@${SOURCEHOST}:${SOURCEPORT}..."
|
||||
|
||||
if ssh -p "$SOURCEPORT" -o BatchMode=yes -o ConnectTimeout=5 root@"$SOURCEHOST" "echo Verbindung erfolgreich" 2>/dev/null; then
|
||||
echo "[OK] SSH-Verbindung über Schlüssel funktioniert."
|
||||
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
|
||||
echo "[WARN] Keine Schlüsselbasierte SSH-Verbindung möglich oder abgelehnt."
|
||||
|
||||
echo "[HINWEIS] Eine Passwortabfrage erfolgt jetzt – dies ist normal."
|
||||
if ssh -p "$SOURCEPORT" root@"$SOURCEHOST" "echo Verbindung erfolgreich"; then
|
||||
echo "[OK] SSH-Verbindung per Passwort erfolgreich."
|
||||
|
||||
if confirm "Möchtest du den lokalen SSH-Schlüssel auf $SOURCEHOST kopieren (per ssh-copy-id)?"; then
|
||||
run_or_echo "ssh-copy-id -p \"$SOURCEPORT\" root@\"$SOURCEHOST\""
|
||||
echo "[INFO] Bitte erneut starten, um die Schlüsselverbindung zu nutzen."
|
||||
exit 0
|
||||
else
|
||||
echo "[HINWEIS] Es wird weiterhin Passwortabfrage benötigt."
|
||||
fi
|
||||
else
|
||||
echo "[FEHLER] SSH-Verbindung nicht möglich – bitte Zugang prüfen!"
|
||||
exit 1
|
||||
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() {
|
||||
echo "[INFO] Lade ZFS-Datasets vom Remote-Host..."
|
||||
[[ -z "${SOURCEHOST:-}" ]] && warn "Kein SOURCEHOST – ZFS-Tagging übersprungen" && return
|
||||
|
||||
LOCAL_HOSTNAME=$(hostname -s)
|
||||
REMOTE_HOSTNAME=$(ssh -p "$SOURCEPORT" root@"$SOURCEHOST" "hostname -s")
|
||||
TAG_KEY="bashclub:miyagi-${REMOTE_HOSTNAME}-${LOCAL_HOSTNAME}"
|
||||
TAG_VALUE="subvols"
|
||||
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; }
|
||||
|
||||
# Hole alle relevanten Pool-Namen
|
||||
mapfile -t pools < <(ssh -p "$SOURCEPORT" root@"$SOURCEHOST" \
|
||||
"zfs list -H -o name" | \
|
||||
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
|
||||
echo "[WARN] Keine geeigneten Datasets gefunden!"
|
||||
warn "Keine geeigneten ZFS-Datasets gefunden"
|
||||
return
|
||||
fi
|
||||
|
||||
local options=()
|
||||
local tagged_pools=()
|
||||
|
||||
for pool in "${pools[@]}"; do
|
||||
local tag status
|
||||
tag=$(ssh -p "$SOURCEPORT" root@"$SOURCEHOST" zfs get -H -o value "$TAG_KEY" "$pool" 2>/dev/null)
|
||||
if [[ "$tag" == "$TAG_VALUE" ]]; then
|
||||
status="on"
|
||||
tagged_pools+=("$pool")
|
||||
else
|
||||
status="off"
|
||||
fi
|
||||
options+=("$pool" "$([[ "$status" == "on" ]] && echo 'TAGGED' || echo 'untagged')" "$status")
|
||||
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=$(whiptail --title "ZFS Pools auswählen" \
|
||||
--checklist "Wähle Datasets zum Taggen aus:" 20 78 12 \
|
||||
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)
|
||||
|
||||
if [[ $? -ne 0 ]]; then
|
||||
echo "[INFO] Auswahl abgebrochen."
|
||||
return
|
||||
fi
|
||||
clear
|
||||
[[ $? -ne 0 ]] && info "ZFS-Tagging abgebrochen" && return
|
||||
|
||||
local selected_array=()
|
||||
eval "selected_array=($selected)"
|
||||
read -ra selected_array <<< "$selected"
|
||||
|
||||
echo "[INFO] Verarbeite Tagging für ausgewählte Pools:"
|
||||
|
||||
for ((i = 0; i < ${#options[@]}; i += 3)); do
|
||||
local pool="${options[i]}"
|
||||
local prev_status="${options[i+2]}"
|
||||
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
|
||||
if [[ "$sel" == "$pool" ]]; then
|
||||
is_selected=1
|
||||
break
|
||||
fi
|
||||
[[ "$sel" == "\"$pool\"" || "$sel" == "$pool" ]] && is_selected=1 && break
|
||||
done
|
||||
|
||||
if [[ "$is_selected" -eq 1 && "$prev_status" == "off" ]]; then
|
||||
echo " → Tagge $pool"
|
||||
run_or_echo ssh -p "$SOURCEPORT" root@"$SOURCEHOST" zfs set "$TAG_KEY=$TAG_VALUE" "$pool"
|
||||
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
|
||||
echo " → Entferne Tag von $pool"
|
||||
run_or_echo ssh -p "$SOURCEPORT" root@"$SOURCEHOST" zfs inherit "$TAG_KEY" "$pool"
|
||||
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() {
|
||||
# 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"
|
||||
[[ $EUID -ne 0 ]] && die "Bitte als root ausführen"
|
||||
|
||||
FIX_INTERFACE_NAMES_URL="https://raw.githubusercontent.com/bashclub/trmm-scripts/refs/heads/main/debian-fix-interface-names.sh"
|
||||
ensure_dialog
|
||||
|
||||
MIYAGI_BACKUP_URL="https://gitea.perlbach24.de/scripte/miyagi-backup/raw/branch/main/miyagi-backup.sh"
|
||||
MIYAGI_CHECK_URL="https://gitea.perlbach24.de/scripte/miyagi-backup/raw/branch/main/miyagi-check.sh"
|
||||
MIYAGI_CONVERT_URL="https://gitea.perlbach24.de/scripte/miyagi-backup/raw/branch/main/miyagi-convert.sh"
|
||||
echo ""
|
||||
echo " ╔══════════════════════════════════════╗"
|
||||
echo " ║ miyagi-backup Installer v3 ║"
|
||||
echo " ╚══════════════════════════════════════╝"
|
||||
echo ""
|
||||
|
||||
DISK_SMART_INFO_URL="https://raw.githubusercontent.com/bashclub/checkmk-smart/main/disk_smart_info.py"
|
||||
local choices
|
||||
choices=$(dialog --clear \
|
||||
--backtitle "miyagi-backup Installer v3" \
|
||||
--title "Komponenten auswählen" \
|
||||
--checklist "Was soll installiert werden?" 22 65 12 \
|
||||
"miyagi" "miyagi-backup.sh + Verzeichnisse + Symlink" on \
|
||||
"checkzfs" "checkzfs (ZFS-Replikationsprüfung)" on \
|
||||
"snapage" "check-snapshot-age" on \
|
||||
"cleansnaps" "cleansnaps (ZFS-Snapshot-Housekeeping)" on \
|
||||
"smartinfo" "disk_smart_info (CheckMK Plugin)" on \
|
||||
"ethtool" "ethtool (WOL-Unterstützung)" off \
|
||||
"ssh" "SSH-Verbindung prüfen / Schlüssel kopieren" on \
|
||||
"zfstag" "ZFS-Pools taggen (benötigt Config -c)" off \
|
||||
3>&1 1>&2 2>&3)
|
||||
clear
|
||||
|
||||
CLEANSNAPS_URL="https://raw.githubusercontent.com/bashclub/zfs-housekeeping/refs/heads/main/cleansnaps.sh"
|
||||
[[ $? -ne 0 ]] && echo "Abgebrochen." && exit 0
|
||||
[[ -z "$choices" ]] && echo "Nichts ausgewählt." && exit 0
|
||||
|
||||
CHECK_SNAPSHOT_AGE_URL="https://gitea.perlbach24.de/scripte/check-zfs-replication/raw/branch/main/check-snapshot-age"
|
||||
[[ "$choices" == *'"miyagi"'* || "$choices" == *'miyagi'* ]] && install_miyagi
|
||||
[[ "$choices" == *'"checkzfs"'* || "$choices" == *'checkzfs'* ]] && install_checkzfs
|
||||
[[ "$choices" == *'"snapage"'* || "$choices" == *'snapage'* ]] && install_check_snapshot_age
|
||||
[[ "$choices" == *'"cleansnaps"'* || "$choices" == *'cleansnaps'* ]] && install_cleansnaps
|
||||
[[ "$choices" == *'"smartinfo"'* || "$choices" == *'smartinfo'* ]] && install_disk_smart_info
|
||||
[[ "$choices" == *'"ethtool"'* || "$choices" == *'ethtool'* ]] && install_ethtool
|
||||
[[ "$choices" == *'"ssh"'* || "$choices" == *'ssh'* ]] && check_ssh
|
||||
[[ "$choices" == *'"zfstag"'* || "$choices" == *'zfstag'* ]] && select_zfs_pools
|
||||
|
||||
CHECKZFS_URL="https://gitea.perlbach24.de/scripte/check-zfs-replication/raw/branch/main/checkzfs.py"
|
||||
|
||||
echo "[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."
|
||||
echo ""
|
||||
ok "Installation abgeschlossen."
|
||||
echo ""
|
||||
}
|
||||
|
||||
# Start
|
||||
parse_args "$@"
|
||||
main
|
||||
File renamed without changes.
@@ -1,24 +0,0 @@
|
||||
# testing – Dev Log
|
||||
|
||||
## 2026-06-18 23:57 – 23:59 (2m)
|
||||
**Beschreibung:** Claude Code Session
|
||||
**Projekt:** miyagi-backup
|
||||
|
||||
### Commits
|
||||
Keine Commits in dieser Session.
|
||||
|
||||
### Geänderte Dateien
|
||||
- config.example | 2 +-
|
||||
|
||||
---
|
||||
## 2026-06-19 00:04 – 00:04 (0m)
|
||||
**Beschreibung:** Claude Code Session
|
||||
**Projekt:** testing
|
||||
|
||||
### Commits
|
||||
Keine Commits in dieser Session.
|
||||
|
||||
### Geänderte Dateien
|
||||
- config.example | 2 +-
|
||||
|
||||
---
|
||||
@@ -1,254 +0,0 @@
|
||||
#!/bin/bash
|
||||
# ==========================================
|
||||
# Miyagi Erweiterungs-Installer mit dialog GUI
|
||||
# ==========================================
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# -------------------------------
|
||||
# 1️⃣ Argumente prüfen (Config laden)
|
||||
# -------------------------------
|
||||
CONFIG_FILE=""
|
||||
|
||||
while getopts "c:" opt; do
|
||||
case "$opt" in
|
||||
c)
|
||||
CONFIG_FILE="$OPTARG"
|
||||
;;
|
||||
*)
|
||||
echo "Verwendung: $0 [-c <configdatei>]"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -n "$CONFIG_FILE" ]]; then
|
||||
if [[ -f "$CONFIG_FILE" ]]; then
|
||||
echo "[INFO] Lade Konfigurationsdatei: $CONFIG_FILE"
|
||||
# shellcheck disable=SC1090
|
||||
source "$CONFIG_FILE"
|
||||
else
|
||||
echo "[WARN] Konfigurationsdatei '$CONFIG_FILE' nicht gefunden!"
|
||||
fi
|
||||
fi
|
||||
|
||||
# -------------------------------
|
||||
# 2️⃣ Voraussetzungen prüfen
|
||||
# -------------------------------
|
||||
if ! command -v dialog &>/dev/null; then
|
||||
echo "[INFO] Installiere 'dialog' für das Erweiterungsmenü..."
|
||||
apt update && apt install -y dialog
|
||||
fi
|
||||
|
||||
# -------------------------------
|
||||
# 3️⃣ ZFS-Pool-Tagging Funktion
|
||||
# -------------------------------
|
||||
zfs_pool_tagging() {
|
||||
echo "[INFO] Starte ZFS-Pool-Tagging..."
|
||||
|
||||
# Host & Port abfragen, falls nicht in Config gesetzt
|
||||
if [[ -z "${SOURCEHOST:-}" ]]; then
|
||||
read -rp "[EINGABE] Remote-Host (z. B. 192.168.1.10): " SOURCEHOST
|
||||
fi
|
||||
|
||||
if [[ -z "${SOURCEPORT:-}" ]]; then
|
||||
read -rp "[EINGABE] SSH-Port [Standard: 22]: " SOURCEPORT
|
||||
SOURCEPORT=${SOURCEPORT:-22}
|
||||
fi
|
||||
|
||||
echo "[INFO] Lade ZFS-Datasets vom Remote-Host..."
|
||||
|
||||
LOCAL_HOSTNAME=$(hostname -s)
|
||||
REMOTE_HOSTNAME=$(ssh -p "$SOURCEPORT" root@"$SOURCEHOST" "hostname -s" 2>/dev/null)
|
||||
|
||||
if [[ -z "$REMOTE_HOSTNAME" ]]; then
|
||||
echo "[FEHLER] Remote-Host konnte nicht erreicht werden!"
|
||||
return 1
|
||||
fi
|
||||
|
||||
TAG_KEY="bashclub:miyagi-${REMOTE_HOSTNAME}-${LOCAL_HOSTNAME}"
|
||||
TAG_VALUE="subvols"
|
||||
|
||||
# Hole Dataset-Namen
|
||||
mapfile -t pools < <(ssh -p "$SOURCEPORT" root@"$SOURCEHOST" \
|
||||
"zfs list -H -o name" | \
|
||||
grep -E '/(vm|subvol)-' | \
|
||||
grep -viE 'repl|replica' | \
|
||||
sed 's:/[^/]*$::' | \
|
||||
sort -u)
|
||||
|
||||
if [[ ${#pools[@]} -eq 0 ]]; then
|
||||
echo "[WARN] Keine geeigneten Datasets gefunden!"
|
||||
return
|
||||
fi
|
||||
|
||||
local options=()
|
||||
for pool in "${pools[@]}"; do
|
||||
local tag status
|
||||
tag=$(ssh -p "$SOURCEPORT" root@"$SOURCEHOST" zfs get -H -o value "$TAG_KEY" "$pool" 2>/dev/null)
|
||||
if [[ "$tag" == "$TAG_VALUE" ]]; then
|
||||
status="on"
|
||||
else
|
||||
status="off"
|
||||
fi
|
||||
options+=("$pool" "$([[ "$status" == "on" ]] && echo 'TAGGED' || echo 'untagged')" "$status")
|
||||
done
|
||||
|
||||
local selected_array=()
|
||||
|
||||
# Auswahl-Dialog
|
||||
if command -v whiptail >/dev/null; then
|
||||
local selected
|
||||
selected=$(whiptail --title "ZFS Pools auswählen" \
|
||||
--checklist "Wähle Datasets zum Taggen aus:" 20 78 12 \
|
||||
"${options[@]}" \
|
||||
3>&1 1>&2 2>&3)
|
||||
[[ $? -ne 0 ]] && echo "[INFO] Auswahl abgebrochen." && return
|
||||
eval "selected_array=($selected)"
|
||||
elif command -v dialog >/dev/null; then
|
||||
local selected
|
||||
selected=$(dialog --clear \
|
||||
--backtitle "ZFS Tagging" \
|
||||
--title "ZFS Pools auswählen" \
|
||||
--checklist "Wähle Datasets zum Taggen aus:" 20 78 12 \
|
||||
"${options[@]}" \
|
||||
3>&1 1>&2 2>&3)
|
||||
clear
|
||||
[[ $? -ne 0 ]] && echo "[INFO] Auswahl abgebrochen." && return
|
||||
eval "selected_array=($selected)"
|
||||
else
|
||||
echo "[INFO] Kein dialog oder whiptail gefunden – Textmodus aktiv."
|
||||
echo "[INFO] Vorhandene Pools:"
|
||||
for i in "${!pools[@]}"; do
|
||||
echo " [$i] ${pools[i]}"
|
||||
done
|
||||
echo
|
||||
read -rp "Gib die Nummern der Pools an (mit Leerzeichen): " user_input
|
||||
for index in $user_input; do
|
||||
selected_array+=("${pools[index]}")
|
||||
done
|
||||
fi
|
||||
|
||||
echo "[INFO] Verarbeite Tagging..."
|
||||
for ((i = 0; i < ${#options[@]}; i += 3)); do
|
||||
local pool="${options[i]}"
|
||||
local prev_status="${options[i+2]}"
|
||||
local is_selected=0
|
||||
|
||||
for sel in "${selected_array[@]}"; do
|
||||
if [[ "$sel" == "$pool" ]]; then
|
||||
is_selected=1
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "$is_selected" -eq 1 && "$prev_status" == "off" ]]; then
|
||||
echo " → Tagge $pool"
|
||||
ssh -p "$SOURCEPORT" root@"$SOURCEHOST" zfs set "$TAG_KEY=$TAG_VALUE" "$pool"
|
||||
elif [[ "$is_selected" -eq 0 && "$prev_status" == "on" ]]; then
|
||||
echo " → Entferne Tag von $pool"
|
||||
ssh -p "$SOURCEPORT" root@"$SOURCEHOST" zfs inherit "$TAG_KEY" "$pool"
|
||||
fi
|
||||
done
|
||||
|
||||
echo "[FERTIG] ZFS-Tagging abgeschlossen."
|
||||
}
|
||||
|
||||
# -------------------------------
|
||||
# 4️⃣ Hauptmenü anzeigen
|
||||
# -------------------------------
|
||||
HEIGHT=20
|
||||
WIDTH=60
|
||||
CHOICE_HEIGHT=10
|
||||
BACKTITLE="Checkmk & ZFS Setup"
|
||||
TITLE="Optionale Erweiterungen"
|
||||
MENU="Wähle gewünschte Zusatzfunktionen:"
|
||||
|
||||
OPTIONS=(
|
||||
1 "Installiere Miyagi-Backup-Skripte" on
|
||||
2 "Installiere disk_smart_info Plugin" on
|
||||
3 "Installiere cleansnaps (ZFS)" on
|
||||
4 "Installiere check-snapshot-age (ZFS)" on
|
||||
5 "Installiere checkzfs (ZFS)" on
|
||||
6 "Starte ZFS-Pool-Tagging" off
|
||||
)
|
||||
|
||||
CHOICES=$(dialog --clear \
|
||||
--backtitle "$BACKTITLE" \
|
||||
--title "$TITLE" \
|
||||
--checklist "$MENU" \
|
||||
$HEIGHT $WIDTH $CHOICE_HEIGHT \
|
||||
"${OPTIONS[@]}" \
|
||||
3>&1 1>&2 2>&3)
|
||||
|
||||
clear
|
||||
|
||||
if [[ -z "$CHOICES" ]]; then
|
||||
echo "[INFO] Keine Erweiterungen ausgewählt. Beende Setup."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# -------------------------------
|
||||
# 5️⃣ Zusammenfassung anzeigen
|
||||
# -------------------------------
|
||||
SUMMARY_TEXT="Du hast folgende Erweiterungen ausgewählt:\n"
|
||||
|
||||
for CHOICE in $CHOICES; do
|
||||
case $CHOICE in
|
||||
1) SUMMARY_TEXT+="\n - Miyagi-Backup-Skripte" ;;
|
||||
2) SUMMARY_TEXT+="\n - disk_smart_info Plugin" ;;
|
||||
3) SUMMARY_TEXT+="\n - cleansnaps (ZFS)" ;;
|
||||
4) SUMMARY_TEXT+="\n - check-snapshot-age (ZFS)" ;;
|
||||
5) SUMMARY_TEXT+="\n - checkzfs (ZFS)" ;;
|
||||
6) SUMMARY_TEXT+="\n - ZFS-Pool-Tagging starten" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
dialog --clear \
|
||||
--backtitle "Checkmk & Erweiterungen" \
|
||||
--title "Zusammenfassung" \
|
||||
--yesno "$SUMMARY_TEXT\n\nMöchtest du die Einrichtung jetzt starten?" \
|
||||
18 70
|
||||
|
||||
response=$?
|
||||
clear
|
||||
[[ $response -ne 0 ]] && echo "[ABBRUCH] Installation abgebrochen." && exit 0
|
||||
|
||||
# -------------------------------
|
||||
# 6️⃣ Ausgewählte Erweiterungen installieren
|
||||
# -------------------------------
|
||||
for CHOICE in $CHOICES; do
|
||||
case $CHOICE in
|
||||
1)
|
||||
echo "[INSTALL] Miyagi-Backup-Skripte..."
|
||||
wget -q --no-cache -O miyagi-backup.sh https://gitea.perlbach24.de/scripte/miyagi-backup/raw/branch/main/miyagi-backup.sh
|
||||
wget -q --no-cache -O miyagi-check.sh https://gitea.perlbach24.de/scripte/miyagi-backup/raw/branch/main/miyagi-check.sh
|
||||
wget -q --no-cache -O miyagi-convert.sh https://gitea.perlbach24.de/scripte/miyagi-backup/raw/branch/main/miyagi-convert.sh
|
||||
;;
|
||||
2)
|
||||
echo "[INSTALL] disk_smart_info Plugin..."
|
||||
curl -fsSL https://raw.githubusercontent.com/bashclub/checkmk-smart/main/disk_smart_info.py -o /usr/lib/check_mk_agent/plugins/disk_smart_info
|
||||
chmod +x /usr/lib/check_mk_agent/plugins/disk_smart_info
|
||||
;;
|
||||
3)
|
||||
echo "[INSTALL] cleansnaps..."
|
||||
wget -q --no-cache -O /usr/local/bin/cleansnaps https://raw.githubusercontent.com/bashclub/zfs-housekeeping/refs/heads/main/cleansnaps.sh
|
||||
chmod +x /usr/local/bin/cleansnaps
|
||||
;;
|
||||
4)
|
||||
echo "[INSTALL] check-snapshot-age..."
|
||||
wget -q --no-cache -O /usr/local/bin/check-snapshot-age https://gitea.perlbach24.de/scripte/check-zfs-replication/raw/branch/main/check-snapshot-age
|
||||
chmod +x /usr/local/bin/check-snapshot-age
|
||||
;;
|
||||
5)
|
||||
echo "[INSTALL] checkzfs..."
|
||||
wget -q --no-cache -O /usr/local/bin/checkzfs https://gitea.perlbach24.de/scripte/check-zfs-replication/raw/branch/main/checkzfs.py
|
||||
chmod +x /usr/local/bin/checkzfs
|
||||
;;
|
||||
6)
|
||||
zfs_pool_tagging
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
echo "[FERTIG] Alle gewählten Komponenten wurden erfolgreich installiert."
|
||||
@@ -1,724 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -uo pipefail
|
||||
IFS=$'\n\t'
|
||||
|
||||
PATH="/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin"
|
||||
SCRIPT_NAME=$(basename "$0")
|
||||
LOGFILE="/var/log/${SCRIPT_NAME%.sh}.log"
|
||||
|
||||
log() {
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOGFILE"
|
||||
}
|
||||
|
||||
usage() {
|
||||
echo "Usage:"
|
||||
echo " $0 -c /path/to/config # Full backup run"
|
||||
echo " $0 [function] # Run individual function"
|
||||
echo " $0 help # Show available functions"
|
||||
exit 1
|
||||
}
|
||||
|
||||
CONFIG_FILE=""
|
||||
while getopts "c:" opt; do
|
||||
case "$opt" in
|
||||
c) CONFIG_FILE="$OPTARG" ;;
|
||||
*) usage ;;
|
||||
esac
|
||||
done
|
||||
shift $((OPTIND - 1))
|
||||
|
||||
if [[ -n "${CONFIG_FILE:-}" ]]; then
|
||||
if [[ ! -f "$CONFIG_FILE" ]]; then
|
||||
log "ERROR: Configuration file not found: $CONFIG_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! bash -n "$CONFIG_FILE"; then
|
||||
log "Syntax error in configuration file $CONFIG_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
source "$CONFIG_FILE"
|
||||
|
||||
REQUIRED_VARS=(
|
||||
SOURCEPORT BACKUPSERVER ZSYNC MAINTDAY SHUTDOWN UPDATES
|
||||
SOURCEHOST ZFSTRGT ZPUSHTAG ZPUSHMINKEEP ZPUSHKEEP ZPUSHLABEL
|
||||
PBSHOST PBSPORT BACKUPSTORE BACKUPSTOREPBS BACKUPEXCLUDE REPLEXCLUDE DYNROUTE REBOOT DDNS_GATEWAY DDNS_SERVER
|
||||
)
|
||||
|
||||
if [[ "${DYNROUTE,,}" == "yes" ]]; then
|
||||
REQUIRED_VARS+=(DDNS_GATEWAY)
|
||||
fi
|
||||
|
||||
MISSING_VARS=()
|
||||
for var in "${REQUIRED_VARS[@]}"; do
|
||||
if [[ -z "${!var:-}" ]]; then
|
||||
MISSING_VARS+=("$var")
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "${#MISSING_VARS[@]}" -gt 0 ]]; then
|
||||
log "Missing configuration variables:"
|
||||
for var in "${MISSING_VARS[@]}"; do
|
||||
log " - $var"
|
||||
done
|
||||
log "Aborting — please check your configuration file."
|
||||
exit 1
|
||||
else
|
||||
log "All required configuration variables are set."
|
||||
fi
|
||||
fi
|
||||
|
||||
rssh() { ssh -o BatchMode=yes -o ConnectTimeout=10 -p "$SOURCEPORT" "$@"; }
|
||||
rscp() { scp -P "$SOURCEPORT" "$@"; }
|
||||
|
||||
get_sourcehostname() {
|
||||
if [[ -z "${SOURCEHOSTNAME:-}" ]]; then
|
||||
log "SOURCEHOSTNAME is empty, retrieving via SSH from $SOURCEHOST..."
|
||||
SOURCEHOSTNAME=$(rssh "$SOURCEHOST" hostname)
|
||||
log "Detected SOURCEHOSTNAME: $SOURCEHOSTNAME"
|
||||
fi
|
||||
}
|
||||
|
||||
zfs_replace() {
|
||||
local status
|
||||
status=$(zpool status)
|
||||
|
||||
if echo "$status" | grep -q 'scan: resilver in progress'; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if echo "$status" | grep -qE 'replacing-[0-9]'; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
wait_replace() {
|
||||
local interval_seconds=300 # Alle 5 Minuten prüfen
|
||||
local waited_minutes=0
|
||||
|
||||
log "ZFS Replace-Vorgang erkannt – warte unbegrenzt auf Abschluss..."
|
||||
|
||||
while true; do
|
||||
if ! zfs_replace; then
|
||||
log "Replace abgeschlossen nach $waited_minutes Minuten – fahre jetzt herunter."
|
||||
return 0
|
||||
fi
|
||||
log "Replace läuft noch – erneut prüfen in $((interval_seconds/60)) Minuten..."
|
||||
sleep "$interval_seconds"
|
||||
((waited_minutes+=interval_seconds/60))
|
||||
done
|
||||
}
|
||||
|
||||
set_wol_g_enabled() {
|
||||
log "Checking if ethtool is installed..."
|
||||
|
||||
if ! command -v ethtool >/dev/null 2>&1; then
|
||||
log "ethtool is not installed, attempting installation..."
|
||||
apt update && apt install -y ethtool || {
|
||||
log "Error: ethtool could not be installed."
|
||||
return 1
|
||||
}
|
||||
else
|
||||
log "ethtool is already installed."
|
||||
fi
|
||||
|
||||
log "Setting Wake-on-LAN (WOL) to 'g' on interfaces with static IP..."
|
||||
|
||||
for iface in $(ls /sys/class/net | grep -vE '^(lo|tap|vmbr|veth|br|docker|bond|wl)'); do
|
||||
if [[ -e "/sys/class/net/$iface/device" ]]; then
|
||||
log "Processing physical interface: $iface"
|
||||
|
||||
current_wol=$(ethtool "$iface" 2>/dev/null | awk '/Wake-on:/ {print $2}')
|
||||
if [[ "$current_wol" != "g" ]]; then
|
||||
log "Setting WOL to 'g' for $iface..."
|
||||
ethtool -s "$iface" wol g || log "Error setting WOL on $iface"
|
||||
else
|
||||
log "WOL already set to 'g' for $iface"
|
||||
fi
|
||||
|
||||
if grep -qE "^\s*iface\s+$iface\s+inet\s+static" /etc/network/interfaces; then
|
||||
if ! grep -A 5 -E "^\s*iface\s+$iface\s+inet\s+static" /etc/network/interfaces | grep -q "post-up ethtool -s $iface wol g"; then
|
||||
log "Adding WOL command in static block for $iface..."
|
||||
sed -i "/^\s*iface\s\+$iface\s\+inet\s\+static/a \ post-up ethtool -s $iface wol g" /etc/network/interfaces
|
||||
else
|
||||
log "WOL command already present in static block for $iface."
|
||||
fi
|
||||
else
|
||||
log "No static entry found for $iface, no changes made."
|
||||
fi
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
write_zsync_config() {
|
||||
get_sourcehostname
|
||||
local conf_file="/etc/bashclub/$SOURCEHOST.conf"
|
||||
log "Writing zsync config to $conf_file"
|
||||
{
|
||||
echo "target=$ZFSTRGT"
|
||||
echo "source=root@$SOURCEHOST"
|
||||
echo "sshport=$SOURCEPORT"
|
||||
echo "tag=$ZPUSHTAG"
|
||||
echo "snapshot_filter=\"$ZPUSHFILTER\""
|
||||
echo "min_keep=$ZPUSHMINKEEP"
|
||||
echo "zfs_auto_snapshot_keep=$ZPUSHKEEP"
|
||||
echo "zfs_auto_snapshot_label=zfs-auto-snap_$ZPUSHLABEL"
|
||||
echo "zfs_auto_snapshot_engine=\"internal\""
|
||||
echo "checkzfs_disabled=0"
|
||||
echo "checkzfs_local=0"
|
||||
echo "checkzfs_prefix=miyagi-$SOURCEHOSTNAME-$(hostname)-$ZPUSHTAG"
|
||||
echo "checkzfs_max_age=1500,2000"
|
||||
echo "checkzfs_max_snapshot_count=180,200"
|
||||
echo "checkzfs_spool=1"
|
||||
echo "checkzfs_spool_maxage=100000"
|
||||
} > "$conf_file"
|
||||
}
|
||||
|
||||
run_zsync() {
|
||||
write_zsync_config
|
||||
if [[ "$ZSYNC" != "no" ]]; then
|
||||
/usr/bin/bashclub-zsync -c "/etc/bashclub/$SOURCEHOST.conf"
|
||||
else
|
||||
log "Zsync is disabled."
|
||||
fi
|
||||
}
|
||||
|
||||
run_remote_updates() {
|
||||
if [[ "${UPDATES,,}" == "yes" ]]; then
|
||||
log "Running updates on local system..."
|
||||
|
||||
TIMESTAMP=$(date +"%Y-%m-%d_%H-%M-%S")
|
||||
SNAPSHOT_TAG="pve-update-via-miyagi"
|
||||
MAX_SNAPSHOTS=5
|
||||
ZFS_DATASETS=("rpool/ROOT/pve-1" "rpool/ROOT/pbs-1" "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
|
||||
|
||||
else
|
||||
log "Updates disabled (UPDATES=$UPDATES)"
|
||||
fi
|
||||
}
|
||||
|
||||
send_piggyback() {
|
||||
get_sourcehostname
|
||||
local combined_host="miyagi-${SOURCEHOSTNAME}-$(hostname)"
|
||||
local filename="100000_${combined_host}"
|
||||
|
||||
log "Reminder: Add a host named ${combined_host} in CMK (without Agent, Piggyback enabled)!"
|
||||
log "Creating piggyback file: $filename"
|
||||
|
||||
{
|
||||
echo "<<<<${combined_host}>>>>"
|
||||
/usr/bin/check_mk_agent
|
||||
echo "<<<<>>>>"
|
||||
} > "$filename"
|
||||
|
||||
if rscp "$filename" "$SOURCEHOST:/var/lib/check_mk_agent/spool/"; then
|
||||
log "Piggyback data successfully sent to $SOURCEHOST"
|
||||
else
|
||||
log "ERROR: Failed to send piggyback data to $SOURCEHOST"
|
||||
fi
|
||||
|
||||
rm -f "$filename"
|
||||
}
|
||||
|
||||
run_pbs_log_clear() {
|
||||
log "Running clear LOG on PBS host ($PBSHOST)..."
|
||||
|
||||
if ! ssh -p "$PBSPORT" root@"$PBSHOST" \
|
||||
find /var/log/proxmox-backup/tasks/ -type f -mtime +60 -delete; then
|
||||
log "Error clearing PBS LOG on $PBSHOST"
|
||||
fi
|
||||
}
|
||||
|
||||
run_pve_log_clear() {
|
||||
log "Running clear LOG on PVE host ($PBSHOST)..."
|
||||
|
||||
if ! ssh -p "$SOURCEPORT" root@"$SOURCEHOST" \
|
||||
find /var/log/vzdump -type f -mtime +30 -delete; then
|
||||
log "Error clearing PVE LOG on $SOURCEHOST"
|
||||
fi
|
||||
}
|
||||
|
||||
run_pbs_backup() {
|
||||
if [[ "${BACKUPSERVER,,}" != "yes" ]]; then
|
||||
log "PBS Backup übersprungen: BACKUPSERVER='$BACKUPSERVER' (muss 'yes' sein)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
get_sourcehostname
|
||||
log "Starte PBS Backup auf Host: $SOURCEHOST"
|
||||
|
||||
# PBS-Storage prüfen und ggf. aktivieren
|
||||
log "Prüfe, ob PBS-Storage '$BACKUPSTORE' auf $SOURCEHOST aktiviert ist..."
|
||||
if rssh root@"$SOURCEHOST" "pvesm status | grep -w '$BACKUPSTORE' | grep -q 'disabled'"; then
|
||||
log "PBS-Storage '$BACKUPSTORE' ist deaktiviert – aktiviere temporär und warte 10 Sekunden..."
|
||||
rssh root@"$SOURCEHOST" "pvesm set '$BACKUPSTORE' --disable 0 && sleep 10"
|
||||
pbs_enabled_by_script=true
|
||||
else
|
||||
log "PBS-Storage '$BACKUPSTORE' ist bereits aktiviert."
|
||||
pbs_enabled_by_script=false
|
||||
fi
|
||||
|
||||
vzdump_success=false
|
||||
|
||||
# Versuch: vzdump mit Change Detection
|
||||
## Eintrag muss in /etc/vzdump.conf entries-max 100000000
|
||||
log "Starte vzdump mit Change Detection..."
|
||||
if rssh root@"$SOURCEHOST" vzdump --pbs-change-detection-mode metadata \
|
||||
--node "$SOURCEHOSTNAME" --storage "$BACKUPSTORE" \
|
||||
--exclude "$BACKUPEXCLUDE" --mode snapshot --all 1 \
|
||||
--notes-template '{{guestname}}'; then
|
||||
|
||||
log "vzdump (Change Detection) erfolgreich."
|
||||
vzdump_success=true
|
||||
else
|
||||
log "vzdump (Change Detection) fehlgeschlagen – versuche Fallback ohne Change Detection..."
|
||||
|
||||
if rssh root@"$SOURCEHOST" vzdump \
|
||||
--node "$SOURCEHOSTNAME" --storage "$BACKUPSTORE" \
|
||||
--exclude "$BACKUPEXCLUDE" --mode snapshot --all 1 \
|
||||
--notes-template '{{guestname}}'; then
|
||||
|
||||
log "Fallback-vzdump erfolgreich."
|
||||
vzdump_success=true
|
||||
else
|
||||
log "FEHLER: vzdump fehlgeschlagen – auch im Fallback."
|
||||
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 root@"$SOURCEHOST" "pvesm set '$BACKUPSTORE' --disable 1"
|
||||
fi
|
||||
|
||||
# Monitoring-Ausgabe
|
||||
if [[ "$vzdump_success" == true ]]; then
|
||||
echo "0 DailyPBS - Daily Backup erfolgreich" > /tmp/cmk_tmp.out
|
||||
else
|
||||
echo "2 DailyPBS - Daily Backup FEHLGESCHLAGEN" > /tmp/cmk_tmp.out
|
||||
fi
|
||||
|
||||
{
|
||||
echo "<<<local>>>"
|
||||
cat /tmp/cmk_tmp.out
|
||||
} > /tmp/1200000_checkpbs
|
||||
|
||||
rscp /tmp/1200000_checkpbs root@"$SOURCEHOST":/var/lib/check_mk_agent/spool \
|
||||
|| log "Fehler beim Übertragen des Monitoring-Outputs via SCP"
|
||||
|
||||
rm -f /tmp/cmk_tmp.out /tmp/1200000_checkpbs
|
||||
#write_pbs_status
|
||||
}
|
||||
|
||||
run_maintenance() {
|
||||
if [[ "${BACKUPSERVER,,}" != "yes" ]]; then
|
||||
log "PBS Backup wird übersprungen (BACKUPSERVER=$BACKUPSERVER)"
|
||||
return
|
||||
fi
|
||||
|
||||
if [[ "$(date +%u)" == "$MAINTDAY" ]]; then
|
||||
log "Running maintenance..."
|
||||
PRUNEJOB=$(ssh -p $PBSPORT "$PBSHOST" proxmox-backup-manager prune-job list --output-format json-pretty | grep -m 1 "id" | cut -d'"' -f4)
|
||||
ssh -p $PBSPORT root@"$PBSHOST" proxmox-backup-manager prune-job run "$PRUNEJOB"
|
||||
ssh -p $PBSPORT root@"$PBSHOST" proxmox-backup-manager garbage-collection start "$BACKUPSTOREPBS"
|
||||
ssh -p $PBSPORT root@"$PBSHOST" proxmox-backup-manager verify backup
|
||||
else
|
||||
log "No maintenance scheduled for today."
|
||||
fi
|
||||
}
|
||||
|
||||
run_scrub_stop() {
|
||||
local mode="$1" # "local" oder "remote"
|
||||
local ssh_cmd=()
|
||||
|
||||
if [[ "$mode" == "remote" ]]; then
|
||||
ssh_cmd=(ssh -p "$SOURCEPORT" root@"$SOURCEHOST")
|
||||
fi
|
||||
|
||||
"${ssh_cmd[@]}" bash -c '
|
||||
for pool in $(zpool list -H -o name); do
|
||||
echo "Stopping scrub on pool: $pool"
|
||||
if zpool status "$pool" | grep -q "scrub in progress"; then
|
||||
if zpool scrub -s "$pool"; then
|
||||
echo "Scrub stopped on $pool"
|
||||
else
|
||||
echo "Error stopping scrub on $pool"
|
||||
fi
|
||||
else
|
||||
echo "No active scrub on $pool"
|
||||
fi
|
||||
done
|
||||
'
|
||||
}
|
||||
|
||||
write_pbs_status() {
|
||||
local spool_file="/var/lib/check_mk_agent/spool/90000_checkpbs_local"
|
||||
local repo="$BACKUPSTOREPBS"
|
||||
local tmpfile
|
||||
tmpfile=$(mktemp)
|
||||
|
||||
if ! command -v proxmox-backup-client >/dev/null 2>&1; then
|
||||
log "proxmox-backup-client nicht installiert – PBS-Status-Ausgabe übersprungen."
|
||||
return
|
||||
fi
|
||||
|
||||
echo "<<<local>>>" > "$tmpfile"
|
||||
|
||||
local now
|
||||
now=$(date +%s)
|
||||
|
||||
# === Schwellenwert(e) parsen ===
|
||||
local warn_threshold=0
|
||||
local crit_threshold=86400 # Default kritisch ab 24h
|
||||
|
||||
if [[ -n "${PBSBACKUP_STATUS:-}" ]]; then
|
||||
IFS=',' read -r t1 t2 <<< "$PBSBACKUP_STATUS"
|
||||
if [[ "$t1" =~ ^[0-9]+$ && -z "$t2" ]]; then
|
||||
# Nur ein Wert vorhanden
|
||||
crit_threshold=$t1
|
||||
log "Verwende einfachen Schwellenwert: CRIT=${crit_threshold}s"
|
||||
elif [[ "$t1" =~ ^[0-9]+$ && "$t2" =~ ^[0-9]+$ ]]; then
|
||||
warn_threshold=$t1
|
||||
crit_threshold=$t2
|
||||
log "Verwende gestaffelte Schwellenwerte: WARN=${warn_threshold}s, CRIT=${crit_threshold}s"
|
||||
else
|
||||
log "WARNUNG: Ungültiges PBSBACKUP_STATUS-Format – Fallback: CRIT=${crit_threshold}s"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Backup-Daten extrahieren
|
||||
rssh root@"$SOURCEHOST" proxmox-backup-client list --repository "$repo" --output-format json 2>/dev/null | \
|
||||
grep -E '"backup-id"|"backup-type"|"backup-time"' | \
|
||||
sed -E 's/[",]//g; s/^ *//' > "$tmpfile.json"
|
||||
|
||||
local backup_type="" backup_id="" backup_time=""
|
||||
while read -r line; do
|
||||
case "$line" in
|
||||
backup-type:*)
|
||||
backup_type="${line#*: }"
|
||||
;;
|
||||
backup-id:*)
|
||||
backup_id="${line#*: }"
|
||||
;;
|
||||
backup-time:*)
|
||||
backup_time="${line#*: }"
|
||||
|
||||
if [[ -n "$backup_type" && -n "$backup_id" && -n "$backup_time" ]]; then
|
||||
local age status timestamp msg
|
||||
age=$((now - backup_time))
|
||||
timestamp=$(date -d "@$backup_time" "+%Y-%m-%d %H:%M:%S")
|
||||
|
||||
if (( age < warn_threshold )); then
|
||||
status=0
|
||||
elif (( age < crit_threshold )); then
|
||||
status=1
|
||||
else
|
||||
status=2
|
||||
fi
|
||||
|
||||
msg="$backup_type/$backup_id last backup $timestamp (age: $((age / 3600))h)"
|
||||
echo "$status PBS_${backup_type}_${backup_id} - $msg" >> "$tmpfile"
|
||||
|
||||
# Reset
|
||||
backup_type="" backup_id="" backup_time=""
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
done < "$tmpfile.json"
|
||||
|
||||
rm -f "$tmpfile.json"
|
||||
mv "$tmpfile" "$spool_file"
|
||||
log "PBS Backup Status lokal geschrieben: $spool_file"
|
||||
}
|
||||
|
||||
shutdown_now() {
|
||||
if [[ "${SHUTDOWN,,}" == "yes" ]]; then
|
||||
if zfs_replace; then
|
||||
log "ZFS Replace-Vorgang erkannt – warte bis zum Abschluss..."
|
||||
if ! wait_replace; then
|
||||
log "Shutdown abgebrochen – Replace ist nach max. Wartezeit noch nicht abgeschlossen."
|
||||
return
|
||||
fi
|
||||
fi
|
||||
send_piggyback
|
||||
send_piggyback_external
|
||||
send_checkzfs_external
|
||||
log "Shutting down now...in 60sec"
|
||||
sleep 60
|
||||
shutdown now
|
||||
else
|
||||
log "No shutdown requested."
|
||||
fi
|
||||
}
|
||||
|
||||
send_piggyback_external() {
|
||||
if [[ "${EPIGGYBACK,,}" != "yes" ]]; then
|
||||
log "Externer Piggyback-Export deaktiviert."
|
||||
return
|
||||
fi
|
||||
get_sourcehostname
|
||||
log "Ermittelter SOURCEHOSTNAME: $SOURCEHOSTNAME"
|
||||
|
||||
local combined_host="miyagi-${SOURCEHOSTNAME}-$(hostname)"
|
||||
local spool_file="100000_${combined_host}_piggyback_external"
|
||||
local temp_dir
|
||||
temp_dir=$(mktemp -d)
|
||||
|
||||
log "Erzeuge temporäre Piggyback-Datei: $temp_dir/$spool_file"
|
||||
|
||||
{
|
||||
echo "<<<<${combined_host}>>>>"
|
||||
/usr/bin/check_mk_agent
|
||||
echo "<<<<>>>>"
|
||||
} > "$temp_dir/$spool_file"
|
||||
|
||||
if scp -P "$EPIGGYBACK_PORT" "$temp_dir/$spool_file" "$EPIGGYBACK_HOST:/var/lib/check_mk_agent/spool/"; then
|
||||
log "Piggyback-Daten erfolgreich an $EPIGGYBACK_HOST gesendet: $spool_file"
|
||||
else
|
||||
log "Fehler beim Übertragen der Piggyback-Daten an $EPIGGYBACK_HOST"
|
||||
rm -rf "$temp_dir"
|
||||
return 1
|
||||
fi
|
||||
|
||||
rm -rf "$temp_dir"
|
||||
return 0
|
||||
}
|
||||
|
||||
send_checkzfs_external() {
|
||||
if [[ "${ECHECKZFS,,}" != "yes" ]]; then
|
||||
log "Externer Piggyback-Export deaktiviert."
|
||||
return
|
||||
fi
|
||||
local config="/etc/bashclub/${SOURCEHOST}.conf"
|
||||
if [[ ! -f "$config" ]]; then
|
||||
log "Konfigurationsdatei fehlt: $config"
|
||||
return 1
|
||||
fi
|
||||
source "$config"
|
||||
|
||||
if [[ "${ECHECKZFS,,}" != "yes" ]]; then
|
||||
log "Externer Check-ZFS deaktiviert (ECHECKZFS=$ECHECKZFS)"
|
||||
return
|
||||
fi
|
||||
|
||||
get_sourcehostname # Setzt SOURCEHOSTNAME, wenn nicht vorhanden
|
||||
|
||||
local checkzfs_cmd="${checkzfs_cmd:-$(command -v checkzfs)}"
|
||||
if [[ -z "$checkzfs_cmd" || ! -x "$checkzfs_cmd" ]]; then
|
||||
log "checkzfs nicht gefunden oder nicht ausführbar – Abbruch."
|
||||
return 1
|
||||
fi
|
||||
log "Verwende checkzfs: $checkzfs_cmd"
|
||||
|
||||
# Filter anhand ZFS-Tags auf dem SOURCEHOST aufbauen
|
||||
local filter=""
|
||||
while IFS=$'\t' read -r name value source; do
|
||||
if [[ "$source" == "local" && "$value" == "subvols" ]]; then
|
||||
filter+="${name}/|"
|
||||
elif [[ "$source" == "local" && "$value" == "all" ]]; then
|
||||
filter+="${name}|"
|
||||
fi
|
||||
done < <(ssh -p "$SOURCEPORT" "$SOURCEHOST" "zfs get -H -o name,value,source -t filesystem,volume $ZPUSHTAG")
|
||||
|
||||
filter="#${filter%|}"
|
||||
log "Generierter ZFS-Filter: $filter"
|
||||
|
||||
local combined="miyagi-${SOURCEHOSTNAME}-$(hostname)-${ZPUSHTAG}"
|
||||
local spoolfile="/tmp/${combined}_checkzfs_external"
|
||||
local spooldest="100000_${combined}_checkzfs_external"
|
||||
|
||||
log "Führe checkzfs aus..."
|
||||
{
|
||||
echo "<<<local>>>"
|
||||
"$checkzfs_cmd" \
|
||||
--source root@"${SOURCEHOST}:${SOURCEPORT}" \
|
||||
--filter "$filter" \
|
||||
--replicafilter "^${ZFSTRGT}" \
|
||||
--prefix "$checkzfs_prefix" \
|
||||
--threshold "$checkzfs_max_age" \
|
||||
--maxsnapshots "$checkzfs_max_snapshot_count" \
|
||||
--output checkmk
|
||||
} > "$spoolfile"
|
||||
|
||||
if scp -P "$ECHECKZFS_PORT" "$spoolfile" root@"$ECHECKZFS_HOST":/var/lib/check_mk_agent/spool/"$spooldest"; then
|
||||
log "Spool-Datei erfolgreich übertragen an $ECHECKZFS_HOST:$spooldest"
|
||||
else
|
||||
log "Fehler beim Übertragen der Spool-Datei an $ECHECKZFS_HOST"
|
||||
fi
|
||||
|
||||
rm -f "$spoolfile"
|
||||
}
|
||||
wait() {
|
||||
echo
|
||||
echo "###########################################"
|
||||
echo "# Das Backup startet in 60 Sekunden... #"
|
||||
echo "# Drücke [Enter], um sofort fortzufahren, #"
|
||||
echo "# oder warte, um automatisch zu starten. #"
|
||||
echo "###########################################"
|
||||
read -t 60 -p "Fortfahren (Enter drücken) oder warten... " input
|
||||
if [[ $? -eq 0 ]]; then
|
||||
log "Press ENTER to continue."
|
||||
else
|
||||
log "No press ENTER, wait for 60 Sec.."
|
||||
sleep 60
|
||||
fi
|
||||
}
|
||||
set_dynamic_route() {
|
||||
local ddns_hostname="$SOURCEHOST"
|
||||
local gateway="$DDNS_GATEWAY"
|
||||
local dns_server="$DDNS_SERVER"
|
||||
|
||||
if [[ -z "$ddns_hostname" || -z "$gateway" ]]; then
|
||||
log "Fehler: SOURCEHOST oder DDNS_GATEWAY nicht gesetzt"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log "Setze temporäre Route zu DNS-Server $dns_server via $gateway..."
|
||||
if ! ip route | grep -q "^$dns_server"; then
|
||||
ip route add "$dns_server" via "$gateway" \
|
||||
&& log "Route zu $dns_server via $gateway gesetzt" \
|
||||
|| log "Fehler beim Setzen der DNS-Route"
|
||||
else
|
||||
log "Route zu $dns_server bereits vorhanden"
|
||||
fi
|
||||
|
||||
log "Löse IP von $ddns_hostname über DNS $dns_server..."
|
||||
CURRENT_IP=$(dig +short @"$dns_server" "$ddns_hostname")
|
||||
|
||||
if [[ -z "$CURRENT_IP" ]]; then
|
||||
log "Fehler: Konnte IP für $ddns_hostname nicht auflösen"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log "Ermittelte IP: $CURRENT_IP – setze Route via $gateway..."
|
||||
if ! ip route | grep -q "^$CURRENT_IP"; then
|
||||
ip route add "$CURRENT_IP" via "$gateway" \
|
||||
&& log "Route erfolgreich gesetzt: $CURRENT_IP via $gateway" \
|
||||
|| log "Fehler beim Setzen der Route zu $CURRENT_IP"
|
||||
else
|
||||
log "Route zu $CURRENT_IP bereits vorhanden"
|
||||
fi
|
||||
}
|
||||
# Main execution:
|
||||
if [[ $# -eq 0 ]]; then
|
||||
if [[ -n "$CONFIG_FILE" ]]; then
|
||||
wait
|
||||
log "Running full backup using configuration file: $CONFIG_FILE"
|
||||
|
||||
if [[ "${DYNROUTE,,}" == "yes" ]]; then
|
||||
log "DYNROUTE ist aktiviert – setze dynamische Route für $SOURCEHOST..."
|
||||
set_dynamic_route
|
||||
else
|
||||
log "DYNROUTE ist deaktiviert oder nicht gesetzt."
|
||||
fi
|
||||
|
||||
write_zsync_config
|
||||
run_zsync
|
||||
|
||||
if [[ "${BACKUPSERVER,,}" == "yes" ]]; then
|
||||
log "BACKUPSERVER ist aktiviert, führe Backup aus..."
|
||||
run_pbs_backup
|
||||
run_maintenance
|
||||
run_pbs_log_clear
|
||||
run_pve_log_clear
|
||||
else
|
||||
log "BACKUPSERVER ist nicht aktiviert (BACKUPSERVER=$BACKUPSERVER), überspringe Backup."
|
||||
fi
|
||||
run_remote_updates
|
||||
if [[ "${EPIGGYBACK,,}" == "yes" ]]; then
|
||||
send_piggyback_external
|
||||
fi
|
||||
|
||||
if [[ "${ECHECKZFS,,}" == "yes" ]]; then
|
||||
send_checkzfs_external
|
||||
fi
|
||||
shutdown_now
|
||||
else
|
||||
usage
|
||||
fi
|
||||
else
|
||||
case "$1" in
|
||||
help)
|
||||
declare -F | awk '{print $3}' | grep -v "^_" | grep -v "^main$"
|
||||
;;
|
||||
*)
|
||||
if declare -F "$1" >/dev/null 2>&1; then
|
||||
"$@"
|
||||
else
|
||||
log "Function $1 not found."
|
||||
usage
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
@@ -1,222 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# ================================
|
||||
# Erweiterungs-Installer mit dialog GUI
|
||||
# ================================
|
||||
|
||||
# 0. Sicherstellen, dass dialog vorhanden ist
|
||||
if ! command -v dialog &>/dev/null; then
|
||||
echo "[INFO] Installiere 'dialog' für das Erweiterungsmenü..."
|
||||
sudo apt update && sudo apt install -y dialog
|
||||
fi
|
||||
|
||||
# 1. Menü anzeigen
|
||||
HEIGHT=20
|
||||
WIDTH=60
|
||||
CHOICE_HEIGHT=10
|
||||
BACKTITLE="Checkmk & ZFS Setup"
|
||||
TITLE="Optionale Erweiterungen"
|
||||
MENU="Wähle gewünschte Zusatzfunktionen:"
|
||||
|
||||
OPTIONS=(
|
||||
1 "Installiere Miyagi-Backup-Skripte" on
|
||||
2 "Installiere disk_smart_info Plugin" on
|
||||
3 "Installiere cleansnaps (ZFS)" on
|
||||
4 "Installiere check-snapshot-age (ZFS)" on
|
||||
5 "Installiere checkzfs (ZFS)" on
|
||||
6 "Starte ZFS-Pool-Tagging" off
|
||||
)
|
||||
|
||||
CHOICES=$(dialog --clear \
|
||||
--backtitle "$BACKTITLE" \
|
||||
--title "$TITLE" \
|
||||
--checklist "$MENU" \
|
||||
$HEIGHT $WIDTH $CHOICE_HEIGHT \
|
||||
"${OPTIONS[@]}" \
|
||||
3>&1 1>&2 2>&3)
|
||||
|
||||
clear
|
||||
|
||||
# 2. Prüfen, ob Auswahl getroffen wurde
|
||||
if [[ -z "$CHOICES" ]]; then
|
||||
echo "[INFO] Keine Erweiterungen ausgewählt. Überspringe Zusatzfunktionen."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 3. Zusammenfassung bauen
|
||||
SUMMARY_TEXT="Du hast folgende Erweiterungen ausgewählt:\n"
|
||||
|
||||
for CHOICE in $CHOICES; do
|
||||
case $CHOICE in
|
||||
1) SUMMARY_TEXT+="\n - Miyagi-Backup-Skripte" ;;
|
||||
2) SUMMARY_TEXT+="\n - disk_smart_info Plugin" ;;
|
||||
3) SUMMARY_TEXT+="\n - cleansnaps (ZFS)" ;;
|
||||
4) SUMMARY_TEXT+="\n - check-snapshot-age (ZFS)" ;;
|
||||
5) SUMMARY_TEXT+="\n - checkzfs (ZFS)" ;;
|
||||
6) SUMMARY_TEXT+="\n - ZFS-Pool-Tagging starten" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# 4. Bestätigung anzeigen
|
||||
dialog --clear \
|
||||
--backtitle "Checkmk & Erweiterungen" \
|
||||
--title "Zusammenfassung" \
|
||||
--yesno "$SUMMARY_TEXT\n\nMöchtest du die Einrichtung jetzt starten?" \
|
||||
18 70
|
||||
|
||||
response=$?
|
||||
clear
|
||||
|
||||
if [[ $response -ne 0 ]]; then
|
||||
echo "[ABBRUCH] Installation abgebrochen durch Benutzer."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "[INFO] Starte nun die Einrichtung der gewählten Komponenten..."
|
||||
|
||||
# 5. Ausgewählte Erweiterungen ausführen
|
||||
for CHOICE in $CHOICES; do
|
||||
case $CHOICE in
|
||||
1)
|
||||
echo "[INSTALL] Miyagi-Backup-Skripte..."
|
||||
wget -q --no-cache -O miyagi-backup.sh https://gitea.perlbach24.de/scripte/miyagi-backup/raw/branch/main/miyagi-backup.sh
|
||||
wget -q --no-cache -O miyagi-check.sh https://gitea.perlbach24.de/scripte/miyagi-backup/raw/branch/main/miyagi-check.sh
|
||||
wget -q --no-cache -O miyagi-convert.sh https://gitea.perlbach24.de/scripte/miyagi-backup/raw/branch/main/miyagi-convert.sh
|
||||
;;
|
||||
2)
|
||||
echo "[INSTALL] disk_smart_info Plugin..."
|
||||
curl -fsSL https://raw.githubusercontent.com/bashclub/checkmk-smart/main/disk_smart_info.py -o /usr/lib/check_mk_agent/plugins/disk_smart_info
|
||||
chmod +x /usr/lib/check_mk_agent/plugins/disk_smart_info
|
||||
;;
|
||||
3)
|
||||
echo "[INSTALL] cleansnaps..."
|
||||
wget -q --no-cache -O /usr/local/bin/cleansnaps https://raw.githubusercontent.com/bashclub/zfs-housekeeping/refs/heads/main/cleansnaps.sh
|
||||
chmod +x /usr/local/bin/cleansnaps
|
||||
;;
|
||||
4)
|
||||
echo "[INSTALL] check-snapshot-age..."
|
||||
wget -q --no-cache -O /usr/local/bin/check-snapshot-age https://gitea.perlbach24.de/scripte/check-zfs-replication/raw/branch/main/check-snapshot-age
|
||||
chmod +x /usr/local/bin/check-snapshot-age
|
||||
;;
|
||||
5)
|
||||
echo "[INSTALL] checkzfs..."
|
||||
wget -q --no-cache -O /usr/local/bin/checkzfs https://gitea.perlbach24.de/scripte/check-zfs-replication/raw/branch/main/checkzfs.py
|
||||
chmod +x /usr/local/bin/checkzfs
|
||||
;;
|
||||
6)
|
||||
echo "[INFO] Starte ZFS-Pool-Tagging..."
|
||||
# Optional: Weitere Dialog-Auswahl oder SSH-Zugriff hier implementieren
|
||||
|
||||
echo "[INFO] Lade ZFS-Datasets vom Remote-Host..."
|
||||
|
||||
LOCAL_HOSTNAME=$(hostname -s)
|
||||
REMOTE_HOSTNAME=$(ssh -p "$SOURCEPORT" root@"$SOURCEHOST" "hostname -s")
|
||||
TAG_KEY="bashclub:miyagi-${REMOTE_HOSTNAME}-${LOCAL_HOSTNAME}"
|
||||
TAG_VALUE="subvols"
|
||||
|
||||
# Hole alle relevanten Pool-Namen
|
||||
mapfile -t pools < <(ssh -p "$SOURCEPORT" root@"$SOURCEHOST" \
|
||||
"zfs list -H -o name" | \
|
||||
grep -E '/(vm|subvol)-' | \
|
||||
grep -viE 'repl|replica' | \
|
||||
sed 's:/[^/]*$::' | \
|
||||
sort -u)
|
||||
|
||||
if [[ ${#pools[@]} -eq 0 ]]; then
|
||||
echo "[WARN] Keine geeigneten Datasets gefunden!"
|
||||
return
|
||||
fi
|
||||
|
||||
local options=()
|
||||
local tagged_pools=()
|
||||
|
||||
for pool in "${pools[@]}"; do
|
||||
local tag status
|
||||
tag=$(ssh -p "$SOURCEPORT" root@"$SOURCEHOST" zfs get -H -o value "$TAG_KEY" "$pool" 2>/dev/null)
|
||||
if [[ "$tag" == "$TAG_VALUE" ]]; then
|
||||
status="on"
|
||||
tagged_pools+=("$pool")
|
||||
else
|
||||
status="off"
|
||||
fi
|
||||
options+=("$pool" "$([[ "$status" == "on" ]] && echo 'TAGGED' || echo 'untagged')" "$status")
|
||||
done
|
||||
|
||||
local selected_array=()
|
||||
|
||||
# 🧠 Prüfen ob whiptail oder dialog vorhanden ist
|
||||
if command -v whiptail >/dev/null; then
|
||||
local selected
|
||||
selected=$(whiptail --title "ZFS Pools auswählen" \
|
||||
--checklist "Wähle Datasets zum Taggen aus:" 20 78 12 \
|
||||
"${options[@]}" \
|
||||
3>&1 1>&2 2>&3)
|
||||
|
||||
if [[ $? -ne 0 ]]; then
|
||||
echo "[INFO] Auswahl abgebrochen."
|
||||
return
|
||||
fi
|
||||
|
||||
eval "selected_array=($selected)"
|
||||
|
||||
elif command -v dialog >/dev/null; then
|
||||
local selected
|
||||
selected=$(dialog --clear \
|
||||
--backtitle "ZFS Tagging" \
|
||||
--title "ZFS Pools auswählen" \
|
||||
--checklist "Wähle Datasets zum Taggen aus:" 20 78 12 \
|
||||
"${options[@]}" \
|
||||
3>&1 1>&2 2>&3)
|
||||
|
||||
clear
|
||||
if [[ $? -ne 0 ]]; then
|
||||
echo "[INFO] Auswahl abgebrochen."
|
||||
return
|
||||
fi
|
||||
|
||||
eval "selected_array=($selected)"
|
||||
|
||||
else
|
||||
# ⚠️ Fallback: einfache Textauswahl
|
||||
echo "[INFO] Kein dialog oder whiptail gefunden – nutze Textauswahl."
|
||||
echo "[INFO] Vorhandene Pools:"
|
||||
for i in "${!pools[@]}"; do
|
||||
echo " [$i] ${pools[i]}"
|
||||
done
|
||||
|
||||
echo
|
||||
echo "Gib die Nummern der Pools an, die getaggt werden sollen (getrennt mit Leerzeichen):"
|
||||
read -rp "Auswahl: " user_input
|
||||
|
||||
for index in $user_input; do
|
||||
selected_array+=("${pools[index]}")
|
||||
done
|
||||
fi
|
||||
|
||||
# ✅ Verarbeitung der Auswahl
|
||||
echo "[INFO] Verarbeite Tagging für ausgewählte Pools:"
|
||||
for ((i = 0; i < ${#options[@]}; i += 3)); do
|
||||
local pool="${options[i]}"
|
||||
local prev_status="${options[i+2]}"
|
||||
local is_selected=0
|
||||
|
||||
for sel in "${selected_array[@]}"; do
|
||||
if [[ "$sel" == "$pool" ]]; then
|
||||
is_selected=1
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "$is_selected" -eq 1 && "$prev_status" == "off" ]]; then
|
||||
echo " → Tagge $pool"
|
||||
run_or_echo ssh -p "$SOURCEPORT" root@"$SOURCEHOST" zfs set "$TAG_KEY=$TAG_VALUE" "$pool"
|
||||
elif [[ "$is_selected" -eq 0 && "$prev_status" == "on" ]]; then
|
||||
echo " → Entferne Tag von $pool"
|
||||
run_or_echo ssh -p "$SOURCEPORT" root@"$SOURCEHOST" zfs inherit "$TAG_KEY" "$pool"
|
||||
fi
|
||||
done
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
echo "[FERTIG] Alle gewählten Komponenten wurden installiert."
|
||||
@@ -1,91 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import os, sys, subprocess, logging, time, tempfile, shutil
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
|
||||
# === Logging Setup ===
|
||||
SCRIPT_NAME = Path(sys.argv[0]).stem
|
||||
LOGFILE = f"/var/log/{SCRIPT_NAME}.log"
|
||||
logging.basicConfig(
|
||||
filename=LOGFILE,
|
||||
level=logging.INFO,
|
||||
format='[%(asctime)s] %(message)s',
|
||||
datefmt='%Y-%m-%d %H:%M:%S'
|
||||
)
|
||||
console = logging.StreamHandler()
|
||||
console.setLevel(logging.INFO)
|
||||
formatter = logging.Formatter('[%(asctime)s] %(message)s', '%Y-%m-%d %H:%M:%S')
|
||||
console.setFormatter(formatter)
|
||||
logging.getLogger().addHandler(console)
|
||||
|
||||
def log(msg):
|
||||
logging.info(msg)
|
||||
|
||||
# === Config Loader ===
|
||||
import yaml
|
||||
|
||||
CONFIG = {}
|
||||
def load_config(path):
|
||||
global CONFIG
|
||||
if not os.path.isfile(path):
|
||||
log(f"ERROR: Config file not found: {path}")
|
||||
sys.exit(1)
|
||||
with open(path) as f:
|
||||
CONFIG = yaml.safe_load(f)
|
||||
log("Config loaded successfully")
|
||||
|
||||
# === SSH / SCP Wrappers ===
|
||||
def rssh(cmd, port=None, host=None):
|
||||
port = port or CONFIG.get('SOURCEPORT', 22)
|
||||
host = host or CONFIG.get('SOURCEHOST')
|
||||
ssh_cmd = ["ssh", "-o", "BatchMode=yes", "-o", "ConnectTimeout=10", "-p", str(port), f"root@{host}"] + cmd
|
||||
return subprocess.run(ssh_cmd, capture_output=True, text=True)
|
||||
|
||||
def rscp(local, remote, port=None, host=None):
|
||||
port = port or CONFIG.get('SOURCEPORT', 22)
|
||||
host = host or CONFIG.get('SOURCEHOST')
|
||||
scp_cmd = ["scp", "-P", str(port), local, f"root@{host}:{remote}"]
|
||||
return subprocess.run(scp_cmd)
|
||||
|
||||
# === Beispiel-Funktionen 1:1 ===
|
||||
def get_sourcehostname():
|
||||
if not CONFIG.get('SOURCEHOSTNAME'):
|
||||
log(f"SOURCEHOSTNAME empty, retrieving from {CONFIG['SOURCEHOST']}...")
|
||||
result = rssh(["hostname"])
|
||||
CONFIG['SOURCEHOSTNAME'] = result.stdout.strip()
|
||||
log(f"Detected SOURCEHOSTNAME: {CONFIG['SOURCEHOSTNAME']}")
|
||||
|
||||
def shutdown_now():
|
||||
if CONFIG.get('SHUTDOWN', 'no').lower() == 'yes':
|
||||
# Hier würde Python wait_replace() aufrufen, send_piggyback() etc.
|
||||
log("Shutdown requested. Would execute now...")
|
||||
else:
|
||||
log("No shutdown requested.")
|
||||
|
||||
# === Weitere Funktionen nach Bash ===
|
||||
# zfs_replace(), wait_replace(), write_zsync_config(), run_zsync(),
|
||||
# run_remote_updates(), send_piggyback(), run_pbs_backup(), etc.
|
||||
|
||||
# === Main Execution ===
|
||||
def main():
|
||||
import argparse
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("-c", "--config", help="Path to config")
|
||||
parser.add_argument("function", nargs='?', help="Run specific function")
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.config:
|
||||
load_config(args.config)
|
||||
|
||||
if args.function:
|
||||
func = globals().get(args.function)
|
||||
if func:
|
||||
func()
|
||||
else:
|
||||
log(f"Function {args.function} not found")
|
||||
sys.exit(1)
|
||||
else:
|
||||
# Full backup run
|
||||
log("Running full backup...")
|
||||
# Full run calls all functions like in Bash
|
||||
# wait(), set_dynamic_route(), write_zsync_config(), run_zsync(), etc.
|
||||
-195
@@ -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 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
|
||||
|
||||
---
|
||||
@@ -1,75 +0,0 @@
|
||||
# Vergleich v1-alt vs. v2-alt
|
||||
|
||||
Grundlage für die Planung der finalen v3-Struktur.
|
||||
|
||||
## Dateiübersicht
|
||||
|
||||
| Datei | v1-alt | v2-alt | Status |
|
||||
|---|---|---|---|
|
||||
| `miyagi-backup.sh` | 489 Zeilen | 724 Zeilen | in beiden, v2 deutlich erweitert |
|
||||
| `miyagi-install.sh` | 255 Zeilen (CLI, `-c`/`--yes`/`--dry-run`) | 222 Zeilen (dialog-GUI) | **komplett unterschiedliche Implementierungen**, nur Name gleich |
|
||||
| `install.sh` | – | 254 Zeilen (dialog-GUI, mit Config-Loader) | nur in v2-alt |
|
||||
| `miyagi-check.sh` | 198 Zeilen | – | nur in v1-alt |
|
||||
| `miyagi-check.sh_new` | vorhanden | – | nur in v1-alt, vermutlich Vorarbeit für check.sh-Überarbeitung |
|
||||
| `miyagi-convert.sh` | vorhanden | – | nur in v1-alt |
|
||||
| `config.example` | vorhanden | – | nur in v1-alt (v2-alt hat keine eigene Beispiel-Config) |
|
||||
| `README` | vorhanden | – | nur in v1-alt |
|
||||
| `DEVLOG.md` | – | vorhanden | nur in v2-alt (Claude-Code-Session-Log) |
|
||||
| `python/miyagi-python.py` | – | 90 Zeilen | nur in v2-alt, noch ungeprüft |
|
||||
|
||||
## `miyagi-backup.sh`: Funktionsvergleich
|
||||
|
||||
| Funktion | v1-alt | v2-alt | Anmerkung |
|
||||
|---|---|---|---|
|
||||
| `log()` | ✓ | ✓ | gleich |
|
||||
| `usage()` | ✓ | ✓ | gleich |
|
||||
| `remote_ssh()` / `remote_scp()` | ✓ (auskommentiert) | – | tot in v1, in v2 ersetzt durch `rssh()`/`rscp()` |
|
||||
| `rssh()` / `rscp()` | – | ✓ | neue Wrapper, zentralisieren `-p "$SOURCEPORT"` |
|
||||
| `get_sourcehostname()` | – | ✓ | neue Helper-Funktion, ersetzt mehrfach kopierten Code-Block in v1 |
|
||||
| `zfs_replace()` / `wait_replace()` | – | ✓ | neu: prüft auf laufenden ZFS-Replace, verzögert Shutdown bis Replace fertig ist |
|
||||
| `set_wol_g_enabled()` | ✓ | ✓ | inhaltlich gleich |
|
||||
| `write_zsync_config()` | ✓ | ✓ | v2 nutzt `get_sourcehostname()` statt Inline-Code; `zfs_auto_snapshot_label` Format leicht anders |
|
||||
| `run_zsync()` | ✓ | ✓ | v2 ruft zusätzlich `write_zsync_config()` selbst auf |
|
||||
| `run_remote_updates()` | ✓ (nur PBS-Update per SSH) | ✓ (deutlich erweitert) | v2: lokales `apt dist-upgrade`, ZFS-Snapshots vor Update, Snapshot-Rotation, Kernel-Update-Erkennung mit optionalem `reboot`, danach PBS-Update. **Bug:** überschreibt globale `LOGFILE`-Variable ohne `local` |
|
||||
| `send_piggyback_data()` / `send_piggyback()` | ✓ | ✓ | umbenannt, sonst ähnlich |
|
||||
| `run_pbs_log_clear()` | – | ✓ | neu: löscht alte PBS-Task-Logs (>60 Tage) |
|
||||
| `run_pve_log_clear()` | – | ✓ | neu: löscht alte vzdump-Logs (>30 Tage) |
|
||||
| `run_pbs_backup()` | ✓ | ✓ | v2 nutzt `rssh()`-Wrapper, ruft `get_sourcehostname()` zentral auf |
|
||||
| `run_maintenance()` | ✓ | ✓ | v2 prüft zusätzlich `BACKUPSERVER` selbst (Doppel-Check) |
|
||||
| `run_scrub_stop_src()` / `run_scrub_stop_local()` | ✓ (zwei separate Funktionen, Code dupliziert) | `run_scrub_stop()` | v2 fasst beide in einer Funktion mit `mode`-Parameter zusammen |
|
||||
| `write_pbs_status()` | – | ✓ | neu: Backup-Alter-Status für Checkmk (WARN/CRIT-Schwellen aus `PBSBACKUP_STATUS`). **Aktuell nicht aufgerufen** (Zeile auskommentiert) |
|
||||
| `shutdown_if_requested()` / `shutdown_now()` | ✓ | ✓ | v2 wartet zusätzlich auf laufenden ZFS-Replace vor dem Shutdown |
|
||||
| `send_piggyback_data_external()` / `send_piggyback_external()` | ✓ | ✓ | v2 nutzt `mktemp -d` statt Datei im Arbeitsverzeichnis |
|
||||
| `send_checkzfs_output_external()` / `send_checkzfs_external()` | ✓ | ✓ | ähnlich, v2 etwas aufgeräumter |
|
||||
| `set_dynamic_route()` | – | ✓ | neu: setzt temporäre Route zu DNS-Server + aufgelöster Quell-IP (für DynDNS-Quellen hinter wechselnder IP) |
|
||||
| `wait()` | – | ✓ | neu: Start-Countdown mit Möglichkeit, per Enter sofort fortzufahren. **Überschreibt das Bash-Builtin `wait`** |
|
||||
| `main()` | ✓ (eigene Funktion) | – | v2 hat kein `main()` mehr, Ablauf steht direkt im Skript-Footer (`if [[ $# -eq 0 ]]; then ... fi`) |
|
||||
|
||||
## Config-Variablen: v1 `config.example` vs. in v2 verwendete Variablen
|
||||
|
||||
Neu in v2 (in v1-`config.example` nicht vorhanden, aber in v1-Konfig-Diskussion z.T. schon angedacht):
|
||||
|
||||
- `DYNROUTE`, `DDNS_GATEWAY`, `DDNS_SERVER` — für `set_dynamic_route()`
|
||||
- `REBOOT` — für automatischen Reboot nach Kernel-Update
|
||||
- `PBSPORT` — eigener SSH-Port für PBS-Host (in v1-Backup-Skript größtenteils noch hart `ssh "$PBSHOST"` ohne Port)
|
||||
|
||||
Alle übrigen Variablen (`SOURCEPORT`, `SOURCEHOST`, `ZFSTRGT`, `ZSYNC`, `ZPUSH*`, `BACKUPSERVER`, `MAINTDAY`, `PBSHOST`, `BACKUPSTORE*`, `BACKUPEXCLUDE`, `REPLEXCLUDE`, `EPIGGYBACK*`, `ECHECKZFS*`) sind in beiden Versionen vorhanden.
|
||||
|
||||
**Fehlt:** v2-alt hat keine eigene `config.example` — müsste aus v1 übernommen und um `DYNROUTE`, `DDNS_GATEWAY`, `DDNS_SERVER`, `REBOOT`, `PBSPORT` ergänzt werden (v1-`config.example` enthält diese teils schon, aber inkonsistent mit dem, was das v1-Backup-Skript tatsächlich nutzt — siehe Soll vs. Ist unten).
|
||||
|
||||
## Bekannte Bugs / offene Punkte (aus vorheriger Analyse)
|
||||
|
||||
1. **v2-alt `miyagi-backup.sh`:** `run_remote_updates()` überschreibt globale `LOGFILE`-Variable ohne `local` → alle späteren `log()`-Aufrufe schreiben ins falsche File.
|
||||
2. **v2-alt `miyagi-backup.sh`:** `$PBSPORT` an mehreren Stellen unquotiert (`ssh -p $PBSPORT ...`).
|
||||
3. **v2-alt `miyagi-backup.sh`:** `write_pbs_status()` ist definiert, aber nicht aktiv (Aufruf auskommentiert) — totes Feature oder vergessen zu aktivieren?
|
||||
4. **v2-alt `miyagi-backup.sh`:** Funktion `wait()` überschreibt Bash-Builtin — funktioniert, ist aber ein Fallstrick.
|
||||
5. **v1-alt `miyagi-check.sh`:** `run_pbs_check` wird vor seiner Definition aufgerufen → Laufzeitfehler (`command not found`).
|
||||
6. **v1-alt vs v2-alt `miyagi-install.sh`:** gleicher Dateiname, aber zwei völlig verschiedene Skripte (CLI-basiert mit `--dry-run` vs. dialog-GUI). Muss in v3 eindeutig benannt/entschieden werden, welcher Ansatz weiterverfolgt wird (evtl. auch `install.sh` aus v2-alt als dritte Variante einbeziehen).
|
||||
|
||||
## Offene Entscheidungen für v3
|
||||
|
||||
- Welcher Installer wird Basis: v1-`miyagi-install.sh` (CLI), v2-`miyagi-install.sh` (dialog) oder v2-`install.sh` (dialog + Config-Loader)?
|
||||
- Wird `miyagi-check.sh` (nur in v1) übernommen und der Funktions-Reihenfolge-Bug gefixt, oder ist die Prüf-Logik inzwischen obsolet?
|
||||
- `miyagi-convert.sh` (nur in v1) — wird das noch gebraucht?
|
||||
- `write_pbs_status()` in v2 aktivieren oder entfernen?
|
||||
- Einheitliche `config.example` für v3 erstellen, die alle tatsächlich verwendeten Variablen abdeckt (inkl. `DYNROUTE`, `DDNS_GATEWAY`, `DDNS_SERVER`, `REBOOT`, `PBSPORT`).
|
||||
@@ -1,51 +0,0 @@
|
||||
#Edit all Variables for best Experience
|
||||
|
||||
UPDATES='yes' # Proxmox VE und PBS Updates nach dem Lauf
|
||||
SHUTDOWN='no' # System nach Ausführung herunterfahren?
|
||||
DYNROUTE='yes' # DYNroute ja oder nein?
|
||||
DDNS_GATEWAY='192.168.66.1' # DYNDNS Gateway
|
||||
DDNS_SERVER='1.1.1.1' #DNS Server extern
|
||||
REBOOT='no' # Reboot nach Update?
|
||||
|
||||
# Quelle (Proxmox VE System, das gesichert wird)
|
||||
SOURCEPORT='22' # SSH-Port, normalerweise 22
|
||||
SOURCEHOST='192.168.50.200' # IP des Quell-Proxmox-Servers
|
||||
SOURCEUSER='root' # SSH-User für SOURCEHOST
|
||||
|
||||
# Replikation (ZFS)
|
||||
ZFSTRGT='rpool-ssd1/repl/pve200' # Zielpfad für Replikation
|
||||
|
||||
# ZFS Zsync Replikation
|
||||
ZSYNC='yes' # ZSYNC aktivieren (ja/nein)
|
||||
ZPUSHTAG='bashclub:zsync-198-ssd' # Benutzer-Tag für ZFS
|
||||
ZPUSHMINKEEP='3' # Mindestens zu behaltende Snapshots
|
||||
ZPUSHKEEP='14' # Snapshots mit dem Tag, die behalten werden
|
||||
ZPUSHLABEL='zsync-rz' # Suffix für Snapshot-Autoengine
|
||||
ZPUSHFILTER='hourly|daily|weekly|monthly' # Weitere Filter (leer lassen oder Muster wie daily| weekly etc.)
|
||||
|
||||
# Backup mit Proxmox Backup Server
|
||||
BACKUPSERVER='no' # Backup via PBS aktivieren?
|
||||
MAINTDAY='7' # Wartungstag (1=Mo, 7=So)
|
||||
PBSHOST='192.168.50.199' # IP des Proxmox Backup Servers
|
||||
PBSPORT='22' # SSH-Port PBS
|
||||
PBSUSER='root' # SSH-User für PBSHOST
|
||||
PBSBACKUP_STATUS='86400' # Warnung ab 24h (86400s)
|
||||
PBS_REPO_USER='root@pam' # PBS-User oder API-Token-ID (z.B. 'user@pbs!tokenname') für write_pbs_status
|
||||
PBS_PASSWORD='' # Passwort oder API-Token-Secret zu PBS_REPO_USER (write_pbs_status)
|
||||
BACKUPSTORE='backup' # Datastore auf Quell-Proxmox
|
||||
BACKUPSTOREPBS='backup' # Datastore auf PBS
|
||||
BACKUPEXCLUDE='10,3252,3253,3254' # VM/CT-IDs, die vom Backup ausgeschlossen sind
|
||||
REPLEXCLUDE="$BACKUPEXCLUDE" # Diese auch von Replikation ausschließen
|
||||
|
||||
# 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
|
||||
@@ -1,925 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -uo pipefail
|
||||
IFS=$'\n\t'
|
||||
|
||||
PATH="/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin"
|
||||
SCRIPT_NAME=$(basename "$0")
|
||||
LOGFILE="/var/log/${SCRIPT_NAME%.sh}.log"
|
||||
|
||||
detect_os() {
|
||||
case "$(uname -s)" in
|
||||
Linux) echo "linux" ;;
|
||||
FreeBSD) echo "freebsd" ;;
|
||||
*) echo "unknown" ;;
|
||||
esac
|
||||
}
|
||||
OS_TYPE="$(detect_os)"
|
||||
|
||||
epoch_to_str() {
|
||||
local epoch="$1"
|
||||
if [[ "$OS_TYPE" == "freebsd" ]]; then
|
||||
date -r "$epoch" "+%Y-%m-%d %H:%M:%S"
|
||||
else
|
||||
date -d "@$epoch" "+%Y-%m-%d %H:%M:%S"
|
||||
fi
|
||||
}
|
||||
|
||||
log() {
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOGFILE"
|
||||
}
|
||||
|
||||
usage() {
|
||||
echo "Usage:"
|
||||
echo " $0 -c /path/to/config # Full backup run"
|
||||
echo " $0 [function] # Run individual function"
|
||||
echo " $0 help # Show available functions"
|
||||
exit 1
|
||||
}
|
||||
|
||||
CONFIG_FILE=""
|
||||
while getopts "c:" opt; do
|
||||
case "$opt" in
|
||||
c) CONFIG_FILE="$OPTARG" ;;
|
||||
*) usage ;;
|
||||
esac
|
||||
done
|
||||
shift $((OPTIND - 1))
|
||||
|
||||
if [[ -n "${CONFIG_FILE:-}" ]]; then
|
||||
if [[ ! -f "$CONFIG_FILE" ]]; then
|
||||
log "ERROR: Configuration file not found: $CONFIG_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! bash -n "$CONFIG_FILE"; then
|
||||
log "Syntax error in configuration file $CONFIG_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
source "$CONFIG_FILE"
|
||||
|
||||
REQUIRED_VARS=(
|
||||
SOURCEPORT BACKUPSERVER ZSYNC MAINTDAY SHUTDOWN UPDATES
|
||||
SOURCEHOST ZFSTRGT ZPUSHTAG ZPUSHMINKEEP ZPUSHKEEP ZPUSHLABEL
|
||||
PBSHOST PBSPORT BACKUPSTORE BACKUPSTOREPBS BACKUPEXCLUDE REPLEXCLUDE DYNROUTE REBOOT
|
||||
)
|
||||
|
||||
if [[ "${DYNROUTE,,}" == "yes" ]]; then
|
||||
REQUIRED_VARS+=(DDNS_GATEWAY DDNS_SERVER)
|
||||
fi
|
||||
|
||||
MISSING_VARS=()
|
||||
for var in "${REQUIRED_VARS[@]}"; do
|
||||
if [[ -z "${!var:-}" ]]; then
|
||||
MISSING_VARS+=("$var")
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "${#MISSING_VARS[@]}" -gt 0 ]]; then
|
||||
log "Missing configuration variables:"
|
||||
for var in "${MISSING_VARS[@]}"; do
|
||||
log " - $var"
|
||||
done
|
||||
log "Aborting — please check your configuration file."
|
||||
exit 1
|
||||
else
|
||||
log "All required configuration variables are set."
|
||||
fi
|
||||
fi
|
||||
|
||||
SOURCEUSER="${SOURCEUSER:-root}"
|
||||
PBSUSER="${PBSUSER:-root}"
|
||||
EPIGGYBACK_USER="${EPIGGYBACK_USER:-root}"
|
||||
ECHECKZFS_USER="${ECHECKZFS_USER:-root}"
|
||||
|
||||
rssh() { ssh -o BatchMode=yes -o ConnectTimeout=10 -p "$SOURCEPORT" "${SOURCEUSER}@${SOURCEHOST}" "$@"; }
|
||||
rscp() { scp -P "$SOURCEPORT" "$@"; }
|
||||
rpbssh() { ssh -o BatchMode=yes -o ConnectTimeout=10 -p "$PBSPORT" "${PBSUSER}@${PBSHOST}" "$@"; }
|
||||
|
||||
detect_host_type() {
|
||||
if command -v pveversion >/dev/null 2>&1; then
|
||||
echo "pve"
|
||||
elif command -v proxmox-backup-server >/dev/null 2>&1; then
|
||||
echo "pbs"
|
||||
else
|
||||
echo "unknown"
|
||||
fi
|
||||
}
|
||||
|
||||
get_sourcehostname() {
|
||||
if [[ -z "${SOURCEHOSTNAME:-}" ]]; then
|
||||
log "SOURCEHOSTNAME is empty, retrieving via SSH from $SOURCEHOST..."
|
||||
SOURCEHOSTNAME=$(rssh hostname)
|
||||
log "Detected SOURCEHOSTNAME: $SOURCEHOSTNAME"
|
||||
fi
|
||||
}
|
||||
|
||||
zfs_replace() {
|
||||
local status
|
||||
status=$(zpool status)
|
||||
|
||||
if echo "$status" | grep -q 'scan: resilver in progress'; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if echo "$status" | grep -qE 'replacing-[0-9]'; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
wait_replace() {
|
||||
local interval_seconds=300 # Alle 5 Minuten prüfen
|
||||
local waited_minutes=0
|
||||
|
||||
log "ZFS Replace-Vorgang erkannt – warte unbegrenzt auf Abschluss..."
|
||||
|
||||
while true; do
|
||||
if ! zfs_replace; then
|
||||
log "Replace abgeschlossen nach $waited_minutes Minuten – fahre jetzt herunter."
|
||||
return 0
|
||||
fi
|
||||
log "Replace läuft noch – erneut prüfen in $((interval_seconds/60)) Minuten..."
|
||||
sleep "$interval_seconds"
|
||||
((waited_minutes+=interval_seconds/60))
|
||||
done
|
||||
}
|
||||
|
||||
set_wol_g_enabled() {
|
||||
if [[ "$OS_TYPE" == "freebsd" ]]; then
|
||||
log "FreeBSD erkannt – set_wol_g_enabled() wird übersprungen (ethtool/apt nicht verfügbar)."
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "Checking if ethtool is installed..."
|
||||
|
||||
if ! command -v ethtool >/dev/null 2>&1; then
|
||||
log "ethtool is not installed, attempting installation..."
|
||||
apt update && apt install -y ethtool || {
|
||||
log "Error: ethtool could not be installed."
|
||||
return 1
|
||||
}
|
||||
else
|
||||
log "ethtool is already installed."
|
||||
fi
|
||||
|
||||
log "Setting Wake-on-LAN (WOL) to 'g' on interfaces with static IP..."
|
||||
|
||||
for iface in $(ls /sys/class/net | grep -vE '^(lo|tap|vmbr|veth|br|docker|bond|wl)'); do
|
||||
if [[ -e "/sys/class/net/$iface/device" ]]; then
|
||||
log "Processing physical interface: $iface"
|
||||
|
||||
current_wol=$(ethtool "$iface" 2>/dev/null | awk '/Wake-on:/ {print $2}')
|
||||
if [[ "$current_wol" != "g" ]]; then
|
||||
log "Setting WOL to 'g' for $iface..."
|
||||
ethtool -s "$iface" wol g || log "Error setting WOL on $iface"
|
||||
else
|
||||
log "WOL already set to 'g' for $iface"
|
||||
fi
|
||||
|
||||
if grep -qE "^\s*iface\s+$iface\s+inet\s+static" /etc/network/interfaces; then
|
||||
if ! grep -A 5 -E "^\s*iface\s+$iface\s+inet\s+static" /etc/network/interfaces | grep -q "post-up ethtool -s $iface wol g"; then
|
||||
log "Adding WOL command in static block for $iface..."
|
||||
sed -i "/^\s*iface\s\+$iface\s\+inet\s\+static/a \ post-up ethtool -s $iface wol g" /etc/network/interfaces
|
||||
else
|
||||
log "WOL command already present in static block for $iface."
|
||||
fi
|
||||
else
|
||||
log "No static entry found for $iface, no changes made."
|
||||
fi
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
write_zsync_config() {
|
||||
get_sourcehostname
|
||||
local conf_file="/etc/bashclub/$SOURCEHOST.conf"
|
||||
log "Writing zsync config to $conf_file"
|
||||
{
|
||||
echo "target=$ZFSTRGT"
|
||||
echo "source=${SOURCEUSER}@${SOURCEHOST}"
|
||||
echo "sshport=$SOURCEPORT"
|
||||
echo "tag=$ZPUSHTAG"
|
||||
echo "snapshot_filter=\"$ZPUSHFILTER\""
|
||||
echo "min_keep=$ZPUSHMINKEEP"
|
||||
echo "zfs_auto_snapshot_keep=$ZPUSHKEEP"
|
||||
echo "zfs_auto_snapshot_label=zfs-auto-snap_$ZPUSHLABEL"
|
||||
echo "zfs_auto_snapshot_engine=internal"
|
||||
echo "checkzfs_disabled=0"
|
||||
echo "checkzfs_local=0"
|
||||
echo "checkzfs_prefix=miyagi-$SOURCEHOSTNAME-$(hostname)-$ZPUSHTAG"
|
||||
echo "checkzfs_max_age=1500,2000"
|
||||
echo "checkzfs_max_snapshot_count=180,200"
|
||||
echo "checkzfs_spool=1"
|
||||
echo "checkzfs_spool_maxage=100000"
|
||||
} > "$conf_file"
|
||||
}
|
||||
|
||||
run_zsync() {
|
||||
write_zsync_config
|
||||
if [[ "$ZSYNC" != "no" ]]; then
|
||||
/usr/bin/bashclub-zsync -c "/etc/bashclub/$SOURCEHOST.conf"
|
||||
else
|
||||
log "Zsync is disabled."
|
||||
fi
|
||||
}
|
||||
|
||||
disable_pve_notifications() {
|
||||
if [[ "$(hostname -I 2>/dev/null)" == *"$SOURCEHOST"* || "$(hostname)" == "$SOURCEHOST" ]]; then
|
||||
log "Lokaler Host ist SOURCEHOST – Notification-Deaktivierung wird hier übersprungen."
|
||||
return
|
||||
fi
|
||||
|
||||
if [[ "$(detect_host_type)" == "pve" ]]; then
|
||||
log "PVE erkannt – deaktiviere Notification-Target mail-to-root und default-matcher (keine lokalen Mails an root)."
|
||||
pvesh set /cluster/notifications/endpoints/sendmail/mail-to-root --disable 1 >/dev/null 2>&1 \
|
||||
|| log "WARN: Notification-Target mail-to-root konnte nicht deaktiviert werden."
|
||||
pvesh set /cluster/notifications/matchers/default-matcher --disable 1 >/dev/null 2>&1 \
|
||||
|| log "WARN: Notification-Matcher default-matcher konnte nicht deaktiviert werden."
|
||||
fi
|
||||
}
|
||||
|
||||
run_local_apt_updates() {
|
||||
log "Running updates on local system..."
|
||||
|
||||
local TIMESTAMP
|
||||
TIMESTAMP=$(date +"%Y-%m-%d_%H-%M-%S")
|
||||
local SNAPSHOT_TAG="pve-update-via-miyagi"
|
||||
local MAX_SNAPSHOTS=5
|
||||
local REBOOT_FLAG="${REBOOT^^}"
|
||||
local UPDATE_LOGFILE="/var/log/proxmox_update.log"
|
||||
|
||||
log_msg() {
|
||||
local level="$1"
|
||||
local message="$2"
|
||||
local timestamp
|
||||
timestamp=$(date +"%Y-%m-%d %H:%M:%S")
|
||||
echo "[$timestamp] $message"
|
||||
echo "[$timestamp] [$level] $message" >> "$UPDATE_LOGFILE"
|
||||
}
|
||||
|
||||
local HOST_TYPE
|
||||
HOST_TYPE=$(detect_host_type)
|
||||
local ZFS_DATASETS=()
|
||||
case "$HOST_TYPE" in
|
||||
pve)
|
||||
log_msg "INFO" "Systemtyp erkannt: PVE"
|
||||
ZFS_DATASETS=("rpool/ROOT/pve-1" "rpool/pveconf")
|
||||
;;
|
||||
pbs)
|
||||
log_msg "INFO" "Systemtyp erkannt: PBS"
|
||||
ZFS_DATASETS=("rpool/ROOT/pbs-1")
|
||||
;;
|
||||
*)
|
||||
log_msg "WARN" "Systemtyp (PVE/PBS) konnte nicht erkannt werden – ueberspringe ZFS-Snapshots."
|
||||
;;
|
||||
esac
|
||||
|
||||
# Prüfen, ob Updates verfügbar sind
|
||||
if ! apt update | tee -a "$UPDATE_LOGFILE" | grep -qi "upgradable"; then
|
||||
log_msg "INFO" "Keine Updates verfügbar."
|
||||
return
|
||||
fi
|
||||
|
||||
log_msg "INFO" "Updates verfügbar. Erstelle Snapshots."
|
||||
|
||||
for dataset in "${ZFS_DATASETS[@]}"; do
|
||||
if ! zfs list -H -o name "$dataset" >/dev/null 2>&1; then
|
||||
log_msg "WARN" "Dataset $dataset existiert nicht – übersprungen."
|
||||
continue
|
||||
fi
|
||||
|
||||
snapshot="${dataset}@${SNAPSHOT_TAG}-${TIMESTAMP}"
|
||||
log_msg "INFO" "Erstelle Snapshot: $snapshot"
|
||||
zfs snapshot "$snapshot"
|
||||
|
||||
# Alte Snapshots bereinigen
|
||||
log_msg "INFO" "Bereinige alte Snapshots in $dataset"
|
||||
old_snaps=$(zfs list -t snapshot -o name -s creation | grep "^${dataset}@${SNAPSHOT_TAG}-")
|
||||
snap_count=$(grep -c . <<< "$old_snaps")
|
||||
if (( snap_count > MAX_SNAPSHOTS )); then
|
||||
snaps_to_delete=$(echo "$old_snaps" | head -n $((snap_count - MAX_SNAPSHOTS)))
|
||||
while IFS= read -r snap; do
|
||||
log_msg "INFO" "Lösche alten Snapshot: $snap"
|
||||
zfs destroy "$snap"
|
||||
done <<< "$snaps_to_delete"
|
||||
fi
|
||||
done
|
||||
|
||||
# System-Upgrade
|
||||
log_msg "INFO" "Starte dist-upgrade"
|
||||
if ! apt dist-upgrade -y | tee -a "$UPDATE_LOGFILE"; then
|
||||
log_msg "ERROR" "Fehler während dist-upgrade"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log_msg "INFO" "Starte autoremove"
|
||||
apt autoremove -y | tee -a "$UPDATE_LOGFILE"
|
||||
|
||||
# Kernel-Update prüfen
|
||||
if apt list --upgradable 2>/dev/null | grep -q "linux-image-"; then
|
||||
log_msg "WARN" "Kernel-Update erkannt. Neustart empfohlen."
|
||||
if [[ "$REBOOT_FLAG" == "YES" ]]; then
|
||||
log_msg "INFO" "REBOOT=YES erkannt. System wird neugestartet."
|
||||
reboot
|
||||
fi
|
||||
else
|
||||
log_msg "INFO" "Update abgeschlossen. Kein Neustart erforderlich."
|
||||
fi
|
||||
}
|
||||
|
||||
run_pbs_apt_updates() {
|
||||
if [[ "${BACKUPSERVER,,}" == "yes" ]]; then
|
||||
log "Running updates on PBS host ($PBSHOST)..."
|
||||
rpbssh apt update && rpbssh apt dist-upgrade -y || {
|
||||
log "Error during updates on $PBSHOST"
|
||||
}
|
||||
else
|
||||
log "PBS updates skipped (BACKUPSERVER=$BACKUPSERVER)"
|
||||
fi
|
||||
}
|
||||
|
||||
run_remote_updates() {
|
||||
if [[ "${UPDATES,,}" == "yes" ]]; then
|
||||
if [[ "$OS_TYPE" == "freebsd" ]]; then
|
||||
log "FreeBSD erkannt – lokales apt-Update wird übersprungen (apt nicht verfügbar)."
|
||||
else
|
||||
run_local_apt_updates
|
||||
disable_pve_notifications
|
||||
fi
|
||||
|
||||
# PBS-Host-Update läuft unabhängig vom lokalen Update-Ergebnis/Skript-Host-OS
|
||||
run_pbs_apt_updates
|
||||
else
|
||||
log "Updates disabled (UPDATES=$UPDATES)"
|
||||
fi
|
||||
}
|
||||
|
||||
send_piggyback() {
|
||||
get_sourcehostname
|
||||
local combined_host="miyagi-${SOURCEHOSTNAME}-$(hostname)"
|
||||
local filename="100000_${combined_host}"
|
||||
|
||||
log "Reminder: Add a host named ${combined_host} in CMK (without Agent, Piggyback enabled)!"
|
||||
log "Creating piggyback file: $filename"
|
||||
|
||||
{
|
||||
echo "<<<<${combined_host}>>>>"
|
||||
/usr/bin/check_mk_agent
|
||||
echo "<<<<>>>>"
|
||||
} > "$filename"
|
||||
|
||||
if rscp "$filename" "${SOURCEUSER}@${SOURCEHOST}:/var/lib/check_mk_agent/spool/"; then
|
||||
log "Piggyback data successfully sent to $SOURCEHOST"
|
||||
else
|
||||
log "ERROR: Failed to send piggyback data to $SOURCEHOST"
|
||||
fi
|
||||
|
||||
rm -f "$filename"
|
||||
}
|
||||
|
||||
run_pbs_log_clear() {
|
||||
log "Running clear LOG on PBS host ($PBSHOST)..."
|
||||
|
||||
if ! rpbssh find /var/log/proxmox-backup/tasks/ -type f -mtime +60 -delete; then
|
||||
log "Error clearing PBS LOG on $PBSHOST"
|
||||
fi
|
||||
}
|
||||
|
||||
run_pve_log_clear() {
|
||||
log "Running clear LOG on PVE host ($PBSHOST)..."
|
||||
|
||||
if ! rssh find /var/log/vzdump -type f -mtime +30 -delete; then
|
||||
log "Error clearing PVE LOG on $SOURCEHOST"
|
||||
fi
|
||||
}
|
||||
|
||||
run_pbs_backup() {
|
||||
if [[ "${BACKUPSERVER,,}" != "yes" ]]; then
|
||||
log "PBS Backup übersprungen: BACKUPSERVER='$BACKUPSERVER' (muss 'yes' sein)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
get_sourcehostname
|
||||
log "Starte PBS Backup auf Host: $SOURCEHOST"
|
||||
|
||||
# PBS-Storage prüfen und ggf. aktivieren
|
||||
log "Prüfe, ob PBS-Storage '$BACKUPSTORE' auf $SOURCEHOST aktiviert ist..."
|
||||
if rssh "pvesm status | grep -w '$BACKUPSTORE' | grep -q 'disabled'"; then
|
||||
log "PBS-Storage '$BACKUPSTORE' ist deaktiviert – aktiviere temporär und warte 10 Sekunden..."
|
||||
rssh "pvesm set '$BACKUPSTORE' --disable 0 && sleep 10"
|
||||
pbs_enabled_by_script=true
|
||||
else
|
||||
log "PBS-Storage '$BACKUPSTORE' ist bereits aktiviert."
|
||||
pbs_enabled_by_script=false
|
||||
fi
|
||||
|
||||
vzdump_success=false
|
||||
|
||||
# Versuch: vzdump mit Change Detection
|
||||
## Eintrag muss in /etc/vzdump.conf entries-max 100000000
|
||||
log "Starte vzdump mit Change Detection..."
|
||||
if rssh vzdump --pbs-change-detection-mode metadata \
|
||||
--node "$SOURCEHOSTNAME" --storage "$BACKUPSTORE" \
|
||||
--exclude "$BACKUPEXCLUDE" --mode snapshot --all 1 \
|
||||
--notes-template '{{guestname}}'; then
|
||||
|
||||
log "vzdump (Change Detection) erfolgreich."
|
||||
vzdump_success=true
|
||||
else
|
||||
log "vzdump (Change Detection) fehlgeschlagen – versuche Fallback ohne Change Detection..."
|
||||
|
||||
if rssh vzdump \
|
||||
--node "$SOURCEHOSTNAME" --storage "$BACKUPSTORE" \
|
||||
--exclude "$BACKUPEXCLUDE" --mode snapshot --all 1 \
|
||||
--notes-template '{{guestname}}'; then
|
||||
|
||||
log "Fallback-vzdump erfolgreich."
|
||||
vzdump_success=true
|
||||
else
|
||||
log "FEHLER: vzdump fehlgeschlagen – auch im Fallback."
|
||||
fi
|
||||
fi
|
||||
|
||||
# PBS-Storage ggf. wieder deaktivieren
|
||||
if [[ "$vzdump_success" == true && "$pbs_enabled_by_script" == true ]]; then
|
||||
log "Deaktiviere temporär aktiviertes PBS-Storage '$BACKUPSTORE' auf $SOURCEHOST..."
|
||||
rssh "pvesm set '$BACKUPSTORE' --disable 1"
|
||||
fi
|
||||
|
||||
# Monitoring-Ausgabe
|
||||
if [[ "$vzdump_success" == true ]]; then
|
||||
echo "0 DailyPBS - Daily Backup erfolgreich" > /tmp/cmk_tmp.out
|
||||
else
|
||||
echo "2 DailyPBS - Daily Backup FEHLGESCHLAGEN" > /tmp/cmk_tmp.out
|
||||
fi
|
||||
|
||||
{
|
||||
echo "<<<local>>>"
|
||||
cat /tmp/cmk_tmp.out
|
||||
} > /tmp/1200000_checkpbs
|
||||
|
||||
rscp /tmp/1200000_checkpbs "${SOURCEUSER}@${SOURCEHOST}":/var/lib/check_mk_agent/spool \
|
||||
|| log "Fehler beim Übertragen des Monitoring-Outputs via SCP"
|
||||
|
||||
rm -f /tmp/cmk_tmp.out /tmp/1200000_checkpbs
|
||||
#write_pbs_status
|
||||
}
|
||||
|
||||
run_maintenance() {
|
||||
if [[ "${BACKUPSERVER,,}" != "yes" ]]; then
|
||||
log "PBS Backup wird übersprungen (BACKUPSERVER=$BACKUPSERVER)"
|
||||
return
|
||||
fi
|
||||
|
||||
if [[ "$(date +%u)" == "$MAINTDAY" ]]; then
|
||||
log "Running maintenance..."
|
||||
PRUNEJOB=$(rpbssh proxmox-backup-manager prune-job list --output-format json-pretty | grep -m 1 "id" | cut -d'"' -f4)
|
||||
rpbssh proxmox-backup-manager prune-job run "$PRUNEJOB"
|
||||
rpbssh proxmox-backup-manager garbage-collection start "$BACKUPSTOREPBS"
|
||||
rpbssh proxmox-backup-manager verify backup
|
||||
else
|
||||
log "No maintenance scheduled for today."
|
||||
fi
|
||||
}
|
||||
|
||||
run_scrub_stop() {
|
||||
local mode="$1" # "local" oder "remote"
|
||||
local ssh_cmd=()
|
||||
|
||||
if [[ "$mode" == "remote" ]]; then
|
||||
ssh_cmd=(ssh -o BatchMode=yes -o ConnectTimeout=10 -p "$SOURCEPORT" "${SOURCEUSER}@${SOURCEHOST}")
|
||||
fi
|
||||
|
||||
"${ssh_cmd[@]}" bash -c '
|
||||
for pool in $(zpool list -H -o name); do
|
||||
echo "Stopping scrub on pool: $pool"
|
||||
if zpool status "$pool" | grep -q "scrub in progress"; then
|
||||
if zpool scrub -s "$pool"; then
|
||||
echo "Scrub stopped on $pool"
|
||||
else
|
||||
echo "Error stopping scrub on $pool"
|
||||
fi
|
||||
else
|
||||
echo "No active scrub on $pool"
|
||||
fi
|
||||
done
|
||||
'
|
||||
}
|
||||
|
||||
write_pbs_status() {
|
||||
local spool_file="/var/lib/check_mk_agent/spool/90000_checkpbs_local"
|
||||
local repo="${PBS_REPO_USER}@${PBSHOST}:${BACKUPSTOREPBS}"
|
||||
local tmpfile
|
||||
tmpfile=$(mktemp)
|
||||
|
||||
if ! command -v proxmox-backup-client >/dev/null 2>&1; then
|
||||
log "proxmox-backup-client nicht installiert – PBS-Status-Ausgabe übersprungen."
|
||||
return
|
||||
fi
|
||||
|
||||
if [[ -z "${PBS_REPO_USER:-}" || -z "${PBS_PASSWORD:-}" ]]; then
|
||||
log "PBS_REPO_USER oder PBS_PASSWORD nicht gesetzt – PBS-Status-Ausgabe übersprungen."
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "<<<local>>>" > "$tmpfile"
|
||||
|
||||
local now
|
||||
now=$(date +%s)
|
||||
|
||||
# === Schwellenwert(e) parsen ===
|
||||
local warn_threshold=0
|
||||
local crit_threshold=86400 # Default kritisch ab 24h
|
||||
|
||||
if [[ -n "${PBSBACKUP_STATUS:-}" ]]; then
|
||||
IFS=',' read -r t1 t2 <<< "$PBSBACKUP_STATUS"
|
||||
if [[ "$t1" =~ ^[0-9]+$ && -z "$t2" ]]; then
|
||||
# Nur ein Wert vorhanden
|
||||
crit_threshold=$t1
|
||||
log "Verwende einfachen Schwellenwert: CRIT=${crit_threshold}s"
|
||||
elif [[ "$t1" =~ ^[0-9]+$ && "$t2" =~ ^[0-9]+$ ]]; then
|
||||
warn_threshold=$t1
|
||||
crit_threshold=$t2
|
||||
log "Verwende gestaffelte Schwellenwerte: WARN=${warn_threshold}s, CRIT=${crit_threshold}s"
|
||||
else
|
||||
log "WARNUNG: Ungültiges PBSBACKUP_STATUS-Format – Fallback: CRIT=${crit_threshold}s"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Backup-Daten extrahieren (direkt vom Skript-Host gegen PBSHOST, mit PBS_PASSWORD authentifiziert)
|
||||
PBS_PASSWORD="$PBS_PASSWORD" proxmox-backup-client list --repository "$repo" --output-format json 2>/dev/null | \
|
||||
grep -E '"backup-id"|"backup-type"|"backup-time"' | \
|
||||
sed -E 's/[",]//g; s/^ *//' > "$tmpfile.json"
|
||||
|
||||
local backup_type="" backup_id="" backup_time=""
|
||||
while read -r line; do
|
||||
case "$line" in
|
||||
backup-type:*)
|
||||
backup_type="${line#*: }"
|
||||
;;
|
||||
backup-id:*)
|
||||
backup_id="${line#*: }"
|
||||
;;
|
||||
backup-time:*)
|
||||
backup_time="${line#*: }"
|
||||
|
||||
if [[ -n "$backup_type" && -n "$backup_id" && -n "$backup_time" ]]; then
|
||||
local age status timestamp msg
|
||||
age=$((now - backup_time))
|
||||
timestamp=$(epoch_to_str "$backup_time")
|
||||
|
||||
if (( age < warn_threshold )); then
|
||||
status=0
|
||||
elif (( age < crit_threshold )); then
|
||||
status=1
|
||||
else
|
||||
status=2
|
||||
fi
|
||||
|
||||
msg="$backup_type/$backup_id last backup $timestamp (age: $((age / 3600))h)"
|
||||
echo "$status PBS_${backup_type}_${backup_id} - $msg" >> "$tmpfile"
|
||||
|
||||
# Reset
|
||||
backup_type="" backup_id="" backup_time=""
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
done < "$tmpfile.json"
|
||||
|
||||
rm -f "$tmpfile.json"
|
||||
mv "$tmpfile" "$spool_file"
|
||||
log "PBS Backup Status lokal geschrieben: $spool_file"
|
||||
}
|
||||
|
||||
run_pre_shutdown_hooks() {
|
||||
local hook_dir="/etc/miyagi-backup/hooks.d/pre-shutdown"
|
||||
if [[ ! -d "$hook_dir" ]]; then
|
||||
return 0
|
||||
fi
|
||||
local hooks=()
|
||||
while IFS= read -r -d '' f; do
|
||||
hooks+=("$f")
|
||||
done < <(find "$hook_dir" -maxdepth 1 -name "*.sh" -executable -print0 | sort -z)
|
||||
if [[ ${#hooks[@]} -eq 0 ]]; then
|
||||
return 0
|
||||
fi
|
||||
log "Führe Pre-Shutdown-Hooks aus ($hook_dir)..."
|
||||
for hook in "${hooks[@]}"; do
|
||||
log "Hook: $hook"
|
||||
if ! bash "$hook"; then
|
||||
log "WARNUNG: Hook $hook fehlgeschlagen (Exit $?) – fahre fort"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
shutdown_now() {
|
||||
if [[ "${SHUTDOWN,,}" == "yes" ]]; then
|
||||
if zfs_replace; then
|
||||
log "ZFS Replace-Vorgang erkannt – warte bis zum Abschluss..."
|
||||
if ! wait_replace; then
|
||||
log "Shutdown abgebrochen – Replace ist nach max. Wartezeit noch nicht abgeschlossen."
|
||||
return
|
||||
fi
|
||||
fi
|
||||
send_piggyback
|
||||
send_piggyback_external
|
||||
send_checkzfs_external
|
||||
run_pre_shutdown_hooks
|
||||
log "Shutting down now...in 60sec"
|
||||
sleep 60
|
||||
shutdown now
|
||||
else
|
||||
log "No shutdown requested."
|
||||
fi
|
||||
}
|
||||
|
||||
send_piggyback_external() {
|
||||
if [[ "${EPIGGYBACK,,}" != "yes" ]]; then
|
||||
log "Externer Piggyback-Export deaktiviert."
|
||||
return
|
||||
fi
|
||||
get_sourcehostname
|
||||
log "Ermittelter SOURCEHOSTNAME: $SOURCEHOSTNAME"
|
||||
|
||||
local combined_host="miyagi-${SOURCEHOSTNAME}-$(hostname)"
|
||||
local spool_file="100000_${combined_host}_piggyback_external"
|
||||
local temp_dir
|
||||
temp_dir=$(mktemp -d)
|
||||
|
||||
log "Erzeuge temporäre Piggyback-Datei: $temp_dir/$spool_file"
|
||||
|
||||
{
|
||||
echo "<<<<${combined_host}>>>>"
|
||||
/usr/bin/check_mk_agent
|
||||
echo "<<<<>>>>"
|
||||
} > "$temp_dir/$spool_file"
|
||||
|
||||
if scp -P "$EPIGGYBACK_PORT" "$temp_dir/$spool_file" "${EPIGGYBACK_USER}@${EPIGGYBACK_HOST}:/var/lib/check_mk_agent/spool/"; then
|
||||
log "Piggyback-Daten erfolgreich an $EPIGGYBACK_HOST gesendet: $spool_file"
|
||||
else
|
||||
log "Fehler beim Übertragen der Piggyback-Daten an $EPIGGYBACK_HOST"
|
||||
rm -rf "$temp_dir"
|
||||
return 1
|
||||
fi
|
||||
|
||||
rm -rf "$temp_dir"
|
||||
return 0
|
||||
}
|
||||
|
||||
send_checkzfs() {
|
||||
if [[ "${ZSYNC,,}" != "yes" ]]; then
|
||||
log "send_checkzfs: ZSYNC nicht aktiv – übersprungen"
|
||||
return 0
|
||||
fi
|
||||
|
||||
get_sourcehostname
|
||||
|
||||
local checkzfs_cmd
|
||||
checkzfs_cmd="$(command -v checkzfs || true)"
|
||||
if [[ -z "$checkzfs_cmd" || ! -x "$checkzfs_cmd" ]]; then
|
||||
log "send_checkzfs: checkzfs nicht gefunden – übersprungen"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Defaults für checkzfs-Parameter (können via zsync-Config überschrieben werden)
|
||||
local checkzfs_prefix="${checkzfs_prefix:-miyagi-${SOURCEHOSTNAME}-$(hostname)-${ZPUSHTAG}}"
|
||||
local checkzfs_max_age="${checkzfs_max_age:-1500,2000}"
|
||||
local checkzfs_max_snapshot_count="${checkzfs_max_snapshot_count:-180,200}"
|
||||
|
||||
# Optional zsync-Config laden falls vorhanden (überschreibt Defaults)
|
||||
local zsync_config="/etc/bashclub/${SOURCEHOST}.conf"
|
||||
[[ -f "$zsync_config" ]] && source "$zsync_config"
|
||||
|
||||
local filter=""
|
||||
while IFS=$'\t' read -r name value src; do
|
||||
if [[ "$src" == "local" && "$value" == "subvols" ]]; then
|
||||
filter+="${name}/|"
|
||||
elif [[ "$src" == "local" && "$value" == "all" ]]; then
|
||||
filter+="${name}|"
|
||||
fi
|
||||
done < <(rssh "zfs get -H -o name,value,source -t filesystem,volume $ZPUSHTAG")
|
||||
filter="#${filter%|}"
|
||||
log "Generierter ZFS-Filter: $filter"
|
||||
|
||||
local combined="miyagi-${SOURCEHOSTNAME}-$(hostname)-${ZPUSHTAG}"
|
||||
local spoolfile="/tmp/${combined}_checkzfs"
|
||||
local spooldest="100000_${combined}_checkzfs"
|
||||
|
||||
log "Führe checkzfs aus..."
|
||||
{
|
||||
echo "<<<local>>>"
|
||||
"$checkzfs_cmd" \
|
||||
--source "${SOURCEUSER}@${SOURCEHOST}:${SOURCEPORT}" \
|
||||
--filter "$filter" \
|
||||
--replicafilter "^${ZFSTRGT}" \
|
||||
--prefix "$checkzfs_prefix" \
|
||||
--threshold "$checkzfs_max_age" \
|
||||
--maxsnapshots "$checkzfs_max_snapshot_count" \
|
||||
--output checkmk
|
||||
} > "$spoolfile"
|
||||
|
||||
if rscp "$spoolfile" "${SOURCEUSER}@${SOURCEHOST}:/var/lib/check_mk_agent/spool/${spooldest}"; then
|
||||
log "CheckZFS-Daten an $SOURCEHOST gesendet: $spooldest"
|
||||
else
|
||||
log "FEHLER: CheckZFS-Daten konnten nicht übertragen werden"
|
||||
fi
|
||||
|
||||
rm -f "$spoolfile"
|
||||
}
|
||||
|
||||
send_checkzfs_external() {
|
||||
if [[ "${ECHECKZFS,,}" != "yes" ]]; then
|
||||
log "Externer CheckZFS deaktiviert."
|
||||
return
|
||||
fi
|
||||
local config="/etc/bashclub/${SOURCEHOST}.conf"
|
||||
if [[ ! -f "$config" ]]; then
|
||||
log "Konfigurationsdatei fehlt: $config"
|
||||
return 1
|
||||
fi
|
||||
source "$config"
|
||||
|
||||
get_sourcehostname # Setzt SOURCEHOSTNAME, wenn nicht vorhanden
|
||||
|
||||
local checkzfs_cmd="${checkzfs_cmd:-$(command -v checkzfs)}"
|
||||
if [[ -z "$checkzfs_cmd" || ! -x "$checkzfs_cmd" ]]; then
|
||||
log "checkzfs nicht gefunden oder nicht ausführbar – Abbruch."
|
||||
return 1
|
||||
fi
|
||||
log "Verwende checkzfs: $checkzfs_cmd"
|
||||
|
||||
# Filter anhand ZFS-Tags auf dem SOURCEHOST aufbauen
|
||||
local filter=""
|
||||
while IFS=$'\t' read -r name value source; do
|
||||
if [[ "$source" == "local" && "$value" == "subvols" ]]; then
|
||||
filter+="${name}/|"
|
||||
elif [[ "$source" == "local" && "$value" == "all" ]]; then
|
||||
filter+="${name}|"
|
||||
fi
|
||||
done < <(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_external"
|
||||
local spooldest="100000_${combined}_checkzfs_external"
|
||||
|
||||
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 scp -P "$ECHECKZFS_PORT" "$spoolfile" ${ECHECKZFS_USER}@"$ECHECKZFS_HOST":/var/lib/check_mk_agent/spool/"$spooldest"; then
|
||||
log "Spool-Datei erfolgreich übertragen an $ECHECKZFS_HOST:$spooldest"
|
||||
else
|
||||
log "Fehler beim Übertragen der Spool-Datei an $ECHECKZFS_HOST"
|
||||
fi
|
||||
|
||||
rm -f "$spoolfile"
|
||||
}
|
||||
wait() {
|
||||
echo
|
||||
echo "###########################################"
|
||||
echo "# Das Backup startet in 60 Sekunden... #"
|
||||
echo "# Drücke [Enter], um sofort fortzufahren, #"
|
||||
echo "# oder warte, um automatisch zu starten. #"
|
||||
echo "###########################################"
|
||||
read -t 60 -p "Fortfahren (Enter drücken) oder warten... " input
|
||||
if [[ $? -eq 0 ]]; then
|
||||
log "Press ENTER to continue."
|
||||
else
|
||||
log "No press ENTER, wait for 60 Sec.."
|
||||
sleep 60
|
||||
fi
|
||||
}
|
||||
set_dynamic_route() {
|
||||
local target="$SOURCEHOST"
|
||||
local gateway="$DDNS_GATEWAY"
|
||||
local dns_server="$DDNS_SERVER"
|
||||
local current_ip
|
||||
|
||||
if [[ -z "$target" || -z "$gateway" ]]; then
|
||||
log "Fehler: SOURCEHOST oder DDNS_GATEWAY nicht gesetzt"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Ist SOURCEHOST bereits eine IPv4- oder IPv6-Adresse?
|
||||
if [[ "$target" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ || "$target" == *:* ]]; then
|
||||
current_ip="$target"
|
||||
log "SOURCEHOST ist bereits eine IP-Adresse: $current_ip"
|
||||
else
|
||||
log "Setze temporäre Route zu DNS-Server $dns_server via $gateway..."
|
||||
|
||||
case "$(uname -s)" in
|
||||
Linux)
|
||||
ip route | grep -q "^$dns_server" || \
|
||||
ip route add "$dns_server" via "$gateway"
|
||||
;;
|
||||
FreeBSD)
|
||||
route -n get "$dns_server" >/dev/null 2>&1 || \
|
||||
route add -host "$dns_server" "$gateway"
|
||||
;;
|
||||
esac
|
||||
|
||||
log "Löse IP von $target über DNS $dns_server..."
|
||||
current_ip=$(dig +short @"$dns_server" "$target" | awk 'NF {print; exit}')
|
||||
|
||||
if [[ -z "$current_ip" ]]; then
|
||||
log "Fehler: Konnte IP für $target nicht auflösen"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
log "Ermittelte IP: $current_ip – setze Route via $gateway..."
|
||||
|
||||
case "$(uname -s)" in
|
||||
Linux)
|
||||
ip route | grep -q "^$current_ip" || \
|
||||
ip route add "$current_ip" via "$gateway"
|
||||
;;
|
||||
FreeBSD)
|
||||
route -n get "$current_ip" >/dev/null 2>&1 || \
|
||||
route add -host "$current_ip" "$gateway"
|
||||
;;
|
||||
*)
|
||||
log "Nicht unterstütztes Betriebssystem: $(uname -s)"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
|
||||
log "Route erfolgreich gesetzt: $current_ip via $gateway"
|
||||
}
|
||||
# Main execution:
|
||||
if [[ $# -eq 0 ]]; then
|
||||
if [[ -n "$CONFIG_FILE" ]]; then
|
||||
wait
|
||||
log "Running full backup using configuration file: $CONFIG_FILE"
|
||||
|
||||
if [[ "${DYNROUTE,,}" == "yes" ]]; then
|
||||
log "DYNROUTE ist aktiviert – setze dynamische Route für $SOURCEHOST..."
|
||||
set_dynamic_route
|
||||
else
|
||||
log "DYNROUTE ist deaktiviert oder nicht gesetzt."
|
||||
fi
|
||||
|
||||
write_zsync_config
|
||||
run_zsync
|
||||
|
||||
if [[ "${BACKUPSERVER,,}" == "yes" ]]; then
|
||||
log "BACKUPSERVER ist aktiviert, führe Backup aus..."
|
||||
run_pbs_backup
|
||||
run_maintenance
|
||||
run_pbs_log_clear
|
||||
run_pve_log_clear
|
||||
else
|
||||
log "BACKUPSERVER ist nicht aktiviert (BACKUPSERVER=$BACKUPSERVER), überspringe Backup."
|
||||
fi
|
||||
run_remote_updates
|
||||
if [[ "${EPIGGYBACK,,}" == "yes" ]]; then
|
||||
send_piggyback_external
|
||||
fi
|
||||
|
||||
if [[ "${ECHECKZFS,,}" == "yes" ]]; then
|
||||
send_checkzfs_external
|
||||
fi
|
||||
shutdown_now
|
||||
else
|
||||
usage
|
||||
fi
|
||||
else
|
||||
# Funktionen die keine Config brauchen
|
||||
CONFIG_FREE_FUNCTIONS=(help detect_os detect_host_type zfs_replace wait_replace set_wol_g_enabled run_scrub_stop)
|
||||
|
||||
case "$1" in
|
||||
help)
|
||||
declare -F | awk '{print $3}' | grep -v "^_" | grep -v "^main$"
|
||||
;;
|
||||
*)
|
||||
if declare -F "$1" >/dev/null 2>&1; then
|
||||
config_required=true
|
||||
for fn in "${CONFIG_FREE_FUNCTIONS[@]}"; do
|
||||
[[ "$1" == "$fn" ]] && config_required=false && break
|
||||
done
|
||||
if [[ "$config_required" == true && -z "${SOURCEHOST:-}" ]]; then
|
||||
log "Funktion '$1' benötigt eine Config. Aufruf: $0 -c /etc/miyagi-backup/backup.conf $1"
|
||||
exit 1
|
||||
fi
|
||||
"$@"
|
||||
else
|
||||
log "Function $1 not found."
|
||||
usage
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
@@ -1,293 +0,0 @@
|
||||
#!/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
|
||||
@@ -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
|
||||
@@ -1,44 +0,0 @@
|
||||
#Edit all Variables for best Experience
|
||||
|
||||
UPDATES='yes' # Proxmox VE und PBS Updates nach dem Lauf
|
||||
SHUTDOWN='no' # System nach Ausführung herunterfahren?
|
||||
DYNROUTE='yes' # DYNroute ja oder nein?
|
||||
DDNS_GATEWAY='192.168.66.1' # DYNDNS Gateway
|
||||
DDNS_SERVER='1.1.1.1' #DNS Server extern
|
||||
REBOOT='no' # Reboot nach Update?
|
||||
|
||||
# Quelle (Proxmox VE System, das gesichert wird)
|
||||
SOURCEPORT='22' # SSH-Port, normalerweise 22
|
||||
SOURCEHOST='192.168.50.200' # IP des Quell-Proxmox-Servers
|
||||
|
||||
# Replikation (ZFS)
|
||||
ZFSTRGT='rpool-ssd1/repl/pve200' # Zielpfad für Replikation
|
||||
|
||||
# ZFS Zsync Replikation
|
||||
ZSYNC='yes' # ZSYNC aktivieren (ja/nein)
|
||||
ZPUSHTAG='bashclub:zsync-198-ssd' # Benutzer-Tag für ZFS
|
||||
ZPUSHMINKEEP='3' # Mindestens zu behaltende Snapshots
|
||||
ZPUSHKEEP='14' # Snapshots mit dem Tag, die behalten werden
|
||||
ZPUSHLABEL='zsync-rz' # Suffix für Snapshot-Autoengine
|
||||
ZPUSHFILTER='hourly|daily|weekly|monthly' # Weitere Filter (leer lassen oder Muster wie daily| weekly etc.)
|
||||
|
||||
# Backup mit Proxmox Backup Server
|
||||
BACKUPSERVER='no' # Backup via PBS aktivieren?
|
||||
MAINTDAY='7' # Wartungstag (1=Mo, 7=So)
|
||||
PBSHOST='192.168.50.199' # IP des Proxmox Backup Servers
|
||||
PBSPORT='22' ##testing##
|
||||
PBSBACKUP_STATUS='86400' # Warnung ab 24h (86400s)
|
||||
BACKUPSTORE='backup' # Datastore auf Quell-Proxmox
|
||||
BACKUPSTOREPBS='backup' # Datastore auf PBS
|
||||
BACKUPEXCLUDE='10,3252,3253,3254' # VM/CT-IDs, die vom Backup ausgeschlossen sind
|
||||
REPLEXCLUDE="$BACKUPEXCLUDE" # Diese auch von Replikation ausschließen
|
||||
|
||||
# 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
|
||||
|
||||
# External Checkzfs Host
|
||||
ECHECKZFS_PORT='2222' # SSH-Port für ECHECKZFS_HOST
|
||||
ECHECKZFS_HOST='192.168.50.210' # Monitoring-Zielhost für check_zfs
|
||||
@@ -1,489 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -uo pipefail
|
||||
IFS=$'\n\t'
|
||||
|
||||
PATH="/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin"
|
||||
SCRIPT_NAME=$(basename "$0")
|
||||
LOGFILE="/var/log/${SCRIPT_NAME%.sh}.log"
|
||||
|
||||
log() {
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOGFILE"
|
||||
}
|
||||
|
||||
usage() {
|
||||
echo "Usage:"
|
||||
echo " $0 -c /path/to/config # Full backup run"
|
||||
echo " $0 [function] # Run individual function"
|
||||
echo " $0 help # Show available functions"
|
||||
exit 1
|
||||
}
|
||||
|
||||
CONFIG_FILE=""
|
||||
while getopts "c:" opt; do
|
||||
case "$opt" in
|
||||
c) CONFIG_FILE="$OPTARG" ;;
|
||||
*) usage ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Bei Einzelaufruf muss Konfiguration geladen sein
|
||||
shift $((OPTIND - 1))
|
||||
|
||||
if [[ -n "${CONFIG_FILE:-}" ]]; then
|
||||
if [[ ! -f "$CONFIG_FILE" ]]; then
|
||||
log "ERROR: Configuration file not found: $CONFIG_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! bash -n "$CONFIG_FILE"; then
|
||||
log "Syntaxfehler in Konfigurationsdatei $CONFIG_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
source "$CONFIG_FILE"
|
||||
|
||||
# ==========================
|
||||
# Konfigurationsprüfung
|
||||
# ==========================
|
||||
REQUIRED_VARS=(
|
||||
SOURCEPORT
|
||||
BACKUPSERVER
|
||||
ZSYNC
|
||||
MAINTDAY
|
||||
SHUTDOWN
|
||||
UPDATES
|
||||
SOURCEHOST
|
||||
ZFSROOT
|
||||
ZFSSECOND
|
||||
ZFSTRGT
|
||||
ZPUSHTAG
|
||||
ZPUSHMINKEEP
|
||||
ZPUSHKEEP
|
||||
ZPUSHLABEL
|
||||
PBSHOST
|
||||
BACKUPSTORE
|
||||
BACKUPSTOREPBS
|
||||
BACKUPEXCLUDE
|
||||
REPLEXCLUDE
|
||||
)
|
||||
|
||||
|
||||
MISSING_VARS=()
|
||||
for var in "${REQUIRED_VARS[@]}"; do
|
||||
#if [[ -z "${!var:-}" ]]; then
|
||||
if ! declare -p "$var" &>/dev/null || [[ -z "${!var}" ]]; then
|
||||
MISSING_VARS+=("$var")
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "${#MISSING_VARS[@]}" -ne 0 ]]; then
|
||||
log "Fehlende Konfigurationsvariablen:"
|
||||
for var in "${MISSING_VARS[@]}"; do
|
||||
log " - $var"
|
||||
done
|
||||
log "Breche ab – bitte Konfigurationsdatei prüfen."
|
||||
exit 1
|
||||
else
|
||||
log "Alle erforderlichen Konfigurationsvariablen sind gesetzt."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Funktionen
|
||||
#remote_ssh() {
|
||||
# ssh -p "$SOURCEPORT" "$@"
|
||||
#}
|
||||
|
||||
#remote_scp() {
|
||||
# scp -P "$SOURCEPORT" "$@"
|
||||
#}
|
||||
|
||||
set_wol_g_enabled() {
|
||||
log "Pruefe, ob ethtool installiert ist..."
|
||||
|
||||
if ! command -v ethtool >/dev/null 2>&1; then
|
||||
log "ethtool ist nicht installiert, versuche Installation..."
|
||||
apt update && apt install -y ethtool || {
|
||||
log "Fehler: ethtool konnte nicht installiert werden."
|
||||
return 1
|
||||
}
|
||||
else
|
||||
log "ethtool ist bereits installiert."
|
||||
fi
|
||||
|
||||
log "Pruefe und setze Wake-on-LAN (WOL) auf 'g' nur bei Interfaces mit statischer IP..."
|
||||
|
||||
for iface in $(ls /sys/class/net | grep -vE '^(lo|tap|vmbr|veth|br|docker|bond|wl)'); do
|
||||
if [[ -e "/sys/class/net/$iface/device" ]]; then
|
||||
log "Bearbeite physisches Interface: $iface"
|
||||
|
||||
# Aktuellen WOL-Status pruefen
|
||||
current_wol=$(ethtool "$iface" 2>/dev/null | awk '/Wake-on:/ {print $2}')
|
||||
if [[ "$current_wol" != "g" ]]; then
|
||||
log "Setze WOL auf 'g' fuer $iface..."
|
||||
ethtool -s "$iface" wol g || log "Fehler beim Setzen von WOL auf $iface"
|
||||
else
|
||||
log "WOL ist bereits korrekt auf 'g' fuer $iface"
|
||||
fi
|
||||
|
||||
# Pruefen, ob ein 'iface $iface inet static' Eintrag existiert
|
||||
if grep -qE "^\s*iface\s+$iface\s+inet\s+static" /etc/network/interfaces; then
|
||||
if ! grep -A 5 -E "^\s*iface\s+$iface\s+inet\s+static" /etc/network/interfaces | grep -q "post-up ethtool -s $iface wol g"; then
|
||||
log "Ergaenze WOL-Befehl im statischen Block fuer $iface..."
|
||||
sed -i "/^\s*iface\s\+$iface\s\+inet\s\+static/a \ post-up ethtool -s $iface wol g" /etc/network/interfaces
|
||||
else
|
||||
log "WOL-Befehl fuer $iface ist bereits im statischen Block vorhanden."
|
||||
fi
|
||||
else
|
||||
log "Kein statischer Eintrag fuer $iface gefunden, keine Aenderung vorgenommen."
|
||||
fi
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
write_zsync_config() {
|
||||
# Falls SOURCEHOSTNAME leer ist, ueber SSH vom Zielhost ermitteln
|
||||
if [[ -z "${SOURCEHOSTNAME:-}" ]]; then
|
||||
log "SOURCEHOSTNAME is empty retrieving via SSH from $SOURCEHOST..."
|
||||
SOURCEHOSTNAME=$(ssh -p "$SOURCEPORT" "$SOURCEHOST" hostname)
|
||||
log "Detected SOURCEHOSTNAME: $SOURCEHOSTNAME"
|
||||
fi
|
||||
local conf_file="/etc/bashclub/$SOURCEHOST.conf"
|
||||
log "Writing zsync config to $conf_file"
|
||||
{
|
||||
echo "target=$ZFSTRGT"
|
||||
echo "source=root@$SOURCEHOST"
|
||||
echo "sshport=$SOURCEPORT"
|
||||
echo "tag=$ZPUSHTAG"
|
||||
echo "snapshot_filter=\"$ZPUSHFILTER\""
|
||||
echo "min_keep=$ZPUSHMINKEEP"
|
||||
echo "zfs_auto_snapshot_keep=$ZPUSHKEEP"
|
||||
echo "zfs_auto_snapshot_label=$ZPUSHLABEL"
|
||||
echo "zfs_auto_snapshot_engine=internal"
|
||||
echo "checkzfs_disabled=0"
|
||||
echo "checkzfs_local=0"
|
||||
echo "checkzfs_prefix=miyagi-$SOURCEHOSTNAME-$(hostname)-$ZPUSHTAG"
|
||||
echo "checkzfs_max_age=1500,2000"
|
||||
echo "checkzfs_max_snapshot_count=180,200"
|
||||
echo "checkzfs_spool=1"
|
||||
echo "checkzfs_spool_maxage=90000"
|
||||
} > "$conf_file"
|
||||
}
|
||||
|
||||
run_zsync() {
|
||||
if [[ "$ZSYNC" != "no" ]]; then
|
||||
/usr/bin/bashclub-zsync -c "/etc/bashclub/$SOURCEHOST.conf"
|
||||
else
|
||||
log "Zsync is disabled"
|
||||
fi
|
||||
}
|
||||
|
||||
run_remote_updates() {
|
||||
if [[ "${UPDATES,,}" == "yes" ]]; then
|
||||
log "Führe Updates auf dem lokalen System aus..."
|
||||
apt update && apt dist-upgrade -y || {
|
||||
log "Fehler bei lokalen Updates"
|
||||
}
|
||||
|
||||
if [[ "${BACKUPSERVER,,}" == "yes" ]]; then
|
||||
log "Führe Updates auf dem PBSHOST ($PBSHOST) aus..."
|
||||
ssh root@"$PBSHOST" apt update && apt dist-upgrade -y || {
|
||||
log "Fehler bei Updates auf $PBSHOST"
|
||||
}
|
||||
else
|
||||
log "PBS-Updates übersprungen (BACKUPSERVER=$BACKUPSERVER)"
|
||||
fi
|
||||
else
|
||||
log "Updates deaktiviert (UPDATES=$UPDATES)"
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
send_piggyback_data() {
|
||||
# Falls SOURCEHOSTNAME leer ist, ueber SSH vom Zielhost ermitteln
|
||||
if [[ -z "${SOURCEHOSTNAME:-}" ]]; then
|
||||
log "SOURCEHOSTNAME is empty retrieving via SSH from $SOURCEHOST..."
|
||||
SOURCEHOSTNAME=$(ssh -p "$SOURCEPORT" "$SOURCEHOST" hostname)
|
||||
log "Detected SOURCEHOSTNAME: $SOURCEHOSTNAME"
|
||||
fi
|
||||
|
||||
local combined_host="miyagi-${SOURCEHOSTNAME}-$(hostname)"
|
||||
local filename="90000_${combined_host}"
|
||||
|
||||
log "Do nott forget to add a Host in CMK named: ${combined_host} (without Agent, Piggyback enabled)!"
|
||||
log "Creating piggyback file: $filename"
|
||||
|
||||
{
|
||||
echo "<<<<${combined_host}>>>>"
|
||||
/usr/bin/check_mk_agent
|
||||
echo "<<<<>>>>"
|
||||
} > "$filename"
|
||||
|
||||
if scp -P "$SOURCEPORT" "$filename" "$SOURCEHOST:/var/lib/check_mk_agent/spool/"; then
|
||||
log "Piggyback data successfully sent to $SOURCEHOST"
|
||||
else
|
||||
log "ERROR: Failed to send piggyback data to $SOURCEHOST"
|
||||
fi
|
||||
}
|
||||
|
||||
run_pbs_backup() {
|
||||
if [[ -z "${SOURCEHOSTNAME:-}" ]]; then
|
||||
SOURCEHOSTNAME=$(ssh -p "$SOURCEPORT" root@"$SOURCEHOST" hostname)
|
||||
fi
|
||||
|
||||
log "Running PBS vzdump job..."
|
||||
|
||||
# PBS-Storage ggf. aktivieren
|
||||
log "Checking if PBS storage '$BACKUPSTORE' is enabled on $SOURCEHOST..."
|
||||
if ssh -p "$SOURCEPORT" root@"$SOURCEHOST" "pvesm status | grep -w '$BACKUPSTORE' | grep -q 'disabled'"; then
|
||||
log "PBS storage '$BACKUPSTORE' is disabled. Attempting to enable... sleep 10 Sekunden"
|
||||
ssh -p "$SOURCEPORT" root@"$SOURCEHOST" "pvesm set '$BACKUPSTORE' --disable 0 && sleep 10"
|
||||
pbs_enabled_by_script=true
|
||||
else
|
||||
log "PBS storage '$BACKUPSTORE' is already enabled."
|
||||
pbs_enabled_by_script=false
|
||||
fi
|
||||
|
||||
vzdump_success=false
|
||||
|
||||
# Hauptversuch mit --pbs-change-detection-mode
|
||||
if ssh -p "$SOURCEPORT" root@"$SOURCEHOST" vzdump --pbs-change-detection-mode metadata \
|
||||
--node "$SOURCEHOSTNAME" --storage "$BACKUPSTORE" \
|
||||
--exclude "$BACKUPEXCLUDE" --mode snapshot --all 1 \
|
||||
--notes-template '{{guestname}}'; then
|
||||
|
||||
log "vzdump with change-detection-mode succeeded"
|
||||
vzdump_success=true
|
||||
|
||||
else
|
||||
log "Fallback: vzdump with change-detection-mode failed, trying without it..."
|
||||
|
||||
if ssh -p "$SOURCEPORT" root@"$SOURCEHOST" vzdump \
|
||||
--node "$SOURCEHOSTNAME" --storage "$BACKUPSTORE" \
|
||||
--exclude "$BACKUPEXCLUDE" --mode snapshot --all 1 \
|
||||
--notes-template '{{guestname}}'; then
|
||||
|
||||
log "Fallback vzdump succeeded"
|
||||
vzdump_success=true
|
||||
|
||||
else
|
||||
log "ERROR: vzdump failed even after fallback"
|
||||
fi
|
||||
fi
|
||||
|
||||
# PBS-Storage wieder deaktivieren, wenn zuvor aktiviert und erfolgreich
|
||||
if [[ "$vzdump_success" == true && "$pbs_enabled_by_script" == true ]]; then
|
||||
log "Disabling PBS storage '$BACKUPSTORE' again on $SOURCEHOST..."
|
||||
ssh -p "$SOURCEPORT" root@"$SOURCEHOST" "pvesm set '$BACKUPSTORE' --disable 1"
|
||||
fi
|
||||
|
||||
# Monitoring-Output für Checkmk
|
||||
if [[ "$vzdump_success" == true ]]; then
|
||||
echo "0 DailyPBS - Daily Backup" > /tmp/cmk_tmp.out
|
||||
else
|
||||
echo "2 DailyPBS - Daily Backup FAILED" > /tmp/cmk_tmp.out
|
||||
fi
|
||||
|
||||
( echo "<<<local>>>" ; cat /tmp/cmk_tmp.out ) > /tmp/90000_checkpbs
|
||||
scp -P "$SOURCEPORT" /tmp/90000_checkpbs root@"$SOURCEHOST":/var/lib/check_mk_agent/spool || log "Fehler beim SCP des Monitoring-Outputs"
|
||||
}
|
||||
|
||||
run_maintenance() {
|
||||
if [[ "$(date +%u)" == "$MAINTDAY" ]]; then
|
||||
log "Running maintenance..."
|
||||
PRUNEJOB=$(ssh "$PBSHOST" proxmox-backup-manager prune-job list --output-format json-pretty | grep -m 1 "id" | cut -d'"' -f4)
|
||||
ssh root@"$PBSHOST" proxmox-backup-manager prune-job run "$PRUNEJOB"
|
||||
ssh root@"$PBSHOST" proxmox-backup-manager garbage-collection start "$BACKUPSTOREPBS"
|
||||
ssh root@"$PBSHOST" proxmox-backup-manager verify backup
|
||||
else
|
||||
log "No maintenance scheduled for today."
|
||||
fi
|
||||
}
|
||||
|
||||
run_scrub_stop_src() {
|
||||
ssh -p "$SOURCEPORT" root@"$SOURCEHOST" 'for pool in $(zpool list -H -o name); do
|
||||
echo "Stoppe Scrub auf Pool: $pool"
|
||||
if zpool status "$pool" | grep -q "scrub in progress"; then
|
||||
if zpool scrub -s "$pool"; then
|
||||
echo "Scrub auf $pool gestoppt"
|
||||
else
|
||||
echo "Fehler beim Stoppen des Scrubs auf $pool"
|
||||
fi
|
||||
else
|
||||
echo "Kein aktiver Scrub auf $pool"
|
||||
fi
|
||||
done'
|
||||
}
|
||||
|
||||
run_scrub_stop_local() {
|
||||
for pool in $(zpool list -H -o name); do
|
||||
echo "Stoppe Scrub auf Pool: $pool"
|
||||
if zpool status "$pool" | grep -q "scrub in progress"; then
|
||||
if zpool scrub -s "$pool"; then
|
||||
echo "Scrub auf $pool gestoppt"
|
||||
else
|
||||
echo "Fehler beim Stoppen des Scrubs auf $pool"
|
||||
fi
|
||||
else
|
||||
echo "Kein aktiver Scrub auf $pool"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
shutdown_if_requested() {
|
||||
log "SHUTDOWN-Variable: '${SHUTDOWN:-nicht gesetzt}'"
|
||||
#if [[ "${SHUTDOWN,,}" == "yes" ]]; then
|
||||
if [[ "$(echo "$SHUTDOWN" | tr '[:upper:]' '[:lower:]')" == "yes" ]]; then
|
||||
send_piggyback_data
|
||||
send_piggyback_data_external
|
||||
log "Shutting down now..."
|
||||
shutdown now
|
||||
else
|
||||
log "No shutdown requested."
|
||||
fi
|
||||
}
|
||||
|
||||
send_piggyback_data_external() {
|
||||
if [[ "${EPIGGYBACK,,}" != "yes" ]]; then
|
||||
log "Externer Piggyback-Export deaktiviert."
|
||||
return
|
||||
fi
|
||||
|
||||
if [[ -z "$EPIGGYBACK_HOST" || -z "$EPIGGYBACK_PORT" ]]; then
|
||||
log "EPIGGYBACK_HOST oder EPIGGYBACK_PORT nicht gesetzt – Abbruch."
|
||||
return 1
|
||||
fi
|
||||
# Falls SOURCEHOSTNAME leer ist, ueber SSH vom Zielhost ermitteln
|
||||
if [[ -z "${SOURCEHOSTNAME:-}" ]]; then
|
||||
log "SOURCEHOSTNAME is empty retrieving via SSH from $SOURCEHOST..."
|
||||
SOURCEHOSTNAME=$(ssh -p "$SOURCEPORT" "$SOURCEHOST" hostname)
|
||||
log "Detected SOURCEHOSTNAME: $SOURCEHOSTNAME"
|
||||
fi
|
||||
|
||||
local combined_host="miyagi-${SOURCEHOSTNAME}-$(hostname)"
|
||||
local filename="90000_${combined_host}_external"
|
||||
|
||||
log "Erzeuge externe Piggyback-Datei: $filename"
|
||||
|
||||
{
|
||||
echo "<<<<${combined_host}>>>>"
|
||||
/usr/bin/check_mk_agent
|
||||
echo "<<<<>>>>"
|
||||
} > "$filename"
|
||||
|
||||
if scp -P "$EPIGGYBACK_PORT" "$filename" "$EPIGGYBACK_HOST:/var/lib/check_mk_agent/spool/"; then
|
||||
log "Piggyback-Daten erfolgreich an $EPIGGYBACK_HOST gesendet."
|
||||
else
|
||||
log "Fehler beim Senden der Piggyback-Daten an $EPIGGYBACK_HOST"
|
||||
fi
|
||||
|
||||
rm -f "$filename"
|
||||
}
|
||||
|
||||
send_checkzfs_output_external() {
|
||||
local config="/etc/bashclub/${SOURCEHOST}.conf"
|
||||
[[ ! -f "$config" ]] && { log "Konfig $config fehlt."; return 1; }
|
||||
source "$config"
|
||||
|
||||
[[ "${ECHECKZFS,,}" != "yes" ]] && { log "Externer Check-ZFS deaktiviert."; return; }
|
||||
|
||||
[[ -z "${SOURCEHOSTNAME:-}" ]] && {
|
||||
log "SOURCEHOSTNAME leer – ermittle via SSH..."
|
||||
SOURCEHOSTNAME=$(ssh -p "$SOURCEPORT" root@"$SOURCEHOST" hostname)
|
||||
log "Erkannt: $SOURCEHOSTNAME"
|
||||
}
|
||||
|
||||
local config="/etc/bashclub/${SOURCEHOST}.conf"
|
||||
[[ ! -f "$config" ]] && { log "Konfig $config fehlt."; return 1; }
|
||||
source "$config"
|
||||
|
||||
local CHECKZFS_CMD="${checkzfs_cmd:-$(command -v checkzfs)}"
|
||||
[[ -z "$CHECKZFS_CMD" || ! -x "$CHECKZFS_CMD" ]] && { log "checkzfs nicht gefunden."; return 1; }
|
||||
log "Verwende checkzfs unter $CHECKZFS_CMD"
|
||||
|
||||
local FILTER=""
|
||||
while IFS= read -r line; do
|
||||
local name value source
|
||||
name=$(echo "$line" | cut -f1)
|
||||
value=$(echo "$line" | cut -f2)
|
||||
source=$(echo "$line" | cut -f3)
|
||||
[[ "$source" == "local" && "$value" == "subvols" ]] && FILTER+="${name}/|"
|
||||
[[ "$source" == "local" && "$value" == "all" ]] && FILTER+="${name}|"
|
||||
done < <(ssh -p "$SOURCEPORT" "$SOURCEHOST" "zfs get -H -o name,value,source -t filesystem,volume $ZPUSHTAG")
|
||||
|
||||
FILTER="#${FILTER%|}"
|
||||
log "checkzfs_filter: $FILTER"
|
||||
|
||||
local TARGET="${ZFSTRGT:-}"
|
||||
local combined="miyagi-${SOURCEHOSTNAME}-$(hostname)"
|
||||
local spool="/tmp/${combined}_checkzfs.tmp"
|
||||
|
||||
log "Running: $CHECKZFS_CMD --source \root@"${SOURCEHOST}:${SOURCEPORT}\" --filter \"$FILTER\" --replicafilter \"^${TARGET}\" --prefix \"$checkzfs_prefix\" --threshold \"$checkzfs_max_age\" --maxsnapshots \"$checkzfs_max_snapshot_count\" --output checkmk"
|
||||
#log "Starte checkzfs mit Filter '$FILTER' und Ziel-Filter '^${TARGET}'"
|
||||
|
||||
local output
|
||||
output=$($CHECKZFS_CMD \
|
||||
--source root@"${SOURCEHOST}:${SOURCEPORT}" \
|
||||
--filter "$FILTER" \
|
||||
--replicafilter "^${TARGET}" \
|
||||
--prefix "$checkzfs_prefix" \
|
||||
--threshold "$checkzfs_max_age" \
|
||||
--maxsnapshots "$checkzfs_max_snapshot_count" \
|
||||
--output checkmk)
|
||||
|
||||
echo "$output" > "$spool"
|
||||
|
||||
local spool_dest="90000_${combined}_checkzfs_external"
|
||||
scp -P "$ECHECKZFS_PORT" "$spool" root@"$ECHECKZFS_HOST":/var/lib/check_mk_agent/spool/"$spool_dest" \
|
||||
&& log "Spool erfolgreich übertragen: $spool_dest" \
|
||||
|| log "Fehler beim Übertragen"
|
||||
|
||||
rm -f "$spool"
|
||||
}
|
||||
|
||||
|
||||
main() {
|
||||
log "Backup-Routine startet in 60 Sekunden..."
|
||||
sleep 60
|
||||
log "Starting full backup routine..."
|
||||
|
||||
log "Versuche Hostname von $SOURCEHOST via SSH (Port $SOURCEPORT) abzurufen..."
|
||||
if ! SOURCEHOSTNAME=$(ssh -p "$SOURCEPORT" -o ConnectTimeout=5 -o BatchMode=yes "$SOURCEHOST" hostname 2>/dev/null); then
|
||||
log "Error: Hostname von $SOURCEHOST konnte nicht abgerufen werden – Abbruch."
|
||||
exit 1
|
||||
fi
|
||||
log "Hostname von $SOURCEHOST: $SOURCEHOSTNAME"
|
||||
|
||||
set_wol_g_enabled
|
||||
write_zsync_config
|
||||
run_zsync
|
||||
send_checkzfs_output_external
|
||||
run_scrub_stop_local
|
||||
run_scrub_stop_src
|
||||
|
||||
if [[ "${BACKUPSERVER,,}" == "yes" ]]; then
|
||||
log "BACKUPSERVER ist aktiviert, führe Backup aus..."
|
||||
run_maintenance
|
||||
run_pbs_backup
|
||||
else
|
||||
log "BACKUPSERVER ist nicht aktiviert (BACKUPSERVER=$BACKUPSERVER), überspringe Backup."
|
||||
fi
|
||||
|
||||
run_remote_updates
|
||||
# run_updates
|
||||
|
||||
shutdown_if_requested
|
||||
}
|
||||
|
||||
# Funktionsbasierter Aufruf
|
||||
if [[ "${1:-}" == "help" ]]; then
|
||||
echo "Verfuegbare Funktionen:"
|
||||
declare -F | awk '{print " - " $3}' | grep -v "^ - _"
|
||||
exit 0
|
||||
elif [[ "${1:-}" =~ ^[a-zA-Z0-9_]+$ && "$(type -t "$1")" == "function" ]]; then
|
||||
FUNC="$1"
|
||||
shift
|
||||
"$FUNC" "$@"
|
||||
exit 0
|
||||
else
|
||||
main
|
||||
fi
|
||||
@@ -1,263 +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=(
|
||||
SOURCEPORT
|
||||
BACKUPSERVER
|
||||
ZSYNC
|
||||
MAINTDAY
|
||||
SHUTDOWN
|
||||
UPDATES
|
||||
SOURCEHOST
|
||||
EPIGGYBACK
|
||||
EPIGGYBACK_PORT
|
||||
EPIGGYBACK_HOST
|
||||
ECHECKZFS
|
||||
ECHECKZFS_PORT
|
||||
ECHECKZFS_HOST
|
||||
ZFSROOT
|
||||
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"
|
||||
local port="$2"
|
||||
LOG "Prüfe SSH-Verbindung zu $host:$port ..."
|
||||
if ssh -p "$port" -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
|
||||
}
|
||||
|
||||
run_host_check() {
|
||||
local host=$1
|
||||
local port=$2
|
||||
local do_pve_check=${3:-yes} # Standard: ja
|
||||
|
||||
LOG ""
|
||||
LOG "=== Prüfung für Host: $host:$port ==="
|
||||
if check_ssh_connection "$host" "$port"; then
|
||||
check_and_copy_ssh_key "$host" "$port"
|
||||
check_sshd_config_recommendation "$host" "$port"
|
||||
|
||||
if [[ "$do_pve_check" == "yes" ]]; then
|
||||
check_pveversion "$host" "$port"
|
||||
else
|
||||
LOG " PVE-Version-Check für $host übersprungen (nicht PVE)."
|
||||
fi
|
||||
fi
|
||||
echo ""
|
||||
}
|
||||
|
||||
|
||||
run_host_check "$SOURCEHOST"
|
||||
|
||||
if [[ "$BACKUPSERVER" == "yes" ]]; then
|
||||
run_host_check "$PBSHOST"
|
||||
else
|
||||
LOG " BACKUPSERVER ist deaktiviert PBSHOST wird übersprungen."
|
||||
fi
|
||||
|
||||
run_pbs_check
|
||||
|
||||
if [[ "${EPIGGYBACK,,}" == "yes" ]]; then
|
||||
run_host_check "$EPIGGYBACK_HOST"
|
||||
else
|
||||
LOG " Externer Piggyback-Host deaktiviert – überspringe."
|
||||
fi
|
||||
|
||||
if [[ "${ECHECKZFS,,}" == "yes" ]]; then
|
||||
run_host_check "$ECHECKZFS_HOST"
|
||||
else
|
||||
LOG " Externer Checkzfs-Host deaktiviert – überspringe."
|
||||
fi
|
||||
|
||||
|
||||
check_pbs_version() {
|
||||
local host=$1
|
||||
LOG "Prüfe PBS-Version auf $host ..."
|
||||
if ssh -p 22 "$host" "command -v proxmox-backup-server >/dev/null"; then
|
||||
ssh -p 22 "$host" "proxmox-backup-server version" | while read -r line; do
|
||||
LOG " $host: $line"
|
||||
done
|
||||
else
|
||||
LOG " 'proxmox-backup-server' ist auf $host nicht verfügbar – kein PBS?"
|
||||
fi
|
||||
}
|
||||
|
||||
run_pbs_check() {
|
||||
if [[ "${BACKUPSERVER,,}" != "yes" ]]; then
|
||||
LOG " BACKUPSERVER ist deaktiviert – PBSHOST wird übersprungen."
|
||||
return
|
||||
fi
|
||||
|
||||
local host=$PBSHOST
|
||||
LOG ""
|
||||
LOG "=== Prüfung für PBS-Host: $host ==="
|
||||
|
||||
if check_ssh_connection "$host"; then
|
||||
check_and_copy_ssh_key "$host"
|
||||
check_sshd_config_recommendation "$host"
|
||||
check_pbs_version "$host"
|
||||
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
|
||||
@@ -1,220 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
IFS=$'\n\t'
|
||||
|
||||
PERMITROOT_YES_HOSTS=()
|
||||
LOG() {
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"
|
||||
}
|
||||
|
||||
CONFIG_FILE="${1:-}"
|
||||
|
||||
if [[ -z "$CONFIG_FILE" ]]; then
|
||||
LOG "Keine Konfigurationsdatei übergeben."
|
||||
echo "Usage: $0 /pfad/zur/config"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -f "$CONFIG_FILE" ]]; then
|
||||
LOG "Konfigurationsdatei nicht gefunden: $CONFIG_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! bash -n "$CONFIG_FILE"; then
|
||||
LOG "Syntaxfehler in der Konfigurationsdatei!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
source "$CONFIG_FILE"
|
||||
|
||||
REQUIRED_VARS=(
|
||||
SOURCEPORT
|
||||
BACKUPSERVER
|
||||
ZSYNC
|
||||
MAINTDAY
|
||||
SHUTDOWN
|
||||
UPDATES
|
||||
SOURCEHOST
|
||||
PBSHOST
|
||||
BACKUPSTORE
|
||||
BACKUPSTOREPBS
|
||||
BACKUPEXCLUDE
|
||||
REPLEXCLUDE
|
||||
)
|
||||
|
||||
MISSING=()
|
||||
for var in "${REQUIRED_VARS[@]}"; do
|
||||
if ! declare -p "$var" &>/dev/null || [[ -z "${!var}" ]]; then
|
||||
MISSING+=("$var")
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ ${#MISSING[@]} -gt 0 ]]; then
|
||||
LOG " Fehlende Variablen in Konfiguration:"
|
||||
for v in "${MISSING[@]}"; do echo " - $v"; done
|
||||
exit 1
|
||||
else
|
||||
LOG " Alle Variablen gesetzt."
|
||||
fi
|
||||
|
||||
if [[ ${#MISSING[@]} -gt 0 ]]; then
|
||||
LOG " Fehlende Variablen in Konfiguration:"
|
||||
for v in "${MISSING[@]}"; do echo " - $v"; done
|
||||
exit 1
|
||||
else
|
||||
LOG " Alle Variablen gesetzt."
|
||||
fi
|
||||
|
||||
# Prüfung, ob SHUTDOWN auch tatsächlich in der Datei definiert wurde
|
||||
if ! grep -qE '^\s*SHUTDOWN\s*=' "$CONFIG_FILE"; then
|
||||
LOG "Hinweis: Die Variable 'SHUTDOWN' ist zwar gesetzt, aber nicht direkt in der Konfigurationsdatei '$CONFIG_FILE' definiert."
|
||||
LOG " → Bitte prüfen, ob dies gewollt ist oder von einer übergeordneten Quelle stammt."
|
||||
fi
|
||||
|
||||
# Anzeigen, ob SHUTDOWN aktiv ist
|
||||
LOG "SHUTDOWN-Status: ${SHUTDOWN:-nicht gesetzt}"
|
||||
|
||||
case "${SHUTDOWN,,}" in
|
||||
yes)
|
||||
LOG "Nach dem Backup wird das System heruntergefahren."
|
||||
;;
|
||||
no)
|
||||
LOG "Kein automatischer Shutdown nach dem Backup."
|
||||
;;
|
||||
*)
|
||||
LOG "Unbekannter SHUTDOWN-Wert: '${SHUTDOWN}' , erwartet: 'yes' oder 'no'"
|
||||
;;
|
||||
esac
|
||||
|
||||
check_ssh_connection() {
|
||||
local host=$1
|
||||
LOG "Prüfe SSH-Verbindung zu $host ..."
|
||||
if ssh -p "$SOURCEPORT" -o BatchMode=yes -o ConnectTimeout=5 "$host" "echo OK" 2>/dev/null | grep -q OK; then
|
||||
LOG " SSH-Verbindung zu $host erfolgreich."
|
||||
return 0
|
||||
else
|
||||
LOG " SSH-Verbindung zu $host fehlgeschlagen!"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
check_and_copy_ssh_key() {
|
||||
local host=$1
|
||||
local keyfile="$HOME/.ssh/id_rsa.pub"
|
||||
|
||||
if [[ ! -f "$keyfile" ]]; then
|
||||
LOG " Lokaler SSH-Public-Key ($keyfile) nicht gefunden!"
|
||||
return 1
|
||||
fi
|
||||
local pubkey
|
||||
pubkey=$(<"$keyfile")
|
||||
|
||||
LOG " Prüfe, ob SSH-Key auf $host autorisiert ist ..."
|
||||
|
||||
if ssh -p "$SOURCEPORT" "$host" "grep -qF '$pubkey' ~/.ssh/authorized_keys" 2>/dev/null; then
|
||||
LOG " SSH-Key ist bereits auf $host hinterlegt."
|
||||
else
|
||||
LOG " SSH-Key nicht auf $host vorhanden."
|
||||
|
||||
read -rp " Möchtest du den SSH-Key jetzt via ssh-copy-id übertragen? [j/N] " ans
|
||||
if [[ "$ans" =~ ^[JjYy]$ ]]; then
|
||||
ssh-copy-id -p "$SOURCEPORT" "$host"
|
||||
else
|
||||
LOG " SSH-Key nicht übertragen."
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
check_sshd_config_recommendation() {
|
||||
local host=$1
|
||||
LOG " Prüfe sshd_config auf $host bzgl. 'PermitRootLogin'..."
|
||||
|
||||
local current_setting
|
||||
current_setting=$(ssh -p "$SOURCEPORT" "$host" "grep -i '^PermitRootLogin' /etc/ssh/sshd_config" 2>/dev/null || echo "")
|
||||
|
||||
if [[ -z "$current_setting" ]]; then
|
||||
LOG " Keine explizite 'PermitRootLogin'-Einstellung gefunden."
|
||||
elif echo "$current_setting" | grep -qi "PermitRootLogin yes"; then
|
||||
LOG " Aktuell ist PermitRootLogin=YES erlaubt auf $host"
|
||||
LOG " Empfehlung: auf 'prohibit-password' umstellen."
|
||||
PERMITROOT_YES_HOSTS+=("$host")
|
||||
else
|
||||
LOG " PermitRootLogin-Einstellung ist: $current_setting"
|
||||
fi
|
||||
}
|
||||
|
||||
check_pveversion() {
|
||||
local host=$1
|
||||
LOG "Prüfe PVE-Version auf $host ..."
|
||||
if ssh -p "$SOURCEPORT" "$host" "command -v pveversion >/dev/null"; then
|
||||
ssh -p "$SOURCEPORT" "$host" "pveversion" | while read -r line; do
|
||||
LOG " $host: $line"
|
||||
done
|
||||
else
|
||||
LOG " 'pveversion' ist auf $host nicht verfügbar – kein Proxmox?"
|
||||
fi
|
||||
}
|
||||
check_pbs_version() {
|
||||
local host=$1
|
||||
local port=$2
|
||||
LOG "Prüfe PBS-Version auf $host ..."
|
||||
if ssh -p "$port" "$host" "command -v proxmox-backup-manager >/dev/null"; then
|
||||
ssh -p "$port" "$host" "proxmox-backup-manager version" | while read -r line; do
|
||||
LOG " $host: $line"
|
||||
done
|
||||
else
|
||||
LOG " 'proxmox-backup-manager' ist auf $host nicht verfügbar – kein PBS?"
|
||||
fi
|
||||
}
|
||||
run_host_check() {
|
||||
local host=$1
|
||||
local type=${2:-pve}
|
||||
local port=$SOURCEPORT
|
||||
local pbsport=$PBSPORT
|
||||
|
||||
LOG ""
|
||||
LOG "=== Prüfung für Host: $host (Typ: $type) ==="
|
||||
|
||||
if check_ssh_connection "$host" "$port"; then
|
||||
check_and_copy_ssh_key "$host"
|
||||
check_sshd_config_recommendation "$host"
|
||||
|
||||
if [[ "$type" == "pve" ]]; then
|
||||
check_pveversion "$host" "$port"
|
||||
elif [[ "$type" == "pbs" ]]; then
|
||||
check_pbs_version "$host" "$pbsport"
|
||||
else
|
||||
LOG " Unbekannter Host-Typ: $type"
|
||||
fi
|
||||
fi
|
||||
echo ""
|
||||
}
|
||||
|
||||
|
||||
|
||||
run_host_check "$SOURCEHOST" pve
|
||||
|
||||
if [[ "$BACKUPSERVER" == "yes" ]]; then
|
||||
run_host_check "$PBSHOST" pbs
|
||||
else
|
||||
LOG " BACKUPSERVER ist deaktiviert PBSHOST wird übersprungen."
|
||||
fi
|
||||
|
||||
# NACHLAUF: Frage zur automatischen Änderung von PermitRootLogin
|
||||
if [[ ${#PERMITROOT_YES_HOSTS[@]} -gt 0 ]]; then
|
||||
echo ""
|
||||
echo " Die folgenden Hosts erlauben derzeit root-Login per Passwort:"
|
||||
for h in "${PERMITROOT_YES_HOSTS[@]}"; do
|
||||
echo " - $h"
|
||||
done
|
||||
|
||||
read -rp " Möchtest du PermitRootLogin auf diesen Hosts auf 'prohibit-password' setzen und sshd neustarten? [j/N] " change_ans
|
||||
if [[ "$change_ans" =~ ^[JjYy]$ ]]; then
|
||||
for h in "${PERMITROOT_YES_HOSTS[@]}"; do
|
||||
echo "Ändere sshd_config auf $h ..."
|
||||
ssh -p "$SOURCEPORT" "$h" "sed -i 's/^PermitRootLogin.*/PermitRootLogin prohibit-password/' /etc/ssh/sshd_config && systemctl reload sshd && echo '✅ sshd auf $h neu geladen.' || echo '❌ Fehler bei $h'"
|
||||
done
|
||||
else
|
||||
echo " Änderung von sshd_config übersprungen."
|
||||
fi
|
||||
fi
|
||||
@@ -1,109 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
IFS=$'\n\t'
|
||||
|
||||
log() {
|
||||
echo "[INFO] $*"
|
||||
}
|
||||
|
||||
error_exit() {
|
||||
echo "[ERROR] $*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
sanitize_value() {
|
||||
echo "$1" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//'
|
||||
}
|
||||
|
||||
load_config() {
|
||||
local config_file="$1"
|
||||
if [[ ! -f "$config_file" ]]; then
|
||||
error_exit "Konfigurationsdatei nicht gefunden: $config_file"
|
||||
fi
|
||||
|
||||
log "Lade und bereinige Konfigurationsdatei: $config_file"
|
||||
while IFS='=' read -r key value; do
|
||||
# nur gültige Variablennamen parsen
|
||||
if [[ "$key" =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]]; then
|
||||
# Kommentar nach Wert entfernen
|
||||
value="${value%%#*}"
|
||||
value="$(sanitize_value "$value")"
|
||||
# Variable setzen (für den write_new_config Zugriff)
|
||||
eval "$key=\"$value\""
|
||||
fi
|
||||
done < "$config_file"
|
||||
}
|
||||
|
||||
write_new_config() {
|
||||
local out_file="$1"
|
||||
|
||||
cat > "$out_file" <<EOF
|
||||
#Edit all Variables for best Experience
|
||||
|
||||
UPDATES='${UPDATES}' # Proxmox VE und PBS Updates nach dem Lauf
|
||||
SHUTDOWN='${SHUTDOWN}' # System nach Ausführung herunterfahren?
|
||||
|
||||
# Quelle (Proxmox VE System, das gesichert wird)
|
||||
SOURCEPORT='${SSHPORT}' # 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"
|
||||
@@ -1,255 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Author: (C) 2025 Patrick Perlbach <patrick@perlbach24.de>
|
||||
# --- Konfiguration und Initiale Variablen ---
|
||||
FORCE_YES=0
|
||||
DRY_RUN=0
|
||||
CONFIG_FILE=""
|
||||
|
||||
# --- Hilfsfunktionen ---
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage: $0 -c <config> [--yes] [--dry-run]
|
||||
|
||||
-c <file> Pfad zur Konfigurationsdatei (Pflicht)
|
||||
--yes Automatische Ausführung ohne Rückfrage
|
||||
--dry-run Zeigt nur, was gemacht würde
|
||||
--help Diese Hilfe anzeigen
|
||||
EOF
|
||||
exit 1
|
||||
}
|
||||
|
||||
run_or_echo() {
|
||||
if [[ "$DRY_RUN" == "1" ]]; then
|
||||
echo "[DRY-RUN] $*"
|
||||
else
|
||||
eval "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
parse_args() {
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-c) CONFIG_FILE="$2"; shift 2 ;;
|
||||
--yes) FORCE_YES=1; shift ;;
|
||||
--dry-run) DRY_RUN=1; shift ;;
|
||||
--help) usage ;;
|
||||
*) echo "[ERROR] Unbekannter Parameter: $1"; usage ;;
|
||||
esac
|
||||
done
|
||||
|
||||
[[ -z "$CONFIG_FILE" || ! -f "$CONFIG_FILE" ]] && {
|
||||
echo "[ERROR] Konfigurationsdatei fehlt oder ungültig!"
|
||||
exit 1
|
||||
}
|
||||
|
||||
source "$CONFIG_FILE"
|
||||
}
|
||||
|
||||
confirm() {
|
||||
if [[ "$FORCE_YES" -eq 1 ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
while true; do
|
||||
read -rp "$1 [j/N]: " yn
|
||||
case $yn in
|
||||
[JjYy]*) return 0 ;;
|
||||
[Nn]*|"") return 1 ;;
|
||||
*) echo "Bitte j (ja) oder n (nein) eingeben." ;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
|
||||
install_cmk() {
|
||||
echo "[INFO] Lade cmk.deb herunter..."
|
||||
run_or_echo "curl -fsSL \"$CMK_URL\" -o \"$CMK_FILE\""
|
||||
|
||||
echo "[INFO] Installiere cmk.deb..."
|
||||
run_or_echo "sudo apt install -y \"$CMK_FILE\""
|
||||
}
|
||||
|
||||
install_fix_interface_names() {
|
||||
echo "[INFO] Installiere Fix für Interface-Namen..."
|
||||
run_or_echo "wget -O /tmp/debian-fix-interface-names.sh \"$FIX_INTERFACE_NAMES_URL\""
|
||||
run_or_echo "chmod +x /tmp/debian-fix-interface-names.sh"
|
||||
run_or_echo "bash /tmp/debian-fix-interface-names.sh"
|
||||
}
|
||||
|
||||
install_miyagi_scripts() {
|
||||
echo "[INFO] Lade Miyagi-Backup-Skripte..."
|
||||
run_or_echo "wget -O miyagi-backup.sh \"$MIYAGI_BACKUP_URL\""
|
||||
run_or_echo "wget -O miyagi-check.sh \"$MIYAGI_CHECK_URL\""
|
||||
run_or_echo "wget -O miyagi-convert.sh \"$MIYAGI_CONVERT_URL\""
|
||||
run_or_echo "mkdir -p /root/miyagi-backup"
|
||||
run_or_echo "mv miyagi-backup.sh miyagi-check.sh miyagi-convert.sh /root/miyagi-backup/"
|
||||
run_or_echo "chmod +x /root/miyagi-backup/*.sh"
|
||||
}
|
||||
|
||||
install_checkmk_plugins() {
|
||||
echo "[INFO] Installiere disk_smart_info Plugin..."
|
||||
run_or_echo "curl -fsSL \"$DISK_SMART_INFO_URL\" -o /usr/lib/check_mk_agent/plugins/disk_smart_info"
|
||||
run_or_echo "chmod +x /usr/lib/check_mk_agent/plugins/disk_smart_info"
|
||||
}
|
||||
|
||||
install_cleansnaps() {
|
||||
echo "[INFO] Installiere cleansnaps Skript..."
|
||||
run_or_echo "wget -O /usr/local/bin/cleansnaps \"$CLEANSNAPS_URL\""
|
||||
run_or_echo "chmod +x /usr/local/bin/cleansnaps"
|
||||
}
|
||||
|
||||
install_check_snapshot_age() {
|
||||
echo "[INFO] Installiere check-snapshot-age..."
|
||||
run_or_echo "wget -O /usr/local/bin/check-snapshot-age \"$CHECK_SNAPSHOT_AGE_URL\""
|
||||
run_or_echo "chmod +x /usr/local/bin/check-snapshot-age"
|
||||
}
|
||||
|
||||
install_checkzfs() {
|
||||
echo "[INFO] Installiere checkzfs..."
|
||||
run_or_echo "wget -O /usr/local/bin/checkzfs \"$CHECKZFS_URL\""
|
||||
run_or_echo "chmod +x /usr/local/bin/checkzfs"
|
||||
}
|
||||
|
||||
check_ssh() {
|
||||
echo "[INFO] Teste SSH-Verbindung zu $SOURCEHOST ..."
|
||||
|
||||
if ssh -p "$SOURCEPORT" -o BatchMode=yes -o ConnectTimeout=5 root@"$SOURCEHOST" "echo Verbindung erfolgreich" 2>/dev/null; then
|
||||
echo "[OK] SSH-Verbindung über Schlüssel funktioniert."
|
||||
else
|
||||
echo "[WARN] Keine Schlüsselbasierte SSH-Verbindung möglich oder abgelehnt."
|
||||
|
||||
echo "[HINWEIS] Eine Passwortabfrage erfolgt jetzt – dies ist normal."
|
||||
if ssh -p "$SOURCEPORT" root@"$SOURCEHOST" "echo Verbindung erfolgreich"; then
|
||||
echo "[OK] SSH-Verbindung per Passwort erfolgreich."
|
||||
|
||||
if confirm "Möchtest du den lokalen SSH-Schlüssel auf $SOURCEHOST kopieren (per ssh-copy-id)?"; then
|
||||
run_or_echo "ssh-copy-id -p \"$SOURCEPORT\" root@\"$SOURCEHOST\""
|
||||
echo "[INFO] Bitte erneut starten, um die Schlüsselverbindung zu nutzen."
|
||||
exit 0
|
||||
else
|
||||
echo "[HINWEIS] Es wird weiterhin Passwortabfrage benötigt."
|
||||
fi
|
||||
else
|
||||
echo "[FEHLER] SSH-Verbindung nicht möglich – bitte Zugang prüfen!"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
select_zfs_pools() {
|
||||
echo "[INFO] Lade ZFS-Datasets vom Remote-Host..."
|
||||
|
||||
LOCAL_HOSTNAME=$(hostname -s)
|
||||
REMOTE_HOSTNAME=$(ssh -p "$SOURCEPORT" root@"$SOURCEHOST" "hostname -s")
|
||||
TAG_KEY="bashclub:miyagi-${REMOTE_HOSTNAME}-${LOCAL_HOSTNAME}"
|
||||
TAG_VALUE="subvols"
|
||||
|
||||
# Hole alle relevanten Pool-Namen
|
||||
mapfile -t pools < <(ssh -p "$SOURCEPORT" root@"$SOURCEHOST" \
|
||||
"zfs list -H -o name" | \
|
||||
grep -E '/(vm|subvol)-' | \
|
||||
grep -viE 'repl|replica' | \
|
||||
sed 's:/[^/]*$::' | \
|
||||
sort -u)
|
||||
|
||||
if [[ ${#pools[@]} -eq 0 ]]; then
|
||||
echo "[WARN] Keine geeigneten Datasets gefunden!"
|
||||
return
|
||||
fi
|
||||
|
||||
local options=()
|
||||
local tagged_pools=()
|
||||
|
||||
for pool in "${pools[@]}"; do
|
||||
local tag status
|
||||
tag=$(ssh -p "$SOURCEPORT" root@"$SOURCEHOST" zfs get -H -o value "$TAG_KEY" "$pool" 2>/dev/null)
|
||||
if [[ "$tag" == "$TAG_VALUE" ]]; then
|
||||
status="on"
|
||||
tagged_pools+=("$pool")
|
||||
else
|
||||
status="off"
|
||||
fi
|
||||
options+=("$pool" "$([[ "$status" == "on" ]] && echo 'TAGGED' || echo 'untagged')" "$status")
|
||||
done
|
||||
|
||||
local selected
|
||||
selected=$(whiptail --title "ZFS Pools auswählen" \
|
||||
--checklist "Wähle Datasets zum Taggen aus:" 20 78 12 \
|
||||
"${options[@]}" \
|
||||
3>&1 1>&2 2>&3)
|
||||
|
||||
if [[ $? -ne 0 ]]; then
|
||||
echo "[INFO] Auswahl abgebrochen."
|
||||
return
|
||||
fi
|
||||
|
||||
local selected_array=()
|
||||
eval "selected_array=($selected)"
|
||||
|
||||
echo "[INFO] Verarbeite Tagging für ausgewählte Pools:"
|
||||
|
||||
for ((i = 0; i < ${#options[@]}; i += 3)); do
|
||||
local pool="${options[i]}"
|
||||
local prev_status="${options[i+2]}"
|
||||
local is_selected=0
|
||||
|
||||
for sel in "${selected_array[@]}"; do
|
||||
if [[ "$sel" == "$pool" ]]; then
|
||||
is_selected=1
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "$is_selected" -eq 1 && "$prev_status" == "off" ]]; then
|
||||
echo " → Tagge $pool"
|
||||
run_or_echo ssh -p "$SOURCEPORT" root@"$SOURCEHOST" zfs set "$TAG_KEY=$TAG_VALUE" "$pool"
|
||||
elif [[ "$is_selected" -eq 0 && "$prev_status" == "on" ]]; then
|
||||
echo " → Entferne Tag von $pool"
|
||||
run_or_echo ssh -p "$SOURCEPORT" root@"$SOURCEHOST" zfs inherit "$TAG_KEY" "$pool"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
main() {
|
||||
# URLs und Dateipfade als Variablen, ggf. in Kopfbereich auslagern
|
||||
CMK_URL="https://nc.sysops.de/index.php/s/YofRT5LBfX5ZDQs/download/cmk.deb"
|
||||
CMK_FILE="/tmp/cmk.deb"
|
||||
|
||||
FIX_INTERFACE_NAMES_URL="https://raw.githubusercontent.com/bashclub/trmm-scripts/refs/heads/main/debian-fix-interface-names.sh"
|
||||
|
||||
MIYAGI_BACKUP_URL="https://gitea.perlbach24.de/scripte/miyagi-backup/raw/branch/main/miyagi-backup.sh"
|
||||
MIYAGI_CHECK_URL="https://gitea.perlbach24.de/scripte/miyagi-backup/raw/branch/main/miyagi-check.sh"
|
||||
MIYAGI_CONVERT_URL="https://gitea.perlbach24.de/scripte/miyagi-backup/raw/branch/main/miyagi-convert.sh"
|
||||
|
||||
DISK_SMART_INFO_URL="https://raw.githubusercontent.com/bashclub/checkmk-smart/main/disk_smart_info.py"
|
||||
|
||||
CLEANSNAPS_URL="https://raw.githubusercontent.com/bashclub/zfs-housekeeping/refs/heads/main/cleansnaps.sh"
|
||||
|
||||
CHECK_SNAPSHOT_AGE_URL="https://gitea.perlbach24.de/scripte/check-zfs-replication/raw/branch/main/check-snapshot-age"
|
||||
|
||||
CHECKZFS_URL="https://gitea.perlbach24.de/scripte/check-zfs-replication/raw/branch/main/checkzfs.py"
|
||||
|
||||
echo "[INFO] Starte Installation mit Konfig: $CONFIG_FILE"
|
||||
|
||||
install_cmk
|
||||
|
||||
install_fix_interface_names
|
||||
|
||||
install_miyagi_scripts
|
||||
|
||||
install_checkmk_plugins
|
||||
|
||||
install_cleansnaps
|
||||
|
||||
install_check_snapshot_age
|
||||
|
||||
install_checkzfs
|
||||
check_ssh
|
||||
select_zfs_pools
|
||||
|
||||
echo "[OK] Installation abgeschlossen."
|
||||
}
|
||||
|
||||
# Start
|
||||
parse_args "$@"
|
||||
main
|
||||
@@ -1,24 +0,0 @@
|
||||
# testing – Dev Log
|
||||
|
||||
## 2026-06-18 23:57 – 23:59 (2m)
|
||||
**Beschreibung:** Claude Code Session
|
||||
**Projekt:** miyagi-backup
|
||||
|
||||
### Commits
|
||||
Keine Commits in dieser Session.
|
||||
|
||||
### Geänderte Dateien
|
||||
- config.example | 2 +-
|
||||
|
||||
---
|
||||
## 2026-06-19 00:04 – 00:04 (0m)
|
||||
**Beschreibung:** Claude Code Session
|
||||
**Projekt:** testing
|
||||
|
||||
### Commits
|
||||
Keine Commits in dieser Session.
|
||||
|
||||
### Geänderte Dateien
|
||||
- config.example | 2 +-
|
||||
|
||||
---
|
||||
@@ -1,254 +0,0 @@
|
||||
#!/bin/bash
|
||||
# ==========================================
|
||||
# Miyagi Erweiterungs-Installer mit dialog GUI
|
||||
# ==========================================
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# -------------------------------
|
||||
# 1️⃣ Argumente prüfen (Config laden)
|
||||
# -------------------------------
|
||||
CONFIG_FILE=""
|
||||
|
||||
while getopts "c:" opt; do
|
||||
case "$opt" in
|
||||
c)
|
||||
CONFIG_FILE="$OPTARG"
|
||||
;;
|
||||
*)
|
||||
echo "Verwendung: $0 [-c <configdatei>]"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -n "$CONFIG_FILE" ]]; then
|
||||
if [[ -f "$CONFIG_FILE" ]]; then
|
||||
echo "[INFO] Lade Konfigurationsdatei: $CONFIG_FILE"
|
||||
# shellcheck disable=SC1090
|
||||
source "$CONFIG_FILE"
|
||||
else
|
||||
echo "[WARN] Konfigurationsdatei '$CONFIG_FILE' nicht gefunden!"
|
||||
fi
|
||||
fi
|
||||
|
||||
# -------------------------------
|
||||
# 2️⃣ Voraussetzungen prüfen
|
||||
# -------------------------------
|
||||
if ! command -v dialog &>/dev/null; then
|
||||
echo "[INFO] Installiere 'dialog' für das Erweiterungsmenü..."
|
||||
apt update && apt install -y dialog
|
||||
fi
|
||||
|
||||
# -------------------------------
|
||||
# 3️⃣ ZFS-Pool-Tagging Funktion
|
||||
# -------------------------------
|
||||
zfs_pool_tagging() {
|
||||
echo "[INFO] Starte ZFS-Pool-Tagging..."
|
||||
|
||||
# Host & Port abfragen, falls nicht in Config gesetzt
|
||||
if [[ -z "${SOURCEHOST:-}" ]]; then
|
||||
read -rp "[EINGABE] Remote-Host (z. B. 192.168.1.10): " SOURCEHOST
|
||||
fi
|
||||
|
||||
if [[ -z "${SOURCEPORT:-}" ]]; then
|
||||
read -rp "[EINGABE] SSH-Port [Standard: 22]: " SOURCEPORT
|
||||
SOURCEPORT=${SOURCEPORT:-22}
|
||||
fi
|
||||
|
||||
echo "[INFO] Lade ZFS-Datasets vom Remote-Host..."
|
||||
|
||||
LOCAL_HOSTNAME=$(hostname -s)
|
||||
REMOTE_HOSTNAME=$(ssh -p "$SOURCEPORT" root@"$SOURCEHOST" "hostname -s" 2>/dev/null)
|
||||
|
||||
if [[ -z "$REMOTE_HOSTNAME" ]]; then
|
||||
echo "[FEHLER] Remote-Host konnte nicht erreicht werden!"
|
||||
return 1
|
||||
fi
|
||||
|
||||
TAG_KEY="bashclub:miyagi-${REMOTE_HOSTNAME}-${LOCAL_HOSTNAME}"
|
||||
TAG_VALUE="subvols"
|
||||
|
||||
# Hole Dataset-Namen
|
||||
mapfile -t pools < <(ssh -p "$SOURCEPORT" root@"$SOURCEHOST" \
|
||||
"zfs list -H -o name" | \
|
||||
grep -E '/(vm|subvol)-' | \
|
||||
grep -viE 'repl|replica' | \
|
||||
sed 's:/[^/]*$::' | \
|
||||
sort -u)
|
||||
|
||||
if [[ ${#pools[@]} -eq 0 ]]; then
|
||||
echo "[WARN] Keine geeigneten Datasets gefunden!"
|
||||
return
|
||||
fi
|
||||
|
||||
local options=()
|
||||
for pool in "${pools[@]}"; do
|
||||
local tag status
|
||||
tag=$(ssh -p "$SOURCEPORT" root@"$SOURCEHOST" zfs get -H -o value "$TAG_KEY" "$pool" 2>/dev/null)
|
||||
if [[ "$tag" == "$TAG_VALUE" ]]; then
|
||||
status="on"
|
||||
else
|
||||
status="off"
|
||||
fi
|
||||
options+=("$pool" "$([[ "$status" == "on" ]] && echo 'TAGGED' || echo 'untagged')" "$status")
|
||||
done
|
||||
|
||||
local selected_array=()
|
||||
|
||||
# Auswahl-Dialog
|
||||
if command -v whiptail >/dev/null; then
|
||||
local selected
|
||||
selected=$(whiptail --title "ZFS Pools auswählen" \
|
||||
--checklist "Wähle Datasets zum Taggen aus:" 20 78 12 \
|
||||
"${options[@]}" \
|
||||
3>&1 1>&2 2>&3)
|
||||
[[ $? -ne 0 ]] && echo "[INFO] Auswahl abgebrochen." && return
|
||||
eval "selected_array=($selected)"
|
||||
elif command -v dialog >/dev/null; then
|
||||
local selected
|
||||
selected=$(dialog --clear \
|
||||
--backtitle "ZFS Tagging" \
|
||||
--title "ZFS Pools auswählen" \
|
||||
--checklist "Wähle Datasets zum Taggen aus:" 20 78 12 \
|
||||
"${options[@]}" \
|
||||
3>&1 1>&2 2>&3)
|
||||
clear
|
||||
[[ $? -ne 0 ]] && echo "[INFO] Auswahl abgebrochen." && return
|
||||
eval "selected_array=($selected)"
|
||||
else
|
||||
echo "[INFO] Kein dialog oder whiptail gefunden – Textmodus aktiv."
|
||||
echo "[INFO] Vorhandene Pools:"
|
||||
for i in "${!pools[@]}"; do
|
||||
echo " [$i] ${pools[i]}"
|
||||
done
|
||||
echo
|
||||
read -rp "Gib die Nummern der Pools an (mit Leerzeichen): " user_input
|
||||
for index in $user_input; do
|
||||
selected_array+=("${pools[index]}")
|
||||
done
|
||||
fi
|
||||
|
||||
echo "[INFO] Verarbeite Tagging..."
|
||||
for ((i = 0; i < ${#options[@]}; i += 3)); do
|
||||
local pool="${options[i]}"
|
||||
local prev_status="${options[i+2]}"
|
||||
local is_selected=0
|
||||
|
||||
for sel in "${selected_array[@]}"; do
|
||||
if [[ "$sel" == "$pool" ]]; then
|
||||
is_selected=1
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "$is_selected" -eq 1 && "$prev_status" == "off" ]]; then
|
||||
echo " → Tagge $pool"
|
||||
ssh -p "$SOURCEPORT" root@"$SOURCEHOST" zfs set "$TAG_KEY=$TAG_VALUE" "$pool"
|
||||
elif [[ "$is_selected" -eq 0 && "$prev_status" == "on" ]]; then
|
||||
echo " → Entferne Tag von $pool"
|
||||
ssh -p "$SOURCEPORT" root@"$SOURCEHOST" zfs inherit "$TAG_KEY" "$pool"
|
||||
fi
|
||||
done
|
||||
|
||||
echo "[FERTIG] ZFS-Tagging abgeschlossen."
|
||||
}
|
||||
|
||||
# -------------------------------
|
||||
# 4️⃣ Hauptmenü anzeigen
|
||||
# -------------------------------
|
||||
HEIGHT=20
|
||||
WIDTH=60
|
||||
CHOICE_HEIGHT=10
|
||||
BACKTITLE="Checkmk & ZFS Setup"
|
||||
TITLE="Optionale Erweiterungen"
|
||||
MENU="Wähle gewünschte Zusatzfunktionen:"
|
||||
|
||||
OPTIONS=(
|
||||
1 "Installiere Miyagi-Backup-Skripte" on
|
||||
2 "Installiere disk_smart_info Plugin" on
|
||||
3 "Installiere cleansnaps (ZFS)" on
|
||||
4 "Installiere check-snapshot-age (ZFS)" on
|
||||
5 "Installiere checkzfs (ZFS)" on
|
||||
6 "Starte ZFS-Pool-Tagging" off
|
||||
)
|
||||
|
||||
CHOICES=$(dialog --clear \
|
||||
--backtitle "$BACKTITLE" \
|
||||
--title "$TITLE" \
|
||||
--checklist "$MENU" \
|
||||
$HEIGHT $WIDTH $CHOICE_HEIGHT \
|
||||
"${OPTIONS[@]}" \
|
||||
3>&1 1>&2 2>&3)
|
||||
|
||||
clear
|
||||
|
||||
if [[ -z "$CHOICES" ]]; then
|
||||
echo "[INFO] Keine Erweiterungen ausgewählt. Beende Setup."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# -------------------------------
|
||||
# 5️⃣ Zusammenfassung anzeigen
|
||||
# -------------------------------
|
||||
SUMMARY_TEXT="Du hast folgende Erweiterungen ausgewählt:\n"
|
||||
|
||||
for CHOICE in $CHOICES; do
|
||||
case $CHOICE in
|
||||
1) SUMMARY_TEXT+="\n - Miyagi-Backup-Skripte" ;;
|
||||
2) SUMMARY_TEXT+="\n - disk_smart_info Plugin" ;;
|
||||
3) SUMMARY_TEXT+="\n - cleansnaps (ZFS)" ;;
|
||||
4) SUMMARY_TEXT+="\n - check-snapshot-age (ZFS)" ;;
|
||||
5) SUMMARY_TEXT+="\n - checkzfs (ZFS)" ;;
|
||||
6) SUMMARY_TEXT+="\n - ZFS-Pool-Tagging starten" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
dialog --clear \
|
||||
--backtitle "Checkmk & Erweiterungen" \
|
||||
--title "Zusammenfassung" \
|
||||
--yesno "$SUMMARY_TEXT\n\nMöchtest du die Einrichtung jetzt starten?" \
|
||||
18 70
|
||||
|
||||
response=$?
|
||||
clear
|
||||
[[ $response -ne 0 ]] && echo "[ABBRUCH] Installation abgebrochen." && exit 0
|
||||
|
||||
# -------------------------------
|
||||
# 6️⃣ Ausgewählte Erweiterungen installieren
|
||||
# -------------------------------
|
||||
for CHOICE in $CHOICES; do
|
||||
case $CHOICE in
|
||||
1)
|
||||
echo "[INSTALL] Miyagi-Backup-Skripte..."
|
||||
wget -q --no-cache -O miyagi-backup.sh https://gitea.perlbach24.de/scripte/miyagi-backup/raw/branch/main/miyagi-backup.sh
|
||||
wget -q --no-cache -O miyagi-check.sh https://gitea.perlbach24.de/scripte/miyagi-backup/raw/branch/main/miyagi-check.sh
|
||||
wget -q --no-cache -O miyagi-convert.sh https://gitea.perlbach24.de/scripte/miyagi-backup/raw/branch/main/miyagi-convert.sh
|
||||
;;
|
||||
2)
|
||||
echo "[INSTALL] disk_smart_info Plugin..."
|
||||
curl -fsSL https://raw.githubusercontent.com/bashclub/checkmk-smart/main/disk_smart_info.py -o /usr/lib/check_mk_agent/plugins/disk_smart_info
|
||||
chmod +x /usr/lib/check_mk_agent/plugins/disk_smart_info
|
||||
;;
|
||||
3)
|
||||
echo "[INSTALL] cleansnaps..."
|
||||
wget -q --no-cache -O /usr/local/bin/cleansnaps https://raw.githubusercontent.com/bashclub/zfs-housekeeping/refs/heads/main/cleansnaps.sh
|
||||
chmod +x /usr/local/bin/cleansnaps
|
||||
;;
|
||||
4)
|
||||
echo "[INSTALL] check-snapshot-age..."
|
||||
wget -q --no-cache -O /usr/local/bin/check-snapshot-age https://gitea.perlbach24.de/scripte/check-zfs-replication/raw/branch/main/check-snapshot-age
|
||||
chmod +x /usr/local/bin/check-snapshot-age
|
||||
;;
|
||||
5)
|
||||
echo "[INSTALL] checkzfs..."
|
||||
wget -q --no-cache -O /usr/local/bin/checkzfs https://gitea.perlbach24.de/scripte/check-zfs-replication/raw/branch/main/checkzfs.py
|
||||
chmod +x /usr/local/bin/checkzfs
|
||||
;;
|
||||
6)
|
||||
zfs_pool_tagging
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
echo "[FERTIG] Alle gewählten Komponenten wurden erfolgreich installiert."
|
||||
@@ -1,772 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -uo pipefail
|
||||
IFS=$'\n\t'
|
||||
|
||||
PATH="/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin"
|
||||
SCRIPT_NAME=$(basename "$0")
|
||||
LOGFILE="/var/log/${SCRIPT_NAME%.sh}.log"
|
||||
|
||||
log() {
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOGFILE"
|
||||
}
|
||||
|
||||
usage() {
|
||||
echo "Usage:"
|
||||
echo " $0 -c /path/to/config # Full backup run"
|
||||
echo " $0 [function] # Run individual function"
|
||||
echo " $0 help # Show available functions"
|
||||
exit 1
|
||||
}
|
||||
|
||||
CONFIG_FILE=""
|
||||
while getopts "c:" opt; do
|
||||
case "$opt" in
|
||||
c) CONFIG_FILE="$OPTARG" ;;
|
||||
*) usage ;;
|
||||
esac
|
||||
done
|
||||
shift $((OPTIND - 1))
|
||||
|
||||
if [[ -n "${CONFIG_FILE:-}" ]]; then
|
||||
if [[ ! -f "$CONFIG_FILE" ]]; then
|
||||
log "ERROR: Configuration file not found: $CONFIG_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! bash -n "$CONFIG_FILE"; then
|
||||
log "Syntax error in configuration file $CONFIG_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
source "$CONFIG_FILE"
|
||||
|
||||
REQUIRED_VARS=(
|
||||
SOURCEPORT BACKUPSERVER ZSYNC MAINTDAY SHUTDOWN UPDATES
|
||||
SOURCEHOST ZFSTRGT ZPUSHTAG ZPUSHMINKEEP ZPUSHKEEP ZPUSHLABEL
|
||||
PBSHOST PBSPORT BACKUPSTORE BACKUPSTOREPBS BACKUPEXCLUDE REPLEXCLUDE DYNROUTE REBOOT DDNS_GATEWAY DDNS_SERVER
|
||||
)
|
||||
|
||||
if [[ "${DYNROUTE,,}" == "yes" ]]; then
|
||||
REQUIRED_VARS+=(DDNS_GATEWAY)
|
||||
fi
|
||||
|
||||
MISSING_VARS=()
|
||||
for var in "${REQUIRED_VARS[@]}"; do
|
||||
if [[ -z "${!var:-}" ]]; then
|
||||
MISSING_VARS+=("$var")
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "${#MISSING_VARS[@]}" -gt 0 ]]; then
|
||||
log "Missing configuration variables:"
|
||||
for var in "${MISSING_VARS[@]}"; do
|
||||
log " - $var"
|
||||
done
|
||||
log "Aborting — please check your configuration file."
|
||||
exit 1
|
||||
else
|
||||
log "All required configuration variables are set."
|
||||
fi
|
||||
fi
|
||||
|
||||
rssh() { ssh -o BatchMode=yes -o ConnectTimeout=10 -p "$SOURCEPORT" "$@"; }
|
||||
rscp() { scp -P "$SOURCEPORT" "$@"; }
|
||||
|
||||
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() {
|
||||
if [[ -z "${SOURCEHOSTNAME:-}" ]]; then
|
||||
log "SOURCEHOSTNAME is empty, retrieving via SSH from $SOURCEHOST..."
|
||||
SOURCEHOSTNAME=$(rssh "$SOURCEHOST" hostname)
|
||||
log "Detected SOURCEHOSTNAME: $SOURCEHOSTNAME"
|
||||
fi
|
||||
}
|
||||
|
||||
zfs_replace() {
|
||||
local status
|
||||
status=$(zpool status)
|
||||
|
||||
if echo "$status" | grep -q 'scan: resilver in progress'; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if echo "$status" | grep -qE 'replacing-[0-9]'; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
wait_replace() {
|
||||
local interval_seconds=300 # Alle 5 Minuten prüfen
|
||||
local waited_minutes=0
|
||||
|
||||
log "ZFS Replace-Vorgang erkannt – warte unbegrenzt auf Abschluss..."
|
||||
|
||||
while true; do
|
||||
if ! zfs_replace; then
|
||||
log "Replace abgeschlossen nach $waited_minutes Minuten – fahre jetzt herunter."
|
||||
return 0
|
||||
fi
|
||||
log "Replace läuft noch – erneut prüfen in $((interval_seconds/60)) Minuten..."
|
||||
sleep "$interval_seconds"
|
||||
((waited_minutes+=interval_seconds/60))
|
||||
done
|
||||
}
|
||||
|
||||
set_wol_g_enabled() {
|
||||
log "Checking if ethtool is installed..."
|
||||
|
||||
if ! command -v ethtool >/dev/null 2>&1; then
|
||||
log "ethtool is not installed, attempting installation..."
|
||||
apt update && apt install -y ethtool || {
|
||||
log "Error: ethtool could not be installed."
|
||||
return 1
|
||||
}
|
||||
else
|
||||
log "ethtool is already installed."
|
||||
fi
|
||||
|
||||
log "Setting Wake-on-LAN (WOL) to 'g' on interfaces with static IP..."
|
||||
|
||||
for iface in $(ls /sys/class/net | grep -vE '^(lo|tap|vmbr|veth|br|docker|bond|wl)'); do
|
||||
if [[ -e "/sys/class/net/$iface/device" ]]; then
|
||||
log "Processing physical interface: $iface"
|
||||
|
||||
current_wol=$(ethtool "$iface" 2>/dev/null | awk '/Wake-on:/ {print $2}')
|
||||
if [[ "$current_wol" != "g" ]]; then
|
||||
log "Setting WOL to 'g' for $iface..."
|
||||
ethtool -s "$iface" wol g || log "Error setting WOL on $iface"
|
||||
else
|
||||
log "WOL already set to 'g' for $iface"
|
||||
fi
|
||||
|
||||
if grep -qE "^\s*iface\s+$iface\s+inet\s+static" /etc/network/interfaces; then
|
||||
if ! grep -A 5 -E "^\s*iface\s+$iface\s+inet\s+static" /etc/network/interfaces | grep -q "post-up ethtool -s $iface wol g"; then
|
||||
log "Adding WOL command in static block for $iface..."
|
||||
sed -i "/^\s*iface\s\+$iface\s\+inet\s\+static/a \ post-up ethtool -s $iface wol g" /etc/network/interfaces
|
||||
else
|
||||
log "WOL command already present in static block for $iface."
|
||||
fi
|
||||
else
|
||||
log "No static entry found for $iface, no changes made."
|
||||
fi
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
write_zsync_config() {
|
||||
get_sourcehostname
|
||||
local conf_file="/etc/bashclub/$SOURCEHOST.conf"
|
||||
log "Writing zsync config to $conf_file"
|
||||
{
|
||||
echo "target=$ZFSTRGT"
|
||||
echo "source=root@$SOURCEHOST"
|
||||
echo "sshport=$SOURCEPORT"
|
||||
echo "tag=$ZPUSHTAG"
|
||||
echo "snapshot_filter=\"$ZPUSHFILTER\""
|
||||
echo "min_keep=$ZPUSHMINKEEP"
|
||||
echo "zfs_auto_snapshot_keep=$ZPUSHKEEP"
|
||||
echo "zfs_auto_snapshot_label=zfs-auto-snap_$ZPUSHLABEL"
|
||||
echo "zfs_auto_snapshot_engine=internal"
|
||||
echo "checkzfs_disabled=0"
|
||||
echo "checkzfs_local=0"
|
||||
echo "checkzfs_prefix=miyagi-$SOURCEHOSTNAME-$(hostname)-$ZPUSHTAG"
|
||||
echo "checkzfs_max_age=1500,2000"
|
||||
echo "checkzfs_max_snapshot_count=180,200"
|
||||
echo "checkzfs_spool=1"
|
||||
echo "checkzfs_spool_maxage=100000"
|
||||
} > "$conf_file"
|
||||
}
|
||||
|
||||
run_zsync() {
|
||||
write_zsync_config
|
||||
if [[ "$ZSYNC" != "no" ]]; then
|
||||
/usr/bin/bashclub-zsync -c "/etc/bashclub/$SOURCEHOST.conf"
|
||||
else
|
||||
log "Zsync is disabled."
|
||||
fi
|
||||
}
|
||||
|
||||
run_remote_updates() {
|
||||
if [[ "${UPDATES,,}" == "yes" ]]; then
|
||||
log "Running updates on local system..."
|
||||
|
||||
TIMESTAMP=$(date +"%Y-%m-%d_%H-%M-%S")
|
||||
SNAPSHOT_TAG="pve-update-via-miyagi"
|
||||
MAX_SNAPSHOTS=5
|
||||
ZFS_DATASETS=("rpool/ROOT/pve-1" "rpool/ROOT/pbs-1" "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
|
||||
|
||||
disable_pve_notifications
|
||||
|
||||
# PBS-Host-Update
|
||||
if [[ "${BACKUPSERVER,,}" == "yes" ]]; then
|
||||
log "Running updates on PBS host ($PBSHOST)..."
|
||||
ssh -p $PBSPORT root@"$PBSHOST" apt update && ssh -p $PBSPORT root@"$PBSHOST" apt dist-upgrade -y || {
|
||||
log "Error during updates on $PBSHOST"
|
||||
}
|
||||
else
|
||||
log "PBS updates skipped (BACKUPSERVER=$BACKUPSERVER)"
|
||||
fi
|
||||
|
||||
else
|
||||
log "Updates disabled (UPDATES=$UPDATES)"
|
||||
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
|
||||
}
|
||||
|
||||
send_piggyback() {
|
||||
get_sourcehostname
|
||||
local combined_host="miyagi-${SOURCEHOSTNAME}-$(hostname)"
|
||||
local filename="100000_${combined_host}"
|
||||
|
||||
log "Reminder: Add a host named ${combined_host} in CMK (without Agent, Piggyback enabled)!"
|
||||
log "Creating piggyback file: $filename"
|
||||
|
||||
{
|
||||
echo "<<<<${combined_host}>>>>"
|
||||
/usr/bin/check_mk_agent
|
||||
echo "<<<<>>>>"
|
||||
} > "$filename"
|
||||
|
||||
if rscp "$filename" "$SOURCEHOST:/var/lib/check_mk_agent/spool/"; then
|
||||
log "Piggyback data successfully sent to $SOURCEHOST"
|
||||
else
|
||||
log "ERROR: Failed to send piggyback data to $SOURCEHOST"
|
||||
fi
|
||||
|
||||
rm -f "$filename"
|
||||
}
|
||||
|
||||
run_pbs_log_clear() {
|
||||
log "Running clear LOG on PBS host ($PBSHOST)..."
|
||||
|
||||
if ! ssh -p "$PBSPORT" root@"$PBSHOST" \
|
||||
find /var/log/proxmox-backup/tasks/ -type f -mtime +60 -delete; then
|
||||
log "Error clearing PBS LOG on $PBSHOST"
|
||||
fi
|
||||
}
|
||||
|
||||
run_pve_log_clear() {
|
||||
log "Running clear LOG on PVE host ($PBSHOST)..."
|
||||
|
||||
if ! ssh -p "$SOURCEPORT" root@"$SOURCEHOST" \
|
||||
find /var/log/vzdump -type f -mtime +30 -delete; then
|
||||
log "Error clearing PVE LOG on $SOURCEHOST"
|
||||
fi
|
||||
}
|
||||
|
||||
run_pbs_backup() {
|
||||
if [[ "${BACKUPSERVER,,}" != "yes" ]]; then
|
||||
log "PBS Backup übersprungen: BACKUPSERVER='$BACKUPSERVER' (muss 'yes' sein)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
get_sourcehostname
|
||||
log "Starte PBS Backup auf Host: $SOURCEHOST"
|
||||
|
||||
# PBS-Storage prüfen und ggf. aktivieren
|
||||
log "Prüfe, ob PBS-Storage '$BACKUPSTORE' auf $SOURCEHOST aktiviert ist..."
|
||||
if rssh root@"$SOURCEHOST" "pvesm status | grep -w '$BACKUPSTORE' | grep -q 'disabled'"; then
|
||||
log "PBS-Storage '$BACKUPSTORE' ist deaktiviert – aktiviere temporär und warte 10 Sekunden..."
|
||||
rssh root@"$SOURCEHOST" "pvesm set '$BACKUPSTORE' --disable 0 && sleep 10"
|
||||
pbs_enabled_by_script=true
|
||||
else
|
||||
log "PBS-Storage '$BACKUPSTORE' ist bereits aktiviert."
|
||||
pbs_enabled_by_script=false
|
||||
fi
|
||||
|
||||
vzdump_success=false
|
||||
|
||||
# Versuch: vzdump mit Change Detection
|
||||
## Eintrag muss in /etc/vzdump.conf entries-max 100000000
|
||||
log "Starte vzdump mit Change Detection..."
|
||||
if rssh root@"$SOURCEHOST" vzdump --pbs-change-detection-mode metadata \
|
||||
--node "$SOURCEHOSTNAME" --storage "$BACKUPSTORE" \
|
||||
--exclude "$BACKUPEXCLUDE" --mode snapshot --all 1 \
|
||||
--notes-template '{{guestname}}'; then
|
||||
|
||||
log "vzdump (Change Detection) erfolgreich."
|
||||
vzdump_success=true
|
||||
else
|
||||
log "vzdump (Change Detection) fehlgeschlagen – versuche Fallback ohne Change Detection..."
|
||||
|
||||
if rssh root@"$SOURCEHOST" vzdump \
|
||||
--node "$SOURCEHOSTNAME" --storage "$BACKUPSTORE" \
|
||||
--exclude "$BACKUPEXCLUDE" --mode snapshot --all 1 \
|
||||
--notes-template '{{guestname}}'; then
|
||||
|
||||
log "Fallback-vzdump erfolgreich."
|
||||
vzdump_success=true
|
||||
else
|
||||
log "FEHLER: vzdump fehlgeschlagen – auch im Fallback."
|
||||
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 root@"$SOURCEHOST" "pvesm set '$BACKUPSTORE' --disable 1"
|
||||
fi
|
||||
|
||||
# Monitoring-Ausgabe
|
||||
if [[ "$vzdump_success" == true ]]; then
|
||||
echo "0 DailyPBS - Daily Backup erfolgreich" > /tmp/cmk_tmp.out
|
||||
else
|
||||
echo "2 DailyPBS - Daily Backup FEHLGESCHLAGEN" > /tmp/cmk_tmp.out
|
||||
fi
|
||||
|
||||
{
|
||||
echo "<<<local>>>"
|
||||
cat /tmp/cmk_tmp.out
|
||||
} > /tmp/1200000_checkpbs
|
||||
|
||||
rscp /tmp/1200000_checkpbs root@"$SOURCEHOST":/var/lib/check_mk_agent/spool \
|
||||
|| log "Fehler beim Übertragen des Monitoring-Outputs via SCP"
|
||||
|
||||
rm -f /tmp/cmk_tmp.out /tmp/1200000_checkpbs
|
||||
#write_pbs_status
|
||||
}
|
||||
|
||||
run_maintenance() {
|
||||
if [[ "${BACKUPSERVER,,}" != "yes" ]]; then
|
||||
log "PBS Backup wird übersprungen (BACKUPSERVER=$BACKUPSERVER)"
|
||||
return
|
||||
fi
|
||||
|
||||
if [[ "$(date +%u)" == "$MAINTDAY" ]]; then
|
||||
log "Running maintenance..."
|
||||
PRUNEJOB=$(ssh -p $PBSPORT "$PBSHOST" proxmox-backup-manager prune-job list --output-format json-pretty | grep -m 1 "id" | cut -d'"' -f4)
|
||||
ssh -p $PBSPORT root@"$PBSHOST" proxmox-backup-manager prune-job run "$PRUNEJOB"
|
||||
ssh -p $PBSPORT root@"$PBSHOST" proxmox-backup-manager garbage-collection start "$BACKUPSTOREPBS"
|
||||
ssh -p $PBSPORT root@"$PBSHOST" proxmox-backup-manager verify backup
|
||||
else
|
||||
log "No maintenance scheduled for today."
|
||||
fi
|
||||
}
|
||||
|
||||
run_scrub_stop() {
|
||||
local mode="$1" # "local" oder "remote"
|
||||
local ssh_cmd=()
|
||||
|
||||
if [[ "$mode" == "remote" ]]; then
|
||||
ssh_cmd=(ssh -p "$SOURCEPORT" root@"$SOURCEHOST")
|
||||
fi
|
||||
|
||||
"${ssh_cmd[@]}" bash -c '
|
||||
for pool in $(zpool list -H -o name); do
|
||||
echo "Stopping scrub on pool: $pool"
|
||||
if zpool status "$pool" | grep -q "scrub in progress"; then
|
||||
if zpool scrub -s "$pool"; then
|
||||
echo "Scrub stopped on $pool"
|
||||
else
|
||||
echo "Error stopping scrub on $pool"
|
||||
fi
|
||||
else
|
||||
echo "No active scrub on $pool"
|
||||
fi
|
||||
done
|
||||
'
|
||||
}
|
||||
|
||||
write_pbs_status() {
|
||||
local spool_file="/var/lib/check_mk_agent/spool/90000_checkpbs_local"
|
||||
local repo="$BACKUPSTOREPBS"
|
||||
local tmpfile
|
||||
tmpfile=$(mktemp)
|
||||
|
||||
if ! command -v proxmox-backup-client >/dev/null 2>&1; then
|
||||
log "proxmox-backup-client nicht installiert – PBS-Status-Ausgabe übersprungen."
|
||||
return
|
||||
fi
|
||||
|
||||
echo "<<<local>>>" > "$tmpfile"
|
||||
|
||||
local now
|
||||
now=$(date +%s)
|
||||
|
||||
# === Schwellenwert(e) parsen ===
|
||||
local warn_threshold=0
|
||||
local crit_threshold=86400 # Default kritisch ab 24h
|
||||
|
||||
if [[ -n "${PBSBACKUP_STATUS:-}" ]]; then
|
||||
IFS=',' read -r t1 t2 <<< "$PBSBACKUP_STATUS"
|
||||
if [[ "$t1" =~ ^[0-9]+$ && -z "$t2" ]]; then
|
||||
# Nur ein Wert vorhanden
|
||||
crit_threshold=$t1
|
||||
log "Verwende einfachen Schwellenwert: CRIT=${crit_threshold}s"
|
||||
elif [[ "$t1" =~ ^[0-9]+$ && "$t2" =~ ^[0-9]+$ ]]; then
|
||||
warn_threshold=$t1
|
||||
crit_threshold=$t2
|
||||
log "Verwende gestaffelte Schwellenwerte: WARN=${warn_threshold}s, CRIT=${crit_threshold}s"
|
||||
else
|
||||
log "WARNUNG: Ungültiges PBSBACKUP_STATUS-Format – Fallback: CRIT=${crit_threshold}s"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Backup-Daten extrahieren
|
||||
rssh root@"$SOURCEHOST" proxmox-backup-client list --repository "$repo" --output-format json 2>/dev/null | \
|
||||
grep -E '"backup-id"|"backup-type"|"backup-time"' | \
|
||||
sed -E 's/[",]//g; s/^ *//' > "$tmpfile.json"
|
||||
|
||||
local backup_type="" backup_id="" backup_time=""
|
||||
while read -r line; do
|
||||
case "$line" in
|
||||
backup-type:*)
|
||||
backup_type="${line#*: }"
|
||||
;;
|
||||
backup-id:*)
|
||||
backup_id="${line#*: }"
|
||||
;;
|
||||
backup-time:*)
|
||||
backup_time="${line#*: }"
|
||||
|
||||
if [[ -n "$backup_type" && -n "$backup_id" && -n "$backup_time" ]]; then
|
||||
local age status timestamp msg
|
||||
age=$((now - backup_time))
|
||||
timestamp=$(date -d "@$backup_time" "+%Y-%m-%d %H:%M:%S")
|
||||
|
||||
if (( age < warn_threshold )); then
|
||||
status=0
|
||||
elif (( age < crit_threshold )); then
|
||||
status=1
|
||||
else
|
||||
status=2
|
||||
fi
|
||||
|
||||
msg="$backup_type/$backup_id last backup $timestamp (age: $((age / 3600))h)"
|
||||
echo "$status PBS_${backup_type}_${backup_id} - $msg" >> "$tmpfile"
|
||||
|
||||
# Reset
|
||||
backup_type="" backup_id="" backup_time=""
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
done < "$tmpfile.json"
|
||||
|
||||
rm -f "$tmpfile.json"
|
||||
mv "$tmpfile" "$spool_file"
|
||||
log "PBS Backup Status lokal geschrieben: $spool_file"
|
||||
}
|
||||
|
||||
shutdown_now() {
|
||||
if [[ "${SHUTDOWN,,}" == "yes" ]]; then
|
||||
if zfs_replace; then
|
||||
log "ZFS Replace-Vorgang erkannt – warte bis zum Abschluss..."
|
||||
if ! wait_replace; then
|
||||
log "Shutdown abgebrochen – Replace ist nach max. Wartezeit noch nicht abgeschlossen."
|
||||
return
|
||||
fi
|
||||
fi
|
||||
send_piggyback
|
||||
send_piggyback_external
|
||||
send_checkzfs_external
|
||||
log "Shutting down now...in 60sec"
|
||||
sleep 60
|
||||
shutdown now
|
||||
else
|
||||
log "No shutdown requested."
|
||||
fi
|
||||
}
|
||||
|
||||
send_piggyback_external() {
|
||||
if [[ "${EPIGGYBACK,,}" != "yes" ]]; then
|
||||
log "Externer Piggyback-Export deaktiviert."
|
||||
return
|
||||
fi
|
||||
get_sourcehostname
|
||||
log "Ermittelter SOURCEHOSTNAME: $SOURCEHOSTNAME"
|
||||
|
||||
local combined_host="miyagi-${SOURCEHOSTNAME}-$(hostname)"
|
||||
local spool_file="100000_${combined_host}_piggyback_external"
|
||||
local temp_dir
|
||||
temp_dir=$(mktemp -d)
|
||||
|
||||
log "Erzeuge temporäre Piggyback-Datei: $temp_dir/$spool_file"
|
||||
|
||||
{
|
||||
echo "<<<<${combined_host}>>>>"
|
||||
/usr/bin/check_mk_agent
|
||||
echo "<<<<>>>>"
|
||||
} > "$temp_dir/$spool_file"
|
||||
|
||||
if scp -P "$EPIGGYBACK_PORT" "$temp_dir/$spool_file" "$EPIGGYBACK_HOST:/var/lib/check_mk_agent/spool/"; then
|
||||
log "Piggyback-Daten erfolgreich an $EPIGGYBACK_HOST gesendet: $spool_file"
|
||||
else
|
||||
log "Fehler beim Übertragen der Piggyback-Daten an $EPIGGYBACK_HOST"
|
||||
rm -rf "$temp_dir"
|
||||
return 1
|
||||
fi
|
||||
|
||||
rm -rf "$temp_dir"
|
||||
return 0
|
||||
}
|
||||
|
||||
send_checkzfs_external() {
|
||||
if [[ "${ECHECKZFS,,}" != "yes" ]]; then
|
||||
log "Externer Piggyback-Export deaktiviert."
|
||||
return
|
||||
fi
|
||||
local config="/etc/bashclub/${SOURCEHOST}.conf"
|
||||
if [[ ! -f "$config" ]]; then
|
||||
log "Konfigurationsdatei fehlt: $config"
|
||||
return 1
|
||||
fi
|
||||
source "$config"
|
||||
|
||||
if [[ "${ECHECKZFS,,}" != "yes" ]]; then
|
||||
log "Externer Check-ZFS deaktiviert (ECHECKZFS=$ECHECKZFS)"
|
||||
return
|
||||
fi
|
||||
|
||||
get_sourcehostname # Setzt SOURCEHOSTNAME, wenn nicht vorhanden
|
||||
|
||||
local checkzfs_cmd="${checkzfs_cmd:-$(command -v checkzfs)}"
|
||||
if [[ -z "$checkzfs_cmd" || ! -x "$checkzfs_cmd" ]]; then
|
||||
log "checkzfs nicht gefunden oder nicht ausführbar – Abbruch."
|
||||
return 1
|
||||
fi
|
||||
log "Verwende checkzfs: $checkzfs_cmd"
|
||||
|
||||
# Filter anhand ZFS-Tags auf dem SOURCEHOST aufbauen
|
||||
local filter=""
|
||||
while IFS=$'\t' read -r name value source; do
|
||||
if [[ "$source" == "local" && "$value" == "subvols" ]]; then
|
||||
filter+="${name}/|"
|
||||
elif [[ "$source" == "local" && "$value" == "all" ]]; then
|
||||
filter+="${name}|"
|
||||
fi
|
||||
done < <(ssh -p "$SOURCEPORT" "$SOURCEHOST" "zfs get -H -o name,value,source -t filesystem,volume $ZPUSHTAG")
|
||||
|
||||
filter="#${filter%|}"
|
||||
log "Generierter ZFS-Filter: $filter"
|
||||
|
||||
local combined="miyagi-${SOURCEHOSTNAME}-$(hostname)-${ZPUSHTAG}"
|
||||
local spoolfile="/tmp/${combined}_checkzfs_external"
|
||||
local spooldest="100000_${combined}_checkzfs_external"
|
||||
|
||||
log "Führe checkzfs aus..."
|
||||
{
|
||||
echo "<<<local>>>"
|
||||
"$checkzfs_cmd" \
|
||||
--source root@"${SOURCEHOST}:${SOURCEPORT}" \
|
||||
--filter "$filter" \
|
||||
--replicafilter "^${ZFSTRGT}" \
|
||||
--prefix "$checkzfs_prefix" \
|
||||
--threshold "$checkzfs_max_age" \
|
||||
--maxsnapshots "$checkzfs_max_snapshot_count" \
|
||||
--output checkmk
|
||||
} > "$spoolfile"
|
||||
|
||||
if scp -P "$ECHECKZFS_PORT" "$spoolfile" root@"$ECHECKZFS_HOST":/var/lib/check_mk_agent/spool/"$spooldest"; then
|
||||
log "Spool-Datei erfolgreich übertragen an $ECHECKZFS_HOST:$spooldest"
|
||||
else
|
||||
log "Fehler beim Übertragen der Spool-Datei an $ECHECKZFS_HOST"
|
||||
fi
|
||||
|
||||
rm -f "$spoolfile"
|
||||
}
|
||||
wait() {
|
||||
echo
|
||||
echo "###########################################"
|
||||
echo "# Das Backup startet in 60 Sekunden... #"
|
||||
echo "# Drücke [Enter], um sofort fortzufahren, #"
|
||||
echo "# oder warte, um automatisch zu starten. #"
|
||||
echo "###########################################"
|
||||
read -t 60 -p "Fortfahren (Enter drücken) oder warten... " input
|
||||
if [[ $? -eq 0 ]]; then
|
||||
log "Press ENTER to continue."
|
||||
else
|
||||
log "No press ENTER, wait for 60 Sec.."
|
||||
sleep 60
|
||||
fi
|
||||
}
|
||||
set_dynamic_route() {
|
||||
local target="$SOURCEHOST"
|
||||
local gateway="$DDNS_GATEWAY"
|
||||
local dns_server="$DDNS_SERVER"
|
||||
local current_ip
|
||||
|
||||
if [[ -z "$target" || -z "$gateway" ]]; then
|
||||
log "Fehler: SOURCEHOST oder DDNS_GATEWAY nicht gesetzt"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Ist SOURCEHOST bereits eine IPv4- oder IPv6-Adresse?
|
||||
if [[ "$target" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ || "$target" == *:* ]]; then
|
||||
current_ip="$target"
|
||||
log "SOURCEHOST ist bereits eine IP-Adresse: $current_ip"
|
||||
else
|
||||
log "Setze temporäre Route zu DNS-Server $dns_server via $gateway..."
|
||||
|
||||
case "$(uname -s)" in
|
||||
Linux)
|
||||
ip route | grep -q "^$dns_server" || \
|
||||
ip route add "$dns_server" via "$gateway"
|
||||
;;
|
||||
FreeBSD)
|
||||
route -n get "$dns_server" >/dev/null 2>&1 || \
|
||||
route add -host "$dns_server" "$gateway"
|
||||
;;
|
||||
esac
|
||||
|
||||
log "Löse IP von $target über DNS $dns_server..."
|
||||
current_ip=$(dig +short @"$dns_server" "$target" | awk 'NF {print; exit}')
|
||||
|
||||
if [[ -z "$current_ip" ]]; then
|
||||
log "Fehler: Konnte IP für $target nicht auflösen"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
log "Ermittelte IP: $current_ip – setze Route via $gateway..."
|
||||
|
||||
case "$(uname -s)" in
|
||||
Linux)
|
||||
ip route | grep -q "^$current_ip" || \
|
||||
ip route add "$current_ip" via "$gateway"
|
||||
;;
|
||||
FreeBSD)
|
||||
route -n get "$current_ip" >/dev/null 2>&1 || \
|
||||
route add -host "$current_ip" "$gateway"
|
||||
;;
|
||||
*)
|
||||
log "Nicht unterstütztes Betriebssystem: $(uname -s)"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
|
||||
log "Route erfolgreich gesetzt: $current_ip via $gateway"
|
||||
}
|
||||
# Main execution:
|
||||
if [[ $# -eq 0 ]]; then
|
||||
if [[ -n "$CONFIG_FILE" ]]; then
|
||||
wait
|
||||
log "Running full backup using configuration file: $CONFIG_FILE"
|
||||
|
||||
if [[ "${DYNROUTE,,}" == "yes" ]]; then
|
||||
log "DYNROUTE ist aktiviert – setze dynamische Route für $SOURCEHOST..."
|
||||
set_dynamic_route
|
||||
else
|
||||
log "DYNROUTE ist deaktiviert oder nicht gesetzt."
|
||||
fi
|
||||
|
||||
write_zsync_config
|
||||
run_zsync
|
||||
|
||||
if [[ "${BACKUPSERVER,,}" == "yes" ]]; then
|
||||
log "BACKUPSERVER ist aktiviert, führe Backup aus..."
|
||||
run_pbs_backup
|
||||
run_maintenance
|
||||
run_pbs_log_clear
|
||||
run_pve_log_clear
|
||||
else
|
||||
log "BACKUPSERVER ist nicht aktiviert (BACKUPSERVER=$BACKUPSERVER), überspringe Backup."
|
||||
fi
|
||||
run_remote_updates
|
||||
if [[ "${EPIGGYBACK,,}" == "yes" ]]; then
|
||||
send_piggyback_external
|
||||
fi
|
||||
|
||||
if [[ "${ECHECKZFS,,}" == "yes" ]]; then
|
||||
send_checkzfs_external
|
||||
fi
|
||||
shutdown_now
|
||||
else
|
||||
usage
|
||||
fi
|
||||
else
|
||||
case "$1" in
|
||||
help)
|
||||
declare -F | awk '{print $3}' | grep -v "^_" | grep -v "^main$"
|
||||
;;
|
||||
*)
|
||||
if declare -F "$1" >/dev/null 2>&1; then
|
||||
"$@"
|
||||
else
|
||||
log "Function $1 not found."
|
||||
usage
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
@@ -1,222 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# ================================
|
||||
# Erweiterungs-Installer mit dialog GUI
|
||||
# ================================
|
||||
|
||||
# 0. Sicherstellen, dass dialog vorhanden ist
|
||||
if ! command -v dialog &>/dev/null; then
|
||||
echo "[INFO] Installiere 'dialog' für das Erweiterungsmenü..."
|
||||
sudo apt update && sudo apt install -y dialog
|
||||
fi
|
||||
|
||||
# 1. Menü anzeigen
|
||||
HEIGHT=20
|
||||
WIDTH=60
|
||||
CHOICE_HEIGHT=10
|
||||
BACKTITLE="Checkmk & ZFS Setup"
|
||||
TITLE="Optionale Erweiterungen"
|
||||
MENU="Wähle gewünschte Zusatzfunktionen:"
|
||||
|
||||
OPTIONS=(
|
||||
1 "Installiere Miyagi-Backup-Skripte" on
|
||||
2 "Installiere disk_smart_info Plugin" on
|
||||
3 "Installiere cleansnaps (ZFS)" on
|
||||
4 "Installiere check-snapshot-age (ZFS)" on
|
||||
5 "Installiere checkzfs (ZFS)" on
|
||||
6 "Starte ZFS-Pool-Tagging" off
|
||||
)
|
||||
|
||||
CHOICES=$(dialog --clear \
|
||||
--backtitle "$BACKTITLE" \
|
||||
--title "$TITLE" \
|
||||
--checklist "$MENU" \
|
||||
$HEIGHT $WIDTH $CHOICE_HEIGHT \
|
||||
"${OPTIONS[@]}" \
|
||||
3>&1 1>&2 2>&3)
|
||||
|
||||
clear
|
||||
|
||||
# 2. Prüfen, ob Auswahl getroffen wurde
|
||||
if [[ -z "$CHOICES" ]]; then
|
||||
echo "[INFO] Keine Erweiterungen ausgewählt. Überspringe Zusatzfunktionen."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 3. Zusammenfassung bauen
|
||||
SUMMARY_TEXT="Du hast folgende Erweiterungen ausgewählt:\n"
|
||||
|
||||
for CHOICE in $CHOICES; do
|
||||
case $CHOICE in
|
||||
1) SUMMARY_TEXT+="\n - Miyagi-Backup-Skripte" ;;
|
||||
2) SUMMARY_TEXT+="\n - disk_smart_info Plugin" ;;
|
||||
3) SUMMARY_TEXT+="\n - cleansnaps (ZFS)" ;;
|
||||
4) SUMMARY_TEXT+="\n - check-snapshot-age (ZFS)" ;;
|
||||
5) SUMMARY_TEXT+="\n - checkzfs (ZFS)" ;;
|
||||
6) SUMMARY_TEXT+="\n - ZFS-Pool-Tagging starten" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# 4. Bestätigung anzeigen
|
||||
dialog --clear \
|
||||
--backtitle "Checkmk & Erweiterungen" \
|
||||
--title "Zusammenfassung" \
|
||||
--yesno "$SUMMARY_TEXT\n\nMöchtest du die Einrichtung jetzt starten?" \
|
||||
18 70
|
||||
|
||||
response=$?
|
||||
clear
|
||||
|
||||
if [[ $response -ne 0 ]]; then
|
||||
echo "[ABBRUCH] Installation abgebrochen durch Benutzer."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "[INFO] Starte nun die Einrichtung der gewählten Komponenten..."
|
||||
|
||||
# 5. Ausgewählte Erweiterungen ausführen
|
||||
for CHOICE in $CHOICES; do
|
||||
case $CHOICE in
|
||||
1)
|
||||
echo "[INSTALL] Miyagi-Backup-Skripte..."
|
||||
wget -q --no-cache -O miyagi-backup.sh https://gitea.perlbach24.de/scripte/miyagi-backup/raw/branch/main/miyagi-backup.sh
|
||||
wget -q --no-cache -O miyagi-check.sh https://gitea.perlbach24.de/scripte/miyagi-backup/raw/branch/main/miyagi-check.sh
|
||||
wget -q --no-cache -O miyagi-convert.sh https://gitea.perlbach24.de/scripte/miyagi-backup/raw/branch/main/miyagi-convert.sh
|
||||
;;
|
||||
2)
|
||||
echo "[INSTALL] disk_smart_info Plugin..."
|
||||
curl -fsSL https://raw.githubusercontent.com/bashclub/checkmk-smart/main/disk_smart_info.py -o /usr/lib/check_mk_agent/plugins/disk_smart_info
|
||||
chmod +x /usr/lib/check_mk_agent/plugins/disk_smart_info
|
||||
;;
|
||||
3)
|
||||
echo "[INSTALL] cleansnaps..."
|
||||
wget -q --no-cache -O /usr/local/bin/cleansnaps https://raw.githubusercontent.com/bashclub/zfs-housekeeping/refs/heads/main/cleansnaps.sh
|
||||
chmod +x /usr/local/bin/cleansnaps
|
||||
;;
|
||||
4)
|
||||
echo "[INSTALL] check-snapshot-age..."
|
||||
wget -q --no-cache -O /usr/local/bin/check-snapshot-age https://gitea.perlbach24.de/scripte/check-zfs-replication/raw/branch/main/check-snapshot-age
|
||||
chmod +x /usr/local/bin/check-snapshot-age
|
||||
;;
|
||||
5)
|
||||
echo "[INSTALL] checkzfs..."
|
||||
wget -q --no-cache -O /usr/local/bin/checkzfs https://gitea.perlbach24.de/scripte/check-zfs-replication/raw/branch/main/checkzfs.py
|
||||
chmod +x /usr/local/bin/checkzfs
|
||||
;;
|
||||
6)
|
||||
echo "[INFO] Starte ZFS-Pool-Tagging..."
|
||||
# Optional: Weitere Dialog-Auswahl oder SSH-Zugriff hier implementieren
|
||||
|
||||
echo "[INFO] Lade ZFS-Datasets vom Remote-Host..."
|
||||
|
||||
LOCAL_HOSTNAME=$(hostname -s)
|
||||
REMOTE_HOSTNAME=$(ssh -p "$SOURCEPORT" root@"$SOURCEHOST" "hostname -s")
|
||||
TAG_KEY="bashclub:miyagi-${REMOTE_HOSTNAME}-${LOCAL_HOSTNAME}"
|
||||
TAG_VALUE="subvols"
|
||||
|
||||
# Hole alle relevanten Pool-Namen
|
||||
mapfile -t pools < <(ssh -p "$SOURCEPORT" root@"$SOURCEHOST" \
|
||||
"zfs list -H -o name" | \
|
||||
grep -E '/(vm|subvol)-' | \
|
||||
grep -viE 'repl|replica' | \
|
||||
sed 's:/[^/]*$::' | \
|
||||
sort -u)
|
||||
|
||||
if [[ ${#pools[@]} -eq 0 ]]; then
|
||||
echo "[WARN] Keine geeigneten Datasets gefunden!"
|
||||
return
|
||||
fi
|
||||
|
||||
local options=()
|
||||
local tagged_pools=()
|
||||
|
||||
for pool in "${pools[@]}"; do
|
||||
local tag status
|
||||
tag=$(ssh -p "$SOURCEPORT" root@"$SOURCEHOST" zfs get -H -o value "$TAG_KEY" "$pool" 2>/dev/null)
|
||||
if [[ "$tag" == "$TAG_VALUE" ]]; then
|
||||
status="on"
|
||||
tagged_pools+=("$pool")
|
||||
else
|
||||
status="off"
|
||||
fi
|
||||
options+=("$pool" "$([[ "$status" == "on" ]] && echo 'TAGGED' || echo 'untagged')" "$status")
|
||||
done
|
||||
|
||||
local selected_array=()
|
||||
|
||||
# 🧠 Prüfen ob whiptail oder dialog vorhanden ist
|
||||
if command -v whiptail >/dev/null; then
|
||||
local selected
|
||||
selected=$(whiptail --title "ZFS Pools auswählen" \
|
||||
--checklist "Wähle Datasets zum Taggen aus:" 20 78 12 \
|
||||
"${options[@]}" \
|
||||
3>&1 1>&2 2>&3)
|
||||
|
||||
if [[ $? -ne 0 ]]; then
|
||||
echo "[INFO] Auswahl abgebrochen."
|
||||
return
|
||||
fi
|
||||
|
||||
eval "selected_array=($selected)"
|
||||
|
||||
elif command -v dialog >/dev/null; then
|
||||
local selected
|
||||
selected=$(dialog --clear \
|
||||
--backtitle "ZFS Tagging" \
|
||||
--title "ZFS Pools auswählen" \
|
||||
--checklist "Wähle Datasets zum Taggen aus:" 20 78 12 \
|
||||
"${options[@]}" \
|
||||
3>&1 1>&2 2>&3)
|
||||
|
||||
clear
|
||||
if [[ $? -ne 0 ]]; then
|
||||
echo "[INFO] Auswahl abgebrochen."
|
||||
return
|
||||
fi
|
||||
|
||||
eval "selected_array=($selected)"
|
||||
|
||||
else
|
||||
# ⚠️ Fallback: einfache Textauswahl
|
||||
echo "[INFO] Kein dialog oder whiptail gefunden – nutze Textauswahl."
|
||||
echo "[INFO] Vorhandene Pools:"
|
||||
for i in "${!pools[@]}"; do
|
||||
echo " [$i] ${pools[i]}"
|
||||
done
|
||||
|
||||
echo
|
||||
echo "Gib die Nummern der Pools an, die getaggt werden sollen (getrennt mit Leerzeichen):"
|
||||
read -rp "Auswahl: " user_input
|
||||
|
||||
for index in $user_input; do
|
||||
selected_array+=("${pools[index]}")
|
||||
done
|
||||
fi
|
||||
|
||||
# ✅ Verarbeitung der Auswahl
|
||||
echo "[INFO] Verarbeite Tagging für ausgewählte Pools:"
|
||||
for ((i = 0; i < ${#options[@]}; i += 3)); do
|
||||
local pool="${options[i]}"
|
||||
local prev_status="${options[i+2]}"
|
||||
local is_selected=0
|
||||
|
||||
for sel in "${selected_array[@]}"; do
|
||||
if [[ "$sel" == "$pool" ]]; then
|
||||
is_selected=1
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "$is_selected" -eq 1 && "$prev_status" == "off" ]]; then
|
||||
echo " → Tagge $pool"
|
||||
run_or_echo ssh -p "$SOURCEPORT" root@"$SOURCEHOST" zfs set "$TAG_KEY=$TAG_VALUE" "$pool"
|
||||
elif [[ "$is_selected" -eq 0 && "$prev_status" == "on" ]]; then
|
||||
echo " → Entferne Tag von $pool"
|
||||
run_or_echo ssh -p "$SOURCEPORT" root@"$SOURCEHOST" zfs inherit "$TAG_KEY" "$pool"
|
||||
fi
|
||||
done
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
echo "[FERTIG] Alle gewählten Komponenten wurden installiert."
|
||||
@@ -1,91 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import os, sys, subprocess, logging, time, tempfile, shutil
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
|
||||
# === Logging Setup ===
|
||||
SCRIPT_NAME = Path(sys.argv[0]).stem
|
||||
LOGFILE = f"/var/log/{SCRIPT_NAME}.log"
|
||||
logging.basicConfig(
|
||||
filename=LOGFILE,
|
||||
level=logging.INFO,
|
||||
format='[%(asctime)s] %(message)s',
|
||||
datefmt='%Y-%m-%d %H:%M:%S'
|
||||
)
|
||||
console = logging.StreamHandler()
|
||||
console.setLevel(logging.INFO)
|
||||
formatter = logging.Formatter('[%(asctime)s] %(message)s', '%Y-%m-%d %H:%M:%S')
|
||||
console.setFormatter(formatter)
|
||||
logging.getLogger().addHandler(console)
|
||||
|
||||
def log(msg):
|
||||
logging.info(msg)
|
||||
|
||||
# === Config Loader ===
|
||||
import yaml
|
||||
|
||||
CONFIG = {}
|
||||
def load_config(path):
|
||||
global CONFIG
|
||||
if not os.path.isfile(path):
|
||||
log(f"ERROR: Config file not found: {path}")
|
||||
sys.exit(1)
|
||||
with open(path) as f:
|
||||
CONFIG = yaml.safe_load(f)
|
||||
log("Config loaded successfully")
|
||||
|
||||
# === SSH / SCP Wrappers ===
|
||||
def rssh(cmd, port=None, host=None):
|
||||
port = port or CONFIG.get('SOURCEPORT', 22)
|
||||
host = host or CONFIG.get('SOURCEHOST')
|
||||
ssh_cmd = ["ssh", "-o", "BatchMode=yes", "-o", "ConnectTimeout=10", "-p", str(port), f"root@{host}"] + cmd
|
||||
return subprocess.run(ssh_cmd, capture_output=True, text=True)
|
||||
|
||||
def rscp(local, remote, port=None, host=None):
|
||||
port = port or CONFIG.get('SOURCEPORT', 22)
|
||||
host = host or CONFIG.get('SOURCEHOST')
|
||||
scp_cmd = ["scp", "-P", str(port), local, f"root@{host}:{remote}"]
|
||||
return subprocess.run(scp_cmd)
|
||||
|
||||
# === Beispiel-Funktionen 1:1 ===
|
||||
def get_sourcehostname():
|
||||
if not CONFIG.get('SOURCEHOSTNAME'):
|
||||
log(f"SOURCEHOSTNAME empty, retrieving from {CONFIG['SOURCEHOST']}...")
|
||||
result = rssh(["hostname"])
|
||||
CONFIG['SOURCEHOSTNAME'] = result.stdout.strip()
|
||||
log(f"Detected SOURCEHOSTNAME: {CONFIG['SOURCEHOSTNAME']}")
|
||||
|
||||
def shutdown_now():
|
||||
if CONFIG.get('SHUTDOWN', 'no').lower() == 'yes':
|
||||
# Hier würde Python wait_replace() aufrufen, send_piggyback() etc.
|
||||
log("Shutdown requested. Would execute now...")
|
||||
else:
|
||||
log("No shutdown requested.")
|
||||
|
||||
# === Weitere Funktionen nach Bash ===
|
||||
# zfs_replace(), wait_replace(), write_zsync_config(), run_zsync(),
|
||||
# run_remote_updates(), send_piggyback(), run_pbs_backup(), etc.
|
||||
|
||||
# === Main Execution ===
|
||||
def main():
|
||||
import argparse
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("-c", "--config", help="Path to config")
|
||||
parser.add_argument("function", nargs='?', help="Run specific function")
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.config:
|
||||
load_config(args.config)
|
||||
|
||||
if args.function:
|
||||
func = globals().get(args.function)
|
||||
if func:
|
||||
func()
|
||||
else:
|
||||
log(f"Function {args.function} not found")
|
||||
sys.exit(1)
|
||||
else:
|
||||
# Full backup run
|
||||
log("Running full backup...")
|
||||
# Full run calls all functions like in Bash
|
||||
# wait(), set_dynamic_route(), write_zsync_config(), run_zsync(), etc.
|
||||
Reference in new issue
Block a user