28 KiB
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
- 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.
- Recovery > Convenience. Eine Konvention, die Restore vereinfacht, schlägt jede Konvention, die nur die Erstinstallation vereinfacht.
- 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.
- 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.
- 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/.... - Keine Sammelordner. Eine Datei hat genau einen logischen Eigentümer (Stack, Share, Nutzkategorie). Mischungen erzeugen Drift und brechen Backup-Excludes.
- 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:
serviceswird vor jedem strukturellen Eingriff am Array (Disk1-Migration Phase 2, Disk-Tausch, Pool-Umzug) vollständig auf zweites Medium gesichertservicesdarf nicht auf einem als instabil bekannten Datenträger leben — Warnung inposture-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,rsyncauf externe Platte, separates Borg-Repo mit kürzerem Schedule) wird indocs/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, nichtMail_Archiver) data/enthält Live-State. Behandlung pro Stack klassifiziert inRESTORE_MATRIX.md:- Reines DB-Datenverzeichnis (z. B. raw Postgres-
data/, raw Mongo-data/): wird nicht direkt gesichert, ist durchdumps/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
- Reines DB-Datenverzeichnis (z. B. raw Postgres-
dumps/enthält von Pre-Hooks erzeugte konsistente Dumps; wird immer von Borg gesichertconfig/ist die einzige Quelle der Container-Konfiguration; idealerweise versioniert oder aus Git deploybarREADME.mdpro 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 indata/(siehe §5 und Klassifikation inRESTORE_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/innerhalbappdata/*/nur dann, wenndata/ausschließlich rohes DB-Datenverzeichnis ist (durchdumps/abgedeckt). Stacks mit echten Nutzdaten indata/(Immich, Paperless, Gitea, Vaultwarden, Mealie, Grafana etc.) sind aus diesem Exclude explizit ausgenommen — pro Stack klassifiziert indocs/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: onlyaktiv - 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 anfrontend_net - Backup-Container hängen entweder an
backend_net(für DB-Connect) oder eigenem internen Netz, nicht anfrontend_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.
- Kein NTFS, exFAT, FAT32 auf Cache, Pool, Array. Nur XFS, BTRFS, ZFS.
- Keine Compose-Bind-Mounts auf
/mnt/cache/...oder/mnt/disk1/.... Immer/mnt/user/.... - Keine Container schreibt nach
/mnt/disks/...(Unassigned Devices) im Dauerbetrieb. - Keine
latest-Image-Tags in Production-Compose. Versionspin oder bewusster:stable/:lts-Alias. - Keine Secrets im Repo. Niemals.
- Kein Backup-Ziel über CIFS mit Hard-Mount ohne Liveness-Check.
- Keine produktive Stack-Änderung ohne Compose-Commit im Repo.
- Keine Disk- oder Pool-Änderung ohne Update dieses Dokuments im selben Commit.
- Kein neuer Stack ohne Eintrag in
STORAGE_LAYOUT.md(Share/Pfad),SERVICE_CATALOG.md(Funktion),RESTORE_MATRIX.md(Restore-Pfad). - Kein Pre-Backup-Hook, der DBs nicht konsistent dumped, für Stacks mit eigener DB.
- 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. - 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.mdin seinemappdata/-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
noangelegt, Promotion aufprefer/onlyist 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:
- Selektive Sicherung aller Disk1-Inhalte auf zweite Disk oder Hetzner
- Hash-Verifikation der Sicherung
- Disk1 entfernen aus Array (oder leer, wenn Daten temporär auf zweiter Disk)
- Disk1 als XFS in Unraid-GUI neu formatieren
- Daten zurückspielen
- Parity neu bauen
- 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, GitOpsdocs/REPO_MAP.md— Repo-Struktur, wo welcher Stack lebtdocs/SERVICE_CATALOG.md— Pro Stack: Funktion, Abhängigkeiten, Eigenheitendocs/RESTORE_MATRIX.md— Pro Stack: Restore-Quelle und -Verfahrendocs/SECRETS_MAP.md— Pro Secret: Speicherort, Rotation, Recoverydocs/DISASTER_RECOVERY.md— Konkrete Recovery-Pläne, inkl. NTFS-Migrationdocs/GITOPS_DRIFT_RUNBOOK.md— Drift-Erkennung und -Behebungdocs/AI_CONTEXT.md— Kontext für AI-Assistentendocs/HARDWARE_INVENTORY.md— physische Host-, Disk- und Health-Baseline
18. Status
Status: Active v1.4 seit 2026-05-27.
Detailhistorie und alte Review-Tabellen liegen in der Git-Historie. Aktuelle Folgepunkte stehen nicht mehr hier, sondern in docs/AUDIT_2026-05-25_TODO.md.