Activate storage layout documentation
This commit is contained in:
@@ -0,0 +1,417 @@
|
||||
# Storage Layout — KalliLab CORE
|
||||
|
||||
| Feld | Wert |
|
||||
|------|------|
|
||||
| Version | 1.4 |
|
||||
| Status | **Active** — bindend als `docs/STORAGE_LAYOUT.md` |
|
||||
| Datum | 2026-05-27 |
|
||||
| Geltungsbereich | Unraid-Host `Kallilabcore`, alle Pools, Array-Disks, User-Shares, Appdata-Strukturen |
|
||||
| Verbindlichkeit | Bindend ab Inkraftsetzung. Abweichungen nur dokumentiert als Ausnahme (siehe Abschnitt 12) |
|
||||
| Vorgänger | keiner; entstanden aus Incident 2026-05-11 (NTFS-Cache-Korruption) |
|
||||
| Verwandte Docs | `HOMELAB_ARCHITECTURE_MASTER_V2.md`, `docs/REPO_MAP.md`, `docs/SERVICE_CATALOG.md`, `docs/RESTORE_MATRIX.md`, `docs/SECRETS_MAP.md`, `docs/DISASTER_RECOVERY.md` |
|
||||
|
||||
---
|
||||
|
||||
## 1. Zweck
|
||||
|
||||
Dieses Dokument definiert die verbindliche Storage-Architektur für den KalliLab-CORE-Homelab-Stack. Es beantwortet:
|
||||
|
||||
- Welche physikalischen Disks gibt es, mit welchem Filesystem.
|
||||
- Welche User-Shares existieren, mit welchen Cache- und Allokations-Settings.
|
||||
- Wo liegen Container-Daten und in welcher Sub-Struktur.
|
||||
- Welche Pfade dürfen in Compose-Dateien als Bind-Mount auftauchen, welche nie.
|
||||
- Welche Backup- und Posture-Check-Pflichten daraus folgen.
|
||||
|
||||
Es ist **vor** jeder Storage- oder Compose-Änderung zu lesen. Wenn ein neuer Stack, ein neuer Pfad, eine neue Disk, ein neuer Share gebraucht wird und hier nicht spezifiziert ist, wird dieses Dokument **erst** ergänzt, dann implementiert.
|
||||
|
||||
## 2. Leitprinzipien
|
||||
|
||||
1. **Filesystem-Semantik ist nicht verhandelbar.** Datenbanken, Container-Persistenz und Array-Daten brauchen Linux-natives Filesystem (XFS, BTRFS, ZFS). NTFS, exFAT, FAT32 sind ausschließlich für Boot-USB und externe Austauschmedien zulässig, niemals für Cache, Array oder Pool.
|
||||
2. **Recovery > Convenience.** Eine Konvention, die Restore vereinfacht, schlägt jede Konvention, die nur die Erstinstallation vereinfacht.
|
||||
3. **Posture-Check als First-Class-Concern.** Jede Annahme über den Host-Zustand (FS-Typ, Mount, Permissions) muss automatisiert prüfbar sein. „Wir wissen schon, dass das stimmt" ist keine zulässige Begründung.
|
||||
4. **GitOps-Konformität.** Alles, was Compose-, Share-, oder Storage-Verhalten beeinflusst, ist im Repo dokumentiert oder versioniert. Was nur im Host lebt, existiert für Recovery nicht.
|
||||
5. **Pfad-Stabilität.** Pfade in Compose-Bind-Mounts bleiben stabil über Filesystem-Migrationen, Disk-Wechsel und Pool-Umzüge hinweg. Konkret: Container kennen `/mnt/user/...`-Pfade, nie `/mnt/cache/...` oder `/mnt/disk1/...`.
|
||||
6. **Keine Sammelordner.** Eine Datei hat genau einen logischen Eigentümer (Stack, Share, Nutzkategorie). Mischungen erzeugen Drift und brechen Backup-Excludes.
|
||||
7. **Bewusste Heterogenität nur mit Begründung.** Wenn ein Stack vom Standard abweicht (z. B. Host-Network, Privileged, Direct-Cache-Mount), ist die Abweichung in Abschnitt 12 dokumentiert mit Begründung und Risiko-Akzeptanz.
|
||||
|
||||
## 3. Physikalisches Layout (Soll-Zustand)
|
||||
|
||||
| Slot | Device | Filesystem | Größe | Zweck | Status nach Recovery |
|
||||
|------|--------|------------|-------|-------|----------------------|
|
||||
| Cache (Pool) | Samsung SSD 970 EVO Plus 2TB, NVMe (`S4J4NM0W609649H`) | **XFS** | 1.8T nutzbar | Appdata, system, domains | Reformat von NTFS auf XFS, Phase 1 |
|
||||
| Disk1 (Array) | WDC WD60EFAX-68JH4N1 (`WD-WX32D90PC0V0`) | **XFS** auf `md1p1` | 5.5T nutzbar | Nutzdaten, Backups, Services | NTFS-zu-XFS-Migration Phase 2 abgeschlossen am 2026-05-25 |
|
||||
| Parity | TOSHIBA HDWG480 (`2460A03VFA3H`) | — (keine FS) | 7.3T | Redundanz für Array | Unverändert |
|
||||
| Boot | Samsung Flash Drive (`0375125090000587`) | FAT32 | 59.8G | Unraid-OS, Konfiguration | Regelmäßig per Flash-Backup gesichert |
|
||||
| Externe Backup-Platte | H:/ `Externe HDD` am Windows-PC | NTFS | 8.0T | Nearline-Pull-Ziel für kritische Restore-Artefakte | Kein Off-site-/Airgap-Ersatz; Pull-Workflow in `docs/H_DRIVE_NEARLINE_PULL.md` |
|
||||
|
||||
Physikalische Basisdaten sind aus `docs/HARDWARE_INVENTORY.md` und dem Host-Readout vom 2026-05-27 übernommen. Detailwerte zu SMART/Health bleiben dort die autoritative Quelle; dieses Dokument hält die Storage-Policy.
|
||||
|
||||
**Begründung Filesystem-Wahl:**
|
||||
|
||||
- **XFS für Cache und Disk1:** Unraid-Standard, robuste fsck, gute Performance für Datenbank-Workloads, niedrige Komplexität, einheitlicher Stack vereinfacht Operations.
|
||||
- **BTRFS bewusst nicht** für Single-Disk-Cache: Mehrwerte (RAID1, Snapshots) werden ohne zweite NVMe nicht realisiert; zusätzlicher COW-Overhead und Komplexität ohne Gegenwert.
|
||||
- **ZFS bewusst nicht** für aktuellen Bedarf: höherer RAM-Verbrauch, in der bestehenden Topologie kein Mehrwert; spätere Migration eines dedizierten Pools auf ZFS bleibt offen (siehe Abschnitt 13).
|
||||
|
||||
**Eskalationspfad bei zweiter Cache-NVMe:** Migration zu BTRFS-Pool (RAID1) als bewusste Architektur-Entscheidung mit eigenem Migrations-Plan. Bis dahin: Single-XFS.
|
||||
|
||||
## 4. Logisches Layout — User-Shares
|
||||
|
||||
Verbindliche Share-Definition. Jeder Share hat genau einen primären Datenträger.
|
||||
|
||||
| Share | Cache-Setting | Primary Storage | Allocation | Split Level | Zweck |
|
||||
|-------|---------------|-----------------|------------|-------------|-------|
|
||||
| `appdata` | **only** | Cache | High Water | 1 | Container-Persistenz, niemals auf Array |
|
||||
| `system` | **only** | Cache | High Water | — | Docker-Image bzw. Folder-Mode-Verzeichnis, libvirt — bewusst `only`, weil Mover-Migration der Docker-Image-Datei oder von libvirt-State zwischen FS-Typen riskant ist |
|
||||
| `domains` | **only** | Cache | High Water | 1 | VM-Disk-Images — Performance und Stabilität für produktive VMs (HAOS, hermes-runner). Wenn ältere/seltene VM-Images aufs Array dürfen, bewusst auf `prefer` herabsetzen, dokumentiert pro VM |
|
||||
| `isos` | yes | Cache → Array | High Water | 2 | VM-Installations-ISOs, optional |
|
||||
| `services` | no | Disk1 | High Water | 2 | Gitea, Komodo-State, secrets-Pfad — **recovery-kritisch**, siehe Hinweis unter Tabelle |
|
||||
| `documents` | no | Disk1 | High Water | 2 | Persönliche Dokumente |
|
||||
| `photos` | no | Disk1 | High Water | 2 | Persönliche Fotos |
|
||||
| `backups` | no | Disk1 | High Water | 2 | Lokale Borg-Repos, Appdata-Backup-Archive |
|
||||
| `media` | no | Disk1 | High Water | 2 | Optional, Media-Files |
|
||||
| `finance` | no | Disk1 | High Water | 2 | Persönlich |
|
||||
| `projekte` | no | Disk1 | High Water | 2 | Persönlich |
|
||||
|
||||
**Erklärung der Cache-Settings:**
|
||||
|
||||
- `only`: Daten landen ausschließlich auf Cache, Mover ignoriert sie. Schutz vor Mover-induzierter Daten-Migration zwischen Filesystemen.
|
||||
- `prefer`: Neue Daten landen auf Cache, Mover schiebt sie bei Cache-Druck auf Array.
|
||||
- `yes`: Neue Daten landen auf Cache, Mover schiebt sie regelmäßig auf Array (klassisches Cache-Schreibverhalten).
|
||||
- `no`: Daten umgehen Cache, gehen direkt auf Array.
|
||||
|
||||
**Wahl `only` für `appdata`, `system`, `domains`** ist die wichtigste Setting in diesem Dokument. Sie verhindert genau die Klasse von Vorfällen, die im Mai 2026 passiert ist: dass Container-Daten, Docker-Image-State oder VM-Disks unbemerkt zwischen Filesystemen wandern. Wenn der Cache voll wird, ist das ein Kapazitätsproblem, kein Filesystem-Problem — und wird durch zweite NVMe oder Cache-Tuning gelöst, nicht durch Mover-Migration.
|
||||
|
||||
**`services` ist recovery-kritisch.** Gitea (operative Source of Truth des GitOps-Stacks) und Komodo-State (Deployment-Runtime) liegen dort. Ohne `services` ist Self-Recovery nicht mehr möglich. Daraus folgt:
|
||||
|
||||
- `services` wird vor jedem strukturellen Eingriff am Array (Disk1-Migration Phase 2, Disk-Tausch, Pool-Umzug) **vollständig** auf zweites Medium gesichert
|
||||
- `services` darf nicht auf einem als instabil bekannten Datenträger leben — Warnung in `posture-check`, Eskalation bei Disk1-FS-Anomalien
|
||||
- Änderungen an Gitea-Repos, Komodo-Stacks oder secrets-Pfad nur nach erfolgreichem letzten Backup-Check (siehe §11)
|
||||
- **Mirror-Backup für Gitea-Repo-Inhalte (Operator-Entscheidung 2026-05-15, verbindlich):** zusätzlich zum Standard-Borg-Lauf wird `services/gitea/git/repositories/` per separatem Mirror-Mechanismus auf ein zweites physisches Medium außerhalb Disk1 gesichert, mit Frequenz ≤ 6 h. Konkrete Implementierung (Optionen: `git bundle`-Bundles in separates Repo auf Cache, `rsync` auf externe Platte, separates Borg-Repo mit kürzerem Schedule) wird in `docs/SERVICES_RECOVERY.md` (zu erstellen) detailliert. Begründung: Gitea ist operative Source of Truth des GitOps; Verlust seit letztem Standard-Backup (bis zu 24 h) wäre für Recovery-Pfad unzumutbar.
|
||||
|
||||
**Erklärung Allocation-Method:** „High Water" verteilt neue Daten auf der am wenigsten gefüllten Disk bis zu einer Wasserlinie, dann auf die nächste. Praktisch unauffällig bei Single-Array-Disk; wird relevant bei Erweiterung.
|
||||
|
||||
**Split Level** bestimmt, wie tief Verzeichnisse über Disks aufgeteilt werden dürfen. Für `appdata` zwingend `1` — ein Stack-Verzeichnis bleibt auf einer Disk, wird nie aufgespalten.
|
||||
|
||||
## 5. Appdata-Sub-Struktur
|
||||
|
||||
Verbindliches Layout pro Stack:
|
||||
|
||||
```
|
||||
/mnt/user/appdata/<stackname>/
|
||||
├── config/ # Statische Konfiguration (yaml, conf, env-Dateien)
|
||||
├── data/ # Live-Datenverzeichnis (DB-Datendir, Container-Persistenz)
|
||||
├── dumps/ # Pre-Backup-Hooks schreiben hier (pg_dump, mongodump, sqlite .backup)
|
||||
├── logs/ # Container-Logs, falls nicht via journald gelöst
|
||||
└── README.md # Pflicht: Kurzbeschreibung, Restore-Zeiger, Sondereigenheiten
|
||||
```
|
||||
|
||||
**Regeln:**
|
||||
|
||||
- Ein Verzeichnis pro Stack, kein Mischen mehrerer Stacks
|
||||
- Stack-Namen sind lowercase, Bindestriche statt Unterstriche (z. B. `mail-archiver`, nicht `Mail_Archiver`)
|
||||
- `data/` enthält Live-State. **Behandlung pro Stack klassifiziert in `RESTORE_MATRIX.md`:**
|
||||
- **Reines DB-Datenverzeichnis** (z. B. raw Postgres-`data/`, raw Mongo-`data/`): wird **nicht** direkt gesichert, ist durch `dumps/` abgedeckt
|
||||
- **Echte Nutzdaten** (z. B. Immich-Uploads, Paperless-Dokumente, Gitea-Repo-Inhalte, Vaultwarden-Attachments, Mealie-Bilder, Grafana-Dashboards/Provisioning): **werden direkt gesichert** oder über expliziten Export-Mechanismus erfasst
|
||||
- **Mischformen** (z. B. ein Stack mit DB-Files und Nutzdaten im selben `data/`): pro Stack im Detail spezifiziert, mit getrennten Sub-Pfaden wo möglich
|
||||
- `dumps/` enthält von Pre-Hooks erzeugte konsistente Dumps; **wird immer von Borg gesichert**
|
||||
- `config/` ist die einzige Quelle der Container-Konfiguration; idealerweise versioniert oder aus Git deploybar
|
||||
- `README.md` pro Stack: drei Zeilen genügen (was, warum, Restore-Zeiger)
|
||||
|
||||
**Ausnahmen, wenn Container das Layout nicht zulassen:** dokumentiert in `docs/SERVICE_CATALOG.md` pro Stack, mit Verweis hier.
|
||||
|
||||
## 6. Naming-Konventionen
|
||||
|
||||
| Element | Konvention | Beispiel |
|
||||
|---------|------------|----------|
|
||||
| Stack-Name | `kebab-case`, lowercase, keine Sonderzeichen | `vaultwarden`, `mail-archiver`, `immich-server` |
|
||||
| Container-Name | identisch zu Stack-Name (oder `<stack>-<role>`), kebab-case | `postgresql-17`, `immich-postgres`, `komodo-mongo` |
|
||||
| Volume-Pfad in Compose | `/mnt/user/appdata/<stack>/<sub>` | `/mnt/user/appdata/vaultwarden/data:/data` |
|
||||
| Netz-Name | `frontend_net`, `backend_net`, oder `<stack>_internal` | `frontend_net`, `immich_internal` |
|
||||
| Env-Datei | `<stack>.env`, im Komodo-Stack-Workspace, **nicht** im Repo | — |
|
||||
|
||||
**Begründung Bindestriche:** vermeiden Verwechslung mit Shell-Variablen (Unterstriche werden in env vars verwendet), passen besser zu Docker-Compose-Konventionen, bleiben URL-safe.
|
||||
|
||||
**Migration Bestand → kebab-case (Operator-Entscheidung 2026-05-15):** existierende Stacks mit Unterstrichen oder gemischten Konventionen (`postgresql17`, `immich_postgres`, `komodo-mongo` etc.) werden im Rahmen der Recovery-Phase (Cache-Restore Phase 1, Disk1-Migration Phase 2) auf kebab-case migriert. Pro Stack: alter Name → neuer Name in `docs/RESTORE_MATRIX.md` dokumentiert. Container-Renames erfolgen sauber per Compose-Down → Rename → Compose-Up mit neuer Identität, **nicht** im laufenden Betrieb. Netzwerk-Referenzen, Healthchecks und Inter-Stack-Abhängigkeiten werden im selben Schritt mit angepasst. Nicht-migrierte Stacks am Ende der Recovery-Phase werden in §20 als bewusste Ausnahme dokumentiert oder auf einen Folge-Termin verschoben.
|
||||
|
||||
## 7. Permissions-Modell
|
||||
|
||||
| Anwendungsfall | UID:GID | Begründung |
|
||||
|----------------|---------|------------|
|
||||
| Standard-Files in `appdata/` | `99:100` (`nobody:users`) | Unraid-Standard, von shfs/Mover sauber behandelt |
|
||||
| Container-spezifischer User | individuell, dokumentiert | manche Images erwarten zwingend `1000:1000` o. ä. |
|
||||
| `secrets/`-Verzeichnis | `0:0`, mode `0700` oder `0750` | Schutz vor versehentlichem Read |
|
||||
| `logs/` | `99:100`, mode `0755` | Lesbar für Debugging, nicht weltschreibbar |
|
||||
| `dumps/` | `99:100`, mode `0750` | Backup-Hook schreibt, Borg liest |
|
||||
|
||||
**Regel:** Permission-Setzung beim Stack-Restore ist explizit per Skript oder Init-Container, nicht „erstmal `chown -R root:root` und gucken was kaputtgeht". Dokumentiert pro Stack.
|
||||
|
||||
## 8. Backup-Architektur
|
||||
|
||||
### 8.0 Aktueller Ist-Zustand vs. Ziel-Zustand
|
||||
|
||||
**Operator-Entscheidung 2026-05-15:** Backrest wird abgeschaltet. **Borg ist die alleinige Backup-Technologie.** Restic-Repos werden nicht weiterentwickelt; vorhandene Restic-Daten verbleiben als historisches Material bis zur expliziten Entsorgung.
|
||||
|
||||
**Ist-Zustand zum Zeitpunkt der Inkraftsetzung:**
|
||||
|
||||
- Borg via Hetzner Storagebox: produktiv, im Vorfall 2026-05-11 als Restore-Quelle verifiziert
|
||||
- Backrest/restic: **wird abgeschaltet** — Container nicht mehr starten, Repo-Daten archivieren oder löschen, Stack-Eintrag aus Compose entfernen
|
||||
- Appdata-Backup-Plugin (CIFS auf WD MyBookLive): **abgeschaltet, Ziel komplett aus Setup entfernt**
|
||||
- Lokales Borg-Repo auf `/mnt/user/backups/borg/`: einzurichten falls noch nicht vorhanden, als zweites Borg-Ziel neben Hetzner
|
||||
|
||||
**Ziel-Zustand (was dieses Dokument definiert):**
|
||||
|
||||
- Borg ist die einzige produktive Backup-Technologie (lokal + remote)
|
||||
- Backup-Pfade, Pre-Hooks und Verifikation einheitlich über Borg
|
||||
- Migrationspfad Backrest-Abschaltung pro Stack in `docs/RESTORE_MATRIX.md`
|
||||
|
||||
Dieses Dokument definiert die Zielarchitektur. Abweichungen zwischen Ist und Soll sind dokumentiert in `docs/RESTORE_MATRIX.md`.
|
||||
|
||||
### 8.1 Backup-Ziele
|
||||
|
||||
| Ziel | Typ | Zweck | Retention |
|
||||
|------|-----|-------|-----------|
|
||||
| `/mnt/user/backups/borg/` (lokal) | Borg-Repo auf Disk1 | Schneller lokaler Restore | 30 Tage täglich, 12 Monate monatlich |
|
||||
| Hetzner Storagebox | Borg-Repo via SSH | Off-Site, Disaster Recovery | 90 Tage täglich, 24 Monate monatlich |
|
||||
| Externe Wechselplatte | Cold Storage, manuell | Air-Gap, Recovery-Material | rotiert manuell, kein festes Schema |
|
||||
|
||||
**WD MyBookLive ist explizit kein Backup-Ziel mehr.** Eintrag in `docs/SECRETS_MAP.md` und allen Backup-Konfigs entfernt.
|
||||
|
||||
### 8.2 Backup-Inhalt
|
||||
|
||||
**Pflicht-Backup-Scope (Borg, beide Repos):**
|
||||
|
||||
- `/mnt/user/appdata/*/config/`
|
||||
- `/mnt/user/appdata/*/dumps/`
|
||||
- `/mnt/user/appdata/*/README.md`
|
||||
- `/mnt/user/appdata/*/data/` für Stacks mit Nutzdaten in `data/` (siehe §5 und Klassifikation in `RESTORE_MATRIX.md`)
|
||||
- `/mnt/user/services/`
|
||||
- `/boot/config/` (Unraid-Konfiguration)
|
||||
- **persönliche Daten vollständig im Scope** (Operator-Entscheidung 2026-05-15):
|
||||
- `/mnt/user/documents/`
|
||||
- `/mnt/user/photos/`
|
||||
- `/mnt/user/finance/`
|
||||
- `/mnt/user/projekte/`
|
||||
- `/mnt/user/media/` (falls behalten)
|
||||
|
||||
Borg dedupliziert über Läufe, der erste Vollbackup ist groß, Folge-Backups inkrementell. Retention nach §8.1.
|
||||
|
||||
**Pflicht-Excludes (mit Vorsicht — siehe §5):**
|
||||
|
||||
- `**/data/` innerhalb `appdata/*/` **nur dann**, wenn `data/` ausschließlich rohes DB-Datenverzeichnis ist (durch `dumps/` abgedeckt). Stacks mit echten Nutzdaten in `data/` (Immich, Paperless, Gitea, Vaultwarden, Mealie, Grafana etc.) sind aus diesem Exclude **explizit ausgenommen** — pro Stack klassifiziert in `docs/RESTORE_MATRIX.md`. Unklassifizierte Stacks gelten als „nicht ausschließbar" — lieber zuviel sichern als versehentlich zu wenig.
|
||||
- `**/cache/`, `**/tmp/`, `**/lost+found/` (jeweils generisch unbedenklich)
|
||||
- Container-Image-Layer (`/var/lib/docker/`)
|
||||
- VM-Disk-Images (`/mnt/user/domains/`) außer auf Operator-Entscheidung pro VM
|
||||
|
||||
### 8.3 Pre-Backup-Hooks pro DB-Stack
|
||||
|
||||
Verbindlich. Jeder Stack mit eigener DB hat einen Pre-Hook, der einen konsistenten Dump nach `appdata/<stack>/dumps/` schreibt:
|
||||
|
||||
| Stack-Typ | Befehl | Ziel |
|
||||
|-----------|--------|------|
|
||||
| Postgres | `pg_dump -Fc` oder `pg_dumpall --globals-only` | `appdata/<stack>/dumps/<dbname>.dump` |
|
||||
| MariaDB/MySQL | `mariadb-dump --single-transaction` | `appdata/<stack>/dumps/<dbname>.sql` |
|
||||
| MongoDB | `mongodump --archive=...` | `appdata/<stack>/dumps/mongo.archive.gz` |
|
||||
| SQLite (Vaultwarden, Gitea) | `sqlite3 db .backup target.db` | `appdata/<stack>/dumps/<dbname>.sqlite` |
|
||||
| Redis | `redis-cli BGSAVE` plus Kopie der `dump.rdb` | `appdata/<stack>/dumps/dump.rdb` |
|
||||
|
||||
**Dumps sind nur dann wertvoll, wenn sie regelmäßig gegen Restore-Test verifiziert werden.** Siehe Abschnitt 11.
|
||||
|
||||
### 8.4 Backup-Verifikation
|
||||
|
||||
| Frequenz | Prüfung | Pass-Kriterium |
|
||||
|----------|---------|----------------|
|
||||
| Pro Backup-Lauf | Exit-Code, Dauer im erwarteten Korridor, Archivgröße im erwarteten Korridor | alle drei grün, sonst Alarm |
|
||||
| Wöchentlich | `borg check --repository-only` auf beiden Repos | fehlerfrei |
|
||||
| Monatlich | Probe-Restore eines zufällig ausgewählten Stacks aus Hetzner-Repo in Wegwerf-Pfad | restored Files lesbar, DB-Dump per `pg_restore --list` lesbar |
|
||||
| Quartalsweise | Voll-Restore-Drill einer kompletten App-Klasse (z. B. Gitea allein) auf Test-Pfad | App startet, Login funktioniert |
|
||||
|
||||
Backup-Verifikations-Ergebnisse landen in `/mnt/user/services/backup-verify/` als Log mit Zeitstempel.
|
||||
|
||||
## 9. Mover-Policy
|
||||
|
||||
- Mover läuft nicht auf einem Schedule, der parallel zu Backup-Jobs liegt
|
||||
- Mover wird **niemals** für Shares mit `cache: only` aktiv
|
||||
- Mover wird vor planmäßigen Reboots manuell ausgelöst und abgewartet
|
||||
- Mover-Logs werden vom Posture-Check auf Anomalien geprüft
|
||||
|
||||
## 10. Network-Bezug zu Storage
|
||||
|
||||
Reine Verweis-Sektion. Authoritative Definition in `HOMELAB_ARCHITECTURE_MASTER_V2.md`.
|
||||
|
||||
- Datenbanken, die Storage-intensiv sind, hängen ausschließlich an `backend_net` (`internal: true`), niemals an `frontend_net`
|
||||
- Backup-Container hängen entweder an `backend_net` (für DB-Connect) oder eigenem internen Netz, nicht an `frontend_net`
|
||||
- Es gibt keine Storage-Pfade, die über `frontend_net`-Container exponiert werden
|
||||
|
||||
## 11. Posture-Check (Pflicht-Audit)
|
||||
|
||||
Automatisierter Check, der mindestens täglich läuft (z. B. via User-Script oder Cron im posture-check-Stack), und bei jedem Fail einen Alarm produziert.
|
||||
|
||||
**Zusätzlich zwingend ausgelöst:**
|
||||
|
||||
- **Bei jedem Boot** des Hosts (Unraid User-Script `at start`, oder systemd-äquivalent) — fängt sofort ab, wenn nach Reboot ein Mount falsch ist
|
||||
- **Vor jedem Backup-Lauf** als Pre-Hook — wenn Posture-Check fehlschlägt, **wird das Backup abgebrochen und alarmiert**, statt fragwürdige Daten zu sichern und so die Backup-Historie zu kontaminieren
|
||||
- **Nach jedem Mover-Lauf** als Post-Hook — verifiziert, dass Mover keine `cache: only`-Shares angefasst hat
|
||||
- **Vor jeder strukturellen Änderung** (Disk-Tausch, Pool-Umzug, Format-Aktion) manuell — Soll-Ist-Abgleich vor destruktiven Aktionen
|
||||
|
||||
**Alarmziel (Operator-Entscheidung 2026-05-15, normalisiert 2026-05-17): ntfy.** Problem-Alerts gehen an den selbst gehosteten ntfy-Server `https://ntfy.kaleschke.info` mit dem Topic `homelab-alerts`. Optionale Erfolgsmeldungen gehen an `homelab-info`. Push-Empfänger sind die Mobil-Geräte des Operators. Optional als Folge-Erweiterung: E-Mail-Fallback über mail-archiver-Stack als persistente Trail für historische Auswertung — nicht jetzt entscheiden, kann später dazukommen.
|
||||
|
||||
**Pflichtprüfungen:**
|
||||
|
||||
| Check | Erwartung | Bei Fail |
|
||||
|-------|-----------|----------|
|
||||
| `findmnt -no FSTYPE /mnt/cache` | `xfs` | Sofortalarm, kritisch |
|
||||
| `findmnt -no FSTYPE /mnt/disk1` | `xfs` (nach Phase 2) | Sofortalarm, kritisch |
|
||||
| `mount` enthält keinen `ntfs3`- oder `fuseblk`-Eintrag auf Cache/Array | wahr | Sofortalarm |
|
||||
| `nvme smart-log /dev/nvme0n1` Critical Warning | `0x00` | Sofortalarm |
|
||||
| `nvme smart-log` Media Errors | `0` oder stabil zur Vorwoche | Warnung |
|
||||
| D-State-Prozesse > 60s | keine | Warnung, ab 5 Min Alarm |
|
||||
| iowait über 5-Min-Mittel | < 30 % | Warnung ab 50 %, Alarm ab 80 % |
|
||||
| Letztes erfolgreiches Borg-Archiv (lokal) | < 26 h alt | Warnung ab 30 h, Alarm ab 48 h |
|
||||
| Letztes erfolgreiches Borg-Archiv (Hetzner) | < 26 h alt | Warnung ab 30 h, Alarm ab 48 h |
|
||||
| Erwartete Mindestgröße pro Borg-Archiv | matcht Profil pro Stack | Warnung |
|
||||
| CIFS/NFS-Mount-Liveness (für jeden konfigurierten Remote-Mount) | `stat` mit 10s-Timeout erfolgreich | Sofortalarm |
|
||||
| Anzahl laufender Container | matcht Soll aus Repo | Warnung |
|
||||
|
||||
**Implementierung:** Skript unter `services/posture-check/posture-check.sh` plus Kompatibilitaets-Wrapper `services/posture-check/posture_check.sh`, ausgegeben als JSON nach `/mnt/user/services/posture-check/last.json`, konsumiert von ntfy.
|
||||
|
||||
## 12. Hard Rules — Constitution
|
||||
|
||||
Diese Regeln sind nicht optional. Verstoß ist Incident, kein Feature-Request.
|
||||
|
||||
1. **Kein NTFS, exFAT, FAT32 auf Cache, Pool, Array.** Nur XFS, BTRFS, ZFS.
|
||||
2. **Keine Compose-Bind-Mounts auf `/mnt/cache/...` oder `/mnt/disk1/...`.** Immer `/mnt/user/...`.
|
||||
3. **Keine Container schreibt nach `/mnt/disks/...` (Unassigned Devices) im Dauerbetrieb.**
|
||||
4. **Keine `latest`-Image-Tags in Production-Compose.** Versionspin oder bewusster `:stable`/`:lts`-Alias.
|
||||
5. **Keine Secrets im Repo.** Niemals.
|
||||
6. **Kein Backup-Ziel über CIFS mit Hard-Mount ohne Liveness-Check.**
|
||||
7. **Keine produktive Stack-Änderung ohne Compose-Commit im Repo.**
|
||||
8. **Keine Disk- oder Pool-Änderung ohne Update dieses Dokuments im selben Commit.**
|
||||
9. **Kein neuer Stack ohne Eintrag in `STORAGE_LAYOUT.md` (Share/Pfad), `SERVICE_CATALOG.md` (Funktion), `RESTORE_MATRIX.md` (Restore-Pfad).**
|
||||
10. **Kein Pre-Backup-Hook, der DBs nicht konsistent dumped, für Stacks mit eigener DB.**
|
||||
11. **Kein produktiver Stack ohne dokumentierten Restore-Pfad in `docs/RESTORE_MATRIX.md`.** Idealerweise mit dokumentiertem Restore-Test (≤ 90 Tage alt); bei fehlendem Test mindestens schriftliche Restore-Schritte und Backup-Quelle. Stacks ohne diesen Eintrag laufen nicht produktiv — entweder dokumentieren oder abschalten.
|
||||
12. **Kein Backup-Lauf ohne vorgeschalteten Posture-Check (siehe §11).** Backup auf kompromittiertem Filesystem überschreibt unter Umständen den letzten guten Stand und kontaminiert die Backup-Historie.
|
||||
|
||||
**Dokumentierte Host-Observability-Ausnahmen (Operator-Entscheidung 2026-05-16):**
|
||||
`glances`, `scrutiny`, `monitoring-promtail`, `monitoring-node-exporter` und `monitoring-cadvisor` duerfen gezielt Host-/Device-Bind-Mounts ausserhalb `/mnt/user/...` nutzen, weil ihre Kernfunktion sonst nicht erfuellbar ist. Erlaubt sind nur die in `docs/SERVICE_CATALOG.md` pro Dienst genannten Binds. Diese Ausnahmen sind keine Datenpersistenz-Pfade und duerfen nicht fuer Appdaten, Backups oder normale Service-Konfiguration erweitert werden. Neue oder geaenderte Host-Binds brauchen eine explizite Doku-Aenderung im selben Commit.
|
||||
|
||||
## 13. Soft Rules — Konventionen
|
||||
|
||||
Erwartet, aber begründbare Abweichungen sind dokumentiert.
|
||||
|
||||
- Stack-Verzeichnisse werden mit der Konvention aus Abschnitt 5 angelegt
|
||||
- Ein Stack hat einen `README.md` in seinem `appdata/`-Verzeichnis
|
||||
- Image-Tags werden quartalsweise auf Updates geprüft, dokumentiert in einem Update-Log
|
||||
- Container ohne klare Eigentümerschaft werden quartalsweise reviewed (rauswerfen oder in Doku aufnehmen)
|
||||
- Neue Shares werden grundsätzlich mit Cache-Setting `no` angelegt, Promotion auf `prefer`/`only` ist bewusste Entscheidung
|
||||
- Compose-Dateien sind kommentiert, wenn die Konfiguration nicht selbsterklärend ist
|
||||
|
||||
## 14. Anti-Patterns mit Begründung
|
||||
|
||||
| Anti-Pattern | Warum Verboten |
|
||||
|--------------|----------------|
|
||||
| Direkter `/mnt/cache/X`-Bind-Mount in Compose | Pfad verschwindet, wenn Share-Setting auf Array migriert wird; bricht beim ersten Mover-Lauf |
|
||||
| `chown -R 1000:1000 /mnt/user/appdata` blanket | Bricht alle Stacks, die andere UIDs erwarten; bei Mover-Lauf werden Permissions ggf. überschrieben |
|
||||
| Backup direkt von Live-DB-Datendir | Inkonsistent unter Last; WAL-Divergenz nach Restore möglich |
|
||||
| `latest`-Tags in Production | Reproduzierbarkeit weg; Rollback nur per Glück möglich |
|
||||
| Mehrere Stacks im selben Appdata-Verzeichnis | Backup-Excludes greifen falsch; Restore betrifft Nachbar-Stack |
|
||||
| Cache-Filesystem ohne automatisierten Posture-Check | Genau der Vorfall vom 2026-05-11 |
|
||||
| Hard-mounted CIFS auf wackelige Geräte | D-State-Prozesse können den ganzen Host blockieren |
|
||||
| Secret in Compose-Env (statt `env_file`) | Secret im Repo lesbar; bei `git log -p` für immer drin |
|
||||
| Stack ohne dokumentierten Restore-Pfad | Bei Recovery muss improvisiert werden; das war der Auslöser dieses Dokuments |
|
||||
|
||||
## 15. Migrations- und Wachstumspfade
|
||||
|
||||
### 15.1 NTFS-zu-XFS-Migration (Cache, Phase 1)
|
||||
|
||||
Behandelt in `docs/DISASTER_RECOVERY.md`, Sektion „Cache Recovery 2026-05".
|
||||
|
||||
### 15.2 NTFS-zu-XFS-Migration (Disk1, Phase 2)
|
||||
|
||||
Folgeprojekt nach mindestens 7 Tagen stabilem Cache-Betrieb. Eigener Plan in `docs/DISASTER_RECOVERY.md`. Grobschritte:
|
||||
|
||||
1. Selektive Sicherung aller Disk1-Inhalte auf zweite Disk oder Hetzner
|
||||
2. Hash-Verifikation der Sicherung
|
||||
3. Disk1 entfernen aus Array (oder leer, wenn Daten temporär auf zweiter Disk)
|
||||
4. Disk1 als XFS in Unraid-GUI neu formatieren
|
||||
5. Daten zurückspielen
|
||||
6. Parity neu bauen
|
||||
7. Posture-Check grün
|
||||
|
||||
### 15.3 Erweiterung um zweite Cache-NVMe
|
||||
|
||||
Trigger: Cache-Auslastung > 70 %, oder Wunsch nach RAID1 für Appdata.
|
||||
|
||||
Pfad: BTRFS-Pool aus zwei NVMe (RAID1) als Ersatz für Single-XFS-Cache. Migration über Pool-Wechsel mit zwischengeschalteter Image-Sicherung. Eigenes Migrations-Dokument vor Beginn.
|
||||
|
||||
### 15.4 Erweiterung um zweite Array-Disk
|
||||
|
||||
Trigger: Disk1 > 80 % voll, oder Performance-Bedarf.
|
||||
|
||||
Pfad: zweite Daten-Disk dem Array hinzufügen, Allocation-Method überprüfen, Split-Level pro Share validieren. Parity-Disk muss ≥ größte Daten-Disk bleiben.
|
||||
|
||||
### 15.5 Multi-Host (Hermes-Skalierung)
|
||||
|
||||
Wenn Hermes-Worker auf weiteren Hosts skaliert: dieser Storage-Layout-Plan gilt zunächst nur für `Kallilabcore`. Weitere Hosts brauchen eigenes Storage-Layout-Dokument. Gemeinsamer Storage (NFS, iSCSI, S3-API) ist ein eigener Architektur-Schritt mit eigenem Dokument.
|
||||
|
||||
## 16. Glossar
|
||||
|
||||
| Begriff | Bedeutung |
|
||||
|---------|-----------|
|
||||
| **Cache (Pool)** | Schneller Pool (NVMe), nicht Teil des Arrays, ohne Parity-Schutz, für Performance-kritische Daten |
|
||||
| **Array** | Klassische Unraid-Daten-Disks plus Parity, paritätsgeschützt, langsamer |
|
||||
| **shfs** | Unraid-User-Share-FUSE-Filesystem; vereinigt Cache und Array unter `/mnt/user/` |
|
||||
| **Mover** | Unraid-Service, der Daten zwischen Cache und Array gemäß Cache-Setting verschiebt |
|
||||
| **Cache-Setting** | Pro Share: `only`, `prefer`, `yes`, `no` — steuert Mover-Verhalten |
|
||||
| **Allocation Method** | High Water / Most Free / Fill Up — wie Daten auf Array-Disks verteilt werden |
|
||||
| **Split Level** | Maximale Verzeichnistiefe, in der Unraid Inhalte über Disks aufteilen darf |
|
||||
| **Pre-Backup-Hook** | Skript, das vor jedem Backup einen konsistenten DB-Dump erzeugt |
|
||||
| **Posture-Check** | Automatisierter Audit, der Host-Realität gegen Soll-Zustand prüft |
|
||||
| **Constitution** | Hard Rules in Abschnitt 12, nicht verhandelbar |
|
||||
|
||||
## 17. Referenzen
|
||||
|
||||
- `HOMELAB_ARCHITECTURE_MASTER_V2.md` — Gesamtarchitektur, Netze, Traefik, GitOps
|
||||
- `docs/REPO_MAP.md` — Repo-Struktur, wo welcher Stack lebt
|
||||
- `docs/SERVICE_CATALOG.md` — Pro Stack: Funktion, Abhängigkeiten, Eigenheiten
|
||||
- `docs/RESTORE_MATRIX.md` — Pro Stack: Restore-Quelle und -Verfahren
|
||||
- `docs/SECRETS_MAP.md` — Pro Secret: Speicherort, Rotation, Recovery
|
||||
- `docs/DISASTER_RECOVERY.md` — Konkrete Recovery-Pläne, inkl. NTFS-Migration
|
||||
- `docs/GITOPS_DRIFT_RUNBOOK.md` — Drift-Erkennung und -Behebung
|
||||
- `docs/AI_CONTEXT.md` — Kontext für AI-Assistenten
|
||||
- `docs/HARDWARE_INVENTORY.md` — physische Host-, Disk- und Health-Baseline
|
||||
|
||||
## 18. Changelog
|
||||
|
||||
| Version | Datum | Änderung | Autor |
|
||||
|---------|-------|----------|-------|
|
||||
| 1.0 (Draft) | 2026-05-15 | Initialfassung nach Incident 2026-05-11. Ursprung: NTFS-Cache-Korruption, fehlende Posture-Checks, ungeplante Backup-Strategie. | Operator + AI-Assistenten |
|
||||
| 1.1 (Draft) | 2026-05-15 | Operator-Review-Feedback eingearbeitet: `system`/`domains` auf `only`, `services` als recovery-kritisch markiert, `data/`-Behandlung pro Stack klassifiziert (statt blanket Exclude), Backup-Tooling Ist/Soll explizit getrennt, Posture-Check zusätzlich bei Boot/vor Backup/nach Mover, Hard Rules 11+12 ergänzt (Restore-Pfad-Pflicht, Posture-vor-Backup), Alarmziel-Optionen benannt, Review-Items in eigene Sektion §20 verschoben. | Operator + AI-Assistenten |
|
||||
| 1.2 (Draft) | 2026-05-15 | Operator-Entscheidungen #3, #4, #6, #9, #11 eingearbeitet: Backrest abgeschaltet (Borg alleinige Backup-Technologie), persönliche Daten vollständig im Pflicht-Backup-Scope, ntfy als Alarmziel verbindlich, kebab-case-Migration im Rahmen der Recovery-Phase, Mirror-Backup für Gitea-Repo-Inhalte als verbindliche Spec (Implementierung in `SERVICES_RECOVERY.md` zu detaillieren). Offen: Items #1, #2, #5, #7, #8, #10. | Operator + AI-Assistenten |
|
||||
| 1.3 (Draft) | 2026-05-15 | Operator-Entscheidungen #1, #7, #8 eingearbeitet: Disk-Größen/-Modelle als Deferred via Posture-Check-Detection (kein Blocker), optionale Stacks (Filebrowser, code-server, Speedtest, Scrutiny, Uptime-Kuma) bleiben im Layout und sind produktiv, Network-Verweis auf MASTER_V2 bestätigt. Damit alle akuten Items entschieden. Verbleibend: Items #2, #5, #10 (Retention, Schwellen-Kalibrierung, RESTORE_MATRIX-Klassifikation) — alle als Folge-Aufgaben über Inkraftsetzung hinaus, kein Commit-Blocker. | Operator + AI-Assistenten |
|
||||
| 1.4 (Active) | 2026-05-27 | Datei von `docs/STORAGE_LAYOUT.draft.md` auf `docs/STORAGE_LAYOUT.md` gehoben; Hardware-/Diskwerte aus `docs/HARDWARE_INVENTORY.md` übernommen; Gitea-Bundle-Mirror und H:/ Nearline-Pull als umgesetzte Folgepfade referenziert. Verbleibend: Retention-Kalibrierung, Monitoring-Schwellen und RESTORE_MATRIX-Detailklassifikation als normale Folgeaufgaben. | Operator + AI-Assistenten |
|
||||
|
||||
## 19. Inkraftsetzung
|
||||
|
||||
Dieses Dokument tritt in Kraft mit dem Commit der finalen Fassung in `master`-Branch des Repos `homelab-infra`. Ab Inkraftsetzung gelten alle Hard Rules; Soft Rules werden im Rahmen der laufenden Recovery-Arbeit etabliert; Posture-Check muss innerhalb von 14 Tagen nach Inkraftsetzung produktiv laufen.
|
||||
|
||||
Erste Audit-Review dieses Dokuments: spätestens 90 Tage nach Inkraftsetzung. Danach jährlich oder bei jeder strukturellen Änderung des Storage-Layouts.
|
||||
|
||||
## 20. Review Items und Folgeaufgaben
|
||||
|
||||
Diese Sektion dokumentiert erledigte Review-Punkte und verbleibende Folgeaufgaben nach Aktivierung des Dokuments.
|
||||
|
||||
| Nr. | Item | Status | Verantwortung |
|
||||
|-----|------|--------|---------------|
|
||||
| 1 | Disk-Größen und Modelle in §3 (Disk1, Parity, externe Backup-Platte) | **ERLEDIGT 2026-05-27** — Werte aus `docs/HARDWARE_INVENTORY.md` übernommen; Disk1-Filesystem ist seit 2026-05-25 XFS | Operator + Posture-Check |
|
||||
| 2 | Retention-Werte in §8.1 (Borg-Repos lokal/remote) — abhängig von tatsächlicher Storage-Kapazität | Vorschlag steht, anzupassen | Operator |
|
||||
| 3 | Backup-Tooling: Backrest abschalten, Borg alleinige Backup-Technologie | **ENTSCHIEDEN 2026-05-15** | erledigt (siehe §8.0) |
|
||||
| 4 | Backup-Scope für persönliche Daten: `documents`, `photos`, `finance`, `projekte` (und `media` falls behalten) **vollständig** im Pflicht-Scope | **ENTSCHIEDEN 2026-05-15** | erledigt (siehe §8.2) |
|
||||
| 5 | Posture-Check-Schwellen in §11 — Vorschlag konservativ, nach erstem Monitoring kalibrieren | Vorschlag steht | Operator nach 30 Tagen |
|
||||
| 6 | Alarmziel: ntfy als primärer Push-Kanal, Mail-Fallback als Folge-Erweiterung optional | **ENTSCHIEDEN 2026-05-15** | erledigt (siehe §11) |
|
||||
| 7 | Optionale Stacks (Filebrowser, code-server, Speedtest, Scrutiny, Uptime-Kuma) — bleiben im Layout, sind produktiv im Scope, folgen den Standard-Konventionen aus §5 und Backup-Pflichten aus §8.2 | **ENTSCHIEDEN 2026-05-15** | erledigt |
|
||||
| 8 | Verweis auf `HOMELAB_ARCHITECTURE_MASTER_V2.md` für Network-Architektur (§10) — Net-Architektur steht dort authoritativ, Verweis ist ausreichend | **ENTSCHIEDEN 2026-05-15** | erledigt (siehe §10) |
|
||||
| 9 | Naming-Konvention: kebab-case durchziehen, Migration im Rahmen der Recovery-Phase | **ENTSCHIEDEN 2026-05-15** | erledigt (siehe §6); pro Stack in RESTORE_MATRIX.md zu dokumentieren |
|
||||
| 10 | Pro-Stack-Klassifizierung in `RESTORE_MATRIX.md` (DB-Typ, Nutzdaten in `data/`, Dump-Verfahren, letzter Restore-Test, kebab-case-Migrationsname) — als Folge-Aufgabe aus Hard Rule §12.11 | Folge-Aufgabe | Operator + Recovery-Phase |
|
||||
| 11 | Mirror-Backup für `services/gitea/git/repositories/` auf zweites Medium, ≤ 6 h Frequenz, konkrete Implementierung in `docs/SERVICES_RECOVERY.md` zu erstellen | **ERLEDIGT 2026-05-26** — `ops/borg-ui/scripts/gitea-bundle-mirror.sh` erzeugt verifizierte Bundles; Host-Erstlauf mit 4 Bundles und `git fsck` erfolgreich | Operator + Folge-Doc |
|
||||
|
||||
Das Dokument ist mit Version 1.4 als Active geführt. Offene Punkte 2, 5 und 10 bleiben normale Folgeaufgaben und blockieren die Gültigkeit der Hard Rules nicht.
|
||||
Reference in New Issue
Block a user