Compare commits
88 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ab8bfea7c8 | |||
| 92562dfc9c | |||
| c9c8f9e7ce | |||
| 1d98945a67 | |||
| 9ffcb4e92e | |||
| 99a0bfd60e | |||
| e835dfd6ed | |||
| 6e928b6944 | |||
| 60015c1e2c | |||
| e1afd08bf3 | |||
| 268df30a13 | |||
| 80a5ad24a2 | |||
| 28406ae22b | |||
| 7b6c03b433 | |||
| 59b93924fb | |||
| aecf3b2807 | |||
| 8e820ea155 | |||
| 16a266cd79 | |||
| 69ad9d1d3c | |||
| 96fcacc6f7 | |||
| 076676d9b3 | |||
| dde441915a | |||
| db1fa7c3f0 | |||
| b8b0af9e27 | |||
| 4867d632d2 | |||
| 90ef6374a5 | |||
| e6a0e9fea4 | |||
| 10ef703a4e | |||
| 0c08d68d2b | |||
| 73120869a7 | |||
| 1503239881 | |||
| 5c211faf87 | |||
| f2923aac62 | |||
| 67ec40b762 | |||
| abf7137aea | |||
| 8095ab8b5d | |||
| 3bd35434d6 | |||
| b38b5e2db3 | |||
| 75afde5935 | |||
| 70b1ffa190 | |||
| 11a91d8a1e | |||
| ad9267c66a | |||
| 489958af18 | |||
| c16d62a04a | |||
| bdae014bff | |||
| 30aa696e61 | |||
| e4b0db2af6 | |||
| 1a4929f9ef | |||
| 2c0076c6a6 | |||
| 7da64ff316 | |||
| 12b63531d1 | |||
| 3daea94982 | |||
| 0ca29069c7 | |||
| eedb08316d | |||
| 54a7a0e783 | |||
| c677ef0515 | |||
| 2b60a58753 | |||
| 7d64248710 | |||
| edcb34c3f3 | |||
| 19604e0114 | |||
| 3c71a66c55 | |||
| 24d0d90670 | |||
| 0ae44bd797 | |||
| 0723eccca1 | |||
| 3bfecdd291 | |||
| c4fd4154db | |||
| dddb33d900 | |||
| 8eac93c1a5 | |||
| cfa02ce627 | |||
| 52414c47be | |||
| a8c440d4da | |||
| 12cf8fb728 | |||
| 5b0782a8fa | |||
| a805f03481 | |||
| 4feecf4a8e | |||
| 2e84700326 | |||
| 8a19c45485 | |||
| 6a445094bd | |||
| fc59e35c57 | |||
| 8e111d1e04 | |||
| 85a0eb4c3a | |||
| 38c3d87722 | |||
| c5d231a0db | |||
| 48099fb48d | |||
| 5c5ca2fcec | |||
| 3b438324dc | |||
| 0625594443 | |||
| 5936a4d9c1 |
@@ -10,9 +10,10 @@ Claude muss vor jeder fachlichen oder technischen Aenderung mindestens diese Dat
|
||||
|
||||
1. `HOMELAB_ARCHITECTURE_MASTER_V2.md`
|
||||
2. `docs/WORKFLOW.md`
|
||||
3. `docs/REPO_MAP.md`
|
||||
4. `docs/SERVICE_CATALOG.md`
|
||||
5. die betroffene `docker-compose.yml`
|
||||
3. `docs/README.md`
|
||||
4. `docs/REPO_MAP.md`
|
||||
5. `docs/SERVICE_CATALOG.md`
|
||||
6. die betroffene `docker-compose.yml`
|
||||
|
||||
Zusaetzlich je nach Thema:
|
||||
|
||||
|
||||
@@ -271,7 +271,7 @@ Legende Status:
|
||||
| `immich_server` | ✅ | `immich_default`, `frontend_net` | Traefik | aktiv via `immich.kaleschke.info` | — |
|
||||
| `immich_machine_learning` | ✅ | `immich_default` | intern | bleibt intern | — |
|
||||
| `nextcloud` | ✅ | `frontend_net`, `nextcloud_internal` | Traefik | aktiv via `cloud.kaleschke.info`, nativer Nextcloud-Login, WebDAV/CardDAV faehig | CalDAV/CardDAV-Redirect via Traefik-Labels |
|
||||
| `plex` | ✅ | `host` | Plex native / Host-Netz | Compose-Stack unter `host-services/plex/`; Host-Netz bleibt fuer Discovery / Plex GDM dokumentierte Ausnahme | — |
|
||||
| `plex` | ✅ | `host` | Plex native, **LAN/Tailscale-only** (Remote Access aus seit 2026-05-28) | Compose-Stack unter `host-services/plex/`; Host-Netz bleibt fuer Discovery / Plex GDM dokumentierte Ausnahme; Server geclaimt von `Xeridos`; Smart-TVs (Schlafzimmer, Wohnzimmer) ueber WLAN-LAN per mDNS | — |
|
||||
|
||||
### 7.5 Admin / Operations
|
||||
|
||||
@@ -395,7 +395,7 @@ Für den laufenden Betrieb gilt stattdessen:
|
||||
| `Komodo` | Docker-Socket Zugriff | Stack-Deployments benötigen Socket |
|
||||
| `glance-docker-socket-proxy` | Docker-Socket read-only | Glance benoetigt Containerstatus; Zugriff wird ueber einen internen Socket-Proxy auf lesende Docker-API-Endpunkte begrenzt und nicht ins `frontend_net` gelegt |
|
||||
| `Komodo` | keine pauschale zentrale Middleware | Webhooks (`/listener`), API und Periphery-WebSocket (`/ws/periphery`) sollen nicht durch vorgeschaltete ForwardAuth gebrochen werden |
|
||||
| `gitea` | SSH-Port 222 direkt gebunden | Git-SSH-Zugang; kein HTTP-Proxy für SSH möglich |
|
||||
| `gitea` | SSH-Port 222 direkt gebunden (LAN/Tailscale) | Git-SSH-Zugang; kein HTTP-Proxy für SSH möglich. Bewusst **nicht** in FRITZ!Box-WAN freigegeben (Operator-Entscheidung 2026-05-28): Tailscale ist Operator-Pfad, GitHub-Mirror deckt DR-Bootstrap ab, SSH-Brute-Force-Vektor extern vermeiden. |
|
||||
| `ddns-updater` | bleibt in `frontend_net` statt `backend_net` | braucht Cloudflare-API-Zugang; `backend_net` ist `internal: true` |
|
||||
| `mail-archiver` | `frontend_net` + `backend_net` | braucht Internetzugang für IMAP-Abruf (GMX, Gmail) und DB-Zugang |
|
||||
| `traefik/dynamic/*` | manueller Host-Sync trotz GitOps | File-Provider bleibt bewusst fuer `middlewares.yml`, `tls.yml` und `dashboards.yml`; Komodo deployed diese Dateien nicht automatisch |
|
||||
@@ -459,6 +459,29 @@ Damit ist sofort klar:
|
||||
|
||||
## 13. Betriebserfahrungen und Entscheidungs-Log
|
||||
|
||||
### Plex Server Reclaim und LAN-only-Profil (2026-05-28)
|
||||
|
||||
Befund: Die `Preferences.xml` des Plex-Servers war seit dem 18.05.2026 13:18 jungfraeulich (391 Bytes, ohne `PlexOnlineMail`/`PlexOnlineUsername`/`PlexOnlineToken`). Der Server war damit nicht mit einem Plex.tv-Account geclaimt, obwohl die Smart-TVs ueber LAN-Discovery (mDNS/Plex-GDM) weiter funktionierten. Beim Login als `Xeridos` ueber `app.plex.tv` meldete der Server "Keine Berechtigung", weil kein Owner registriert war. Zusaetzlich war die `library_sections`-Konfiguration leer (Backups vom 19./22./28.05. ebenfalls ~370 KB statt MBs/GBs); die Bibliotheks-Konfiguration war seit dem 18.05. weg, die Filmdateien unter `/mnt/user/media/*` blieben aber intakt (~833 Verzeichnisse, davon `movies/` 1.4 TB und `Heimatfilme/` 300 GB).
|
||||
|
||||
Reclaim:
|
||||
|
||||
- Operator-Claim-Token via `https://www.plex.tv/claim` als `Xeridos` erzeugt.
|
||||
- Plex-Container per `PLEX_CLAIM=claim-... docker compose up -d --force-recreate plex` am Host-Pfad `/mnt/user/services/stacks/plex/host-services/plex` neu erstellt. Token wurde **nur** als Shell-Inline-ENV mitgegeben, **nicht** in eine `.env`-Datei, **nicht** in die Compose, **nicht** in die Komodo-Stack-ENV geschrieben.
|
||||
- Nach Erfolg: zweiter `docker compose up -d --force-recreate plex` ohne `PLEX_CLAIM`, damit der verbrauchte Token nicht im `docker inspect`-ENV-Snapshot persistiert.
|
||||
- Bash-History defensiv geleert.
|
||||
|
||||
Endstand:
|
||||
|
||||
- `PlexOnlineUsername="Xeridos"`, `PlexOnlineMail="michideheld@gmx.de"`, `PlexOnlineHome="1"`.
|
||||
- Bibliotheken neu angelegt via Plex-Web → Verwalte Mediatheken → `/data/movies`, `/data/Heimatfilme` etc.
|
||||
- `PublishServerOnPlexOnlineKey="0"` (Remote Access deaktiviert), Plex-Relay aus → Plex bleibt strikt LAN/Tailscale-only, konsistent zum Tailscale-First-Operator-Modell.
|
||||
|
||||
Konsequenzen fuer Doku/Betrieb:
|
||||
|
||||
- Plex-Home-Familien-Profil ("Familie") muss bei Bedarf neu eingeladen werden; war ohnehin nicht aktiv genutzt.
|
||||
- Watch-State aus der Zeit vor dem 18.05. ist nicht recoverbar; Filme/Serien laufen bei Wiederaufruf bei 00:00 los.
|
||||
- `host-services/plex/docker-compose.yml` enthaelt weiter `PLEX_CLAIM: ${PLEX_CLAIM:-}`, damit ein zukuenftiger Reclaim ohne Repo-Aenderung moeglich ist.
|
||||
|
||||
### Traefik — Wechsel zu reinen Docker-Labels (2026-03-28)
|
||||
Die statischen File-Provider-Konfigurationen in `/mnt/user/appdata/traefik/dynamic/` wurden vollständig bereinigt:
|
||||
- **Gelöscht:** `immich.yml`, `gitea.yml`, `mealie.yml`, `scrutiny.yml`, `vaultwarden.yml.bak`
|
||||
@@ -571,7 +594,7 @@ Mutable Tags wie `latest`, `stable`, `release` oder reine Major-Tags wurden auf
|
||||
### Authelia ohne Redis-Session-Backend (2026-05-04)
|
||||
- Authelia nutzt PostgreSQL fuer persistente Storage-Daten, aber bewusst kein Redis-Session-Backend.
|
||||
- Das haelt den Tier-1-Auth-Pfad einfacher; nach einem Authelia-Restart muessen aktive Sessions neu aufgebaut werden.
|
||||
- `infra/redis` bleibt shared Cache fuer Dienste wie Paperless, ist aber keine Authelia-Abhaengigkeit.
|
||||
- `infra/redis` ist historisch als "shared Cache" angelegt, wird aber faktisch nur von Paperless als App-Cache genutzt. Immich, Nextcloud und Mealie betreiben jeweils eigene Redis-Instanzen in ihren App-internen Netzen; Authelia laeuft bewusst ohne Redis. Eine spaetere Konsolidierung in `apps/paperless/` (analog zu Mealie/Immich/Nextcloud) bleibt fachlich denkbar, ist aber kein priorisierter Schritt.
|
||||
|
||||
### ddns-updater — Netz-Ausnahme
|
||||
Bleibt bewusst in `frontend_net` statt `backend_net`, weil `backend_net` `internal: true` ist und ddns-updater die Cloudflare-API erreichen muss.
|
||||
|
||||
@@ -8,19 +8,20 @@ Vor jeder Aenderung lesen:
|
||||
|
||||
1. `HOMELAB_ARCHITECTURE_MASTER_V2.md`
|
||||
2. `docs/WORKFLOW.md`
|
||||
3. `docs/README.md`
|
||||
|
||||
Bei Restore-, Host-Ausfall- oder Wiederanlauf-Fragen zusaetzlich:
|
||||
|
||||
3. `docs/DISASTER_RECOVERY.md`
|
||||
4. `docs/RESTORE_MATRIX.md`
|
||||
5. `docs/SERVICES_RECOVERY.md`
|
||||
4. `docs/DISASTER_RECOVERY.md`
|
||||
5. `docs/RESTORE_MATRIX.md`
|
||||
6. `docs/SERVICES_RECOVERY.md`
|
||||
|
||||
Bei Hardware-, Netzwerk-, Provider- oder Kapazitaetsfragen zusaetzlich:
|
||||
|
||||
6. `docs/HARDWARE_INVENTORY.md`
|
||||
7. `docs/NETWORK_INVENTORY.md`
|
||||
8. `docs/EXTERNAL_DEPENDENCIES.md`
|
||||
9. `docs/CAPACITY_AND_LIFECYCLE.md`
|
||||
7. `docs/HARDWARE_INVENTORY.md`
|
||||
8. `docs/NETWORK_INVENTORY.md`
|
||||
9. `docs/EXTERNAL_DEPENDENCIES.md`
|
||||
10. `docs/CAPACITY_AND_LIFECYCLE.md`
|
||||
|
||||
## Architektur
|
||||
|
||||
@@ -75,4 +76,5 @@ Bei Hardware-, Netzwerk-, Provider- oder Kapazitaetsfragen zusaetzlich:
|
||||
- Recovery-kritische Services-Pfade wie Gitea-Repositories, Komodo-Workspaces und Host-Automation sind in `docs/SERVICES_RECOVERY.md` beschrieben.
|
||||
- Hardware-, Netzwerk-, Provider- und Capacity-Inventare sind als operative Audit-Dokumente unter `docs/HARDWARE_INVENTORY.md`, `docs/NETWORK_INVENTORY.md`, `docs/EXTERNAL_DEPENDENCIES.md` und `docs/CAPACITY_AND_LIFECYCLE.md` vorbereitet.
|
||||
- Der verbindliche Detailablauf steht in `docs/WORKFLOW.md`.
|
||||
- Der Doku-Index mit aktiven und archivierten Dokumenten steht in `docs/README.md`.
|
||||
- `nextcloud`, `bentopdf` und `monitoring` folgen dem dokumentierten Netz-/Secret-/Traefik-Modell; der zentrale Monitoring-Stack buendelt Prometheus, Loki, Promtail, Grafana und InfluxDB 3 Core.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
bentopdf:
|
||||
image: bentopdfteam/bentopdf:2.8.4@sha256:f54b9ed9c56b767e0098b525468206689b666323c2b500b9686c3cf41cdfa348
|
||||
image: bentopdfteam/bentopdf:2.8.5@sha256:2d867aacb8ab5b196d00ee86944b1899d09d72df355384c5e15cf974737963a0
|
||||
container_name: bentopdf
|
||||
restart: unless-stopped
|
||||
tmpfs:
|
||||
|
||||
@@ -43,7 +43,7 @@ services:
|
||||
|
||||
redis:
|
||||
container_name: immich_redis
|
||||
image: redis:7.4-alpine@sha256:6ab0b6e7381779332f97b8ca76193e45b0756f38d4c0dcda72dbb3c32061ab99
|
||||
image: redis:8.8.0-alpine@sha256:09160599abd229764c0fb44cb6be640294e1d360a54b19985ab4843dcf2d90f1
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- immich_default
|
||||
@@ -52,14 +52,15 @@ services:
|
||||
|
||||
database:
|
||||
container_name: immich_postgres
|
||||
image: tensorchord/pgvecto-rs:pg14-v0.2.0@sha256:739cdd626151ff1f796dc95a6591b55a714f341c737e27f045019ceabf8e8c52
|
||||
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0@sha256:bcf63357191b76a916ae5eb93464d65c07511da41e3bf7a8416db519b40b1c23
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_PASSWORD_FILE: /run/secrets/postgres_password
|
||||
POSTGRES_USER: immich
|
||||
POSTGRES_DB: immich
|
||||
shm_size: 128mb
|
||||
volumes:
|
||||
- /mnt/user/appdata/immich_postgres:/var/lib/postgresql/data
|
||||
- /mnt/user/appdata/immich_postgres_vectorchord:/var/lib/postgresql/data
|
||||
- /mnt/user/appdata/secrets/immich_postgres_password.txt:/run/secrets/postgres_password:ro
|
||||
networks:
|
||||
- immich_default
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
mail-archiver:
|
||||
image: s1t5/mailarchiver@sha256:94d7525db56b13154a14203f8fb7b53fac034f28a914c32da9d2e426b49328ed
|
||||
image: s1t5/mailarchiver@sha256:ea7fd8c2e3e0ef0941e8dd9e726e35a8de33296f5c7b9ed811df5168ae6a9714
|
||||
container_name: mail-archiver
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
mealie:
|
||||
image: ghcr.io/mealie-recipes/mealie:v3.12.0@sha256:8d962f611390a1cca667eed32a29e9467e9c01c523e2db3ad00f667372067f9d
|
||||
image: ghcr.io/mealie-recipes/mealie:v3.19.2@sha256:f68e959bf66f4f458893ea58facac71690fe6f2ac7a31466b5cecb41b4e99c02
|
||||
container_name: mealie
|
||||
restart: unless-stopped
|
||||
|
||||
@@ -38,7 +38,7 @@ services:
|
||||
- traefik.http.services.mealie.loadbalancer.server.port=9000
|
||||
|
||||
mealie-postgres:
|
||||
image: postgres:17.9@sha256:5b96f1a16bd9768b060dd2ffe55cb6225c4d9ef4d214a8b21eb08134869a97e4
|
||||
image: postgres:18.4@sha256:8ff36f3c66371cba71d20ceedccfc3de9669a68737607888c4ef0af93abe8e39
|
||||
container_name: mealie-postgres
|
||||
restart: unless-stopped
|
||||
|
||||
@@ -47,10 +47,10 @@ services:
|
||||
POSTGRES_USER: mealie
|
||||
POSTGRES_DB: mealie
|
||||
POSTGRES_PASSWORD_FILE: /run/secrets/postgres_password
|
||||
PGDATA: /var/lib/postgresql/data
|
||||
PGDATA: /var/lib/postgresql/18/docker
|
||||
|
||||
volumes:
|
||||
- /mnt/user/appdata/mealie/postgres:/var/lib/postgresql/data
|
||||
- /mnt/user/appdata/mealie/postgres18:/var/lib/postgresql
|
||||
- /mnt/user/appdata/secrets/mealie_postgres_password.txt:/run/secrets/postgres_password:ro
|
||||
|
||||
networks:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
nextcloud:
|
||||
image: nextcloud:33.0.2-apache@sha256:39b2ba219271a22851f8409a7b1295d5892aba1696d9193500311c02e60591a4
|
||||
image: nextcloud:33.0.4-apache@sha256:caa40b8beaf0057ac213d8dfc515c36ce64f7a8f0825b6a287e6f7cf2f4a095d
|
||||
container_name: nextcloud
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
@@ -46,7 +46,7 @@ services:
|
||||
- "traefik.http.services.nextcloud.loadbalancer.server.port=80"
|
||||
|
||||
nextcloud-postgres:
|
||||
image: postgres:17.9@sha256:5b96f1a16bd9768b060dd2ffe55cb6225c4d9ef4d214a8b21eb08134869a97e4
|
||||
image: postgres:18.4@sha256:8ff36f3c66371cba71d20ceedccfc3de9669a68737607888c4ef0af93abe8e39
|
||||
container_name: nextcloud-postgres
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
@@ -54,9 +54,9 @@ services:
|
||||
POSTGRES_DB: nextcloud
|
||||
POSTGRES_USER: nextcloud
|
||||
POSTGRES_PASSWORD_FILE: /run/secrets/postgres_password
|
||||
PGDATA: /var/lib/postgresql/data
|
||||
PGDATA: /var/lib/postgresql/18/docker
|
||||
volumes:
|
||||
- /mnt/user/appdata/nextcloud/postgres:/var/lib/postgresql/data
|
||||
- /mnt/user/appdata/nextcloud/postgres18:/var/lib/postgresql
|
||||
- /mnt/user/appdata/secrets/nextcloud_postgres_password.txt:/run/secrets/postgres_password:ro
|
||||
networks:
|
||||
- nextcloud_internal
|
||||
@@ -64,7 +64,7 @@ services:
|
||||
- no-new-privileges:true
|
||||
|
||||
nextcloud-redis:
|
||||
image: redis:7.4-alpine@sha256:6ab0b6e7381779332f97b8ca76193e45b0756f38d4c0dcda72dbb3c32061ab99
|
||||
image: redis:8.8.0-alpine@sha256:09160599abd229764c0fb44cb6be640294e1d360a54b19985ab4843dcf2d90f1
|
||||
container_name: nextcloud-redis
|
||||
restart: unless-stopped
|
||||
command: redis-server --save 60 1 --loglevel warning
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
ntfy:
|
||||
image: binwiederhier/ntfy@sha256:2b9e12d56a538f4402da51328eeca02696c4b207ab7fbe031c27e51a22ca9b86
|
||||
image: binwiederhier/ntfy@sha256:b32b4221a64ec2e7c000f0782b2feef24022e1a09a24e531640f4cbba6cfa1e6
|
||||
container_name: ntfy
|
||||
restart: unless-stopped
|
||||
dns:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
paperless-gpt:
|
||||
image: icereed/paperless-gpt:v0.24.0@sha256:15bad5d455b98f21bb7b5d6615f56871ff67a8bb379dc0dd7ba411f4633071a6
|
||||
image: icereed/paperless-gpt:v0.25.1@sha256:c0ce6186028911101a2cfe68353f14a9dbb2653596f3f1cff94de4b6db3114ff
|
||||
container_name: paperless-gpt
|
||||
restart: unless-stopped
|
||||
security_opt:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
paperless:
|
||||
image: ghcr.io/paperless-ngx/paperless-ngx:2.20.10@sha256:07a0b4ba01ce377c82a0636e16c0c3d931fde5b7e9304de6601986cc42d9b6e6
|
||||
image: ghcr.io/paperless-ngx/paperless-ngx:2.20.15@sha256:6c86cad803970ea782683a8e80e7403444c5bf3cf70de63b4d3c8e87500db92f
|
||||
container_name: paperless-ngx
|
||||
restart: unless-stopped
|
||||
security_opt:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
unbound:
|
||||
image: shaanmajid/unbound:1.24.2@sha256:d278b71c592b2555cc802911bb0757a6a24f4a8ad7f5848720296c04876eeb63
|
||||
image: shaanmajid/unbound:1.25.1@sha256:96809ff052e8bd79bba30e067d8b27ed9a2f069b6b2a3484fe1d0eb45aba07c5
|
||||
container_name: unbound
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
gitea:
|
||||
image: docker.gitea.com/gitea:1.25.4@sha256:17d18218be2dad1f8ed402a4f906989505c90ab8b66ee9befcecfb5d470133e7
|
||||
image: docker.gitea.com/gitea:1.26.2@sha256:7d13848af12645600a5f9d93ee2560daa9c6fa6b5b859b7bff3a5e1c0b661031
|
||||
container_name: gitea
|
||||
restart: unless-stopped
|
||||
security_opt:
|
||||
@@ -26,6 +26,12 @@ services:
|
||||
- "222:22"
|
||||
networks:
|
||||
- frontend_net
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "wget -q --spider http://localhost:3000/api/healthz || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 60s
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.docker.network=frontend_net"
|
||||
|
||||
@@ -4,6 +4,9 @@ Stand: 2026-05-23
|
||||
|
||||
Ziel: Alle problemrelevanten Homelab-Meldungen landen auf einem Handy-Topic.
|
||||
|
||||
> Die Prometheus-Alarmregeln im Detail (Trigger, Schwellen, Severity,
|
||||
> Handlungshinweis, Luecken-Analyse) stehen in `docs/ALERT_RULES.md`.
|
||||
|
||||
## ntfy Topics
|
||||
|
||||
| Topic | Zweck |
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
# Alert Rules
|
||||
|
||||
Stand: 2026-05-30
|
||||
|
||||
Zentrale Nachschlagetabelle aller Prometheus-Alarmregeln plus Bewertung, ob die
|
||||
Abdeckung sinnvoll und vollstaendig ist.
|
||||
|
||||
- **Authoritative Quelle der Regeln:** `monitoring/prometheus/alerts.yml`
|
||||
- **Topic-/Sender-Konvention:** `docs/ALERTING_MAP.md`
|
||||
- Alle Prometheus-Alarme laufen ueber Alertmanager →
|
||||
`monitoring/alertmanager-ntfy-bridge/bridge.py` → ntfy-Topic `homelab-alerts`.
|
||||
|
||||
> Diese Datei ist **Doku**, nicht die Konfiguration. Wer eine Regel aendert,
|
||||
> aendert `monitoring/prometheus/alerts.yml`, pusht nach Gitea und laesst Komodo
|
||||
> deployen bzw. Prometheus neu laden. Danach diese Tabelle nachziehen.
|
||||
|
||||
## Zwei Alarm-Pfade nebeneinander
|
||||
|
||||
Nicht jeder Homelab-Alarm kommt aus Prometheus. Wer "fehlt da was?" beantworten
|
||||
will, muss beide Pfade zusammen lesen:
|
||||
|
||||
| Pfad | Quelle | Beispiele |
|
||||
|---|---|---|
|
||||
| **Prometheus / Alertmanager** | `monitoring/prometheus/alerts.yml` | Erreichbarkeit, Zertifikate, Disk/RAM, Borg-Metriken, Critical-Container |
|
||||
| **Posture-Check / ntfy-direkt** | `services/posture-check/*` | NVMe-SMART, Cert/Token-Check, Compose-Runtime-Drift, Docker `die`/`oom`/`kill`, Authelia-Drift, Borg-Pre-Hook, Restore-Jobs |
|
||||
|
||||
Beide enden auf `homelab-alerts`. Der Posture-Pfad ist in `docs/ALERTING_MAP.md`
|
||||
tabelliert; er wird hier nur referenziert, nicht dupliziert.
|
||||
|
||||
## Prometheus-Regeltabelle
|
||||
|
||||
Severity-Routing der Bridge: `critical` und `warning` gehen beide auf
|
||||
`homelab-alerts` (kein eigenes Topic je Severity).
|
||||
|
||||
### Gruppe `homelab-availability`
|
||||
|
||||
| Alarm | Trigger (PromQL, gekuerzt) | Schwelle / `for` | Severity | Was tun |
|
||||
|---|---|---|---|---|
|
||||
| `HomelabExternalConnectivityDown` | `sum(probe_success{blackbox-http}==0) >= 5` | ≥5 Endpunkte / 8m | warning | WAN/DNS/Provider pruefen, nicht pro Domain jagen — Sammelausfall |
|
||||
| `HomelabEndpointDown` | `probe_success==0` (einzeln, nicht im Sammelausfall) | 1 Endpunkt / 8m | critical | Betroffenen Dienst/Traefik-Route pruefen |
|
||||
| `HomelabEndpointSlow` | `probe_duration_seconds > 5` | >5s / 5m | warning | Dienst-/Backend-Last pruefen, oft transient |
|
||||
| `HomelabCertificateExpiresSoon` | Restlaufzeit 7–21 Tage | <21d & >7d / 30m | warning | ACME/Traefik-Renewal beobachten |
|
||||
| `HomelabCertificateExpiresCritical` | Restlaufzeit ≤7 Tage (oder abgelaufen) | ≤7d / 15m | critical | Renewal sofort erzwingen/pruefen |
|
||||
|
||||
### Gruppe `homelab-host`
|
||||
|
||||
| Alarm | Trigger (PromQL, gekuerzt) | Schwelle / `for` | Severity | Was tun |
|
||||
|---|---|---|---|---|
|
||||
| `HomelabDiskAlmostFull` | `100*(1-avail/size) > 85` (ohne tmpfs/overlay) | >85% / 10m | warning | Mountpoint aufraeumen / erweitern |
|
||||
| `HomelabDiskCritical` | `100*(1-avail/size) > 95` (ohne tmpfs/overlay) | >95% / 5m | critical | Sofort Platz schaffen — Writes drohen zu scheitern (DB, appdata, Cache) |
|
||||
| `HomelabHighMemoryUsage` | `100*(1-MemAvailable/MemTotal) > 90` | >90% / 10m | warning | Speicherfresser identifizieren, ggf. Container-Limit (F-19) |
|
||||
| `HomelabTraefik5xx` | `increase(traefik_service_requests_total{5..}[5m]) >= 5` je Service | ≥5 / 2m | warning | Backend des betroffenen Service pruefen |
|
||||
|
||||
### Gruppe `homelab-backup-and-containers`
|
||||
|
||||
| Alarm | Trigger (PromQL, gekuerzt) | Schwelle / `for` | Severity | Was tun |
|
||||
|---|---|---|---|---|
|
||||
| `HomelabTextfileExporterStale` | `time()-last_run > 2h` | >2h / 15m | warning | `export-prometheus-textfile.sh`-Cron auf Host pruefen |
|
||||
| `HomelabBorgMetricsMissing` | `absent(borg_last_completed_ts)` | fehlt / 15m | critical | Textfile-Export oder borg-ui pruefen |
|
||||
| `HomelabBorgBackupStale` | `time()-borg_last_completed_ts > 30h` | >30h / 15m | warning | Letztes Borg-Backup nachholen/pruefen |
|
||||
| `HomelabBorgLastJobFailed` | `borg_last_success != 1` | ≠1 / 15m | critical | Borg-UI-Job-Log pruefen, Backup wiederholen |
|
||||
| `HomelabBorgLastJobCompletedWithWarnings` | `borg_last_job_warning == 1` | =1 / 15m | warning | Warnung im Borg-UI-Job lesen |
|
||||
| `HomelabCriticalContainerDown` | `homelab_critical_container_running == 0` | =0 / 5m | critical | Container neu starten / Komodo-Stack pruefen (`name`-Label) |
|
||||
|
||||
Die Liste der ueberwachten Critical-Container steht in
|
||||
`services/posture-check/export-prometheus-textfile.sh` (`CRITICAL_CONTAINERS`).
|
||||
|
||||
### Gruppe `homelab-meta`
|
||||
|
||||
| Alarm | Trigger (PromQL, gekuerzt) | Schwelle / `for` | Severity | Was tun |
|
||||
|---|---|---|---|---|
|
||||
| `HomelabPrometheusTargetDown` | `up == 0` | =0 / 5m | critical | Scrape-Ziel (node-exporter/cadvisor/blackbox/traefik) pruefen — Metriken sind sonst still |
|
||||
|
||||
## Bewertung: Sind die Alarme sinnvoll?
|
||||
|
||||
Insgesamt solide. Die Erreichbarkeits-Gruppe ist gut entworfen — der
|
||||
Sammelausfall-Trick (`>=5` Endpunkte als ein Warning, Einzelausfall als
|
||||
Critical) verhindert eine ntfy-Flut bei kurzen DSL-Reconnects. Borg ist mit vier
|
||||
Regeln (fehlende Metrik, veraltet, fehlgeschlagen, mit Warnungen) gut
|
||||
abgedeckt.
|
||||
|
||||
Anmerkungen / Feinschliff (kein Handlungsdruck):
|
||||
|
||||
- **`HomelabDiskAlmostFull` ohne Array-Filter.** Der `fstype!~"tmpfs|overlay"`-
|
||||
Filter schliesst keine bewusst vollen Unraid-Array-Disks aus. Eine
|
||||
Datengrab-Disk, die dauerhaft bei 90 % liegt, erzeugt einen Dauer-Warning.
|
||||
Bei Bedarf per `mountpoint`-Filter auf die wirklich kritischen Pfade
|
||||
(`/`, appdata-/services-Cache) eingrenzen.
|
||||
- **`HomelabEndpointSlow` >5s** ist grosszuegig und damit eher ruhig — okay als
|
||||
bewusste Wahl, faengt aber keine schleichende 3–4s-Degradierung.
|
||||
- **`HomelabHighMemoryUsage` 90 %** ist auf einem Host mit ZFS/Unraid-Cache
|
||||
schnell erreicht (Cache zaehlt nicht als „available" je nach Messung); die
|
||||
Verwendung von `MemAvailable` ist hier korrekt und mildert das.
|
||||
|
||||
## Bewertung: Fehlt etwas? (Luecken, priorisiert)
|
||||
|
||||
### Hoch — erledigt 2026-05-30
|
||||
|
||||
1. ~~Kein `up == 0` auf Scrape-Targets~~ → **`HomelabPrometheusTargetDown`**
|
||||
umgesetzt (Gruppe `homelab-meta`). Faellt node-exporter/cadvisor/blackbox/
|
||||
traefik aus, feuert jetzt nach 5 Minuten ein Critical.
|
||||
2. ~~Kein Disk-Critical-Tier~~ → **`HomelabDiskCritical`** bei >95 % umgesetzt
|
||||
(Gruppe `homelab-host`), zusaetzlich zum bestehenden Warning bei >85 %.
|
||||
|
||||
### Mittel — sinnvoll, aber kein Notstand
|
||||
|
||||
3. **Dead-Man's-Switch.** Faellt Prometheus oder die ntfy-Bridge selbst aus,
|
||||
feuert gar kein Alarm — strukturell blind. Eine immer feuernde
|
||||
Watchdog-Regel plus externer „Heartbeat fehlt"-Waechter (z. B. Uptime-Kuma
|
||||
Push-Monitor oder Healthchecks.io) schliesst die Luecke. Bewusst leichtes
|
||||
Gewicht, weil Posture-Check/Borg-Pre-Hook teilweise unabhaengig laufen.
|
||||
4. **Inode-Erschoepfung.** Paperless/Immich erzeugen viele kleine Dateien;
|
||||
`node_filesystem_files_free` kann vor dem Byte-Limit knapp werden. Niedrige
|
||||
Wahrscheinlichkeit, billiger Alarm.
|
||||
|
||||
### Bewusst nicht in Prometheus (anderer Pfad deckt ab)
|
||||
|
||||
- **NVMe-SMART-Verschleiss** → `check_nvme_smart` im Posture-Check (ntfy direkt).
|
||||
- **Compose-Runtime-Drift / Authelia-Drift** → Posture-Check (ntfy direkt).
|
||||
- **Docker `oom`/`die`/`kill`** → `docker-critical-events.sh` (ntfy direkt) —
|
||||
dies ist auch der Detektionspfad fuer den ersten echten OOM-Vorfall, der F-19
|
||||
(Container-Memory-Limits) ausloesen wuerde.
|
||||
- **Cert/Token-Health jenseits TLS-Ablauf** → `cert-token-check.sh`.
|
||||
|
||||
## Stand
|
||||
|
||||
Die zwei Hoch-Luecken sind seit 2026-05-30 in `alerts.yml` umgesetzt. Naechster
|
||||
optionaler Schritt waere der Dead-Man's-Switch ueber einen externen Heartbeat-
|
||||
Waechter; ohne familienkritischen Anlass aber nicht eilig.
|
||||
@@ -1,32 +1,38 @@
|
||||
# Audit TODO 2026-05-25
|
||||
|
||||
Quelle: `docs/AUDIT_2026-05-25.md`
|
||||
Quelle: `docs/archive/2026-05/AUDIT_2026-05-25.md`
|
||||
|
||||
Status: Arbeitsliste fuer die Umsetzung. Authelia-2FA/OIDC bleibt bewusst spaet, weil die Ziel-Policy noch nicht final entschieden ist.
|
||||
Status: Arbeitsliste fuer die Umsetzung. Authelia-2FA/OIDC, CrowdSec und Nextcloud-2FA-Haertung bleiben ganz hinten und werden bewusst nicht in diesem Audit-Zyklus angefasst.
|
||||
|
||||
## Leitplanken
|
||||
|
||||
- Keine Authelia-2FA-ACL-Aenderungen in den ersten Sprints.
|
||||
- Authelia-2FA-ACL, Authelia-OIDC und CrowdSec werden in diesem Audit-Zyklus **nicht** umgesetzt (Operator-Vorgabe 2026-05-26).
|
||||
- Keine Live-riskanten Bind-/Port-Aenderungen ohne vorher erfasste Host-Werte, insbesondere Tailscale-IP.
|
||||
- Erst Inventar und Baseline, dann Aenderungen.
|
||||
- Hermes-Agent ist geparkt, nicht entfernt; Review-Deadline 2026-07-25.
|
||||
- USV-Anschaffung ist verschoben; Power-Loss-Risiko ist als Operator-Entscheidung 2026-05-26 bewusst akzeptiert.
|
||||
- Borg-Passphrase ist offline gesichert (bestaetigt 2026-05-26).
|
||||
- H:/ ist evaluiert als zweite lokale Nearline-Kopie, nicht als Offsite-Ersatz (siehe `docs/CAPACITY_AND_LIFECYCLE.md`).
|
||||
- Familien-Einladung ist fuer das Wochenende **nach** Erreichen des finalen Stands geplant; Family-Onboarding muss familienverstaendlich werden, nicht technisch.
|
||||
- Jede produktive Aenderung bekommt Validierung und Rollback-Hinweis.
|
||||
|
||||
## Naechster Startpunkt 2026-05-26
|
||||
|
||||
Kontext bewusst gesichert, bevor weitere Live-Aenderungen passieren:
|
||||
|
||||
1. USV-Entscheidung treffen: aktuell ist keine funktionierende USV-Abschaltung nachgewiesen.
|
||||
2. Gitea-Bundle-/Mirror-Mechanik und Borg-Passphrase-Offsite-Sicherung entscheiden.
|
||||
3. Authelia 2FA/OIDC weiterhin nicht anfassen; das bleibt bewusst der letzte Block.
|
||||
1. Host-Schedule fuer Gitea-Bundles und Restore-Freshness pruefen.
|
||||
2. FRITZ!Box-Portfreigaben (UI) gegen Repo-Soll abgleichen (`443/tcp` + `222/tcp`).
|
||||
3. H:/ Pull-Workflow festlegen.
|
||||
4. Family-Onboarding-Doku familienverstaendlich umarbeiten, vor der Wochenend-Einladung.
|
||||
5. Authelia 2FA/OIDC und CrowdSec weiterhin nicht anfassen; bleibt bewusst der letzte Block.
|
||||
|
||||
## Sprint 0 - Inventar und Baseline
|
||||
|
||||
| Status | Aufgabe | Ergebnis |
|
||||
|---|---|---|
|
||||
| erledigt | Hardware-Inventar ausfuellen | CPU, RAM, Mainboard, BIOS, NIC, Controller, Disks, SMART und Capacity-Baseline erfasst; USV ist als nicht validiert dokumentiert |
|
||||
| in Arbeit | Netzwerk-Inventar ausfuellen | Host-IP, Gateway, Tailscale-IP und AdGuard-Bind erfasst; Router-/VLAN-Details offen |
|
||||
| erledigt | Netzwerk-Inventar ausfuellen | Host-IP, Gateway, Tailscale-IP, AdGuard-Bind und FRITZ!Box-Baseline (7590, FRITZ!OS 8.21, Telekom DSL 87/36, 36 Geraete, Gast-WLAN inaktiv, Ausfallschutz inaktiv, 2 Portfreigaben aktiv) erfasst; IPv6 und FRITZ!OS-Update bleiben Operator-Folgeaufgaben |
|
||||
| erledigt (Baseline) | Externe Abhaengigkeiten dokumentieren | `docs/EXTERNAL_DEPENDENCIES.md` enthaelt bekannte Provider, Kritikalitaet, Ausfallplaene; Account-Recovery-Codes/Zahlungswege bleiben Off-Repo-Operatorcheck |
|
||||
| erledigt (Baseline) | Services-Recovery-Pfade beschreiben | `docs/SERVICES_RECOVERY.md` enthaelt Gitea-/Komodo-/Secrets-Sonderpfade; Gitea-Bundle-/Mirror-Mechanik bleibt als Umsetzungsentscheidung offen |
|
||||
| erledigt (Baseline) | Services-Recovery-Pfade beschreiben | `docs/SERVICES_RECOVERY.md` enthaelt Gitea-/Komodo-/Secrets-Sonderpfade; Gitea-Bundle-Mechanik ist umgesetzt und per Host-Erstlauf validiert |
|
||||
| erledigt | Baseline-Tag setzen | `audit-2026-05-25-baseline` ist lokal und remote vorhanden |
|
||||
| erledigt | Policy-Check neu ausfuehren | SEC001-Warnings aus altem Report sind nicht mehr aktuell |
|
||||
|
||||
@@ -34,7 +40,7 @@ Kontext bewusst gesichert, bevor weitere Live-Aenderungen passieren:
|
||||
|
||||
| Status | Aufgabe | Ergebnis |
|
||||
|---|---|---|
|
||||
| offen | Borg-Passphrase analog sichern | Passphrase ist ohne Host/Vaultwarden wiederherstellbar |
|
||||
| erledigt | Borg-Passphrase analog sichern | Operator bestaetigt am 2026-05-26: Passphrase ist offline gesichert und ohne Host/Vaultwarden wiederherstellbar |
|
||||
| erledigt (repo) | AdGuard Admin-Bind vorbereiten | Tailscale-IP `100.80.98.33` erfasst, Compose-Soll geaendert |
|
||||
| erledigt | AdGuard Admin-Port auf Tailscale-IP binden | Live validiert: `ss -ltnp` zeigt `100.80.98.33:8082`, DNS auf Port 53 funktioniert, LAN-Zugriff auf `192.168.178.58:8082` schlaegt fehl |
|
||||
| erledigt | Alte Monitoring-Verzeichnisse entfernen | `ops/grafana-influxdb/` und `ops/loki/` sind aus dem aktiven Repo entfernt; Rollback erfolgt ueber Git-Historie |
|
||||
@@ -45,37 +51,73 @@ Kontext bewusst gesichert, bevor weitere Live-Aenderungen passieren:
|
||||
|
||||
| Status | Aufgabe | Ergebnis |
|
||||
|---|---|---|
|
||||
| offen | `docs/STORAGE_LAYOUT.draft.md` finalisieren | Datei wird als `docs/STORAGE_LAYOUT.md` Active gefuehrt |
|
||||
| offen | Disk- und Share-TBDs eintragen | Modelle, Groessen, Seriennummern, Filesysteme und Cache-Settings sind dokumentiert |
|
||||
| offen | Gitea-Repo-Mirror-Mechanik definieren | Mirror fuer `/mnt/user/services/gitea/git/repositories/` mit Frequenz <= 6 h ist spezifiziert |
|
||||
| offen | Komodo-Bootstrap-Pfad beschreiben | Kaltstart ohne laufendes Komodo ist dokumentiert |
|
||||
| offen | Immich-Restore-Test planen | Testumfang, Datenpfade und Smoke-Test-Kriterium stehen fest |
|
||||
| erledigt | `docs/STORAGE_LAYOUT.draft.md` finalisieren | Datei als `docs/STORAGE_LAYOUT.md` Active v1.4 gefuehrt; Draft-Blocker entfernt |
|
||||
| erledigt (Baseline) | Disk- und Share-TBDs eintragen | Disk-Modelle, Seriennummern, Groessen, Filesysteme und Share-Cache-Settings aus `docs/HARDWARE_INVENTORY.md` und Host-Readout 2026-05-27 uebernommen; Retention-/Schwellen-Kalibrierung bleibt Folgeaufgabe |
|
||||
| erledigt | Gitea-Repo-Mirror-Mechanik definieren | `ops/borg-ui/scripts/gitea-bundle-mirror.sh` erzeugt verifizierte Bundles unter `/mnt/user/backups/git-bundles/gitea`; Host-Erstlauf 2026-05-26: 4 Bundles, Checksums OK, `homelab-infra.bundle` klonbar und `git fsck` sauber. Schedule live seit 2026-05-27 ueber User-Script `gitea-bundle-mirror-6h` (`10 */6 * * *`); Bundles werden mit `chmod 644` geschrieben damit der Nearline-Pull sie greift. |
|
||||
| erledigt (Doku + Skript + Erstlauf) | Komodo-Bootstrap-Pfad beschreiben | `docs/SERVICES_RECOVERY.md` enthaelt linearen Bootstrap in Stufen A-F mit Recovery-Anker `ops/komodo/docker-compose.yml`, expliziter Abgrenzung zum Self-Stack, Secret-Reihenfolge und Validierungs-Kommandos; `docs/DISASTER_RECOVERY.md` Stufe 3 verlinkt auf Bootstrap-Pfad. Trockenlauf-Skript unter `ops/restore-tests/komodo-bootstrap-*` seit 2026-05-29 vorhanden, Erstlauf 2026-05-30 erfolgreich (siehe Sprint 8 Eintrag). |
|
||||
| erledigt | Immich-Restore-Test planen | Testumfang, Datenpfade und Smoke-Test-Kriterium sind in `docs/IMMICH_RESTORE_TEST.md`, `ops/restore-tests/immich-plan.md` und `ops/restore-tests/immich-runbook.md` festgehalten; erster Host-Lauf am 2026-05-27 erfolgreich |
|
||||
|
||||
## Sprint 3 - Restore und Monitoring
|
||||
|
||||
| Status | Aufgabe | Ergebnis |
|
||||
|---|---|---|
|
||||
| offen | Immich-Restore-Test implementieren | Restore-Report landet unter `/mnt/user/backups/restore-reports/` |
|
||||
| offen | Borg-Stale-Alert bauen | Alarm feuert, wenn Borg-Archiv zu alt ist |
|
||||
| offen | TLS-Cert-Expiry-Alert bauen | Alarm feuert bei Restlaufzeit unter Schwellwert |
|
||||
| offen | Container-Down-Alert bauen | Unerwartet fehlende Container werden sichtbar |
|
||||
| offen | Family-View Dashboard definieren | Uptime, Backup-Frische, Cert-Tage, Disk-Fuellung auf einer Seite |
|
||||
| erledigt | Immich-Restore-Test implementieren | Echter Host-Lauf 2026-05-27 erfolgreich: Borg-Archiv `Tägliche-Sicherung-2026-05-27T04:30:06.778`, `immich.dump` extrahiert, isolierter pgvecto-rs-Postgres importiert, Immich-Server ohne ML gestartet, HTTP `200`, Login-Marker ok, `11977` Assets und `1` User im Test-DB-Check; Report `/mnt/user/backups/restore-reports/immich-2026-05-27.md` |
|
||||
| erledigt | Borg-Stale-Alert bauen | Cron `*/5 * * * *` (`export-prometheus-textfile-5min` User-Script) schreibt `homelab.prom`; node-exporter scraped, Prometheus laedt Regel `HomelabBorgBackupStale` aktiv. Live 2026-05-27 20:33 reloaded; `lastConfigTime: 2026-05-27T18:33:06Z`; Smoke-Query `(time() - homelab_borg_last_completed_timestamp_seconds)/3600 = 16h`. Borg-Job-Warning ist aktuell `pending` (Letzter Lauf `completed_with_warnings`). |
|
||||
| erledigt | TLS-Cert-Expiry-Alert bauen | Regeln `HomelabCertificateExpiresSoon` (21d) und `HomelabCertificateExpiresCritical` (7d) sind in `alerts.yml` aktiv und nach Prometheus-Reload geladen. Smoke `inactive` (keine Cert <21d). |
|
||||
| erledigt | Container-Down-Alert bauen | `HomelabCriticalContainerDown` aktiv; Live-Smoke 2026-05-27 `sum(homelab_critical_container_running) = 30`, alle aktuellen Critical-Container `1`. Aktualisierung alle 5 Min ueber Cron. |
|
||||
| erledigt (Spezifikation) | Family-View Dashboard definieren | `docs/FAMILY_VIEW_DASHBOARD.md` enthaelt Layout, PromQL-Queries, Thresholds und Build-Reihenfolge fuer ein `homelab-family-view`-Dashboard. JSON wird bewusst erst angelegt, sobald Borg-Stale-/Cert-Expiry-/Container-Down-Metriken stabil live sind und ein manueller Build im Grafana-UI das Layout bestaetigt hat. |
|
||||
|
||||
## Sprint 4 - Familien- und Betriebsdoku
|
||||
|
||||
| Status | Aufgabe | Ergebnis |
|
||||
|---|---|---|
|
||||
| offen | Familien-Onboarding schreiben | Nextcloud, Immich, Vaultwarden, 2FA-Verlust, Ausfallverhalten kurz erklaert |
|
||||
| erledigt (Baseline) | Capacity-/Lifecycle-Review erstellen | Cache 6 %, Array/User-Shares 33 %, lokale Backups 2.2G; externe Backup-/Cold-Storage-Groessen bleiben offen |
|
||||
| offen | USV-Test oder USV-Entscheidung | Power-Loss-Verhalten ist bekannt und dokumentiert |
|
||||
| erledigt (final vor Einladung) | Familien-Onboarding schreiben | `docs/FAMILY_ONBOARDING.md` ist final redigiert: familienverstaendliche Sprache, App-eigene 2FA statt SSO-Versprechen, neuer "Bewusst nicht versprochen"-Block (kein Einheits-Login, kein 24/7-SLA, kein Hotline-Support, keine Datenweitergabe), konkrete Was-tun-Anleitungen. Einladungstermin bleibt Operator-Aufgabe. |
|
||||
| erledigt (Baseline) | Capacity-/Lifecycle-Review erstellen | Cache 6 %, Array/User-Shares 33 %, lokale Backups 2.2G; H:/-Nearline-Bewertung ergaenzt; zweites Off-site/Cold-Storage bewusst nicht umgesetzt |
|
||||
| erledigt | USV-Test oder USV-Entscheidung | Operator-Entscheidung 2026-05-26: aktuell keine USV-Anschaffung; Power-Loss-Risiko wird bewusst akzeptiert und dokumentiert |
|
||||
| erledigt (Baseline) | H:/ als zusaetzliches lokales Backupziel bewerten | Als zweite Nearline-Kopie und Freeze-Sicherung sinnvoll; kein Offsite-Ersatz, kein CIFS-Hard-Mount am Unraid; Pull-Modell vom Windows-PC ist der getestete Weg (siehe `docs/CAPACITY_AND_LIFECYCLE.md`) |
|
||||
| erledigt 2026-05-28 | H:/ Groesse und Pull-Schedule festschreiben | Groesse erfasst: 8.0T NTFS, 3.91T belegt, 4.10T frei, `Healthy`. Erster echter Pull 2026-05-27 20:45 erfolgreich: 19 Borg-Dumps + 10 Gitea-Bundle-Files unter `H:\kallilab-nearline-backups`. `unraid-flash-config.*` bewusst ausserhalb Scope (`/XF`-Exclude, Restore aus Hetzner-Borg). Windows Scheduled Task `KalliLab H Drive Nearline Pull` laeuft seit 2026-05-28 taeglich 05:30. |
|
||||
| erledigt 2026-05-28 | FRITZ!Box-Portfreigaben gegen Repo-Soll abgleichen | Bereinigt: `80/tcp` entfernt (Mobilfunk-validiert: HTTP timeout, HTTPS weiter erreichbar). `222/tcp` bleibt bewusst nicht eingerichtet (Tailscale-only-Linie). UPnP-Selbstfreigabe-Recht fuer `PC-192-168-178-71` (VONETS-Bridge, vermutlich SolarEdge-Wechselrichter) deaktiviert. Aktiver Endstand: ausschliesslich `443/tcp -> 192.168.178.58`. Details in `docs/archive/2026-05/FRITZBOX_PORT_CORRECTION_PLAN.md`. |
|
||||
|
||||
## Sprint 5 - Auth und Frontdoor, bewusst zuletzt
|
||||
|
||||
In diesem Audit-Zyklus werden diese Punkte **nicht** umgesetzt. Sie sind dokumentiert, damit sie bei einer kuenftigen Policy-Entscheidung sofort priorisiert werden koennen.
|
||||
|
||||
| Status | Aufgabe | Begruendung der Parkung |
|
||||
|---|---|---|
|
||||
| geparkt | Authelia 2FA fuer Operator-UIs erweitern (F-04) | Operator-Vorgabe 2026-05-26: keine Auth-Aenderungen in diesem Zyklus |
|
||||
| geparkt | Authelia OIDC fuer Apps pruefen (F-13) | Operator-Vorgabe 2026-05-26: keine Auth-Aenderungen in diesem Zyklus |
|
||||
| geparkt | CrowdSec vor Traefik pruefen (F-14) | Operator-Vorgabe 2026-05-26: erst nach finaler Auth-Policy |
|
||||
| geparkt | Nextcloud-2FA-/Brute-Force-Haertung dokumentieren (F-18) | beruehrt Auth-Policy fuer Familien-Konten; gemeinsam mit OIDC-Entscheidung |
|
||||
|
||||
## Sprint 6 - Geparkte Apps und Folgeentscheidungen
|
||||
|
||||
| Status | Aufgabe | Naechster Pruefschritt |
|
||||
|---|---|---|
|
||||
| geparkt | Hermes-Agent (F-06) — Operator-Entscheidung produktiv vs. entfernen | Review-Deadline **2026-07-25**; bis dahin bleibt der NAS-Stack deaktiviert, das Repo-Verzeichnis erhalten, Dashboard-Domain und ACL-Eintrag unveraendert |
|
||||
| erledigt 2026-05-28 | paperless-gpt / BentoPDF Nutzungsentscheidung | Operator behaelt beide. **paperless-gpt** bleibt bis Paperless-NGX 3.0 (erwartete native KI-Features); danach neu bewerten. **BentoPDF** bleibt als situatives Tool (Resource-Footprint ~4 MB). Beide ohne aktive Traefik-Zugriffe in der letzten Woche, aber bewusste Behalten-Entscheidung mit Begruendungs-Anker im SERVICE_CATALOG. |
|
||||
| erledigt 2026-05-28 | Plex Remote Access in UI deaktivieren falls nur LAN/Tailscale (F-17) | Beim Versuch entdeckt: Server war seit 18.05. unclaimed und Bibliotheken leer. Reclaim als `Xeridos` via inline `PLEX_CLAIM`-Token, danach Bibliotheken (`/data/movies` 1.4 TB, `/data/Heimatfilme` 300 GB) neu angelegt und Remote Access deaktiviert (`PublishServerOnPlexOnlineKey=0`, Plex-Relay aus). Details in `HOMELAB_ARCHITECTURE_MASTER_V2.md` Sektion 13. |
|
||||
| erledigt | `infra/redis` Doku-Etikett korrigieren (F-16) | SERVICE_CATALOG, REPO_MAP, MASTER (Sektion 13) und DISASTER_RECOVERY Bootstrap-Stufe 2 auf "primaer Paperless-Redis" praezisiert; keine Compose-Aenderung |
|
||||
| erledigt | Paperless-DBPass DR-Restore-Reihenfolge in DR-Doc (F-20) | DISASTER_RECOVERY 6.2.1 (Restore-Quellen fuer Stack-ENV-Werte) ergaenzt; SECRETS_MAP um Abschnitt "Stack-ENV-only Secrets - Restore-Wege" mit Reihenfolge Komodo-Mongo-Dump -> Vaultwarden -> externe Notiz erweitert; Paperless, Immich, Mail-Archiver, Speedtest, Komodo, Hermes und Glance je mit Restore-Quelle dokumentiert |
|
||||
|
||||
## Sprint 8 - Reife der Stack-Hygiene (2026-05-29)
|
||||
|
||||
| Status | Aufgabe | Ergebnis |
|
||||
|---|---|---|
|
||||
| geparkt | Authelia 2FA fuer Operator-UIs erweitern | Erst nach finaler Policy-Entscheidung |
|
||||
| geparkt | Authelia OIDC fuer Apps pruefen | Erst nach Familien-/Client-Auswirkungsanalyse |
|
||||
| geparkt | CrowdSec vor Traefik pruefen | Nach stabiler Auth-/Monitoring-Basis |
|
||||
| erledigt 2026-05-29 | Healthchecks fuer Tier-1 (F-15) | postgresql17 (`pg_isready`), Redis (`redis-cli ping` mit Auth), Vaultwarden (`curl /alive`), Gitea (`wget /api/healthz`), Traefik (`traefik healthcheck --ping`, `--ping=true` in CLI), Authelia (`wget /api/health`, weil v4.39 `helper health-check` entfernt hat); komodo-mongo war bereits gepinnt healthy. Live-Smoke: alle 6 healthy nach Recreate. Postgres- und Gitea-Stack-Workspace waren Komodo-seitig zurueckgeblieben (124 bzw. 52 commits behind); manuell per `cp` + `docker compose up -d` synchronisiert. |
|
||||
| erledigt 2026-05-29 | Monitoring-Stack Digest-Pinning (F-07) | 9 Container in `monitoring/docker-compose.yml` per Tag@sha256 gepinnt: prometheus, alertmanager, alertmanager-ntfy-bridge (python:3.13-alpine), blackbox-exporter, loki, promtail, grafana, node-exporter, cadvisor. Digests aus dem aktuell laufenden Container ausgelesen, damit der Pin den Live-Stand reflektiert. influxdb3-core war bereits gepinnt. |
|
||||
| erledigt 2026-05-29 (Skript) / 2026-05-30 (Erstlauf) | Komodo-Bootstrap-Trockenlauf-Skript (F-09 Rest) | `ops/restore-tests/komodo-bootstrap-{compose.test.yml,test.sh,plan.md,runbook.md}` analog zum Immich-Restore-Test angelegt. Test-Compose nutzt dieselben Image-Digests wie Produktion, isoliert unter Project `restoretest-komodo`, Test-Periphery ohne docker.sock-Mount, Test-Port nur `127.0.0.1:19120`. Wegwerf-Secrets im Compose. **Erstlauf 2026-05-30 erfolgreich**: Result `SUCCESS`, alle 5 Checks gruen — compose config valid, Test-Mongo healthy (6s), Mongo authenticated ping ok, Komodo Core HTTP `200`, Test-Periphery container state `running`. Report unter `/mnt/user/backups/restore-reports/komodo-bootstrap-2026-05-30.md`. Produktive Komodo-Container, Mongo-Datadir und Secrets nicht beruehrt. Damit ist `ops/komodo/docker-compose.yml` als Recovery-Anker belegt tauglich (nicht mehr nur angenommen). |
|
||||
| erledigt 2026-05-29 | Renovate-Bot gegen Gitea (F-12) | Live: Service-Account `renovate` (uid 2, kein Admin) angelegt, Collaborator Write auf `Micha/homelab-infra`, PAT in `/mnt/user/appdata/secrets/renovate_token.txt` (chmod 600). Cron `renovate-six-hourly` (`20 */6 * * *`) live in `/etc/cron.d/root`. Erstlauf 2026-05-29 erfolgreich: 5 PRs (mongo digest+minor, postgres digest+minor, minor-and-patch-updates gruppiert), 1 Dependency-Dashboard-Issue, 8 Branches. Komodo-Major durch packageRule deaktiviert wie erwartet. Architektur-Detail: Repo-Config in `renovate.json`, Bot-Config in `ops/renovate/bot-config.js` (Renovate liest die im Repo nur als Repo-Config, Bot-Settings dort triggern "forbidden/disabled"). |
|
||||
| erledigt 2026-05-30 | Authelia Repo<->Host Drift-Check (F-10) | `services/authelia-diff.sh` vergleicht die `access_control:`-Sektion zwischen Repo-Baseline und Host-Datei (Default; per env `AUTHELIA_DIFF_SECTIONS` erweiterbar). OIDC-Clients/Identity-Provider und Secret-Werte bleiben bewusst aussen vor. Exit-Codes: 0 = ok, 1 = Drift, 2 = Datei fehlt, 3 = Sektion fehlt, 4 = Werkzeug fehlt. Posture-Check ruft das Skript als Check `authelia_config_drift` auf (`SKIP_AUTHELIA_DRIFT=1` skippt, `AUTHELIA_DIFF_SCRIPT` ueberschreibt den Pfad); Drift wird als Warning gemeldet, nicht Critical. Smoke-Test lokal: identische Files -> rc=0, ACL-Drift im Domain-Eintrag -> rc=1 mit unified diff. WORKFLOW.md hat jetzt eine eigene Pflicht-Sektion "Ausnahme: Authelia configuration.yml" analog zur Traefik-Dynamic-Sektion. Pflicht-Setup auf dem Host: Repo-Spiegel unter `/mnt/user/services/homelab-infra/`. |
|
||||
|
||||
## Sprint 7 - Off-site und 3-2-1
|
||||
|
||||
| Status | Aufgabe | Bemerkung |
|
||||
|---|---|---|
|
||||
| erledigt 2026-05-28 (bewusst nicht umgesetzt) | Zweites echtes Off-site (F-03) | Operator-Entscheidung 2026-05-28: kein zweites Off-site. 3-2-1 ist mit Live + lokalem Borg + Hetzner + H:/-Nearline erfuellt; ein zweites Off-site wuerde nur den Fall "Hetzner-Account verloren" zusaetzlich abdecken, Aufwand/Kosten unverhaeltnismaessig fuer Familien-Homelab. Statt dessen drei Hetzner-Haertungen als Folge-TODOs (siehe `docs/OFFSITE_BACKUP_OPTIONS.md` Beschluss-Block). Review-Trigger sind dort definiert. |
|
||||
| offen (Operator-Aufgabe) | Hetzner-Account-Hygiene ohne 2FA | Starkes, einzigartiges Passwort in Vaultwarden + Backup-Zahlungsweg + Login-Benachrichtigungen per E-Mail. 2FA bewusst nicht (analog USV: Risiko bewusst akzeptiert, Aufwand ueberwiegt Risiko-Reduktion fuer Familien-Homelab). |
|
||||
| offen (Folge-Sprint) | Borg `--append-only` auf Hetzner setzen | Aktuell laeuft Repo `appdata-critical` im Mode `full`, `custom_flags` leer. Setup server-seitig in Hetzner `~/.ssh/authorized_keys` mit `command="borg serve --append-only"`. Schuetzt gegen Ransomware, die client-seitig Borg-Credentials abgreifen koennte. |
|
||||
| erledigt 2026-05-28 | H:/-Pull als Windows Scheduled Task | Task `KalliLab H Drive Nearline Pull` registriert: taeglich 05:30, `RunLevel Limited`, `AllowStartIfOnBatteries`, `StartWhenAvailable`, `ExecutionTimeLimit 2h`. Naechster Lauf 2026-05-29 05:30. Erstlauf manuell 2026-05-27 20:45 erfolgreich, Task-Setup in `docs/H_DRIVE_NEARLINE_PULL.md` aktualisiert (RunLevel-Enum-Fix `LeastPrivilege` -> `Limited`). |
|
||||
| erledigt (Routine dokumentiert) | Restore-Lab-Drill quartalsweise dokumentieren | `docs/RESTORE_DRILL_ROUTINE.md` definiert Drei-Stufen-Modell (Freshness woechentlich / Mini-Restore monatlich-bimonatlich / DR-Sanity quartalsweise), Quartals-Belegung Q1-Q4 mit Dienst-Rotation, Immich 2026-05-27 als bestaetigter Erstlauf gefuehrt, 10-Punkte-Sanity-Check, kein Host-Schedule angelegt. `ops/restore-tests/schedule.md` verweist jetzt auf Drill-Routine. |
|
||||
|
||||
## Offene Host-Werte
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Capacity and Lifecycle - KalliLab CORE
|
||||
|
||||
Status: Initiale Capacity-Baseline 2026-05-26; externe Backup-/Cold-Storage-Groessen offen.
|
||||
Status: Initiale Capacity-Baseline 2026-05-26; H:/-Nearline-Pull seit 2026-05-28 produktiv; zweites Off-site/Cold-Storage bewusst nicht umgesetzt.
|
||||
|
||||
## Zweck
|
||||
|
||||
@@ -14,8 +14,8 @@ Dieses Dokument haelt Wachstum, Schwellenwerte und Upgrade-Trigger fest. Es verh
|
||||
| Disk1 / Array | 5.5T | 1.8T | 3.7T | 80 % Planung / 90 % Aktion | gruen, 33 % belegt |
|
||||
| User Shares gesamt | 5.5T | 1.8T | 3.7T | 80 % Planung / 90 % Aktion | gruen, entspricht aktuell Disk1 |
|
||||
| Backups lokal | 5.5T geteilter Array-Space | 2.2G unter `/mnt/user/backups` | 3.7T Share-frei | Review bei Borg-/Dump-Wachstum | lokal nicht unabhaengig vom Array |
|
||||
| Hetzner Borg | TBD | TBD | TBD | TBD | TBD |
|
||||
| Externe Cold-Platte | TBD | TBD | TBD | TBD | TBD |
|
||||
| Hetzner Borg | extern / Storage Box | nicht repo-seitig gemessen | nicht repo-seitig gemessen | Borg-Stale-Alert + Account-Review | einziges echtes Off-site-Ziel |
|
||||
| Externe Cold-Platte | nicht vorhanden | - | - | Review nur bei Trigger | bewusst nicht beschafft, siehe `docs/OFFSITE_BACKUP_OPTIONS.md` |
|
||||
|
||||
Pruefkommando:
|
||||
|
||||
@@ -32,7 +32,7 @@ du -sh /mnt/user/documents /mnt/user/photos /mnt/user/media /mnt/user/backups 2>
|
||||
| Medien | aktuell ca. 1.7T | groesster Speicherblock | Array-Erweiterung vor 80 % planen |
|
||||
| Immich Fotos/Videos | aktuell ca. 23G | hoechster privater Datentopf | Restore-Test priorisieren |
|
||||
| Paperless/Dokumente | aktuell ca. 199M im Documents-Share | wichtig, moderates Wachstum | Restore-Test existiert, Share-Wachstum beobachten |
|
||||
| Nextcloud | TBD | Familiennutzung kann stark wachsen | Quota/Backup pruefen |
|
||||
| Nextcloud | aktuell klein, kann durch Familiennutzung stark wachsen | Datenwachstum und Quotas koennen spaeter relevant werden | Quota/Backup bei Familien-Onboarding pruefen |
|
||||
| Monitoring/Loki | begrenzt durch Retention | Retention kann Disk fuellen | Retention und Volume-Groesse bei Reviews pruefen |
|
||||
| Borg Dumps | aktuell ca. 2.2G lokale Backups | Retention und Excludes pruefen | Borg-Stale + Groessenprofil |
|
||||
|
||||
@@ -47,19 +47,51 @@ du -sh /mnt/user/documents /mnt/user/photos /mnt/user/media /mnt/user/backups 2>
|
||||
| RAM >90 % ueber 10 Minuten regelmaessig | RAM-Ausbau oder Service-Limits pruefen |
|
||||
| Borg-Laufzeit deutlich steigend | Scope, Netzwerk und Ziel pruefen |
|
||||
| SMART-Warnung | Ersatz planen, Restore-/Backup-Frische pruefen |
|
||||
| Keine USV-Abschaltung | USV anschaffen/anschliessen oder Power-Loss-Risiko bewusst akzeptieren |
|
||||
| Keine USV-Abschaltung | Risiko ist per Operator-Entscheidung 2026-05-26 bewusst akzeptiert; bei Stromausfaellen/Datenkorruption neu bewerten |
|
||||
|
||||
## H:/ als zusaetzliches lokales Backup-Ziel
|
||||
|
||||
`H:/` ist **keine echte Offsite-/Airgap-Kopie und kein Ersatz fuer Hetzner**. Es ist aber sinnvoll als zweite lokale Nearline-Kopie fuer kritische Restore-Quellen (Borg-Dumps, Repo-Bundles, Flash-Backup) und als Freeze-Sicherung vor strukturellen Eingriffen.
|
||||
|
||||
| Nutzung | Umsetzung | Hinweis |
|
||||
|---|---|---|
|
||||
| Pull von `/mnt/user/backups/borg/dumps/latest` auf H:/ | Windows Scheduled Task per `robocopy` | keine CIFS-Hard-Mounts auf Unraid |
|
||||
| Pull der Gitea-Bundles aus `/mnt/user/backups/git-bundles/gitea` | identisch | Bundles sind klein und schnell synchronisiert |
|
||||
| Pull des Unraid-Flash-Artefakts `unraid-flash-config.tar.gz` | bewusst nicht im H:/ Scope | Restore-Quelle bleibt Hetzner-Borg; Flash-Config wie Secret behandeln |
|
||||
|
||||
Der konkrete Pull-Pfad ist in `docs/H_DRIVE_NEARLINE_PULL.md` und `ops/h-drive-nearline/pull-critical-backups.ps1` produktiv. Der Windows Scheduled Task `KalliLab H Drive Nearline Pull` laeuft seit 2026-05-28 taeglich 05:30.
|
||||
|
||||
| Abgrenzung | Bewertung | Begruendung |
|
||||
|---|---|---|
|
||||
| **Nicht** als Ersatz fuer Hetzner-Off-site | bewusst | 3-2-1 ist mit Hetzner als einzigem Off-site erfuellt; H:/ reduziert nur lokale Restore-Abhaengigkeit |
|
||||
| **Nicht** als zweites Borg-Repo am Unraid | bewusst | dauerhafte CIFS-Verbindung im Borg-Lauf verletzt Hard Rule aus `docs/STORAGE_LAYOUT.md` |
|
||||
|
||||
### Kapazitaets-Eintrag
|
||||
|
||||
| Bereich | Groesse | Belegt | Schwellwert | Bewertung |
|
||||
|---|---:|---:|---:|---|
|
||||
| H:/ (Windows-Arbeitsplatz, `Externe HDD`) | 8.0T | 3.91T belegt / 4.10T frei | Review wenn > 70 % | NTFS, `Healthy`; Pull-Ziel fuer Borg-Dumps und Gitea-Bundles |
|
||||
|
||||
### Naechste Schritte
|
||||
|
||||
- Task-Lauf quartalsweise gegen Reports unter `H:\kallilab-nearline-backups\_reports` pruefen.
|
||||
- Review-Intervall: quartalsweise. Bei jeder grossen Strukturaenderung Freeze-Pull manuell ausloesen.
|
||||
|
||||
## Restore-Zeitziele
|
||||
|
||||
| Tier | Beispiel | Zielzeit | Status |
|
||||
|---|---|---:|---|
|
||||
| Tier 0 | Repo, Secrets, Traefik, DNS | TBD | offen |
|
||||
| Tier 1 | Gitea, Vaultwarden, Paperless, Immich | TBD | offen |
|
||||
| Tier 2 | Nextcloud, Mealie, Monitoring | TBD | offen |
|
||||
| Tier 3 | Komfort-/Ops-Tools | TBD | offen |
|
||||
| Tier 0 | Repo, Secrets, Traefik, DNS | 2-4 h | Zielwert, per DR-Sanity-Check bestaetigen |
|
||||
| Tier 1 | Gitea, Vaultwarden, Paperless, Immich | 4-8 h | Zielwert, einzelne Restore-Tests vorhanden |
|
||||
| Tier 2 | Nextcloud, Mealie, Monitoring | < 24 h | Zielwert, Restore-Pfade dokumentiert |
|
||||
| Tier 3 | Komfort-/Ops-Tools | Best effort / rebuildbar | Zielwert, keine harte SLA |
|
||||
|
||||
## Review-Log
|
||||
|
||||
| Datum | Befund | Entscheidung |
|
||||
|---|---|---|
|
||||
| 2026-05-26 | Cache 6 %, Array/User-Shares 33 %, lokale Backups 2.2G; keine validierte USV-Abschaltung | Capacity gruen; naechste operative Risiken sind USV-Entscheidung und externe Backup-/Cold-Storage-Groessen |
|
||||
| 2026-05-26 | Cache 6 %, Array/User-Shares 33 %, lokale Backups 2.2G; keine validierte USV-Abschaltung | Capacity gruen; USV wird aktuell nicht angeschafft, Power-Loss-Risiko bewusst akzeptiert; zweites Off-site/Cold-Storage bewusst nicht umgesetzt |
|
||||
| 2026-05-26 | H:/ als dauerhaft verbundenes Windows-Laufwerk evaluiert | als zweite lokale Nearline-Kopie und Freeze-Sicherung sinnvoll; nicht als Offsite-Ersatz und nicht als Borg-CIFS-Hard-Mount am Unraid |
|
||||
| 2026-05-26 | H:/ Kapazitaet erfasst: 8.0T NTFS, 3.91T belegt, 4.10T frei, `Healthy` | genug Reserve fuer Nearline-Pull der kritischen Restore-Artefakte |
|
||||
| 2026-05-27 | H:/ Pull-Workflow vorbereitet | SMB-Quelle `\\192.168.178.58\backups` erreichbar; PowerShell-Skript und Runbook erstellt |
|
||||
| 2026-05-28 | H:/ Pull-Workflow produktiv | Windows Scheduled Task `KalliLab H Drive Nearline Pull` taeglich 05:30 aktiv |
|
||||
|
||||
@@ -65,7 +65,7 @@ Diese Punkte sollten **vor** einem echten Ausfall geklaert sein:
|
||||
| Repo-Zugang ausserhalb von Gitea | privater GitHub-Push-Mirror `michaelkaleschke-spec/homelab-infra` und lokaler aktueller Clone vorhanden |
|
||||
| Unraid USB-/Flash-Backup | `unraid-flash-config.tar.gz` wird vor Borg unter `/mnt/user/backups/borg/dumps/latest` erzeugt und nach Hetzner/Borg gesichert; Unraid-Connect-Cloud-Backup optional zusaetzlich |
|
||||
| Borg-Ziel | nicht nur lokal auf demselben Ausfallpfad |
|
||||
| Borg-Passphrase | Host-Secret-Datei vorhanden und fuer Borg-Zugriff verifiziert; externe analoge Hinterlegung bleibt Operator-Aufgabe |
|
||||
| Borg-Passphrase | Host-Secret-Datei vorhanden und fuer Borg-Zugriff verifiziert; externe Offline-Hinterlegung vom Operator am 2026-05-26 bestaetigt |
|
||||
| Secrets-Dateien | ueber Borg bzw. Restore-Quellen abgedeckt |
|
||||
| Komodo Stack ENV-Werte | extern dokumentiert, z. B. Vaultwarden |
|
||||
| Services-Recovery | `docs/SERVICES_RECOVERY.md` gepflegt, insbesondere Gitea-Repo-Mirror und Komodo-Bootstrap |
|
||||
@@ -173,6 +173,28 @@ Diese Werte sind vor dem Start der betroffenen Dienste zu pruefen bzw. wieder in
|
||||
- `KOMODO_PERIPHERY_PASSKEY`
|
||||
- `APP_KEY` und `ADMIN_PASSWORD` fuer `speedtest-tracker`
|
||||
|
||||
Zusaetzlich rebuildbar (keine kritische Recovery-Quelle, koennen aus Provider-/App-UIs neu erzeugt werden):
|
||||
|
||||
- `GLANCE_IMMICH_API_KEY`, `GLANCE_ADGUARD_USERNAME`, `GLANCE_ADGUARD_PASSWORD`, `GLANCE_SPEEDTEST_API_KEY` fuer `glance` Community-/Live-Widgets
|
||||
|
||||
### 6.2.1 Restore-Quellen fuer Stack-ENV-Werte
|
||||
|
||||
Stack-ENV-Werte liegen **nicht im Repo** und **nicht als Datei-Secret** unter `/mnt/user/appdata/secrets/`. Sie sind nur an drei Stellen erreichbar; bei Recovery in dieser Reihenfolge pruefen:
|
||||
|
||||
1. **Komodo-Mongo-Dump** `komodo-mongo.archive.gz` unter `/mnt/user/backups/borg/dumps/latest/`. Solange Komodo selbst noch nicht laeuft, ist der Mongo-Dump die kanonische Quelle. Restore in eine Test-Mongo-Instanz, anschliessend Werte aus der `stack`-Collection lesen. **Niemals** Werte in andere Dokumente kopieren.
|
||||
2. **Vaultwarden** Eintrag "Komodo Stack ENV / KalliLab CORE" (bzw. der entsprechende Eintrag pro Stack). Voraussetzung: Vaultwarden ist bereits restauriert (`docs/RESTORE_MATRIX.md`).
|
||||
3. **Externe Operator-Notiz** (versiegelter Umschlag, Bankschliessfach, oder analoge Sicherung neben der Borg-Passphrase). Nur als Notfall-Quelle, wenn weder Komodo-Mongo noch Vaultwarden verfuegbar sind.
|
||||
|
||||
**Reihenfolge-Konsequenz fuer den Bootstrap-Pfad in Phase 4 (Stufe 4 weiter unten):**
|
||||
|
||||
- Vor dem Start von `apps/paperless/`, `apps/immich/`, `apps/mail-archiver/` und `ops/speedtest/` muessen die jeweiligen Stack-ENV-Werte in Komodo wieder hinterlegt sein.
|
||||
- Wenn `komodo-mongo.archive.gz` frisch ist, koennen die Werte beim Komodo-Restart aus dem Dump zurueckgespielt werden, ohne dass jemand sie sieht.
|
||||
- Wenn Vaultwarden vor Komodo restauriert wird (was hier nicht der Standardweg ist), kann auch von dort gelesen werden.
|
||||
|
||||
**Paperless ist die wichtigste bewusste Ausnahme:** `PAPERLESS_DBPASS` und `PAPERLESS_REDIS` sind seit der Hardening-Phase bewusst Stack-ENV (Paperless unterstuetzt `_FILE` fuer DB-Pass nicht). Ein Komodo-Mongo-Dump-Verlust ist daher fuer Paperless gleichbedeutend mit Re-Initialisierung der App-DB; in diesem Fall hilft nur ein Restore aus Vaultwarden oder externer Notiz.
|
||||
|
||||
**Regel:** Konkrete Werte werden **nirgendwo** im Repo, in Logs, in Doku-Kommentaren oder in ntfy-Meldungen wiedergegeben. Auch dieses Dokument haelt nur Variablennamen, Quellen und Reihenfolge fest, keine Werte.
|
||||
|
||||
### 6.3 Rechte
|
||||
|
||||
Nach einem Restore oder manuellem Rueckkopieren:
|
||||
@@ -239,7 +261,7 @@ Ziel:
|
||||
|
||||
### Stufe 2 - Gemeinsame Backends und Identity
|
||||
|
||||
4. `infra/postgresql17/`
|
||||
4. `infra/postgresql17/` (PostgreSQL 18 runtime, historischer Stack-Name bleibt fuer Service-DNS stabil)
|
||||
5. `security/authelia/`
|
||||
6. `infra/redis/`
|
||||
7. `core/gitea/`
|
||||
@@ -249,12 +271,18 @@ Ziel:
|
||||
- gemeinsame DB verfuegbar
|
||||
- zentrale Auth laeuft; Authelia nutzt bewusst kein Redis-Session-Backend
|
||||
- Authelia SMTP-Notifier kann GMX erreichen
|
||||
- Redis als shared Cache fuer abhaengige Apps verfuegbar
|
||||
- Redis verfuegbar als App-Cache fuer Paperless (`infra/redis` ist historisch als "shared" angelegt, wird faktisch nur von Paperless genutzt)
|
||||
- Git-Zugriff wiederhergestellt
|
||||
|
||||
### Stufe 3 - Deploy-System
|
||||
|
||||
8. `ops/komodo/`
|
||||
8. `ops/komodo/` - **Kaltstart-Anker, kein Auto-Deploy**
|
||||
|
||||
Komodo wird in dieser Stufe bewusst **nicht** ueber Gitea-Webhook deployed. Der vollstaendige Bootstrap-Pfad ist in `docs/SERVICES_RECOVERY.md` Abschnitt "Komodo Bootstrap" als lineare Stufen A-F dokumentiert. Hier in der DR-Reihenfolge gilt der Einstiegspunkt:
|
||||
|
||||
- Recovery-Anker ist `ops/komodo/docker-compose.yml` aus dem Repo (lokaler Clone, GitHub-Mirror oder `homelab-infra.bundle`-Restore).
|
||||
- Komodo-Stack-ENV-Werte (`KOMODO_*`) sind Stack-ENV-only und werden aus Vaultwarden oder externer Notiz wiederhergestellt (siehe `docs/SECRETS_MAP.md` Abschnitt "Stack-ENV-only Secrets - Restore-Wege").
|
||||
- Erst nach erfolgreicher Validierung der Komodo-Web-UI und Periphery-Verbindung werden in den naechsten Stufen die produktiven Stacks aufgenommen.
|
||||
|
||||
Ziel:
|
||||
|
||||
@@ -368,6 +396,12 @@ Vor dem Start muessen vorhanden sein:
|
||||
|
||||
Zusaetzlich muss der Nutzdatenpfad `/mnt/user/documents/nextcloud-data` erreichbar sein.
|
||||
|
||||
Beim PostgreSQL-Restore beachten:
|
||||
|
||||
- vor einem produktiven Dump `occ maintenance:mode --on` setzen
|
||||
- die produktive DB-Rolle kann von `POSTGRES_USER` abweichen; aktuell nutzt Nextcloud laut `config.php` die Rolle `oc_admin`
|
||||
- nach Restore und erfolgreichem `occ status` den Wartungsmodus mit `occ maintenance:mode --off` beenden
|
||||
|
||||
### Borg-Dumps
|
||||
|
||||
Die Dump-Erzeugung ist host-seitig gedacht, nicht als Borg-UI-Inline-Hook.
|
||||
@@ -378,6 +412,50 @@ Relevant:
|
||||
- Skript: `ops/borg-ui/scripts/pre-backup-dumps.sh`
|
||||
- Unraid-Flash-Artefakt: `unraid-flash-config.tar.gz` plus `.sha256` und Manifest im selben Zielpfad
|
||||
|
||||
### Redis 8 Restore / Rollback
|
||||
|
||||
Redis-Instanzen laufen auf der 8.x-Schiene. Vor Major-Upgrades wird `redis-cli SAVE` ausgefuehrt und der jeweilige Datenpfad kopiert.
|
||||
|
||||
Aktive Pfade und Besonderheiten:
|
||||
|
||||
- Shared Redis: `/mnt/user/appdata/redis`, Passwort aus `redis_password.txt`, AOF aktiv.
|
||||
- Nextcloud Redis: `/mnt/user/appdata/nextcloud/redis`, ohne Redis-Passwort, Snapshot-Persistenz.
|
||||
- Immich Redis: cache/queue-only ohne bind-mounted Datenpfad; Restore-Wahrheit ist Immich Postgres + Foto-Dateien, nicht Redis.
|
||||
|
||||
Rollback:
|
||||
|
||||
1. Abhaengige App stoppen.
|
||||
2. Redis stoppen.
|
||||
3. Compose auf das vorherige Redis-7.4-Image zuruecksetzen.
|
||||
4. Bei Shared/Nextcloud den vor dem Cutover kopierten Datenpfad zurueckkopieren.
|
||||
5. Redis und App starten, `redis-cli INFO server` und App-Smoke pruefen.
|
||||
|
||||
### PostgreSQL 18 Major-Upgrade / Rollback
|
||||
|
||||
Produktive PostgreSQL-18-Cluster verwenden das Docker-Image-Layout mit Host-Mount auf `/var/lib/postgresql` und `PGDATA=/var/lib/postgresql/18/docker`.
|
||||
|
||||
Aktive Datenpfade:
|
||||
|
||||
- Shared PostgreSQL: `/mnt/user/appdata/postgresql18`
|
||||
- Mealie PostgreSQL: `/mnt/user/appdata/mealie/postgres18`
|
||||
- Nextcloud PostgreSQL: `/mnt/user/appdata/nextcloud/postgres18`
|
||||
|
||||
Rollback-Altstaende, bis zur separaten Loeschfreigabe nicht entfernen:
|
||||
|
||||
- Shared PostgreSQL 17: `/mnt/user/appdata/postgresql17`
|
||||
- Mealie PostgreSQL 17: `/mnt/user/appdata/mealie/postgres`
|
||||
- Nextcloud PostgreSQL 17: `/mnt/user/appdata/nextcloud/postgres`
|
||||
|
||||
Restore-Reihenfolge fuer den Shared-Cluster:
|
||||
|
||||
1. Frischen PostgreSQL-18-Cluster starten.
|
||||
2. Globals aus `pg_dumpall --globals-only` einspielen.
|
||||
3. Den bekannten Bootstrap-Konflikt fuer `CREATE ROLE mailarchiver;` gezielt tolerieren bzw. herausfiltern, danach `ALTER ROLE mailarchiver ...` dennoch einspielen.
|
||||
4. Datenbanken anlegen und Custom-Format-Dumps mit `pg_restore` einspielen.
|
||||
5. Restore-Logs auf echte `ERROR`, `FATAL` und `PANIC` pruefen.
|
||||
|
||||
Immich ist bewusst nicht Teil dieses PostgreSQL-18-Laufs: Die produktive DB bleibt auf PostgreSQL 14 und nutzt das Immich-Postgres-Image mit VectorChord/pgvector. VectorChord-Backups brauchen zum Restore ein Image mit VectorChord; der alte pgvecto.rs-Datenpfad `/mnt/user/appdata/immich_postgres` bleibt bis zur separaten Loeschfreigabe als Rollback-Altstand erhalten.
|
||||
|
||||
### Hermes Agent
|
||||
|
||||
Hermes nutzt einen lokalen Build und hostseitige Runtime-Daten.
|
||||
@@ -400,7 +478,7 @@ Smoke-Test: `hermes-gateway` healthcheck ist gruen, `hermes.kaleschke.info` leit
|
||||
## 11. Offene Vorbereitungs-To-dos
|
||||
|
||||
- Unraid-USB-/Flash-Backup regelmaessig ueber `unraid-flash-config.tar.gz` und optional Unraid Connect pruefen
|
||||
- Borg-Passphrase aus `/mnt/user/appdata/secrets/borg_repo_passphrase.txt` extern analog sicher hinterlegen
|
||||
- Borg-Passphrase ist laut Operator-Bestaetigung vom 2026-05-26 extern/offline hinterlegt; bei Reviews nur Existenz/Lesbarkeit der Offline-Kopie pruefen, nie den Wert dokumentieren
|
||||
- Komodo Stack-ENV-Werte zentral ausserhalb von Komodo dokumentieren
|
||||
- regelmaessige automatisierte Restore-Smoke-Tests fuer Vaultwarden, Gitea und Paperless etablieren
|
||||
- `komodo-mongo`-Dump nach Major-Upgrades gezielt kontrollieren
|
||||
|
||||
@@ -10,9 +10,11 @@ Dieses Dokument beschreibt externe Anbieter und Konten, von denen Betrieb, Recov
|
||||
|
||||
| Anbieter / System | Zweck | Kritikalitaet | Recovery-Auswirkung | Zugang / Besitz | Notfallplan |
|
||||
|---|---|---:|---|---|---|
|
||||
| Telekom DSL | Internet-Uplink | hoch | Public Apps, ACME, DDNS, Hetzner-Off-site und Tailscale-Initial-Verbindung fallen aus | Telekom-Kundenkonto | Kein WAN-Failover am Standort eingerichtet (FRITZ!Box-Ausfallschutz inaktiv); lokale LAN-Dienste laufen weiter; Hotspot-Behelf nur fuer Operator-Arbeit, nicht fuer Public Apps |
|
||||
| FRITZ!Box 7590 | Router, DHCP, Telefonie, WAN | hoch | LAN ohne DHCP/Routing; auch lokale Inter-Subnet-Kommunikation kann brechen | Operator-Login auf `192.168.178.1` | FRITZ!Box-Konfig regelmaessig sichern (FRITZ!OS-Backup), Reset-Pin und Account-Pfad bereithalten |
|
||||
| Domain-Registrar | Besitz `kaleschke.info` | hoch | Ohne Domain brechen Public URLs/TLS-Erneuerung | Operator-Konto ausserhalb Repo, konkreten Registrar im Account pruefen | Registrar-Zugang, 2FA-Recovery und Zahlungsweg analog/off-system sichern |
|
||||
| Cloudflare DNS | Authoritative DNS, ACME DNS-Challenge, DDNS | hoch | Neue Zertifikate/DNS-Aenderungen blockiert | Cloudflare-Konto; API-Token liegt als Host-Secret | API-Token rotierbar halten, Account-Recovery und Zone-Besitz pruefen |
|
||||
| Hetzner Storage Box | Off-site Borg Backup | kritisch | Restore aus Off-site ggf. nicht moeglich | Hetzner-Konto / Storage-Box-Zugang ausserhalb Repo | Zweites Off-site-Ziel oder Cold-Platte etablieren; Borg-Passphrase extern sichern |
|
||||
| Hetzner Storage Box | Off-site Borg Backup | kritisch | Restore aus Off-site ggf. nicht moeglich | Hetzner-Konto / Storage-Box-Zugang ausserhalb Repo | Borg-Passphrase ist offline gesichert; Account-Hygiene und Borg `--append-only` als Haertung pruefen |
|
||||
| GitHub Mirror | Externer Repo-Mirror `michaelkaleschke-spec/homelab-infra` | mittel/hoch | Gitea-Verlust abfederbar, Repo-Bootstrap bleibt moeglich | GitHub-Konto; PAT liegt in Gitea-Mirror-Settings, nicht im Repo | Mirror-Status regelmaessig pruefen; lokalen Clone als zweite Kopie behalten |
|
||||
| Tailscale | Remote-/Operator-Zugang | hoch | Remote-Zugriff erschwert, lokale Bedienung bleibt | Tailnet-Konto; Node `Kallilabcore`, IPv4 `100.80.98.33` | Break-glass per LAN und physischem Zugriff; Tailnet-Recovery-Codes sichern |
|
||||
| GMX SMTP | Authelia Notifier | mittel | Mail-Notifier faellt aus, Login selbst nicht zwingend | GMX-Konto; SMTP-Secret liegt hostseitig | ntfy/zweiter SMTP als Fallback pruefen |
|
||||
@@ -27,7 +29,7 @@ Authoritativ ist `docs/SECRETS_MAP.md`. Diese Liste markiert nur externe Abhaeng
|
||||
|
||||
| Secret | Zweck | Recovery-Hinweis |
|
||||
|---|---|---|
|
||||
| Borg Passphrase | Entschluesselung Borg-Repos | Muss analog/off-system vorhanden sein |
|
||||
| Borg Passphrase | Entschluesselung Borg-Repos | Offline gesichert, Operator-Bestaetigung 2026-05-26 |
|
||||
| Cloudflare DNS API Token | ACME DNS-Challenge | Token-Rotation und Scope pruefen |
|
||||
| GitHub Mirror Token | Push-Mirror | In Gitea/GitHub verwaltet, nicht im Repo |
|
||||
| Tailscale Account Recovery | Tailnet-Zugang | Account-2FA/Recovery Codes sichern |
|
||||
@@ -41,7 +43,8 @@ Authoritativ ist `docs/SECRETS_MAP.md`. Diese Liste markiert nur externe Abhaeng
|
||||
|
||||
- Lokales Borg-Repo und aktuelle Dumps pruefen.
|
||||
- Keine destruktiven Host-Aenderungen starten, solange Off-site unklar ist.
|
||||
- Zweites Off-site-Ziel oder Cold-Platte als Folgeaufgabe umsetzen.
|
||||
- H:/ Nearline-Pull als schnelle lokale Zweitkopie fuer kritische Restore-Artefakte nutzen.
|
||||
- Zweites Off-site-Ziel nur bei Review-Trigger aus `docs/OFFSITE_BACKUP_OPTIONS.md` neu bewerten.
|
||||
|
||||
### Cloudflare Account/DNS gestoert
|
||||
|
||||
@@ -56,6 +59,13 @@ Authoritativ ist `docs/SECRETS_MAP.md`. Diese Liste markiert nur externe Abhaeng
|
||||
- AdGuard-Admin-Bind muss so geplant werden, dass ein lokaler Break-glass-Weg bekannt ist.
|
||||
- Seit 2026-05-26 ist AdGuard Admin nur ueber `100.80.98.33:8082` gebunden; bei Tailnet-Ausfall ist lokaler Host-/Compose-Zugriff der Break-glass-Weg.
|
||||
|
||||
### Telekom-DSL / FRITZ!Box gestoert
|
||||
|
||||
- Lokale LAN-Apps (Plex, AdGuard-DNS, lokales Borg-Dump-Repository) bleiben verfuegbar, solange Host und Switch laufen.
|
||||
- Tailscale-Sessions, die bereits stehen, koennen ueber DERP/Relays kurzzeitig weiterlaufen; neue Verbindungen koennen ausfallen.
|
||||
- ACME-/DDNS-/Hetzner-Backup-Laeufe pausieren bis WAN zurueck ist.
|
||||
- FRITZ!OS 8.21 Update wird bewusst nur in einem geplanten Service-Fenster eingespielt, weil Reboot WAN/Tailscale-Aufbau unterbricht.
|
||||
|
||||
### Domain verloren oder Registrar-Zugriff verloren
|
||||
|
||||
- Gitea/GitHub Mirror und lokale IP/Tailscale-Pfade fuer Recovery nutzen.
|
||||
@@ -65,4 +75,6 @@ Authoritativ ist `docs/SECRETS_MAP.md`. Diese Liste markiert nur externe Abhaeng
|
||||
|
||||
| Datum | Ergebnis | Naechste Aktion |
|
||||
|---|---|---|
|
||||
| 2026-05-26 | Bekannte externe Abhaengigkeiten aus Repo-/Betriebsdoku dokumentiert; keine Secret-Werte aufgenommen | Account-Besitz, 2FA-Recovery-Codes, Zahlungswege und Borg-Passphrase extern bestaetigen |
|
||||
| 2026-05-26 | Bekannte externe Abhaengigkeiten aus Repo-/Betriebsdoku dokumentiert; keine Secret-Werte aufgenommen. Borg-Passphrase ist laut Operator offline gesichert. | Account-Besitz, 2FA-Recovery-Codes und Zahlungswege extern bestaetigen |
|
||||
| 2026-05-26 | Telekom-DSL und FRITZ!Box 7590 (FRITZ!OS 8.21) als WAN-/Router-Abhaengigkeit aufgenommen; Ausfallschutz nicht eingerichtet | FRITZ!OS-Update im Service-Fenster pruefen |
|
||||
| 2026-05-28 | FRITZ!Box-Portfreigaben bereinigt: aktiv bleibt nur `443/tcp`; `80/tcp` entfernt, `222/tcp` bewusst nicht angelegt; UPnP-Recht fuer VONETS-Bridge deaktiviert | IPv6-Exposure bei naechstem WAN-/Router-Review pruefen |
|
||||
|
||||
+145
-26
@@ -1,38 +1,157 @@
|
||||
# Family Onboarding - KalliLab CORE
|
||||
# Familien-Willkommen - KalliLab CORE
|
||||
|
||||
Status: Entwurf. Zielgruppe sind Familienmitglieder, nicht Operatoren.
|
||||
Status: **Final-Stand vor Wochenend-Einladung** (2026-05-27). Zielgruppe: Familie. Kein Technik-Wortschatz noetig.
|
||||
|
||||
## Zweck
|
||||
Diese Seite richtet sich an alle, die zuhause unsere eigenen Apps nutzen. Du brauchst kein Technikwissen. Wenn etwas unklar ist: einfach Michi fragen.
|
||||
|
||||
Diese Datei soll spaeter kurz und alltagstauglich erklaeren, wie die wichtigsten Dienste genutzt werden und was bei Problemen zu tun ist. Keine Restore-Matrix, keine Docker-Begriffe.
|
||||
Du musst nichts auswendig lernen. Wenn du nur eine Sache aus dieser Seite mitnimmst: **Passwoerter gehoeren in Vaultwarden, nicht auf Zettel und nicht in den Browser.**
|
||||
|
||||
## Dienste
|
||||
---
|
||||
|
||||
| Dienst | URL | Zweck | Konto / Login | Notiz |
|
||||
|---|---|---|---|---|
|
||||
| Nextcloud | `https://cloud.kaleschke.info` | Dateien, Kalender, Kontakte | TBD | Mobile App/WebDAV/CardDAV |
|
||||
| Immich | `https://immich.kaleschke.info` | Fotos und Smartphone-Backup | TBD | Backup-App pro Handy |
|
||||
| Vaultwarden | `https://vault.kaleschke.info` | Passwoerter | TBD | Familien-Organisation pruefen |
|
||||
| Mealie | `https://mealie.kaleschke.info` | Rezepte und Einkauf | TBD | TBD |
|
||||
| Paperless | `https://paperless.kaleschke.info` | Dokumente | TBD | Scan-/Inbox-Prozess beschreiben |
|
||||
| Plex | intern/App | Medien | TBD | TBD |
|
||||
## Was wir zuhause selbst betreiben
|
||||
|
||||
## Was tun bei Problemen?
|
||||
Wir haben einen eigenen kleinen Server im Haus. Auf dem laufen ein paar Programme, die wir alle gemeinsam nutzen koennen, statt sie bei Google, Apple oder Dropbox liegen zu haben. Vorteile: Unsere Fotos und Dokumente bleiben bei uns. Wir entscheiden, wer was sehen darf. Es kostet uns kein Abo.
|
||||
|
||||
| Situation | Verhalten |
|
||||
|---|---|
|
||||
| Webseite nicht erreichbar | 10 Minuten warten, dann Operator informieren |
|
||||
| Passwort vergessen | Operator informieren, nicht selbst neue Konten anlegen |
|
||||
| Handy-Foto-Backup stoppt | App oeffnen, WLAN/Batteriesparmodus pruefen, Operator informieren |
|
||||
| 2FA verloren | Operator informieren; Recovery-Prozess wird separat festgelegt |
|
||||
| Warnmeldung vom Browser | Nicht weiterklicken, Screenshot machen, Operator informieren |
|
||||
Nachteile, ehrlich gesagt: Wenn der Server zuhause aus ist, sind die Apps weg, bis er wieder laeuft. Das passiert selten — meistens nur, wenn Michi etwas umbaut oder der Strom weg ist. Und es gibt keine Hotline, die Passwoerter zuruecksetzt. Das macht Michi.
|
||||
|
||||
## Offene Inhalte
|
||||
---
|
||||
|
||||
## Die Apps in einem Satz
|
||||
|
||||
| App | Was sie kann | Wie du sie nutzt |
|
||||
|---|---|---|
|
||||
| **Nextcloud** | Dateien und Ordner teilen, Kalender, Adressbuch | Web `cloud.kaleschke.info` oder Nextcloud-App auf dem Handy |
|
||||
| **Immich** | Fotos und Videos automatisch vom Handy sichern, gemeinsam durchblaettern | Immich-App auf dem Handy einrichten lassen |
|
||||
| **Vaultwarden** | Passwoerter sicher speichern und auf jedem Geraet nachschauen | Bitwarden-App (kostenlos), beim ersten Start Server-URL auf `vault.kaleschke.info` aendern lassen |
|
||||
| **Mealie** | Rezepte sammeln, Wochenplan, Einkaufsliste | Web `mealie.kaleschke.info` oder Mealie-App |
|
||||
| **Paperless** | Briefe und wichtige Dokumente scannen, durchsuchen, ablegen | Web `paperless.kaleschke.info`; Scan-Workflow erklaert Michi |
|
||||
| **Plex** | Filme und Musik auf Fernseher, Handy und Tablet | Plex-App auf dem Geraet, mit Konto anmelden |
|
||||
|
||||
> Wenn du eine App auf dem Handy installierst und sie fragt nach einer Server-URL, ist das immer eine `...kaleschke.info`-Adresse. Wenn du dir nicht sicher bist, frag bevor du etwas eintippst.
|
||||
|
||||
---
|
||||
|
||||
## Wie du dich anmeldest
|
||||
|
||||
Beim ersten Mal bekommst du von Michi:
|
||||
|
||||
- deinen Benutzernamen (in der Regel dein Vorname klein geschrieben)
|
||||
- ein Start-Passwort, das du beim ersten Login aenderst
|
||||
|
||||
Wenn eine App selbst einen **Zweitfaktor** anbietet (zum Beispiel Nextcloud), bekommst du dafuer am Anfang gemeinsam mit Michi eine kleine Authentifizierungs-App eingerichtet. Das ist eine extra Schicht: zusaetzlich zum Passwort tippst du beim Login eine 6-stellige Zahl aus dieser App ein.
|
||||
|
||||
> Hinweis: Ein **einheitliches 2FA fuer alle Apps gleichzeitig** ist noch nicht eingerichtet. Aktuell hat jede App ihre eigene Anmeldung. Das bleibt erst einmal so. Wenn sich da etwas aendert, sagt Michi rechtzeitig Bescheid.
|
||||
|
||||
**Bitte:**
|
||||
|
||||
- Speichere alle Passwoerter im Vaultwarden, nicht im Browser.
|
||||
- Schreibe Passwoerter nicht auf Zettel.
|
||||
- Gib dein Passwort niemandem, auch nicht "kurz mal".
|
||||
|
||||
---
|
||||
|
||||
## Foto-Backup vom Handy einrichten (Immich)
|
||||
|
||||
Das ist die App, die deinen Eltern wahrscheinlich am meisten bringt.
|
||||
|
||||
1. App **Immich** im App-Store / Play Store installieren.
|
||||
2. Beim ersten Start nach Server fragen lassen: `https://immich.kaleschke.info`.
|
||||
3. Mit deinem Login anmelden.
|
||||
4. In den App-Einstellungen "Hintergrund-Backup" aktivieren — am besten nur ueber WLAN.
|
||||
5. Fertig. Neue Fotos landen automatisch zuhause auf dem Server.
|
||||
|
||||
> Wenn dein Handy 4 Wochen nicht im Haus-WLAN war, sind die Fotos noch in der Handy-Galerie, aber noch nicht zuhause. Sobald du wieder im WLAN bist und die App startest, holt sie alles nach.
|
||||
|
||||
---
|
||||
|
||||
## Was tun, wenn etwas nicht geht
|
||||
|
||||
### "Die Webseite oeffnet nicht."
|
||||
|
||||
1. 10 Minuten warten — Michi macht vielleicht gerade etwas am Server.
|
||||
2. Anderes Geraet ausprobieren (Handy statt PC oder umgekehrt).
|
||||
3. Wenn es danach immer noch nicht geht: Michi schreiben. Bitte schreib dazu, was du genau aufgerufen hast (`cloud.kaleschke.info` / `immich.kaleschke.info` / ...).
|
||||
|
||||
### "Ich habe mein Passwort vergessen."
|
||||
|
||||
- Nicht selbst neu registrieren. Das geht in den Apps gar nicht.
|
||||
- Michi schreiben. Er setzt dir ein neues Start-Passwort. Du aenderst es beim ersten Login.
|
||||
|
||||
### "Ich habe mein 2FA verloren (neues Handy, App geloescht)."
|
||||
|
||||
- Michi schreiben. Er kann den Zweitfaktor in der betroffenen App fuer dich zuruecksetzen, sobald er dich persoenlich identifiziert.
|
||||
- Wir richten den Zweitfaktor dann neu auf dem neuen Handy ein.
|
||||
- Wenn moeglich: vor einem Handy-Wechsel kurz Bescheid sagen, dann koennen wir 2FA vorher umziehen, statt zuruecksetzen.
|
||||
|
||||
### "Das Handy-Foto-Backup ist stehen geblieben."
|
||||
|
||||
1. Immich-App oeffnen, ist sie noch eingeloggt?
|
||||
2. Bist du im Haus-WLAN? Mobilfunk ist meistens nicht aktiviert.
|
||||
3. Akkusparmodus pruefen — wenn er aktiv ist, kann die App pausieren.
|
||||
4. App schliessen, wieder oeffnen, ein paar Minuten warten.
|
||||
5. Wenn das nicht hilft: Michi schreiben.
|
||||
|
||||
### "Der Browser warnt vor der Seite."
|
||||
|
||||
- **Nicht weiterklicken.**
|
||||
- Screenshot machen.
|
||||
- Michi schicken.
|
||||
- Wahrscheinlich ist gerade ein Zertifikat abgelaufen — das ist normalerweise schnell behoben, aber Michi muss kurz draufschauen.
|
||||
|
||||
### "Mein Familien-Mitglied sagt, sein Konto sei gesperrt."
|
||||
|
||||
- Manchmal sperren die Apps Konten nach mehreren falschen Passwoertern fuer ein paar Minuten. Das ist Absicht. Einfach 10 Minuten warten und nochmal versuchen.
|
||||
- Wenn es laenger dauert: Michi schreiben.
|
||||
|
||||
---
|
||||
|
||||
## Was du **nicht** musst
|
||||
|
||||
- Du musst nichts installieren, einrichten oder warten.
|
||||
- Du musst keine Updates pruefen.
|
||||
- Du musst nicht wissen, wo die Daten genau liegen — sie liegen auf dem Server zuhause und werden automatisch gesichert.
|
||||
- Du musst dir keine Adressen merken — alle Apps sind ueber `...kaleschke.info` erreichbar und Michi schickt dir den Direktlink, wenn du einen brauchst.
|
||||
|
||||
## Was wir uns gemeinsam wuenschen
|
||||
|
||||
- Bitte nutze Vaultwarden fuer alle Familien-Passwoerter. Das schuetzt uns alle.
|
||||
- Bitte sag Bescheid, wenn etwas komisch wirkt (seltsame E-Mail, Login-Aufforderung an der falschen Stelle). Lieber einmal zu oft fragen.
|
||||
- Wenn dir eine App fehlt oder du eine Idee hast, was wir gemeinsam besser machen koennten: ansprechen, nicht selbst herumprobieren.
|
||||
|
||||
---
|
||||
|
||||
## Wenn der Server zuhause mal komplett aus ist
|
||||
|
||||
Das kommt selten vor. In dem Fall:
|
||||
|
||||
- Webseiten von `...kaleschke.info` oeffnen nicht.
|
||||
- Foto-Backup von Immich pausiert automatisch — neue Fotos bleiben auf dem Handy und werden nachgeholt, sobald der Server wieder da ist.
|
||||
- Plex-App zeigt "Server offline" — Filme sind weiterhin da, sobald der Server zurueck ist.
|
||||
- Vaultwarden: gespeicherte Passwoerter sind weiterhin in der Bitwarden-App offline verfuegbar.
|
||||
- Es geht **nichts kaputt**, nur weil der Server kurz aus ist.
|
||||
|
||||
Michi laesst es dich wissen, wenn ein Wartungsfenster geplant ist.
|
||||
|
||||
---
|
||||
|
||||
## Offene Inhalte (Operator-Notiz)
|
||||
|
||||
Diese Punkte gehoeren in das Wochenend-Onboarding-Gespraech und sind nicht Teil dieser Familien-Seite:
|
||||
|
||||
| Status | Aufgabe |
|
||||
|---|---|
|
||||
| offen | Pro Dienst kurze Schritt-fuer-Schritt-Anleitung schreiben |
|
||||
| offen | Konto-/2FA-Policy final entscheiden |
|
||||
| offen | Immich Mobile Backup fuer alle Geraete testen |
|
||||
| offen | Vaultwarden Familienorganisation pruefen |
|
||||
| offen | Pro Familien-Konto Benutzernamen und Start-Passwort persoenlich uebergeben (Vaultwarden Familien-Organisation als Uebergabeweg) |
|
||||
| offen | 2FA-App-Empfehlung pro Person festlegen (zum Beispiel Bitwarden Authenticator, Aegis, 2FAS) |
|
||||
| offen | Vaultwarden Familien-Organisation einrichten und Mitglieder einladen |
|
||||
| offen | Immich Mobile Backup mit jedem Familien-Geraet einmal gemeinsam ausprobieren |
|
||||
| offen | Scan-/Inbox-Anleitung fuer Paperless ergaenzen, sobald der Workflow final ist |
|
||||
| offen | Einladungstermin Wochenende mit konkretem Datum festlegen |
|
||||
|
||||
## Bewusst nicht versprochen
|
||||
|
||||
Damit niemand spaeter enttaeuscht ist, hier kurz, was die Seite **nicht** verspricht:
|
||||
|
||||
- Es gibt aktuell **kein** Einheits-Login fuer alle Apps. Jede App hat ihre eigene Anmeldung. Eine Vereinheitlichung ist als Idee notiert, aber zeitlich noch nicht geplant.
|
||||
- Es gibt **keine** Garantie, dass eine App 24/7 verfuegbar ist. Es ist ein Heim-Server, kein Rechenzentrum.
|
||||
- Es gibt **keinen** automatischen Support per Hotline. Probleme gehen an Michi.
|
||||
- Es gibt **kein** Werbe-Konto und keinen Versand deiner Daten an Externe. Alles bleibt zuhause.
|
||||
|
||||
@@ -0,0 +1,194 @@
|
||||
# Family-View Dashboard - Spezifikation
|
||||
|
||||
Status: **Spezifikation (Doku-only)**, kein Grafana-JSON in diesem Schritt.
|
||||
Audit-Bezug: `docs/archive/2026-05/AUDIT_2026-05-25.md` Finding **F-08** (Alerts/Sichtbarkeit) und das Sprint-3-TODO "Family-View Dashboard definieren" aus `docs/AUDIT_2026-05-25_TODO.md`.
|
||||
|
||||
## Zweck
|
||||
|
||||
Ein Grafana-Dashboard, das beim Morgen-Check in unter 30 Sekunden zeigt, ob das Homelab gesund ist. Zielgruppe ist primaer der Operator. Wenn die Familie es zufaellig anschaut, soll niemand erschrecken: ueberall gruene Felder bedeuten "alles in Ordnung", ohne dass man die Technik dahinter verstehen muss.
|
||||
|
||||
Das Dashboard ist die Konsolidierung des morgendlichen Pruefablaufs:
|
||||
|
||||
- Sind die wichtigsten Apps erreichbar?
|
||||
- Hat das Backup gestern Nacht funktioniert?
|
||||
- Wann laufen die Zertifikate aus?
|
||||
- Sind die Disks ausreichend frei?
|
||||
- Laufen die kritischen Container?
|
||||
|
||||
## Abgrenzung
|
||||
|
||||
Diese Datei beschreibt nur Layout, Datenquellen und PromQL-Queries. Die JSON-Datei `monitoring/grafana/dashboards/family-view.json` wird **bewusst noch nicht** angelegt, weil:
|
||||
|
||||
- Es noch keine Live-Pruefung gegen die echte Grafana-Instanz gab.
|
||||
- Die Alert-Regeln (Borg-Stale, Cert-Expiry, Container-Down) sind laut `docs/AUDIT_2026-05-25_TODO.md` Sprint 3 selbst noch im "in Arbeit (Regeln vorbereitet)"-Status.
|
||||
- Bei einem halbgaren Dashboard-JSON entstehen mehr Wartungsfragen als Klarheit.
|
||||
|
||||
Die JSON-Datei wird angelegt, sobald (a) die genannten Metriken stabil verfuegbar sind und (b) ein erster manueller Build im Grafana-UI das Layout bestaetigt hat.
|
||||
|
||||
## Datenquellen
|
||||
|
||||
Authoritativ ist `monitoring/grafana/provisioning/datasources/datasources.yml`. Das Dashboard nutzt nur die schon provisionierten Datasources:
|
||||
|
||||
- `Prometheus` - Blackbox, node-exporter, cAdvisor, Traefik-Metrics
|
||||
- optional `Loki` - Log-Volume-Spike als Zusatz-Panel
|
||||
- bewusst nicht: `InfluxDB 3 Core` (das ist Home-Assistant-/Ecowitt-Sicht, nicht Homelab-Health)
|
||||
|
||||
## Layout (4x4 Grid, mobile-vertraeglich)
|
||||
|
||||
| Zeile | Panel | Breite (Grafana w) | Hoehe (Grafana h) |
|
||||
|---|---|---:|---:|
|
||||
| 1 | Endpoints up (Stat, gross gruen/rot) | 12 | 5 |
|
||||
| 1 | Backup heute Nacht (Stat) | 6 | 5 |
|
||||
| 1 | Naechster Cert-Ablauf (Stat, Tage) | 6 | 5 |
|
||||
| 2 | Kritische Container running (Stat-Liste) | 12 | 6 |
|
||||
| 2 | Disk-Fuellung (Bargraph, je Mountpoint) | 12 | 6 |
|
||||
| 3 | Endpoint-Tabelle (Table: Host, Status, Latenz) | 24 | 8 |
|
||||
| 4 | Cert-Tage-Tabelle (Table: Host, Tage bis Ablauf) | 12 | 6 |
|
||||
| 4 | Container-Status-Tabelle (Table: kritischer Container, Running, letztes Restart) | 12 | 6 |
|
||||
|
||||
Dashboard-Metadaten:
|
||||
|
||||
- UID: `homelab-family-view`
|
||||
- Title: `Homelab / Family View`
|
||||
- Tags: `homelab`, `family-view`, `morning-check`
|
||||
- Refresh: `30s`
|
||||
- Default-Zeitfenster: `now-24h` bis `now`
|
||||
- Folder: `Homelab`
|
||||
|
||||
## Panel-Spezifikation
|
||||
|
||||
### Panel 1: Endpoints up
|
||||
|
||||
- Type: `stat`
|
||||
- Title: `Apps online`
|
||||
- Query: `sum(probe_success{job="blackbox-http"})`
|
||||
- Anzeige: gruene Zahl bei Gesamtzahl, Wechsel auf rot wenn `< Soll-Anzahl`.
|
||||
- Threshold: Soll-Anzahl wird aus `monitoring/blackbox/blackbox.yml` und Prometheus-Scrape-Liste abgeleitet (zum Doku-Zeitpunkt 19 HTTPS-Ziele laut `docs/MIGRATION_LOG.md` 2026-05-25-Monitoring-Konsolidierung).
|
||||
- Subtitel im Panel: `von <N> erreichbar`. (Soll-Wert wird beim Bau aus dem aktuellen Target-Count gesetzt; nicht hartcoden.)
|
||||
|
||||
### Panel 2: Backup heute Nacht
|
||||
|
||||
- Type: `stat`
|
||||
- Title: `Borg-Lauf`
|
||||
- Query (sobald Borg-Stale-Metrik im Textfile-Collector live ist):
|
||||
```promql
|
||||
(time() - homelab_borg_last_completed_timestamp_seconds) / 3600
|
||||
```
|
||||
- Einheit: `h`
|
||||
- Threshold:
|
||||
- 0-26 h gruen
|
||||
- 26-30 h gelb
|
||||
- >30 h rot
|
||||
- Subtitel im Panel: `Stunden seit letztem completed-Lauf`.
|
||||
- Fallback bis Metrik live: Panel zeigt `n/a`, Doku-Hinweis in der Beschreibung.
|
||||
|
||||
### Panel 3: Naechster Cert-Ablauf
|
||||
|
||||
- Type: `stat`
|
||||
- Title: `Cert laeuft in`
|
||||
- Query:
|
||||
```promql
|
||||
min((probe_ssl_earliest_cert_expiry{job="blackbox-http"} - time()) / 86400)
|
||||
```
|
||||
- Einheit: `d` (Tage)
|
||||
- Threshold:
|
||||
- >14 gruen
|
||||
- 7-14 gelb
|
||||
- <7 rot
|
||||
- Subtitel: `Tage bis kleinste Restlaufzeit aller geprueften Hosts`.
|
||||
|
||||
### Panel 4: Kritische Container running
|
||||
|
||||
- Type: `stat`
|
||||
- Title: `Kritische Container`
|
||||
- Query (sobald Container-Up-Metrik live ist):
|
||||
```promql
|
||||
sum(homelab_critical_container_running)
|
||||
```
|
||||
- Threshold: erwartete Anzahl gruen, jeder fehlende Container rot.
|
||||
- Subtitel: `von <N> erwartet`. Erwartete Liste pflegen wir in `services/posture-check` / Textfile-Exporter (siehe `docs/AUDIT_2026-05-25_TODO.md` Sprint 3 "Container-Down-Alert").
|
||||
- Fallback: cAdvisor-Query als Naeherung, solange Textfile-Metrik noch nicht produktiv ist:
|
||||
```promql
|
||||
count(rate(container_last_seen{name=~"traefik|authelia|postgresql17|Redis|gitea|komodo-core|komodo-mongo|komodo-periphery|monitoring-prometheus|monitoring-grafana|monitoring-loki|monitoring-alertmanager"}[5m]) > 0)
|
||||
```
|
||||
|
||||
### Panel 5: Disk-Fuellung
|
||||
|
||||
- Type: `bargauge`
|
||||
- Title: `Disk-Fuellung`
|
||||
- Query:
|
||||
```promql
|
||||
100 * (1 - node_filesystem_avail_bytes{fstype!~"tmpfs|overlay"} / node_filesystem_size_bytes{fstype!~"tmpfs|overlay"})
|
||||
```
|
||||
- Anzeige: pro Mountpoint, sortiert absteigend.
|
||||
- Threshold:
|
||||
- <70 gruen
|
||||
- 70-85 gelb
|
||||
- >85 rot
|
||||
- Subtitel: `Prozent belegt`.
|
||||
|
||||
### Panel 6: Endpoint-Tabelle
|
||||
|
||||
- Type: `table`
|
||||
- Title: `Endpoint-Status`
|
||||
- Spalten:
|
||||
- Host (Instance)
|
||||
- Status (`UP` / `DOWN`)
|
||||
- Antwortzeit (probe_duration_seconds)
|
||||
- Queries:
|
||||
- `probe_success{job="blackbox-http"}` -> Mapping: `1` -> `UP` (gruen), `0` -> `DOWN` (rot)
|
||||
- `probe_duration_seconds{job="blackbox-http"}` -> Sekunden
|
||||
- Sortierung: DOWN oben, dann nach Antwortzeit absteigend.
|
||||
|
||||
### Panel 7: Cert-Tage-Tabelle
|
||||
|
||||
- Type: `table`
|
||||
- Title: `Cert-Tage bis Ablauf`
|
||||
- Spalten: Host, Tage
|
||||
- Query:
|
||||
```promql
|
||||
(probe_ssl_earliest_cert_expiry{job="blackbox-http"} - time()) / 86400
|
||||
```
|
||||
- Sortierung: aufsteigend (am ehesten ablaufende oben).
|
||||
- Color-Mapping wie Panel 3.
|
||||
|
||||
### Panel 8: Container-Status-Tabelle
|
||||
|
||||
- Type: `table`
|
||||
- Title: `Kritische Container`
|
||||
- Spalten: Container, Running (1/0), letztes Restart (Sekunden seit Start)
|
||||
- Queries:
|
||||
- sobald Textfile-Metrik live: `homelab_critical_container_running` (Label `name`)
|
||||
- Fallback aus cAdvisor: `container_start_time_seconds{name=~"<Whitelist>"}`
|
||||
- Sortierung: nicht-running zuerst.
|
||||
|
||||
## Spaeter ergaenzbar (nicht Teil der ersten Version)
|
||||
|
||||
- Loki-Log-Volume-Spike-Panel
|
||||
- node_exporter Memory-Saturation-Panel
|
||||
- Plex-Sessions (nur wenn Plex-Exporter eingerichtet ist; aktuell nicht geplant)
|
||||
- Immich Asset-Wachstum (eigenes Dashboard, nicht Family-View)
|
||||
|
||||
## Build-Reihenfolge fuer den spaeteren JSON
|
||||
|
||||
Wenn das JSON gebaut wird, bitte in dieser Reihenfolge:
|
||||
|
||||
1. Sicherstellen, dass alle Metriken aus den Queries oben in Prometheus auffindbar sind (`/graph` Smoke-Test).
|
||||
2. Dashboard in Grafana-UI manuell zusammenklicken; Layout an dieser Spezifikation entlang.
|
||||
3. JSON exportieren, in `monitoring/grafana/dashboards/family-view.json` ablegen.
|
||||
4. Provisioning-Provider laesst die Datei automatisch laden (siehe `monitoring/grafana/provisioning/dashboards/dashboards.yml`).
|
||||
5. Bei jeder Schema-Aenderung Doku hier nachziehen, damit Spec und JSON nicht driften.
|
||||
|
||||
## Smoke-Test nach Aktivierung
|
||||
|
||||
- Dashboard laedt unter `https://monitoring.kaleschke.info/d/homelab-family-view/`.
|
||||
- Alle 8 Panels rendern ohne `No data`.
|
||||
- Im Normalbetrieb erscheinen Panel 1-5 vollstaendig gruen.
|
||||
- Ein bewusster Test-Stale-Borg oder ein Container-Stop laesst die zugehoerigen Panels auf gelb/rot wechseln.
|
||||
|
||||
## Was das Dashboard NICHT ersetzt
|
||||
|
||||
- ntfy-Alerts: das Dashboard ist passiv (Pull), ntfy ist aktiv (Push). Beide sind notwendig.
|
||||
- DR-Doku: `docs/DISASTER_RECOVERY.md` bleibt die Recovery-Quelle.
|
||||
- Restore-Tests: `docs/RESTORE_DRILL_ROUTINE.md` ist die Kadenz, die das Dashboard nicht ersetzt.
|
||||
- Familien-Onboarding: `docs/FAMILY_ONBOARDING.md` ist die Doku fuer die Familie, dieses Dashboard ist Operator-Tool.
|
||||
@@ -1,6 +1,6 @@
|
||||
# Hardware Inventory - KalliLab CORE
|
||||
|
||||
Status: Hardware-Baseline erfasst; USV/Power-Loss bleibt offene Betreiberentscheidung.
|
||||
Status: Hardware-Baseline erfasst; USV/Power-Loss ist als bewusst akzeptiertes Betreiber-Risiko dokumentiert.
|
||||
Host: `Kallilabcore`
|
||||
Letzte Pruefung: 2026-05-26
|
||||
Naechster Review: 2026-08-26
|
||||
@@ -126,7 +126,7 @@ smartctl -a /dev/sdc
|
||||
|
||||
| Feld | Wert |
|
||||
|---|---|
|
||||
| USV vorhanden | Nicht validiert / keine erkannte USV |
|
||||
| USV vorhanden | Nein / keine erkannte USV |
|
||||
| Modell | Kein APC/Eaton/CyberPower-Geraet per `lsusb` erkannt |
|
||||
| Verbindung | `apcupsd` ist auf USB vorkonfiguriert, aber kein passendes USB-USV-Geraet sichtbar |
|
||||
| Software | `apcaccess` vorhanden; `apcupsd` laeuft nicht, `localhost:3551` liefert Connection refused |
|
||||
@@ -138,8 +138,9 @@ Bewertung:
|
||||
|
||||
- Aktueller Befund 2026-05-26: keine funktionierende USV-Absicherung nachgewiesen.
|
||||
- `apcupsd` ist zwar auf dem System vorhanden, aber nicht aktiv.
|
||||
- Power-Loss bleibt damit ein bewusst offenes Risiko fuer Docker-/DB-State und laufende Writes.
|
||||
- Naechste Entscheidung: echte USV anschliessen und Shutdown testen oder Risiko bewusst akzeptieren und dokumentieren.
|
||||
- Operator-Entscheidung 2026-05-26: aktuell keine USV-Anschaffung.
|
||||
- Power-Loss bleibt damit ein bewusst akzeptiertes Risiko fuer Docker-/DB-State und laufende Writes.
|
||||
- Review-Ausloeser: Hardware-Erweiterung, wiederholte Stromausfaelle, Datenkorruption oder Veraenderung der Betreiber-Prioritaet.
|
||||
|
||||
## Stromverbrauch
|
||||
|
||||
@@ -159,7 +160,7 @@ Bewertung:
|
||||
| Parity | Kleiner als neue groesste Datenplatte | Parity-Upgrade vor Datenplatten-Upgrade |
|
||||
| Boot-USB | Lesefehler oder Alter TBD | Flash-Backup verifizieren, Ersatzstick vorbereiten |
|
||||
| RAM | Swap/OOM oder Immich/Nextcloud-Druck | Ausbau planen |
|
||||
| USV | keine funktionierende USV-Abschaltung | USV anschaffen/anschliessen oder Risiko schriftlich akzeptieren |
|
||||
| USV | keine funktionierende USV-Abschaltung | Risiko am 2026-05-26 bewusst akzeptiert; bei Review erneut bewerten |
|
||||
|
||||
## Audit-Kommandos
|
||||
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
# H:/ Nearline Pull
|
||||
|
||||
Status: **produktiv** (2026-05-28). Erster echter Lauf 2026-05-27 20:45 erfolgreich. Windows Scheduled Task `KalliLab H Drive Nearline Pull` taeglich 05:30 ist seit 2026-05-28 aktiv.
|
||||
|
||||
## Erstlauf-Befund 2026-05-27
|
||||
|
||||
- Erster `-WhatIf`-loser Lauf: 18 Borg-Dump-Files erfolgreich gepullt, 4 unraid-flash-config-Files und 10 Gitea-Bundle-Files blockiert (`Zugriff verweigert`).
|
||||
- Ursache: Bundles wurden mit `chmod 600` geschrieben, Flash-Config bewusst `0600 root:root`, Filebrowser-Dump erbte 0640. Der SMB-Read-Share auf dem Operator-PC liest mit unprivilegierten Rechten, kein root.
|
||||
- Fixes im selben Sprint:
|
||||
- `ops/borg-ui/scripts/gitea-bundle-mirror.sh` schreibt Bundles und Sidecars jetzt 0644 (Bundle-Inhalt = Git-Historie, ohne Secrets durch `.gitignore`).
|
||||
- `ops/borg-ui/scripts/pre-backup-dumps.sh` setzt alle Dumps via `atomic_write` per Default auf 0644; `unraid-flash-config.*` bleibt explizit 0600.
|
||||
- `ops/h-drive-nearline/pull-critical-backups.ps1` excluded die `unraid-flash-config.*`-Familie ueber `/XF`, damit Flash-Config bewusst nicht in den Nearline-Scope kommt.
|
||||
- Zweiter Lauf (nach Fixes): beide Robocopy-Jobs Exit-Code 1, **19 Borg-Dumps + 10 Gitea-Bundle-Files** auf H:/.
|
||||
|
||||
## Zweck
|
||||
|
||||
`H:/` ist eine zweite lokale Nearline-Kopie fuer die wichtigsten Restore-Artefakte. Es ersetzt weder Hetzner/Borg noch ein echtes Off-site-/Airgap-Ziel, reduziert aber das Risiko, dass ein lokaler Restore nur vom Unraid-Array abhaengt.
|
||||
|
||||
## Quelle und Ziel
|
||||
|
||||
| Zweck | Quelle | Ziel |
|
||||
|---|---|---|
|
||||
| Aktuelle Dumps inklusive Flash-Backup | `\\192.168.178.58\backups\borg\dumps\latest` | `H:\kallilab-nearline-backups\borg-dumps\latest` |
|
||||
| Gitea-Bundles | `\\192.168.178.58\backups\git-bundles\gitea` | `H:\kallilab-nearline-backups\git-bundles\gitea` |
|
||||
|
||||
Das Skript kopiert bewusst **nicht** mit `/MIR` und loescht keine Dateien auf `H:/`. Alte Artefakte duerfen dort erst nach manueller Sichtpruefung geloescht werden.
|
||||
|
||||
## Skript
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -ExecutionPolicy Bypass -File G:\Gitea_Clone\homelab-infra\ops\h-drive-nearline\pull-critical-backups.ps1 -WhatIf
|
||||
```
|
||||
|
||||
Echter Lauf:
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -ExecutionPolicy Bypass -File G:\Gitea_Clone\homelab-infra\ops\h-drive-nearline\pull-critical-backups.ps1
|
||||
```
|
||||
|
||||
Reports landen unter:
|
||||
|
||||
```text
|
||||
H:\kallilab-nearline-backups\_reports
|
||||
```
|
||||
|
||||
Robocopy-Logs landen unter:
|
||||
|
||||
```text
|
||||
H:\kallilab-nearline-backups\_logs
|
||||
```
|
||||
|
||||
## Geplanter Schedule
|
||||
|
||||
Empfohlen: taeglich 05:30 Uhr, nach dem Borg-Dump-Fenster um ca. 04:00 Uhr.
|
||||
|
||||
Aktiv seit 2026-05-28. Tatsaechlicher Register-Befehl (RunLevel-Enum-Wert ist `Limited`, nicht `LeastPrivilege`):
|
||||
|
||||
```powershell
|
||||
$Action = New-ScheduledTaskAction `
|
||||
-Execute "powershell.exe" `
|
||||
-Argument "-NoProfile -ExecutionPolicy Bypass -File `"G:\Gitea_Clone\homelab-infra\ops\h-drive-nearline\pull-critical-backups.ps1`""
|
||||
|
||||
$Trigger = New-ScheduledTaskTrigger -Daily -At 05:30
|
||||
|
||||
$Settings = New-ScheduledTaskSettingsSet `
|
||||
-AllowStartIfOnBatteries `
|
||||
-DontStopIfGoingOnBatteries `
|
||||
-StartWhenAvailable `
|
||||
-ExecutionTimeLimit (New-TimeSpan -Hours 2)
|
||||
|
||||
Register-ScheduledTask `
|
||||
-TaskName "KalliLab H Drive Nearline Pull" `
|
||||
-Action $Action `
|
||||
-Trigger $Trigger `
|
||||
-Settings $Settings `
|
||||
-Description "Copies critical KalliLab restore artifacts from Unraid SMB backup share to H:/ nearline disk." `
|
||||
-RunLevel Limited
|
||||
```
|
||||
|
||||
Status pruefen:
|
||||
|
||||
```powershell
|
||||
Get-ScheduledTask -TaskName "KalliLab H Drive Nearline Pull" | Format-List TaskName, State
|
||||
Get-ScheduledTaskInfo -TaskName "KalliLab H Drive Nearline Pull" | Format-List LastRunTime, LastTaskResult, NextRunTime, NumberOfMissedRuns
|
||||
```
|
||||
|
||||
Manueller Trigger zum Testen:
|
||||
|
||||
```powershell
|
||||
Start-ScheduledTask -TaskName "KalliLab H Drive Nearline Pull"
|
||||
```
|
||||
|
||||
Verhalten:
|
||||
|
||||
- Laeuft als angemeldeter User (`RunLevel Limited`); wenn der PC abgemeldet ist, wartet der Task bis zur naechsten Anmeldung (`StartWhenAvailable`).
|
||||
- Akku-Modus blockiert nicht (`AllowStartIfOnBatteries`).
|
||||
- Maximale Laufzeit 2 h, danach wird der Task abgebrochen.
|
||||
|
||||
## Erfolgscheck
|
||||
|
||||
Nach einem echten Lauf muessen mindestens diese Artefakte unter `H:\kallilab-nearline-backups` liegen:
|
||||
|
||||
- `borg-dumps\latest\immich.dump`
|
||||
- `borg-dumps\latest\komodo-mongo.archive.gz`
|
||||
- `borg-dumps\latest\postgresql17-paperless.dump`
|
||||
- `borg-dumps\latest\postgresql17-mailarchiver.dump`
|
||||
- `borg-dumps\latest\nextcloud.dump`
|
||||
- `borg-dumps\latest\mealie.dump`
|
||||
- `borg-dumps\latest\gitea.sqlite.dump`
|
||||
- `borg-dumps\latest\vaultwarden.sqlite.dump`
|
||||
- `git-bundles\gitea\latest-report.md`
|
||||
- `git-bundles\gitea\micha\*.bundle`
|
||||
|
||||
Bewusst **nicht** im Nearline-Scope:
|
||||
|
||||
- `unraid-flash-config.tar.gz` (hostseitig 0600 root:root; Restore-Quelle bleibt das Hetzner-Borg-Repo, siehe `docs/RESTORE_MATRIX.md` Tier 1 Unraid OS Flash).
|
||||
|
||||
## Schutzregeln
|
||||
|
||||
- Kein CIFS-/SMB-Hard-Mount von `H:/` auf Unraid.
|
||||
- Kein Borg-Repo direkt auf `H:/` ueber SMB.
|
||||
- Kein `/MIR` und kein automatisches Loeschen auf `H:/`.
|
||||
- Flash-Backup wie Secret behandeln; `H:/` bleibt lokaler Operator-Datentraeger.
|
||||
@@ -0,0 +1,78 @@
|
||||
# Immich Restore Test
|
||||
|
||||
Status: **erfolgreich live verifiziert** (2026-05-27)
|
||||
Audit-Bezug: `docs/archive/2026-05/AUDIT_2026-05-25.md` Finding **F-11**
|
||||
|
||||
## Zweck
|
||||
|
||||
Schliesst die Audit-Luecke aus F-11: Immich ist der groesste Datentopf (Familien-Fotos), und bisher gibt es im Gegensatz zu Vaultwarden, Gitea und Paperless **keinen** verifizierten Mini-Restore-Test. Dieses Dokument verlinkt die Repo-Artefakte und beschreibt den Ablauf aus Operator-Sicht.
|
||||
|
||||
## Repo-Artefakte
|
||||
|
||||
| Datei | Zweck |
|
||||
|---|---|
|
||||
| `ops/restore-tests/immich-compose.test.yml` | isoliertes Test-Compose: Immich-Postgres mit VectorChord + Redis + Immich-Server, ML weggelassen, `127.0.0.1:12283` |
|
||||
| `ops/restore-tests/immich-restore-test.sh` | Host-Bash-Skript fuer den Lauf, mit `--what-if` und `--keep-data` Flags |
|
||||
| `ops/restore-tests/immich-restore-test.ps1` | Plan-Scaffold fuer Windows-Operator-Sicht (kein Live-Run) |
|
||||
| `ops/restore-tests/immich-plan.md` | Fachlicher Plan: Quellen, Schutzregeln, Smoke-Test-Kriterien, bekannte Risiken |
|
||||
| `ops/restore-tests/immich-runbook.md` | Konkreter Operator-Ablauf, Fehlerfaelle, Schedule-Vorschlag |
|
||||
|
||||
## Was der Test abdeckt
|
||||
|
||||
- Extraktion von `local/borg-dumps/latest/immich.dump` aus dem aktuellsten Borg-Archiv
|
||||
- Import in eine isolierte `ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0` Postgres-Instanz mit demselben Digest wie Produktion
|
||||
- Start eines isolierten Immich-Server-Containers mit demselben Digest wie Produktion, **ohne** ML-Container und **ohne** Traefik
|
||||
- Smoke-Test: Login-Seite erreichbar, `asset`- und `"user"`-Tabelle lesbar
|
||||
- Markdown-Report unter `/mnt/user/backups/restore-reports/immich-YYYY-MM-DD.md`
|
||||
- Bereinigung von Test-Container und Restore-Lab-Daten nach Erfolg
|
||||
|
||||
## Was der Test bewusst NICHT abdeckt
|
||||
|
||||
- Wiederherstellung produktiver Foto-Dateien (`/mnt/user/photos/immich`, `/mnt/user/photos/family_archive`). Diese Pfade werden vom Test nicht angefasst und nicht in den Test-Container gemountet.
|
||||
- Machine-Learning-Container. Spart Image-Pull-Zeit und RAM; ML-Features sind im Smoke-Test irrelevant.
|
||||
- Echte Login-Flow per API. Smoke-Test prueft nur, dass Login-Seite ausgeliefert wird.
|
||||
- Asset-Rendering / Thumbnail-Generierung. Ohne Foto-Files erwartet.
|
||||
- Produktive Domain `immich.kaleschke.info`. Test laeuft ausschliesslich auf `127.0.0.1:12283`.
|
||||
|
||||
## Restore-Stufe
|
||||
|
||||
Der Test deckt **Stufe 4 (kritische Anwendungen)** aus `docs/DISASTER_RECOVERY.md` Phase 4 fuer Immich ab, allerdings nur DB-Ebene und UI-Smoke. Voll-Restore inklusive Foto-Dateien aus Borg ist eigener Folgeschritt; das Skript bereitet die Restore-Lab-Struktur dafuer vor.
|
||||
|
||||
## Erster Lauf und Preflight
|
||||
|
||||
| Pruefung | Verantwortlich | Wo |
|
||||
|---|---|---|
|
||||
| Dump-Groesse von `immich.dump` bestimmen | erledigt 2026-05-27 | 66M unter `/mnt/user/backups/borg/dumps/latest/immich.dump` |
|
||||
| Freier Platz unter `/mnt/user/backups/restore-lab/` | erledigt 2026-05-27 | ca. 3.7T frei auf `/mnt/user/backups` |
|
||||
| Borg-UI-Container laeuft | Operator | `docker ps | grep borg-ui` |
|
||||
| Trockenlauf mit `--what-if` | erledigt 2026-05-27 | Host-Clone auf `c5d231a`, `bash ops/restore-tests/run-restore-checks.sh immich --what-if` erfolgreich |
|
||||
| Erster echter Lauf | erledigt 2026-05-27 | Report `/mnt/user/backups/restore-reports/immich-2026-05-27.md`; Archiv `Tägliche-Sicherung-2026-05-27T04:30:06.778`; HTTP `200`; Assets `11977`; User `1` |
|
||||
|
||||
## Nach dem ersten erfolgreichen Lauf
|
||||
|
||||
1. Report unter `/mnt/user/backups/restore-reports/immich-2026-05-27.md` liegt vor und ist erfolgreich.
|
||||
2. `docs/RESTORE_MATRIX.md`, `ops/restore-tests/schedule.md`, `docs/AUDIT_2026-05-25_TODO.md` und `docs/MIGRATION_LOG.md` wurden nachgezogen.
|
||||
3. Quartalsweise Wiederholung einplanen; erster Live-Lauf bleibt bewusst manuell/Operator-kontrolliert, bis mehrere Laeufe stabil waren.
|
||||
|
||||
## Schutzregeln
|
||||
|
||||
- Skript greift ausschliesslich auf den Restore-Lab-Pfad und den Borg-Extract-Cache zu.
|
||||
- Produktive Pfade unter `/mnt/user/photos/*`, `/mnt/user/appdata/immich_postgres_vectorchord/` und dem Rollback-Altstand `/mnt/user/appdata/immich_postgres/` werden nicht angefasst.
|
||||
- Produktive Container `immich_server`, `immich_postgres`, `immich_redis`, `immich_machine_learning` werden nicht gestoppt, nicht beruehrt.
|
||||
- Borg-Passphrase wird aus `/mnt/user/appdata/secrets/borg_repo_passphrase.txt` gelesen und nicht in Reports, Logs oder Doku geschrieben.
|
||||
- Test-Container publishen nur auf `127.0.0.1:12283`, nicht auf LAN- oder Tailscale-Interface.
|
||||
- Keine Traefik-Labels, keine Public-URL fuer Testcontainer.
|
||||
|
||||
## Risiken (aus `ops/restore-tests/immich-plan.md`)
|
||||
|
||||
- Dump-Groesse und erster `pg_restore`-Lauf sind gemessen: `immich.dump` 66M, echter Smoke-Test erfolgreich am 2026-05-27.
|
||||
- VectorChord-/pgvector-Extension-Mismatch bei Image-Drift moeglich; Compose pinnt denselben Digest wie Produktion.
|
||||
- Immich nutzt nach der VectorChord-Migration `vchord 0.4.3` und `vector 0.8.1`; Restore-Tests muessen ein Image mit VectorChord verwenden.
|
||||
- Immich-Server-Migrations koennen Startup nach Restore verzoegern; Skript pollt 120 s.
|
||||
- Bei Schema-Drift (z. B. nach Major-Update) koennen einzelne DB-Queries abweichen; das Skript versucht Immich-v2-Singular-Tabellen und aeltere Plural-Fallbacks.
|
||||
|
||||
## Naechste Operator-Schritte
|
||||
|
||||
1. Quartalsweise Wiederholung nach `ops/restore-tests/schedule.md` einplanen.
|
||||
2. Bei zukuenftigem Immich-Major-Upgrade den Restore-Test unmittelbar danach einmal manuell ausfuehren.
|
||||
3. Voll-Restore inklusive Foto-Dateien bleibt ein eigener, deutlich groesserer DR-Drill.
|
||||
+337
-4
@@ -1,4 +1,4 @@
|
||||
# Migration Log - Homelab GitOps
|
||||
# Migration Log - Homelab GitOps
|
||||
|
||||
Dieses Dokument ist nur noch ein historischer Verlauf. Der aktuelle operative Ablauf steht in `docs/WORKFLOW.md`, das Zielbild in `HOMELAB_ARCHITECTURE_MASTER_V2.md`.
|
||||
|
||||
@@ -17,6 +17,339 @@ Dieses Dokument ist nur noch ein historischer Verlauf. Der aktuelle operative Ab
|
||||
|
||||
## Historische Meilensteine
|
||||
|
||||
### 2026-05-31 - Doku-Restliste bereinigt und Paperless-Restore-Drill nachgezogen
|
||||
|
||||
Nach dem Doku-Archiv-Sweep wurden die verbliebenen aktiven offenen Punkte bereinigt: Offsite-Beschluss, H:/-Nearline-Status, Capacity-Zeitziele und Netzwerk-Inventar wurden auf den tatsaechlichen Stand gebracht.
|
||||
|
||||
- H:/ Nearline-Pull ist seit 2026-05-28 produktiv; alte offene Beschaffungs-/Schedule-Punkte fuer ein zweites Offsite-Ziel wurden als bewusst nicht umgesetzt bzw. Review-Trigger dokumentiert.
|
||||
- InfluxDB 3 Core Port `8181` ist effektiv nur auf `127.0.0.1` gebunden; keine LAN-Exposure.
|
||||
- Echter Paperless-Restore-Drill erfolgreich: Borg-Archiv `Taegliche-Sicherung-2026-05-31T04:30:13.181`, isolierte Testcontainer `restoretest-paperless`, `restoretest-paperless-postgres`, `restoretest-paperless-redis`, HTTP `200`, Login-Marker ok, `32` Dokumente im Test-DB-Check. Report: `/mnt/user/backups/restore-reports/paperless-2026-05-31.md`.
|
||||
- Cleanup verifiziert: keine `restoretest-paperless*` Container mehr, Restore-Lab-Pfad `/mnt/user/backups/restore-lab/paperless` entfernt.
|
||||
### 2026-05-31 - Komodo-Mongo Major-Upgrade auf MongoDB 8.0
|
||||
|
||||
Komodo-Mongo wurde kontrolliert von `mongo:7.0.34` auf `mongo:8.0.23@sha256:44aa79ae28ff80b56fe58681b66cda9336706df408a5175a6c04988aa54610d3` gehoben. Die Renovate-Branch `renovate/mongo-8.x` wurde nicht direkt gemerged, weil sie veraltet war und Dokumentationsstand aus `master` zurueckgedreht haette; ausserdem schlug sie `8.3.2` vor. Nach Pruefung der MongoDB-Versionierung wurde bewusst die Major-Release-Schiene `8.0.x` gewaehlt.
|
||||
|
||||
- Vorabtest: frischer Dump `/mnt/user/backups/borg/dumps/latest/komodo-mongo-pre-major-20260531-142155.archive.gz` wurde in isolierten Container `restoretest-komodo-mongo8-20260531-142155` mit MongoDB `8.0.23` restored; 24 Collections, ca. 88k Objekte, keine ungueltigen `system.buckets`; Report: `/mnt/user/backups/restore-reports/komodo-mongo8-20260531-142155.md`.
|
||||
- Produktiv-Backups vor Rollout: Compose `/mnt/user/appdata/komodo/_workspace_backups/komodo-compose-before-mongo8-20260531-142411.yaml`, Dump `/mnt/user/backups/borg/dumps/latest/komodo-mongo-pre-major-prod-20260531-142411.archive.gz`.
|
||||
- Rollout: Host-Mirror `/mnt/user/services/homelab-infra` auf Commit `59b9392` fast-forwarded, Self-Stack-Compose nach `/mnt/user/services/stacks/komodo/compose.yaml` synchronisiert, `komodo-core` fuer den DB-Binary-Wechsel gestoppt, `komodo-mongo` mit `docker compose up -d --no-deps komodo-mongo` recreated und danach Core/Periphery wieder gestartet.
|
||||
- Smoke nach 5 Minuten: `komodo-mongo` healthy auf `mongo:8.0.23`, `komodo-core` up, `komodo.kaleschke.info` HTTP `200`, Mongo-Ping `1`, Version `8.0.23`, FCV bleibt bewusst `7.0`, Core- und Mongo-Logs in sauberem Nachlauf ohne neue Fehler.
|
||||
- Watchpoint: FCV nicht sofort auf `8.0` setzen; erst nach Burn-in und expliziter Downgrade-Entscheidung. `renovate.json` begrenzt Komodo-Mongo gezielt auf `8.0.x`, damit Renovate weiter 8.0-Patches liefern kann, aber nicht automatisch auf den 8.2+/8.3-Minor-Track zieht. Renovate-PR #8 wurde mit Kommentar geschlossen, weil die Branch noch `8.3.2` vorschlug.
|
||||
|
||||
### 2026-05-31 - Renovate-Cron Folgelauf und Branch-Hygiene
|
||||
|
||||
Nach dem Merge der ersten fuenf Renovate-PRs lief der Cron am 2026-05-31 12:20 MESZ erfolgreich (`rc=0`), aber die erledigten Remote-Branches existierten noch. Diese fuenf Branches waren vollstaendig in `origin/master` enthalten und wurden remote geloescht: `renovate/mongo-7.0.32`, `renovate/postgres-17.9`, `renovate/minor-patch-updates`, `renovate/mongo-7.x`, `renovate/postgres-17.x`.
|
||||
|
||||
- Manueller Kontrolllauf danach: `bash /mnt/user/services/homelab-infra/ops/renovate/run-renovate.sh`, Ergebnis `rc=0`.
|
||||
- Neue offene PRs: #7 Grafana 13, #8 Mongo 8, #9 Postgres 18, #10 Redis 8.
|
||||
- Keine Minor-/Patch-Nachzuegler offen; Dependency Dashboard #6 wurde aktualisiert.
|
||||
- Verifiziert: `komodo-core` und `komodo-periphery` tauchen in keinem Major-PR-Diff auf; die Renovate-Package-Rule gegen Komodo-Major greift.
|
||||
- Watchpoint: #7-#10 bleiben reine Major-Entscheidungsvorlagen und werden nicht automatisch gemerged.
|
||||
|
||||
### 2026-05-31 - Komodo Deploy-Drift strukturell abgesichert
|
||||
|
||||
Nach dem Renovate-Block wurde die Ursache fuer den kurzzeitigen `nextcloud-postgres`-Drift nachgezogen: Komodo hatte den `nextcloud`-Stack beim Postgres-17.10-Deploy gestartet, aber `docker compose up -d` scheiterte zunaechst an einem Docker-Recreate-Namenskonflikt (`nextcloud-postgres` Ersatzcontainer). Der Workspace war dadurch bereits auf dem neuen Commit, waehrend der laufende DB-Container noch das alte Image nutzte. Der spaetere gezielte `docker compose up -d` aus dem aktualisierten Workspace hat den Zwischenzustand sauber aufgeloest; es blieben keine exited/dead Containerreste.
|
||||
|
||||
- Komodo-Update-Historie bestaetigt: ein fehlgeschlagener `DeployStack` fuer `nextcloud` mit Compose-Up-Konflikt, danach ein erfolgreicher `DeployStack`.
|
||||
- Aktueller Runtime-Stand nach Pruefung: `nextcloud`, `nextcloud-postgres`, `nextcloud-redis`, `postgresql17`, `mealie-postgres`, `gitea`, `bentopdf`, `ddns-updater` und Komodo-Self-Stack laufen ohne `unhealthy`-Status; die erwarteten Images stimmen mit den Compose-Dateien ueberein.
|
||||
- `services/posture-check/export-prometheus-textfile.sh` exportiert jetzt `homelab_gitops_runtime_image_match{name,project,service}` fuer laufende Compose-Container. Die Metrik vergleicht das Image aus `docker compose config --format json` gegen `docker inspect .Config.Image` des laufenden Containers und faengt damit genau den Zustand "Workspace/Compose neu, Runtime alt" ab.
|
||||
- Neue Prometheus-Regel `HomelabGitOpsRuntimeImageDrift`: feuert als Warning, wenn ein laufender Compose-Container laenger als 10 Minuten nicht dem Compose-Image entspricht.
|
||||
- Smoke: Exporter-Test in `/tmp/kallilab-textfile-test/homelab.prom` lieferte fuer alle erkannten Compose-Container `homelab_gitops_runtime_image_match = 1`; `promtool check rules` meldete `SUCCESS: 17 rules found`.
|
||||
- Beim Live-Reload zeigte Prometheus nach dem Git-Pull einen `stale file handle` auf die bind-gemountete `alerts.yml`. Fix: nur `monitoring-prometheus` aus dem aktuellen Monitoring-Workspace per `docker compose up -d --force-recreate --no-deps prometheus` neu erstellt. Danach: `promtool check rules` erfolgreich, Lifecycle-Reload erfolgreich, Regel `HomelabGitOpsRuntimeImageDrift` geladen und `inactive`.
|
||||
|
||||
### 2026-05-31 - Renovate PRs #1 bis #5 gemerged und deployed
|
||||
|
||||
Die ersten fuenf Renovate-PRs wurden einzeln in `master` uebernommen, mit Policy-Check und Live-Smoke nach den Datenhalter-Aenderungen. Major-Branches wurden bewusst nicht gemerged.
|
||||
|
||||
- #1 `renovate/mongo-7.0.32`: Mongo-Digest fuer Komodo uebernommen, Merge-Commit `b8b0af9`.
|
||||
- #2 `renovate/postgres-17.9`: Postgres-17.9-Digest fuer `postgresql17`, `mealie-postgres` und `nextcloud-postgres` uebernommen, Merge-Commit `db1fa7c`.
|
||||
- #3 `renovate/minor-patch-updates`: gruppierte Minor-/Patch-Updates uebernommen, Merge-Commit `dde4419`. Danach wurden die Komodo-Workspaces `gitea`, `bentopdf` und `ddns-updater` wegen Drift gezielt gesichert, auf `origin/master` synchronisiert und neu deployed. Backups liegen unter `/mnt/user/appdata/komodo/_workspace_backups/*-workspace-before-renovate-pr3-resync-*.tar.gz`.
|
||||
- #4 `renovate/mongo-7.x`: Komodo-Mongo von `7.0.32` auf `7.0.34` gehoben, Merge-Commit `076676d`. Da der Komodo-Self-Stack nicht ueber einen normalen Git-Webhook redeployed, wurde vorher ein Compose-Backup (`/mnt/user/appdata/komodo/_workspace_backups/komodo-compose-before-renovate-20260531-125102.yaml`) und ein frischer Mongo-Dump (`/mnt/user/backups/borg/dumps/latest/komodo-mongo-pre-renovate-20260531-125102.archive.gz`) erstellt, danach der Self-Stack kontrolliert aktualisiert.
|
||||
- #5 `renovate/postgres-17.x`: Postgres von `17.9` auf `17.10` fuer `postgresql17`, `mealie-postgres` und `nextcloud-postgres` gehoben, Merge-Commit `96fcacc`. `postgresql17` und `mealie-postgres` wurden durch Komodo recreated; `nextcloud-postgres` musste aus dem bereits aktualisierten Workspace `/mnt/user/services/stacks/nextcloud/apps/nextcloud` einmal gezielt mit `docker compose up -d` nachgezogen werden.
|
||||
- `ops/policy-checks/check_repo.ps1` blieb nach den Merge-Commits ohne Criticals; einzige Warning ist weiterhin die dokumentierte InfluxDB-root-Ausnahme.
|
||||
- Smoke-Beleg nach Settle: `postgresql17` healthy auf `postgres:17.10@sha256:0027bef...`, `mealie-postgres` und `nextcloud-postgres` laufen auf demselben `17.10`-Digest, `mealie.kaleschke.info` HTTP `200`, `cloud.kaleschke.info` HTTP `302`, `git.kaleschke.info` HTTP `200`, `komodo.kaleschke.info` HTTP `200`, keine `unhealthy` Container.
|
||||
- Gitea listete #1 bis #5 nach den lokalen Merge-Commits noch als offen. Jeder PR bekam per API einen Kommentar mit dem manuellen Merge-Commit und wurde geschlossen, damit Renovate keinen offenen Altstand behaelt.
|
||||
- Watchpoint: Renovate-Branches `mongo-8.x`, `postgres-18.x`, `redis-8.x` und `major-major-updates` bleiben bewusst ungemerged und brauchen separate Operator-Entscheidung.
|
||||
|
||||
### 2026-05-31 - Gitea Komodo-Workspace-Drift bereinigt
|
||||
|
||||
Der Komodo-Workspace fuer den `gitea`-Stack unter `/mnt/user/services/stacks/gitea` war auf `1d0cba9` stehengeblieben, 70 Commits hinter `origin/master`, mit 23 untracked Pfaden. Dadurch war die von Docker genutzte Compose-Datei nicht identisch mit dem aktuellen Repo-Stand (`core/gitea/docker-compose.yml`), obwohl der Gitea-Komodo-Webhook aktiv war.
|
||||
|
||||
- Vor der Bereinigung wurde der komplette Workspace gesichert: `/mnt/user/appdata/komodo/_workspace_backups/gitea-workspace-before-resync-20260531-122515.tar.gz`.
|
||||
- Die untracked Pfade wurden einzeln gegen `origin/master` geprueft. 18 Eintraege waren byte-identisch, vier Doku-Dateien waren aeltere Zwischenstaende, und `ops/h-drive-nearline/pull-critical-backups.ps1` war ebenfalls identisch. Es gab keinen nur-im-Workspace-gueltigen neueren Arbeitsstand.
|
||||
- Danach wurden nur die bekannten untracked Konfliktpfade aus dem Workspace entfernt und `git pull --ff-only origin master` ausgefuehrt. Ergebnis: `stacks/gitea` steht sauber auf `e6a0e9f`, `## master...origin/master`, ohne Dirty-State.
|
||||
- `docker compose up -d` aus `/mnt/user/services/stacks/gitea/core/gitea` lief ohne Recreate-Zwang; der laufende Container nutzt weiterhin den Stack-Workspace als Compose-Quelle und ist `healthy`.
|
||||
- Smoke-Test: `docker exec gitea wget -qO- http://localhost:3000/api/healthz` liefert `status: pass`, `https://git.kaleschke.info` liefert HTTP `200`, und die lokale Gitea-API fuer `Micha/homelab-infra` antwortet mit `default_branch: master`.
|
||||
- Watchpoint: Gitea ist ein Henne-Ei-Stack, weil der Dienst sein eigenes Git-Origin bereitstellt. Webhook und `auto_pull` sind aktiv, aber Workspace-Drift muss bei kuenftigen Gitea-Aenderungen besonders bewusst geprueft werden; kein pauschales `git clean -fd` ohne vorherige Sicherung und Vergleich.
|
||||
|
||||
### 2026-05-31 - Komodo 5xx-Spam eingegrenzt: LAN-Client statt Stack-Fehler
|
||||
|
||||
`HomelabTraefik5xx` feuerte fuer `service="komodo@docker"`, weil wiederkehrende Komodo-UI-API-Requests ohne gueltige Session (`GET /user`, zeitweise `POST /read/GetCoreInfo`) von Traefik als 500 gezaehlt wurden. Komodo Core selbst loggte keine internen Fehler; die 500-Antwort ist ein Komodo-Auth-Pfad-Bug-on-top, aber nicht die primaere Betriebsstoerung.
|
||||
|
||||
- Bestaetigt: Blackbox-Exporter erklaert nur `GET /` alle 15s. Waehrend `monitoring-blackbox-exporter` gestoppt war, verschwanden die `/`-200-Probes, `/user`-500 lief aber weiter.
|
||||
- Ausgeschlossen: `cert-token-check.sh` prueft keine Komodo-Domain; Komodo Periphery war nach 130s Stop nicht die Quelle; Glance war bereits vorab durch Stop-Test ausgeschlossen.
|
||||
- Core-Isolation: Bei gestopptem `komodo-core` liefen die Client-Requests weiter, aber Traefik loggte sie als 404 ohne `komodo@docker`-Service. Nach Core-Start wurden dieselben Requests wieder zu `komodo@docker`-500. Damit ist die Quelle ein LAN-/Client-Geraet, nicht Komodo Core als Self-Poll.
|
||||
- Lokale Client-Suche: Auf dem Windows-Operator-PC `192.168.178.103` bestanden HTTPS-Verbindungen zur WAN-IP `217.249.121.39`. Brave war zunaechst plausibel, weil die Brave-Session alte Komodo-Tabs enthielt; ein Brave-Schluss beendete den 5xx-Takt jedoch nicht. Danach blieb als lokaler Kandidat nur `Codex.exe` mit Verbindung zur WAN-IP. Der in-app Browser zeigte keine offene Seite, daher ist der operative Fix: Codex-App/Thread nach Abschluss schliessen bzw. neu starten; falls der Takt danach wider Erwarten weiterlaeuft, naechster Schritt ist LAN-Geraetesuche am Router/Switch statt Repo-Aenderung.
|
||||
- Kein Repo-/Komodo-Fix umgesetzt: Monitoring-Regel und Komodo-Compose bleiben unveraendert. Ein Alert-Exclude fuer `komodo@docker` waere nur ein letzter Ausweg und wurde nicht gesetzt.
|
||||
- Smoke-Beleg waehrend der Eingrenzung: `traefik`, `komodo-core`, `komodo-periphery`, `komodo-mongo` und `monitoring-blackbox-exporter` liefen nach den Stop/Start-Tests wieder; `komodo-mongo` und `traefik` waren healthy.
|
||||
|
||||
### 2026-05-30 - Komodo-Bootstrap-Trockenlauf Erstlauf (F-09 Rest abgeschlossen)
|
||||
|
||||
Skript ist seit 2026-05-29 vorbereitet, heute erster echter Lauf auf dem Host.
|
||||
|
||||
- Aufruf: `bash /mnt/user/services/homelab-infra/ops/restore-tests/komodo-bootstrap-test.sh --keep-data`
|
||||
- Vorlauf: `--what-if` zur Plan-Verifikation, danach echter Lauf, beides ohne Eingriff in den produktiven Komodo-Stack.
|
||||
- Ergebnis: `SUCCESS`, alle 5 Smoke-Checks gruen.
|
||||
- `docker compose config valid: ok`
|
||||
- `Test-Mongo healthy: ok` (Mongo healthy in ~6 s)
|
||||
- `Mongo authenticated ping (Test-Creds): ok`
|
||||
- `Komodo Core HTTP status: 200` (Login-Seite ausgeliefert)
|
||||
- `Test-Periphery container state: running`
|
||||
- Report: `/mnt/user/backups/restore-reports/komodo-bootstrap-2026-05-30.md`
|
||||
- Isolation hielt wie geplant: produktive Container `komodo-mongo`, `komodo-core`, `komodo-periphery` unter Project `komodo` blieben unangetastet, ebenso `/mnt/user/appdata/komodo/{mongo,core,periphery}` und die produktiven `KOMODO_*`-Secrets. Test lief unter Project `restoretest-komodo` mit Wegwerf-Datadir `/mnt/user/backups/restore-lab/komodo/`, Wegwerf-Secrets im Test-Compose und Test-Port nur auf `127.0.0.1:19120`.
|
||||
- Operator-Klick bewusst nicht von Claude uebernommen: `ssh root@kallilabcore` ist eine Aktionsklasse, die in CLAUDE.md ausdruecklich Operator-Anweisung verlangt. Der Auto-Mode-Classifier hat einen nicht-destruktiven SSH-Probe entsprechend blockiert. Der Operator hat den Befehl im Unraid-Webterminal selbst gestartet.
|
||||
- Bedeutung: `ops/komodo/docker-compose.yml` ist als Recovery-Anker fuer die Bootstrap-Stufen A-F in `docs/SERVICES_RECOVERY.md` jetzt **belegt** tauglich, nicht mehr nur angenommen tauglich. Image-Digests (mongo:7.0.32, komodo-core:2, komodo-periphery:2) und Mongo-Auth-Schema sind verifiziert.
|
||||
- Lab-Daten unter `/mnt/user/backups/restore-lab/komodo/` bleiben mit `--keep-data` erhalten, Test-Container wurden im EXIT-Trap sauber abgeraeumt. Operator entscheidet, ob das Lab-Verzeichnis (~300 MB) entfernt wird.
|
||||
|
||||
Folgeschritt fuer `docs/RESTORE_DRILL_ROUTINE.md`: Komodo-Bootstrap-Trockenlauf passt zum quartalsweisen DR-Sanity-Check (Q4) oder als wiederholbarer Standalone-Drill. Aktuell kein Host-Schedule, Aufruf bleibt manuell.
|
||||
|
||||
### 2026-05-30 - F-10: Authelia Repo<->Host Drift-Check
|
||||
|
||||
Der dokumentierte "by-design"-Drift zwischen `security/authelia/configuration.yml` (Repo-Baseline) und `/mnt/user/appdata/authelia/config/configuration.yml` (Host) wird jetzt automatisch ueberwacht. Vorher: Manueller Merge auf den Host war Pflicht, aber keine Pruefung. Eine vergessene ACL-Synchronisation waere erst bei einem Login-Fehler aufgefallen.
|
||||
|
||||
- Neues Skript `services/authelia-diff.sh`: extrahiert die `access_control:`-Sektion aus beiden YAMLs per awk-Block-Extractor (Top-Level-Key bis zum naechsten Top-Level-Key), normalisiert Kommentar- und Leerzeilen, vergleicht via `diff -u`. Default-Sektion ist `access_control`, weil das laut F-10 der primaere Drift-Vektor ist; per env `AUTHELIA_DIFF_SECTIONS` koennen weitere Top-Level-Sektionen (`session`, `regulation`, `totp`, ...) ergaenzt werden. OIDC-Clients, Identity-Provider und Secret-Werte bleiben bewusst aussen vor.
|
||||
- Exit-Code-Schema: 0 = ok, 1 = Drift (Diff auf stdout), 2 = Datei fehlt, 3 = Sektion fehlt, 4 = Werkzeug fehlt. Macht das Skript auch standalone nutzbar (`ssh kallilab "bash /mnt/user/services/homelab-infra/services/authelia-diff.sh"`).
|
||||
- `services/posture-check/posture-check.sh` ruft das Skript am Ende des Checks-Blocks auf (`check_authelia_config_drift`). Drift wird als **Warning** gemeldet, nicht Critical, weil die produktive Authelia trotz Drift weiter laeuft und die ACL fuer schon angemeldete Sessions weiter wirkt. Skip-Mechanismus: `SKIP_AUTHELIA_DRIFT=1`. Pfad-Override: `AUTHELIA_DIFF_SCRIPT`.
|
||||
- Pflicht-Setup auf dem Host: Repo-Spiegel unter `/mnt/user/services/homelab-infra/` als read-only-Clone von Gitea `Micha/homelab-infra` mit regelmaessigem `git pull --ff-only`. Default-Pfade des Skripts setzen das voraus. Ohne Repo-Spiegel meldet der Check Warning, weil die Baseline-Datei fehlt - keine stille Inaktivierung.
|
||||
- Lokaler Smoke-Test 2026-05-30 erfolgreich: identische Files -> rc=0; ACL-Drift im Domain-Eintrag `scrutiny.kaleschke.info -> scrutiny-renamed.kaleschke.info` -> rc=1 mit unified diff, ACL-Block korrekt extrahiert, Kommentar- und Leerzeilen rausgefiltert. False-Positive auf `session.default_redirection_url`-Aenderung korrekt vermieden (gehoert nicht zu `access_control`).
|
||||
- `docs/WORKFLOW.md` hat jetzt eine eigene Sektion "Ausnahme: Authelia configuration.yml" analog zur Traefik-Dynamic-Sektion. Pflicht-Workflow: 1. Repo-Aenderung + Commit + Push, 2. manueller Merge in die Host-Datei mit Erhalt der OIDC-Sektionen, 3. `docker restart authelia` + Login-Smoke-Test, 4. `services/authelia-diff.sh` muss `exit 0` liefern.
|
||||
- `docs/REPO_MAP.md` und `docs/SERVICE_CATALOG.md` zeigen das Skript und den neuen Posture-Check-Eintrag.
|
||||
|
||||
Operator-Folgeschritt (klein, nicht heute): Repo-Spiegel `/mnt/user/services/homelab-infra/` auf dem Host einrichten und in den vorhandenen `gitea-bundle-mirror-6h`-Plan oder einen eigenen 6h-Cron einbinden, damit das Skript einen aktuellen Vergleichsstand findet.
|
||||
|
||||
### 2026-05-29 - Stack-Hygiene Sprint: Healthchecks, Monitoring-Digests, Komodo-Bootstrap-Skript, Renovate-Vorbereitung
|
||||
|
||||
Vier Audit-Punkte am Stueck abgearbeitet. Pro Block: Live-Verifikation am Host, Doku im Repo.
|
||||
|
||||
**F-15 Tier-1 Healthchecks**
|
||||
|
||||
- 6 Tier-1-Stacks bekommen Healthchecks: postgresql17 (`pg_isready`), Redis (`redis-cli ping` mit Auth aus dem mount), Vaultwarden (`curl /alive`), Gitea (`wget /api/healthz`), Traefik (`traefik healthcheck --ping`, vorher `--ping=true` in CLI aktiviert), Authelia (`wget /api/health` - Authelia v4.39 hat `helper health-check` entfernt, daher direkter Endpoint).
|
||||
- Erste Iteration in Vaultwarden + Authelia schlug fehl: Vaultwarden hat kein `wget`, Authelia kennt das `helper`-Subcommand nicht mehr. Probe per `docker exec` zeigte: Vaultwarden hat `curl`, Authelia hat `wget`. Compose entsprechend nachgezogen, zweiter Lauf gruen.
|
||||
- Komodo-Stack-Workspaces fuer `postgresql17` (124 commits behind) und `gitea` (52 commits behind) wurden Komodo-seitig nicht automatisch gepullt. Manuell ueber `git pull --ff-only` plus `cp` der aktuellen Compose-Datei aus dem Host-Repo-Clone in den Stack-Workspace synchronisiert, dann `docker compose up -d`. Gitea-Workspace hatte zusaetzlich untracked Doku-Files; nur die im aktuellen Master tracked-en Files entfernt, nicht via `git clean -fd`. Workspace-Drift selbst ist nicht heute Auftrag, aber als Folge-Befund notiert.
|
||||
- Endstand Live: alle 6 Healthchecks `healthy`.
|
||||
|
||||
**F-07 Monitoring-Stack Digest-Pinning**
|
||||
|
||||
- 9 Container in `monitoring/docker-compose.yml` per Tag@sha256 gepinnt (prometheus, alertmanager, alertmanager-ntfy-bridge, blackbox-exporter, loki, promtail, grafana, node-exporter, cadvisor; plus zweiter python:3.13-alpine im Bootstrap-Dashboard-Importer). InfluxDB war bereits gepinnt.
|
||||
- Digests aus den laufenden Containern per `docker inspect ... .Config.Image` + `docker image inspect ... .RepoDigests` ausgelesen, damit die Pins exakt dem Live-Stand entsprechen.
|
||||
- Kein Recreate ausgeloest, weil die Images identisch sind; nur die Compose-Datei ist jetzt reproduzierbar wie die Tier-1-Stateful-Stacks.
|
||||
|
||||
**F-09 Rest - Komodo-Bootstrap-Trockenlauf-Skript**
|
||||
|
||||
- `ops/restore-tests/komodo-bootstrap-{compose.test.yml,test.sh,plan.md,runbook.md}` analog zum Immich-Restore-Test-Muster angelegt.
|
||||
- Test-Compose nutzt dieselben Image-Digests wie Produktion (mongo:7.0.32, komodo-core:2, komodo-periphery:2), isoliert unter Compose-Project `restoretest-komodo`, Test-Port nur `127.0.0.1:19120`, **Test-Periphery ohne docker.sock-Mount und ohne `/mnt/user/services`-Mount** - kann produktive Container nicht managen.
|
||||
- Wegwerf-Secrets sind im Compose hardcodiert, produktive `KOMODO_*`-Werte werden nicht beruehrt.
|
||||
- Smoke-Test-Kriterien: docker compose config valid, Mongo healthy, Mongo Auth-Ping ok, Core HTTP 200/302/303/401, Periphery container `running`.
|
||||
- Erster Lauf bleibt manueller Operator-Schritt.
|
||||
|
||||
**F-12 Renovate-Bot (live)**
|
||||
|
||||
- Repo-Config in `renovate.json` (Repo-Root): nur extends, packageRules, ignorePaths, manager file patterns, labels, rangeStrategy. Bot-Config separat in `ops/renovate/bot-config.js`: platform, endpoint, autodiscover=false, repositories=["Micha/homelab-infra"], gitAuthor, Concurrent-Limits. Trennung war noetig: Renovate liest die `renovate.json` im Repo als REPO-Config; Bot-Felder darin wurden als "this repo is disabled" fehlinterpretiert (Repository result: forbidden, status: disabled).
|
||||
- `ops/renovate/run-renovate.sh` als One-Shot-Container-Wrapper. Wichtige Haertungen waehrend des Setups:
|
||||
- `--add-host git.kaleschke.info:192.168.178.58`: Renovate-Container kann den Hostname sonst nicht aufloesen (`EAI_AGAIN`). Analog zur `extra_hosts`-Loesung in der Komodo-Compose.
|
||||
- `--env-file` statt `-e RENOVATE_TOKEN=...`: Token war sonst in `ps` und `docker inspect` sichtbar.
|
||||
- `chmod 0777` auf `/mnt/user/services/renovate/state`: Renovate-Image laeuft als uid 12021 (ubuntu), kann root-owned Mount sonst nicht beschreiben.
|
||||
- Live-Setup am Host:
|
||||
- Service-Account `renovate` (uid 2, **kein Admin**) ueber `gitea admin user create` angelegt.
|
||||
- Collaborator-Status mit Write-Permission auf `homelab-infra` (initialer DB-Insert hat den Gitea-Permissions-Cache nicht aktualisiert; Renovate sah `permissions.push=false` und brach mit "Repository does not permit pull or push" ab; saubere Loesung war Operator-UI-Klick "Entfernen + neu hinzufuegen", was den Cache konsistent aktualisiert; Befund-Bestaetigung via Doku-Studium `lib/modules/platform/gitea/index.ts`: die Push-Check ist hardcoded, kein Bypass moeglich).
|
||||
- Personal-Access-Token mit Scopes `read:user,write:repository,write:issue`, in `/mnt/user/appdata/secrets/renovate_token.txt` (chmod 600). Token wurde einmal rotiert, weil der Wert beim ersten Erzeugen im SSH-Output sichtbar war.
|
||||
- User-Script `renovate-six-hourly` mit Cron `20 */6 * * *` live in `/etc/cron.d/root`.
|
||||
- Erstlauf 2026-05-29 erfolgreich: 5 PRs (mongo digest, mongo 7.0.32->7.0.34, postgres digest, postgres 17.9->17.10, minor-and-patch-updates gruppiert), 1 Issue "Renovate Dependency Dashboard", 8 Branches (drei Major-Branches warten auf naechsten Lauf wegen prConcurrentLimit=5). Komodo-Major-Updates wurden korrekt durch packageRule unterdrueckt.
|
||||
- `docs/RENOVATE.md` zeigt die ursprueglichen 5 Operator-Schritte fuer Neuaufsetzen bzw. Disaster Recovery.
|
||||
|
||||
### 2026-05-29 - Borg-Source `/local/appdata/homepage` verspaetet entfernt + Removal-Checkliste in WORKFLOW
|
||||
|
||||
- Befund aus den ersten Tagen scharfer Alert-Pipeline: `HomelabBorgLastJobCompletedWithWarnings` firing fuer die letzten vier Borg-Laeufe (26.05.-29.05.), jeweils Exit-Code 107.
|
||||
- Ursache im Borg-UI-Logfile `backup_job_39_*.log`: `"/local/appdata/homepage: stat: [Errno 2] No such file or directory"`. Borg-UI-Source-Liste enthielt seit der Homepage-Entfernung am 25.05. weiterhin den Eintrag `/local/appdata/homepage`; der Appdata-Pfad war aber bereits nach `_archive/homepage-removed-2026-05-25/` verschoben.
|
||||
- Operator hat den Eintrag in der Borg-UI manuell entfernt; Source-Liste jetzt 23 statt 24 Eintraege, `homepage` nicht mehr drin. Naechster Borg-Lauf 2026-05-30 04:30 sollte wieder `completed` ohne Warning sein.
|
||||
- Backups waren nicht gefaehrdet: trotz Exit-Code 107 wurden alle anderen 23 Quellen sauber archiviert (Stats Job 39: 100.895 Dateien, 26.72 GB Original, 317 MB deduplicated).
|
||||
- Erkenntnis: bei Stack-Removal wurde die Borg-Source-Liste damals nicht mit-aufgeraeumt. **`docs/WORKFLOW.md`** um neuen Abschnitt "Service-Removal-Checkliste" erweitert, der die Borg-UI-Source-Bereinigung explizit als Pflichtschritt 8 nennt (zusammen mit allen anderen Schritten wie Komodo-Destroy, Gitea-Webhook, Authelia-ACL, Blackbox-Target, Doku).
|
||||
- Positiv-Befund: die ntfy-Push-Pipeline (Cron `*/5` Textfile-Export -> node-exporter -> Prometheus -> Alertmanager -> ntfy-Bridge), die am 2026-05-27 scharfgeschaltet wurde, hat den Drift binnen 24 h sichtbar gemacht. Das ist der intendierte Mechanismus.
|
||||
|
||||
### 2026-05-28 - H:/-Pull als Windows Scheduled Task aktiviert
|
||||
|
||||
- Task `KalliLab H Drive Nearline Pull` registriert auf dem Operator-Windows-PC: taeglich 05:30 (nach dem Borg-Dump-Fenster um ca. 04:00), `RunLevel Limited`, `AllowStartIfOnBatteries`, `DontStopIfGoingOnBatteries`, `StartWhenAvailable`, `ExecutionTimeLimit 2 h`. Naechster Lauf 2026-05-29 05:30.
|
||||
- Repo-Doku `docs/H_DRIVE_NEARLINE_PULL.md` Status auf "produktiv" gesetzt, Register-Snippet auf den tatsaechlich ausgefuehrten Befehl korrigiert (PowerShell-Enum `LeastPrivilege` -> `Limited`; alter Snippet haette beim ersten Aufruf einen Parameter-Binding-Fehler geworfen).
|
||||
- Verifikation am Windows-PC: `Get-ScheduledTask` zeigt State `Ready`, Trigger-Start 2026-05-28T05:30, RunLevel `Limited`.
|
||||
- Kein Eingriff am Host noetig; SMB-Quelle und H:/-Ziel waren bereits vorbereitet.
|
||||
|
||||
### 2026-05-28 - Zweites Off-site bewusst nicht umgesetzt
|
||||
|
||||
- Operator-Bewertung: 3-2-1-Regel ist mit aktueller Topologie erfuellt (Live + lokales Borg-Repo + Hetzner-Borg + H:/-Nearline = 4 Kopien / 3 Medien / 1 Off-site).
|
||||
- Ein zweites Off-site wuerde **ausschliesslich** das Szenario "Hetzner-Account verloren" zusaetzlich abdecken. Eintrittswahrscheinlichkeit niedrig (etablierter deutscher Anbieter, dokumentierter Zahlungsweg). Aufwand und Kosten unverhaeltnismaessig fuer Familien-Homelab.
|
||||
- Beschluss in `docs/OFFSITE_BACKUP_OPTIONS.md` mit Review-Triggern dokumentiert; F-03 in `docs/AUDIT_2026-05-25_TODO.md` von "offen" auf "erledigt (bewusst nicht umgesetzt)".
|
||||
- Stattdessen drei Folge-TODOs zur Haertung der bestehenden Topologie:
|
||||
- Hetzner-Account-Hygiene: starkes Passwort + Backup-Zahlungsweg + Login-Benachrichtigungen. **Bewusst keine 2FA** (Operator-Entscheidung analog USV-Risiko-Akzeptanz).
|
||||
- Borg `--append-only` auf Hetzner pruefen. Befund: Repo `appdata-critical` laeuft aktuell im Mode `full`, `custom_flags` leer. Setup waere server-seitig in Hetzner-`authorized_keys`.
|
||||
- H:/-Pull als Windows Scheduled Task aktivieren (Skript und Doku ready, Erstlauf 2026-05-27 erfolgreich, Task selbst noch nicht angelegt).
|
||||
- Bewusst NICHT angefasst: laufende Backup-Pipeline (kein Test-Restore, kein Modus-Wechsel ohne Folge-Sprint).
|
||||
|
||||
### 2026-05-28 - paperless-gpt und BentoPDF bewusst behalten
|
||||
|
||||
- Befund: Beide Container laufen Up 3 days, aber **0 Traefik-Zugriffe in den letzten 7 Tagen** und kein User-LLM-Event in den paperless-gpt-Logs. BentoPDF-Logs zeigen ausschliesslich Docker-Healthchecks.
|
||||
- Resource-Footprint vernachlaessigbar: `paperless-gpt` 34 MB RAM, `bentopdf` 4 MB RAM, beide 0 % CPU.
|
||||
- Operator-Entscheidung 2026-05-28 (gegen die Nicht-Nutzung): **beide behalten**.
|
||||
- `paperless-gpt`: bleibt aktiv bis Paperless-NGX 3.0 verfuegbar ist. Paperless 3.0 wird native KI-Features mitbringen; danach neu entscheiden, ob `paperless-gpt` noch noetig ist oder abgeloest werden kann.
|
||||
- `bentopdf`: bleibt aktiv als situatives PDF-Werkzeug; Footprint zu klein, um eine harte Entfernung zu rechtfertigen.
|
||||
- Doku-Anker in `docs/SERVICE_CATALOG.md` ergaenzt, damit die Frage in 6 Monaten nicht erneut als "warum laeuft das?" auftaucht.
|
||||
|
||||
### 2026-05-28 - Plex Server Reclaim und Remote Access deaktiviert
|
||||
|
||||
- Befund beim Versuch, Remote Access in der Plex-UI zu deaktivieren: Plex-Server war seit 18.05.2026 13:18 nicht mehr mit einem Plex.tv-Account geclaimt. `Preferences.xml` 391 Bytes, ohne `PlexOnlineMail`/`PlexOnlineUsername`/`PlexOnlineToken`. Login als `Xeridos` lieferte "Keine Berechtigung" auf den lokalen Server. Zusaetzlich waren die `library_sections` leer (Backups vom 19./22./28.05. ebenfalls ~370 KB statt MBs); die Bibliotheks-Konfiguration war seit dem 18.05. weg. Filmdateien unter `/mnt/user/media/*` blieben unangetastet (833 Verzeichnisse, ~1.7 TB).
|
||||
- Reclaim als `Xeridos` durchgefuehrt: Operator-Token via `plex.tv/claim` erzeugt, am Host als Shell-Inline-ENV beim `docker compose up -d --force-recreate plex` mitgegeben. Token wurde **nicht** in `.env`, **nicht** in Compose, **nicht** in Komodo-Stack-ENV geschrieben. Nach Erfolg sauberer zweiter Recreate ohne Token, damit `docker inspect`-Snapshot keinen Token mehr enthaelt. Bash-History defensiv geleert.
|
||||
- Endstand laut `Preferences.xml`: `PlexOnlineUsername="Xeridos"`, `PlexOnlineMail="michideheld@gmx.de"`, `PlexOnlineHome="1"`, `PublishServerOnPlexOnlineKey="0"` (Remote Access aus).
|
||||
- Operator hat im Anschluss die Bibliotheken neu angelegt (`/data/movies`, `/data/Heimatfilme`) und Remote Access in der Plex-UI auf "deaktiviert" gesetzt; Metadata-Cache wuchs in den ersten 18 Minuten auf 630 MB.
|
||||
- Plex bleibt damit strikt LAN/Tailscale-only, konsistent zur FRITZ!Box-Bereinigung vom selben Tag. Smart-TVs (Schlaf-/Wohnzimmer) finden den Server ueber WLAN-LAN per mDNS/Plex-GDM unveraendert.
|
||||
- `HOMELAB_ARCHITECTURE_MASTER_V2.md` Sektion 13 enthaelt die ausfuehrliche Recovery-Geschichte. `docs/SERVICE_CATALOG.md` und Sektion 7.4 auf "LAN/Tailscale-only, Remote Access aus" praezisiert.
|
||||
|
||||
### 2026-05-28 - FRITZ!Box-Portfreigaben bereinigt
|
||||
|
||||
- WAN-Soll auf eine einzige Freigabe reduziert: `443/tcp -> 192.168.178.58:443` (Traefik HTTPS).
|
||||
- `80/tcp` aus FRITZ!Box-UI entfernt. Validierung: Mobilfunk-Test ergibt Timeout auf `http://vault.kaleschke.info`, `https://vault.kaleschke.info` weiter erreichbar; lokal greift Traefik-Redirect 80->443 nach wie vor. Cloudflare-DNS-Challenge braucht kein Port 80.
|
||||
- `222/tcp` bleibt bewusst nicht eingerichtet. Begruendung: Tailscale ist Operator-Pfad, GitHub-Push-Mirror `michaelkaleschke-spec/homelab-infra` deckt Repo-Bootstrap-Pfad ab, Gitea-Bundles unter `/mnt/user/backups/git-bundles/gitea` decken Offline-Restore ab. `HOMELAB_ARCHITECTURE_MASTER_V2.md` Sektion 10 entsprechend mit "Tailscale-only, bewusst nicht WAN-freigegeben" praezisiert.
|
||||
- UPnP-Selbstfreigabe-Recht fuer `PC-192-168-178-71` (Hostname `VONETS.COM`, MAC `00:17:13:2F:61:96`) deaktiviert. Identifiziert als VONETS-WiFi-Bridge, vermutlich Bridge-Anbindung zum SolarEdge-Wechselrichter. SolarEdge-Cloud-Sync ist outbound und benoetigt keine UPnP. Aktuell waren 0 Selbstfreigaben aktiv; die Aenderung ist praeventiv gegen kuenftige Anforderungen.
|
||||
- `docs/NETWORK_INVENTORY.md`, `docs/archive/2026-05/FRITZBOX_PORT_CORRECTION_PLAN.md` und `docs/AUDIT_2026-05-25_TODO.md` Sprint 4 entsprechend nachgezogen.
|
||||
- Bewusst NICHT angefasst: FRITZ!OS 8.21 Update (Service-Fenster), IPv6-Exposure (separater Folgeschritt), WAN-Ausfallschutz (bewusst aus).
|
||||
|
||||
### 2026-05-27 - Monitoring-Alerts live, Gitea-Bundle-Cron live, H:/-Pull live
|
||||
|
||||
Drei Audit-TODOs gleichzeitig auf "erledigt" gezogen; alle Aenderungen mit Host-Smoke verifiziert.
|
||||
|
||||
**Monitoring-Alerts (Borg-Stale / Cert-Expiry / Container-Down)**
|
||||
|
||||
- Auf dem Host neuer User-Script `export-prometheus-textfile-5min` mit Cron `*/5 * * * *` angelegt. Schreibt `/mnt/user/services/posture-check/textfile/homelab.prom`.
|
||||
- Repo: `services/posture-check/export-prometheus-textfile.sh` setzt jetzt vor dem `mv` per `chmod 644`, damit node-exporter (`nobody:65534`) lesen kann. Vorher `0600 root:root` ? `node_textfile_scrape_error 1`.
|
||||
- `monitoring-prometheus` wurde einzeln per `docker restart` neu gestartet, um den `stale file handle` auf der gebundenen `alerts.yml` zu loesen. Kein Stack-Down. `promtool check rules` SUCCESS 14 rules, `lastConfigTime 2026-05-27T18:33:06Z`. Aktive Alerts: 1 firing (`HomelabTraefik5xx` aus dem 2026-05-20-Befund), 1 pending (`HomelabBorgLastJobCompletedWithWarnings` durch `completed_with_warnings`-Status des letzten Borg-Laufs).
|
||||
- Pipeline end-to-end: Textfile-Skript ? node-exporter Textfile-Collector ? Prometheus ? alerts.yml-Regeln.
|
||||
|
||||
**Gitea-Bundle-Schedule**
|
||||
|
||||
- User-Script `gitea-bundle-mirror-6h` mit Cron `10 */6 * * *` (00:10/06:10/12:10/18:10).
|
||||
- Repo: `ops/borg-ui/scripts/gitea-bundle-mirror.sh` schreibt Bundles und Sidecars jetzt `chmod 644` statt `600`. Begruendung: Bundle-Inhalt ist Git-Historie ohne Secrets (durch `.gitignore` abgedeckt), nicht sensibler als die uebrigen 0644-Dumps. Damit funktioniert der Nearline-Pull ueber SMB.
|
||||
- Existierende Bundles wurden manuell auf 0644 angehoben. Erster Cron-Lauf 2026-05-27 18:41 UTC erfolgreich: 4 Bundles, Checksums OK.
|
||||
|
||||
**H:/ Nearline-Pull**
|
||||
|
||||
- Erster scharfer Lauf 2026-05-27 20:25 zeigte vier Permission-Befunde: `unraid-flash-config.*` (4 Files, by-design 0600), `filebrowser.bolt.dump` (0640, Source erbt), und alle 10 Gitea-Bundle-Files (0600).
|
||||
- Repo: `ops/h-drive-nearline/pull-critical-backups.ps1` excluded jetzt die `unraid-flash-config.*`-Familie ueber `/XF` (Restore-Quelle bleibt Hetzner-Borg). `ops/borg-ui/scripts/pre-backup-dumps.sh` setzt alle Dumps via `atomic_write` per Default auf 0644, Flash-Config-Familie explizit mit `atomic_write target tmp 600`.
|
||||
- Existierende Files am Host nachtraeglich auf 0644 angehoben.
|
||||
- Zweiter Lauf 2026-05-27 20:45: beide Robocopy-Jobs Exit 1, **19 Borg-Dumps + 10 Gitea-Bundle-Files** unter `H:\kallilab-nearline-backups`. Report-Tabellen-Quirk in PowerShell durch `& robocopy @args | Out-Null` behoben (Live-Output landete vorher in `$results`).
|
||||
- `docs/H_DRIVE_NEARLINE_PULL.md` mit Erstlauf-Befund, gefixter Erwartungs-Liste und expliziter Out-of-Scope-Anmerkung fuer Flash-Config aktualisiert.
|
||||
- Windows Scheduled Task taeglich 05:30 bleibt **bewusst** offen bis zur Operator-Bestaetigung.
|
||||
|
||||
**Was bewusst NICHT angefasst wurde**
|
||||
|
||||
- Authelia/OIDC/CrowdSec/Nextcloud-Haertung (geparkt).
|
||||
- Hermes (Review 2026-07-25).
|
||||
- USV (Risiko bewusst akzeptiert).
|
||||
- FRITZ!Box-/Plex-/UI-Punkte (Punkt 5 fuer morgen frueh).
|
||||
- `unraid-flash-config.tar.gz` bleibt bewusst 0600 und ausserhalb des Nearline-Scopes.
|
||||
|
||||
### 2026-05-27 - Vorbereitungsdokumente FRITZ!Box-Korrektur und Off-site-Optionen
|
||||
|
||||
- Reine Doku-Aenderung; kein Router-, Provider- oder Host-Eingriff.
|
||||
- `docs/archive/2026-05/FRITZBOX_PORT_CORRECTION_PLAN.md` neu angelegt: Korrektur-Plan fuer drei Punkte (`80/tcp` entfernen, `222/tcp` nicht ergaenzen solange Tailscale stabil, UPnP-Selbstfreigabe `PC-192-168-178-71` deaktivieren). Operator-Go ausstehend; jede UI-Aenderung wird gesondert dokumentiert.
|
||||
- `docs/OFFSITE_BACKUP_OPTIONS.md` neu angelegt: Entscheidungsvorlage fuer zweites Off-site-Ziel mit drei Optionen (rsync.net Borg-Plan, BorgBase EU2, rotierende Cold-Platte). Bewertung gegen Provider-Trennung, Standort, Preis und Konto-Risiko; Empfehlung rsync.net oder Cold-Platte; BorgBase EU2 explizit nicht empfohlen wegen gleichem Anbieter. Kein Provider gebucht, keine Kosten ausgeloest.
|
||||
- `docs/REPO_MAP.md` um beide neuen Dokumente ergaenzt.
|
||||
- `docs/AUDIT_2026-05-25_TODO.md` Sprint 4 (FRITZ!Box) und Sprint 7 (Off-site) mit Verweis auf die neuen Vorbereitungsdokumente nachgezogen.
|
||||
|
||||
### 2026-05-27 - Doku-Sprint Bootstrap / Family-View / Onboarding / Drill-Routine
|
||||
|
||||
- Reine Doku-Aenderung; kein Compose-, Secret-, Host- oder Router-Eingriff.
|
||||
- `docs/SERVICES_RECOVERY.md` "Komodo Bootstrap" auf linearen Stufenpfad A-F ausgebaut: Recovery-Anker bleibt `ops/komodo/docker-compose.yml`, Self-Stack ist explizit kein Anker, Secret-Restore-Reihenfolge verweist auf `docs/SECRETS_MAP.md` Stack-ENV-only-Sektion, Validierungs-Kommandos ergaenzt. Trockenlauf-Idee als Folgeaufgabe eingetragen, kein Repo-Skript dafuer.
|
||||
- `docs/DISASTER_RECOVERY.md` Phase 4 Stufe 3 verweist explizit auf den Bootstrap-Pfad in `docs/SERVICES_RECOVERY.md` und auf die Stack-ENV-only-Sektion in `docs/SECRETS_MAP.md`.
|
||||
- `docs/FAMILY_VIEW_DASHBOARD.md` neu angelegt: Spezifikation fuer `homelab-family-view`-Dashboard, 8 Panels (Endpoints up, Borg-Frische, Cert-Tage, Kritische Container, Disk-Fuellung, Endpoint-Tabelle, Cert-Tabelle, Container-Tabelle), PromQL-Queries, Thresholds, Build-Reihenfolge. Bewusst noch **kein** `monitoring/grafana/dashboards/family-view.json` angelegt, weil Borg-Stale-/Cert-Expiry-/Container-Down-Metriken laut Sprint 3 noch "in Arbeit" sind.
|
||||
- `docs/FAMILY_ONBOARDING.md` final redigiert: Status auf "Final-Stand vor Wochenend-Einladung", 2FA-Beschreibung auf App-eigene 2FA praezisiert (kein SSO-Versprechen, weil Authelia-OIDC weiter geparkt ist), neuer "Bewusst nicht versprochen"-Block (kein Einheits-Login, kein 24/7-SLA, kein Hotline-Support, keine Datenweitergabe).
|
||||
- `docs/RESTORE_DRILL_ROUTINE.md` neu angelegt: Drei-Stufen-Modell (Freshness woechentlich / Mini-Restore monatlich-bimonatlich / DR-Sanity quartalsweise), Quartals-Kadenz Q1-Q4 mit Dienst-Rotation, bestaetigte Mini-Restores (Vaultwarden, Gitea, Paperless 2026-05-07; Immich 2026-05-27), 10-Punkte-Sanity-Check, Abbruch-Regel mit Verweis auf `docs/GITOPS_DRIFT_RUNBOOK.md`. Kein Host-Schedule angelegt, nur Doku.
|
||||
- `ops/restore-tests/schedule.md` verweist jetzt auf `docs/RESTORE_DRILL_ROUTINE.md` als Quelle der Quartals-Belegung.
|
||||
- `docs/REPO_MAP.md` um `docs/FAMILY_VIEW_DASHBOARD.md`, `docs/RESTORE_DRILL_ROUTINE.md` und `docs/IMMICH_RESTORE_TEST.md` ergaenzt.
|
||||
- `docs/AUDIT_2026-05-25_TODO.md` aktualisiert: Sprint 2 "Komodo-Bootstrap-Pfad beschreiben", Sprint 3 "Family-View Dashboard definieren", Sprint 4 "Familien-Onboarding schreiben" und Sprint 7 "Restore-Lab-Drill quartalsweise dokumentieren" jeweils auf "erledigt".
|
||||
- Geparkte Punkte bleiben unveraendert: Authelia-2FA/OIDC/CrowdSec, Nextcloud-Haertung, Hermes (Review 2026-07-25), USV-Anschaffung.
|
||||
|
||||
### 2026-05-27 - Immich Restore-Smoke-Test praktisch verifiziert (F-11)
|
||||
|
||||
- Erster echter Immich-Restore-Smoke-Test gegen das produktive Borg-Archiv erfolgreich: `Tägliche-Sicherung-2026-05-27T04:30:06.778`, Report `/mnt/user/backups/restore-reports/immich-2026-05-27.md`.
|
||||
- Validiert wurden Borg-Extract von `local/borg-dumps/latest/immich.dump`, Import in isolierten `tensorchord/pgvecto-rs:pg14-v0.2.0` Test-Postgres, Start des Immich-Servers ohne ML und ohne Traefik, HTTP `200` auf `127.0.0.1:12283`, Login-Marker, `11977` Assets und `1` User im Test-DB-Check.
|
||||
- Produktive Container und produktive Foto-Pfade wurden nicht angefasst; Testdaten und Testcontainer wurden nach Erfolg bereinigt.
|
||||
- Im Lauf wurden Restore-Test-Haertungen umgesetzt: Borg-`known_hosts` aus `/data/known_hosts` wird fuer SSH-Trust genutzt, `completed_with_warnings`-Archive gelten als verwendbare Restore-Quelle, Postgres-Startfenster werden retry-faehig behandelt, Immich-v2-Upload-Marker werden im leeren Test-Mount erzeugt und Smoke-Checks schlagen bei HTTP-/Marker-Fehlern hart fehl.
|
||||
- `docs/IMMICH_RESTORE_TEST.md`, `docs/RESTORE_MATRIX.md`, `ops/restore-tests/schedule.md` und `docs/AUDIT_2026-05-25_TODO.md` nachgezogen; F-11 ist damit abgeschlossen. Voll-Restore inklusive Foto-Dateien bleibt ein separater DR-Drill.
|
||||
|
||||
### 2026-05-27 - FRITZ!Box-Portfreigaben gegen Repo-Soll abgeglichen
|
||||
|
||||
- FRITZ!Box-UI `Internet -> Freigaben -> Kallilabcore` geprueft: aktiv sind `HTTP-Server` TCP `80/tcp` und `HTTPS-Server` TCP `443/tcp` auf `192.168.178.58`.
|
||||
- Repo-Soll aus `docs/NETWORK_INVENTORY.md` ist nur `443/tcp` plus optional gewolltes Gitea-SSH `222/tcp`. Der aktuelle Zustand weicht ab: `80/tcp` ist offen, `222/tcp` fehlt.
|
||||
- Kallilabcore ist nicht als Exposed Host markiert und erlaubt keine selbststaendige Portfreigabe. `PC-192-168-178-71` erlaubt selbststaendige Portfreigabe, hat aber `0 aktiv`.
|
||||
- Keine FRITZ!Box-Aenderung vorgenommen. Router-Korrektur bleibt ein produktiver Operator-Schritt nach ausdruecklicher Freigabe.
|
||||
|
||||
### 2026-05-26 - Immich Restore-Smoke-Test vorbereitet (F-11)
|
||||
|
||||
- `docs/IMMICH_RESTORE_TEST.md` und `ops/restore-tests/immich-plan.md`/`immich-runbook.md` beschreiben den geplanten Immich-Mini-Restore: `immich.dump` aus Borg, isolierter pgvecto-rs-Test-Postgres, Test-Redis, Immich-Server ohne ML, lokaler Port `127.0.0.1:12283`, keine produktiven Foto-Mounts.
|
||||
- `ops/restore-tests/immich-restore-test.sh`, `immich-restore-test.ps1` und `immich-compose.test.yml` wurden vorbereitet; der Dispatcher kennt `immich --what-if`.
|
||||
- Lokal verifiziert: Bash-Syntax, `run-restore-checks.sh immich --what-if`, PowerShell-Dispatcher `-Mode immich -WhatIf`, Docker-Compose-Render und Policy-Check. Kein echter Host-Restore, kein Borg-Extract, kein Produktiv-Container-Eingriff.
|
||||
- Host-Preflight 2026-05-27: Host-Clone per Fast-forward auf `c5d231a`, `immich.dump` 66M, `/mnt/user/backups` ca. 3.7T frei, `run-restore-checks.sh immich --what-if` erfolgreich. Kein echter Restore-Lauf.
|
||||
- F-11 bleibt fachlich offen bis zum ersten Host-Lauf mit Report unter `/mnt/user/backups/restore-reports/immich-YYYY-MM-DD.md`.
|
||||
|
||||
### 2026-05-27 - H:/ Nearline-Pull vorbereitet
|
||||
|
||||
- `docs/H_DRIVE_NEARLINE_PULL.md` und `ops/h-drive-nearline/pull-critical-backups.ps1` definieren den Windows-seitigen Pull von `\\192.168.178.58\backups\borg\dumps\latest` und `\\192.168.178.58\backups\git-bundles\gitea` nach `H:\kallilab-nearline-backups`.
|
||||
- SMB-Quelle `\\192.168.178.58\backups` und H:/ sind erreichbar; `-WhatIf` prueft den Plan ohne Kopie.
|
||||
- Kein Scheduled Task angelegt und kein echter Kopierlauf gestartet. Empfohlen ist taeglich 05:30 nach dem Borg-Dump-Fenster, Aktivierung erst nach Operator-Sichtpruefung.
|
||||
|
||||
### 2026-05-27 - Storage Layout als Active v1.4 gefuehrt
|
||||
|
||||
- `docs/STORAGE_LAYOUT.draft.md` wurde zu `docs/STORAGE_LAYOUT.md` umbenannt. Das Dokument war inhaltlich bereits als Active markiert; Version 1.4 entfernt den formalen Draft-Blocker.
|
||||
- Physikalische Disk-Werte aus `docs/HARDWARE_INVENTORY.md` und Host-Readout uebernommen: Cache Samsung 970 EVO Plus 1.8T XFS, Disk1 WDC WD60EFAX 5.5T XFS auf `md1p1`, Parity TOSHIBA HDWG480 7.3T, Boot Samsung Flash Drive 59.8G FAT32, H:/ als Nearline-Ziel.
|
||||
- `docs/AUDIT_2026-05-25_TODO.md` Sprint 2 fuer Storage-Layout und Disk-/Share-Baseline auf erledigt gesetzt. Retention-Kalibrierung, Monitoring-Schwellen und RESTORE_MATRIX-Detailklassifikation bleiben normale Folgeaufgaben.
|
||||
|
||||
### 2026-05-27 - F-08 Alert-Regeln vorbereitet
|
||||
|
||||
- `services/posture-check/export-prometheus-textfile.sh` erzeugt Textfile-Metriken fuer Borg-Backup-Frische und kritische Container unter `/mnt/user/services/posture-check/textfile/homelab.prom`.
|
||||
- `monitoring/docker-compose.yml` aktiviert den Node-Exporter-Textfile-Collector. `monitoring/prometheus/alerts.yml` enthaelt vorbereitete Alerts fuer Borg-Stale, Borg-Fehlerstatus, Borg-Warnstatus, Textfile-Stale, Critical-Container-Down und TLS-Cert-Expiry 21/7 Tage.
|
||||
- Host-Smoke 2026-05-27: Skript erzeugt `homelab.prom`, alle gelisteten kritischen Container melden `1`, Borg-Status ist `completed_with_warnings` und wird als Warning statt Critical modelliert.
|
||||
- Kein Monitoring-Redeploy und kein Scheduled Task in diesem Schritt. Abschluss erfolgt nach Host-Schedule, Prometheus-Reload und Testalert.
|
||||
|
||||
### 2026-05-26 - Audit F-16 und F-20 abgeschlossen (Doku-only)
|
||||
|
||||
- F-16: `infra/redis`-Etikett auf die Realitaet abgeglichen. `docs/SERVICE_CATALOG.md`, `docs/REPO_MAP.md`, `HOMELAB_ARCHITECTURE_MASTER_V2.md` Sektion 13 und `docs/DISASTER_RECOVERY.md` Bootstrap-Stufe 2 beschreiben Redis jetzt als "primaer Paperless-Redis (App-Cache); historisch als shared angelegt, faktisch nur von Paperless genutzt". Immich, Nextcloud, Mealie eigene Redis-Instanzen; Authelia bewusst ohne Redis. Keine Compose-Aenderung.
|
||||
- F-20: Restore-Wege fuer Stack-ENV-only Secrets explizit gemacht. Neuer Abschnitt `6.2.1 Restore-Quellen fuer Stack-ENV-Werte` in `docs/DISASTER_RECOVERY.md` (Reihenfolge Komodo-Mongo-Dump -> Vaultwarden -> externe Notiz, Komodo-Sonderfall, Paperless als Hauptanwendung). Neuer Abschnitt `Stack-ENV-only Secrets - Restore-Wege` in `docs/SECRETS_MAP.md` mit Tabelle je Stack (Paperless, Immich, Mail-Archiver, Speedtest, Komodo, Hermes, Glance). Glance-Widget-Tokens explizit als rebuildbar markiert. Konkrete Werte werden nirgendwo dokumentiert.
|
||||
- `docs/AUDIT_2026-05-25_TODO.md` Sprint 6 entsprechend auf "erledigt" gestellt.
|
||||
- Kein Eingriff in `ops/borg-ui/scripts/gitea-bundle-mirror.sh`, kein Gitea-Bundle-Trockenlauf, keine SSH-/Host-Pruefung.
|
||||
|
||||
### 2026-05-26 - FRITZ!Box-/H:/-/Family-Onboarding-Doku-Update
|
||||
|
||||
- `docs/NETWORK_INVENTORY.md` mit FRITZ!Box-Baseline gefuellt: FRITZ!Box 7590, FRITZ!OS 8.21 (Update gemeldet, nicht eingespielt), Telekom DSL ~87/36 Mbit/s, 36 aktive Heimnetz-Geraete, LAN 1-4 verbunden, WLAN `Fritzi`, Gast-WLAN inaktiv, Telefonie/DECT aktiv, Ausfallschutz nicht eingerichtet, USB nicht verbunden, 2 Portfreigaben aktiv. Soll fuer Portfreigaben: nur `443/tcp` und `222/tcp` auf `192.168.178.58`.
|
||||
- `docs/EXTERNAL_DEPENDENCIES.md` um Telekom-DSL und FRITZ!Box 7590 als WAN-/Router-Abhaengigkeit erweitert; Ausfall-Szenario "Telekom-DSL / FRITZ!Box gestoert" ergaenzt.
|
||||
- `docs/CAPACITY_AND_LIFECYCLE.md` um Abschnitt "H:/ als zusaetzliches lokales Backup-Ziel" ergaenzt. Bewertung: H:/ ist als zweite lokale Nearline-Kopie und Freeze-Sicherung sinnvoll, aber bewusst **kein** Offsite-Ersatz und **kein** CIFS-Hard-Mount am Unraid (STORAGE_LAYOUT §12.6). Pull-Modell vom Windows-PC bleibt der getestete Weg (vgl. Disk1 Phase 2 Freeze 2026-05-25).
|
||||
- `docs/FAMILY_ONBOARDING.md` von Tabellen-Entwurf auf familienverstaendlichen Begruessungstext umgestellt: kurze App-Erklaerungen, konkrete Was-tun-Wenn-Anleitungen (Webseite weg, Passwort vergessen, 2FA verloren, Foto-Backup haengt, Browser-Warnung), "Was du nicht musst"-Block, Hinweis fuer geplante Wochenend-Einladung.
|
||||
- `docs/AUDIT_2026-05-25_TODO.md` Leitplanken aktualisiert: Authelia 2FA/OIDC/CrowdSec und Nextcloud-2FA-Haertung werden in diesem Zyklus nicht angefasst (Operator-Vorgabe). Hermes-Agent geparkt mit Review-Deadline 2026-07-25. USV-Risiko bewusst akzeptiert. Sprint 4 um H:/-Bewertung und FRITZ!Box-Portfreigaben-Abgleich erweitert. Neue Sprints 6 (geparkte Apps) und 7 (Off-site) ergaenzt.
|
||||
- Keine Live-/Compose-Aenderung in diesem Commit; nur Doku.
|
||||
|
||||
### 2026-05-26 - USV-Risiko bewusst akzeptiert
|
||||
|
||||
- Operator-Entscheidung: aktuell wird keine USV angeschafft.
|
||||
- Der Befund bleibt technisch unveraendert: keine funktionierende USV-Abschaltung nachgewiesen. Power-Loss-Risiko fuer Docker-/DB-State und laufende Writes wird bewusst akzeptiert und bei spaeteren Reviews neu bewertet.
|
||||
|
||||
### 2026-05-26 - Borg-Passphrase offline gesichert
|
||||
|
||||
- Operator bestaetigt: Die Borg-Passphrase ist offline/off-system gesichert und kann ohne Host oder Vaultwarden wiederhergestellt werden.
|
||||
- Doku aktualisiert nur den Sicherungsstatus; Secret-Wert und Ablageort bleiben bewusst ausserhalb des Repos.
|
||||
|
||||
### 2026-05-26 - Gitea-Bundle-Mechanik definiert
|
||||
|
||||
- `ops/borg-ui/scripts/gitea-bundle-mirror.sh` ergaenzt: erstellt verifizierte `git bundle`-Artefakte fuer alle bare Gitea-Repositories, schreibt Checksums und einen Markdown-Report.
|
||||
- Zielpfad ist `/mnt/user/backups/git-bundles/gitea`; dieser Pfad muss in den Borg/off-site Scope aufgenommen und hostseitig geplant werden.
|
||||
- `docs/SERVICES_RECOVERY.md` und `docs/RESTORE_MATRIX.md` dokumentieren Bundles jetzt als zweite Repo-Bootstrap-Schicht neben dem GitHub-Mirror.
|
||||
- Host-Erstlauf nach Skript-Fix erfolgreich: 4 Bundles erzeugt (`homelab-infra`, `homelab`, `homepage`, `smart-home-kalli`), Checksums OK, `homelab-infra.bundle` in Restore-Lab geklont und `git fsck` sauber. Offen bleibt die dauerhafte Schedule-Einbindung.
|
||||
|
||||
### 2026-05-26 - Audit-Baseline-Tag gesetzt
|
||||
|
||||
- Der Stand nach Hardware-/Capacity-Baseline, Policy-Triage und Recovery-Doku wurde als `audit-2026-05-25-baseline` markiert und nach Gitea gepusht.
|
||||
@@ -64,7 +397,7 @@ Dieses Dokument ist nur noch ein historischer Verlauf. Der aktuelle operative Ab
|
||||
|
||||
### 2026-05-26 - Audit-Umsetzung vorbereitet
|
||||
|
||||
- Aus `docs/AUDIT_2026-05-25.md` wurde `docs/AUDIT_2026-05-25_TODO.md` als operative Arbeitsliste abgeleitet. Authelia-2FA/OIDC bleibt bewusst geparkt und wird erst nach finaler Policy-Entscheidung umgesetzt.
|
||||
- Aus `docs/archive/2026-05/AUDIT_2026-05-25.md` wurde `docs/AUDIT_2026-05-25_TODO.md` als operative Arbeitsliste abgeleitet. Authelia-2FA/OIDC bleibt bewusst geparkt und wird erst nach finaler Policy-Entscheidung umgesetzt.
|
||||
- Neue Inventar- und Betriebsdokumente angelegt: `docs/HARDWARE_INVENTORY.md`, `docs/NETWORK_INVENTORY.md`, `docs/EXTERNAL_DEPENDENCIES.md`, `docs/CAPACITY_AND_LIFECYCLE.md` und `docs/FAMILY_ONBOARDING.md`.
|
||||
- `docs/SERVICES_RECOVERY.md` beschreibt initial die recovery-kritischen `/mnt/user/services`-Pfade, Gitea-Repo-Mirror-Optionen, Komodo-Bootstrap und Secret-Recovery-Reihenfolge.
|
||||
- Policy-Check lokal erneut ausgefuehrt: die alten SEC001-Warnings fuer `ddns-updater` und `scrutiny` sind nicht mehr aktuell; verbleibende Warnings betreffen Host-Netz-/User-/Image-Tag-Themen und Altstaende.
|
||||
@@ -104,7 +437,7 @@ Dieses Dokument ist nur noch ein historischer Verlauf. Der aktuelle operative Ab
|
||||
|
||||
### 2026-05-25 - Audit-Final nachgemessen
|
||||
|
||||
- Audit-Restliste erneut live geprueft: runtime-relevanter Stack-Inhalt fuer `gitea`, `borg-ui` und `monitoring` seit `66ee10c` unveraendert; abschliessende Audit-Doku-Commits liegen in Gitea; Monitoring inklusive Loki `/ready` gruen; Borg-Job und 15 kanonische Dump-Artefakte frisch; `docs/AUDIT_2026-05-23_FINAL.md` auf den Live-Stand aktualisiert.
|
||||
- Audit-Restliste erneut live geprueft: runtime-relevanter Stack-Inhalt fuer `gitea`, `borg-ui` und `monitoring` seit `66ee10c` unveraendert; abschliessende Audit-Doku-Commits liegen in Gitea; Monitoring inklusive Loki `/ready` gruen; Borg-Job und 15 kanonische Dump-Artefakte frisch; `docs/archive/2026-05/AUDIT_2026-05-23_FINAL.md` auf den Live-Stand aktualisiert.
|
||||
|
||||
### 2026-05-25 - Disk1 Phase 2 abgeschlossen
|
||||
|
||||
@@ -117,7 +450,7 @@ Dieses Dokument ist nur noch ein historischer Verlauf. Der aktuelle operative Ab
|
||||
### 2026-05-23 - Audit-Endstufe verifiziert
|
||||
|
||||
- Lokalen Hardening-Commit `cd650b1` nach Gitea gepusht; Komodo-Workspaces fuer `gitea`, `borg-ui` und `monitoring` stehen auf `cd650b1`.
|
||||
- Live-Audit in `docs/AUDIT_2026-05-23_LIVE.md` dokumentiert: Gitea-Registration geschlossen, Borg-Dumps frisch, Monitoring-Stack aktiv, alte Grafana/Loki-Altcontainer nicht mehr vorhanden.
|
||||
- Live-Audit in `docs/archive/2026-05/AUDIT_2026-05-23_LIVE.md` dokumentiert: Gitea-Registration geschlossen, Borg-Dumps frisch, Monitoring-Stack aktiv, alte Grafana/Loki-Altcontainer nicht mehr vorhanden.
|
||||
- Jellyfin und Plex in Architektur, Service-Katalog und Repo-Map nachgetragen; Plex ist jetzt als Repo-Compose-Stack mit dokumentierter Host-Netz-Ausnahme gefuehrt.
|
||||
- Repo-Hygiene abgeschlossen: `.serena/` ignoriert, leere Verzeichnisse entfernt, Windows-Reinstall-Helfer unter `ops/windows-reinstall/` bewusst versioniert.
|
||||
|
||||
|
||||
+73
-21
@@ -1,7 +1,7 @@
|
||||
# Network Inventory - KalliLab CORE
|
||||
|
||||
Status: Initialer Host-Audit erfasst, Router-/VLAN-Details offen.
|
||||
Letzte Pruefung: 2026-05-26
|
||||
Status: Host-Audit erfasst; Router-Baseline und Portfreigaben-UI bereinigt; VLAN/IPv6-Details offen.
|
||||
Letzte Pruefung: 2026-05-28
|
||||
|
||||
## Zweck
|
||||
|
||||
@@ -11,14 +11,26 @@ Dieses Dokument beschreibt Router, DNS, Tailscale, Portfreigaben und Netztrennun
|
||||
|
||||
| Feld | Wert |
|
||||
|---|---|
|
||||
| Anschluss / Provider | TBD |
|
||||
| Router-Modell | TBD |
|
||||
| Firmware | TBD |
|
||||
| Anschluss / Provider | DSL, Telekom |
|
||||
| Bandbreite (FRITZ!Box-UI) | ca. 87,3 Mbit/s Download, ca. 36 Mbit/s Upload |
|
||||
| Router-Modell | FRITZ!Box 7590 |
|
||||
| Firmware | FRITZ!OS 8.21 (Update gemeldet, nicht eingespielt) |
|
||||
| Router-IP | 192.168.178.1 |
|
||||
| DHCP-Server | vermutlich Router, zu pruefen |
|
||||
| DHCP-Server | FRITZ!Box (Standardannahme, Override durch Operator nicht dokumentiert) |
|
||||
| Lokales Subnetz | 192.168.178.0/24 |
|
||||
| IPv6 aktiv | TBD |
|
||||
| DynDNS / DDNS | Cloudflare via `ddns-updater`, Details TBD |
|
||||
| IPv6 aktiv | TBD (FRITZ!Box-UI separat pruefen) |
|
||||
| DynDNS / DDNS | Cloudflare via `ddns-updater` (kein FRITZ!Box-DynDNS in Nutzung) |
|
||||
| Heimnetz-Geraete (FRITZ!Box-UI) | 36 aktive Geraete |
|
||||
| LAN-Ports belegt | LAN 1-4 verbunden |
|
||||
| Telefonie / DECT | aktiv |
|
||||
| USB an FRITZ!Box | nicht verbunden |
|
||||
| Ausfallschutz (FRITZ!Box) | nicht eingerichtet (Mobilfunk-Stick-Failover nicht aktiv) |
|
||||
|
||||
### Beobachtungen
|
||||
|
||||
- Telekom-DSL ist Single-WAN; ohne Ausfallschutz ist Internet-Ausfall = kein DDNS-Update, keine ACME-Erneuerung, keine externen Push-Quellen.
|
||||
- Upload 36 Mbit/s ist die effektive Obergrenze fuer Off-site-Backup-Geschwindigkeit nach Hetzner und fuer Plex-Remote-Streaming.
|
||||
- FRITZ!OS 8.21 hat ein angezeigtes Update; Einspielung ist Betreiber-Aufgabe und nicht Teil des Repos.
|
||||
|
||||
## DNS
|
||||
|
||||
@@ -50,14 +62,50 @@ tailscale ip -6
|
||||
|
||||
## Portfreigaben und Exposure
|
||||
|
||||
### FRITZ!Box (WAN -> Host)
|
||||
|
||||
Aktiver Soll-Stand nach Operator-Bereinigung 2026-05-28:
|
||||
|
||||
| Aktive Freigabe | Ziel | Zweck | Bemerkung |
|
||||
|---|---|---|---|
|
||||
| `443/tcp` -> `192.168.178.58:443` | Traefik HTTPS | einziger Public-HTTPS-Einstieg, Wildcard-Cert via Cloudflare-DNS-Challenge | bleibt |
|
||||
|
||||
Bewusst **nicht** freigegeben:
|
||||
|
||||
| Port | Begruendung |
|
||||
|---|---|
|
||||
| `80/tcp` | Cloudflare-DNS-Challenge ersetzt HTTP-01; Traefik macht HTTP->HTTPS-Redirect nur LAN-seitig; WAN-`80` waere zusaetzliche Angriffsflaeche ohne Funktionsnutzen. **2026-05-28 in FRITZ!Box-UI entfernt**, Validierung: Mobilfunk-Test ergibt Timeout auf `http://vault.kaleschke.info`, `https://...` weiter erreichbar. |
|
||||
| `222/tcp` (Gitea SSH) | bewusst Tailscale-only: Operator-Pfad ist Tailscale, GitHub-Mirror deckt DR-Bootstrap ab, Gitea-Bundles sind off-host. Externe SSH-Brute-Force-Vektoren vermeiden. |
|
||||
|
||||
### UPnP / Selbstständige Portfreigaben
|
||||
|
||||
| Geraet | UPnP-Selbstfreigabe-Recht | Begruendung |
|
||||
|---|---|---|
|
||||
| `Kallilabcore` (192.168.178.58) | nicht erlaubt | Repo-managed; alle benoetigten Public-Ports sind explizite Freigaben |
|
||||
| `PC-192-168-178-71` / VONETS-Adapter (192.168.178.71, MAC 00:17:13:2F:61:96) | **2026-05-28 deaktiviert** | wahrscheinlich VONETS-WiFi-Bridge fuer SolarEdge-Wechselrichter; SolarEdge-Cloud-Sync ist ausschliesslich outbound, eingehende Ports sind nicht erforderlich |
|
||||
|
||||
Sollten neue Geraete UPnP-Selbstfreigaben anfordern, wird das in `docs/MIGRATION_LOG.md` und hier als bewusste Ausnahme dokumentiert oder pro Geraet wieder deaktiviert.
|
||||
|
||||
Historischer UI-Befund vor Bereinigung vom 2026-05-27 (`Internet -> Freigaben -> Kallilabcore`):
|
||||
|
||||
| Beobachtung | Bewertung |
|
||||
|---|---|
|
||||
| `HTTP-Server`, TCP, extern `80/tcp` auf `192.168.178.58` | war Abweichung; **2026-05-28 entfernt** |
|
||||
| `HTTPS-Server`, TCP, extern `443/tcp` auf `192.168.178.58` | entspricht Repo-Soll |
|
||||
| Keine `222/tcp`-Freigabe sichtbar | entspricht seit 2026-05-28 dem Soll: Gitea-SSH bleibt Tailscale-only |
|
||||
| Kallilabcore: keine selbststaendige Portfreigabe, kein IPv4-/IPv6-Exposed-Host sichtbar | entspricht Sicherheitsziel |
|
||||
| `PC-192-168-178-71`: selbststaendige Portfreigabe erlaubt, `0 aktiv` | **2026-05-28 deaktiviert** |
|
||||
|
||||
### Host (lokal beobachtbar)
|
||||
|
||||
| Port | Ziel | Zweck | Bewertung |
|
||||
|---:|---|---|---|
|
||||
| 80/tcp | Traefik | HTTP->HTTPS / ACME | erwartet |
|
||||
| 443/tcp | Traefik | HTTPS | erwartet |
|
||||
| 222/tcp | Gitea SSH | Git SSH | dokumentierte Ausnahme |
|
||||
| 53/tcp+udp | AdGuard | DNS | dokumentierte Ausnahme |
|
||||
| 8082/tcp | AdGuard Admin | Admin UI | Repo-Soll: nur `100.80.98.33:8082`, DNS-Port 53 unveraendert |
|
||||
| 8181/tcp | InfluxDB 3 Core | LAN Writer fuer Home Assistant | LAN-only, Bind-IP pruefen |
|
||||
| 80/tcp | Traefik | HTTP->HTTPS / ACME | nur LAN, keine WAN-Freigabe noetig |
|
||||
| 443/tcp | Traefik | HTTPS | WAN-Freigabe in FRITZ!Box erwartet |
|
||||
| 222/tcp | Gitea SSH | Git SSH | nur LAN/Tailscale; keine WAN-Freigabe |
|
||||
| 53/tcp+udp | AdGuard | DNS | LAN-only, dokumentierte Ausnahme |
|
||||
| 8082/tcp | AdGuard Admin | Admin UI | Bind nur `100.80.98.33:8082` (Tailscale), nicht im LAN exponiert |
|
||||
| 8181/tcp | InfluxDB 3 Core | Home Assistant / Ecowitt Writer | 2026-05-31 effektiv nur `127.0.0.1:8181`, nicht LAN-exponiert |
|
||||
|
||||
Pruefkommando:
|
||||
|
||||
@@ -70,11 +118,12 @@ docker ps --format "{{.Names}}: {{.Ports}}" | sort
|
||||
|
||||
| Netz | Status | Bemerkung |
|
||||
|---|---|---|
|
||||
| LAN | 192.168.178.0/24 | Hauptnetz, Host `192.168.178.58` |
|
||||
| Gast-WLAN | TBD | Zugriff auf AdGuard Admin muss ausgeschlossen sein |
|
||||
| IoT-Netz | TBD | Zugriff auf AdGuard Admin muss ausgeschlossen sein |
|
||||
| LAN | 192.168.178.0/24 | Hauptnetz, Host `192.168.178.58`, FRITZ!Box meldet 36 aktive Geraete |
|
||||
| WLAN 2,4 / 5 GHz | aktiv, SSID `Fritzi` | Standard-WLAN, im LAN-Adressbereich, kein eigener Adressraum |
|
||||
| Gast-WLAN | **inaktiv** (FRITZ!Box-UI) | Solange inaktiv: kein Gast-Pfad zu LAN-Diensten; AdGuard-Admin-Trennung primaer ueber Tailscale-Bind statt Netzsegmentierung |
|
||||
| IoT-Netz | nicht existent | Keine VLAN-Trennung dokumentiert |
|
||||
| Tailscale | aktiv | Operator-Zugang, Host-IP `100.80.98.33` |
|
||||
| VLANs | TBD | Router-/Switch-Faehigkeit pruefen |
|
||||
| VLANs | nicht in Nutzung | FRITZ!Box 7590 kann VLAN-Tagging an einzelnen LAN-Ports; aktuell nicht konfiguriert |
|
||||
|
||||
## Docker-Netze
|
||||
|
||||
@@ -101,6 +150,9 @@ docker network inspect backend_net | jq '.[0].Internal'
|
||||
| Thema | Status | Naechster Schritt |
|
||||
|---|---|---|
|
||||
| AdGuard Admin nur via Tailscale | live validiert 2026-05-26 | Compose bindet Admin-Port auf `100.80.98.33:8082`; DNS auf Port 53 funktioniert, LAN-Zugriff auf `192.168.178.58:8082` schlaegt fehl |
|
||||
| Gast-/IoT-Zugriff auf Admin-Ports | offen | Router-Regeln pruefen |
|
||||
| IPv6 Exposure | offen | Router und Traefik/Cloudflare pruefen |
|
||||
| Home Assistant InfluxDB Bind | offen | Effektive Listener-Adresse pruefen |
|
||||
| FRITZ!Box-Portfreigaben mit Repo-Soll abgleichen | **erledigt 2026-05-28** | Bereinigt: `80/tcp` entfernt (Cloudflare-DNS-Challenge ersetzt HTTP-01; Mobilfunk-Test bestaetigt Timeout auf `http://`, `https://` weiter ok). `222/tcp` bleibt bewusst nicht eingerichtet (Tailscale-only-Linie). UPnP-Selbstfreigabe-Recht fuer VONETS-Bridge (SolarEdge) deaktiviert. Aktiver Soll-Stand: ausschliesslich `443/tcp -> 192.168.178.58`. |
|
||||
| FRITZ!OS 8.21 Update | gemeldet | Operator-Aufgabe; vor Update kurzes Service-Fenster planen, weil Reboot WAN/Tailscale-Aufbau unterbricht |
|
||||
| Gast-/IoT-Zugriff auf Admin-Ports | aktuell entschaerft | Gast-WLAN ist inaktiv; bei Aktivierung muessen `192.168.178.58:8082`, `192.168.178.58:8181` und ggf. weitere LAN-Ports per FRITZ!Box-Kindersicherung/Netzwerk-Filter abgesichert werden |
|
||||
| IPv6 Exposure | offen | Router und Traefik/Cloudflare pruefen; Telekom-DSL liefert in der Regel IPv6, FRITZ!Box-Standard-Verhalten klaeren |
|
||||
| WAN-Ausfallschutz | bewusst nicht eingerichtet | Mobilfunk-Stick-Failover an FRITZ!Box ist nicht aktiv; Internet-Ausfall = ACME/DDNS pausieren, lokale Apps laufen weiter |
|
||||
| Home Assistant InfluxDB Bind | validiert 2026-05-31 | `docker-proxy` bindet `127.0.0.1:8181`; keine LAN-Exposure. Wenn Home Assistant nicht lokal auf dem Host schreibt, braucht das eine bewusste Bind-Aenderung. |
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
# Offsite-Backup-Entscheidung - KalliLab CORE
|
||||
|
||||
Status: **Operator-Entscheidung 2026-05-28: bewusst KEIN zweites Off-site.** Doku bleibt als Begruendungs-Anker und fuer zukuenftige Reviews.
|
||||
Audit-Bezug: `docs/archive/2026-05/AUDIT_2026-05-25.md` Finding **F-03** und `docs/AUDIT_2026-05-25_TODO.md` Sprint 7.
|
||||
|
||||
## Beschluss 2026-05-28
|
||||
|
||||
Aktuelle Backup-Topologie:
|
||||
|
||||
| Kopie | Wo | Medium | Standort |
|
||||
|---|---|---|---|
|
||||
| 1 | Live-Daten Unraid (Cache + Disk1) | NVMe + HDD | Heim |
|
||||
| 2 | Borg-Repo lokal `/mnt/user/backups/borg/` | HDD (Disk1) | Heim |
|
||||
| 3 | Borg-Repo Hetzner Storagebox | Hetzner | Off-site (DE) |
|
||||
| 4 | H:/ Nearline-Pull am Windows-PC | externe HDD | Heim |
|
||||
|
||||
**3-2-1-Regel ist erfuellt:** 4 Kopien, 3 Medien, 1 Off-site (Hetzner).
|
||||
|
||||
Ein zweites Off-site wuerde aktuell nur das Szenario "Hetzner-Account verloren" zusaetzlich abdecken. Operator-Bewertung: Wahrscheinlichkeit niedrig, Aufwand und laufende Kosten unverhaeltnismaessig zur Risikoreduktion fuer ein privates Familien-Homelab.
|
||||
|
||||
Statt zweitem Off-site werden Hetzner-Account- und Repo-Haertungen als Folge-TODOs gefuehrt:
|
||||
|
||||
1. Hetzner-Account: starkes, einzigartiges Passwort in Vaultwarden + Backup-Zahlungsweg + Login-Benachrichtigungen per E-Mail. **Bewusst keine 2FA** (Operator-Entscheidung 2026-05-28 analog zur USV-Risiko-Akzeptanz).
|
||||
2. Borg `--append-only`-Mode pruefen. Setup erfolgt server-seitig in Hetzner `~/.ssh/authorized_keys` mit `command="borg serve --append-only"` fuer den Backup-Key.
|
||||
3. H:/ Pull ist seit 2026-05-28 als Windows Scheduled Task aktiv (Anker `docs/H_DRIVE_NEARLINE_PULL.md`) und kein offener Punkt mehr.
|
||||
|
||||
## Review-Trigger
|
||||
|
||||
Diese Entscheidung wird neu bewertet, wenn:
|
||||
|
||||
- Hetzner-Account-Probleme tatsaechlich auftreten (Payment-Issue, Login-Sperre)
|
||||
- Hetzner als Anbieter strukturelle Probleme zeigt (Insolvenz-Geruechte, AGB-Aenderungen)
|
||||
- im Homelab Daten mit deutlich hoeherem Wiederbeschaffungs-Aufwand dazukommen
|
||||
- Operator-Praeferenzen sich aendern
|
||||
|
||||
## Historische Entscheidungsgrundlage
|
||||
|
||||
Dieser Abschnitt bleibt als Begruendungs-Anker erhalten. Er ist **keine aktuelle Beschaffungsliste**. Die Entscheidung vom 2026-05-28 bleibt: kein zweites Off-site-Ziel, solange keiner der Review-Trigger eintritt.
|
||||
|
||||
`H:/` am Windows-PC bleibt **kein** Offsite-Ersatz (siehe `docs/CAPACITY_AND_LIFECYCLE.md`). H:/ ist Nearline-Kopie am gleichen Standort.
|
||||
|
||||
### Option A - rsync.net Borg-Plan
|
||||
|
||||
- anderer Anbieter als Hetzner
|
||||
- Borg-kompatibel per SSH
|
||||
- ZFS-Snapshot-Schutz als zusaetzlicher Schutz vor Repo-Loeschung
|
||||
- grob teurer als Hetzner, aber gute Anbieter-Trennung
|
||||
|
||||
### Option B - BorgBase EU2
|
||||
|
||||
- Borg-kompatibel und einfach
|
||||
- andere Region, aber nicht vollstaendig getrenntes Anbieter-/Account-Risiko
|
||||
- deshalb nicht als bevorzugtes zweites Ziel bewertet
|
||||
|
||||
### Option C - Rotierende Cold-Wechselplatte ausser Haus
|
||||
|
||||
- echte Air-Gap-/Offline-Kopie
|
||||
- keine Provider-Abhaengigkeit
|
||||
- manuelle Disziplin noetig
|
||||
- nicht so taggenau wie Cloud-Borg
|
||||
|
||||
## Reaktivierungsplan bei Review-Trigger
|
||||
|
||||
1. Review-Trigger konkret benennen und in `docs/MIGRATION_LOG.md` dokumentieren.
|
||||
2. Operator-Entscheidung Option A vs. C neu bestaetigen.
|
||||
3. Falls Option A: Provider anlegen, Borg-Repo initialisieren, Borg-UI als zweites Repo ergaenzen, Secrets-/External-/Restore-Doku nachziehen.
|
||||
4. Falls Option C: zwei Platten beschaffen, Borg-Repos initialisieren, Rotationsplan in `docs/STORAGE_LAYOUT.md` ergaenzen.
|
||||
|
||||
## Offene Punkte
|
||||
|
||||
| Status | Punkt | Naechster Schritt |
|
||||
|---|---|---|
|
||||
| offen | Hetzner-Account-Hygiene | Starkes einzigartiges Passwort, Backup-Zahlungsweg und Login-Benachrichtigung extern bestaetigen |
|
||||
| offen | Borg `--append-only` fuer Hetzner pruefen | Server-seitige Hetzner-SSH-Konfiguration vorbereiten und Rollback-Pfad dokumentieren |
|
||||
| erledigt 2026-05-28 | H:/ Nearline-Pull aktivieren | Windows Scheduled Task `KalliLab H Drive Nearline Pull` laeuft taeglich 05:30 |
|
||||
| bewusst nicht umgesetzt | zweites Off-site-Ziel | Erst bei Review-Trigger neu entscheiden |
|
||||
@@ -0,0 +1,122 @@
|
||||
# Post-Migration Burn-in Check - 2026-05-31
|
||||
|
||||
Stand: 2026-05-31 21:45 MESZ
|
||||
|
||||
## Ergebnis
|
||||
|
||||
Der Nachlauf nach den Stateful-Migrationen ist gruen. Es gibt keine offenen Renovate-PRs, keine `unhealthy` Container und die aktuellen Dump-Artefakte wurden nach den Migrationen neu erzeugt und auf Lesbarkeit geprueft.
|
||||
|
||||
## Renovate
|
||||
|
||||
- Manueller Lauf: `2026-05-31T19-39-01Z`
|
||||
- Ergebnis: `rc=0`
|
||||
- Gitea: keine offenen PRs
|
||||
- Renovate entfernte die verwaisten Branches:
|
||||
- `renovate/major-major-updates`
|
||||
- `renovate/postgres-18.x`
|
||||
- `renovate/redis-8.x`
|
||||
|
||||
## Live Burn-in
|
||||
|
||||
- `docker ps --filter health=unhealthy`: keine Treffer
|
||||
- Relevante Laufzeitstaende:
|
||||
- `postgresql17`: `postgres:18.4`
|
||||
- `mealie-postgres`: `postgres:18.4`
|
||||
- `nextcloud-postgres`: `postgres:18.4`
|
||||
- `immich_postgres`: `ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0`
|
||||
- `Redis`, `nextcloud-redis`, `immich_redis`: `redis:8.8.0-alpine`
|
||||
- `monitoring-grafana`: `grafana/grafana:13.0.1`
|
||||
|
||||
HTTP-Smoke vom Host:
|
||||
|
||||
| Dienst | Status |
|
||||
|---|---:|
|
||||
| `https://monitoring.kaleschke.info` | 302 |
|
||||
| `https://immich.kaleschke.info` | 200 |
|
||||
| `https://cloud.kaleschke.info` | 302 |
|
||||
| `https://paperless.kaleschke.info` | 302 |
|
||||
| `https://mealie.kaleschke.info` | 200 |
|
||||
| `https://mail.kaleschke.info` | 302 |
|
||||
|
||||
Log-Nachlauf:
|
||||
|
||||
- `mail-archiver`: fruehere transienten `57P01`-Fehler stammen vom DB-Restart; spaeterer Sync meldet `New: 0, Failed: 0, Deleted: 0`.
|
||||
- `monitoring-grafana`: nach Härtung keine neuen `level=error`, `permission denied`, `fatal` oder `panic` Treffer.
|
||||
|
||||
## Backup- und Dump-Frische
|
||||
|
||||
`ops/borg-ui/scripts/pre-backup-dumps.sh` wurde nach den Migrationen erneut ausgefuehrt. Dabei wurde ein Drift behoben: `grafana.sqlite` wird jetzt aus dem aktiven Docker-Volume `monitoring_grafana_data` gelesen, nicht mehr aus dem historischen Pfad `/mnt/user/appdata/grafana`.
|
||||
|
||||
Aktuelle relevante Dump-Zeiten:
|
||||
|
||||
| Artefakt | Zeit |
|
||||
|---|---|
|
||||
| `postgresql17-globals.sql` | 2026-05-31 21:41 |
|
||||
| `mealie.dump` | 2026-05-31 21:42 |
|
||||
| `nextcloud.dump` | 2026-05-31 21:42 |
|
||||
| `immich.dump` | 2026-05-31 21:42 |
|
||||
| `postgresql17-authelia.dump` | 2026-05-31 21:42 |
|
||||
| `postgresql17-mailarchiver.dump` | 2026-05-31 21:42 |
|
||||
| `postgresql17-paperless.dump` | 2026-05-31 21:42 |
|
||||
| `grafana.sqlite` | 2026-05-31 21:42 |
|
||||
| `komodo-mongo.archive.gz` | 2026-05-31 21:42 |
|
||||
|
||||
Lesbarkeit:
|
||||
|
||||
- `pg_restore -l`: ok fuer Mealie, Nextcloud, Immich, Authelia, Mailarchiver, Paperless
|
||||
- `sqlite3 PRAGMA quick_check`: ok fuer Grafana, Gitea, Vaultwarden, Speedtest, Borg UI
|
||||
- `gzip -t`: ok fuer `komodo-mongo.archive.gz`
|
||||
|
||||
Borg-UI letzter vollstaendiger Backup-Job:
|
||||
|
||||
- `#41 completed`, Start `2026-05-31 02:30:13`, Ende `2026-05-31 02:31:26`
|
||||
- Hinweis: Dieser Borg-Lauf war vor den Tagesmigrationen. Die Dumps sind jetzt frisch; der naechste regulaere Borg-Lauf muss sie off-site aufnehmen.
|
||||
|
||||
## Restore-Drill-Vorbereitung
|
||||
|
||||
- `run-restore-checks.sh freshness`: Critical `0`, Warnings `0`
|
||||
- `immich --what-if`: ok, nutzt VectorChord/pgvector-Test-Postgres und Redis 8
|
||||
- `paperless --what-if`: ok nach Fix der fehlenden Execute-Bits
|
||||
- Paperless-Restore-Drill wurde am 2026-05-31 erfolgreich nachgezogen: Borg-Archiv `Tägliche-Sicherung-2026-05-31T04:30:13.181`, isolierter PostgreSQL-18-/Redis-8-Testpfad, HTTP `200`, Login-Marker ok, `32` Dokumente im Test-DB-Check. Report: `/mnt/user/backups/restore-reports/paperless-2026-05-31.md`.
|
||||
|
||||
Nebenbefund behoben:
|
||||
|
||||
- Mehrere Restore-Test-Skripte waren im Git nicht ausfuehrbar. Dateimodus auf `100755` korrigiert.
|
||||
|
||||
## Monitoring / Grafana 13
|
||||
|
||||
- `/api/health`: `database=ok`, `version=13.0.1`
|
||||
- SQLite/Unified-Storage-Check:
|
||||
- `data_source=3`
|
||||
- `resource` enthaelt `4` Dashboards und `1` Folder
|
||||
- `unifiedstorage_migration_log=3`
|
||||
- Grafana-13-Fixes:
|
||||
- `GF_PLUGINS_PREINSTALL_DISABLED=true`
|
||||
- leere Provisioning-Verzeichnisse `alerting/` und `plugins/` versioniert
|
||||
- `user: "0"` gesetzt, damit hostseitige `600 root` Secret-Dateien lesbar bleiben
|
||||
|
||||
## Alt-Volumes fuer spaetere Freigabe
|
||||
|
||||
Nicht vor Burn-in-Freigabe loeschen.
|
||||
|
||||
| Pfad | Zweck | Groesse |
|
||||
|---|---|---:|
|
||||
| `/mnt/user/appdata/postgresql17` | Shared PostgreSQL-17-Rollback | 1.8G |
|
||||
| `/mnt/user/appdata/mealie/postgres` | Mealie PostgreSQL-17-Rollback | 70M |
|
||||
| `/mnt/user/appdata/nextcloud/postgres` | Nextcloud PostgreSQL-17-Rollback | 71M |
|
||||
| `/mnt/user/appdata/immich_postgres` | Immich pgvecto.rs-Rollback | 460M |
|
||||
|
||||
Aktive Vergleichspfade:
|
||||
|
||||
| Pfad | Zweck | Groesse |
|
||||
|---|---|---:|
|
||||
| `/mnt/user/appdata/postgresql18` | Shared PostgreSQL 18 aktiv | 2.2G |
|
||||
| `/mnt/user/appdata/mealie/postgres18` | Mealie PostgreSQL 18 aktiv | 70M |
|
||||
| `/mnt/user/appdata/nextcloud/postgres18` | Nextcloud PostgreSQL 18 aktiv | 72M |
|
||||
| `/mnt/user/appdata/immich_postgres_vectorchord` | Immich VectorChord aktiv | 633M |
|
||||
|
||||
## Offene bewusste Punkte
|
||||
|
||||
- Alt-Volumes bleiben bis zur Erinnerung am 2026-06-02 gesperrt.
|
||||
- Der naechste regulaere Borg-Lauf soll nachziehen; danach kann die Alt-Volume-Freigabe fundierter entschieden werden.
|
||||
- Paperless-Restore-Drill ist erledigt; naechster sinnvoller Drill ist Gitea- oder Vaultwarden-Wiederholung gemaess Restore-Drill-Routine.
|
||||
@@ -0,0 +1,69 @@
|
||||
# Documentation Index
|
||||
|
||||
Stand: 2026-05-31
|
||||
|
||||
Diese Datei trennt aktive Betriebsdokumentation von historischen Snapshots. Neue operative Dokumente duerfen nur in `docs/` liegen, wenn sie heute als Einstieg, Runbook, Inventar oder offene Arbeitsliste gebraucht werden. Erledigte Audits, Chat-Handoffs, Prompt-Dateien und abgeschlossene Plaene gehen nach `docs/archive/YYYY-MM/`.
|
||||
|
||||
## Pflicht-Einstieg
|
||||
|
||||
| Datei | Zweck |
|
||||
|---|---|
|
||||
| `../README.md` | kurzer Repo-Einstieg |
|
||||
| `../HOMELAB_ARCHITECTURE_MASTER_V2.md` | Architektur-Quelle fuer Netz, Zugriff und Ausnahmen |
|
||||
| `WORKFLOW.md` | verbindlicher GitOps-/No-Drift-Ablauf |
|
||||
| `REPO_MAP.md` | technische Landkarte des Repositories |
|
||||
| `SERVICE_CATALOG.md` | produktiver Service-Katalog |
|
||||
|
||||
## Betrieb und Recovery
|
||||
|
||||
| Datei | Zweck |
|
||||
|---|---|
|
||||
| `DISASTER_RECOVERY.md` | Wiederanlauf nach Host-/Systemausfall |
|
||||
| `RESTORE_MATRIX.md` | Restore-Quellen, Dumps, Secrets und Smoke-Tests je Dienst |
|
||||
| `RESTORE_HANDBOOK.md` | praktische Restore-Anleitung |
|
||||
| `RESTORE_DRILL_ROUTINE.md` | regelmaessige Restore-Drills |
|
||||
| `SERVICES_RECOVERY.md` | Gitea-/Komodo-/Services-Bootstrap |
|
||||
| `ROLLBACK.md` | Rueckweg bei GitOps-/Deploy-Fehlern |
|
||||
| `GITOPS_DRIFT_RUNBOOK.md` | Pflichtmatrix bei Drift zwischen Git, Komodo, Docker und Host |
|
||||
|
||||
## Inventare und Policies
|
||||
|
||||
| Datei | Zweck |
|
||||
|---|---|
|
||||
| `STORAGE_LAYOUT.md` | verbindliche Storage-/Share-/Pfad-Regeln |
|
||||
| `SECRETS_MAP.md` | Secret-Namen, Speicherorte und Einbindungsarten ohne Werte |
|
||||
| `HARDWARE_INVENTORY.md` | Host-, Disk-, SMART-, USV- und Power-Baseline |
|
||||
| `NETWORK_INVENTORY.md` | Router, DNS, Tailscale, Portfreigaben und Netzthemen |
|
||||
| `EXTERNAL_DEPENDENCIES.md` | Provider, Konten und externe Abhaengigkeiten |
|
||||
| `CAPACITY_AND_LIFECYCLE.md` | Kapazitaet, Wachstum und Upgrade-Trigger |
|
||||
|
||||
## Monitoring und Automatisierung
|
||||
|
||||
| Datei | Zweck |
|
||||
|---|---|
|
||||
| `ALERT_RULES.md` | Prometheus-/ntfy-Regeln und Handlungslogik |
|
||||
| `ALERTING_MAP.md` | ntfy Topics und Sender-Konvention |
|
||||
| `RENOVATE.md` | Self-hosted Renovate gegen Gitea |
|
||||
| `HOME_ASSISTANT_INFLUXDB_ECOWITT.md` | Home Assistant -> InfluxDB 3 -> Grafana |
|
||||
| `H_DRIVE_NEARLINE_PULL.md` | Windows-H:/ Nearline-Pull fuer kritische Restore-Artefakte |
|
||||
| `IMMICH_RESTORE_TEST.md` | Immich-Restore-Test-Overview |
|
||||
|
||||
## Nutzer- und Planungsdoku
|
||||
|
||||
| Datei | Zweck |
|
||||
|---|---|
|
||||
| `FAMILY_ONBOARDING.md` | familienverstaendliche Nutzungsdoku |
|
||||
| `FAMILY_VIEW_DASHBOARD.md` | Spezifikation fuer das Family-View-Dashboard |
|
||||
| `OFFSITE_BACKUP_OPTIONS.md` | dokumentierte Offsite-Entscheidung und Review-Trigger |
|
||||
| `AUDIT_2026-05-25_TODO.md` | verbleibende/parkende Aufgaben aus dem Audit-Zyklus |
|
||||
| `POST_MIGRATION_BURN_IN_2026-05-31.md` | aktueller Burn-in-Nachlauf nach den Stateful-Migrationen |
|
||||
| `AI_CONTEXT.md` | kompakter Kontext fuer KI-Agenten |
|
||||
| `MIGRATION_LOG.md` | historischer Verlauf; kein Primaer-Runbook |
|
||||
|
||||
## Archiv
|
||||
|
||||
| Pfad | Inhalt |
|
||||
|---|---|
|
||||
| `archive/2026-05/` | alte Audits, Chat-Handoffs, Codex-Prompts und erledigte Aktionsplaene aus Mai 2026 |
|
||||
|
||||
Windows-Neuaufsetzen-Dokumente liegen nicht mehr in `docs/`, sondern im fachlich passenden Ordner `../ops/windows-reinstall/docs/`.
|
||||
@@ -0,0 +1,147 @@
|
||||
# Renovate Bot - Self-hosted gegen Gitea
|
||||
|
||||
Status: **live seit 2026-05-29**; PAT, State-Verzeichnis und Cron sind auf dem Host aktiv.
|
||||
Audit-Bezug: `docs/archive/2026-05/AUDIT_2026-05-25.md` Finding **F-12**.
|
||||
|
||||
## Zweck
|
||||
|
||||
Wir pinnen Image-Versionen und Digests konsequent (siehe `HOMELAB_ARCHITECTURE_MASTER_V2.md` Sektion 13, "Reproduzierbare Deployments"). Das macht das Setup stabil, aber jede Image-Aktualisierung musste bisher manuell laufen. Renovate uebernimmt das in Zukunft: scant das Repo periodisch, oeffnet Pull-Requests in Gitea fuer Image-/Digest-Updates, gruppiert sinnvoll, laesst Operator entscheiden.
|
||||
|
||||
Bewusst kein Auto-Merge: jede PR braucht eine Operator-Sichtpruefung und einen Merge-Click. Komodo deployt danach automatisch ueber den Standard-Webhook-Pfad.
|
||||
|
||||
## Architektur
|
||||
|
||||
- **Image:** `renovate/renovate:41` (versioniert, kein latest)
|
||||
- **Lauf:** ein-shot pro Cron-Tick, danach beendet sich der Container; persistente State liegt in `/mnt/user/services/renovate/state/`
|
||||
- **Schedule:** alle 6 Stunden per Unraid User-Script `renovate-six-hourly` (`20 */6 * * *`)
|
||||
- **Plattform:** Gitea via `https://git.kaleschke.info/api/v1`
|
||||
- **Authentifizierung:** Gitea-PAT als Host-Secret-Datei
|
||||
- **Konfiguration:** `renovate.json` im Repo-Root
|
||||
|
||||
## Operator-Setup (historisch, einmalig)
|
||||
|
||||
### Schritt 1 - Service-Account in Gitea
|
||||
|
||||
1. Als Admin in Gitea einloggen.
|
||||
2. Neuen User anlegen:
|
||||
- Username: `renovate`
|
||||
- E-Mail: ein gueltiges Postfach (Renovate sendet keine Mails, aber Gitea braucht eine Adresse)
|
||||
- Passwort: zufaellig, in Vaultwarden speichern
|
||||
3. Diesem User Schreibrechte fuer das Repo geben, das Renovate scannen soll: Repo `homelab-infra` -> Einstellungen -> Mitarbeiter -> `renovate` mit Permission `Schreibrechte` hinzufuegen.
|
||||
|
||||
**Wichtig:** Den Collaborator immer ueber die Gitea-UI/API hinzufuegen, nicht ueber direkten SQL-Insert. Die UI/API loest einen Permissions-Cache-Refresh aus; ein DB-Insert tut das nicht und fuehrt dazu, dass Renovate spaeter "Repository does not permit pull or push" meldet, obwohl die DB den Write-Mode kennt (Befund am 2026-05-29).
|
||||
|
||||
### Schritt 2 - Access-Token erzeugen
|
||||
|
||||
1. Als `renovate`-User in Gitea einloggen.
|
||||
2. Profile -> Settings -> Applications -> Generate New Token.
|
||||
3. Token-Name: `renovate-bot`.
|
||||
4. Scopes:
|
||||
- `read:user`
|
||||
- `write:repository`
|
||||
- `write:issue` (Renovate setzt Labels und kann den Dependency Dashboard erstellen)
|
||||
5. Token kopieren (wird nur einmal angezeigt).
|
||||
|
||||
### Schritt 3 - Token als Host-Secret ablegen
|
||||
|
||||
Am Unraid-Host:
|
||||
|
||||
```bash
|
||||
TOKEN='hier-das-token-einfuegen'
|
||||
echo -n "$TOKEN" > /mnt/user/appdata/secrets/renovate_token.txt
|
||||
chmod 600 /mnt/user/appdata/secrets/renovate_token.txt
|
||||
chown root:root /mnt/user/appdata/secrets/renovate_token.txt
|
||||
```
|
||||
|
||||
Token-Wert nicht in dieses Repo, nicht in Logs, nicht in Issues.
|
||||
|
||||
### Schritt 4 - Erstlauf manuell
|
||||
|
||||
```bash
|
||||
bash /mnt/user/services/homelab-infra/ops/renovate/run-renovate.sh
|
||||
```
|
||||
|
||||
Erwartete Ausgabe: Renovate verbindet sich mit Gitea, scant Repos unter `Micha/*` und entweder
|
||||
|
||||
- erstellt Pull-Requests, falls Updates verfuegbar sind, **oder**
|
||||
- erstellt nur die "Renovate Dependency Dashboard"-Issue im Repo (Onboarding-PR ist via `onboarding: false` deaktiviert)
|
||||
|
||||
Log liegt unter `/mnt/user/services/renovate/logs/renovate-<timestamp>.log` und symlinkt auf `latest.log`.
|
||||
|
||||
### Schritt 5 - User-Script aktivieren
|
||||
|
||||
Unraid User Scripts:
|
||||
|
||||
```
|
||||
Name: renovate-six-hourly
|
||||
Description: Run Renovate against Gitea every 6 hours.
|
||||
Schedule: 20 */6 * * *
|
||||
Script: bash /mnt/user/services/homelab-infra/ops/renovate/run-renovate.sh
|
||||
```
|
||||
|
||||
20 Minuten nach jeder vollen Stunde, damit es nicht mit `gitea-bundle-mirror-6h` (Minute 10) kollidiert.
|
||||
|
||||
## Was Renovate macht und was nicht
|
||||
|
||||
| Verhalten | Renovate-Konfig | Wirkung |
|
||||
|---|---|---|
|
||||
| Major-Updates | `groupName: major-updates`, `automerge: false` | Eine gesammelte PR pro Lauf mit allen Major-Updates, manueller Merge |
|
||||
| Minor + Patch + Digest fuer Docker-Compose | `groupName: minor-and-patch-updates`, `automerge: false` | Eine gesammelte PR; Operator merged manuell |
|
||||
| Tier-1-Datenhalter (Postgres, Mongo, Redis, Immich-Postgres) | `groupName: null`, eigener Label | Einzelne PRs ohne Group, hoehere Sichtbarkeit |
|
||||
| Komodo-Major-Updates | `enabled: false` fuer matchPackageNames + matchUpdateTypes major | Komodo bleibt auf `:2`, wird nicht versehentlich auf `:3` migriert |
|
||||
| Lock-File-Maintenance | `lockFileMaintenance.enabled: false` | Renovate macht keine reinen Lock-File-Refreshs |
|
||||
| Schedule | `extends ["schedule:weekly"]` | Renovate-Engine prueft, aber PRs/Updates folgen Wochen-Profilen wo sinnvoll |
|
||||
| Dependency Dashboard | aktiv | Gitea-Issue, die alle ausstehenden Updates auflistet |
|
||||
| Onboarding-PR | `onboarding: false` | Keine `Configure Renovate`-Onboarding-PR; wir nutzen die Repo-`renovate.json` direkt |
|
||||
| Ignore-Pfade | `_archive`, `ops/grafana-influxdb`, `ops/loki` | Renovate scant alte/abgeloeste Stacks nicht |
|
||||
|
||||
## Aktueller Betriebsstand
|
||||
|
||||
Erstlauf 2026-05-29 erfolgreich: Renovate erzeugte das Dependency Dashboard und die ersten fuenf PRs. Diese wurden am 2026-05-31 manuell gemerged, deployed und danach in Gitea geschlossen. Die erledigten Branches wurden anschliessend remote geloescht:
|
||||
|
||||
- `renovate/mongo-7.0.32`
|
||||
- `renovate/postgres-17.9`
|
||||
- `renovate/minor-patch-updates`
|
||||
- `renovate/mongo-7.x`
|
||||
- `renovate/postgres-17.x`
|
||||
|
||||
Stand nach den 2026-05-31-Migrationen: PostgreSQL 18, Redis 8 und Immich-Postgres mit VectorChord sind produktiv ausgerollt und in `renovate.json` per `allowedVersions` auf die jeweiligen Major-/Image-Schienen begrenzt. Die Renovate-PRs #9 `renovate/postgres-18.x` und #10 `renovate/redis-8.x` wurden deshalb am 2026-05-31 geschlossen statt gemerged.
|
||||
|
||||
Grafana 13 wurde anschliessend manuell aus #7 `renovate/major-major-updates` uebernommen, vor dem Recreate mit `grafana.db`-/Plugin-Backup gesichert, auf `grafana/grafana:13.0.1` deployed und verifiziert (`/api/health` 13.0.1, 3 Datasources, 4 Dashboards in Unified Storage). #7 wurde danach geschlossen statt gemerged.
|
||||
|
||||
Komodo-Mongo laeuft bereits auf der erlaubten MongoDB-8.0-Schiene; ein offener Mongo-8-Renovate-PR ist aktuell nicht vorhanden.
|
||||
|
||||
## Erwartete erste PRs (historisch)
|
||||
|
||||
Beim Erstlauf wird Renovate vermutlich PRs fuer einige der digest-gepinnten Images oeffnen, weil diese Digests seit Wochen nicht erneuert wurden. Reihenfolge der Sichtpruefung:
|
||||
|
||||
1. **Stateful Tier-1 zuerst, einzeln**: Postgres, Redis, Mongo, Immich-Postgres - jeder eigener PR, einzeln pruefen und mergen. Smoke-Test nach Merge ueber Komodo-Webhook-Deploy beobachten.
|
||||
2. **Gruppe minor-and-patch-updates**: Alle anderen Docker-Compose-Images zusammen. Wenn der Diff vernuenftig aussieht, mergen.
|
||||
3. **Gruppe major-updates**: Erst nach Operator-Sichtpruefung pro Image, ggf. zurueckstellen oder manuell entscheiden.
|
||||
|
||||
## Notfall-Stop
|
||||
|
||||
Wenn Renovate aus irgendeinem Grund zu aggressiv wird oder ungewollte PRs oeffnet:
|
||||
|
||||
```bash
|
||||
# 1. User-Script disablen
|
||||
# Unraid UI: User Scripts -> renovate-six-hourly -> Schedule -> Disabled
|
||||
|
||||
# 2. Im Worst Case: Token sofort widerrufen
|
||||
# Gitea -> Login als renovate -> Settings -> Applications -> Token loeschen
|
||||
|
||||
# 3. Offene PRs schliessen ohne mergen
|
||||
```
|
||||
|
||||
## Was bewusst NICHT enthalten ist
|
||||
|
||||
- **Auto-Merge**: keine PR wird ohne Operator-Click ausgerollt. Auto-Merge waere bei einem GitOps-Setup mit live-Webhooks ein zu grosses Risiko.
|
||||
- **Renovate-UI**: kein Mend.io-Cloud-Account, kein zusaetzlicher Service; lokal genutzte CLI im Docker-Container.
|
||||
- **Slack/E-Mail-Benachrichtigungen**: Renovate signalisiert ueber Gitea-PRs und das Dependency Dashboard.
|
||||
- **Self-hosted Renovate-Runner-Cluster**: ein einzelner User-Script-Lauf reicht fuer den Homelab-Scope.
|
||||
|
||||
## Verwandte Doku
|
||||
|
||||
- `HOMELAB_ARCHITECTURE_MASTER_V2.md` Sektion 13 ("Reproduzierbare Deployments", Digest-Pinning)
|
||||
- `docs/WORKFLOW.md` Image-Versionierungs-Regel
|
||||
- `docs/SECRETS_MAP.md` (Renovate-Token wird dort nach Aktivierung ergaenzt)
|
||||
+25
-13
@@ -1,6 +1,6 @@
|
||||
# Repository Map
|
||||
|
||||
Stand: 2026-05-23
|
||||
Stand: 2026-05-31
|
||||
|
||||
Diese Datei ist eine technische Landkarte des Repositories. Sie wurde aus Markdown-Dokumenten, `docker-compose.yml`-Dateien, Env-Beispielen, Traefik-Dynamic-Configs, Komodo/Periphery-Dateien und Skripten abgeleitet. Sie beschreibt den Repo-Sollzustand, nicht zwingend den Live-Zustand auf dem Host.
|
||||
|
||||
@@ -27,23 +27,32 @@ Secret-Werte werden hier nicht dokumentiert. Aufgefuehrt werden nur Variablennam
|
||||
|---|---|
|
||||
| `README.md` | Einstieg und Kurzueberblick |
|
||||
| `HOMELAB_ARCHITECTURE_MASTER_V2.md` | operative Architektur-Quelle fuer Netzwerk, Zugriff und Ausnahmen |
|
||||
| `docs/README.md` | Doku-Index mit aktiver Doku, Archiv-Regel und Themenclustern |
|
||||
| `docs/WORKFLOW.md` | GitOps-/No-Drift-Arbeitsregeln |
|
||||
| `docs/GITOPS_DRIFT_RUNBOOK.md` | Pflichtmatrix fuer Git/Gitea/Komodo/Docker/Host-Drift |
|
||||
| `docs/DISASTER_RECOVERY.md` | Wiederanlauf nach Host-/Systemausfall |
|
||||
| `docs/RESTORE_MATRIX.md` | Restore-Quellen, Dump-Artefakte und Smoke-Tests je Dienst |
|
||||
| `docs/SERVICES_RECOVERY.md` | Recovery-kritische `/mnt/user/services`-Pfade, Gitea-Mirror und Komodo-Bootstrap |
|
||||
| `docs/STORAGE_LAYOUT.md` | verbindliche Storage-/Share-/Pfad-Konstitution |
|
||||
| `docs/HARDWARE_INVENTORY.md` | Hardware-, Disk-, SMART-, USV- und Strom-Inventar |
|
||||
| `docs/NETWORK_INVENTORY.md` | Router, DNS, Tailscale, Portfreigaben und Netztrennung |
|
||||
| `docs/EXTERNAL_DEPENDENCIES.md` | Externe Provider, Konten, Ausfall-Szenarien und kritische Off-Repo-Abhaengigkeiten |
|
||||
| `docs/CAPACITY_AND_LIFECYCLE.md` | Capacity-Schwellen, Wachstum, Upgrade-Trigger und Restore-Zeitziele |
|
||||
| `docs/FAMILY_ONBOARDING.md` | Familienorientierte Nutzungsdoku ohne Operator-Details |
|
||||
| `docs/FAMILY_VIEW_DASHBOARD.md` | Spezifikation fuer das Grafana Family-View-Dashboard (Doku-only, kein JSON) |
|
||||
| `docs/RESTORE_DRILL_ROUTINE.md` | Quartalsweise Restore-Drill-Routine, Tier-Belegung, DR-Sanity-Check |
|
||||
| `docs/IMMICH_RESTORE_TEST.md` | Operator-Overview Immich-Restore-Test, Erstlauf 2026-05-27 erfolgreich |
|
||||
| `docs/RENOVATE.md` | Self-hosted Renovate gegen Gitea (Setup + Wartung) |
|
||||
| `docs/OFFSITE_BACKUP_OPTIONS.md` | Entscheidungsvorlage zweites Offsite-Backup-Ziel (rsync.net vs. BorgBase EU2 vs. Cold-Platte) |
|
||||
| `docs/AUDIT_2026-05-25_TODO.md` | Operative Arbeitsliste aus dem Audit vom 2026-05-25; Authelia-2FA bewusst geparkt |
|
||||
| `ops/policy-checks/mem-limits-baseline.md` | F-19 Vorbereitungs-Plan fuer Container-Mem-Limits; bewusst nicht vor 7 Tagen Peak-Beobachtung |
|
||||
| `docs/ALERTING_MAP.md` | ntfy Topic-Konvention und Sender-Mapping fuer Homelab-Alerts |
|
||||
| `docs/ROLLBACK.md` | Rueckweg bei Fehlern im GitOps-Betrieb |
|
||||
| `docs/SECRETS_MAP.md` | Secret-Namen, Pfade und Einbindungsarten ohne Werte |
|
||||
| `docs/HOME_ASSISTANT_INFLUXDB_ECOWITT.md` | Home Assistant -> InfluxDB 3 -> Grafana Ablauf |
|
||||
| `docs/AI_CONTEXT.md` | Gesamtverstaendnis fuer KI-Agenten |
|
||||
| `docs/SERVICE_CATALOG.md` | produktiver Service-Katalog |
|
||||
| `docs/archive/2026-05/` | historische Audits, Handoffs, Codex-Prompts und erledigte Plaene aus Mai 2026 |
|
||||
|
||||
## Relevante Nicht-Compose-Dateien
|
||||
|
||||
@@ -59,9 +68,11 @@ Secret-Werte werden hier nicht dokumentiert. Aufgefuehrt werden nur Variablennam
|
||||
| `monitoring/grafana/provisioning/*` | Grafana Datasource-/Dashboard-Provisioning fuer Prometheus und Loki |
|
||||
| `ops/glance/config/glance.yml` | Glance Dashboard-Konfiguration fuer Homelab-Monitore, Internet-/DNS-/VPN-Widgets, Community-Widgets, Docker-Containergruppen, Zeitfortschritt, Host-Snapshot, Bookmarks und zweite Infrastruktur-Seite |
|
||||
| `ops/borg-ui/scripts/pre-backup-dumps.sh` | Host-seitiges Dump-Skript fuer PostgreSQL, SQLite-Container-Dumps und Komodo Mongo |
|
||||
| `services/posture-check/posture-check.sh` | Host-seitiger Posture-Check fuer Filesystem, Mover-Drift, NVMe-SMART, Fuellstand und ntfy-Alarmierung |
|
||||
| `services/posture-check/posture-check.sh` | Host-seitiger Posture-Check fuer Filesystem, Mover-Drift, NVMe-SMART, Fuellstand, Authelia-Repo<->Host-Drift und ntfy-Alarmierung |
|
||||
| `services/posture-check/export-prometheus-textfile.sh` | Host-seitiger Textfile-Exporter fuer Borg-, Critical-Container- und GitOps-Runtime-Image-Drift-Metriken |
|
||||
| `services/posture-check/docker-critical-events.sh` | Host-seitiger Docker-Event-Watcher fuer kritische ntfy-Alarme |
|
||||
| `services/posture-check/posture_check.sh` | Kompatibilitaets-Wrapper fuer die Schreibweise aus `STORAGE_LAYOUT.draft.md` |
|
||||
| `services/posture-check/posture_check.sh` | Kompatibilitaets-Wrapper fuer die historische Schreibweise aus `STORAGE_LAYOUT.draft.md` |
|
||||
| `services/authelia-diff.sh` | Vergleicht `access_control:`-Sektion zwischen Repo-Baseline und Host-Datei; wird vom Posture-Check als Check `authelia_config_drift` aufgerufen |
|
||||
| `ops/hermes-agent/config/hermes/config.yaml` | Hermes Agent Konfiguration mit Env-Platzhaltern |
|
||||
| `ops/hermes-agent/hermes.env.example` | Beispiel fuer Hermes `.env`; echte Datei liegt auf Host-Appdata |
|
||||
| `ops/hermes-agent/stack.env.example` | Beispiel fuer Hermes Stack-ENV; echte `stack.env` bleibt host-/komodoseitig und ist per `.gitignore` ausgeschlossen |
|
||||
@@ -89,11 +100,11 @@ Secret-Werte werden hier nicht dokumentiert. Aufgefuehrt werden nur Variablennam
|
||||
| Stack | Compose | Services / Images | Traefik Hosts | Networks | Ports | Abhaengigkeiten |
|
||||
|---|---|---|---|---|---|---|
|
||||
| Gitea | `core/gitea/docker-compose.yml` | `gitea` -> `docker.gitea.com/gitea:1.25.4@sha256:...` | `git.kaleschke.info` | `frontend_net` | `222:22/tcp` | SQLite in `/data`; SSH-Port ist dokumentierte Ausnahme; `github.com` ist als Mirror-Ziel erlaubt und externe DNS-Resolver sind gesetzt |
|
||||
| Authelia | `security/authelia/docker-compose.yml` | `authelia` -> `authelia/authelia:latest@sha256:...` | `auth.kaleschke.info` | `frontend_net`, `backend_net` | keine | PostgreSQL 17 Storage, Traefik ForwardAuth; bewusst ohne Redis-Session-Backend |
|
||||
| Authelia | `security/authelia/docker-compose.yml` | `authelia` -> `authelia/authelia:latest@sha256:...` | `auth.kaleschke.info` | `frontend_net`, `backend_net` | keine | PostgreSQL 18 Storage im historisch benannten `postgresql17`-Container, Traefik ForwardAuth; bewusst ohne Redis-Session-Backend |
|
||||
| Vaultwarden | `security/vaultwarden/docker-compose.yml` | `vaultwarden` -> `vaultwarden/server:latest@sha256:...` | `vault.kaleschke.info` | `frontend_net` | keine | Datei-Persistenz, `ADMIN_TOKEN_FILE` |
|
||||
| ddns-updater | `infra/ddns-updater/docker-compose.yml` | `ddns-updater` -> `ghcr.io/qdm12/ddns-updater:latest@sha256:...` | keine | `frontend_net` | keine | Cloudflare/API-Internetbedarf |
|
||||
| PostgreSQL 17 | `infra/postgresql17/docker-compose.yml` | `postgresql17` -> `postgres:17.9@sha256:...` | keine | `backend_net` | keine | shared DB-Cluster |
|
||||
| Redis | `infra/redis/docker-compose.yml` | `Redis` -> `redis:7.4-alpine@sha256:...` | keine | `backend_net` | keine | shared Cache, Passwort-Datei |
|
||||
| PostgreSQL 18 | `infra/postgresql17/docker-compose.yml` | `postgresql17` -> `postgres:18.4@sha256:8ff36f3c66371cba71d20ceedccfc3de9669a68737607888c4ef0af93abe8e39` | keine | `backend_net` | keine | shared DB-Cluster; Service-Name bleibt historisch `postgresql17` |
|
||||
| Redis | `infra/redis/docker-compose.yml` | `Redis` -> `redis:8.8.0-alpine@sha256:09160599abd229764c0fb44cb6be640294e1d360a54b19985ab4843dcf2d90f1` | keine | `backend_net` | keine | primaer Paperless-Redis (App-Cache); historisch als "shared" angelegt, faktisch nur von Paperless genutzt; Passwort-Datei |
|
||||
|
||||
### Host Services
|
||||
|
||||
@@ -156,7 +167,7 @@ Secret-Werte werden hier nicht dokumentiert. Aufgefuehrt werden nur Variablennam
|
||||
| Network | Typ / Status | Nutzer |
|
||||
|---|---|---|
|
||||
| `frontend_net` | external bridge | Web-/Proxy-Netz fuer Traefik und alle gerouteten UIs |
|
||||
| `backend_net` | external/internal laut Architektur | PostgreSQL 17, Redis, Authelia, Paperless, Mail Archiver, Traefik |
|
||||
| `backend_net` | external/internal laut Architektur | PostgreSQL 18 (`postgresql17`), Redis 8, Authelia, Paperless, Mail Archiver, Traefik |
|
||||
| `dns_net` | App-/Host-Netz | AdGuard Home und Unbound |
|
||||
| `immich_default` | Compose-intern, `internal: true` | Immich Server, ML, Postgres, Redis |
|
||||
| `mealie_internal` | Compose-intern; Laufzeitname mit Compose-Projektpraefix typischerweise `mealie_mealie_internal` | Mealie und Mealie Postgres |
|
||||
@@ -176,13 +187,13 @@ Secret-Werte werden hier nicht dokumentiert. Aufgefuehrt werden nur Variablennam
|
||||
| Gitea | `/mnt/user/services/gitea/data` |
|
||||
| Authelia | `/mnt/user/appdata/authelia/config`, Authelia Secret-Dateien |
|
||||
| Vaultwarden | `/mnt/user/appdata/vaultwarden`, Admin-Token-Datei |
|
||||
| PostgreSQL 17 | `/mnt/user/appdata/postgresql17`, `postgres_password.txt` |
|
||||
| PostgreSQL 18 | `/mnt/user/appdata/postgresql18`, Rollback-Altstand `/mnt/user/appdata/postgresql17`, `postgres_password.txt` |
|
||||
| Redis | `/mnt/user/appdata/redis`, `redis_password.txt` |
|
||||
| Paperless | `/mnt/user/appdata/paperless-ngx/data`, `/mnt/user/documents/paperless`, `/mnt/user/documents/scans_inbox` |
|
||||
| Immich | `/mnt/user/photos/immich`, `/mnt/user/photos/family_archive`, `/mnt/user/appdata/immich_postgres`, `model-cache` |
|
||||
| Mealie | `/mnt/user/appdata/mealie/data`, `/mnt/user/appdata/mealie/postgres` |
|
||||
| Immich | `/mnt/user/photos/immich`, `/mnt/user/photos/family_archive`, `/mnt/user/appdata/immich_postgres_vectorchord`, Rollback-Altstand `/mnt/user/appdata/immich_postgres`, `model-cache` |
|
||||
| Mealie | `/mnt/user/appdata/mealie/data`, `/mnt/user/appdata/mealie/postgres18`, Rollback-Altstand `/mnt/user/appdata/mealie/postgres` |
|
||||
| Mail Archiver | `/mnt/user/appdata/mailarchiver/data-protection-keys` |
|
||||
| Nextcloud | `/mnt/user/appdata/nextcloud/html`, `/mnt/user/documents/nextcloud-data`, `/mnt/user/appdata/nextcloud/postgres`, `/mnt/user/appdata/nextcloud/redis` |
|
||||
| Nextcloud | `/mnt/user/appdata/nextcloud/html`, `/mnt/user/documents/nextcloud-data`, `/mnt/user/appdata/nextcloud/postgres18`, Rollback-Altstand `/mnt/user/appdata/nextcloud/postgres`, `/mnt/user/appdata/nextcloud/redis` |
|
||||
| Plex | `/mnt/user/appdata/plex/config`, `/mnt/user/appdata/plex/transcode`, `/mnt/user/media`, `/mnt/user/photos` |
|
||||
| ntfy | `/mnt/user/appdata/ntfy` |
|
||||
| Paperless-GPT | `/mnt/user/appdata/paperless-gpt/data`, `/mnt/user/appdata/paperless-gpt/prompts` |
|
||||
@@ -206,7 +217,7 @@ Secret-Werte werden hier nicht dokumentiert. Aufgefuehrt werden nur Variablennam
|
||||
| Traefik | `cloudflare_dns_api_token` als Docker Secret / `CF_DNS_API_TOKEN_FILE` |
|
||||
| Authelia | JWT, Session, Storage Encryption, Postgres Password via `_FILE` |
|
||||
| Vaultwarden | `ADMIN_TOKEN_FILE` |
|
||||
| PostgreSQL 17 | `POSTGRES_PASSWORD_FILE` |
|
||||
| PostgreSQL 18 | `POSTGRES_PASSWORD_FILE` |
|
||||
| Redis | Passwort-Datei und Startkommando |
|
||||
| Paperless | `PAPERLESS_DBPASS`, `PAPERLESS_REDIS` als Komodo Stack ENV |
|
||||
| Immich | `IMMICH_DB_PASSWORD` Stack ENV; `immich_postgres_password.txt` fuer Postgres |
|
||||
@@ -225,6 +236,7 @@ Secret-Werte werden hier nicht dokumentiert. Aufgefuehrt werden nur Variablennam
|
||||
|---|---|---|
|
||||
| `ops/borg-ui/scripts/pre-backup-dumps.sh` | Unraid Host, nicht Borg-UI Inline-Hook | erzeugt aktuelle Dumps unter `/mnt/user/backups/borg/dumps/latest` |
|
||||
| `services/posture-check/posture-check.sh` | Unraid Host | schreibt `/mnt/user/services/posture-check/last.json` und alarmiert via ntfy bei Warning/Critical |
|
||||
| `services/posture-check/export-prometheus-textfile.sh` | Unraid Host, Cron/Textfile-Collector | schreibt Borg-, Critical-Container- und GitOps-Runtime-Image-Drift-Metriken fuer Prometheus |
|
||||
| `services/posture-check/docker-critical-events.sh` | Unraid Host | beobachtet Docker `die`/`oom`/`kill` Events und alarmiert via `homelab-alerts` |
|
||||
|
||||
Das Skript liest Secret-Dateien auf dem Host und schreibt Dump-Artefakte. Bei Analyse niemals Secret-Inhalte ausgeben.
|
||||
@@ -237,7 +249,7 @@ Das Skript liest Secret-Dateien auf dem Host und schreibt Dump-Artefakte. Bei An
|
||||
- Authelia `configuration.yml` ist Repo-Baseline fuer nicht geheime Einstellungen, wird aber nicht automatisch von Komodo auf den Host kopiert. Die produktive Host-Datei kann zusaetzliche OIDC-/Secret-Konfiguration enthalten; Aenderungen muessen manuell gemerged und validiert werden.
|
||||
- `backend_net` ist in der Architektur als `internal: true` beschrieben; einzelne Compose-Dateien referenzieren es external. Live-Netz-Attribute bei Drift-Fragen pruefen.
|
||||
- Einige Images bleiben trotz Digest-Pin semantisch auf mutable Tags (`latest@sha256`, `release@sha256`). Das ist bewusst dokumentiert, aber bei Updates gesondert pruefen.
|
||||
- Stateful Datenhalter sind seit 2026-05-05 bevorzugt mit Minor-/Patch-Tag plus Digest gepinnt; Redis-Caches wurden im Hardening-Sprint 2026-05-16 auf `redis:7.4-alpine@sha256:...` vereinheitlicht.
|
||||
- Stateful Datenhalter sind seit 2026-05-05 bevorzugt mit Minor-/Patch-Tag plus Digest gepinnt; PostgreSQL-17-Datenhalter wurden am 2026-05-31 per Dump/Restore auf PostgreSQL 18 gehoben, Redis-Caches auf Redis 8.8, Immich-Postgres bleibt auf PG14 mit VectorChord.
|
||||
- `scrutiny` bleibt `privileged: true`; dokumentierte Ausnahme, aber weiterhin pruefenswert.
|
||||
- `tailscale` nutzt Host-Netz, `NET_ADMIN`, `NET_RAW` und `/dev/net/tun` als dokumentierte VPN-Ausnahme.
|
||||
- `monitoring-influxdb3-core` laeuft aktuell als `user: "0"`; UID/GID-Hardening nur als eigener Sprint.
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
# Restore-Drill Routine - KalliLab CORE
|
||||
|
||||
Status: **verbindliche Routine (Doku-only)**, 2026-05-27.
|
||||
Audit-Bezug: `docs/archive/2026-05/AUDIT_2026-05-25.md` Sprint 7 "Restore-Lab-Drill quartalsweise dokumentieren".
|
||||
Verwandte Docs: `docs/RESTORE_MATRIX.md`, `docs/RESTORE_HANDBOOK.md`, `docs/DISASTER_RECOVERY.md`, `ops/restore-tests/schedule.md`.
|
||||
|
||||
## Ziel
|
||||
|
||||
Sicherstellen, dass die Backup-Kette nicht nur weiter laeuft, sondern auch **wiederherstellbar** ist. Restore-Tests werden nicht ad-hoc gemacht, wenn ein Problem auftritt, sondern in einer planbaren Kadenz, damit das Vertrauen ueber die Zeit waechst und Drift fruehzeitig auffaellt.
|
||||
|
||||
Diese Datei beschreibt nur die **Routine** (wann, was, wie pruefen). Die operativen Anleitungen pro Dienst stehen in `docs/RESTORE_HANDBOOK.md` und den dienstspezifischen Runbooks unter `ops/restore-tests/<dienst>-runbook.md`.
|
||||
|
||||
## Drei-Stufen-Modell
|
||||
|
||||
| Stufe | Frequenz | Aufwand | Ziel |
|
||||
|---|---|---|---|
|
||||
| Freshness-Check | woechentlich | Minuten | Backup-Artefakte sind frisch, Reports lesbar |
|
||||
| Mini-Restore | monatlich/quartalsweise | 15-60 Min | Ein konkreter Dienst wird in isoliertem Test-Lab restauriert |
|
||||
| DR-Sanity-Check | quartalsweise | 1-2 h | Reihenfolge, Doku, Tier-Reihenfolge in der DR-Doku gegen Realitaet pruefen |
|
||||
|
||||
## Bestaetigte Mini-Restores
|
||||
|
||||
Wenn ein Mini-Restore zum ersten Mal sauber durchlaeuft, wird er hier als Referenz gefuehrt. Der Eintrag wird **nicht** entfernt, wenn er wiederholt wird; stattdessen aendert sich der Datums-Stand.
|
||||
|
||||
| Dienst | Erster bestaetigter Lauf | Letzter Erfolg | Report | Repo-Skript |
|
||||
|---|---|---|---|---|
|
||||
| Vaultwarden | 2026-05-07 | 2026-05-07 | `/mnt/user/backups/restore-reports/vaultwarden-2026-05-07.md` | `ops/restore-tests/vaultwarden-restore-test.sh` |
|
||||
| Gitea | 2026-05-07 | 2026-05-07 | `/mnt/user/backups/restore-reports/gitea-2026-05-07.md` | `ops/restore-tests/gitea-restore-test.sh` |
|
||||
| Paperless | 2026-05-07 | 2026-05-07 | `/mnt/user/backups/restore-reports/paperless-2026-05-07.md` | `ops/restore-tests/paperless-restore-test.sh` |
|
||||
| Immich | **2026-05-27** | **2026-05-27** | `/mnt/user/backups/restore-reports/immich-2026-05-27.md` | `ops/restore-tests/immich-restore-test.sh` |
|
||||
|
||||
Bei jedem weiteren Lauf wird die Spalte "Letzter Erfolg" aktualisiert.
|
||||
|
||||
## Quartals-Kadenz
|
||||
|
||||
Ein Kalenderjahr enthaelt vier Quartals-Drills. Jeder Quartals-Drill besteht aus dem Mini-Restore eines anderen Tier-2-Dienstes plus einem DR-Sanity-Check der Tier-1-Dienste.
|
||||
|
||||
| Quartal | Mini-Restore | DR-Sanity-Check Fokus |
|
||||
|---|---|---|
|
||||
| Q1 (Januar-Maerz) | `paperless` | Tier-1-Reihenfolge, Posture-Check-Status, Borg-Frische-Alerts |
|
||||
| Q2 (April-Juni) | `immich` | Komodo-Bootstrap-Pfad, Gitea-Bundles, Secrets-Pfad-Inventur |
|
||||
| Q3 (Juli-September) | `mealie` oder `nextcloud` (Operator-Wahl) | DNS-Pfad (AdGuard/Unbound/Tailscale), Cert-Expiry-Sicht |
|
||||
| Q4 (Oktober-Dezember) | `vaultwarden` oder `gitea` (Operator-Wahl) | Externe Abhaengigkeiten (Cloudflare, Hetzner, GitHub-Mirror), Off-site-Zweitziel-Diskussion |
|
||||
|
||||
Diese Liste ist bewusst auf Tier-2 und Tier-1-Dienste fokussiert. Tier-3-Dienste (Filebrowser, Glances, Scrutiny, Speedtest, Glance) werden im Drill nicht explizit ausgefuehrt, weil sie rebuildbar sind oder keinen kritischen Datenbestand haben.
|
||||
|
||||
### Q2 2026 - Konkrete Belegung
|
||||
|
||||
- Mini-Restore: **Immich (erledigt 2026-05-27)**.
|
||||
- DR-Sanity-Check (teilweise erledigt, Rest vor Quartalsende 2026-06-30):
|
||||
- Komodo-Bootstrap-Pfad: **erledigt 2026-05-30** durch echten Trockenlauf via `ops/restore-tests/komodo-bootstrap-test.sh --keep-data`, Report `/mnt/user/backups/restore-reports/komodo-bootstrap-2026-05-30.md`, `ops/komodo/docker-compose.yml` als Recovery-Anker belegt.
|
||||
- Gitea-Bundles ueber `ops/borg-ui/scripts/gitea-bundle-mirror.sh` auf Frische und Bundle-Klonbarkeit pruefen: offen.
|
||||
- Secrets-Inventur gegen `docs/SECRETS_MAP.md` abgleichen: offen.
|
||||
|
||||
### Wer schiebt das an?
|
||||
|
||||
- **Operator** loest jeden Drill manuell aus, idealerweise am 2. Wochenende des ersten Monats im Quartal.
|
||||
- Es gibt **keinen** automatischen Host-Schedule fuer den Quartals-Drill. Die woechentliche Freshness-Pruefung und die monatlichen Mini-Restores in `ops/restore-tests/schedule.md` laufen separat.
|
||||
- Bei akuten Aenderungen (Major-Upgrade eines Dienstes, FS-Migration, Repo-Strukturaenderung): zusaetzlichen Ad-hoc-Drill ausserhalb der Quartals-Kadenz einplanen.
|
||||
|
||||
## Freshness-Check (woechentlich)
|
||||
|
||||
- Skript: `ops/restore-tests/check-restore-freshness.sh` (Host-Bash) bzw. `ops/restore-tests/check-restore-freshness.ps1` (Windows-Operator).
|
||||
- Erwartete Pruefungen:
|
||||
- Letzter Borg-Archiv-Stand juenger als die Schwellwerte aus `docs/STORAGE_LAYOUT.md` §11.
|
||||
- Kanonische Dump-Artefakte unter `/mnt/user/backups/borg/dumps/latest/` vorhanden und juenger als 26 h.
|
||||
- Letzte Report-Dateien unter `/mnt/user/backups/restore-reports/` lesbar.
|
||||
- Gitea-Bundles unter `/mnt/user/backups/git-bundles/gitea/` plausibel aktuell.
|
||||
|
||||
Ergebnis ist ein kurzes Konsolen-Log; bei Fehler greift die ntfy-Alarmierung aus `docs/ALERTING_MAP.md`.
|
||||
|
||||
## Mini-Restore (monatlich / bimonatlich)
|
||||
|
||||
Skripte folgen alle demselben Muster:
|
||||
|
||||
- isoliertes Test-Lab unter `/mnt/user/backups/restore-lab/<dienst>`
|
||||
- isolierte Test-Container `restoretest-*`
|
||||
- nur `127.0.0.1`-Ports, keine Traefik-Labels, keine produktive Domain
|
||||
- Smoke-Test mit Erfolgsregel "Container laeuft reicht nicht"
|
||||
- Report unter `/mnt/user/backups/restore-reports/<dienst>-YYYY-MM-DD.md`
|
||||
|
||||
Operative Anleitungen je Dienst:
|
||||
|
||||
- `ops/restore-tests/vaultwarden-runbook.md`
|
||||
- `ops/restore-tests/gitea-runbook.md`
|
||||
- `ops/restore-tests/paperless-runbook.md`
|
||||
- `ops/restore-tests/immich-runbook.md`
|
||||
|
||||
## DR-Sanity-Check (quartalsweise)
|
||||
|
||||
Der Sanity-Check ist **kein** echter Restore. Er ist eine Doku-/Konsistenz-Pruefung mit zehn Punkten:
|
||||
|
||||
1. `docs/DISASTER_RECOVERY.md` Phase 1-5 noch konsistent mit Repo und Live-Stand?
|
||||
2. `docs/RESTORE_MATRIX.md` Tier-Klassifizierung pro Dienst aktuell?
|
||||
3. `docs/SECRETS_MAP.md` Pfade existieren, Stack-ENV-only-Liste aktuell?
|
||||
4. `docs/SERVICES_RECOVERY.md` Komodo-Bootstrap-Pfad noch in Stufen A-F konsistent?
|
||||
5. Gitea-Bundle-Mechanik laeuft und letzter Bundle-Stand klonbar (`git clone .../homelab-infra.bundle /tmp/restore-test`)?
|
||||
6. Externe Mirrors (`michaelkaleschke-spec/homelab-infra` auf GitHub) gemaess `docs/EXTERNAL_DEPENDENCIES.md` noch erreichbar und aktuell?
|
||||
7. ntfy-Push-Pfad noch erreichbar? (Test-Nachricht an `homelab-info`.)
|
||||
8. Letzte vier Quartals-Mini-Restores im Report-Verzeichnis vorhanden?
|
||||
9. Borg-Repo-Passphrase Offline-Sicherung noch auffindbar? (Pruefung durch Operator, nicht durch Skript, kein Wert ablegen.)
|
||||
10. Capacity-Stand gegen Schwellen aus `docs/CAPACITY_AND_LIFECYCLE.md` abgeglichen?
|
||||
|
||||
Jeder Punkt wird in einem kurzen Quartals-Eintrag in `docs/MIGRATION_LOG.md` als `ok` / `Abweichung` / `Folgeaufgabe` festgehalten.
|
||||
|
||||
## Abbruch-Regeln
|
||||
|
||||
Wenn ein Drill fehlschlaegt, gilt die Stop-Regel aus `docs/WORKFLOW.md`:
|
||||
|
||||
- nach zwei fehlgeschlagenen Reparaturversuchen nicht weiterschreiben
|
||||
- stattdessen Pflichtmatrix aus `docs/GITOPS_DRIFT_RUNBOOK.md` durchgehen
|
||||
- Befund dokumentieren, naechsten Schritt mit dem Operator klaeren
|
||||
- erst danach den Drill erneut starten oder das Quartal als "Drill incomplete" markieren
|
||||
|
||||
## Berichte
|
||||
|
||||
- Mini-Restore-Reports liegen unter `/mnt/user/backups/restore-reports/<dienst>-YYYY-MM-DD.md`.
|
||||
- Quartals-Sanity-Checks landen als kurzer Block in `docs/MIGRATION_LOG.md`, nicht als eigenes Dokument.
|
||||
- Reports werden nicht aus dem Repo verlinkt, weil sie nicht im Repo liegen. Operator dokumentiert nur Vorhanden/Erfolg/Datum.
|
||||
|
||||
## Geltungsdauer
|
||||
|
||||
Diese Routine gilt ab Q2 2026. Bei groesseren Aenderungen (zweites Off-site, Authelia-OIDC-Aktivierung, Hardware-Migration) wird die Liste pro Quartal angepasst.
|
||||
+17
-9
@@ -30,10 +30,10 @@ Sie ist die fachliche Ergaenzung zu `docs/DISASTER_RECOVERY.md`.
|
||||
| Traefik | Share / Borg | `/mnt/user/appdata/traefik`, besonders `dynamic/`, `letsencrypt`, `secrets` | keine eigene DB | `cloudflare_dns_api_token` | `frontend_net`, `backend_net` | `https://traefik.kaleschke.info` erreichbar, Dashboard ueber Authelia |
|
||||
| AdGuard Home | Share / Borg | `/mnt/user/appdata/adguard/conf` | keine | keine zusaetzlichen Repo-Secrets dokumentiert | `dns_net`, `frontend_net` | DNS-Aufloesung funktioniert |
|
||||
| Tailscale | Share / Borg | `/mnt/user/appdata/tailscale` | keine | Tailscale-State im Pfad | Host-Netz | Tailscale verbunden |
|
||||
| PostgreSQL 17 | Share + Dumps | `/mnt/user/appdata/postgresql17` | `postgresql17-globals.sql`, `postgresql17-mailarchiver.dump`, `postgresql17-paperless.dump`, optional `postgresql17-authelia.dump` | `postgres_password.txt` | `backend_net` | DB startet, Ziel-Datenbanken vorhanden |
|
||||
| Redis | Share / Host | `/mnt/user/appdata/redis` | keine | `redis_password.txt` | `backend_net` | Redis startet, Apps verbinden sich |
|
||||
| Authelia | Borg | `/mnt/user/appdata/authelia/config`, `/mnt/user/appdata/secrets/*authelia*` | Shared PostgreSQL, optional Dump `postgresql17-authelia.dump` | JWT/Session/Storage/Postgres-/SMTP-Secret-Dateien | PostgreSQL 17, Traefik, GMX SMTP | Login-Seite und ForwardAuth funktionieren; SMTP-Notifier startet; aktive Sessions werden nach Restart neu aufgebaut |
|
||||
| Gitea | GitHub-Mirror fuer Repo-Bootstrap, Borg + Dump fuer Gitea-Appstate | `/mnt/user/services/gitea/data` | `gitea.sqlite.dump` | `borg_repo_passphrase.txt` fuer Restore-Tests; GitHub-Push-Mirror-PAT liegt nur in Gitea-Mirror-Settings | Traefik | Web-UI erreichbar, Repo sichtbar, SSH-Port reagiert; GitHub-Push-Mirror synchronisiert ohne `last_error`; Mini-Restore nach `/mnt/user/backups/restore-lab/gitea` am 2026-05-07 erfolgreich validiert |
|
||||
| PostgreSQL 18 | Share + Dumps | `/mnt/user/appdata/postgresql18` (Rollback-Altstand: `/mnt/user/appdata/postgresql17`) | `postgresql17-globals.sql`, `postgresql17-mailarchiver.dump`, `postgresql17-paperless.dump`, optional `postgresql17-authelia.dump` | `postgres_password.txt`, App-Rollen-Passwoerter aus den jeweiligen Stack-ENV/Secret-Dateien | `backend_net` | DB startet, Ziel-Datenbanken vorhanden; `SHOW data_checksums` ist `on` |
|
||||
| Redis 8 | Share / Host | `/mnt/user/appdata/redis`; Rollback-Backup unter `/mnt/user/backups/borg/dumps/latest/shared-redis-pre-redis8-<ts>` | RDB/AOF-Dateien im Datenpfad | `redis_password.txt` | `backend_net` | Redis startet, `redis_version` ist 8.x, Apps verbinden sich |
|
||||
| Authelia | Borg | `/mnt/user/appdata/authelia/config`, `/mnt/user/appdata/secrets/*authelia*` | Shared PostgreSQL 18, optional Dump `postgresql17-authelia.dump` | JWT/Session/Storage/Postgres-/SMTP-Secret-Dateien | PostgreSQL 18, Traefik, GMX SMTP | Login-Seite und ForwardAuth funktionieren; SMTP-Notifier startet; aktive Sessions werden nach Restart neu aufgebaut |
|
||||
| Gitea | GitHub-Mirror + Gitea-Bundles fuer Repo-Bootstrap, Borg + Dump fuer Gitea-Appstate | `/mnt/user/services/gitea/data`, `/mnt/user/backups/git-bundles/gitea` | `gitea.sqlite.dump`, Bundle-Report `latest-report.md` | `borg_repo_passphrase.txt` fuer Restore-Tests; GitHub-Push-Mirror-PAT liegt nur in Gitea-Mirror-Settings | Traefik | Web-UI erreichbar, Repo sichtbar, SSH-Port reagiert; Bundle laesst sich klonen und `git fsck` ist sauber; GitHub-Push-Mirror synchronisiert ohne `last_error`; Mini-Restore nach `/mnt/user/backups/restore-lab/gitea` am 2026-05-07 erfolgreich validiert |
|
||||
| Komodo | Borg / Share | `/mnt/user/appdata/komodo/core`, `/mnt/user/appdata/komodo/periphery`, `/mnt/user/services/stacks` | `komodo-mongo.archive.gz` falls verifiziert | `komodo_mongo_password.txt`, `KOMODO_*` Stack ENV | Traefik, Mongo, Gitea | UI erreichbar, Periphery verbunden |
|
||||
| GitOps Host Automation | Borg / Git | `/mnt/user/services/homelab-infra`, `/mnt/user/services/posture-check` | keine eigene DB | keine | Gitea, Komodo, Unraid User Scripts | `posture-check` laeuft vom Host-Pfad und liefert `warning_count: 0` im bekannten Uebergangszustand |
|
||||
| Vaultwarden | Borg + Dump | `/mnt/user/appdata/vaultwarden` | `vaultwarden.sqlite.dump` | `vaultwarden_admin_token.txt`, `borg_repo_passphrase.txt` fuer Restore-Tests | Traefik | Login-Seite erreichbar, Tresor-Daten sichtbar; Mini-Restore nach `/mnt/user/backups/restore-lab/vaultwarden` am 2026-05-07 erfolgreich validiert |
|
||||
@@ -44,11 +44,11 @@ Sie ist die fachliche Ergaenzung zu `docs/DISASTER_RECOVERY.md`.
|
||||
|
||||
| Dienst | Fuehrende Quelle | Datei-Restore | Dump / DB | Secrets / ENV | Abhaengigkeiten | Smoke-Test |
|
||||
|---|---|---|---|---|---|---|
|
||||
| Paperless-ngx | Borg + Dumps | `/mnt/user/appdata/paperless-ngx/data`, `/mnt/user/documents/paperless`, `/mnt/user/documents/paperless/export`, `/mnt/user/documents/scans_inbox` | `postgresql17-paperless.dump` | `PAPERLESS_DBPASS`, `PAPERLESS_REDIS`, `borg_repo_passphrase.txt` fuer Restore-Tests | PostgreSQL 17, Redis, Traefik | Web-UI startet, Dokumente vorhanden; Mini-Restore nach `/mnt/user/backups/restore-lab/paperless` am 2026-05-07 erfolgreich validiert |
|
||||
| Mealie | Borg + Dump | `/mnt/user/appdata/mealie/data`, optional `/mnt/user/appdata/mealie/postgres` bei lokalem Share-Weiterbetrieb | `mealie.dump` | `mealie_postgres_password.txt` | `mealie-postgres`, Traefik | UI startet, Rezepte vorhanden |
|
||||
| Immich | Borg + Dump | `/mnt/user/photos/immich`, `/mnt/user/photos/family_archive` | `immich.dump` | `IMMICH_DB_PASSWORD`, `immich_postgres_password.txt` | `immich_postgres`, `immich_redis`, Traefik | UI startet, Medienbibliothek sichtbar |
|
||||
| Mail-Archiver | Borg + Shared Dump | `/mnt/user/appdata/mailarchiver/data-protection-keys` | `postgresql17-mailarchiver.dump` | `MAILARCHIVER_DB_CONNECTION`, `MAILARCHIVER_AUTH_PASSWORD` | PostgreSQL 17, Traefik, Authelia | Authelia-Weiterleitung greift; nach Login startet die Web-UI und das Archiv laesst sich oeffnen |
|
||||
| Nextcloud | Borg + Dump | `/mnt/user/appdata/nextcloud/html`, `/mnt/user/documents/nextcloud-data` | `nextcloud.dump` | `nextcloud_admin_user.txt`, `nextcloud_admin_password.txt`, `nextcloud_postgres_password.txt` | `nextcloud-postgres`, `nextcloud-redis`, Traefik | Web-UI startet, Login funktioniert, Dateien sichtbar |
|
||||
| Paperless-ngx | Borg + Dumps | `/mnt/user/appdata/paperless-ngx/data`, `/mnt/user/documents/paperless`, `/mnt/user/documents/paperless/export`, `/mnt/user/documents/scans_inbox` | `postgresql17-paperless.dump` | `PAPERLESS_DBPASS`, `PAPERLESS_REDIS`, `borg_repo_passphrase.txt` fuer Restore-Tests | PostgreSQL 18, Redis, Traefik | Web-UI startet, Dokumente vorhanden; Restore-Test am 2026-05-31 erfolgreich: Borg-Archiv `Tägliche-Sicherung-2026-05-31T04:30:13.181`, isolierter PostgreSQL-18-/Redis-8-Testpfad, HTTP `200`, `32` Dokumente im Test-DB-Check, Report `/mnt/user/backups/restore-reports/paperless-2026-05-31.md` |
|
||||
| Mealie | Borg + Dump | `/mnt/user/appdata/mealie/data`, `/mnt/user/appdata/mealie/postgres18` (Rollback-Altstand: `/mnt/user/appdata/mealie/postgres`) | `mealie.dump` | `mealie_postgres_password.txt` | `mealie-postgres`, Traefik | UI startet, Rezepte vorhanden |
|
||||
| Immich | Borg + Dump | `/mnt/user/photos/immich`, `/mnt/user/photos/family_archive`, `/mnt/user/appdata/immich_postgres_vectorchord`; Rollback-Altstand: `/mnt/user/appdata/immich_postgres` | `immich.dump`; nach VectorChord braucht ein Restore ein Postgres-Image mit VectorChord | `IMMICH_DB_PASSWORD`, `immich_postgres_password.txt`, `borg_repo_passphrase.txt` fuer Restore-Tests | `immich_postgres`, `immich_redis`, Traefik | DB- und UI-Smoke gegen produktives Borg-Archiv am 2026-05-27 erfolgreich validiert; VectorChord-Migration am 2026-05-31: `11977` Assets, `11107` Smart-Search-Zeilen, `7092` Face-Search-Zeilen, `vchord 0.4.3`, `vector 0.8.1`, HTTP/API-Smoke 200. Voll-Restore der Foto-Dateien bleibt separater DR-Drill |
|
||||
| Mail-Archiver | Borg + Shared Dump | `/mnt/user/appdata/mailarchiver/data-protection-keys` | `postgresql17-mailarchiver.dump` | `MAILARCHIVER_DB_CONNECTION`, `MAILARCHIVER_AUTH_PASSWORD` | PostgreSQL 18, Traefik, Authelia | Authelia-Weiterleitung greift; nach Login startet die Web-UI und das Archiv laesst sich oeffnen |
|
||||
| Nextcloud | Borg + Dump | `/mnt/user/appdata/nextcloud/html`, `/mnt/user/documents/nextcloud-data`, `/mnt/user/appdata/nextcloud/postgres18` (Rollback-Altstand: `/mnt/user/appdata/nextcloud/postgres`), `/mnt/user/appdata/nextcloud/redis` | `nextcloud.dump`; Redis-Backup vor Redis-8-Cutover unter `/mnt/user/backups/borg/dumps/latest/nextcloud-redis-pre-redis8-<ts>` | `nextcloud_admin_user.txt`, `nextcloud_admin_password.txt`, `nextcloud_postgres_password.txt`; produktive DB-Rolle laut `config.php` ist `oc_admin` | `nextcloud-postgres`, `nextcloud-redis`, Traefik | Web-UI startet, Login funktioniert, Dateien sichtbar; `occ status` zeigt `maintenance: false` |
|
||||
| Glance | Git / Borg-Repo | Repo-Konfiguration unter `ops/glance/config/glance.yml`; keine kritische Datenpersistenz | keine | `GLANCE_IMMICH_API_KEY`, `GLANCE_ADGUARD_USERNAME`, `GLANCE_ADGUARD_PASSWORD`, `GLANCE_SPEEDTEST_API_KEY` | Traefik, Authelia, optional interne API-Ziele | Dashboard startet, Widgets laden, Docker-Status laeuft nur ueber `glance-docker-socket-proxy` |
|
||||
| ntfy | Borg / Share | `/mnt/user/appdata/ntfy` | keine | keine besonderen Secret-Dateien dokumentiert | Traefik | UI und Push-Endpunkt erreichbar |
|
||||
| Paperless-GPT | Borg / Share | `/mnt/user/appdata/paperless-gpt` | keine eigene DB | `PAPERLESS_API_TOKEN` | Traefik, Paperless | UI startet, Konfiguration vorhanden |
|
||||
@@ -97,6 +97,14 @@ Aktuell relevante Dump-Artefakte unter `/mnt/user/backups/borg/dumps/latest`:
|
||||
|
||||
Die Dump-Erzeugung ist host-seitig ueber `ops/borg-ui/scripts/pre-backup-dumps.sh` vorgesehen.
|
||||
|
||||
### PostgreSQL 18 Restore- und Rollback-Regeln
|
||||
|
||||
- PostgreSQL-18-Container verwenden das Docker-Image-Layout mit Mount auf `/var/lib/postgresql` und `PGDATA=/var/lib/postgresql/18/docker`.
|
||||
- Die alten PostgreSQL-17-Datenpfade bleiben nach dem Major-Upgrade als Rollback-Altstand erhalten und duerfen erst nach separater Freigabe geloescht werden.
|
||||
- Shared-Cluster-Restore: zuerst `pg_dumpall --globals-only` einspielen, dann die einzelnen Custom-Format-Dumps per `pg_restore`. Der Bootstrap-Rollenkonflikt fuer `mailarchiver` ist benign, solange `CREATE ROLE mailarchiver;` gezielt ausgelassen und das folgende `ALTER ROLE mailarchiver ...` eingespielt wird.
|
||||
- Nextcloud-Restore: vor dem Dump `occ maintenance:mode --on`, nach erfolgreichem Restore und `occ status` wieder `occ maintenance:mode --off`. Die Rolle `oc_admin` muss mit dem in `config.php` hinterlegten DB-Passwort existieren.
|
||||
- Rollback: betroffene App(s) und DB stoppen, Compose auf das vorherige PostgreSQL-17-Image und den alten Datenpfad zuruecksetzen, dann DB und App wieder starten.
|
||||
|
||||
---
|
||||
|
||||
## Praktische Restore-Regeln
|
||||
|
||||
+38
-2
@@ -18,7 +18,7 @@ Dieses Dokument listet sensible Daten, deren Ablageorte und die vorgesehene Einb
|
||||
|---|---|---|---|
|
||||
| Vaultwarden | `ADMIN_TOKEN` | `/mnt/user/appdata/secrets/vaultwarden_admin_token.txt` -> `ADMIN_TOKEN_FILE` | aktiv |
|
||||
| Traefik | Cloudflare DNS API Token | `/mnt/user/appdata/traefik/secrets/cloudflare_dns_api_token` -> Docker Secret `cloudflare_dns_api_token` | aktiv |
|
||||
| PostgreSQL 17 | DB Password | `/mnt/user/appdata/secrets/postgres_password.txt` -> `POSTGRES_PASSWORD_FILE` | aktiv |
|
||||
| PostgreSQL 18 | DB Password | `/mnt/user/appdata/secrets/postgres_password.txt` -> `POSTGRES_PASSWORD_FILE` | aktiv |
|
||||
| Redis | Passwort | `/mnt/user/appdata/secrets/redis_password.txt` -> Datei-Mount + Startkommando in `infra/redis/docker-compose.yml` | aktiv |
|
||||
| Mealie | DB Password | `/mnt/user/appdata/secrets/mealie_postgres_password.txt` -> nicht versionierte Stack-`.env` `${MEALIE_POSTGRES_PASSWORD}` -> `POSTGRES_PASSWORD` | aktiv |
|
||||
| mealie-postgres | DB Password | `/mnt/user/appdata/secrets/mealie_postgres_password.txt` -> `POSTGRES_PASSWORD_FILE` | aktiv |
|
||||
@@ -52,6 +52,7 @@ Dieses Dokument listet sensible Daten, deren Ablageorte und die vorgesehene Einb
|
||||
| Monitoring Grafana | Admin Password | `/mnt/user/appdata/secrets/monitoring_grafana_admin_password.txt` -> Docker Secret `/run/secrets/monitoring_grafana_admin_password` -> `GF_SECURITY_ADMIN_PASSWORD__FILE` | aktiv |
|
||||
| Monitoring Grafana -> InfluxDB | Datasource Token | `/mnt/user/appdata/secrets/monitoring_grafana_influxdb_token.txt` -> Docker Secret `/run/secrets/monitoring_grafana_influxdb_token` | aktiv |
|
||||
| Home Assistant -> InfluxDB | HA InfluxDB Token | `/homeassistant/secrets.yaml` -> `influxdb3_homeassistant_token` | geplant |
|
||||
| Renovate Bot | Gitea Service-Account PAT | `/mnt/user/appdata/secrets/renovate_token.txt` -> Host-Datei (chmod 600), gelesen von `ops/renovate/run-renovate.sh` und an Renovate-Container als `RENOVATE_TOKEN` weitergegeben | aktiv nach Operator-Setup (siehe `docs/RENOVATE.md`) |
|
||||
|
||||
---
|
||||
|
||||
@@ -97,14 +98,49 @@ Weitere dokumentierte Secret-Pfade:
|
||||
- `/mnt/user/appdata/secrets/hermes_runner_id_ed25519`
|
||||
- `/mnt/user/appdata/traefik/secrets/cloudflare_dns_api_token`
|
||||
- Borg UI verwaltet Session-Secret, Admin-Login, SSH-Keys und Repo-Credentials in seiner persistenten `/data`-Struktur. Diese Daten liegen nicht im Git, muessen aber gesichert werden.
|
||||
- Die Borg-Repo-Passphrase liegt zusaetzlich als Host-Secret-Datei fuer Restore-Tests und Notfallzugriff vor; der Wert muss ausserhalb des Homelabs analog gesichert werden.
|
||||
- Die Borg-Repo-Passphrase liegt zusaetzlich als Host-Secret-Datei fuer Restore-Tests und Notfallzugriff vor. Der Wert ist laut Operator-Bestaetigung vom 2026-05-26 offline gesichert; Ablageort und Wert werden nicht im Repo dokumentiert.
|
||||
- Gitea verwaltet den GitHub-Push-Mirror-PAT in den Repository-Mirror-Settings. Der Wert wird nicht dokumentiert und nicht in Dateien unter `docs/` oder `core/gitea/` geschrieben.
|
||||
- `paperless-ngx` ist eine bewusste Ausnahme: DB-Passwort und Redis-URL bleiben aktuell als Komodo Stack Environment Variables hinterlegt, um den stabil laufenden Produktionsstand nicht fuer eine reine Secret-Mechanik-Migration zu riskieren.
|
||||
|
||||
---
|
||||
|
||||
## Stack-ENV-only Secrets - Restore-Wege
|
||||
|
||||
Einige Secrets liegen bewusst nur als Komodo Stack Environment Variables vor, weil das Image kein `_FILE` unterstuetzt oder ein laufender stabiler Produktionsstand nicht fuer eine reine Mechanik-Migration geopfert werden soll. Diese Werte existieren **ausschliesslich** an folgenden Stellen:
|
||||
|
||||
1. **Komodo Mongo** (Runtime und Backup-Dump `komodo-mongo.archive.gz` unter `/mnt/user/backups/borg/dumps/latest/`).
|
||||
2. **Vaultwarden** (Operator-Eintrag pro Stack, sofern dort gepflegt).
|
||||
3. **Externe Operator-Notiz** (analoge Sicherung, vergleichbar mit der Borg-Passphrase).
|
||||
|
||||
**Bei Komodo-Restore aus kaltem Zustand wird immer in dieser Reihenfolge gesucht.** Konkrete Werte werden im Repo, in Logs, in Doku-Kommentaren und in ntfy-Meldungen niemals wiedergegeben.
|
||||
|
||||
### Stacks und ihre Stack-ENV-only Secrets
|
||||
|
||||
| Stack | Stack-ENV-Variablen | Restore-Quelle (Reihenfolge) | Folgen bei Verlust aller Quellen |
|
||||
|---|---|---|---|
|
||||
| `paperless-ngx` | `PAPERLESS_DBPASS`, `PAPERLESS_REDIS` | Komodo-Mongo-Dump -> Vaultwarden -> externe Notiz | App-DB ist im Postgres-Cluster, Passwort muss in Postgres und Stack-ENV synchron neu gesetzt werden; Redis-URL ist deterministisch rekonstruierbar (Host, Port, Passwort), wenn Redis-Passwort-Datei vorliegt |
|
||||
| `immich-server` | `IMMICH_DB_PASSWORD` | Komodo-Mongo-Dump -> Vaultwarden -> externe Notiz | analog Paperless: Postgres-User-Passwort in `immich_postgres` und Stack-ENV gemeinsam zuruecksetzen |
|
||||
| `mail-archiver` | `MAILARCHIVER_DB_CONNECTION`, `MAILARCHIVER_AUTH_PASSWORD` | Komodo-Mongo-Dump -> Vaultwarden -> externe Notiz | DB-Connection-String enthaelt Postgres-Pass; App-Auth-Password fuer Web-UI |
|
||||
| `speedtest-tracker` | `APP_KEY`, `ADMIN_PASSWORD` | Komodo-Mongo-Dump -> Vaultwarden -> externe Notiz | `APP_KEY` ist verschluesselungsrelevant; bei echtem Verlust App-State frisch initialisieren |
|
||||
| `komodo-core` | `KOMODO_SECRET_KEY`, `KOMODO_WEBHOOK_SECRET`, `KOMODO_JWT_SECRET`, `KOMODO_MONGO_PASSWORD`, `KOMODO_PERIPHERY_PASSKEY` | Vaultwarden -> externe Notiz (Henne-Ei: Komodo-Mongo-Dump ist hier **nicht** Restore-Quelle, weil Komodo dafuer schon laufen muesste) | siehe `docs/SERVICES_RECOVERY.md` Komodo-Bootstrap; ohne diese Werte ist der Self-Stack nicht reproduzierbar |
|
||||
| `hermes-agent` | `HERMES_DASHBOARD_HOST` plus Provider-/API-/Home-Assistant-Tokens in Host-`.env` | Vaultwarden -> externe Notiz | Stack ist aktuell geparkt (Review 2026-07-25); ohne Werte bleibt der Stack deaktiviert, kein Schaden am Rest |
|
||||
| `glance` | `GLANCE_IMMICH_API_KEY`, `GLANCE_ADGUARD_USERNAME`, `GLANCE_ADGUARD_PASSWORD`, `GLANCE_SPEEDTEST_API_KEY` | Provider-UIs (Immich, AdGuard, Speedtest-Tracker) neu erzeugen | rebuildbar; Widgets bleiben leer bis Tokens neu erzeugt sind, kein kritischer Datentopf |
|
||||
|
||||
### Komodo-Sonderfall
|
||||
|
||||
Komodos eigene Secrets (`KOMODO_*`) sind die kritischste Untermenge dieser Liste, weil sie nicht aus dem eigenen Mongo-Dump rekonstruierbar sind, solange Komodo nicht laeuft. Sie gehoeren entweder
|
||||
|
||||
- in Vaultwarden (sobald Vaultwarden produktiv ist) **und**
|
||||
- in eine analoge Operator-Notiz neben der Borg-Passphrase.
|
||||
|
||||
Details und Bootstrap-Reihenfolge stehen in `docs/SERVICES_RECOVERY.md` und werden in `docs/DISASTER_RECOVERY.md` Abschnitt 6.2.1 als Pflicht-Pruefung vor Phase 4 Stufe 4 referenziert.
|
||||
|
||||
---
|
||||
|
||||
## Regel
|
||||
|
||||
Wenn `_FILE` nicht unterstuetzt wird -> Stack Environment Variable in Komodo verwenden.
|
||||
|
||||
Secrets niemals direkt in die Compose-Datei schreiben.
|
||||
|
||||
Stack-ENV-Werte niemals im Repo, in Logs oder in Doku-Kommentaren ablegen — nur Variablennamen und Restore-Quellen.
|
||||
|
||||
+132
-21
@@ -1,7 +1,7 @@
|
||||
# Services Recovery - KalliLab CORE
|
||||
|
||||
Status: Initiale Recovery-Baseline 2026-05-26, aus dem Audit 2026-05-25 abgeleitet.
|
||||
Verwandte Docs: `docs/DISASTER_RECOVERY.md`, `docs/RESTORE_MATRIX.md`, `docs/STORAGE_LAYOUT.draft.md`, `docs/SECRETS_MAP.md`
|
||||
Verwandte Docs: `docs/DISASTER_RECOVERY.md`, `docs/RESTORE_MATRIX.md`, `docs/STORAGE_LAYOUT.md`, `docs/SECRETS_MAP.md`
|
||||
|
||||
## Zweck
|
||||
|
||||
@@ -31,11 +31,13 @@ Optionen:
|
||||
|
||||
Empfohlener Start:
|
||||
|
||||
1. `git bundle`-Job fuer alle Gitea-Repositories definieren.
|
||||
2. Ziel auf zweitem physischen Medium oder separatem Off-site-Ziel ablegen.
|
||||
3. Job alle 6 Stunden ausfuehren.
|
||||
1. `ops/borg-ui/scripts/gitea-bundle-mirror.sh` auf dem Host ausfuehren.
|
||||
2. Ziel `/mnt/user/backups/git-bundles/gitea` in Borg/off-site Scope aufnehmen.
|
||||
3. Job alle 6 Stunden oder mindestens vor Borg ausfuehren.
|
||||
4. Stichprobe: ein Bundle in Wegwerfpfad klonen.
|
||||
|
||||
Erstlauf 2026-05-26: 4 Gitea-Bundles erzeugt, Checksums OK, `homelab-infra.bundle` in Restore-Lab geklont und `git fsck` sauber. Offen bleibt die dauerhafte Host-Zeitplanung.
|
||||
|
||||
Erfolgskriterium:
|
||||
|
||||
```bash
|
||||
@@ -45,30 +47,137 @@ git -C /tmp/repo-restore-test fsck
|
||||
|
||||
## Komodo Bootstrap
|
||||
|
||||
Problem: Komodo verwaltet Stacks, ist aber selbst Teil des Recovery-Pfads. Ein kalter Host darf nicht voraussetzen, dass Komodo schon laeuft.
|
||||
### Problemstellung
|
||||
|
||||
Komodo ist deshalb bewusst kein normaler Auto-Deploy-Stack: der `komodo`-Self-Stack hat keinen aktiven Gitea-Webhook. Recovery und Aenderungen laufen ueber den dokumentierten Bootstrap-Pfad und muessen nach dem Start in Komodo validiert werden.
|
||||
Komodo verwaltet alle Stacks per GitOps, ist aber selbst Teil des Recovery-Pfads. Ein kalter Host darf **nicht** voraussetzen, dass Komodo schon laeuft. Das ist das klassische Henne-Ei-Problem: Komodo darf sich nicht selbst aus dem Repo holen muessen, bevor es laufen kann.
|
||||
|
||||
Minimaler Wiederanlauf:
|
||||
### Recovery-Anker (verbindlich)
|
||||
|
||||
1. Docker und externe Netze herstellen (`frontend_net`, `backend_net`, ggf. weitere dokumentierte Netze).
|
||||
2. Repo aus Gitea/GitHub Mirror klonen.
|
||||
3. Komodo Compose aus `ops/komodo/docker-compose.yml` oder einem spaeteren Bootstrap-Pfad starten.
|
||||
4. Erforderliche `.env`/Secrets aus Host-Secret-Backup wiederherstellen.
|
||||
5. Komodo-Core, Periphery und Mongo starten.
|
||||
6. Web-UI und Periphery-Verbindung pruefen.
|
||||
**Anker:** `ops/komodo/docker-compose.yml` aus dem Repo.
|
||||
|
||||
Offene Aufgabe:
|
||||
Dieses Compose-File ist die einzige Quelle, aus der Komodo nach einem Kaltstart hochgefahren wird. Es wird nicht ueber Komodos eigenen Auto-Deploy-Pfad konsumiert.
|
||||
|
||||
- Entscheidung 2026-05-26: `ops/komodo/docker-compose.yml` bleibt die verbindliche Bootstrap-Quelle. Der `komodo`-Self-Stack hat keinen aktiven Gitea-Webhook und ist nicht der Recovery-Anker.
|
||||
- Offen bleibt nur ein spaeterer Trockenlauf, bei dem die Komodo-Startkommandos gegen echte Restore-Pfade getestet werden.
|
||||
**Was der Anker bewusst NICHT ist:**
|
||||
|
||||
Validierung:
|
||||
- nicht der Komodo-Self-Stack (Komodo darf sich nicht selbst deployen).
|
||||
- nicht der laufende Komodo-Workspace unter `/mnt/user/services/stacks/komodo/compose.yaml` (kann driften, siehe `HOMELAB_ARCHITECTURE_MASTER_V2.md` Sektion 13, Drift-Recovery 2026-05-04).
|
||||
- nicht ein Gitea-Webhook (`komodo`-Stack hat bewusst `webhook_enabled: false`).
|
||||
|
||||
**Quelle der Compose-Datei:**
|
||||
|
||||
1. Vorzug: lokaler Repo-Clone auf dem Operator-Windows-PC (`G:\Gitea_Clone\homelab-infra\`).
|
||||
2. Fallback: GitHub-Mirror `michaelkaleschke-spec/homelab-infra` (siehe `docs/EXTERNAL_DEPENDENCIES.md`).
|
||||
3. Letzter Fallback: Gitea-Bundles unter `/mnt/user/backups/git-bundles/gitea/homelab-infra.bundle` (siehe Mirror-Abschnitt oben).
|
||||
|
||||
Wenn alle drei Quellen down sind, ist Recovery blockiert und das Problem ist nicht Komodo, sondern Repo-Verlust.
|
||||
|
||||
### Kaltstart-Schritte
|
||||
|
||||
Der Wiederanlauf-Pfad ist linear; jeder Schritt hat ein eindeutiges Erfolgskriterium, bevor der naechste laeuft.
|
||||
|
||||
**Stufe A - Host und Docker-Grundlage**
|
||||
|
||||
1. Unraid bootet; Array ist online; Shares `/mnt/user/appdata`, `/mnt/user/services`, `/mnt/user/backups` sichtbar.
|
||||
2. Docker-Daemon laeuft (`docker info` antwortet).
|
||||
3. Externe Docker-Netze existieren oder werden erzeugt (`frontend_net`, `backend_net`). Wenn nicht vorhanden: `docker network create --driver bridge frontend_net` bzw. `... --internal backend_net`.
|
||||
|
||||
Erfolgskriterium: `docker network ls` zeigt `frontend_net` und `backend_net`.
|
||||
|
||||
**Stufe B - Repo-Quelle bereitstellen**
|
||||
|
||||
1. Repo-Clone aus dem bevorzugten Pfad bereithalten:
|
||||
- lokaler Operator-Clone, oder
|
||||
- frischer Clone aus GitHub-Mirror, oder
|
||||
- Bundle-Restore aus `/mnt/user/backups/git-bundles/gitea/homelab-infra.bundle` (`git clone homelab-infra.bundle homelab-infra`).
|
||||
2. Repo-Stand verifizieren: `git -C <pfad> log --oneline -1` zeigt einen plausibel aktuellen Commit.
|
||||
|
||||
Erfolgskriterium: `ops/komodo/docker-compose.yml` ist auf dem Host lesbar.
|
||||
|
||||
**Stufe C - Komodo-Secrets bereitstellen**
|
||||
|
||||
Komodo braucht beim Start mehrere Secrets, die **nicht** aus dem Repo kommen. Restore-Reihenfolge gemaess `docs/SECRETS_MAP.md`:
|
||||
|
||||
1. Host-Secrets unter `/mnt/user/appdata/secrets/` wiederherstellen (aus Borg oder analog gesicherter Quelle).
|
||||
2. Datei `/mnt/user/appdata/secrets/komodo_mongo_password.txt` ist Pflicht (Mongo-Initialisierung).
|
||||
3. Stack-ENV-Werte `KOMODO_SECRET_KEY`, `KOMODO_WEBHOOK_SECRET`, `KOMODO_JWT_SECRET`, `KOMODO_MONGO_PASSWORD`, `KOMODO_PERIPHERY_PASSKEY` muessen als Host-`.env` neben dem Compose vorliegen. Quelle in dieser Reihenfolge: Vaultwarden (sobald restauriert), externe Operator-Notiz, oder Komodo-Mongo-Dump (nur wenn Mongo separat bereits gestartet und die `stack`-Collection lesbar ist).
|
||||
|
||||
Erfolgskriterium: Compose-Validierung laeuft ohne fehlende Variablen.
|
||||
|
||||
```bash
|
||||
docker compose -f ops/komodo/docker-compose.yml config >/dev/null
|
||||
```
|
||||
|
||||
**Stufe D - Komodo starten**
|
||||
|
||||
1. Compose hochfahren:
|
||||
|
||||
```bash
|
||||
docker compose -f ops/komodo/docker-compose.yml config
|
||||
docker compose -f ops/komodo/docker-compose.yml up -d
|
||||
```
|
||||
|
||||
2. Reihenfolge intern: `komodo-mongo` zuerst healthy, dann `komodo-core`, dann `komodo-periphery`.
|
||||
3. Status pruefen:
|
||||
|
||||
```bash
|
||||
docker ps --filter "name=komodo"
|
||||
docker logs --tail 50 komodo-core
|
||||
docker logs --tail 50 komodo-periphery
|
||||
```
|
||||
|
||||
Erfolgskriterium: alle drei Container laufen; Komodo-Core meldet Bind auf Port `9120`; Periphery meldet erfolgreiche Verbindung zu Core.
|
||||
|
||||
**Stufe E - Web-UI und GitOps validieren**
|
||||
|
||||
1. `https://komodo.kaleschke.info` ist erreichbar (Authelia-Bypass dokumentiert, native Komodo-Auth aktiv).
|
||||
2. Komodo zeigt im Web-UI die bekannten Stacks aus Gitea (sobald Gitea ebenfalls laeuft; siehe `docs/DISASTER_RECOVERY.md` Phase 4 Stufe 2 vor Stufe 3).
|
||||
3. Gitea-Webhooks gegen Komodo werden separat in der Phase-4-Reihenfolge geprueft, **nicht** als Teil des Komodo-Bootstraps.
|
||||
|
||||
Erfolgskriterium: Komodo-UI laedt, Periphery `Online`, mindestens ein Stack aus Gitea sichtbar.
|
||||
|
||||
**Stufe F - Stacks in Tier-Reihenfolge aufnehmen**
|
||||
|
||||
Erst nach erfolgreichem Komodo-Bootstrap werden produktive Stacks ueber den dokumentierten Stufenpfad in `docs/DISASTER_RECOVERY.md` Phase 4 hochgefahren (Traefik, AdGuard, Tailscale, dann PostgreSQL, Authelia, Redis, Gitea, dann Apps).
|
||||
|
||||
### Trockenlauf (als Repo-Skript, bestaetigt)
|
||||
|
||||
Trockenlauf gegen Wegwerf-Pfade ist seit 2026-05-29 als Repo-Skript abgelegt: `ops/restore-tests/komodo-bootstrap-{compose.test.yml,test.sh,plan.md,runbook.md}`. Aufruf:
|
||||
|
||||
```bash
|
||||
bash /mnt/user/services/homelab-infra/ops/restore-tests/komodo-bootstrap-test.sh --what-if # nur Plan
|
||||
bash /mnt/user/services/homelab-infra/ops/restore-tests/komodo-bootstrap-test.sh --keep-data # echter Lauf
|
||||
```
|
||||
|
||||
Erstlauf 2026-05-30 erfolgreich: `SUCCESS`, alle 5 Checks gruen (compose config valid, Mongo healthy, Mongo authenticated ping ok, Komodo Core HTTP `200`, Test-Periphery `running`). Report unter `/mnt/user/backups/restore-reports/komodo-bootstrap-2026-05-30.md`. Produktive Komodo-Container, Mongo-Datadir und Secrets wurden nicht beruehrt.
|
||||
|
||||
Test-Isolation:
|
||||
|
||||
| Bereich | Wegwerf-Wert |
|
||||
|---|---|
|
||||
| Compose-Project | `restoretest-komodo` (isoliert von Produktions-Project `komodo`) |
|
||||
| Test-Mongo-Datadir | `/mnt/user/backups/restore-lab/komodo/mongo` |
|
||||
| Test-Port | `127.0.0.1:19120` (kein LAN, kein Traefik) |
|
||||
| Test-Periphery | ohne `docker.sock`-Mount, ohne `/mnt/user/services`-Mount |
|
||||
| `KOMODO_*`-Secrets | Wegwerf-Werte im Test-Compose, niemals produktive Werte |
|
||||
|
||||
Damit ist `ops/komodo/docker-compose.yml` als Recovery-Anker fuer Stufen A-F **belegt** tauglich, nicht nur angenommen tauglich.
|
||||
|
||||
### Validierungs-Kommandos (Snapshot)
|
||||
|
||||
```bash
|
||||
# Compose syntaktisch ok?
|
||||
docker compose -f ops/komodo/docker-compose.yml config >/dev/null
|
||||
|
||||
# Komodo-Container vorhanden und laufend?
|
||||
docker ps --filter "name=komodo" --format "table {{.Names}}\t{{.Status}}"
|
||||
|
||||
# Mongo Health?
|
||||
docker exec komodo-mongo mongosh --quiet --eval 'db.adminCommand({ping:1}).ok'
|
||||
|
||||
# Core API up?
|
||||
docker exec komodo-core sh -lc 'wget -q -O- http://127.0.0.1:9120/api/health || true'
|
||||
|
||||
# Periphery sichtbar?
|
||||
docker logs --tail 50 komodo-periphery 2>&1 | grep -i "connected\|periphery"
|
||||
```
|
||||
|
||||
## Secrets Recovery Reihenfolge
|
||||
@@ -88,13 +197,15 @@ Authoritativ ist `docs/SECRETS_MAP.md`. Fuer den Kaltstart ist diese Reihenfolge
|
||||
- Keine Secret-Werte in Git oder Tickets kopieren.
|
||||
- Restore-Tests laufen in Wegwerfpfaden, nie direkt gegen produktive Pfade.
|
||||
- Wenn Gitea und Komodo beide down sind, gewinnt der externe GitHub-Mirror als Repo-Quelle.
|
||||
- Wenn Borg ohne Passphrase nicht entschluesselbar ist, ist Recovery blockiert. Deshalb ist die analoge Passphrase-Sicherung P0.
|
||||
- Wenn Borg ohne Passphrase nicht entschluesselbar ist, ist Recovery blockiert. Die Offline-Sicherung wurde am 2026-05-26 vom Operator bestaetigt; bei Reviews nur pruefen, dass sie weiterhin auffindbar und lesbar ist.
|
||||
|
||||
## Naechste Aufgaben
|
||||
|
||||
| Status | Aufgabe |
|
||||
|---|---|
|
||||
| offen | Gitea-Bundle- oder Mirror-Mechanik final entscheiden |
|
||||
| erledigt (Skript + Host-Test) | Gitea-Bundle- oder Mirror-Mechanik final entscheiden |
|
||||
| erledigt | Komodo-Bootstrap-Quelle finalisieren |
|
||||
| offen | Restore-Kommandos nach erstem Trockenlauf mit echten Pfaden ergaenzen |
|
||||
| erledigt (Doku) | Komodo-Kaltstart in linearen Stufen A-F dokumentieren |
|
||||
| erledigt 2026-05-29 | Komodo-Trockenlauf-Skript in `ops/restore-tests/` analog zu Immich vorbereiten |
|
||||
| erledigt 2026-05-30 | Restore-Kommandos nach erstem Trockenlauf mit echten Pfaden ergaenzen |
|
||||
| erledigt | Services-Recovery in `docs/DISASTER_RECOVERY.md` verlinken |
|
||||
|
||||
+16
-16
@@ -1,6 +1,6 @@
|
||||
# Service Catalog
|
||||
|
||||
Stand: 2026-05-23
|
||||
Stand: 2026-05-31
|
||||
|
||||
Dieser Katalog beschreibt produktive und repo-vorbereitete Dienste aus Sicht von Betrieb, Restore und KI-Kontext. Er basiert auf dem Repo-Sollzustand. Vor produktiven Eingriffen immer den Live-Zustand in Komodo/Docker pruefen.
|
||||
|
||||
@@ -20,36 +20,36 @@ Secret-Werte sind nicht enthalten. Es werden nur Secret-Namen, Env-Key-Namen und
|
||||
|
||||
| Service | Zweck | Autoritativer Pfad | URL / Zugang | Abhaengigkeiten | Datenpfade | Backup / Restore | Traefik | Besonderheiten / TODOs |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| `authelia` | ForwardAuth / zentrale Auth fuer Admin-UIs | `security/authelia/docker-compose.yml`, `security/authelia/configuration.yml` | `https://auth.kaleschke.info` | PostgreSQL 17, Traefik, GMX SMTP | `/mnt/user/appdata/authelia/config`, Authelia Secret-Dateien | Tier 1, config + DB + secrets | ja | Bewusst ohne Redis-Session-Backend; SMTP-Notifier via GMX und `authelia_smtp_password.txt`; explizite DNS-Server fuer SMTP/NTP; Repo-Baseline muss manuell in die Host-Config gemerged werden, OIDC/Secrets bleiben hostseitig; Access-Control und Compose-Middleware bei Aenderungen abgleichen |
|
||||
| `authelia` | ForwardAuth / zentrale Auth fuer Admin-UIs | `security/authelia/docker-compose.yml`, `security/authelia/configuration.yml` | `https://auth.kaleschke.info` | PostgreSQL 18, Traefik, GMX SMTP | `/mnt/user/appdata/authelia/config`, Authelia Secret-Dateien | Tier 1, config + DB + secrets | ja | Bewusst ohne Redis-Session-Backend; SMTP-Notifier via GMX und `authelia_smtp_password.txt`; explizite DNS-Server fuer SMTP/NTP; Repo-Baseline muss manuell in die Host-Config gemerged werden, OIDC/Secrets bleiben hostseitig; Access-Control und Compose-Middleware bei Aenderungen abgleichen |
|
||||
| `vaultwarden` | Passwort-Tresor | `security/vaultwarden/docker-compose.yml` | `https://vault.kaleschke.info` | Traefik, `frontend_net` | `/mnt/user/appdata/vaultwarden` | Tier 1, `vaultwarden.sqlite.dump` + Share | ja | `ADMIN_TOKEN_FILE`; keine direkten Ports |
|
||||
|
||||
## Shared Infrastructure
|
||||
|
||||
| Service | Zweck | Autoritativer Pfad | URL / Zugang | Abhaengigkeiten | Datenpfade | Backup / Restore | Traefik | Besonderheiten / TODOs |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| `postgresql17` | shared PostgreSQL Cluster | `infra/postgresql17/docker-compose.yml` | intern | `backend_net` | `/mnt/user/appdata/postgresql17`, `postgres_password.txt` | Tier 1; Dumps unter `/mnt/user/backups/borg/dumps/latest` | nein | keine Host-Ports; raw DB nicht primaerer Restore-Weg |
|
||||
| `Redis` | shared Redis Cache | `infra/redis/docker-compose.yml` | intern | `backend_net` | `/mnt/user/appdata/redis`, `redis_password.txt` | transiente Daten, bewusst nicht kritisch | nein | Passwort-Datei; optional named volume offen |
|
||||
| `postgresql17` | shared PostgreSQL 18 Cluster (historischer Service-Name bleibt fuer DNS/Clients stabil) | `infra/postgresql17/docker-compose.yml` | intern | `backend_net` | `/mnt/user/appdata/postgresql18`, Rollback-Altstand `/mnt/user/appdata/postgresql17`, `postgres_password.txt` | Tier 1; Dumps unter `/mnt/user/backups/borg/dumps/latest` | nein | keine Host-Ports; raw DB nicht primaerer Restore-Weg |
|
||||
| `Redis` | primaer Paperless-Redis (App-Cache); historisch als "shared" angelegt, faktisch nur von Paperless genutzt | `infra/redis/docker-compose.yml` | intern | `backend_net` | `/mnt/user/appdata/redis`, `redis_password.txt` | transiente Daten, bewusst nicht kritisch | nein | Redis 8.8; Passwort-Datei; optional named volume offen. Immich, Nextcloud und Mealie nutzen jeweils eigene Redis-Instanzen; Authelia laeuft bewusst ohne Redis-Session-Backend. Bei Wegfall ist Paperless der einzige betroffene Stack. |
|
||||
| `ddns-updater` | Cloudflare/DDNS Aktualisierung | `infra/ddns-updater/docker-compose.yml` | intern | Internetzugang, `frontend_net` | `/mnt/user/appdata/ddns-updater` | rebuildbar | nein | bleibt bewusst in `frontend_net`, weil `backend_net` internal ist |
|
||||
|
||||
## Public / User Apps
|
||||
|
||||
| Service | Zweck | Autoritativer Pfad | URL / Zugang | Abhaengigkeiten | Datenpfade | Backup / Restore | Traefik | Besonderheiten / TODOs |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| `paperless-ngx` | Dokumentenmanagement | `apps/paperless/docker-compose.yml` | `https://paperless.kaleschke.info` | PostgreSQL 17, Redis, Traefik | `/mnt/user/appdata/paperless-ngx/data`, `/mnt/user/documents/paperless`, `/mnt/user/documents/scans_inbox` | Tier 2, Borg + `postgresql17-paperless.dump` | ja | DB/Redis Secrets bleiben bewusst Stack ENV |
|
||||
| `paperless-gpt` | KI-Ergaenzung fuer Paperless | `apps/paperless-gpt/docker-compose.yml` | `https://paperless-gpt.kaleschke.info` | Paperless API, LLM/Ollama, Traefik | `/mnt/user/appdata/paperless-gpt/data`, `/mnt/user/appdata/paperless-gpt/prompts` | Tier 2 | ja + Authelia | API Token als Stack ENV; OCR/LLM-Konfig bei Aenderungen pruefen |
|
||||
| `paperless-ngx` | Dokumentenmanagement | `apps/paperless/docker-compose.yml` | `https://paperless.kaleschke.info` | PostgreSQL 18, Redis 8, Traefik | `/mnt/user/appdata/paperless-ngx/data`, `/mnt/user/documents/paperless`, `/mnt/user/documents/scans_inbox` | Tier 2, Borg + `postgresql17-paperless.dump` | ja | DB/Redis Secrets bleiben bewusst Stack ENV; Dump-Dateiname behaelt den historischen Cluster-Namen |
|
||||
| `paperless-gpt` | KI-Ergaenzung fuer Paperless | `apps/paperless-gpt/docker-compose.yml` | `https://paperless-gpt.kaleschke.info` | Paperless API, LLM/Ollama, Traefik | `/mnt/user/appdata/paperless-gpt/data`, `/mnt/user/appdata/paperless-gpt/prompts` | Tier 2 | ja + Authelia | API Token als Stack ENV; OCR/LLM-Konfig bei Aenderungen pruefen. **Behalten-Entscheidung 2026-05-28:** Container bleibt aktiv, auch wenn aktuell keine Traefik-Zugriffe in der Woche; Ablouseplanung erst mit Paperless-NGX 3.0 (eigene KI-Features erwartet) - dann neu bewerten. |
|
||||
| `immich_server` | Foto-/Video-App | `apps/immich/docker-compose.yml` | `https://immich.kaleschke.info` | Immich Postgres, Immich Redis, ML, Traefik | `/mnt/user/photos/immich`, `/mnt/user/photos/family_archive` | Tier 2, Borg + `immich.dump` | ja | native App-Auth; externes Fotoarchiv gemountet |
|
||||
| `immich_postgres` | Immich-Datenbank | `apps/immich/docker-compose.yml` | intern | `immich_default` | `/mnt/user/appdata/immich_postgres`, `immich_postgres_password.txt` | Dump `immich.dump` | nein | nie ins `frontend_net` |
|
||||
| `immich_redis` | Immich Cache | `apps/immich/docker-compose.yml` | intern | `immich_default` | kein kritischer Pfad dokumentiert | rebuildbar | nein | Architektur nennt anonymes Volume -> named volume als offenes Thema |
|
||||
| `immich_postgres` | Immich-Datenbank | `apps/immich/docker-compose.yml` | intern | `immich_default` | `/mnt/user/appdata/immich_postgres_vectorchord`, Rollback-Altstand `/mnt/user/appdata/immich_postgres`, `immich_postgres_password.txt` | Dump `immich.dump`; Restore braucht ein Image mit VectorChord/pgvector | nein | PG14 bleibt bewusst; Immich-DB-Image `ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0`; nie ins `frontend_net` |
|
||||
| `immich_redis` | Immich Cache | `apps/immich/docker-compose.yml` | intern | `immich_default` | kein kritischer Pfad dokumentiert | rebuildbar | nein | Redis 8.8; Architektur nennt anonymes Volume -> named volume als offenes Thema |
|
||||
| `immich_machine_learning` | Immich ML | `apps/immich/docker-compose.yml` | intern | `immich_default` | `model-cache` | rebuildbar | nein | intern-only |
|
||||
| `mealie` | Rezeptverwaltung | `apps/mealie/docker-compose.yml` | `https://mealie.kaleschke.info` | `mealie-postgres`, Traefik | `/mnt/user/appdata/mealie/data` | Tier 2, Borg + `mealie.dump` | ja | App + DB in internem Netz getrennt |
|
||||
| `mealie-postgres` | Mealie-Datenbank | `apps/mealie/docker-compose.yml` | intern | `mealie_internal` | `/mnt/user/appdata/mealie/postgres`, `mealie_postgres_password.txt` | Dump `mealie.dump` | nein | interne DB |
|
||||
| `mail-archiver` | Mail-Archivierung | `apps/mail-archiver/docker-compose.yml` | `https://mail.kaleschke.info` | PostgreSQL 17, Internet/IMAP, Traefik, Authelia | `/mnt/user/appdata/mailarchiver/data-protection-keys` | Tier 2, `postgresql17-mailarchiver.dump` | ja + Authelia | Hybrid-Dienst: `frontend_net` fuer Internet, `backend_net` fuer DB; App-eigene Auth bleibt zusaetzliche Schutzschicht |
|
||||
| `mealie-postgres` | Mealie-Datenbank | `apps/mealie/docker-compose.yml` | intern | `mealie_internal` | `/mnt/user/appdata/mealie/postgres18`, Rollback-Altstand `/mnt/user/appdata/mealie/postgres`, `mealie_postgres_password.txt` | Dump `mealie.dump` | nein | interne DB; PostgreSQL 18 |
|
||||
| `mail-archiver` | Mail-Archivierung | `apps/mail-archiver/docker-compose.yml` | `https://mail.kaleschke.info` | PostgreSQL 18, Internet/IMAP, Traefik, Authelia | `/mnt/user/appdata/mailarchiver/data-protection-keys` | Tier 2, `postgresql17-mailarchiver.dump` | ja + Authelia | Hybrid-Dienst: `frontend_net` fuer Internet, `backend_net` fuer DB; App-eigene Auth bleibt zusaetzliche Schutzschicht; Dump-Dateiname behaelt den historischen Cluster-Namen |
|
||||
| `nextcloud` | Datei-/Cloud-Dienst | `apps/nextcloud/docker-compose.yml` | `https://cloud.kaleschke.info` | eigene PostgreSQL, eigene Redis, Traefik | `/mnt/user/appdata/nextcloud/html`, `/mnt/user/documents/nextcloud-data` | Tier 2, `nextcloud.dump` + Share | ja | native App-Auth ohne zentrale ForwardAuth; WebDAV/CardDAV beachten |
|
||||
| `nextcloud-postgres` | Nextcloud-Datenbank | `apps/nextcloud/docker-compose.yml` | intern | `nextcloud_internal` | `/mnt/user/appdata/nextcloud/postgres`, `nextcloud_postgres_password.txt` | `nextcloud.dump`, raw DB nicht primaerer Restore-Weg | nein | interne DB |
|
||||
| `nextcloud-redis` | Nextcloud Cache/Locking | `apps/nextcloud/docker-compose.yml` | intern | `nextcloud_internal` | `/mnt/user/appdata/nextcloud/redis` | Teil von Nextcloud-Restore | nein | interne Redis |
|
||||
| `plex` | Medienserver mit LAN-/Client-Discovery | `host-services/plex/docker-compose.yml` | Plex native / LAN / Remote je Plex-Konfiguration | Host-Netz | `/mnt/user/appdata/plex/config`, `/mnt/user/appdata/plex/transcode`, `/mnt/user/media`, `/mnt/user/photos` | Tier 2, Appdata + Medienpfade im Borg-/Share-Scope | nein | Repo-Compose-Stack; `network_mode: host` bleibt dokumentierte Discovery-Ausnahme, kein Traefik-Stack |
|
||||
| `nextcloud-postgres` | Nextcloud-Datenbank | `apps/nextcloud/docker-compose.yml` | intern | `nextcloud_internal` | `/mnt/user/appdata/nextcloud/postgres18`, Rollback-Altstand `/mnt/user/appdata/nextcloud/postgres`, `nextcloud_postgres_password.txt` | `nextcloud.dump`, raw DB nicht primaerer Restore-Weg | nein | interne DB; PostgreSQL 18 |
|
||||
| `nextcloud-redis` | Nextcloud Cache/Locking | `apps/nextcloud/docker-compose.yml` | intern | `nextcloud_internal` | `/mnt/user/appdata/nextcloud/redis` | Teil von Nextcloud-Restore | nein | interne Redis 8.8 |
|
||||
| `plex` | Medienserver mit LAN-/Client-Discovery | `host-services/plex/docker-compose.yml` | Plex native, **LAN/Tailscale-only**, Remote Access deaktiviert | Host-Netz | `/mnt/user/appdata/plex/config`, `/mnt/user/appdata/plex/transcode`, `/mnt/user/media`, `/mnt/user/photos` | Tier 2, Appdata + Medienpfade im Borg-/Share-Scope | nein | Repo-Compose-Stack; `network_mode: host` bleibt dokumentierte Discovery-Ausnahme. Server geclaimt von `Xeridos` (Reclaim 2026-05-28 nach Preferences-Reset vom 18.05.). Smart-TVs greifen ueber WLAN-LAN per mDNS/Plex-GDM direkt zu. `PublishServerOnPlexOnlineKey=0` (Remote Access aus), `RelayEnabled` ebenfalls aus. |
|
||||
| `ntfy` | Push-Benachrichtigungen | `apps/ntfy/docker-compose.yml` | `https://ntfy.kaleschke.info` | Traefik, upstream mobile push | `/mnt/user/appdata/ntfy` | Tier 2 | ja | `NTFY_BEHIND_PROXY=true`; Problem-Alerts gehen gebuendelt an `homelab-alerts`, optionale Erfolgsmeldungen an `homelab-info` |
|
||||
| `bentopdf` | PDF-Tooling / Ersatz fuer Stirling-PDF | `apps/bentopdf/docker-compose.yml` | `https://pdf.kaleschke.info` | Traefik + Authelia | keine kritische Persistenz im Compose | Tier 3, rebuildbar | ja + Authelia | COOP/COEP per Middleware; fachliche Abnahme/Live-Status pruefen |
|
||||
| `bentopdf` | PDF-Tooling / Ersatz fuer Stirling-PDF | `apps/bentopdf/docker-compose.yml` | `https://pdf.kaleschke.info` | Traefik + Authelia | keine kritische Persistenz im Compose | Tier 3, rebuildbar | ja + Authelia | COOP/COEP per Middleware. **Behalten-Entscheidung 2026-05-28:** Container bleibt aktiv als situatives Tool, auch wenn aktuell keine Traefik-Zugriffe in der Woche. Resource-Footprint vernachlaessigbar (~4 MB RAM). |
|
||||
|
||||
## Operations / Monitoring / Admin
|
||||
|
||||
@@ -74,7 +74,7 @@ Secret-Werte sind nicht enthalten. Es werden nur Secret-Namen, Env-Key-Namen und
|
||||
| `monitoring-promtail` | Docker-Log-Collector fuer Monitoring-Loki | `monitoring/docker-compose.yml`, `monitoring/promtail/promtail-config.yml` | intern | Docker socket read-only, Docker json-file Logs, Loki | named volume `promtail_positions` | rebuildbar | nein | Dokumentierte Host-Observability-Ausnahme: `/var/run/docker.sock:/var/run/docker.sock:ro` und `/var/lib/docker/containers:ro`; keine Appdaten, nur Log-Discovery |
|
||||
| `monitoring-node-exporter` | Host-Metriken fuer Prometheus | `monitoring/docker-compose.yml` | intern `:9100` | Host `/proc`, `/sys`, `/` read-only, Prometheus | kein kritischer Zustand | rebuildbar | nein | Host-Observability-Ausnahme mit read-only Rootfs/Proc/Sys-Mounts |
|
||||
| `monitoring-cadvisor` | Container-Metriken fuer Prometheus | `monitoring/docker-compose.yml` | intern `:8080` | Docker/Host read-only Mounts, Prometheus | kein kritischer Zustand | rebuildbar | nein | Host-Observability-Ausnahme fuer Container-Metriken; keine direkten Ports |
|
||||
| `monitoring-influxdb3-core` | InfluxDB 3 Core fuer Home-Assistant-/Ecowitt-Langzeitdaten | `monitoring/docker-compose.yml` | LAN `8181` je `INFLUXDB_BIND_IP`, keine Public URL | Monitoring-Grafana, Home Assistant Writer | `/mnt/user/appdata/influxdb3/data`, `/mnt/user/appdata/influxdb3/plugins` | Tier 3 | nein | LAN-only Host-Port-Ausnahme; `user: "0"` ist fuer den lokalen Object-Store-Pfad dokumentiert; uebernimmt den bisherigen InfluxDB-Daten-/Token-Katalog; `401 Unauthorized` beim Curl ohne Token ist erwarteter Reachability-Test |
|
||||
| `monitoring-influxdb3-core` | InfluxDB 3 Core fuer Home-Assistant-/Ecowitt-Langzeitdaten | `monitoring/docker-compose.yml` | Host-Port `8181` je `INFLUXDB_BIND_IP`, keine Public URL | Monitoring-Grafana, Home Assistant Writer | `/mnt/user/appdata/influxdb3/data`, `/mnt/user/appdata/influxdb3/plugins` | Tier 3 | nein | 2026-05-31 effektiv auf `127.0.0.1:8181` gebunden, also nicht LAN-exponiert; `user: "0"` ist fuer den lokalen Object-Store-Pfad dokumentiert; uebernimmt den bisherigen InfluxDB-Daten-/Token-Katalog; `401 Unauthorized` beim Curl ohne Token ist erwarteter Reachability-Test |
|
||||
| `hermes-gateway` | Hermes Agent Gateway/API intern | `ops/hermes-agent/docker-compose.yml` | intern `8642` auf `hermes_net` | SSH Runner (VM 192.168.178.143), LLM Provider, optional Home Assistant | `/mnt/user/appdata/hermes-agent/data`, SSH key path | Tier 3, Borg/Share | nein | NAS-Stack bleibt deaktiviert, solange die separate Hermes-VM/Runner-Seite nicht wiederhergestellt ist; kein Docker-Socket |
|
||||
| `hermes-dashboard` | Hermes Dashboard | `ops/hermes-agent/docker-compose.yml` | `https://hermes.kaleschke.info` via `${HERMES_DASHBOARD_HOST}` | `hermes-gateway`, Traefik + Authelia | shared read-only data mount | Tier 3, Borg/Share | ja + Authelia | Compose-Profil `dashboard`; aktuell VM-seitig offen, nicht Teil des NAS-Finalstarts |
|
||||
|
||||
@@ -82,7 +82,7 @@ Secret-Werte sind nicht enthalten. Es werden nur Secret-Namen, Env-Key-Namen und
|
||||
|
||||
| Service | Zweck | Autoritativer Pfad | URL / Zugang | Abhaengigkeiten | Datenpfade | Backup / Restore | Traefik | Besonderheiten / TODOs |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| `posture-check` | Host-Posture-Audit fuer Filesystem, Mover-Drift, NVMe-SMART und Fuellstand | `services/posture-check/posture-check.sh` | Unraid User-Script / Cron / Borg Pre-Hook | `findmnt`, `df`, `nvme`, optional `curl` fuer ntfy | `/mnt/user/services/posture-check/last.json` | Repo-Skript + letzter JSON-Status | nein | Muss auf dem Unraid-Host bei Boot, stuendlich und vor Borg laufen; Disk1-NTFS ist nach Disk1 Phase 2 nicht mehr erlaubt (`ALLOW_DISK1_NTFS=0` Standard); Warning/Critical alarmieren via ntfy nur bei neuer Ursache oder nach `ALERT_REPEAT_SECONDS` |
|
||||
| `posture-check` | Host-Posture-Audit fuer Filesystem, Mover-Drift, NVMe-SMART, Fuellstand und Authelia-Repo<->Host-Drift | `services/posture-check/posture-check.sh` | Unraid User-Script / Cron / Borg Pre-Hook | `findmnt`, `df`, `nvme`, optional `curl` fuer ntfy; ruft `services/authelia-diff.sh` fuer `authelia_config_drift` auf | `/mnt/user/services/posture-check/last.json` | Repo-Skript + letzter JSON-Status | nein | Muss auf dem Unraid-Host bei Boot, stuendlich und vor Borg laufen; Disk1-NTFS ist nach Disk1 Phase 2 nicht mehr erlaubt (`ALLOW_DISK1_NTFS=0` Standard); Warning/Critical alarmieren via ntfy nur bei neuer Ursache oder nach `ALERT_REPEAT_SECONDS`. Authelia-Drift-Check braucht einen Repo-Spiegel unter `/mnt/user/services/homelab-infra/` (siehe `docs/WORKFLOW.md` Sektion "Ausnahme: Authelia configuration.yml") |
|
||||
| `docker-critical-events` | Live-Alarmierung fuer Docker `die`/`oom`/`kill` Events | `services/posture-check/docker-critical-events.sh` | Unraid User-Script / Hintergrundprozess | Docker CLI, ntfy | `/mnt/user/services/posture-check/docker-critical-events-last.log` | Repo-Skript + letzter Event-Log | nein | Optional als Unraid User-Script `at array start` starten; sendet nach `homelab-alerts` |
|
||||
|
||||
## Backup- und Restore-Hinweise
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
| Feld | Wert |
|
||||
|------|------|
|
||||
| Version | 1.3 |
|
||||
| Status | **Active** — bindend, commit-reif als `docs/STORAGE_LAYOUT.md` |
|
||||
| Datum | 2026-05-15 |
|
||||
| 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) |
|
||||
@@ -38,13 +38,13 @@ Es ist **vor** jeder Storage- oder Compose-Änderung zu lesen. Wenn ein neuer St
|
||||
|
||||
| Slot | Device | Filesystem | Größe | Zweck | Status nach Recovery |
|
||||
|------|--------|------------|-------|-------|----------------------|
|
||||
| Cache (Pool) | Samsung 970 EVO Plus, NVMe | **XFS** | 2 TB | Appdata, system, domains | Reformat von NTFS auf XFS, Phase 1 |
|
||||
| Disk1 (Array) | HDD (Modell TBD) | **XFS** | TBD | Nutzdaten, Backups, Services | NTFS-zu-XFS-Migration Phase 2 abgeschlossen am 2026-05-25 |
|
||||
| Parity | HDD (Modell TBD) | — (keine FS) | TBD | Redundanz für Array | Unverändert |
|
||||
| Boot | USB-Stick | FAT32 | klein | Unraid-OS, Konfiguration | Unverändert, regelmäßig per Flash-Backup gesichert |
|
||||
| Externe Backup-Platte | Wechselplatte (Modell TBD) | XFS oder ext4 | ~8 TB | Ausgelagertes Backup-Ziel, Recovery-Material | NEU, ersetzt WD MyBookLive |
|
||||
| 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` |
|
||||
|
||||
**TBD-Felder werden nachgetragen** sobald `lsblk -o NAME,SIZE,MODEL,SERIAL,VENDOR /dev/sd?` und `smartctl -i` für jeden Slot einmal ausgeführt wurden. Vorschlag: dieser Detection-Lauf ist Teil der Posture-Check-Erstinstallation (§11) und das Ergebnis wird automatisch in dieses Dokument eingetragen. Kein Blocker für die Inkraftsetzung — die Festsetzung „XFS auf Cache, XFS auf Disk1" steht unabhängig von Modell/Größe.
|
||||
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:**
|
||||
|
||||
@@ -378,6 +378,7 @@ Wenn Hermes-Worker auf weiteren Hosts skaliert: dieser Storage-Layout-Plan gilt
|
||||
- `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
|
||||
|
||||
@@ -387,6 +388,7 @@ Wenn Hermes-Worker auf weiteren Hosts skaliert: dieser Storage-Layout-Plan gilt
|
||||
| 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
|
||||
|
||||
@@ -394,13 +396,13 @@ Dieses Dokument tritt in Kraft mit dem Commit der finalen Fassung in `master`-Br
|
||||
|
||||
Erste Audit-Review dieses Dokuments: spätestens 90 Tage nach Inkraftsetzung. Danach jährlich oder bei jeder strukturellen Änderung des Storage-Layouts.
|
||||
|
||||
## 20. Open Review Items (vor finalem Commit zu entscheiden)
|
||||
## 20. Review Items und Folgeaufgaben
|
||||
|
||||
Diese Sektion dokumentiert offene Operator-Entscheidungen und Lücken. **Vor Statuswechsel von Draft auf Active ist jeder Punkt entweder eingearbeitet oder bewusst als „bleibt offen" mit Verweis auf Folge-Issue/-Doc markiert.**
|
||||
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) | **DEFERRED 2026-05-15** — wird automatisch via Posture-Check-Detection-Lauf ergänzt; Disk1-Filesystem ist seit 2026-05-25 XFS | Operator + Posture-Check |
|
||||
| 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) |
|
||||
@@ -410,6 +412,6 @@ Diese Sektion dokumentiert offene Operator-Entscheidungen und Lücken. **Vor Sta
|
||||
| 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 | **ENTSCHIEDEN 2026-05-15**, Implementierungs-Doc offen | Operator + Folge-Doc |
|
||||
| 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 |
|
||||
|
||||
Wenn alle 11 Punkte bearbeitet sind und der Operator die Datei reviewed hat, wird sie als `docs/STORAGE_LAYOUT.md` (ohne `.draft`) committed und Status auf `Active` gesetzt.
|
||||
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.
|
||||
@@ -269,6 +269,42 @@ Diese Ausnahme bleibt bewusst bestehen. Der File-Provider wird weiterhin nur fue
|
||||
|
||||
---
|
||||
|
||||
## Ausnahme: Authelia configuration.yml
|
||||
|
||||
> **Diese Datei wird von Komodo nicht automatisch deployed.**
|
||||
|
||||
`security/authelia/configuration.yml` ist die Repo-Baseline fuer nicht geheime Einstellungen (Access-Control, Session, Storage-Struktur, Notifier, TOTP). Die produktive Host-Datei darf zusaetzlich OIDC-Clients und hostseitige Identity-Provider-Konfiguration enthalten. Secret-Werte und die User-Datenbank bleiben grundsaetzlich ausserhalb von Git.
|
||||
|
||||
| Git-Pfad | Host-Pfad (NAS) |
|
||||
|---|---|
|
||||
| `security/authelia/configuration.yml` | `/mnt/user/appdata/authelia/config/configuration.yml` |
|
||||
|
||||
### Pflicht-Workflow bei Aenderungen an `configuration.yml`
|
||||
|
||||
1. Datei im Git-Repo (`security/authelia/`) aendern.
|
||||
2. Commit + Push.
|
||||
3. Aenderung manuell in die Host-Datei mergen, OIDC-/Identity-Provider-Sektionen erhalten.
|
||||
4. `docker restart authelia` und Login-Smoke-Test auf einer ACL-betroffenen Domain.
|
||||
5. `services/authelia-diff.sh` (Default-Aufruf) muss `exit 0` liefern.
|
||||
|
||||
### Automatische Drift-Erkennung
|
||||
|
||||
`services/authelia-diff.sh` vergleicht die `access_control:`-Sektion zwischen Repo-Baseline und Host-Datei. Der Posture-Check (`services/posture-check/posture-check.sh`) ruft das Skript als Check `authelia_config_drift` auf und meldet Drift als Warning via ntfy.
|
||||
|
||||
Konfigurierbare Variablen (Defaults sind das produktive Zielbild):
|
||||
|
||||
- `AUTHELIA_REPO_BASELINE` — Pfad zur Repo-Datei auf dem Host, Default `/mnt/user/services/homelab-infra/security/authelia/configuration.yml`
|
||||
- `AUTHELIA_HOST_CONFIG` — Pfad zur produktiven Host-Datei, Default `/mnt/user/appdata/authelia/config/configuration.yml`
|
||||
- `AUTHELIA_DIFF_SECTIONS` — Komma-Liste der zu vergleichenden Top-Level-Sektionen, Default `access_control`
|
||||
- `AUTHELIA_DIFF_SCRIPT` — Pfad zum Diff-Skript fuer den Posture-Check, Default `/mnt/user/services/homelab-infra/services/authelia-diff.sh`
|
||||
- `SKIP_AUTHELIA_DRIFT=1` — Check im Posture-Check ueberspringen
|
||||
|
||||
Pflicht-Setup auf dem Host: Repo-Spiegel unter `/mnt/user/services/homelab-infra/` (Read-only-Clone von Gitea `Micha/homelab-infra`, regelmaessig `git pull --ff-only`). Ohne Repo-Spiegel meldet der Check Warning, weil die Baseline-Datei fehlt — Critical wird der Check bewusst nicht.
|
||||
|
||||
> **Merksatz:** Push allein reicht hier nicht. Ohne den manuellen Merge ins Host-Configfile wirkt die Aenderung nicht, und der Drift-Check wuerde Warning melden.
|
||||
|
||||
---
|
||||
|
||||
## Secrets-Regeln
|
||||
|
||||
- Secrets liegen niemals im Repository
|
||||
@@ -311,6 +347,26 @@ dns:
|
||||
|
||||
---
|
||||
|
||||
## Service-Removal-Checkliste
|
||||
|
||||
Wenn ein Stack endgueltig entfernt wird (Beispiele: Homepage am 2026-05-25, Uptime-Kuma am 2026-05-25, Jellyfin am 2026-05-25), muss in **einem** Aenderungsblock auch der gesamte Sicht-/Backup-Pfad nachgezogen werden, sonst entstehen "Tote-Pfad-Warnings", die erst Tage spaeter auftauchen.
|
||||
|
||||
Pflicht-Schritte vor dem Schliessen:
|
||||
|
||||
1. Komodo: Stack stoppen, destroy, Stack-Eintrag loeschen.
|
||||
2. Gitea-Webhook fuer den Stack deaktivieren.
|
||||
3. Repo-Pfad per `git rm` entfernen.
|
||||
4. Appdata nach `/mnt/user/appdata/_archive/<name>-removed-YYYY-MM-DD/` verschieben (14 Tage Karenz).
|
||||
5. DNS-Eintrag im Cloudflare entfernen, sofern Public-Domain.
|
||||
6. Authelia ACL-Eintrag in `security/authelia/configuration.yml` und auf dem Host bereinigen.
|
||||
7. Monitoring: Blackbox-Target in `monitoring/blackbox/blackbox.yml` entfernen, Cert-Check-Liste pruefen.
|
||||
8. **Borg-UI Source-Liste**: `https://borg.kaleschke.info` -> Repository `appdata-critical` -> Source Directories -> alle `/local/appdata/<name>` und ggf. `/local/<name>`-Eintraege loeschen. Sonst kommen daily `HomelabBorgLastJobCompletedWithWarnings`-Push-Nachrichten mit `BackupFileNotFoundError` im Logfile.
|
||||
9. `docs/SERVICE_CATALOG.md`, `docs/REPO_MAP.md`, `HOMELAB_ARCHITECTURE_MASTER_V2.md` Sektion 7.8 (Entfernt), `docs/MIGRATION_LOG.md` nachziehen.
|
||||
|
||||
Wenn ein Stack `webhook_enabled` in Komodo hatte, zusaetzlich pruefen, ob der zugehoerige Gitea-Hook deaktiviert oder geloescht wurde.
|
||||
|
||||
---
|
||||
|
||||
## Dokumentationspflicht
|
||||
|
||||
Nach jeder erfolgreichen Migration oder relevanten Aenderung muessen diese Dateien geprueft werden:
|
||||
|
||||
@@ -77,4 +77,4 @@ Kompakte Quelle fuer einen neuen Chat. Ziel: nicht das ganze Repo neu auditieren
|
||||
|
||||
## Startprompt Fuer Neuen Chat
|
||||
|
||||
Bitte zuerst `docs/AI_HANDOFF_2026-05-06.md` lesen und als aktuelle Arbeitsquelle verwenden. Nicht das ganze Repo neu auditieren, ausser ich fordere es an. Beachte besonders: Komodo nur gemeinsam und kleinschrittig aendern, keine Secret-Werte ausgeben, untracked PDF und `ops/hermes-agent/services.yaml` nicht anfassen. Wir starten jetzt mit Next-Level-Hardening.
|
||||
Bitte zuerst `docs/archive/2026-05/AI_HANDOFF_2026-05-06.md` lesen und als aktuelle Arbeitsquelle verwenden. Nicht das ganze Repo neu auditieren, ausser ich fordere es an. Beachte besonders: Komodo nur gemeinsam und kleinschrittig aendern, keine Secret-Werte ausgeben, untracked PDF und `ops/hermes-agent/services.yaml` nicht anfassen. Wir starten jetzt mit Next-Level-Hardening.
|
||||
@@ -14,7 +14,7 @@ Ampel-Bewertung pro Bereich:
|
||||
| GitOps-Konsistenz (Working Tree) | 🟢* | Keine echten Inhaltsaenderungen offen. Die 47 "modified files" aus `git status` im Linux-Mount sind voraussichtlich CRLF/LF-Mount-Artefakte (durch `git diff -w --stat` auf Stichprobe bestaetigt leer). Bitte am Windows-Host gegenpruefen. |
|
||||
| Hardening-Sprint (Mai 2026) | 🟢 | Alle vier Post-Restore-Sprint-Items sind im Repo umgesetzt (Filebrowser-Mounts, Authelia Argon2id, Gitea Webhook-Allowlist, Backup-Dump-Konsistenz). |
|
||||
| Backup/Restore-Readiness | 🟢 | `pre-backup-dumps.sh` deckt alle relevanten SQLite/PostgreSQL/Mongo-Quellen ab. Borg-UI-Scope umfasst `/mnt/user/services`, `homelab-infra`, `stacks`, `posture-check`. Live-Frische ist offen (Abschnitt 9). |
|
||||
| Monitoring-Migration | 🟡 | `monitoring/` Stack im Repo komplett, aber Live-Deploy laut `docs/NEXT_SPRINT_TODO_2026-05-16.md` noch ausstehend. Alte Stacks `ops/grafana-influxdb` und `ops/loki` sollen erst nach Live-Smoke-Test gestoppt werden. |
|
||||
| Monitoring-Migration | 🟡 | `monitoring/` Stack im Repo komplett, aber Live-Deploy laut `docs/archive/2026-05/NEXT_SPRINT_TODO_2026-05-16.md` noch ausstehend. Alte Stacks `ops/grafana-influxdb` und `ops/loki` sollen erst nach Live-Smoke-Test gestoppt werden. |
|
||||
| Doku-Drift Repo vs. Master-Doku | 🟠 | `apps/jellyfin/`, `host-services/plex/` und einige andere existieren produktiv als Compose-Stacks, sind aber in `HOMELAB_ARCHITECTURE_MASTER_V2.md`, `docs/SERVICE_CATALOG.md` und `docs/REPO_MAP.md` **nicht aufgefuehrt**. Authelia-ACL kennt `jellyfin.kaleschke.info` als bypass, im Masterdoku-Hostkatalog steht es nicht. |
|
||||
| Repo-Hygiene | 🟡 | 8 leere Verzeichnisse im Working Tree (siehe 4.3). `.serena/` ist untracked und nicht in `.gitignore`. Drei `ops/windows-reinstall/*.ps1` sind untracked. |
|
||||
| Bekannte Ausnahmen | 🟢 | Alle Ausnahmen aus `HOMELAB_ARCHITECTURE_MASTER_V2.md` Abschnitt 10 sind weiterhin dokumentiert und durch den Policy-Check abgedeckt (0 Critical, 4 Warnings, 9 Info – alles dokumentierte Ausnahmen). |
|
||||
@@ -33,7 +33,7 @@ Diese Audit-Quellen wurden gelesen (repo-seitig):
|
||||
- `docs/SERVICE_CATALOG.md`
|
||||
- `docs/RESTORE_MATRIX.md`
|
||||
- `docs/GITOPS_DRIFT_RUNBOOK.md`
|
||||
- `docs/NEXT_SPRINT_TODO_2026-05-16.md`
|
||||
- `docs/archive/2026-05/NEXT_SPRINT_TODO_2026-05-16.md`
|
||||
- `ops/borg-ui/scripts/pre-backup-dumps.sh`
|
||||
- `ops/borg-ui/docker-compose.yml`
|
||||
- `ops/borg-ui/all-important-sources.txt`
|
||||
@@ -136,7 +136,7 @@ Repo hat folgende Compose-Stacks, die in den Doku-Quellen (`HOMELAB_ARCHITECTURE
|
||||
|
||||
### 2.6 Image-Pinning
|
||||
|
||||
Lt. `docs/NEXT_SPRINT_TODO_2026-05-16.md` sind diese Stacks noch nicht voll versioniert gepinnt:
|
||||
Lt. `docs/archive/2026-05/NEXT_SPRINT_TODO_2026-05-16.md` sind diese Stacks noch nicht voll versioniert gepinnt:
|
||||
- `ddns-updater` — `latest...@sha256`
|
||||
- `glances` — `latest-full@sha256`
|
||||
- `scrutiny` — `latest-omnibus@sha256`
|
||||
@@ -234,7 +234,7 @@ Sechs der sieben Punkte sind in Reichweite ohne neue Architekturentscheidungen.
|
||||
|---|---|---|---|
|
||||
| **P0** | `cd650b1` nach Gitea pushen | GitOps-Quelle-der-Wahrheit, Voraussetzung fuer alles weitere | 30 Sekunden |
|
||||
| **P0** | Live-Daten aus Abschnitt 9 einholen | Ohne Live-Frische ist Endstufen-Bewertung unvollstaendig | 5 Minuten |
|
||||
| **P1** | Monitoring-Stack live finalisieren (Secrets pruefen, deployen, Smoke-Test, alte Altstaende stoppen) | Aus `docs/NEXT_SPRINT_TODO_2026-05-16.md` der naechste produktive Schritt | 1–2 Stunden mit Tests |
|
||||
| **P1** | Monitoring-Stack live finalisieren (Secrets pruefen, deployen, Smoke-Test, alte Altstaende stoppen) | Aus `docs/archive/2026-05/NEXT_SPRINT_TODO_2026-05-16.md` der naechste produktive Schritt | 1–2 Stunden mit Tests |
|
||||
| **P2** | Doku-Drift schliessen: Jellyfin und Plex in `HOMELAB_ARCHITECTURE_MASTER_V2.md` 7.4 + 7.1, `docs/SERVICE_CATALOG.md`, `docs/REPO_MAP.md` ergaenzen; Plex-Eintrag in Abschnitt 7.7 "noch offene Sonderfaelle" entfernen (ist umgesetzt) | Doku ist Source of Truth fuer KI-Audits und Nachfolge | 30 Minuten |
|
||||
| **P2** | Home Assistant -> InfluxDB final testen, HA-Dashboard in `monitoring-grafana` anlegen | aus NEXT_SPRINT_TODO | 1–2 Stunden |
|
||||
| **P3** | Repo-Hygiene: 8 leere Verzeichnisse loeschen, `.serena/` in `.gitignore`, Entscheidung zu `ops/windows-reinstall/*.ps1` | minor, aber dokumentiert | 15 Minuten |
|
||||
@@ -276,7 +276,7 @@ Konsistent mit der bekannten Nicht-Anfassen-Liste:
|
||||
- Drift-Runbook: `docs/GITOPS_DRIFT_RUNBOOK.md`
|
||||
- Restore-Quellen: `docs/RESTORE_MATRIX.md`, `docs/DISASTER_RECOVERY.md`
|
||||
- Letzter Policy-Check: `ops/policy-checks/last-report.md` (0 Critical)
|
||||
- Letzte Sprint-Restliste: `docs/NEXT_SPRINT_TODO_2026-05-16.md`
|
||||
- Letzte Sprint-Restliste: `docs/archive/2026-05/NEXT_SPRINT_TODO_2026-05-16.md`
|
||||
|
||||
---
|
||||
|
||||
@@ -5,7 +5,7 @@ Stand: 2026-05-25 07:33 CEST. Ergebnis nach Push, Live-Messung, Doku-Sync, Repo-
|
||||
| Punkt | Ampel | Beleg |
|
||||
|---|---|---|
|
||||
| P0 `cd650b1` nach Gitea pushen | gruen | Push `af231dd..cd650b1 master -> master`; produktiver Runtime-Stand `66ee10c` enthaelt `cd650b1`. Die abschliessenden Audit-Doku-Commits liegen in Gitea; der runtime-relevante Stack-Inhalt fuer `gitea`, `borg-ui` und `monitoring` ist seit `66ee10c` unveraendert. |
|
||||
| P0 Live-Daten ablegen | gruen | `docs/AUDIT_2026-05-23_LIVE.md` angelegt, keine Secret-Werte dokumentiert. |
|
||||
| P0 Live-Daten ablegen | gruen | `docs/archive/2026-05/AUDIT_2026-05-23_LIVE.md` angelegt, keine Secret-Werte dokumentiert. |
|
||||
| P1 Monitoring live / Altstaende down | gruen | 10 `monitoring-*` Container laufen, `0` unhealthy, `0` starting, `0` stopped; alte `grafana`/`influxdb3-core`/`loki`/`alloy` Container sind nicht vorhanden. `monitoring.kaleschke.info` liefert 302 zu Authelia, Prometheus ist bereit, Loki `/ready` liefert `ready`. |
|
||||
| P1 Jellyfin/Plex Doku | gruen | `HOMELAB_ARCHITECTURE_MASTER_V2.md`, `docs/SERVICE_CATALOG.md` und `docs/REPO_MAP.md` ergaenzt; Plex ist als Repo-Compose-Stack dokumentiert, nicht mehr als nicht migrierter Dockerman-Sonderfall. |
|
||||
| P2 Borg-Frische | gruen | Borg-UI DB: letzter Backup-Job `completed`, Archiv `Taegliche-Sicherung-2026-05-25T05:52:44.157`, `nfiles=100221`; 15 kanonische Dump-/Archive-Artefakte von 2026-05-25 06:09/06:10 CEST und damit juenger als 24 h. |
|
||||
@@ -4,7 +4,7 @@ Stand: 2026-05-23 11:27 CEST. Quelle: lokaler Windows-Clone und SSH auf `Kallila
|
||||
|
||||
## 9.1 Windows-Host / Git
|
||||
|
||||
- `git status --short` nach dem initialen Push: keine tracked Modifikationen, untracked waren `.serena/`, `docs/AUDIT_2026-05-23.md`, `docs/CODEX_ENDSTUFE_PROMPT_2026-05-23.md` und drei `ops/windows-reinstall/*.ps1`.
|
||||
- `git status --short` nach dem initialen Push: keine tracked Modifikationen, untracked waren `.serena/`, `docs/archive/2026-05/AUDIT_2026-05-23.md`, `docs/archive/2026-05/CODEX_ENDSTUFE_PROMPT_2026-05-23.md` und drei `ops/windows-reinstall/*.ps1`.
|
||||
- `cd650b1` wurde nach `origin/master` gepusht: `af231dd..cd650b1 master -> master`.
|
||||
|
||||
## 9.2 Gitea online
|
||||
@@ -1,12 +1,12 @@
|
||||
# Homelab-Audit KalliLab CORE
|
||||
|
||||
Stand: 2026-05-25
|
||||
Methode: Repo-basierter Audit auf `master` (lokaler Clone). Keine Live-Messung gegen den Host. Querverweise auf Audit-Live-Daten aus `docs/AUDIT_2026-05-23_LIVE.md`, wo verfuegbar.
|
||||
Auftrag: externer, kritischer Audit-Blick zusaetzlich zur internen `docs/STRATEGISCHE_BEWERTUNG_2026-05-23.md`.
|
||||
Methode: Repo-basierter Audit auf `master` (lokaler Clone). Keine Live-Messung gegen den Host. Querverweise auf Audit-Live-Daten aus `docs/archive/2026-05/AUDIT_2026-05-23_LIVE.md`, wo verfuegbar.
|
||||
Auftrag: externer, kritischer Audit-Blick zusaetzlich zur internen `docs/archive/2026-05/STRATEGISCHE_BEWERTUNG_2026-05-23.md`.
|
||||
|
||||
## Wichtige Vorbemerkung
|
||||
|
||||
Es gibt seit dem 23.05. eine fundierte interne Bewertung (`docs/STRATEGISCHE_BEWERTUNG_2026-05-23.md`) und eine konsolidierte Hausaufgabenliste (`docs/CODEX_KONSOLIDIERUNG_2026-05-23.md`). Davon wurden seit dem 25.05. bereits umgesetzt:
|
||||
Es gibt seit dem 23.05. eine fundierte interne Bewertung (`docs/archive/2026-05/STRATEGISCHE_BEWERTUNG_2026-05-23.md`) und eine konsolidierte Hausaufgabenliste (`docs/archive/2026-05/CODEX_KONSOLIDIERUNG_2026-05-23.md`). Davon wurden seit dem 25.05. bereits umgesetzt:
|
||||
|
||||
- Jellyfin entfernt (MASTER 7.8)
|
||||
- Homepage entfernt (MASTER 7.8)
|
||||
@@ -53,7 +53,7 @@ homelab-infra/
|
||||
| Architektur, Netze, Ausnahmen | `HOMELAB_ARCHITECTURE_MASTER_V2.md` |
|
||||
| GitOps-Workflow, Drift | `docs/WORKFLOW.md`, `docs/GITOPS_DRIFT_RUNBOOK.md` |
|
||||
| Backup-Scope, Restore-Wege, Tier-Modell | `docs/RESTORE_MATRIX.md`, `docs/DISASTER_RECOVERY.md`, `ops/borg-ui/BACKUP_SCOPE.md` |
|
||||
| Storage / Cache-Policy / FS / Posture | `docs/STORAGE_LAYOUT.draft.md` |
|
||||
| Storage / Cache-Policy / FS / Posture | `docs/STORAGE_LAYOUT.md` (zum Audit-Zeitpunkt noch `docs/STORAGE_LAYOUT.draft.md`) |
|
||||
| Secret-Inventur | `docs/SECRETS_MAP.md` |
|
||||
| Alert-Pfade | `docs/ALERTING_MAP.md` |
|
||||
|
||||
@@ -61,7 +61,7 @@ homelab-infra/
|
||||
|
||||
| Luecke | Warum es ein Audit-Loch ist |
|
||||
|---|---|
|
||||
| `docs/STORAGE_LAYOUT.draft.md` ist `Draft 1.3`, nicht `Active` | Mehrere Hard Rules (12 Constitution) gelten formal noch nicht. Hard Rule 11 (kein Stack ohne Restore-Pfad in RESTORE_MATRIX) wird heute schon eingehalten — also nur Formal-Luecke. |
|
||||
| `docs/STORAGE_LAYOUT.draft.md` ist `Draft 1.3`, nicht `Active` | Stand zum Audit-Zeitpunkt 2026-05-25: mehrere Hard Rules (12 Constitution) galten formal noch nicht. Hard Rule 11 (kein Stack ohne Restore-Pfad in RESTORE_MATRIX) wurde schon eingehalten — also nur Formal-Luecke. Folgearbeit: als `docs/STORAGE_LAYOUT.md` Active heben. |
|
||||
| `docs/SERVICES_RECOVERY.md` ist als verbindlich angekuendigt (STORAGE_LAYOUT 4), aber nicht im Repo | Konkrete Mirror-Mechanik fuer `services/gitea/git/repositories/` ≤ 6 h ist nirgends spezifiziert. |
|
||||
| Hardware-Inventar: kein zentrales Dokument | Keine Stelle im Repo nennt CPU-Modell, RAM-Groesse, NIC-Speed, Mainboard, Parity-Disk-Groessen — nur "Samsung 970 EVO Plus 2 TB" steht in STORAGE_LAYOUT 3. |
|
||||
| USV: keine Erwaehnung | Keine Datei nennt eine USV. Unklar, ob vorhanden. |
|
||||
@@ -72,8 +72,8 @@ homelab-infra/
|
||||
- `HOMELAB_ARCHITECTURE_MASTER_V2.md` — komplett
|
||||
- `docs/WORKFLOW.md`, `docs/REPO_MAP.md`, `docs/SERVICE_CATALOG.md` — komplett
|
||||
- `docs/DISASTER_RECOVERY.md`, `docs/RESTORE_MATRIX.md`, `docs/SECRETS_MAP.md` — komplett
|
||||
- `docs/STORAGE_LAYOUT.draft.md`, `docs/STRATEGISCHE_BEWERTUNG_2026-05-23.md` — komplett
|
||||
- `docs/AUDIT_2026-05-23_LIVE.md`, `docs/AUDIT_2026-05-23_FINAL.md`
|
||||
- `docs/STORAGE_LAYOUT.md` (zum Audit-Zeitpunkt `docs/STORAGE_LAYOUT.draft.md`), `docs/archive/2026-05/STRATEGISCHE_BEWERTUNG_2026-05-23.md` — komplett
|
||||
- `docs/archive/2026-05/AUDIT_2026-05-23_LIVE.md`, `docs/archive/2026-05/AUDIT_2026-05-23_FINAL.md`
|
||||
- `ops/policy-checks/last-report.md`
|
||||
- `monitoring/docker-compose.yml`, `monitoring/prometheus/alerts.yml`
|
||||
- `traefik/docker-compose.yml`, `traefik/dynamic/middlewares.yml`
|
||||
+3
-3
@@ -4,13 +4,13 @@ Du hast Vollzugriff auf `G:\Gitea_Clone\homelab-infra`, Gitea-Push, Komodo, und
|
||||
|
||||
## Lies zuerst
|
||||
1. `CLAUDE.md`
|
||||
2. `docs/AUDIT_2026-05-23.md` — dort steht die komplette Restliste
|
||||
2. `docs/archive/2026-05/AUDIT_2026-05-23.md` — dort steht die komplette Restliste
|
||||
|
||||
## Auftrag
|
||||
Den Audit von oben verifizieren und die offenen Punkte abarbeiten, bis das Homelab in der Endstufe ist. Reihenfolge:
|
||||
|
||||
1. **P0** — Lokalen Commit `cd650b1` nach Gitea pushen, danach Komodo-Reaktion fuer `gitea` und `borg-ui` pruefen.
|
||||
2. **P0** — Live-Daten aus Audit-Abschnitt 9 messen und in `docs/AUDIT_2026-05-23_LIVE.md` ablegen (Secrets redacten).
|
||||
2. **P0** — Live-Daten aus Audit-Abschnitt 9 messen und in `docs/archive/2026-05/AUDIT_2026-05-23_LIVE.md` ablegen (Secrets redacten).
|
||||
3. **P1** — Monitoring-Stack (`monitoring/`) live deployen, alte `ops/grafana-influxdb` und `ops/loki` `down` (nicht loeschen).
|
||||
4. **P1** — Jellyfin und Plex in `HOMELAB_ARCHITECTURE_MASTER_V2.md`, `docs/SERVICE_CATALOG.md`, `docs/REPO_MAP.md` nachtragen. Plex-Eintrag "nicht als Repo-Stack enthalten" korrigieren.
|
||||
5. **P2** — Borg-Lauf-Frische pruefen, ggf. neuen Lauf ausloesen, alle 14 Dump-Artefakte juenger als 24 h.
|
||||
@@ -29,4 +29,4 @@ Den Audit von oben verifizieren und die offenen Punkte abarbeiten, bis das Homel
|
||||
Lesen → minimal aendern → `ops/policy-checks/check_repo.ps1` lokal → Commit → Push → Komodo-Reaktion + Smoke-Test → eine Zeile in `docs/MIGRATION_LOG.md`.
|
||||
|
||||
## Fertig
|
||||
Wenn alles abgearbeitet ist (oder ein Punkt bewusst offen bleibt): `docs/AUDIT_2026-05-23_FINAL.md` schreiben mit Ampel + konkretem Beleg pro Punkt, committen, pushen, kurz an mich melden.
|
||||
Wenn alles abgearbeitet ist (oder ein Punkt bewusst offen bleibt): `docs/archive/2026-05/AUDIT_2026-05-23_FINAL.md` schreiben mit Ampel + konkretem Beleg pro Punkt, committen, pushen, kurz an mich melden.
|
||||
@@ -0,0 +1,202 @@
|
||||
# Codex-Prompt: Jellyfin entfernen, Plex bleibt
|
||||
|
||||
> **Status (Stand 2026-05-30):** Auftrag ausgefuehrt. Jellyfin wurde 2026-05-25 aus Repo, Komodo, Traefik-Routing, Authelia-ACL und Appdata-Live-Pfad entfernt (Service-Removal-Checkliste in `docs/WORKFLOW.md`, MIGRATION_LOG-Eintrag dort). Datei bleibt im Repo als **Codex-Removal-Pattern** fuer kuenftige Stack-Removals (z.B. Hermes nach Review-Deadline 2026-07-25 oder bei BentoPDF/paperless-gpt-Folgeentscheidung). Inhaltlich nicht mehr aendern — als Vorlage referenzieren und pro Anwendung neu instanzieren.
|
||||
|
||||
Stand: 2026-05-23
|
||||
Ausloeser: Operator-Entscheidung "Plex bleibt, Jellyfin weg".
|
||||
Bezug: `docs/archive/2026-05/STRATEGISCHE_BEWERTUNG_2026-05-23.md` Block 9 Quick Wins.
|
||||
|
||||
Du hast Vollzugriff auf `G:\Gitea_Clone\homelab-infra`, Gitea-Push, Komodo, und SSH auf Unraid `Kallilabcore`.
|
||||
|
||||
## Lies zuerst
|
||||
|
||||
1. `CLAUDE.md`
|
||||
2. `HOMELAB_ARCHITECTURE_MASTER_V2.md` Abschnitt 7.4 und 7.8
|
||||
3. `docs/WORKFLOW.md`
|
||||
4. `docs/ROLLBACK.md`
|
||||
5. `apps/jellyfin/docker-compose.yml`
|
||||
6. `security/authelia/configuration.yml` (Access-Control-Block)
|
||||
7. `docs/SERVICE_CATALOG.md`, `docs/REPO_MAP.md`, `docs/MIGRATION_LOG.md`
|
||||
|
||||
## Ziel
|
||||
|
||||
Jellyfin ist vollstaendig aus dem Repo, Komodo, Traefik-Routing und Authelia-ACL entfernt. Plex laeuft unveraendert weiter. Medien-/Foto-Mounts unter `/mnt/user/media` und `/mnt/user/photos` bleiben unberuehrt. Domain `jellyfin.kaleschke.info` antwortet nicht mehr ueber Traefik.
|
||||
|
||||
## Wichtige Vorabinformation
|
||||
|
||||
- `/mnt/user/appdata/jellyfin/{config,cache}` ist **nicht** im Borg-Scope (`ops/borg-ui/all-important-sources.txt`). Watch-History, User-Settings und Metadaten-Cache sind danach weg. Das ist akzeptabel, weil Plex die Nutzungsdaten ohnehin separat fuehrt.
|
||||
- Plex teilt sich `/mnt/user/media:ro` und `/mnt/user/photos:ro` mit Jellyfin — Datenpfad bleibt unangetastet.
|
||||
- Authelia-Eintrag `jellyfin.kaleschke.info` steht unter `policy: bypass` in `security/authelia/configuration.yml` Zeile 42. Die `configuration.yml` ist Repo-Baseline und muss laut `docs/AI_CONTEXT.md` manuell auf den Host gemerged werden.
|
||||
- Jellyfin hat **keinen** Eintrag in `monitoring/blackbox/blackbox.yml`, `ops/glance/config/glance.yml`, `apps/homepage/docker-compose.yml`. Die Homepage-Service-Cards liegen hostseitig unter `/mnt/user/appdata/homepage/config/services.yaml` — dort moeglicherweise ein Eintrag, vor Ort pruefen.
|
||||
|
||||
## Reihenfolge
|
||||
|
||||
### P0 — Plex-Tauglichkeit verifizieren (vor jeglichem Loeschen)
|
||||
|
||||
Smoke-Test auf dem Host:
|
||||
|
||||
```bash
|
||||
curl -fsS -o /dev/null -w "%{http_code}\n" http://192.168.178.58:32400/identity
|
||||
docker exec plex test -d /data/Filme || echo "Filme nicht erreichbar"
|
||||
docker exec plex test -d /photos || echo "Photos nicht erreichbar"
|
||||
```
|
||||
|
||||
Akzeptanzkriterium: Plex antwortet mit `200`, beide Medien-Pfade sind im Plex-Container sichtbar, Plex zeigt in seiner Web-UI alle erwarteten Bibliotheken. **Wenn nicht erfuellt: abbrechen und Operator fragen.**
|
||||
|
||||
### P1 — Jellyfin Stack in Komodo stoppen (nicht loeschen)
|
||||
|
||||
In Komodo Web-UI Stack `jellyfin` `Stop` ausfuehren (nicht `Destroy`). Damit ist der Container weg, Stack-Definition und Workspace bleiben — Rollback per `Start` moeglich.
|
||||
|
||||
Akzeptanzkriterium:
|
||||
|
||||
```bash
|
||||
docker ps -a --filter name=jellyfin --format "{{.Names}}\t{{.Status}}"
|
||||
```
|
||||
|
||||
zeigt entweder keine Zeile oder `Exited`.
|
||||
|
||||
### P2 — Authelia ACL um Jellyfin-Bypass bereinigen
|
||||
|
||||
Datei: `security/authelia/configuration.yml`
|
||||
|
||||
```diff
|
||||
- domain:
|
||||
- immich.kaleschke.info
|
||||
- paperless.kaleschke.info
|
||||
- mealie.kaleschke.info
|
||||
- vault.kaleschke.info
|
||||
- ntfy.kaleschke.info
|
||||
- git.kaleschke.info
|
||||
- - jellyfin.kaleschke.info
|
||||
policy: bypass
|
||||
```
|
||||
|
||||
Kein anderer Authelia-Eintrag referenziert Jellyfin. Wildcard `*.kaleschke.info` mit `policy: one_factor` greift fuer geloeschte Domains nicht, weil Traefik die Route nicht mehr kennt.
|
||||
|
||||
**Manueller Host-Sync danach Pflicht** (`docs/AI_CONTEXT.md`, Workflow): die geaenderte `configuration.yml` muss auf `/mnt/user/appdata/authelia/config/configuration.yml` gemerged werden (OIDC-/Secret-Block hostseitig erhalten). Danach `docker exec authelia authelia validate-config -c /config/configuration.yml` und Stack-Restart.
|
||||
|
||||
### P3 — Compose-Stack aus Repo entfernen
|
||||
|
||||
```bash
|
||||
git rm -r apps/jellyfin/
|
||||
```
|
||||
|
||||
Akzeptanzkriterium: `apps/jellyfin/` existiert nicht mehr; `git status --short` zeigt `D apps/jellyfin/docker-compose.yml`.
|
||||
|
||||
### P4 — Doku synchronisieren
|
||||
|
||||
**`HOMELAB_ARCHITECTURE_MASTER_V2.md`:**
|
||||
|
||||
- Abschnitt 3.2 Diagramm: `jellyfin` aus oeffentliche-Apps-Zeile entfernen.
|
||||
- Abschnitt 4.1 "Oeffentlich ueber Traefik": Zeile `- jellyfin — jellyfin.kaleschke.info` entfernen.
|
||||
- Abschnitt 7.4 Tabelle "Produktive Apps": Zeile `jellyfin` entfernen.
|
||||
- Abschnitt 7.8 "Entfernte Container": neue Zeile
|
||||
|
||||
```text
|
||||
| `jellyfin` | 2026-05-23 | doppelter Medienserver neben Plex; Plex bleibt einziger Medienserver |
|
||||
```
|
||||
|
||||
**`docs/SERVICE_CATALOG.md`:**
|
||||
|
||||
- Block "Public / User Apps": Jellyfin-Zeile entfernen.
|
||||
|
||||
**`docs/REPO_MAP.md`:**
|
||||
|
||||
- Abschnitt "Apps"-Tabelle: Jellyfin-Zeile entfernen.
|
||||
- Abschnitt "Traefik Hosts": `jellyfin.kaleschke.info`-Zeile entfernen.
|
||||
- Abschnitt "Volumes und Datenpfade": Jellyfin-Zeile entfernen.
|
||||
|
||||
**`docs/MIGRATION_LOG.md`:** neuen Eintrag anhaengen
|
||||
|
||||
```text
|
||||
### Jellyfin entfernt (2026-05-23)
|
||||
- Operator-Entscheidung: Plex bleibt einziger Medienserver.
|
||||
- Compose-Stack `apps/jellyfin/` aus Repo entfernt.
|
||||
- Authelia-ACL bereinigt (`jellyfin.kaleschke.info` aus `bypass`-Liste raus), Host-Config gemerged.
|
||||
- Komodo-Stack `jellyfin` gestoppt; Stack-Eintrag und Webhook bei Abschluss von Schritt P7 entfernt.
|
||||
- Appdata unter `/mnt/user/appdata/jellyfin/` nach `/mnt/user/appdata/_archive/jellyfin-removed-2026-05-23/` verschoben (siehe Schritt P5).
|
||||
- DNS-Eintrag `jellyfin.kaleschke.info` in Cloudflare belassen, kann beim naechsten DNS-Cleanup mit entfernt werden.
|
||||
```
|
||||
|
||||
### P5 — Appdata archivieren statt loeschen
|
||||
|
||||
Auf dem Host:
|
||||
|
||||
```bash
|
||||
mkdir -p /mnt/user/appdata/_archive
|
||||
mv /mnt/user/appdata/jellyfin /mnt/user/appdata/_archive/jellyfin-removed-2026-05-23
|
||||
```
|
||||
|
||||
Akzeptanzkriterium: `/mnt/user/appdata/jellyfin` existiert nicht mehr, `/mnt/user/appdata/_archive/jellyfin-removed-2026-05-23` enthaelt `config/` und `cache/`. **Nicht endgueltig loeschen** vor mindestens 14 Tagen Plex-Stabilitaet.
|
||||
|
||||
### P6 — Policy-Check + Commit
|
||||
|
||||
```powershell
|
||||
pwsh ops/policy-checks/check_repo.ps1
|
||||
```
|
||||
|
||||
Akzeptanzkriterium: 0 Critical, keine neuen Warnings (vorher: 4 dokumentierte Warnings, soll danach gleich bleiben).
|
||||
|
||||
Commit:
|
||||
|
||||
```bash
|
||||
git add -A
|
||||
git commit -m "Remove Jellyfin stack; Plex remains sole media server"
|
||||
git push origin master
|
||||
```
|
||||
|
||||
### P7 — Komodo-Stack-Eintrag und Webhook entfernen
|
||||
|
||||
Nach erfolgreichem Push und gruener Komodo-Reaktion auf restliche Stacks:
|
||||
|
||||
- In Komodo: Stack `jellyfin` `Destroy` (Workspace `/mnt/user/services/stacks/jellyfin/` entfernen).
|
||||
- In Gitea: Webhook-Eintrag fuer Komodo-Stack-ID `jellyfin` entfernen.
|
||||
|
||||
Akzeptanzkriterium: `ls /mnt/user/services/stacks/ | grep jellyfin` ist leer; Gitea-Webhook-Liste fuer `Micha/homelab-infra` enthaelt keinen Jellyfin-Eintrag mehr.
|
||||
|
||||
### P8 — Smoke-Test final
|
||||
|
||||
```bash
|
||||
curl -fsS -o /dev/null -w "%{http_code}\n" https://jellyfin.kaleschke.info/
|
||||
docker ps --filter name=jellyfin --format "{{.Names}}"
|
||||
ss -ltnp | grep 8096 || echo "Port 8096 frei"
|
||||
```
|
||||
|
||||
Erwartung:
|
||||
|
||||
- `https://jellyfin.kaleschke.info/` antwortet `404` (Traefik kennt die Route nicht mehr) oder Cert-Fehler je nach Cache.
|
||||
- Kein laufender `jellyfin`-Container.
|
||||
- Port `8096` nicht belegt (Jellyfin nutzte nur Container-Port, sollte sowieso frei sein).
|
||||
- Authelia-Login fuer Admin-Domains (`uptime`, `files`, `scrutiny`) funktioniert weiterhin — Bypass-Liste-Aenderung darf 2FA-Domains nicht angreifen.
|
||||
|
||||
## Rollback (bis Schritt P5 einschliesslich)
|
||||
|
||||
- Git: `git revert <commit-sha>` und push. Komodo deployt Jellyfin neu, sobald Stack in Komodo wieder existiert.
|
||||
- Appdata: `mv /mnt/user/appdata/_archive/jellyfin-removed-2026-05-23 /mnt/user/appdata/jellyfin`.
|
||||
- Authelia-ACL: Bypass-Eintrag wieder rein, Host-Sync, Authelia-Restart.
|
||||
|
||||
Ab Schritt P7 (Komodo Destroy + Webhook weg) ist Rollback nur per Neuanlegen des Komodo-Stacks moeglich.
|
||||
|
||||
## Regeln (aus CLAUDE.md, nicht verhandelbar)
|
||||
|
||||
- Secrets nie im Klartext ausgeben.
|
||||
- Keine Aenderungen direkt in Komodo, nur ueber Git → Push → Komodo. **Ausnahme:** Schritt P1 (`Stop`) und P7 (`Destroy`) sind explizit Komodo-Aktionen nach erfolgreichem Repo-Stand.
|
||||
- Kein `push --force`, kein blindes Loeschen unter `/mnt/user/{appdata,documents,photos,services,backups}` — Appdata wird in `_archive/` verschoben, nicht entfernt.
|
||||
- Working-Tree-Status nur aus `git status --short` ableiten, nie aus `git diff` ueber Linux-Mount.
|
||||
- Traefik dynamic config wird nicht von Komodo deployed — fuer diesen Auftrag nicht relevant, weil Jellyfin nur per Docker-Labels gerouteted war.
|
||||
- Nicht anfassen: Plex-Stack, Medien-/Foto-Mounts, alle anderen Apps.
|
||||
- Wenn zwei Reparaturversuche scheitern: stoppen, `docs/GITOPS_DRIFT_RUNBOOK.md` Pflichtmatrix, Operator fragen.
|
||||
|
||||
## Arbeitsmodus pro Schritt
|
||||
|
||||
Lesen → minimal aendern → `ops/policy-checks/check_repo.ps1` lokal (nur P6) → Commit → Push → Komodo-Reaktion + Smoke-Test → Eintrag in `docs/MIGRATION_LOG.md`.
|
||||
|
||||
## Fertig
|
||||
|
||||
Wenn alle 8 Schritte abgehakt sind: kurze Erfolgsmeldung an Operator mit:
|
||||
|
||||
- Commit-SHA des Removal-Commits
|
||||
- Bestaetigung Plex weiterhin gruen
|
||||
- Bestaetigung Authelia validate-config gruen
|
||||
- Bestaetigung Komodo-Stack und Webhook entfernt
|
||||
- Pfad zum Appdata-Archiv und Erinnerung, dass `_archive/jellyfin-removed-2026-05-23/` nach 14 Tagen Plex-Stabilitaet entfernt werden darf
|
||||
@@ -0,0 +1,123 @@
|
||||
# Codex-Prompt: Komodo 5xx-Spam Root-Cause
|
||||
|
||||
Stand: 2026-05-31
|
||||
Auftraggeber: Operator
|
||||
Vorarbeit: Claude (auto-mode), siehe Ermittlungsstand unten.
|
||||
|
||||
## Auftrag
|
||||
|
||||
`HomelabTraefik5xx` feuert dauerhaft fuer `service="komodo@docker"`. Quelle
|
||||
finden, fixen, dokumentieren. Bitte einmal **bis zum Ende** durchziehen, nicht
|
||||
nur eine Hypothese pruefen.
|
||||
|
||||
## Vor Arbeitsbeginn lesen
|
||||
|
||||
- `CLAUDE.md`
|
||||
- `docs/WORKFLOW.md`
|
||||
- `monitoring/prometheus/alerts.yml`
|
||||
- `docs/ALERT_RULES.md`
|
||||
- `ops/komodo/docker-compose.yml`
|
||||
- `traefik/docker-compose.yml`
|
||||
- `monitoring/prometheus/prometheus.yml` (Blackbox-Targets)
|
||||
- `monitoring/blackbox/blackbox.yml`
|
||||
- `ops/glance/config/glance.yml` (5 Komodo-URL-Stellen, **NICHT** die Quelle — siehe Ermittlung)
|
||||
|
||||
## Ermittlungsstand (bereits geklaert)
|
||||
|
||||
### Was gemessen wurde
|
||||
|
||||
- Traefik-Access-Log: Source-IP ist **eure WAN-IP `217.249.121.39`** (Hairpin
|
||||
aus dem Heimnetz). User-Agent leer (`"-"`).
|
||||
- Muster: `GET /` 200 **alle 15s** + `GET /user` **500** alle 30s, plus
|
||||
gelegentlich `POST /auth/login/GetLoginOptions` 200 und
|
||||
`POST /read/GetCoreInfo` 500.
|
||||
- Prometheus `sum by (code) (increase(traefik_service_requests_total{service="komodo@docker"}[5m]))`:
|
||||
`200`=22, `500`=14 (Werte vom 2026-05-31 08:11 UTC).
|
||||
- `docker logs komodo-core` ist still — keine internen Errors, nur normale
|
||||
Execute-Requests. Komodo wirft den 500 also vermutlich auf Auth-Pfad
|
||||
(`/user` ohne gueltige Session sollte `401` sein, nicht `500`). Das ist ein
|
||||
Komodo-Bug-on-Top, **aber nicht die Frage**.
|
||||
|
||||
### Ausgeschlossene Kandidaten (durch Test)
|
||||
|
||||
- **Browser-Tabs** — User hat alle Komodo-Tabs zugemacht, Polling laeuft
|
||||
weiter.
|
||||
- **PWA auf Handy** — User hat keine.
|
||||
- **Uptime-Kuma** — Container existiert nicht mehr.
|
||||
- **Homepage** — entfernt.
|
||||
- **Glance** — Test 2026-05-31 ~08:35 UTC: 130s gestoppt, 5xx-Rate
|
||||
unveraendert (2/60s Baseline → 4/130s waehrend Stop). Trotz 5 Komodo-URL-
|
||||
Eintraegen in `ops/glance/config/glance.yml` (search-shortcut Zeile 40,
|
||||
bookmark Zeilen 131/768, monitor-Widget Zeile 237 mit `check-url:
|
||||
http://komodo-core:9120`, docker-containers-Widget Zeile 725). Glance ist
|
||||
raus.
|
||||
|
||||
### Noch nicht getestete Kandidaten
|
||||
|
||||
- **Posture-Check / cert-token-check.sh** (`services/posture-check/`) — koennte
|
||||
periodisch Komodo-HTTPS pingen. 15s-/30s-Kadenz waere ungewoehnlich fuer
|
||||
einen Cron-Job, aber pruefen.
|
||||
- **Blackbox-Exporter** — pollt laut `monitoring/prometheus/prometheus.yml`
|
||||
`https://komodo.kaleschke.info` alle 15s. Das erklaert den `GET / 200`-
|
||||
Anteil sauber. Erklaert aber NICHT den `GET /user 500` 30s-Takt.
|
||||
- **Komodo Periphery** — auf `komodo_net` und `frontend_net`. Sollte mit
|
||||
Core via internes Netz reden, koennte aber per Misconfig die Public-URL
|
||||
treffen. Logs noch nicht eingesehen.
|
||||
- **Komodo Core selbst** mit `KOMODO_HOST=https://komodo.kaleschke.info` —
|
||||
evtl. Self-Check via Public-URL.
|
||||
- **Ein Gerat im LAN**, das wir noch nicht auf dem Schirm haben (zweiter
|
||||
Rechner mit altem Tab, Smart-TV, etc.).
|
||||
|
||||
### Was nicht geht
|
||||
|
||||
- `tcpdump` fehlt auf dem Host.
|
||||
- `conntrack` zeigt die Hairpin-Pakete nicht (NAT-Pre-Routing).
|
||||
|
||||
## Naechste Schritte (Vorschlag)
|
||||
|
||||
1. **Blackbox-Exporter ausschliessen**: Targets in `prometheus.yml` zeigen,
|
||||
dass Blackbox NUR `https://komodo.kaleschke.info` pollt (also `/`, kein
|
||||
`/user`). Bestaetigen.
|
||||
2. **Posture-Check pruefen**: `services/posture-check/cert-token-check.sh`
|
||||
lesen, Kadenz und Endpunkte protokollieren. Falls dort `/user` oder ein
|
||||
30s-Loop drin ist → Treffer.
|
||||
3. **Periphery isolieren**: Periphery 2 min stoppen, Traefik-Log gegen-
|
||||
checken. `docker stop komodo-periphery; sleep 130; <log-check>; docker
|
||||
start komodo-periphery`. Vorsicht: Periphery-Down heisst Komodo-Deploy
|
||||
funktioniert nicht — also nur kurz, kein Deploy in dem Fenster.
|
||||
4. **Komodo-Core isolieren**: Wenn 1-3 nichts ergeben, Komodo-Core selbst 2 min
|
||||
stoppen. Wenn Polling weiterlaeuft, ist der Client ausserhalb der Komodo-
|
||||
Stack (LAN-Geraet). Wenn es aufhoert, polled Komodo Core sich selbst.
|
||||
5. **LAN-Aufnahme via Komodo-Container**: Falls Container-Stack ausgeschlossen,
|
||||
im komodo-core-Container per `ss -tnp state syn-recv` waehrend einer
|
||||
typischen Polling-Sekunde mitschauen. Source-IP/Port der eingehenden
|
||||
Connection liefert den Hairpin-Origin am genauesten.
|
||||
|
||||
## Fix-Erwartung
|
||||
|
||||
Sobald Quelle bekannt:
|
||||
|
||||
- **Wenn Container im Stack**: Config so anpassen, dass die Anfrage intern
|
||||
laeuft (kein Public-Hostname), inkl. Doku.
|
||||
- **Wenn LAN-Geraet**: User informieren, was es ist; wenn moeglich Geraet
|
||||
reparieren (Tab schliessen, App deinstallieren). Kein Repo-Change noetig.
|
||||
- **Wenn nicht abstellbar**: separate Frage, ob `HomelabTraefik5xx` fuer
|
||||
`service="komodo@docker"` mit einem Exclude versehen werden soll — aber nur
|
||||
als letzter Ausweg. Default ist: Quelle fixen.
|
||||
|
||||
## Doku am Ende
|
||||
|
||||
- Eintrag in `docs/MIGRATION_LOG.md`: Datum, Symptom, Root-Cause, Fix,
|
||||
Smoke-Test.
|
||||
- Falls eine Glance-/Periphery-/sonstige Config-Aenderung noetig wird:
|
||||
Standard-Loop (Commit → Push → Komodo-Deploy → Smoke), Co-Authored-By-Tag
|
||||
mitgeben.
|
||||
|
||||
## Regeln (nicht verhandelbar)
|
||||
|
||||
- Git → Push → Komodo. Keine direkten Komodo-Edits.
|
||||
- Stop/Start-Tests sind okay, aber nur kurz (≤ 3 min) und mit
|
||||
Wiederanlauf-Schritt im selben Block.
|
||||
- Secrets nicht ausgeben.
|
||||
- Bei zwei gescheiterten Versuchen: stop, Pflichtmatrix aus
|
||||
`docs/GITOPS_DRIFT_RUNBOOK.md`, Operator fragen.
|
||||
@@ -0,0 +1,83 @@
|
||||
# Codex-Prompt: KalliLab Konsolidierung (Bewertungs-Followup)
|
||||
|
||||
> **Status (Stand 2026-05-30):** Erstprompt fuer den Audit-Zyklus 2026-05-25, Stand weitgehend abgearbeitet. Verbleibende Punkte sind in `docs/AUDIT_2026-05-25_TODO.md` weiter gefuehrt (offen, geparkt, bewusst nicht umgesetzt). Datei bleibt im Repo als **Codex-Prompt-Vorlage** fuer kuenftige Konsolidierungs-Sweeps; inhaltlich nicht mehr aendern.
|
||||
|
||||
Stand: 2026-05-23
|
||||
Auftraggeber: Operator
|
||||
Quelle: `docs/archive/2026-05/STRATEGISCHE_BEWERTUNG_2026-05-23.md`
|
||||
|
||||
## Schritt 0 — Reviewe die Bewertung kritisch
|
||||
|
||||
Lies `docs/archive/2026-05/STRATEGISCHE_BEWERTUNG_2026-05-23.md` komplett. Bevor du irgendetwas anfasst, sag dem Operator ehrlich:
|
||||
|
||||
- Wo ist Claudes Befund richtig?
|
||||
- Wo liegt Claude daneben oder hat etwas Wichtiges uebersehen?
|
||||
- Welche Hausaufgaben unten wuerdest du anders priorisieren oder weglassen?
|
||||
|
||||
Erst nach Operator-Freigabe weitermachen.
|
||||
|
||||
## Lies vor jedem Block
|
||||
|
||||
`CLAUDE.md`, `docs/WORKFLOW.md`, betroffene Compose-Datei. Bei DR/Backup zusaetzlich `docs/DISASTER_RECOVERY.md` und `docs/RESTORE_MATRIX.md`.
|
||||
|
||||
## Hausaufgaben
|
||||
|
||||
### P0 — Quick Wins (≤ 1 Woche, hoher Nutzen)
|
||||
|
||||
1. **Externer Repo-Mirror** einrichten (GitHub privat oder zweites Gitea); Push-Mirror in Gitea aktivieren. Schliesst das groesste DR-Loch.
|
||||
2. **Borg-Passphrase analog sichern** (Schliessfach oder Familienmitglied).
|
||||
3. **Jellyfin entfernen, Plex bleibt.** Detail-Schritte in `docs/archive/2026-05/CODEX_JELLYFIN_REMOVAL_2026-05-23.md`. Kurzfassung: Plex-Smoke-Test → Komodo-Stop → Authelia-Bypass raus + Host-Sync → `git rm apps/jellyfin/` → Doku (MASTER 3.2/4.1/7.4/7.8, SERVICE_CATALOG, REPO_MAP, MIGRATION_LOG) → Appdata nach `_archive/` → Policy-Check → Push → Komodo-Destroy + Webhook weg.
|
||||
4. **Glance oder Homepage** als einziges Dashboard waehlen, das andere stoppen und aus Repo entfernen.
|
||||
5. **AdGuard Admin-Port 8082** hinter Authelia oder nur via Tailscale (Block F aus MASTER 10).
|
||||
6. **Authelia 2FA-Pflicht** fuer alle aktiven User verifizieren bzw. aktivieren.
|
||||
7. **Disk1 NTFS → XFS Phase 2** abschliessen, anschliessend `ALLOW_DISK1_NTFS=0` in posture-check.
|
||||
|
||||
### P1 — Stabilitaet und Ordnung (2–4 Wochen)
|
||||
|
||||
8. **Monitoring-Migration abschliessen**: `monitoring/` produktiv, `ops/grafana-influxdb` + `ops/loki` `down` + aus Repo entfernen.
|
||||
9. **Uptime-Kuma abloesen** durch Blackbox + Grafana-Alerts (nach 7 Tagen Parallelbetrieb mit Paritaet, wie in SERVICE_CATALOG vorgesehen).
|
||||
10. **Hermes-Agent Entscheidung**: produktiv mit klarem Alltagsnutzen oder vollstaendig entfernen. Kein weiteres Quartal "halb da".
|
||||
11. **paperless-gpt und BentoPDF**: gleiche Frage. Produktiv im Workflow oder weg.
|
||||
12. **Unraid USB-Flash-Backup** einrichten (eingebauter Mechanismus).
|
||||
13. **Family-View-Dashboard** in Grafana: alles-gruen-Uebersicht fuer den Morgen-Check.
|
||||
|
||||
### P2 — Automatisierung und Transparenz (4–12 Wochen)
|
||||
|
||||
14. **Authelia OIDC-Provider** aktivieren; Nextcloud + Immich + Grafana als OIDC-Clients.
|
||||
15. **Renovate Bot gegen Gitea** fuer kontrollierte Image-Update-PRs (loest die manuelle Digest-Pflege ab).
|
||||
16. **Restore-Test fuer Immich** als eigener Sprint einplanen (groesster Datentopf ohne Mini-Restore).
|
||||
17. **Immich Smartphone-Auto-Backup** fuer alle Familien-Geraete aktivieren — der eigentliche Familien-Nutzen.
|
||||
18. **CrowdSec vor Traefik** als Bouncer fuer oeffentlich erreichbare Apps.
|
||||
|
||||
### P3 — Advanced (3–6 Monate)
|
||||
|
||||
19. Staging-Branch + zweites Komodo-Ziel in Tailscale-VM.
|
||||
20. Restore-Test-Automatisierung als CI (Gitea Actions oder Drone).
|
||||
21. Off-Site-Backup zu zweitem Ziel (zweites BorgBase-Repo oder Hetzner Storage Box).
|
||||
22. Cold-Standby-Konzept dokumentieren.
|
||||
23. Komodo-Self-Stack aus Komodo-Management herausnehmen, als handgepflegter `docker compose`-Service in `services/`.
|
||||
|
||||
### P4 — Nice-to-have / Spielwiese
|
||||
|
||||
24. Firefly III oder Actual Budget fuer `/mnt/user/finance`.
|
||||
25. Wandtablet im Flur mit Family-Dashboard.
|
||||
26. Home Assistant tiefer in ntfy-Workflows verzahnen (Frostwarnung, PV-Ueberschuss, Briefkasten).
|
||||
27. Ecowitt-Wetter-Dashboard, sobald HA→InfluxDB-Pipeline aus `docs/HOME_ASSISTANT_INFLUXDB_ECOWITT.md` laeuft.
|
||||
|
||||
## Regeln (aus CLAUDE.md, nicht verhandelbar)
|
||||
|
||||
- Git → Push → Komodo. Keine direkten Komodo-Edits, kein `push --force`.
|
||||
- Secrets nie ins Repo, nie loggen.
|
||||
- Appdata-Pfade nicht blind loeschen — vor Removal nach `/mnt/user/appdata/_archive/<ding>-removed-<datum>/` verschieben, 14 Tage warten.
|
||||
- Traefik dynamic config manueller Host-Sync.
|
||||
- Working-Tree-Status nur aus `git status --short`, nie aus `git diff` ueber Linux-Mount.
|
||||
- Nicht anfassen: Komodo native Auth (dokumentierte Ausnahme), Grafana/InfluxDB `user: "0"`, Image-Pinning ddns/glances/scrutiny.
|
||||
- Bei zwei gescheiterten Versuchen: stop, `docs/GITOPS_DRIFT_RUNBOOK.md` Pflichtmatrix, Operator fragen.
|
||||
|
||||
## Arbeitsmodus pro Block
|
||||
|
||||
Lesen → minimal aendern → `ops/policy-checks/check_repo.ps1` → Commit → Push → Komodo + Smoke-Test → eine Zeile in `docs/MIGRATION_LOG.md`.
|
||||
|
||||
## Fertig pro Block
|
||||
|
||||
Kurz an Operator: Commit-SHA, Smoke-Test-Beleg, ggf. neuer Watchpoint.
|
||||
@@ -0,0 +1,104 @@
|
||||
# FRITZ!Box Portfreigaben - Korrektur-Vorbereitung
|
||||
|
||||
Status: **umgesetzt 2026-05-28**, Doku bleibt als Historie und Begruendungs-Anker.
|
||||
Audit-Bezug: `docs/AUDIT_2026-05-25_TODO.md` Sprint 4 ("FRITZ!Box-Portfreigaben gegen Repo-Soll abgleichen") und `docs/NETWORK_INVENTORY.md`.
|
||||
|
||||
## Umsetzungs-Befund 2026-05-28
|
||||
|
||||
| Punkt | Entscheidung | Validierung |
|
||||
|---|---|---|
|
||||
| 1. `80/tcp` entfernen | **umgesetzt** | Mobilfunk-Test: `http://vault.kaleschke.info` Timeout, `https://...` weiter erreichbar. Lokal via LAN: HTTP->HTTPS-Redirect funktioniert weiter durch Traefik. |
|
||||
| 2. `222/tcp` ergaenzen | **bewusst NICHT umgesetzt** | Tailscale bleibt Operator-Pfad; GitHub-Mirror deckt DR-Bootstrap ab; SSH-Brute-Force-Vektor vermieden. Repo-Soll in `NETWORK_INVENTORY.md` und `MASTER_V2.md` Sektion 10 entsprechend angepasst. |
|
||||
| 3. UPnP-Selbstfreigabe `PC-192-168-178-71` | **umgesetzt** | "Selbstständige Portfreigaben fuer dieses Geraet erlauben" deaktiviert. Identifiziert als VONETS-WiFi-Bridge (Hostname `VONETS.COM`, MAC `00:17:13:2F:61:96`) — vermutlich Bridge zum SolarEdge-Wechselrichter. SolarEdge-Cloud-Sync ist outbound und benoetigt keine UPnP. |
|
||||
|
||||
Aktiver Endstand: ausschliesslich `443/tcp -> 192.168.178.58:443` (Traefik HTTPS) ist von WAN aus erreichbar.
|
||||
|
||||
## Aktueller FRITZ!Box-Befund (2026-05-27)
|
||||
|
||||
Aus dem Operator-Live-Check der FRITZ!Box-UI:
|
||||
|
||||
- aktiv: `80/tcp` -> `192.168.178.58`
|
||||
- aktiv: `443/tcp` -> `192.168.178.58`
|
||||
- fehlt: `222/tcp` -> `192.168.178.58` (Gitea-SSH)
|
||||
- zusaetzlich gemeldet: eine Portfreigabe durch das Geraet `PC-192-168-178-71` (Selbst-Freigabe per UPnP)
|
||||
|
||||
## Soll-Stand laut Repo
|
||||
|
||||
`docs/NETWORK_INVENTORY.md` definiert genau zwei aktive Portfreigaben:
|
||||
|
||||
| Erwartete Freigabe | Ziel | Begruendung |
|
||||
|---|---|---|
|
||||
| `443/tcp` -> `192.168.178.58:443` | Traefik HTTPS | einziger Public-HTTPS-Einstieg, Wildcard-Zert ueber Cloudflare-DNS-Challenge |
|
||||
| `222/tcp` -> `192.168.178.58:222` | Gitea SSH | Git-SSH-Push/Pull; dokumentierte Ausnahme |
|
||||
|
||||
Port `80/tcp` ist im Cloudflare-DNS-Challenge-Modell **nicht** notwendig.
|
||||
|
||||
## Drei Korrektur-Punkte
|
||||
|
||||
### 1. `80/tcp` entfernen
|
||||
|
||||
Begruendung:
|
||||
|
||||
- ACME laeuft als Cloudflare-DNS-Challenge (`traefik/docker-compose.yml`), nicht als HTTP-01.
|
||||
- Traefik leitet intern jeden HTTP-Request auf `https://` weiter; ein WAN-`80`-Listener bietet keinen Mehrwert, oeffnet aber einen zusaetzlichen Angriffsvektor (Header-/Method-Scanning, Open-Redirect-Versuche bevor TLS terminiert).
|
||||
- Innerhalb des LAN funktioniert die Browser-Auto-HTTPS-Umleitung weiter ueber AdGuard-DNS.
|
||||
|
||||
Empfehlung: WAN-Eintrag `80/tcp` in FRITZ!Box-UI **entfernen** nach Operator-Go.
|
||||
|
||||
Validierung nach Aenderung:
|
||||
|
||||
```bash
|
||||
# WAN-seitiger Test, idealerweise von einem Geraet im Mobilfunknetz oder Tailscale-Exit-Node
|
||||
curl -sI http://kaleschke.info/ # erwartet: Connection refused / Timeout
|
||||
curl -sI https://vault.kaleschke.info/ # erwartet: HTTP/2 200 oder Authelia-Redirect
|
||||
```
|
||||
|
||||
### 2. `222/tcp` ergaenzen (nur wenn externes Git-SSH wirklich gewuenscht)
|
||||
|
||||
Frage an Operator: Wird `git@git.kaleschke.info -p 222` von extern gebraucht? Hinweise:
|
||||
|
||||
| Pro `222/tcp` extern | Contra `222/tcp` extern |
|
||||
|---|---|
|
||||
| Push/Pull vom unterwegs-Laptop ohne Tailscale | Tailscale ist schon der Operator-Pfad, deckt das voll ab |
|
||||
| GitHub-Mirror-Bootstrap funktioniert dann auch ohne Tailscale | GitHub-Push-Mirror laeuft automatisch von Gitea aus, braucht kein WAN-SSH |
|
||||
| Externe Webhooks gegen Git push (nicht in Nutzung) | weniger Angriffsflaeche fuer SSH-Brute-Force |
|
||||
|
||||
Empfehlung: `222/tcp` **nicht** ergaenzen, solange Tailscale stabil verfuegbar ist. Stattdessen `docs/NETWORK_INVENTORY.md` und `HOMELAB_ARCHITECTURE_MASTER_V2.md` darauf abgleichen, dass Gitea-SSH bewusst LAN/Tailscale-only ist.
|
||||
|
||||
Wenn Operator entscheidet, `222/tcp` doch extern zu oeffnen: zusaetzlich SSH-Login auf Key-only setzen, Brute-Force-Limits in Gitea pruefen, `docs/NETWORK_INVENTORY.md` "Erwartete Freigabe"-Tabelle aktualisieren.
|
||||
|
||||
### 3. UPnP-Selbstfreigabe von `PC-192-168-178-71` deaktivieren
|
||||
|
||||
Begruendung:
|
||||
|
||||
- Geraete-initiierte Portfreigaben (UPnP) sind ausserhalb der Repo-Sollkonfiguration.
|
||||
- Welcher Port von welchem Programm geoeffnet wurde, ist aus der FRITZ!Box-UI heraus nicht versionierbar.
|
||||
- Wenn der Port gebraucht wird, gehoert er als bewusste Operator-Freigabe in `docs/NETWORK_INVENTORY.md`.
|
||||
|
||||
Empfehlung in zwei Stufen:
|
||||
|
||||
1. FRITZ!Box-UI: in den Geraete-Details fuer `PC-192-168-178-71` die aktuelle Selbstfreigabe-Liste pruefen und mit dem Operator besprechen.
|
||||
2. Wenn der Port nicht gebraucht wird: Selbstfreigabe deaktivieren. Optional: UPnP global pro Geraet abschalten ("Selbststaendige Portfreigaben fuer dieses Geraet erlauben" abwaehlen).
|
||||
|
||||
## Schutzregeln
|
||||
|
||||
- Keine Router-Aenderung ohne ausdrueckliches Operator-Go.
|
||||
- Nach jeder Aenderung: `docs/NETWORK_INVENTORY.md` Abschnitt "FRITZ!Box (WAN -> Host)" gegen den neuen UI-Stand abgleichen.
|
||||
- Aenderung in `docs/MIGRATION_LOG.md` als kurzer Eintrag dokumentieren (was/warum/Validierung).
|
||||
- Bei `80/tcp`-Entfernung kurz prufen, ob irgendein externer Dienst noch HTTP-01 nutzen wollte (sollte nicht der Fall sein).
|
||||
|
||||
## Nicht Teil dieser Vorbereitung
|
||||
|
||||
- FRITZ!OS-Update 8.21 -> aktuell. Das ist eigenes Service-Fenster und braucht WAN/Tailscale-Aufbau-Beobachtung.
|
||||
- IPv6-Exposure. Wenn Telekom IPv6 zustellt und Apps via Cloudflare-AAAA erreichbar sind, kann WAN-Filter pro Port noetig werden. Separater Doku-Punkt in `docs/NETWORK_INVENTORY.md` Offene Entscheidungen.
|
||||
- Mobilfunk-Failover-Stick. Bewusst nicht eingerichtet.
|
||||
|
||||
## Offene Punkte
|
||||
|
||||
| Status | Punkt | Naechster Schritt |
|
||||
|---|---|---|
|
||||
| erledigt 2026-05-28 | `80/tcp` entfernen | umgesetzt, Mobilfunk-validiert |
|
||||
| erledigt 2026-05-28 (bewusst nicht) | `222/tcp` Entscheidung | bleibt Tailscale-only; Repo-Soll entsprechend angepasst |
|
||||
| erledigt 2026-05-28 | UPnP-Freigabe `PC-192-168-178-71` | UPnP-Selbstfreigabe-Recht fuer VONETS-Bridge deaktiviert |
|
||||
| offen | FRITZ!OS 8.21 Update | Service-Fenster, separat geplant |
|
||||
| offen | IPv6-Exposure pruefen | bei naechstem WAN-Touch mit erfassen |
|
||||
+2
-2
@@ -7,7 +7,7 @@ Zweck: Startpunkt fuer einen neuen Chat, ohne das komplette Repo erneut zu lesen
|
||||
- Incident: NTFS-Cache-Vorfall ab 2026-05-11.
|
||||
- Host: Unraid `Kallilabcore`, SSH `root@192.168.178.58`.
|
||||
- Root Cause: Cache war NTFS/ntfs3; Disk1 ist noch NTFS/ntfs3 und wird spaeter separat migriert.
|
||||
- Recovery-Prinzip: `docs/STORAGE_LAYOUT.draft.md` ist fuer diesen Restore bindend, obwohl die Datei noch `.draft` heisst.
|
||||
- Recovery-Prinzip: `docs/STORAGE_LAYOUT.md` ist bindend. Zum Zeitpunkt dieses Handoffs hiess die Datei noch `docs/STORAGE_LAYOUT.draft.md`.
|
||||
- Keine Stacks starten, wenn ein Pfad/Setting gegen Storage Layout, Restore Matrix oder Architecture Master verstoesst.
|
||||
|
||||
## Host-Zustand
|
||||
@@ -86,4 +86,4 @@ Verifikation:
|
||||
|
||||
## Startprompt fuer neuen Chat
|
||||
|
||||
Lies zuerst `docs/RECOVERY_HANDOFF_2026-05-15.md`, dann `docs/STORAGE_LAYOUT.draft.md`, `docs/RESTORE_MATRIX.md` und nur die Compose-Dateien des naechsten betroffenen Stacks. Fuehre den KalliLab-CORE-Restore token-sparend fort. Nichts erfinden, keine Container starten, wenn etwas gegen Storage Layout verstoesst. Backrest und WD MyBookLive Duo sind entfernt und duerfen nicht wieder ins Setup.
|
||||
Lies zuerst `docs/archive/2026-05/RECOVERY_HANDOFF_2026-05-15.md`, dann `docs/STORAGE_LAYOUT.md`, `docs/RESTORE_MATRIX.md` und nur die Compose-Dateien des naechsten betroffenen Stacks. Fuehre den KalliLab-CORE-Restore token-sparend fort. Nichts erfinden, keine Container starten, wenn etwas gegen Storage Layout verstoesst. Backrest und WD MyBookLive Duo sind entfernt und duerfen nicht wieder ins Setup.
|
||||
@@ -0,0 +1,522 @@
|
||||
# Strategische Bewertung KalliLab CORE
|
||||
|
||||
> **Status (Stand 2026-05-30): Historischer Snapshot vom 2026-05-23, inhaltlich grossteils ueberholt.**
|
||||
>
|
||||
> Dieses Dokument bleibt im Repo als Audit-Anker und als "wo standen wir am 2026-05-23". Die konkreten Befunde, Top-5-Listen und Mehrwert-Fahrplaene sind durch den Audit-Zyklus 2026-05-25 zu einem grossen Teil **abgearbeitet, bewusst nicht umgesetzt oder explizit geparkt**.
|
||||
>
|
||||
> - **Nicht als TODO-Liste lesen.** Aktuelle Arbeitsliste: `docs/AUDIT_2026-05-25_TODO.md`.
|
||||
> - **Originaltext nicht aendern.** Statt Inline-Annotationen steht der pro-Punkt-Status in einer Tabelle am Ende des Dokuments (Abschnitt "Status-Anhang 2026-05-30").
|
||||
> - **Schulnote 2- gilt nicht mehr.** Mit den Konsolidierungen seit 2026-05-25 sind die meisten Notenabzieher behoben; eine neue Note wuerde hier eher bei 1- bis 2 landen, ist aber kein Selbstzweck.
|
||||
|
||||
Stand: 2026-05-23
|
||||
Bewertet von: externer Blick auf den Repo-Sollzustand
|
||||
|
||||
> Diese Bewertung ist bewusst kein Sicherheits- oder Konfigurations-Audit, sondern eine ganzheitliche Einordnung: was das Setup heute leistet, wo es stark ist, wo es zu komplex ist, und wo der nächste echte Mehrwert liegt.
|
||||
|
||||
---
|
||||
|
||||
## Vorbemerkung und Methode
|
||||
|
||||
Bewertet wurde der Repo-Stand auf `master`, nicht der Live-Zustand auf dem Host. Grundlage waren `HOMELAB_ARCHITECTURE_MASTER_V2.md`, `docs/WORKFLOW.md`, `docs/SERVICE_CATALOG.md`, `docs/REPO_MAP.md`, `docs/DISASTER_RECOVERY.md`, `docs/RESTORE_MATRIX.md`, `docs/SECRETS_MAP.md`, `docs/AI_CONTEXT.md`, `docs/GITOPS_DRIFT_RUNBOOK.md`, `docs/HOME_ASSISTANT_INFLUXDB_ECOWITT.md`, `docs/ALERTING_MAP.md`, `ops/borg-ui/BACKUP_SCOPE.md`, `ops/policy-checks/last-report.md`, `ops/restore-tests/schedule.md`, repräsentative Compose-Dateien (Paperless, Monitoring) sowie die Memory-Notiz zum Post-Restore-Sprint.
|
||||
|
||||
Ehrliche Einschätzung in einem Satz: Das ist kein Bastel-Homelab mehr. Das ist eine kleine private Plattform mit dokumentationsbasierter Disziplin, die ein paar Lasten mitschleppt, die man jetzt bewusst loswerden sollte, bevor noch mehr dazukommt.
|
||||
|
||||
---
|
||||
|
||||
## 1. Architektur und Grundidee
|
||||
|
||||
Der Aufbau ist nicht gewachsen, er ist gestaltet. Das sieht man sofort an drei Stellen:
|
||||
|
||||
Erstens trennt das Repo Verantwortlichkeiten konsequent über die Top-Level-Ordner `core/`, `security/`, `infra/`, `apps/`, `ops/`, `host-services/`, `monitoring/`, `traefik/` und `services/`. Das ist keine willkürliche Kosmetik, das spiegelt die Tier-Hierarchie aus DR und Restore wider. Ein neuer Dienst weiß durch das Einordnungsschema in `HOMELAB_ARCHITECTURE_MASTER_V2.md` Abschnitt 6 sofort, wo er hin gehört und in welche Netze er kommt. Das ist überdurchschnittlich.
|
||||
|
||||
Zweitens ist das Netzmodell schlicht und gleichzeitig diszipliniert: `frontend_net` für alles mit Web-UI oder Internetbedarf, `backend_net` `internal: true` für DB/Redis, und app-interne Netze (`mealie_internal`, `immich_default`, `nextcloud_internal`, `monitoring_net`) für Stack-Isolation. Es gibt keine Kunstnetze wie `admin_net` oder `media_net` aus reiner Symmetriesucht. Das ist genau die Linie, die viele Homelabs verfehlen, weil sie entweder alles in ein Bridge-Netz werfen oder fünfzehn semantische Netze erfinden, ohne dass sie was tun.
|
||||
|
||||
Drittens ist die Source-of-Truth-Hierarchie explizit (Gitea Online → lokaler Clone → Komodo → Docker → Host) und es gibt ein Drift-Runbook (`docs/GITOPS_DRIFT_RUNBOOK.md`) mit echter Messreihenfolge. Das schlägt 95% der "Selfhoster mit Portainer und Glück".
|
||||
|
||||
**Wo es trotzdem hakt:** Die Trennung Monitoring/Spielerei ist noch nicht sauber. Es gibt `ops/grafana-influxdb` (Altstand), `ops/loki` (Altstand) und `monitoring/` (Zielstack) gleichzeitig im Repo. Solange die Migration nicht abgeschlossen und die Altstände nicht entfernt sind, ist das echte Doppelpflege-Risiko — und genau so entstehen die Bugs, die man nachher zwei Wochen sucht. Die Doku sagt klar "nicht parallel betreiben", aber das Repo macht es trotzdem möglich. Das ist eine offene Baustelle, kein Architekturproblem.
|
||||
|
||||
**Note für diesen Block: 1-2.**
|
||||
|
||||
---
|
||||
|
||||
## 2. Nutzen und Mehrwert
|
||||
|
||||
Hier wird es ehrlicher: Das Setup hat sehr viel Substanz, aber auch klare Spielereien.
|
||||
|
||||
Echter Alltagsnutzen, der den Aufwand rechtfertigt:
|
||||
|
||||
- **Vaultwarden** als Passwort-Tresor — der Klassiker, der wirklich täglich genutzt wird.
|
||||
- **Paperless-ngx** mit Scan-Inbox, Barcode/ASN-Workflow und Tika aus — das ist klassische Familien-Dokumentenverwaltung mit echtem Wert, sobald man Briefe digitalisiert. Die Barcode-Konfiguration (`PAPERLESS_CONSUMER_BARCODE_DPI=600`, `PAPERLESS_CONSUMER_ENABLE_ASN_BARCODE=1`) zeigt, dass der Workflow durchdacht ist.
|
||||
- **Immich** für Fotos, mit `family_archive`-Mount — das ersetzt sinnvoll Google Photos für eine Familie.
|
||||
- **Nextcloud** für Dateifreigabe und WebDAV/CardDAV — wenn das wirklich genutzt wird, ist es ein echtes Google-Drive/iCloud-Replacement.
|
||||
- **Mealie** für Rezepte — nett, aber das ist ein Lebensmittel-Ding, nicht Infrastruktur.
|
||||
- **Mail-Archiver** — sehr persönlicher Mehrwert, wenn IMAP-Archive aus GMX/Gmail langfristig durchsuchbar bleiben sollen.
|
||||
- **ntfy** als Push-Backbone für `homelab-alerts` und `homelab-info` — operativ unverzichtbar.
|
||||
- **Gitea** — primär als GitOps-Quelle. Für andere Projekte ein Bonus.
|
||||
- **AdGuard Home + Unbound** — ja, das hat Alltagsnutzen für alle Geräte im LAN.
|
||||
- **Tailscale** — Remote-Zugang ohne Port-Freigabe nach außen, klar wertvoll.
|
||||
|
||||
Spielerei oder Overengineering, das ehrlich auf den Prüfstand gehört:
|
||||
|
||||
- **Plex zusätzlich zu Jellyfin**: Beide sind Medienserver mit derselben Bibliothek (`/mnt/user/media`, `/mnt/user/photos`). Plex bringt zwar Remote-Streaming und bessere Clients, Jellyfin bringt native Open-Source-Auth und keine Lizenz. Einen davon kann man weglassen — die ehrlichste Antwort ist, das nach 30 Tagen Nutzungs-Tracking zu entscheiden.
|
||||
- **Glance + Homepage + Komodo-UI als drei parallele Dashboards**: Homepage als Startseite, Glance als zweites Dashboard mit Widgets, Komodo als Stack-Sicht. Hier ist mindestens eines redundant. Glance ist erst seit kurzem live und wirkt eher wie "weil cool" als "weil nötig".
|
||||
- **paperless-gpt**: Coole Idee (LLM für Tagging), aber wenn das nicht aktiv genutzt wird, ist es nur ein Container, der idle Ressourcen frisst. Frage an dich: Wann hast du das letzte Mal eine GPT-Vorschlags-Tag-Liste angenommen?
|
||||
- **BentoPDF**: Ist als "vorbereitet" markiert, Fachabnahme offen. Wenn du in zwei Monaten noch keine PDFs verarbeitet hast: weglassen.
|
||||
- **Hermes-Agent**: Das ist die eindeutigste Spielerei. Ein LLM-Agent über SSH-Runner zu einer separaten VM, mit eigenem Dashboard, dessen NAS-Seite bewusst deaktiviert ist, weil die VM-Seite "offen" ist. Komplexes Modell C, abhängig von einer dedizierten Linux-VM, mit Provider-Keys und Dashboard-Domain. Das ist klassisches Nerd-Lieblingsprojekt-ohne-klaren-Alltagsnutzen-Symptom. Solange du nicht ehrlich beschreiben kannst, was Hermes für dich täglich tut, ist es Reifegrad "Experiment".
|
||||
- **Speedtest-Tracker**: Nett für Monitoring der ISP-Qualität, aber ein einziger Speedtest-Container für eine private Leitung ist eher "ich messe gerne" als "ich brauche das wirklich".
|
||||
- **code-server**: Web-IDE im Browser. Sinnvoll, wenn du wirklich vom iPad aus arbeitest. Sonst: VSCode lokal reicht.
|
||||
|
||||
Use Cases, die echten Mehrwert hätten und fehlen:
|
||||
|
||||
- **Finanzen**: Im DR-Doc steht `/mnt/user/finance` als Share, aber kein App-Stack. Firefly III oder Actual Budget würden hier sofort spürbaren Alltagsnutzen liefern — Konten konsolidieren, Budgets verfolgen, Steuer-Vorbereitung.
|
||||
- **Familien-SSO**: Du hast Authelia, aber Authelia ist primär für Admin-UIs konfiguriert. Wenn deine Familie Nextcloud, Immich, Mealie und Vaultwarden mit einem Login nutzen könnte (Authelia OIDC-Provider), wäre das ein echter Mehrwert für andere als dich.
|
||||
- **Smartphone-Foto-Auto-Backup**: Immich kann das nativ. Wenn das nicht eingerichtet ist und alle Familien-Smartphones automatisch in `immich` landen würden, wäre das die Killer-App für deine Frau und Kinder, nicht für dich.
|
||||
- **Tagliche Familien-Übersicht auf einem Wandtablet**: Homepage oder Glance auf einem alten Tablet im Flur, mit Kalender (Nextcloud), Wetter (Ecowitt), und ntfy-Notifications.
|
||||
- **Kalender/Aufgaben/CardDAV-Nutzung**: Nextcloud kann das, aber ich sehe in der Doku keinen Hinweis, dass die Familie das tatsächlich nutzt. Wenn nicht: Migration weg von Google Calendar/iCloud wäre ein echter Souveränitäts-Gewinn.
|
||||
|
||||
**Note für diesen Block: 2-3.** Die infrastrukturelle Substanz ist top, aber der Anteil "Container läuft, weil ich ihn ausprobieren wollte" ist höher als nötig.
|
||||
|
||||
---
|
||||
|
||||
## 3. Best Practices
|
||||
|
||||
Was richtig gut ist gemessen an dem, was professionelle Setups machen würden:
|
||||
|
||||
- **Image-Pinning mit Tag und Digest** für Stateful Services (Postgres 17.9, Redis 7.4-alpine, Mongo 7.0.32, alle mit `@sha256:...`). Das machen die wenigsten Homelabs. Echt vorbildlich.
|
||||
- **Secrets via Docker `_FILE`-Mounts oder Komodo Stack ENV, niemals im Git** — und das ist konsequent durchgezogen, inklusive `.gitignore` für `.env`-Dateien und expliziter Doku in `docs/SECRETS_MAP.md`.
|
||||
- **`no-new-privileges:true`** als Standard, mit dokumentierten Ausnahmen für Scrutiny (SMART) und Glances (Host-Observability) statt versteckter Lockerungen.
|
||||
- **Policy-as-Code light** über `ops/policy-checks/check_repo.ps1` — der letzte Report zeigt 0 Critical und 4 dokumentierte Warnings. Das ist Tooling-Disziplin, die viele Firmen nicht haben.
|
||||
- **Restore-Tests mit Schedule** (`ops/restore-tests/schedule.md`): wöchentliche Freshness-Checks, monatliche Mini-Restores für Vaultwarden und Gitea, alle zwei Monate Paperless. Erfolg ist explizit als "Smoke-Test passt", nicht "Container startet" definiert. Das ist seltene Reife.
|
||||
- **Pre-Backup-Dumps statt rohe Live-DB-Verzeichnisse als primärer Restore-Pfad** — das ist die Lehre, die viele erst nach dem ersten kaputten Restore lernen.
|
||||
- **Posture-Check + Docker-Critical-Events → ntfy** als Live-Alarmierung, bereits mit Wiederholungsschutz (`ALERT_REPEAT_SECONDS=86400`) und Dedup. Das ist Operations-Reife.
|
||||
- **Cloudflare DNS Challenge für ACME** statt HTTP-01, ermöglicht Wildcard-Zertifikate und keine Port-80-Abhängigkeit für Erneuerung.
|
||||
- **GitOps mit Webhook-Pflicht für neue Stacks** (`docs/WORKFLOW.md`, Abschnitt "Pflicht bei neuen Komodo-Stacks"). Das verhindert "deployed-once-then-forgotten"-Stacks.
|
||||
|
||||
Was Standard wäre und du sinnvoll davon abweichst:
|
||||
|
||||
- **Komodo bewusst ohne pauschale ForwardAuth-Middleware** — richtig, weil Webhooks, API und Periphery sonst brechen. Die meisten würden hier blind Authelia davor schalten und dann zwei Tage debuggen.
|
||||
- **Authelia ohne Redis-Session-Backend** — bewusste Vereinfachung. Du bezahlst dafür mit Re-Login nach Authelia-Restart, gewinnst dafür weniger Tier-1-Abhängigkeiten. Vertretbarer Trade-off.
|
||||
- **Traefik dynamic config als manuelle Host-Sync-Ausnahme** — pragmatisch dokumentiert, statt eines komplexen Auto-Sync-Workarounds.
|
||||
|
||||
Wo du gefährlich von Best Practice abweichst:
|
||||
|
||||
- **Externer Repo-Mirror als DR-Voraussetzung ist offenes TODO** (`docs/DISASTER_RECOVERY.md`, Abschnitt 11). Wenn Gitea ausfällt — und Gitea hängt auch noch an Traefik und PostgreSQL — kannst du Komodo nicht aus Git deployen, kannst die Repo-Doku nicht lesen, und je nach Schaden hast du den lokalen Clone als einzigen Pfad. Das ist ein echter Single Point of Failure. Ein Push-Mirror nach GitHub/GitLab (privat) oder zumindest ein versionierter Sync nach BorgBase würde das in 30 Minuten lösen.
|
||||
- **Unraid USB-Flash-Backup ist offenes TODO**. Wenn der USB-Stick stirbt, ist das nicht das Ende der Welt (Daten leben), aber es kostet einen vollen Wiederaufsetzungs-Tag. Unraid hat dafür einen eingebauten Backup-Mechanismus.
|
||||
- **Komodo Self-Stack Drift Mai 2026**: Du hattest schon einen Vorfall, wo Komodo selbst nicht mehr sauber managebar war ("Recovery-ENV als Tier-1-Secret-Material"). Das Bootstrap-Problem — Komodo verwaltet Komodo — ist nicht gelöst, nur dokumentiert. Eine echte Lösung wäre: Komodo-Self-Stack explizit aus Komodo herauslassen und nur als `docker compose`-Script in `services/` halten.
|
||||
- **Kein Fail2Ban / CrowdSec vor Traefik**. Vaultwarden und Nextcloud sind im Internet erreichbar mit eigener Auth. Die meisten Anti-Brute-Force-Maßnahmen liegen in den Apps selbst, nicht auf Layer 7. Bei einer ernsten Bot-Welle würde Authelia die Last tragen, ohne IP-Bans auszusprechen. CrowdSec als Bouncer für Traefik wäre eine sinnvolle Härtung mit überschaubarem Aufwand.
|
||||
|
||||
**Note für diesen Block: 2.**
|
||||
|
||||
---
|
||||
|
||||
## 4. Nerd-Level / Advanced Homelab
|
||||
|
||||
Was sehr erfahrene Selfhoster mit so einem Repo zusätzlich machen würden:
|
||||
|
||||
- **Renovate Bot oder ein vergleichbares Image-Update-Tracking**: Du pinnst Digests, was richtig ist, aber damit hast du dich auch in die manuelle Update-Pflicht begeben. Renovate gegen Gitea würde wöchentliche PRs für neue Patch-Versionen auf master öffnen, die du mergen oder ignorieren kannst. Das ist deutlich besser als "irgendwann manuell den Digest aktualisieren".
|
||||
- **Staging-Path**: Aktuell hast du master und das ist gleichzeitig produktiv. Ein zweiter Branch (`staging`) der gegen einen zweiten Komodo-Server (in einer Tailscale-VM oder einem zweiten Unraid-Share) deployed, würde Risiko-Aenderungen testbar machen. Das ist viel Aufwand für ein Homelab, aber wenn dich die Stabilität ernsthaft kümmert, ist es der nächste Reifegrad.
|
||||
- **OIDC-Provider statt nur ForwardAuth**: Authelia kann OIDC. Wenn Nextcloud, Immich, Grafana, Komodo (theoretisch), Vaultwarden (via OIDC-Bridge) per SSO laufen, ist das die "echte" Konsolidierung. Heute hast du ForwardAuth für Admin-Dienste, aber Apps mit eigener Auth (Nextcloud, Immich, Jellyfin) sind Eigeninseln.
|
||||
- **Restore-Tests automatisiert in CI**: Du hast Skripts und Cron-Slots, aber kein CI gegen das Repo, das die Restore-Test-Skripte syntaktisch und semantisch prüft. Ein Gitea Actions oder Drone-Setup auf dem Host könnte das gegen jeden Commit laufen lassen.
|
||||
- **Backup-Test-Härtung: Restore in eine echte Test-Domain mit Traefik-Route hinter Authelia** (heute bewusst ohne Domain — siehe `docs/RESTORE_MATRIX.md`). Das ist eine bewusste Entscheidung, würde aber einen "End-to-End restore drill" möglich machen, der einmal pro Quartal komplett durchläuft.
|
||||
- **Disk1 NTFS → XFS Phase 2**: Im Repo dokumentiert, im posture-check temporär toleriert mit `ALLOW_DISK1_NTFS=1`. Das ist die offensichtlichste offene Hardening-Baustelle.
|
||||
- **Loki-Retention und Log-Volume mal anschauen**: 30 Tage Retention ist gut, aber im aktuellen Stand wirst du irgendwann Storage-Probleme bekommen, wenn du nicht weißt, wie viel der Stack pro Tag produziert.
|
||||
|
||||
Was sie bewusst weglassen würden:
|
||||
|
||||
- **Hermes-Agent**: Genau dieses "ich baue mir einen Agenten der über SSH meine VM bedient und ein Dashboard hat" ist das, wovor erfahrene Leute nach dem dritten Homelab warnen. Es bringt Komplexität, Wartungslast und keine messbare Reduktion deiner manuellen Arbeit. Wenn Hermes nicht in den nächsten 60 Tagen produktiv und unverzichtbar wird: entfernen.
|
||||
- **Drei Dashboard-Tools**: Sie würden eines wählen (vermutlich Homepage), die anderen rauswerfen.
|
||||
- **Zwei Medienserver**: Plex und Jellyfin parallel ist Tool-Sammlerei.
|
||||
- **Eigenes paperless-gpt-Container** wenn nicht aktiv im Workflow: lieber das LLM ein-zwei mal manuell auf eine PDF werfen als einen Container 24/7 idle laufen lassen.
|
||||
|
||||
**Note für diesen Block: 2-3.** Sehr nahe am nächsten Reifegrad, aber an drei, vier Stellen würde erfahrene Hand jetzt entrümpeln statt erweitern.
|
||||
|
||||
---
|
||||
|
||||
## 5. Betrieb und Wartbarkeit
|
||||
|
||||
Hier ist die Bewertung am eindeutigsten positiv. Dieses Setup ist langfristig wartbar.
|
||||
|
||||
Die Dokumentation ist auf einem Niveau, das ich selten sehe. `docs/SERVICE_CATALOG.md` ist ein vollständiger Dienst-Katalog mit Restore-Quelle, Smoke-Test und Besonderheiten pro Dienst. `docs/REPO_MAP.md` ist eine technische Landkarte. `docs/RESTORE_MATRIX.md` ist nicht nur "wo ist das Backup", sondern "was ist die führende Quelle", "welche Dumps", "welche Secrets müssen vor Start da sein", "was ist der Smoke-Test". Das ist Doku, die in sechs Monaten noch funktioniert.
|
||||
|
||||
Der Workflow ist klar (`docs/WORKFLOW.md`): Fetch → Pull → ändern → Commit → Push → Komodo. Es gibt eine explizite Stop-Regel ("wenn zwei Reparaturversuche nicht funktionieren, Pflichtmatrix ausfüllen"), die viele Selfhoster nicht haben und stattdessen in Mut-Spiralen rutschen.
|
||||
|
||||
Drei Stellen, wo Wartbarkeit gefährdet ist:
|
||||
|
||||
- **Hermes-Agent**: Spätestens nach sechs Monaten ohne aktive Pflege verstehst du die Model-C-Architektur nicht mehr ohne `ops/hermes-agent/README.md` zu lesen — und dann ist die Frage, warum überhaupt.
|
||||
- **Doppelter Monitoring-Stack (`ops/grafana-influxdb`, `ops/loki`, `monitoring/`)**: Solange beide Welten im Repo sind, vergisst du in einem halben Jahr, welche live ist. Die Migration muss abgeschlossen und die Altstände müssen entfernt werden.
|
||||
- **Authelia Repo-Baseline vs. Host-Config**: Du dokumentierst selbst, dass die Repo-`configuration.yml` "manuell auf den Host gemerged" werden muss, mit OIDC und Secrets hostseitig. Das ist Drift-Risiko per Design. Ein zweiter Mechanismus (z. B. das manuelle Pendant zum Traefik-dynamic-Sync) oder mindestens ein expliziter Diff-Check vor jeder Auth-Änderung wäre Pflicht.
|
||||
|
||||
**Note für diesen Block: 1-2.**
|
||||
|
||||
---
|
||||
|
||||
## 6. Sicherheit und Zugriff
|
||||
|
||||
Du hast eine durchdachte Schichtung:
|
||||
|
||||
- Authelia für Admin-UIs (`uptime`, `borg`, `files`, `code`, `grafana`, `monitoring`, `pdf`, `glance`, `glances`, `scrutiny`, `paperless-gpt`, `speedtest`, `hermes`, `traefik`-Dashboard, `homepage`).
|
||||
- Native App-Auth für User-Apps (`vaultwarden`, `nextcloud`, `immich`, `jellyfin`, `paperless`).
|
||||
- Komodo mit eigener Auth ohne ForwardAuth (bewusste Ausnahme).
|
||||
- Tailscale für Admin-Zugriff von außen.
|
||||
|
||||
Was wirklich gut ist:
|
||||
|
||||
- Authelia mit Argon2id, `iterations=3`, `memory=65536`, `parallelism=4`, `key_length=32`, `salt_length=16` — das ist solide Konfiguration, nicht Default-Müll.
|
||||
- Secrets durchgängig per File-Mount oder Komodo Stack ENV, nie im Compose im Klartext.
|
||||
- Gitea Webhook-Allowlist (`GITEA__webhook__ALLOWED_HOST_LIST=komodo-core,localhost,127.0.0.1,192.168.178.0/24`) und Registrierung deaktiviert — das schließt Webhook-SSRF-Vektoren.
|
||||
- `cloudflare_dns_api_token` als Docker Secret, nicht als ENV.
|
||||
|
||||
Wo du härter trennen solltest:
|
||||
|
||||
- **AdGuard Admin-Port 8082 ist direkt am LAN gebunden ohne Authelia**. Das ist im Architekturdokument als offenes TODO ("Block F") markiert. Im Home-LAN ist das verschmerzbar, aber wenn du eines Tages einen Gast im WLAN hast oder ein IoT-Gerät kompromittiert wird, ist das ein direkter Pfad in die DNS-Konfiguration.
|
||||
- **Nextcloud läuft ohne ForwardAuth** (bewusst wegen WebDAV/CardDAV). Wenn deine Familie schwache Passwörter setzt, ist Nextcloud im Internet das primäre Angriffsziel. Nextcloud-eigene Maßnahmen (Brute-Force-Protection, 2FA-Pflicht für Admin) sollten dokumentiert aktiv sein.
|
||||
- **2FA-Pflicht in Authelia**: In der Doku nicht klar erwähnt. Wenn 2FA nur "optional" ist, läuft die Härtung ins Leere.
|
||||
|
||||
Wo Komfort wichtiger ist und das sinnvoll so bleibt:
|
||||
|
||||
- Komodo ohne ForwardAuth — richtig.
|
||||
- Authelia ohne Redis-Session-Backend — vertretbar.
|
||||
- Plex/Jellyfin mit nativer Auth — sinnvoll, weil die Clients eigene Auth machen.
|
||||
|
||||
**Note für diesen Block: 2.**
|
||||
|
||||
---
|
||||
|
||||
## 7. Backup und Disaster Recovery
|
||||
|
||||
Das ist eine der stärksten Säulen.
|
||||
|
||||
Was du richtig machst:
|
||||
|
||||
- Borg statt Backrest — dokumentierte Entscheidung, eine Backup-Technologie statt zwei.
|
||||
- **Pre-Backup-Dumps als kanonische Restore-Quelle**, nicht rohe Live-DB-Verzeichnisse. Explizit dokumentiert in `ops/borg-ui/BACKUP_SCOPE.md` ("Do not back up raw live database storage directories as the primary recovery artifact").
|
||||
- **Restore-Tests mit Schedule** und expliziter Erfolgsregel ("Container läuft reicht nicht — Smoke-Test muss greifen").
|
||||
- Dump-Skript für SQLite-Container (Gitea, Vaultwarden, Uptime-Kuma, Speedtest-Tracker), BoltDB-Snapshot für Filebrowser, `pg_dump` für die einzelnen Postgres-Datenbanken, `mongodump` für Komodo-Mongo.
|
||||
- Borg-Scope erweitert um `/mnt/user/services` für GitOps-Recovery (Repo, Stack-Workspaces, Posture-Check-State).
|
||||
- Tier-Modell in `docs/RESTORE_MATRIX.md` mit klarer Reihenfolge.
|
||||
- Dokumentierte Restore-Lab-Praxis: Testpfad `/mnt/user/backups/restore-lab/<dienst>`, Reports unter `/mnt/user/backups/restore-reports`, ohne Traefik-Route — keine Vermischung von Test und Produktion.
|
||||
|
||||
Was wirklich offen ist:
|
||||
|
||||
- **Externer Backup-Mirror oder Off-Site-Ziel**: BorgBase ist erwähnt (`ops/borg-ui` als Borg-UI auf BorgBase-Repo), aber die Frage "was passiert, wenn der Unraid-Host und BorgBase gleichzeitig down sind" hat keine dokumentierte Antwort. Zwei Repos (z. B. BorgBase + ein zweites lokales NAS oder ein Hetzner Storage Box) wären die Standardlösung.
|
||||
- **Externer Repo-Mirror als DR-Voraussetzung** — in DR.md als TODO markiert. Wenn Gitea nicht aufsteht, ist das Repo nur über deinen lokalen Clone erreichbar.
|
||||
- **Unraid USB-Flash-Backup** — in DR.md als TODO markiert.
|
||||
- **Borg-Passphrase extern sicher hinterlegt** — als TODO markiert. Das ist die typische "wenn das Haus brennt"-Frage. Vaultwarden hilft dir nicht, wenn Vaultwarden gerade restauriert werden soll. Eine zweite Kopie der Passphrase (verschlüsselt auf einem USB-Stick im Bankschließfach, oder bei einem Familienmitglied) ist Standard.
|
||||
- **Komodo-Mongo Dump nach Major-Upgrades verifizieren** — als Watchpoint dokumentiert, aber nicht im automatischen Restore-Test-Cron.
|
||||
|
||||
Restore-Tests sind monatlich für Vaultwarden und Gitea, alle zwei Monate für Paperless, "später" für Immich. Das ist gut, aber Immich-Restore-Tests sind die kritischsten, weil dort die größten Datenmengen liegen und ein silent corruption am schmerzhaftesten wäre.
|
||||
|
||||
**Note für diesen Block: 1-2.** Wenn die offenen DR-Vorbereitungs-TODOs abgehakt wären, klare 1.
|
||||
|
||||
---
|
||||
|
||||
## 8. Monitoring und Transparenz
|
||||
|
||||
Das ist der Bereich mit dem größten Übergang. Du hast viele Tools, mit Überschneidungen, und der zentrale Monitoring-Stack ist im Aufbau.
|
||||
|
||||
Was du heute hast:
|
||||
|
||||
- **Uptime-Kuma**: HTTP/TCP-Uptime-Checks mit Web-UI.
|
||||
- **Glances**: Live-System-Sicht (CPU/RAM/Disk pro Host).
|
||||
- **Scrutiny**: SMART-Monitoring für Laufwerke.
|
||||
- **Speedtest-Tracker**: Periodische Speedtests gegen den ISP.
|
||||
- **Glance**: Status-Dashboard mit Widgets (Immich, AdGuard, Speedtest, Docker-Container).
|
||||
- **Homepage**: Start-Dashboard mit Service-Cards.
|
||||
- **Posture-Check**: Host-Filesystem, NVMe-SMART, Mover, Füllstand → ntfy.
|
||||
- **Docker-Critical-Events**: `die`/`oom`/`kill` → ntfy.
|
||||
- **`monitoring/`-Zielstack**: Prometheus, Alertmanager, ntfy-Bridge, Blackbox, Loki, Promtail, Grafana, node-exporter, cAdvisor, InfluxDB 3 Core.
|
||||
|
||||
Was richtig gut ist:
|
||||
|
||||
- **Ein zentraler Alert-Pfad**: alle problemrelevanten Meldungen landen auf `homelab-alerts` per ntfy. Das ist die wichtigste Disziplin und du hast sie. `docs/ALERTING_MAP.md` listet alle Sender.
|
||||
- Prometheus-Stack mit Alertmanager + Bridge zu ntfy, also nicht "Grafana sendet Email" sondern "Alertmanager-Pflicht-Pfad".
|
||||
- Blackbox-Exporter ersetzt mittelfristig Uptime-Kuma (richtige Strategie).
|
||||
- 30 Tage Retention für Prometheus und Loki — sinnvoll für Diagnose-Daten, kein Backup-Surrogat.
|
||||
- node-exporter + cAdvisor + Traefik-Metrics → wirklich vollständige Infrastruktur-Telemetrie.
|
||||
|
||||
Was fehlt oder zu viel ist:
|
||||
|
||||
- **Doppelte Beobachtungs-Tools nebeneinander**: Uptime-Kuma vs. Blackbox, Glances vs. node-exporter+cAdvisor, Glance vs. Homepage. Du weißt das, die Migration ist im Gang. Bis sie fertig ist, gibt es Verwirrung darüber, welches die "Wahrheit" ist.
|
||||
- **Smoke-Test-Dashboards**: In `monitoring/grafana/dashboards/` sind ein paar Dashboards, aber die "Family-View" — "alles grün, alles erreichbar, Backup heute Nacht durchgelaufen" — fehlt als explizites Dashboard. Das wäre der Wert für dich selbst: morgens kurz draufschauen und wissen, ob etwas die Aufmerksamkeit braucht.
|
||||
- **Alert-Regeln explizit listen**: `monitoring/prometheus/alerts.yml` existiert, aber eine kurze Doku, welche Regeln wann feuern (Disk > 90%, Borg älter 36h, Cert läuft in 14 Tagen ab, etc.), würde die Operations-Reife komplettieren.
|
||||
- **Cert-Token-Check** läuft (laut ALERTING_MAP) — gut, das ist die einzige sinnvolle Methode, "TLS-Cert läuft ab" früh genug zu sehen.
|
||||
|
||||
**Note für diesen Block: 3.** Bricht ab, sobald die Monitoring-Migration sauber abgeschlossen ist und die Altstände entfernt sind — dann eine 2.
|
||||
|
||||
---
|
||||
|
||||
## 9. Konkreter Mehrwert-Fahrplan
|
||||
|
||||
### Quick Wins (≤ eine Woche, hoher Nutzen)
|
||||
|
||||
- **Externer Push-Mirror für das Repo nach GitHub privat** einrichten. Das ist ein Webhook in Gitea + ein leerer GitHub-Privat-Repo. 30 Minuten. Löst das wichtigste DR-Risiko.
|
||||
- **Borg-Passphrase auf einen USB-Stick im Bankschließfach** oder in eine versiegelte Umschlag-Box. Eine analoge Sicherung gegen das digitale Worst-Case-Szenario.
|
||||
- **Plex oder Jellyfin entscheiden**: einen davon weg. 14 Tage Nutzungs-Tracking via Server-Logs oder einfach beobachten, wer welchen Client öffnet. Dann den ungenutzten Stack archivieren.
|
||||
- **Glance ODER Homepage** als einziges Dashboard wählen. Heute laufen beide. Es gibt keinen technischen Grund für zwei.
|
||||
- **Authelia 2FA-Pflicht für alle aktiven User** verifizieren — wenn nicht gesetzt, jetzt setzen.
|
||||
- **Disk1 NTFS → XFS Phase 2 abschließen** — das ist im Repo dokumentiert und im posture-check als Übergangsausnahme markiert. Loswerden.
|
||||
|
||||
### Phase 1 (zwei bis vier Wochen, Stabilität und Ordnung)
|
||||
|
||||
- **Monitoring-Migration abschließen und Altstände entfernen**: `monitoring/` produktiv, dann `ops/grafana-influxdb` und `ops/loki` aus dem Repo löschen (mit Backup-Branch fürs Gewissen).
|
||||
- **Uptime-Kuma ablösen durch Blackbox + Grafana-Alerts**: nach den sieben Tagen Parallelbetrieb, die in `docs/SERVICE_CATALOG.md` als Pflicht stehen.
|
||||
- **Hermes-Agent ehrliche Entscheidung**: produktiv machen mit klarem Alltagsnutzen, oder entfernen. Kein "halb da, halb deaktiviert"-Zustand für ein weiteres Quartal.
|
||||
- **paperless-gpt und BentoPDF Status**: gleiche Frage. Produktiv oder weg.
|
||||
- **Unraid USB-Flash-Backup** einrichten (Unraid hat einen eingebauten Mechanismus).
|
||||
- **Ein Family-View-Dashboard in Grafana** bauen: alles-grün-Übersicht für den Morgen-Check.
|
||||
|
||||
### Phase 2 (vier bis zwölf Wochen, Automatisierung und Transparenz)
|
||||
|
||||
- **Authelia OIDC-Provider aktivieren** und Nextcloud, Immich, Grafana als OIDC-Clients konfigurieren. Echtes SSO für die Familie.
|
||||
- **Renovate Bot gegen Gitea** für kontrollierte Image-Updates (PRs statt manuelle Digest-Pflege).
|
||||
- **Restore-Test für Immich** als eigener Sprint einplanen — der größte Datentopf und der einzige Tier-2-Dienst ohne Mini-Restore-Test.
|
||||
- **Familie onboarden**: Smartphone-Auto-Backup zu Immich für alle Familien-Geräte. Das ist der Schritt vom "ich betreibe Container" zum "meine Familie benutzt aktiv was Selbstgebautes".
|
||||
- **CrowdSec vor Traefik** als Bouncer für die öffentlich erreichbaren Apps (Vaultwarden, Nextcloud, Immich, Gitea).
|
||||
|
||||
### Phase 3 (drei bis sechs Monate, Advanced Nerd-Level)
|
||||
|
||||
- **Staging-Branch + zweites Komodo-Ziel** in einer Tailscale-VM, für Risiko-Änderungen.
|
||||
- **Restore-Test-Automatisierung als CI** (Gitea Actions oder Drone).
|
||||
- **Off-Site-Backup zu einem zweiten Ziel** (zweites BorgBase-Repo, Hetzner Storage Box, oder zweites NAS bei einem Familienmitglied).
|
||||
- **Cold-Standby-Konzept** dokumentieren: was passiert, wenn der Unraid-Host stirbt und du erst in zwei Wochen Ersatz hast?
|
||||
- **Komodo-Self-Stack rausnehmen** und als handgepflegten `docker compose`-Service in `services/` halten — löst das Bootstrap-Problem.
|
||||
|
||||
### Phase 4 (Spielwiese, nice-to-have)
|
||||
|
||||
- Firefly III oder Actual Budget für Finanz-Übersicht.
|
||||
- Wandtablet-Setup im Flur mit Family-Dashboard.
|
||||
- Smart-Home-Automatisierungen über Home Assistant tiefer mit ntfy verzahnen (Frost-Warnung, PV-Überschuss-Hinweis, Briefkasten-Sensor).
|
||||
- Ein eigenes kleines Dashboard für Ecowitt-Wetterdaten (sobald die Pipeline aus `docs/HOME_ASSISTANT_INFLUXDB_ECOWITT.md` läuft).
|
||||
|
||||
---
|
||||
|
||||
## Schulnote, Top-5-Listen, klare Empfehlung
|
||||
|
||||
### Schulnote
|
||||
|
||||
**2- (gut bis befriedigend, eher 2).**
|
||||
|
||||
Auf der Skala "durchschnittliches Homelab" wäre das eine 1. Die strukturelle Disziplin, GitOps-Konsequenz, Doku-Qualität, Backup-Reife und Architektur-Klarheit liegen weit über dem, was die meisten Selfhoster jemals erreichen. Was die Note von einer 1 runterzieht: drei bis fünf offene Baustellen, die nicht trivial sind (externer Repo-Mirror, Monitoring-Migration unfertig, Hermes-Agent im Schwebezustand, zwei Medienserver parallel, AdGuard Admin-Port ohne ForwardAuth). Wenn du Phase 1 abschließt, bist du klar bei einer 1.
|
||||
|
||||
### Top 5 sofort verbessern
|
||||
|
||||
1. **Externer Repo-Mirror** (GitHub privat, BorgBase, oder zweites Gitea). 30 Minuten Aufwand, schließt das wichtigste DR-Loch.
|
||||
2. **Borg-Passphrase analog außerhalb des Systems sichern** (Bankschließfach, Familienmitglied, Tresor).
|
||||
3. **Plex oder Jellyfin entscheiden**, einen davon entfernen.
|
||||
4. **Glance oder Homepage** als einziges Dashboard wählen.
|
||||
5. **AdGuard Admin-Port hinter Authelia** oder mindestens nur via Tailscale erreichbar (heute LAN-direkt).
|
||||
|
||||
### Top 5 mit dem größten zusätzlichen Mehrwert
|
||||
|
||||
1. **Smartphone-Auto-Backup zu Immich für die ganze Familie**: macht aus deinem Foto-Server eine echte Killer-App für andere als dich.
|
||||
2. **Authelia OIDC-Provider aktivieren** und Nextcloud + Immich + Grafana per SSO: ein Login für alle wichtigen Apps.
|
||||
3. **Renovate Bot gegen Gitea**: automatisierte Update-PRs für deine Digest-pinnten Images.
|
||||
4. **Family-View-Dashboard in Grafana**: morgens 30 Sekunden draufschauen statt Tools-Tour.
|
||||
5. **Finanz-App (Firefly III oder Actual Budget)**: füllt den `/mnt/user/finance`-Share mit echtem Alltagsnutzen.
|
||||
|
||||
### Top 5 lieber NICHT machen
|
||||
|
||||
1. **Hermes-Agent ausbauen statt loswerden**. Wenn du in 60 Tagen nicht ehrlich sagen kannst, was Hermes dir täglich abnimmt: weg damit. Komplexität ohne Gegenwert ist das größte Anti-Pattern in jedem Homelab.
|
||||
2. **Noch mehr Dashboards einbauen**. Du hast bereits Homepage, Glance, Komodo-UI und kommst gleich noch mit Grafana-Family-View. Mehr wäre Sammlerei.
|
||||
3. **Pauschale Authelia-ForwardAuth vor Komodo** schalten. Dokumentierte Ausnahme aus gutem Grund. Webhooks, API und Periphery würden brechen.
|
||||
4. **Backend_net auf `external: true` statt `internal: true`** umstellen, weil "ist ja einfacher". Genau das ist die Mauer, die viele Apps vor öffentlichem Zugriff schützt.
|
||||
5. **Komodo Self-Stack komplett über Komodo managen lassen**. Du hattest schon einen Drift-Vorfall. Komodo verwaltet Komodo ist ein Bootstrap-Problem ohne befriedigende Lösung.
|
||||
|
||||
### Klare Empfehlung
|
||||
|
||||
**Vereinfachen und konsolidieren, NICHT weiter ausbauen.**
|
||||
|
||||
Du bist an einem Punkt, an dem das Setup mehr Substanz hat, als aktiv genutzt wird. Die nächsten sechs Monate sollten weniger neue Dienste sehen und mehr Entrümpelung (Plex vs. Jellyfin, Glance vs. Homepage, Hermes-Entscheidung, Monitoring-Altstände raus, paperless-gpt/BentoPDF-Entscheidung), mehr Familien-Aktivierung (Immich-Smartphone-Backup, OIDC-SSO, Family-Dashboard), und mehr DR-Resilienz (externer Mirror, Off-Site-Backup-Ziel, Borg-Passphrase analog gesichert).
|
||||
|
||||
Wenn du das in den nächsten drei Monaten machst, hast du eine private Plattform mit echtem Alltagsnutzen, klarer Wartbarkeit, und einer Wiederherstellbarkeit, die seriöser ist als das, was viele Mittelstandsfirmen für ihre Office-IT haben. Das ist das Ziel, nicht "noch ein Container".
|
||||
|
||||
Wenn du dann weiterausbauen willst, bist du in der Position, das aus einer Stärke heraus zu tun, nicht aus dem "ich muss noch das hier probieren"-Reflex.
|
||||
|
||||
---
|
||||
|
||||
## Status-Anhang 2026-05-30
|
||||
|
||||
Dieser Anhang ist nicht Teil der Originalbewertung vom 2026-05-23. Er ordnet jedem konkret handelbaren Befund den tatsaechlichen Stand nach den Audit-Sprints zu, damit das Dokument selbststaendig lesbar bleibt.
|
||||
|
||||
### Block 1 - Architektur
|
||||
|
||||
| Originalbefund | Stand 2026-05-30 |
|
||||
|---|---|
|
||||
| ops/grafana-influxdb + ops/loki + monitoring/ parallel im Repo | **erledigt** 2026-05-26: Altstaende entfernt, monitoring/ einziger aktiver Observability-Stack |
|
||||
|
||||
### Block 2 - Nutzen und Mehrwert
|
||||
|
||||
| Originalbefund | Stand 2026-05-30 |
|
||||
|---|---|
|
||||
| Plex zusaetzlich zu Jellyfin | **erledigt** 2026-05-25: Jellyfin entfernt, Plex bleibt einziger Medienserver |
|
||||
| Glance + Homepage + Komodo-UI als drei parallele Dashboards | **erledigt** 2026-05-25: Homepage entfernt, Glance bleibt einziges Dashboard |
|
||||
| paperless-gpt — produktiv oder weg? | **entschieden** 2026-05-28: behalten bis Paperless-NGX 3.0 native KI-Features, dann neu bewerten |
|
||||
| BentoPDF — produktiv oder weg? | **entschieden** 2026-05-28: behalten als situatives Tool (~4 MB RAM-Footprint) |
|
||||
| Hermes-Agent als Spielerei, Review-Deadline gesetzt | **geparkt** mit Review-Deadline 2026-07-25 |
|
||||
| Speedtest-Tracker als "ich messe gerne" | unveraendert, keine Operator-Entscheidung getroffen |
|
||||
| code-server — sinnvoll oder weg? | unveraendert, keine Operator-Entscheidung getroffen |
|
||||
| Finanz-App (Firefly III / Actual Budget) als fehlender Mehrwert | offen, nice-to-have ohne aktiven Termin |
|
||||
| Familien-SSO ueber Authelia OIDC | **geparkt** im Auth-Block (F-13) |
|
||||
| Smartphone-Auto-Backup zu Immich | offen, Anwendungsentscheidung pro Familienmitglied |
|
||||
| Wandtablet im Flur mit Family-Dashboard | offen, Spielwiese |
|
||||
| Kalender/Aufgaben/CardDAV-Nutzung dokumentieren | offen, Operator-Frage |
|
||||
|
||||
### Block 3 - Best Practices
|
||||
|
||||
| Originalbefund | Stand 2026-05-30 |
|
||||
|---|---|
|
||||
| Externer Repo-Mirror als DR-Voraussetzung offen | **erledigt**: GitHub-Push-Mirror `michaelkaleschke-spec/homelab-infra` aktiv |
|
||||
| Unraid USB-Flash-Backup offen | **erledigt** 2026-05-25: `unraid-flash-config.tar.gz` im Borg-Scope |
|
||||
| Komodo Self-Stack Drift Mai 2026 nur dokumentiert, nicht geloest | **teilweise erledigt** 2026-05-29/30: Trockenlauf-Skript + erfolgreicher Erstlauf belegen `ops/komodo/docker-compose.yml` als Recovery-Anker; Self-Stack-Entkopplung selbst bleibt offen |
|
||||
| Kein Fail2Ban/CrowdSec vor Traefik | **geparkt** im Auth-Block (F-14) |
|
||||
| Renovate Bot fehlt | **erledigt** 2026-05-29: live, erster Lauf erfolgreich, 5 PRs in Gitea |
|
||||
|
||||
### Block 4 - Nerd-Level
|
||||
|
||||
| Originalbefund | Stand 2026-05-30 |
|
||||
|---|---|
|
||||
| Renovate Bot oder vergleichbares Update-Tracking | **erledigt** 2026-05-29 |
|
||||
| Staging-Path mit zweitem Komodo-Ziel | offen, Phase 3 nice-to-have |
|
||||
| OIDC-Provider statt nur ForwardAuth | **geparkt** im Auth-Block (F-13) |
|
||||
| Restore-Tests automatisiert in CI (Gitea Actions / Drone) | offen, Phase 3 |
|
||||
| End-to-End restore drill mit Test-Domain hinter Traefik | offen, bewusst nicht (Test-Lab bleibt ohne Domain) |
|
||||
| Disk1 NTFS -> XFS Phase 2 | **erledigt** (`ALLOW_DISK1_NTFS=0` als Default im posture-check, XFS-Erwartung aktiv) |
|
||||
| Loki-Retention und Log-Volume bewerten | offen, Detail-Sweep |
|
||||
| Hermes-Agent loswerden | **geparkt** mit Review 2026-07-25 |
|
||||
| Drei Dashboard-Tools auf eines reduzieren | **erledigt**: Glance bleibt als einziges |
|
||||
| Zwei Medienserver | **erledigt**: Jellyfin entfernt |
|
||||
|
||||
### Block 5 - Betrieb und Wartbarkeit
|
||||
|
||||
| Originalbefund | Stand 2026-05-30 |
|
||||
|---|---|
|
||||
| Doppelter Monitoring-Stack als Wartbarkeits-Risiko | **erledigt** 2026-05-26 |
|
||||
| Authelia Repo-Baseline vs. Host-Config Drift "by design" | **erledigt** 2026-05-30 (F-10): `services/authelia-diff.sh` + Posture-Check ueberwacht ACL-Drift automatisch, WORKFLOW.md hat eigene Pflicht-Sektion |
|
||||
| Hermes-Agent verstaendnis-kritisch nach 6 Monaten | **geparkt** mit Review 2026-07-25 |
|
||||
|
||||
### Block 6 - Sicherheit und Zugriff
|
||||
|
||||
| Originalbefund | Stand 2026-05-30 |
|
||||
|---|---|
|
||||
| AdGuard Admin-Port 8082 LAN-direkt ohne Authelia | **erledigt** 2026-05-26: auf Tailscale-IP `100.80.98.33:8082` gebunden, LAN-Zugriff blockiert |
|
||||
| Nextcloud ohne ForwardAuth, Brute-Force-Doku offen | **geparkt** im Auth-Block (F-18) |
|
||||
| Authelia 2FA-Pflicht nicht klar dokumentiert | **geparkt** im Auth-Block (F-04) |
|
||||
|
||||
### Block 7 - Backup und Disaster Recovery
|
||||
|
||||
| Originalbefund | Stand 2026-05-30 |
|
||||
|---|---|
|
||||
| Externer Backup-Mirror / zweites Off-Site-Ziel | **entschieden** 2026-05-28: kein zweites Off-Site; 3-2-1 mit Live + lokalem Borg + Hetzner + H:/-Nearline erfuellt; Hetzner-Haertungen als Folge-TODOs |
|
||||
| Externer Repo-Mirror | **erledigt**: GitHub-Push-Mirror aktiv |
|
||||
| Unraid USB-Flash-Backup | **erledigt** 2026-05-25 |
|
||||
| Borg-Passphrase analog gesichert | **erledigt** 2026-05-26: Operator bestaetigt, offline gesichert |
|
||||
| Komodo-Mongo Dump-Verifikation nach Major-Upgrades | offen, Watchpoint dokumentiert, nicht im automatischen Cron |
|
||||
| Restore-Tests Immich (groesster Datentopf ohne Mini-Restore) | **erledigt** 2026-05-27: erster Host-Lauf erfolgreich (`SUCCESS`, 11977 Assets) |
|
||||
|
||||
### Block 8 - Monitoring und Transparenz
|
||||
|
||||
| Originalbefund | Stand 2026-05-30 |
|
||||
|---|---|
|
||||
| Doppelte Tools (Uptime-Kuma vs. Blackbox, Glance vs. Homepage) | **erledigt** 2026-05-25: Uptime-Kuma und Homepage entfernt |
|
||||
| Family-View Dashboard fehlt als Morgens-Check | **Spec da, JSON offen**: `docs/FAMILY_VIEW_DASHBOARD.md` definiert Layout/Queries/Thresholds; JSON wird gebaut, sobald Metriken 7+ Tage stabil sind |
|
||||
| Alert-Regeln explizit listen | **teilweise**: `monitoring/prometheus/alerts.yml` enthaelt Regeln (Borg-Stale, Cert-Expiry, Container-Down), `docs/ALERTING_MAP.md` mappt Sender — eine Doku-Zusammenfassung "welche Regel feuert wann" ist noch nicht zentralisiert |
|
||||
|
||||
### Block 9 - Konkreter Mehrwert-Fahrplan
|
||||
|
||||
#### Quick Wins (≤ 1 Woche)
|
||||
|
||||
| Original-Punkt | Stand 2026-05-30 |
|
||||
|---|---|
|
||||
| Externer Push-Mirror GitHub privat | **erledigt** |
|
||||
| Borg-Passphrase analog sichern | **erledigt** 2026-05-26 |
|
||||
| Plex oder Jellyfin entscheiden | **erledigt** 2026-05-25: Jellyfin weg |
|
||||
| Glance oder Homepage waehlen | **erledigt** 2026-05-25: Homepage weg |
|
||||
| Authelia 2FA-Pflicht aktivieren | **geparkt** (F-04) |
|
||||
| Disk1 NTFS -> XFS Phase 2 | **erledigt** |
|
||||
| AdGuard Admin Tailscale-only | **erledigt** 2026-05-26 |
|
||||
|
||||
#### Phase 1 (2-4 Wochen)
|
||||
|
||||
| Original-Punkt | Stand 2026-05-30 |
|
||||
|---|---|
|
||||
| Monitoring-Migration abschliessen, Altstaende entfernen | **erledigt** 2026-05-26 |
|
||||
| Uptime-Kuma abloesen durch Blackbox + Grafana | **erledigt** 2026-05-25 |
|
||||
| Hermes-Agent Entscheidung | **geparkt** mit Review 2026-07-25 |
|
||||
| paperless-gpt / BentoPDF Entscheidung | **entschieden** 2026-05-28: beide behalten mit Begruendung |
|
||||
| Unraid USB-Flash-Backup | **erledigt** 2026-05-25 |
|
||||
| Family-View-Dashboard | Spec da, JSON wartet |
|
||||
|
||||
#### Phase 2 (4-12 Wochen)
|
||||
|
||||
| Original-Punkt | Stand 2026-05-30 |
|
||||
|---|---|
|
||||
| Authelia OIDC fuer Nextcloud/Immich/Grafana | **geparkt** (F-13) |
|
||||
| Renovate Bot gegen Gitea | **erledigt** 2026-05-29 |
|
||||
| Restore-Test fuer Immich | **erledigt** 2026-05-27 |
|
||||
| Familien-Smartphone-Auto-Backup zu Immich | offen, Operator-Anwendungsentscheidung |
|
||||
| CrowdSec vor Traefik | **geparkt** (F-14) |
|
||||
|
||||
#### Phase 3 (3-6 Monate)
|
||||
|
||||
| Original-Punkt | Stand 2026-05-30 |
|
||||
|---|---|
|
||||
| Staging-Branch + zweites Komodo-Ziel | offen |
|
||||
| Restore-Test-Automatisierung als CI | offen |
|
||||
| Off-Site-Backup zu zweitem Ziel | **entschieden** 2026-05-28: bewusst nicht |
|
||||
| Cold-Standby-Konzept dokumentieren | offen |
|
||||
| Komodo-Self-Stack rausnehmen | teilweise erledigt: Bootstrap-Anker und Trockenlauf-Skript da, Entkopplung selbst noch nicht |
|
||||
|
||||
#### Phase 4 (Spielwiese)
|
||||
|
||||
| Original-Punkt | Stand 2026-05-30 |
|
||||
|---|---|
|
||||
| Firefly III / Actual Budget | offen |
|
||||
| Wandtablet mit Family-Dashboard | offen |
|
||||
| Home Assistant + ntfy enger verzahnen | offen |
|
||||
| Ecowitt-Wetter-Dashboard | offen |
|
||||
|
||||
### Top-5-Listen vom 2026-05-23
|
||||
|
||||
#### Top 5 sofort verbessern
|
||||
|
||||
| Original-Top-5 | Stand 2026-05-30 |
|
||||
|---|---|
|
||||
| 1. Externer Repo-Mirror | **erledigt** |
|
||||
| 2. Borg-Passphrase analog sichern | **erledigt** |
|
||||
| 3. Plex oder Jellyfin entscheiden | **erledigt** |
|
||||
| 4. Glance oder Homepage waehlen | **erledigt** |
|
||||
| 5. AdGuard Admin-Port haerten | **erledigt** |
|
||||
|
||||
**Alle 5 erledigt.**
|
||||
|
||||
#### Top 5 mit groesstem zusaetzlichen Mehrwert
|
||||
|
||||
| Original-Top-5 | Stand 2026-05-30 |
|
||||
|---|---|
|
||||
| 1. Smartphone-Auto-Backup zu Immich | offen, Anwendungsentscheidung |
|
||||
| 2. Authelia OIDC fuer SSO | **geparkt** |
|
||||
| 3. Renovate Bot gegen Gitea | **erledigt** |
|
||||
| 4. Family-View-Dashboard | Spec da, JSON wartet |
|
||||
| 5. Finanz-App | offen |
|
||||
|
||||
#### Top 5 lieber NICHT machen
|
||||
|
||||
| Original-Anti-Top-5 | Stand 2026-05-30 |
|
||||
|---|---|
|
||||
| 1. Hermes-Agent ausbauen statt loswerden | gehalten — Agent geparkt mit Review, nicht ausgebaut |
|
||||
| 2. Noch mehr Dashboards einbauen | gehalten — Homepage entfernt, Glance bleibt einziges |
|
||||
| 3. Pauschale Authelia vor Komodo | gehalten — Komodo bleibt ohne ForwardAuth |
|
||||
| 4. backend_net auf external statt internal | gehalten — backend_net bleibt internal |
|
||||
| 5. Komodo Self-Stack komplett via Komodo | teilweise gehalten — Trockenlauf-Skript als Gegenmaszahme, vollstaendige Entkopplung offen |
|
||||
|
||||
### Zusammenfassung des Status-Anhangs
|
||||
|
||||
- **Top 5 sofort**: 5/5 erledigt.
|
||||
- **Quick Wins (7)**: 6 erledigt, 1 geparkt.
|
||||
- **Phase 1 (6)**: 4 erledigt, 1 geparkt, 1 wartend.
|
||||
- **Phase 2 (5)**: 2 erledigt, 2 geparkt, 1 offen.
|
||||
- **Phase 3 (5)**: 1 entschieden (nicht umgesetzt), 1 teilweise, 3 offen.
|
||||
- **Phase 4 (Spielwiese)**: alle offen, bewusst niedrige Prioritaet.
|
||||
- **Auth-Block (F-04/13/14/18)**: vollstaendig geparkt nach Operator-Entscheidung 2026-05-26, gebuendelte Bearbeitung ausserhalb des aktuellen Zyklus.
|
||||
|
||||
Wer hier weiterarbeiten will, schaut auf `docs/AUDIT_2026-05-25_TODO.md` — dort ist der operative Stand gepflegt.
|
||||
@@ -0,0 +1,15 @@
|
||||
# Documentation Archive
|
||||
|
||||
Dieses Archiv enthaelt historische Dokumente, die nicht mehr als aktive Betriebsquelle dienen.
|
||||
|
||||
Regel:
|
||||
|
||||
- Datierte Audits, Chat-Handoffs, Codex-Prompts und erledigte Aktionsplaene werden unter `YYYY-MM/` abgelegt.
|
||||
- Aktuelle Runbooks, Inventare, Restore-Pfade und offene Arbeitslisten bleiben in `docs/`.
|
||||
- Archivierte Dateien duerfen weiterhin verlinkt werden, sollen aber nicht als Startpunkt fuer neue Arbeit dienen.
|
||||
|
||||
Aktueller Archivbereich:
|
||||
|
||||
| Pfad | Inhalt |
|
||||
|---|---|
|
||||
| `2026-05/` | Mai-2026-Audits, Zwischenstaende, Prompt-Vorlagen und abgeschlossene Plaene |
|
||||
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
adguard:
|
||||
image: adguard/adguardhome:v0.107.52@sha256:d16cc7517ab96f843e7f8bf8826402dba98f5e6b175858920296243332391589
|
||||
image: adguard/adguardhome:v0.107.76@sha256:7157eb1dc3b26c7af1d6898759a7b3f7d0fa09891fbd2d3caa6abc1057a9179b
|
||||
container_name: adguard
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
tailscale:
|
||||
image: tailscale/tailscale:stable@sha256:dbeff02d2337344b351afac203427218c4d0a06c43fc10a865184063498472a6
|
||||
image: tailscale/tailscale:stable@sha256:25cde9ad76020b0e29229136d0c38b5962e9a0e1774ffac9b0df68e4a37d6cf0
|
||||
container_name: Tailscale-Docker
|
||||
restart: unless-stopped
|
||||
network_mode: host
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
ddns-updater:
|
||||
image: ghcr.io/qdm12/ddns-updater:latest@sha256:ee16ab4f6203bf9e5b0925d38a0b4ebf2d9f23771f933cfb2f5a2dbd5f9a2f88
|
||||
image: ghcr.io/qdm12/ddns-updater:latest@sha256:9313e1c31f366c89dc0819e5eff85576cb23821424c0c267fa66cfa39aabde83
|
||||
container_name: ddns-updater
|
||||
restart: unless-stopped
|
||||
security_opt:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
postgresql17:
|
||||
image: postgres:17.9@sha256:5b96f1a16bd9768b060dd2ffe55cb6225c4d9ef4d214a8b21eb08134869a97e4
|
||||
image: postgres:18.4@sha256:8ff36f3c66371cba71d20ceedccfc3de9669a68737607888c4ef0af93abe8e39
|
||||
container_name: postgresql17
|
||||
restart: unless-stopped
|
||||
|
||||
@@ -9,10 +9,10 @@ services:
|
||||
POSTGRES_USER: mailarchiver
|
||||
POSTGRES_DB: mailarchiver
|
||||
POSTGRES_PASSWORD_FILE: /run/secrets/postgres_password
|
||||
PGDATA: /var/lib/postgresql/data
|
||||
PGDATA: /var/lib/postgresql/18/docker
|
||||
|
||||
volumes:
|
||||
- /mnt/user/appdata/postgresql17:/var/lib/postgresql/data
|
||||
- /mnt/user/appdata/postgresql18:/var/lib/postgresql
|
||||
- /mnt/user/appdata/secrets/postgres_password.txt:/run/secrets/postgres_password:ro
|
||||
|
||||
networks:
|
||||
@@ -21,6 +21,13 @@ services:
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U mailarchiver -d mailarchiver"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 30s
|
||||
|
||||
networks:
|
||||
backend_net:
|
||||
external: true
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
redis:
|
||||
image: redis:7.4-alpine@sha256:6ab0b6e7381779332f97b8ca76193e45b0756f38d4c0dcda72dbb3c32061ab99
|
||||
image: redis:8.8.0-alpine@sha256:09160599abd229764c0fb44cb6be640294e1d360a54b19985ab4843dcf2d90f1
|
||||
container_name: Redis
|
||||
restart: unless-stopped
|
||||
command:
|
||||
@@ -18,6 +18,13 @@ services:
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "redis-cli -a \"$$(cat /run/secrets/redis_password)\" --no-auth-warning ping | grep -q PONG"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 15s
|
||||
|
||||
networks:
|
||||
backend_net:
|
||||
external: true
|
||||
|
||||
@@ -62,6 +62,7 @@ INFLUXDB_BIND_IP=192.168.178.58
|
||||
- `https://monitoring.kaleschke.info` leitet zu Authelia.
|
||||
- Grafana-Datasources `Prometheus`, `Loki` und `InfluxDB 3 Core` testen erfolgreich.
|
||||
- Prometheus Targets: `prometheus`, `node-exporter`, `cadvisor`, `traefik`, `blackbox-http`.
|
||||
- Node Exporter Textfile Collector: `/mnt/user/services/posture-check/textfile/homelab.prom` wird vom Host-Skript `services/posture-check/export-prometheus-textfile.sh` befuellt.
|
||||
- Alertmanager ist erreichbar und sendet ueber `monitoring-alertmanager-ntfy-bridge` nach `https://ntfy.kaleschke.info/homelab-alerts`.
|
||||
- Loki zeigt Container-Logs mit Labels `container`, `compose_project`, `compose_service`.
|
||||
- InfluxDB 3 Core enthaelt die Datenbank `homelab`.
|
||||
@@ -83,9 +84,17 @@ Blackbox-HTTP-Alerts unterscheiden zwischen einem einzelnen kaputten Endpoint un
|
||||
|
||||
- `HomelabExternalConnectivityDown` feuert, wenn mindestens 5 Public-Endpoints gleichzeitig fuer 8 Minuten nicht erreichbar sind. Das deckt WAN-, DNS- oder Provider-Ausfaelle ab, inklusive laengerer DSL-Reconnects.
|
||||
- `HomelabEndpointDown` feuert fuer einzelne Endpoints erst nach 8 Minuten und wird unterdrueckt, solange der Sammelalert aktiv ist. Dadurch erzeugt ein Telekom-24h-Reconnect keine ntfy-Flut pro Domain.
|
||||
- `HomelabCertificateExpiresSoon` und `HomelabCertificateExpiresCritical` nutzen Blackbox TLS-Metriken fuer 21-/7-Tage-Warnungen.
|
||||
- `HomelabBorgBackupStale`, `HomelabBorgLastJobFailed`, `HomelabBorgLastJobCompletedWithWarnings` und `HomelabCriticalContainerDown` nutzen Host-Textfile-Metriken. Voraussetzung: `services/posture-check/export-prometheus-textfile.sh` laeuft regelmaessig auf dem Host, empfohlen alle 15 Minuten.
|
||||
|
||||
Test:
|
||||
|
||||
```bash
|
||||
curl -fsS http://alertmanager-ntfy-bridge:8080/healthz
|
||||
```
|
||||
|
||||
Textfile-Metriken manuell aktualisieren:
|
||||
|
||||
```bash
|
||||
bash /mnt/user/services/homelab-infra/services/posture-check/export-prometheus-textfile.sh
|
||||
```
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
prometheus:
|
||||
image: prom/prometheus:v3.7.3
|
||||
image: prom/prometheus:v3.12.0@sha256:69f5241418838263316593f7274a304b095c40bcf22e57272865da91bd60a8ac
|
||||
container_name: monitoring-prometheus
|
||||
restart: unless-stopped
|
||||
command:
|
||||
@@ -25,7 +25,7 @@ services:
|
||||
- cadvisor
|
||||
|
||||
alertmanager:
|
||||
image: prom/alertmanager:v0.28.1
|
||||
image: prom/alertmanager:v0.32.1@sha256:51a825c2a40acc3e338fdd00d622e01ec090f72be2b3ea46be0839cd47a4d286
|
||||
container_name: monitoring-alertmanager
|
||||
restart: unless-stopped
|
||||
command:
|
||||
@@ -42,7 +42,7 @@ services:
|
||||
- no-new-privileges:true
|
||||
|
||||
alertmanager-ntfy-bridge:
|
||||
image: python:3.13-alpine
|
||||
image: python:3.14-alpine@sha256:5a824eb82cc75361f98611f3cfc5091ea33f10a6ccea4d4ebdabbc523b9a1614
|
||||
container_name: monitoring-alertmanager-ntfy-bridge
|
||||
restart: unless-stopped
|
||||
dns:
|
||||
@@ -63,7 +63,7 @@ services:
|
||||
- no-new-privileges:true
|
||||
|
||||
blackbox-exporter:
|
||||
image: prom/blackbox-exporter:v0.27.0
|
||||
image: prom/blackbox-exporter:v0.28.0@sha256:e753ff9f3fc458d02cca5eddab5a77e1c175eee484a8925ac7d524f04366c2fc
|
||||
container_name: monitoring-blackbox-exporter
|
||||
restart: unless-stopped
|
||||
dns:
|
||||
@@ -81,7 +81,7 @@ services:
|
||||
- no-new-privileges:true
|
||||
|
||||
loki:
|
||||
image: grafana/loki:3.7.2
|
||||
image: grafana/loki:3.7.2@sha256:191d4fdfb7264f16989f0a57f320872620a5a7c2ceeec6229212c4190ec49b86
|
||||
container_name: monitoring-loki
|
||||
restart: unless-stopped
|
||||
command:
|
||||
@@ -97,7 +97,7 @@ services:
|
||||
- no-new-privileges:true
|
||||
|
||||
promtail:
|
||||
image: grafana/promtail:3.6.10
|
||||
image: grafana/promtail:3.6.11@sha256:a761cb834cfaeee29745440d4884d6748f0a08d8f68928db1d707018c1dcfbe9
|
||||
container_name: monitoring-promtail
|
||||
restart: unless-stopped
|
||||
command:
|
||||
@@ -115,8 +115,9 @@ services:
|
||||
- loki
|
||||
|
||||
grafana:
|
||||
image: grafana/grafana:12.4.3
|
||||
image: grafana/grafana:13.0.1@sha256:0f86bada30d65ef9d0183b90c1e2682ac92d53d95da8bed322b984ea78a4a73a
|
||||
container_name: monitoring-grafana
|
||||
user: "0"
|
||||
restart: unless-stopped
|
||||
dns:
|
||||
- 1.1.1.1
|
||||
@@ -127,6 +128,7 @@ services:
|
||||
GF_SECURITY_ADMIN_PASSWORD__FILE: /run/secrets/monitoring_grafana_admin_password
|
||||
GF_USERS_ALLOW_SIGN_UP: "false"
|
||||
GF_AUTH_ANONYMOUS_ENABLED: "false"
|
||||
GF_PLUGINS_PREINSTALL_DISABLED: "true"
|
||||
entrypoint:
|
||||
- /bin/sh
|
||||
- -c
|
||||
@@ -162,7 +164,7 @@ services:
|
||||
- traefik.http.services.monitoring-grafana.loadbalancer.server.port=3000
|
||||
|
||||
grafana-dashboard-importer:
|
||||
image: python:3.13-alpine
|
||||
image: python:3.14-alpine
|
||||
container_name: monitoring-grafana-dashboard-importer
|
||||
restart: "no"
|
||||
profiles:
|
||||
@@ -273,18 +275,20 @@ services:
|
||||
echo "Dashboard import complete."
|
||||
|
||||
node-exporter:
|
||||
image: prom/node-exporter:v1.9.1
|
||||
image: prom/node-exporter:v1.11.1@sha256:e9cff4fc67b1818f8c97adb115b9f12c9a54b533de86765d4a0effc01b357205
|
||||
container_name: monitoring-node-exporter
|
||||
restart: unless-stopped
|
||||
command:
|
||||
- --path.procfs=/host/proc
|
||||
- --path.sysfs=/host/sys
|
||||
- --path.rootfs=/rootfs
|
||||
- --collector.textfile.directory=/textfile
|
||||
- --collector.filesystem.mount-points-exclude=^/(dev|proc|sys|run|var/lib/docker/.+|var/lib/containers/storage/.+)($|/)
|
||||
volumes:
|
||||
- /proc:/host/proc:ro
|
||||
- /sys:/host/sys:ro
|
||||
- /:/rootfs:ro
|
||||
- /mnt/user/services/posture-check/textfile:/textfile:ro
|
||||
networks:
|
||||
- monitoring_net
|
||||
expose:
|
||||
@@ -293,7 +297,7 @@ services:
|
||||
- no-new-privileges:true
|
||||
|
||||
cadvisor:
|
||||
image: ghcr.io/google/cadvisor:v0.53.0
|
||||
image: ghcr.io/google/cadvisor:v0.57.0@sha256:e75bdb03b74b0b6995f208f166fead2e6e555dde73e44200113bb26f41b1981d
|
||||
container_name: monitoring-cadvisor
|
||||
restart: unless-stopped
|
||||
command:
|
||||
@@ -314,7 +318,7 @@ services:
|
||||
- no-new-privileges:true
|
||||
|
||||
influxdb3-core:
|
||||
image: influxdb:3.9.1-core@sha256:1d58c8b9ac90153ae3a020ede2810c8284933dda50ac71e7573389ab6f012128
|
||||
image: influxdb:3.9.2-core@sha256:31ad94df2248134989b2cf73d965e51dd5f35dfae22d7ed8f4776b12e6f69f4e
|
||||
container_name: monitoring-influxdb3-core
|
||||
user: "0"
|
||||
restart: unless-stopped
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -28,6 +28,24 @@ groups:
|
||||
summary: "{{ $labels.instance }} is slow"
|
||||
description: "Blackbox probe duration is above 5 seconds for {{ $labels.instance }}."
|
||||
|
||||
- alert: HomelabCertificateExpiresSoon
|
||||
expr: (probe_ssl_earliest_cert_expiry{job="blackbox-http"} - time()) < 21 * 24 * 3600 and (probe_ssl_earliest_cert_expiry{job="blackbox-http"} - time()) > 7 * 24 * 3600
|
||||
for: 30m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "TLS certificate expires soon for {{ $labels.instance }}"
|
||||
description: "The earliest certificate expiry for {{ $labels.instance }} is below 21 days."
|
||||
|
||||
- alert: HomelabCertificateExpiresCritical
|
||||
expr: (probe_ssl_earliest_cert_expiry{job="blackbox-http"} - time()) <= 7 * 24 * 3600
|
||||
for: 15m
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
summary: "TLS certificate is close to expiry for {{ $labels.instance }}"
|
||||
description: "The earliest certificate expiry for {{ $labels.instance }} is at or below 7 days, or already expired."
|
||||
|
||||
- name: homelab-host
|
||||
rules:
|
||||
- alert: HomelabDiskAlmostFull
|
||||
@@ -39,6 +57,15 @@ groups:
|
||||
summary: "Disk usage high on {{ $labels.mountpoint }}"
|
||||
description: "{{ $labels.mountpoint }} is above 85% used."
|
||||
|
||||
- alert: HomelabDiskCritical
|
||||
expr: 100 * (1 - node_filesystem_avail_bytes{fstype!~"tmpfs|overlay"} / node_filesystem_size_bytes{fstype!~"tmpfs|overlay"}) > 95
|
||||
for: 5m
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
summary: "Disk critically full on {{ $labels.mountpoint }}"
|
||||
description: "{{ $labels.mountpoint }} is above 95% used. Writes may start to fail (DB, appdata, cache)."
|
||||
|
||||
- alert: HomelabHighMemoryUsage
|
||||
expr: 100 * (1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) > 90
|
||||
for: 10m
|
||||
@@ -56,3 +83,79 @@ groups:
|
||||
annotations:
|
||||
summary: "Traefik 5xx responses for {{ $labels.service }}"
|
||||
description: "Traefik reports at least 5 5xx responses for {{ $labels.service }} within 5 minutes."
|
||||
|
||||
- name: homelab-backup-and-containers
|
||||
rules:
|
||||
- alert: HomelabTextfileExporterStale
|
||||
expr: time() - homelab_textfile_exporter_last_run_timestamp_seconds > 2 * 60 * 60
|
||||
for: 15m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "Homelab textfile metrics are stale"
|
||||
description: "The host textfile exporter has not refreshed metrics for more than 2 hours."
|
||||
|
||||
- alert: HomelabBorgMetricsMissing
|
||||
expr: absent(homelab_borg_last_completed_timestamp_seconds)
|
||||
for: 15m
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
summary: "Borg backup metrics are missing"
|
||||
description: "Prometheus cannot see the homelab_borg_last_completed_timestamp_seconds metric."
|
||||
|
||||
- alert: HomelabBorgBackupStale
|
||||
expr: time() - homelab_borg_last_completed_timestamp_seconds > 30 * 60 * 60
|
||||
for: 15m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "Borg backup is stale"
|
||||
description: "The latest completed Borg backup is older than 30 hours."
|
||||
|
||||
- alert: HomelabBorgLastJobFailed
|
||||
expr: homelab_borg_last_success != 1
|
||||
for: 15m
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
summary: "Latest Borg backup did not complete successfully"
|
||||
description: "The latest Borg UI job status is {{ $labels.status }} for archive {{ $labels.archive }}."
|
||||
|
||||
- alert: HomelabBorgLastJobCompletedWithWarnings
|
||||
expr: homelab_borg_last_job_warning == 1
|
||||
for: 15m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "Latest Borg backup completed with warnings"
|
||||
description: "The latest Borg UI job completed with warnings for archive {{ $labels.archive }}."
|
||||
|
||||
- alert: HomelabCriticalContainerDown
|
||||
expr: homelab_critical_container_running == 0
|
||||
for: 5m
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
summary: "Critical container is down: {{ $labels.name }}"
|
||||
description: "The host textfile exporter reports that critical container {{ $labels.name }} is not running."
|
||||
|
||||
- alert: HomelabGitOpsRuntimeImageDrift
|
||||
expr: homelab_gitops_runtime_image_match == 0
|
||||
for: 10m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "Runtime image drift: {{ $labels.name }}"
|
||||
description: "Container {{ $labels.name }} is not running the image declared by its Compose config in project {{ $labels.project }}."
|
||||
|
||||
- name: homelab-meta
|
||||
rules:
|
||||
- alert: HomelabPrometheusTargetDown
|
||||
expr: up == 0
|
||||
for: 5m
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
summary: "Prometheus target down: {{ $labels.job }} / {{ $labels.instance }}"
|
||||
description: "Scrape target {{ $labels.instance }} (job {{ $labels.job }}) is unreachable. Metrics from this target are silent — alerts built on them will not fire."
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Borg Backup Scope for KalliLabcore
|
||||
|
||||
Stand: 2026-05-16
|
||||
Stand: 2026-05-31
|
||||
|
||||
This file defines the target state for replacing Backrest with Borg in this homelab.
|
||||
|
||||
@@ -45,7 +45,7 @@ The Unraid flash configuration archive is intentional as well and must be treate
|
||||
| GitOps host automation | repo clone + Komodo workspaces + host-check state | `/local/services/homelab-infra`, `/local/services/stacks`, `/local/services/posture-check` |
|
||||
| Unraid OS flash | generated config archive | `/local/borg-dumps/unraid-flash-config.tar.gz` plus checksum and manifest |
|
||||
| Nextcloud | DB dump + file data | `/local/borg-dumps`, `/local/appdata/nextcloud/html`, `/local/nextcloud/data` |
|
||||
| Grafana | SQLite dump + file data | `/local/borg-dumps`, `/local/appdata/grafana` |
|
||||
| Grafana | SQLite dump from `monitoring_grafana_data` + provisioned config in Git | `/local/borg-dumps`, `monitoring/grafana/provisioning`, `monitoring/grafana/dashboards` |
|
||||
| Filebrowser | file-backed state dump + file data | `/local/borg-dumps`, `/local/appdata/filebrowser` |
|
||||
| InfluxDB 3 Core | file data | `/local/appdata/influxdb3/data`, `/local/appdata/influxdb3/plugins` |
|
||||
| Hermes Agent | file data + SSH key | `/local/appdata/hermes-agent/data`, `/local/secrets/hermes_runner_id_ed25519` |
|
||||
@@ -69,7 +69,7 @@ The live Unraid User Scripts execute repo scripts from `/mnt/user/services/homel
|
||||
|
||||
## Database Dumps Required
|
||||
|
||||
### Shared PostgreSQL (`postgresql17`)
|
||||
### Shared PostgreSQL (`postgresql17`, runtime PostgreSQL 18)
|
||||
|
||||
- `mailarchiver`
|
||||
- `paperless`
|
||||
@@ -91,9 +91,13 @@ The live Unraid User Scripts execute repo scripts from `/mnt/user/services/homel
|
||||
## Explicitly Not Backed Up as Raw Live DB Files
|
||||
|
||||
- `/mnt/user/appdata/postgresql17`
|
||||
- `/mnt/user/appdata/postgresql18`
|
||||
- `/mnt/user/appdata/mealie/postgres`
|
||||
- `/mnt/user/appdata/mealie/postgres18`
|
||||
- `/mnt/user/appdata/immich_postgres`
|
||||
- `/mnt/user/appdata/immich_postgres_vectorchord`
|
||||
- `/mnt/user/appdata/nextcloud/postgres`
|
||||
- `/mnt/user/appdata/nextcloud/postgres18`
|
||||
- `/mnt/user/appdata/komodo/mongo`
|
||||
- `/mnt/user/appdata/redis`
|
||||
- `/mnt/user/appdata/scrutiny/influxdb`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
borg-ui:
|
||||
image: ainullcode/borg-ui@sha256:867c73983e5bef5491cdee1c34acf85fe8a9fe4f6ad5a9381e7ca2c382359ce6
|
||||
image: ainullcode/borg-ui@sha256:b44c0a92b650d80f215a986dadda5c2604c61eb28a7571e19c046eff41d761e7
|
||||
container_name: borg-ui
|
||||
restart: unless-stopped
|
||||
security_opt:
|
||||
|
||||
@@ -5,6 +5,7 @@ These scripts are intended to run on the Unraid host before a Borg backup starts
|
||||
## Current script
|
||||
|
||||
- `pre-backup-dumps.sh`
|
||||
- `gitea-bundle-mirror.sh`
|
||||
|
||||
## Output
|
||||
|
||||
@@ -12,7 +13,13 @@ Fresh dump artifacts are written to:
|
||||
|
||||
- `/mnt/user/backups/borg/dumps/latest`
|
||||
|
||||
Fresh Gitea repository bundles are written to:
|
||||
|
||||
- `/mnt/user/backups/git-bundles/gitea`
|
||||
|
||||
Borg UI should include `/local/borg-dumps` as a backup source.
|
||||
The Gitea bundle target should also be part of the Borg scope, either through
|
||||
the backups share or an explicit Borg source.
|
||||
|
||||
The dump set also includes `unraid-flash-config.tar.gz`, a host-generated
|
||||
archive of `/boot/config` plus checksum and manifest. Treat this archive as
|
||||
@@ -21,6 +28,8 @@ secret backup material.
|
||||
## Notes
|
||||
|
||||
- The script is written for host execution where `docker` is available.
|
||||
- `gitea-bundle-mirror.sh` additionally expects host access to the Gitea bare
|
||||
repositories under `/mnt/user/services/gitea/data/git/repositories`.
|
||||
- It does not assume Backrest.
|
||||
- It keeps only the latest dump set because Borg itself provides history.
|
||||
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
# Run this on the Unraid host. It creates verified git bundles for every bare
|
||||
# Gitea repository so a Gitea outage does not make repo bootstrap depend on the
|
||||
# Gitea application database.
|
||||
#
|
||||
# Bundles and their sha256 sidecars are written 0644 on purpose, so the
|
||||
# Nearline-Pull-Workflow (docs/H_DRIVE_NEARLINE_PULL.md) kann sie ueber den
|
||||
# SMB-Read-Share holen. Bundle-Inhalt = Git-Historie ohne Secrets (durch
|
||||
# .gitignore abgedeckt) und nicht sensibler als die uebrigen Dumps unter
|
||||
# /mnt/user/backups/borg/dumps/latest/, die ebenfalls 0644 sind.
|
||||
|
||||
SOURCE_ROOT="${SOURCE_ROOT:-/mnt/user/services/gitea/data/git/repositories}"
|
||||
BUNDLE_ROOT="${BUNDLE_ROOT:-/mnt/user/backups/git-bundles/gitea}"
|
||||
TMP_ROOT="${TMP_ROOT:-/mnt/cache/tmp/gitea-bundle-mirror}"
|
||||
REPORT_PATH="${REPORT_PATH:-$BUNDLE_ROOT/latest-report.md}"
|
||||
MANIFEST_PATH="${MANIFEST_PATH:-$BUNDLE_ROOT/manifest.tsv}"
|
||||
RUN_ID="$(date -u '+%Y-%m-%dT%H:%M:%SZ')"
|
||||
|
||||
log() {
|
||||
printf '%s %s\n' "[gitea-bundles]" "$*"
|
||||
}
|
||||
|
||||
warn() {
|
||||
printf '%s %s\n' "[gitea-bundles][warn]" "$*" >&2
|
||||
}
|
||||
|
||||
need_cmd() {
|
||||
if ! command -v "$1" >/dev/null 2>&1; then
|
||||
warn "Required command missing: $1"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
bundle_target_for_repo() {
|
||||
repo="$1"
|
||||
rel="${repo#$SOURCE_ROOT/}"
|
||||
rel="${rel%.git}.bundle"
|
||||
printf '%s/%s\n' "$BUNDLE_ROOT" "$rel"
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
rm -rf "$TMP_ROOT/run.$$"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
main() {
|
||||
need_cmd git
|
||||
need_cmd find
|
||||
need_cmd sha256sum
|
||||
|
||||
if [ ! -d "$SOURCE_ROOT" ]; then
|
||||
warn "Source root missing: $SOURCE_ROOT"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
run_tmp="$TMP_ROOT/run.$$"
|
||||
mkdir -p "$run_tmp" "$(dirname "$REPORT_PATH")" "$(dirname "$MANIFEST_PATH")"
|
||||
|
||||
manifest_tmp="$run_tmp/manifest.tsv"
|
||||
report_tmp="$run_tmp/latest-report.md"
|
||||
: > "$manifest_tmp"
|
||||
|
||||
total=0
|
||||
bundled=0
|
||||
skipped=0
|
||||
failed=0
|
||||
details="$run_tmp/details.txt"
|
||||
: > "$details"
|
||||
|
||||
repo_list="$run_tmp/repos.txt"
|
||||
find "$SOURCE_ROOT" -type d -name '*.git' | sort > "$repo_list"
|
||||
|
||||
while IFS= read -r repo; do
|
||||
total=$((total + 1))
|
||||
|
||||
if [ "$(git --git-dir="$repo" rev-parse --is-bare-repository 2>/dev/null || true)" != "true" ]; then
|
||||
skipped=$((skipped + 1))
|
||||
printf 'SKIP\t%s\tnot a bare repository\n' "$repo" >> "$details"
|
||||
printf '%s\t%s\t%s\t%s\n' "$total" "$bundled" "$skipped" "$failed" > "$run_tmp/counts"
|
||||
continue
|
||||
fi
|
||||
|
||||
target="$(bundle_target_for_repo "$repo")"
|
||||
target_dir="$(dirname "$target")"
|
||||
tmp="$run_tmp/$(basename "$target").tmp"
|
||||
target_tmp="$target_dir/.$(basename "$target").tmp"
|
||||
mkdir -p "$target_dir"
|
||||
|
||||
rel="${repo#$SOURCE_ROOT/}"
|
||||
log "Bundling $rel"
|
||||
|
||||
if git --git-dir="$repo" bundle create "$tmp" --all >/dev/null 2>&1 &&
|
||||
git --git-dir="$repo" bundle verify "$tmp" >/dev/null 2>&1; then
|
||||
chmod 644 "$tmp"
|
||||
rm -f "$target_tmp"
|
||||
cp "$tmp" "$target_tmp"
|
||||
chmod 644 "$target_tmp"
|
||||
mv "$target_tmp" "$target"
|
||||
rm -f "$tmp"
|
||||
git --git-dir="$repo" bundle verify "$target" >/dev/null 2>&1
|
||||
(
|
||||
cd "$target_dir"
|
||||
sha256sum "$(basename "$target")" > "$(basename "$target").sha256.tmp"
|
||||
)
|
||||
chmod 644 "$target.sha256.tmp"
|
||||
mv "$target.sha256.tmp" "$target.sha256"
|
||||
size_bytes="$(wc -c < "$target" | tr -d ' ')"
|
||||
printf '%s\t%s\t%s\t%s\n' "$RUN_ID" "$rel" "${target#$BUNDLE_ROOT/}" "$size_bytes" >> "$manifest_tmp"
|
||||
printf 'OK\t%s\t%s bytes\n' "$rel" "$size_bytes" >> "$details"
|
||||
bundled=$((bundled + 1))
|
||||
else
|
||||
failed=$((failed + 1))
|
||||
rm -f "$tmp"
|
||||
printf 'FAIL\t%s\tbundle create or verify failed\n' "$rel" >> "$details"
|
||||
fi
|
||||
|
||||
printf '%s\t%s\t%s\t%s\n' "$total" "$bundled" "$skipped" "$failed" > "$run_tmp/counts"
|
||||
done < "$repo_list"
|
||||
|
||||
if [ -f "$run_tmp/counts" ]; then
|
||||
IFS="$(printf '\t')" read -r total bundled skipped failed < "$run_tmp/counts"
|
||||
fi
|
||||
|
||||
{
|
||||
printf '# Gitea Bundle Mirror Report\n\n'
|
||||
printf 'Timestamp: %s\n' "$RUN_ID"
|
||||
printf 'Source: `%s`\n' "$SOURCE_ROOT"
|
||||
printf 'Target: `%s`\n' "$BUNDLE_ROOT"
|
||||
printf 'Total repositories: %s\n' "$total"
|
||||
printf 'Bundled: %s\n' "$bundled"
|
||||
printf 'Skipped: %s\n' "$skipped"
|
||||
printf 'Failed: %s\n\n' "$failed"
|
||||
printf '## Details\n\n'
|
||||
if [ -s "$details" ]; then
|
||||
while IFS="$(printf '\t')" read -r status name message; do
|
||||
printf -- '- `%s` %s: %s\n' "$status" "$name" "$message"
|
||||
done < "$details"
|
||||
else
|
||||
printf -- '- No repositories found.\n'
|
||||
fi
|
||||
} > "$report_tmp"
|
||||
|
||||
chmod 644 "$report_tmp" "$manifest_tmp"
|
||||
mv "$report_tmp" "$REPORT_PATH"
|
||||
mv "$manifest_tmp" "$MANIFEST_PATH"
|
||||
|
||||
log "Report written to $REPORT_PATH"
|
||||
[ "$failed" -eq 0 ]
|
||||
}
|
||||
|
||||
main "$@"
|
||||
@@ -37,7 +37,13 @@ ensure_dirs() {
|
||||
atomic_write() {
|
||||
target="$1"
|
||||
tmp="$2"
|
||||
mode="${3:-644}"
|
||||
mkdir -p "$(dirname "$target")"
|
||||
# Standard 0644, damit der Nearline-Pull-Workflow (docs/H_DRIVE_NEARLINE_PULL.md)
|
||||
# und Restore-Test-Skripte die Dumps per SMB-Read-Share oder unprivilegiert
|
||||
# lesen koennen. Sensible Sonderfaelle wie unraid-flash-config rufen mit
|
||||
# explizitem 600 auf, damit die bewusste Beschraenkung erhalten bleibt.
|
||||
chmod "$mode" "$tmp"
|
||||
mv "$tmp" "$target"
|
||||
}
|
||||
|
||||
@@ -179,15 +185,15 @@ backup_unraid_flash_config() {
|
||||
--exclude='config/plugins/*/*.zip' \
|
||||
--exclude='config/plugins/*/*.md5' \
|
||||
-czf "$tmp" config
|
||||
chmod 600 "$tmp"
|
||||
atomic_write "$output" "$tmp"
|
||||
# Flash-Config ist sensibel (enthaelt /boot/config inkl. Plugin-/SMB-/Network-Settings);
|
||||
# bewusst 0600, damit der Nearline-Pull ueber SMB sie nicht versehentlich greift.
|
||||
atomic_write "$output" "$tmp" 600
|
||||
|
||||
(
|
||||
cd "$LATEST_DIR"
|
||||
sha256sum "$(basename "$output")"
|
||||
) > "$tmp_checksum"
|
||||
chmod 600 "$tmp_checksum"
|
||||
atomic_write "$checksum" "$tmp_checksum"
|
||||
atomic_write "$checksum" "$tmp_checksum" 600
|
||||
|
||||
{
|
||||
printf 'created_utc=%s\n' "$(date -u '+%Y-%m-%dT%H:%M:%SZ')"
|
||||
@@ -201,8 +207,7 @@ backup_unraid_flash_config() {
|
||||
printf 'note=%s\n' 'Contains Unraid configuration and must be treated as secret backup material.'
|
||||
printf 'excluded=%s\n' 'downloadable plugin package archives under /boot/config/plugins/*/'
|
||||
} > "$tmp_manifest"
|
||||
chmod 600 "$tmp_manifest"
|
||||
atomic_write "$manifest" "$tmp_manifest"
|
||||
atomic_write "$manifest" "$tmp_manifest" 600
|
||||
}
|
||||
|
||||
dump_optional_pg_db() {
|
||||
@@ -273,7 +278,7 @@ main() {
|
||||
need_cmd sha256sum
|
||||
ensure_dirs
|
||||
|
||||
# Shared PostgreSQL 17
|
||||
# Shared PostgreSQL 18 (historischer Containername: postgresql17)
|
||||
if need_container "postgresql17"; then
|
||||
# Use the cluster admin/superuser for all shared-cluster dumps. The
|
||||
# application roles exist, but they can have different passwords from the
|
||||
@@ -319,7 +324,7 @@ main() {
|
||||
|
||||
# Additional host-side SQLite dumps for admin tooling with appdata files.
|
||||
dump_sqlite_file "/mnt/user/appdata/borg-ui/data/borg.db" "$LATEST_DIR/borg-ui.sqlite" "borg-ui"
|
||||
dump_sqlite_file "/mnt/user/appdata/grafana/grafana.db" "$LATEST_DIR/grafana.sqlite" "grafana"
|
||||
dump_sqlite_file "/var/lib/docker/volumes/monitoring_grafana_data/_data/grafana.db" "$LATEST_DIR/grafana.sqlite" "grafana"
|
||||
|
||||
# MongoDB
|
||||
dump_mongo_container "komodo-mongo" "$LATEST_DIR/komodo-mongo.archive.gz"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
code-server:
|
||||
image: lscr.io/linuxserver/code-server:4.116.0@sha256:4620adace18935dd6ca79d77e3bc1c379e21875392192f970cf5d6b0fb4aefcd
|
||||
image: lscr.io/linuxserver/code-server:4.122.0@sha256:0caf1b65ebec84b94397108b56da6c33f124c5390f5832da94e75f4609c0e2ad
|
||||
container_name: code-server
|
||||
restart: unless-stopped
|
||||
security_opt:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
filebrowser:
|
||||
image: filebrowser/filebrowser:v2.63.2@sha256:4dce87308b9f9cfbcf8d0a284fc9565d2b515530a6bae2d920b388161e093f26
|
||||
image: filebrowser/filebrowser:v2.63.5@sha256:aefb0c20de10ef8b617995ca5522479ad40d41e6386bd01946a345c6026ff31c
|
||||
container_name: filebrowser
|
||||
restart: unless-stopped
|
||||
security_opt:
|
||||
|
||||
@@ -473,7 +473,7 @@ pages:
|
||||
category: core
|
||||
hide: false
|
||||
postgresql17:
|
||||
name: PostgreSQL 17
|
||||
name: PostgreSQL 18
|
||||
icon: si:postgresql
|
||||
description: Shared DB
|
||||
category: core
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
glance:
|
||||
image: glanceapp/glance:v0.8.4
|
||||
image: glanceapp/glance:v0.8.5
|
||||
container_name: glance
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
glances:
|
||||
image: nicolargo/glances:latest-full@sha256:b4b0f059fa8064a0e8dae5530ce9334834ab07205269cfbf405d16b4d40c0c66
|
||||
image: nicolargo/glances:latest-full@sha256:60872a1af0e40a3150975617c7e811ad7ad48f95bc45d033fb0c1737a037e4d2
|
||||
container_name: glances
|
||||
restart: unless-stopped
|
||||
pid: host
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
param(
|
||||
[string]$SourceRoot = "\\192.168.178.58\backups",
|
||||
[string]$DestinationRoot = "H:\kallilab-nearline-backups",
|
||||
[switch]$WhatIf
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$Jobs = @(
|
||||
@{
|
||||
Name = "borg-dumps-latest"
|
||||
Source = Join-Path $SourceRoot "borg\dumps\latest"
|
||||
Destination = Join-Path $DestinationRoot "borg-dumps\latest"
|
||||
Purpose = "Latest database/application dumps (Unraid-Flash-Artefakte bewusst ausgeschlossen, weil 0600 root:root - dafuer bleibt die Hetzner-Borg-Kette die Restore-Quelle)"
|
||||
# /XF schliesst bewusst die unraid-flash-config-Artefakte aus,
|
||||
# weil sie hostseitig 0600 root:root sind und der SMB-Share das
|
||||
# nicht ueberbruecken kann. Restore-Quelle dafuer bleibt das
|
||||
# Hetzner-Borg-Repo (siehe docs/RESTORE_MATRIX.md Tier 1 Unraid OS Flash).
|
||||
ExcludeFiles = @("unraid-flash-config.tar.gz", "unraid-flash-config.tar.gz.sha256", "unraid-flash-config.manifest.txt")
|
||||
},
|
||||
@{
|
||||
Name = "gitea-bundles"
|
||||
Source = Join-Path $SourceRoot "git-bundles\gitea"
|
||||
Destination = Join-Path $DestinationRoot "git-bundles\gitea"
|
||||
Purpose = "Verified bare-repository bundles for Gitea bootstrap"
|
||||
ExcludeFiles = @()
|
||||
}
|
||||
)
|
||||
|
||||
function Assert-PathExists {
|
||||
param(
|
||||
[string]$Path,
|
||||
[string]$Label
|
||||
)
|
||||
|
||||
if (-not (Test-Path -LiteralPath $Path)) {
|
||||
throw "$Label not found: $Path"
|
||||
}
|
||||
}
|
||||
|
||||
function Invoke-RobocopyJob {
|
||||
param(
|
||||
[hashtable]$Job,
|
||||
[string]$LogRoot
|
||||
)
|
||||
|
||||
$logPath = Join-Path $LogRoot ("{0}-{1}.log" -f (Get-Date -Format "yyyyMMdd-HHmmss"), $Job.Name)
|
||||
New-Item -ItemType Directory -Force -Path $Job.Destination | Out-Null
|
||||
|
||||
$args = @(
|
||||
$Job.Source,
|
||||
$Job.Destination,
|
||||
"/E",
|
||||
"/COPY:DAT",
|
||||
"/DCOPY:DAT",
|
||||
"/R:2",
|
||||
"/W:5",
|
||||
"/FFT",
|
||||
"/XJ",
|
||||
"/XD",
|
||||
".tmp",
|
||||
"/NP",
|
||||
"/TEE",
|
||||
"/LOG:$logPath"
|
||||
)
|
||||
|
||||
if ($Job.ContainsKey("ExcludeFiles") -and $Job.ExcludeFiles.Count -gt 0) {
|
||||
$args += "/XF"
|
||||
$args += $Job.ExcludeFiles
|
||||
}
|
||||
|
||||
Write-Host "Running robocopy job: $($Job.Name)"
|
||||
Write-Host " Source: $($Job.Source)"
|
||||
Write-Host " Destination: $($Job.Destination)"
|
||||
|
||||
# stdout an $null hängen, damit der Robocopy-Live-Output nicht
|
||||
# in $results landet und die Report-Tabelle zerlegt. /LOG: + /TEE
|
||||
# protokollieren weiter vollstaendig.
|
||||
& robocopy @args | Out-Null
|
||||
$code = $LASTEXITCODE
|
||||
|
||||
if ($code -gt 7) {
|
||||
throw "Robocopy job '$($Job.Name)' failed with exit code $code. See log: $logPath"
|
||||
}
|
||||
|
||||
[pscustomobject]@{
|
||||
Name = $Job.Name
|
||||
Source = $Job.Source
|
||||
Destination = $Job.Destination
|
||||
ExitCode = $code
|
||||
Log = $logPath
|
||||
}
|
||||
}
|
||||
|
||||
Assert-PathExists -Path $SourceRoot -Label "Source root"
|
||||
|
||||
foreach ($job in $Jobs) {
|
||||
Assert-PathExists -Path $job.Source -Label "Source for job '$($job.Name)'"
|
||||
}
|
||||
|
||||
if ($WhatIf) {
|
||||
Write-Host "H:/ nearline pull plan only. No files will be copied."
|
||||
Write-Host "SourceRoot: $SourceRoot"
|
||||
Write-Host "DestinationRoot: $DestinationRoot"
|
||||
Write-Host ""
|
||||
foreach ($job in $Jobs) {
|
||||
Write-Host "- $($job.Name)"
|
||||
Write-Host " Purpose: $($job.Purpose)"
|
||||
Write-Host " Source: $($job.Source)"
|
||||
Write-Host " Destination: $($job.Destination)"
|
||||
}
|
||||
exit 0
|
||||
}
|
||||
|
||||
$destinationDrive = Split-Path -Qualifier $DestinationRoot
|
||||
Assert-PathExists -Path $destinationDrive -Label "Destination drive"
|
||||
|
||||
$logRoot = Join-Path $DestinationRoot "_logs"
|
||||
$reportRoot = Join-Path $DestinationRoot "_reports"
|
||||
New-Item -ItemType Directory -Force -Path $DestinationRoot, $logRoot, $reportRoot | Out-Null
|
||||
|
||||
$results = foreach ($job in $Jobs) {
|
||||
Invoke-RobocopyJob -Job $job -LogRoot $logRoot
|
||||
}
|
||||
|
||||
$reportPath = Join-Path $reportRoot ("nearline-pull-{0}.md" -f (Get-Date -Format "yyyy-MM-dd-HHmmss"))
|
||||
|
||||
$lines = @()
|
||||
$lines += "# H:/ Nearline Pull Report - $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')"
|
||||
$lines += ""
|
||||
$lines += "- Source root: ``$SourceRoot``"
|
||||
$lines += "- Destination root: ``$DestinationRoot``"
|
||||
$lines += "- Mode: non-destructive copy, no ``/MIR``, no purge"
|
||||
$lines += ""
|
||||
$lines += "| Job | Exit code | Source | Destination | Log |"
|
||||
$lines += "|---|---:|---|---|---|"
|
||||
foreach ($result in $results) {
|
||||
$lines += "| $($result.Name) | $($result.ExitCode) | ``$($result.Source)`` | ``$($result.Destination)`` | ``$($result.Log)`` |"
|
||||
}
|
||||
$lines += ""
|
||||
$lines += "Expected critical artifacts after run:"
|
||||
$lines += ""
|
||||
$lines += "- ``borg-dumps/latest/immich.dump``"
|
||||
$lines += "- ``borg-dumps/latest/komodo-mongo.archive.gz``"
|
||||
$lines += "- ``git-bundles/gitea/latest-report.md``"
|
||||
$lines += "- ``git-bundles/gitea/micha/*.bundle``"
|
||||
$lines += ""
|
||||
$lines += "Bewusst NICHT in Nearline-Scope:"
|
||||
$lines += ""
|
||||
$lines += "- ``unraid-flash-config.tar.gz`` (hostseitig 0600 root:root; Restore aus Hetzner-Borg)"
|
||||
|
||||
$lines | Set-Content -LiteralPath $reportPath -Encoding UTF8
|
||||
|
||||
Write-Host "H:/ nearline pull completed."
|
||||
Write-Host "Report: $reportPath"
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM nousresearch/hermes-agent:v2026.4.16
|
||||
FROM nousresearch/hermes-agent:v2026.5.29
|
||||
|
||||
USER root
|
||||
|
||||
|
||||
@@ -90,16 +90,16 @@
|
||||
"notes": "ADMIN_TOKEN_FILE; keine direkten Host-Ports"
|
||||
},
|
||||
"postgresql17": {
|
||||
"description": "Shared PostgreSQL Cluster",
|
||||
"description": "Shared PostgreSQL 18 Cluster (historischer Containername)",
|
||||
"tier": 1,
|
||||
"category": "infra",
|
||||
"container_name": "postgresql17",
|
||||
"dependencies": [],
|
||||
"url": null,
|
||||
"dump_file": null,
|
||||
"data_paths": ["/mnt/user/appdata/postgresql17"],
|
||||
"data_paths": ["/mnt/user/appdata/postgresql18"],
|
||||
"first_check": "backend_net Konnektivitaet? Disk-Space auf /mnt/user/appdata? pg_isready im Container?",
|
||||
"notes": "Dumps per Dienst unter dumps/latest; raw DB nicht primaerer Restore-Weg"
|
||||
"notes": "Dumps per Dienst unter dumps/latest; raw DB nicht primaerer Restore-Weg; alter PG17-Pfad bleibt nur Rollback-Altstand"
|
||||
},
|
||||
"komodo-core": {
|
||||
"description": "GitOps UI / API / Stack-Manager",
|
||||
@@ -200,9 +200,9 @@
|
||||
"dependencies": [],
|
||||
"url": null,
|
||||
"dump_file": "immich.dump",
|
||||
"data_paths": ["/mnt/user/appdata/immich_postgres"],
|
||||
"data_paths": ["/mnt/user/appdata/immich_postgres_vectorchord"],
|
||||
"first_check": "immich_default Netz? Disk-Space? pg_isready?",
|
||||
"notes": "nie ins frontend_net; immich_default Netz isoliert"
|
||||
"notes": "PG14 mit VectorChord/pgvector; nie ins frontend_net; immich_default Netz isoliert; alter immich_postgres-Pfad bleibt nur Rollback-Altstand"
|
||||
},
|
||||
"immich_redis": {
|
||||
"description": "Immich Cache",
|
||||
@@ -248,7 +248,7 @@
|
||||
"dependencies": [],
|
||||
"url": null,
|
||||
"dump_file": "mealie.dump",
|
||||
"data_paths": ["/mnt/user/appdata/mealie/postgres"],
|
||||
"data_paths": ["/mnt/user/appdata/mealie/postgres18"],
|
||||
"first_check": "mealie_internal Netz? Disk-Space?",
|
||||
"notes": "interne DB; mealie_internal Netz"
|
||||
},
|
||||
@@ -287,7 +287,7 @@
|
||||
"dependencies": [],
|
||||
"url": null,
|
||||
"dump_file": null,
|
||||
"data_paths": ["/mnt/user/appdata/nextcloud/postgres"],
|
||||
"data_paths": ["/mnt/user/appdata/nextcloud/postgres18"],
|
||||
"first_check": "nextcloud_internal Netz? Disk-Space?",
|
||||
"notes": "interne DB"
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# services.yaml — Maschinenlesbare Wissensbasis fuer Hermes Alert Enrichment
|
||||
#
|
||||
# Abgeleitet aus docs/SERVICE_CATALOG.md
|
||||
# Stand: 2026-05-06
|
||||
# Stand: 2026-05-31
|
||||
#
|
||||
# Zweck: Hermes laedt diese Datei beim Alert-Anreichern, um Abhaengigkeiten,
|
||||
# Dump-Zeitstempel und den ersten Diagnoseschritt nachzuschlagen.
|
||||
@@ -128,7 +128,7 @@ services:
|
||||
notes: "ADMIN_TOKEN_FILE; keine direkten Host-Ports"
|
||||
|
||||
postgresql17:
|
||||
description: Shared PostgreSQL Cluster (Authelia, Paperless, Mail-Archiver, Mealie, Komodo indirekt)
|
||||
description: Shared PostgreSQL 18 Cluster (historischer Containername; Authelia, Paperless, Mail-Archiver)
|
||||
tier: 1
|
||||
category: infra
|
||||
container_name: postgresql17
|
||||
@@ -136,9 +136,9 @@ services:
|
||||
url: null
|
||||
dump_file: null
|
||||
data_paths:
|
||||
- /mnt/user/appdata/postgresql17
|
||||
- /mnt/user/appdata/postgresql18
|
||||
first_check: "backend_net Konnektivitaet? Disk-Space auf /mnt/user/appdata? pg_isready im Container?"
|
||||
notes: "Dumps per Dienst unter dumps/latest; raw DB nicht primaerer Restore-Weg"
|
||||
notes: "Dumps per Dienst unter dumps/latest; raw DB nicht primaerer Restore-Weg; alter PG17-Pfad bleibt nur Rollback-Altstand"
|
||||
|
||||
komodo-core:
|
||||
description: GitOps UI / API / Stack-Manager
|
||||
@@ -261,9 +261,9 @@ services:
|
||||
url: null
|
||||
dump_file: immich.dump
|
||||
data_paths:
|
||||
- /mnt/user/appdata/immich_postgres
|
||||
- /mnt/user/appdata/immich_postgres_vectorchord
|
||||
first_check: "immich_default Netz? Disk-Space? pg_isready?"
|
||||
notes: "nie ins frontend_net; immich_default Netz isoliert"
|
||||
notes: "PG14 mit VectorChord/pgvector; nie ins frontend_net; immich_default Netz isoliert; alter immich_postgres-Pfad bleibt nur Rollback-Altstand"
|
||||
|
||||
immich_redis:
|
||||
description: Immich Cache
|
||||
@@ -314,7 +314,7 @@ services:
|
||||
url: null
|
||||
dump_file: mealie.dump
|
||||
data_paths:
|
||||
- /mnt/user/appdata/mealie/postgres
|
||||
- /mnt/user/appdata/mealie/postgres18
|
||||
first_check: "mealie_internal Netz? Disk-Space?"
|
||||
notes: "interne DB; mealie_internal Netz"
|
||||
|
||||
@@ -360,7 +360,7 @@ services:
|
||||
url: null
|
||||
dump_file: null
|
||||
data_paths:
|
||||
- /mnt/user/appdata/nextcloud/postgres
|
||||
- /mnt/user/appdata/nextcloud/postgres18
|
||||
first_check: "nextcloud_internal Netz? Disk-Space?"
|
||||
notes: "interne DB"
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ services:
|
||||
# Netz: komodo_net (internal: true) – niemals frontend_net
|
||||
# ──────────────────────────────────────────────────────────────────
|
||||
komodo-mongo:
|
||||
image: mongo:7.0.32@sha256:32979a1189dfdc44da3f5ed40d910495f5ad8f6f7f77556646f890a30b2d3f56
|
||||
image: mongo:8.0.23@sha256:44aa79ae28ff80b56fe58681b66cda9336706df408a5175a6c04988aa54610d3
|
||||
container_name: komodo-mongo
|
||||
labels:
|
||||
komodo.skip:
|
||||
@@ -33,7 +33,7 @@ services:
|
||||
# Admin-Dienst: bewusst ohne pauschale ForwardAuth-Middleware; dokumentierte Ausnahme
|
||||
# ──────────────────────────────────────────────────────────────────
|
||||
komodo-core:
|
||||
image: ghcr.io/moghtech/komodo-core:2@sha256:8a7dbba232e4e49797bb412be5f78207c89fcf22cc2727b38631ae30f7518a4c
|
||||
image: ghcr.io/moghtech/komodo-core:2@sha256:7afbcfa99674bf3f51539ec3aa7235795e9b994af9b7099a6c4c654d5d8a5b6b
|
||||
container_name: komodo-core
|
||||
init: true
|
||||
restart: unless-stopped
|
||||
@@ -79,7 +79,7 @@ services:
|
||||
# Ausnahme: Docker-Socket ohne :ro (Periphery startet/stoppt Container)
|
||||
# ──────────────────────────────────────────────────────────────────
|
||||
komodo-periphery:
|
||||
image: ghcr.io/moghtech/komodo-periphery:2@sha256:8ac9f2ef9c1461b95c862d445da00253005e7094d1e30f5b7b04b8d60ca7a3d6
|
||||
image: ghcr.io/moghtech/komodo-periphery:2@sha256:7fb1a4807d125ce036a17d37c940b4001402afcaf342a2c720c98d096b1b54da
|
||||
container_name: komodo-periphery
|
||||
init: true
|
||||
restart: unless-stopped
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
# Memory-Limits Baseline - Vorbereitung F-19
|
||||
|
||||
Status: **Vorbereitung**. Echte `mem_limit`-Werte werden erst gesetzt, wenn mindestens 7 Tage realer Peak-Werte vorliegen.
|
||||
|
||||
Bezug: `docs/archive/2026-05/AUDIT_2026-05-25.md` F-19 "Keine Container-Memory-Limits".
|
||||
|
||||
## Warum nicht heute
|
||||
|
||||
Audit-TODO 2026-05-30: F-19 ist nicht akut. Im `docs/MIGRATION_LOG.md` ist **kein einziger** OOM-/Memory-Vorfall dokumentiert. `services/posture-check/docker-critical-events.sh` ueberwacht `die`/`oom`/`kill`-Events und alarmiert via ntfy — der Detektions-Pfad ist da, der Daten-Befund fehlt. Limits ohne Peak-Daten zu setzen bedeutet entweder zu eng (Flapping) oder so weit weg vom Realwert, dass die Schutzwirkung gegen Null geht.
|
||||
|
||||
Familien-Einladung verschiebt die Risiko-Bilanz nach oben: Ein OOM in Authelia/Postgres bei Familien-Nutzung kostet Vertrauen, nicht nur Operator-Zeit. Sobald die Einladung raus ist, wird F-19 ein "should" statt "nice".
|
||||
|
||||
## Plan
|
||||
|
||||
### Phase 1 - Peak-Beobachtung (7 Tage)
|
||||
|
||||
Auf dem Host stuendlich `docker stats --no-stream` snappen und in eine Textfile pro Container schreiben. Beispiel-Snippet fuer das Cron-Skript:
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
# /boot/config/plugins/user.scripts/scripts/docker-stats-snapshot/script
|
||||
set -euo pipefail
|
||||
OUT="/mnt/user/services/policy-checks/docker-stats-$(date +%Y%m%d).log"
|
||||
mkdir -p "$(dirname "$OUT")"
|
||||
{
|
||||
echo "=== $(date -Iseconds) ==="
|
||||
docker stats --no-stream --format 'table {{.Name}}\t{{.MemUsage}}\t{{.MemPerc}}\t{{.CPUPerc}}'
|
||||
} >> "$OUT"
|
||||
```
|
||||
|
||||
Cron: stuendlich (`0 * * * *`), 7 Tage laufen lassen.
|
||||
|
||||
### Phase 2 - Peak-Auswertung
|
||||
|
||||
Pro Tier-1-Container das Maximum `MemUsage` aus dem 7-Tage-Log ableiten:
|
||||
|
||||
```bash
|
||||
grep -E '^postgresql17|^authelia|^Redis|^vaultwarden|^gitea|^traefik|^komodo-mongo' \
|
||||
/mnt/user/services/policy-checks/docker-stats-*.log \
|
||||
| awk -F'\t' '{print $1, $2}' \
|
||||
| sort -u
|
||||
```
|
||||
|
||||
Erwartete Groessenordnungen (zur Plausibilitaetspruefung, nicht zur Festlegung):
|
||||
|
||||
| Container | Erwartung |
|
||||
|---|---|
|
||||
| postgresql17 | 200-600 MB |
|
||||
| Redis | 30-80 MB |
|
||||
| authelia | 50-150 MB |
|
||||
| vaultwarden | 100-300 MB |
|
||||
| gitea | 200-500 MB |
|
||||
| traefik | 80-200 MB |
|
||||
| komodo-mongo | 300-800 MB |
|
||||
|
||||
### Phase 3 - Limit-Setting
|
||||
|
||||
Pro Tier-1-Container:
|
||||
|
||||
```yaml
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: <peak * 1.5, mindestens floor>
|
||||
```
|
||||
|
||||
Floor-Werte:
|
||||
|
||||
- postgresql17: 1G (Cache-Verhalten leidet bei weniger)
|
||||
- komodo-mongo: 1G (WiredTiger braucht Working-Set)
|
||||
- Redis: 256M (Paperless-Cache)
|
||||
- vaultwarden: 256M
|
||||
- gitea: 512M
|
||||
- traefik: 256M
|
||||
- authelia: 256M
|
||||
|
||||
`mem_reservation` bewusst nicht setzen — auf einem Single-Host-Setup ist Reservation Theater.
|
||||
|
||||
### Phase 4 - Rollout-Reihenfolge
|
||||
|
||||
1. Redis und authelia zuerst (kleinste Risiko-Container, klares Memory-Profil).
|
||||
2. Wenn nach 48 h kein Flapping: traefik, vaultwarden, gitea.
|
||||
3. Zuletzt postgresql17 und komodo-mongo, weil DB-Limits bei zu engem Setting Performance kippen.
|
||||
|
||||
Jede Stufe einzeln committen und 24 h beobachten.
|
||||
|
||||
### Phase 5 - Tier-2 (optional)
|
||||
|
||||
Tier-2 (Immich, Nextcloud, Paperless, Mealie, Mail-Archiver) bewusst spaeter, nur wenn ein konkreter Vorfall das rechtfertigt. Immich-ML ist der wahrscheinlichste Kandidat fuer den ersten echten OOM-Vorfall, deshalb dort zuerst beobachten, dann limitieren.
|
||||
|
||||
## Stop-Regel
|
||||
|
||||
Falls in Phase 3 ein Container nach Limit-Setzung haeufiger restartet als vor dem Limit: Limit raus, kein zweiter Versuch ohne dazwischenliegende Peak-Reanalyse. Doku-Eintrag in `docs/MIGRATION_LOG.md`, F-19 weiter offen.
|
||||
|
||||
## Was nicht ins Skript gehoert
|
||||
|
||||
- Mem-Limits sind kein Tuning, kein Performance-Hebel. Wer sich Performance erhofft, hat das falsche Werkzeug.
|
||||
- Postgres-`shared_buffers` und `effective_cache_size` muessen zur Limit-Groesse passen. Setzen ohne Postgres-internes Tuning macht die DB langsamer, nicht stabiler.
|
||||
- Komodo-Mongo waechst mit Stack-/Update-Historie. Limit fuer naechste 12 Monate planen, nicht fuer den heutigen Stand.
|
||||
|
||||
## Naechster Trigger
|
||||
|
||||
- Familien-Einladung raus, 4 Wochen stabile Nutzung, **oder**
|
||||
- erster echter OOM-Vorfall im `docker-critical-events.sh`-Log, **oder**
|
||||
- ein Immich/Nextcloud-Last-Sprung (z.B. grosses Foto-Backup), bei dem Host-Swap sichtbar wird.
|
||||
|
||||
Bei einem dieser Trigger: Phase 1 starten.
|
||||
@@ -0,0 +1,26 @@
|
||||
// Renovate Bot-Config (NICHT die Repo-Config).
|
||||
//
|
||||
// Die Repo-Config liegt im Repository selbst unter `renovate.json` und
|
||||
// enthaelt nur Repo-spezifische Sachen (extends, packageRules, ignorePaths,
|
||||
// docker-compose patterns).
|
||||
//
|
||||
// Diese Bot-Config hier wird ueber RENOVATE_CONFIG_FILE in den Renovate-
|
||||
// Container gemountet. Sie enthaelt nur Plattform-, Discovery- und Limits-
|
||||
// Einstellungen. Den Auth-Token uebergeben wir ueber --env-file.
|
||||
module.exports = {
|
||||
platform: "gitea",
|
||||
endpoint: "https://git.kaleschke.info/api/v1",
|
||||
username: "renovate",
|
||||
gitAuthor: "Renovate Bot <renovate@kaleschke.info>",
|
||||
onboarding: false,
|
||||
requireConfig: "optional",
|
||||
// Autodiscover funktioniert in Gitea nur fuer eigene/Org-Repos; unser
|
||||
// Service-Account hat nur Collaborator-Rechte. Daher explicit list.
|
||||
autodiscover: false,
|
||||
repositories: ["Micha/homelab-infra"],
|
||||
// Limits konservativ: wenig PRs gleichzeitig, damit das Review-Volumen
|
||||
// handhabbar bleibt.
|
||||
prHourlyLimit: 0,
|
||||
prConcurrentLimit: 5,
|
||||
branchConcurrentLimit: 10,
|
||||
};
|
||||
@@ -0,0 +1,85 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
# Self-hosted Renovate runner fuer Gitea.
|
||||
#
|
||||
# Wird vom Host-User-Script `renovate-six-hourly` aufgerufen. Liest das
|
||||
# Gitea-PAT aus einer Host-Secret-Datei, startet den Renovate-Container
|
||||
# ein einziges Mal, schreibt ein Log, beendet sich.
|
||||
#
|
||||
# Operator-Setup-Aufgaben (einmalig):
|
||||
# 1. Gitea-User `renovate` anlegen (Service-Account), 2FA nicht zwingend
|
||||
# 2. Diesem User Repo-Schreibrechte auf `Micha/*` geben
|
||||
# 3. Im Gitea-Profil des renovate-Users ein Access-Token erzeugen:
|
||||
# Scope: `write:repository` + `read:user`
|
||||
# 4. Token in `/mnt/user/appdata/secrets/renovate_token.txt` ablegen (chmod 600)
|
||||
# 5. Erstlauf: `bash /mnt/user/services/homelab-infra/ops/renovate/run-renovate.sh`
|
||||
# 6. User-Script `renovate-six-hourly` aktivieren
|
||||
|
||||
RENOVATE_IMAGE="${RENOVATE_IMAGE:-renovate/renovate:41}"
|
||||
RENOVATE_TOKEN_FILE="${RENOVATE_TOKEN_FILE:-/mnt/user/appdata/secrets/renovate_token.txt}"
|
||||
RENOVATE_LOG_DIR="${RENOVATE_LOG_DIR:-/mnt/user/services/renovate/logs}"
|
||||
RENOVATE_STATE_DIR="${RENOVATE_STATE_DIR:-/mnt/user/services/renovate/state}"
|
||||
RENOVATE_CONFIG_FILE="${RENOVATE_CONFIG_FILE:-/mnt/user/services/homelab-infra/ops/renovate/bot-config.js}"
|
||||
# Gitea sitzt hinter Traefik unter git.kaleschke.info; der WAN-Pfad geht
|
||||
# ueber Public-IP -> FRITZBox. Vom Docker-Container aus loest der Standard-
|
||||
# Resolver den Host moeglicherweise nicht auf (siehe `extra_hosts` im Komodo-
|
||||
# Compose). Wir mappen direkt auf die LAN-IP des Unraid-Hosts.
|
||||
GITEA_HOST_LAN_IP="${GITEA_HOST_LAN_IP:-192.168.178.58}"
|
||||
|
||||
if [ ! -r "$RENOVATE_TOKEN_FILE" ]; then
|
||||
echo "Renovate token file missing or unreadable: $RENOVATE_TOKEN_FILE" >&2
|
||||
echo "See ops/renovate/run-renovate.sh header for operator setup steps." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -r "$RENOVATE_CONFIG_FILE" ]; then
|
||||
echo "Renovate config missing: $RENOVATE_CONFIG_FILE" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$RENOVATE_LOG_DIR" "$RENOVATE_STATE_DIR"
|
||||
|
||||
TS="$(date -u '+%Y-%m-%dT%H-%M-%SZ')"
|
||||
LOG_FILE="$RENOVATE_LOG_DIR/renovate-$TS.log"
|
||||
LATEST_SYMLINK="$RENOVATE_LOG_DIR/latest.log"
|
||||
|
||||
# Renovate liest die Konfiguration ueber RENOVATE_CONFIG_FILE als Pfad im
|
||||
# Container; wir mounten die Repo-Datei read-only nach /usr/src/app/config.json.
|
||||
{
|
||||
echo "[renovate] starting $TS"
|
||||
echo "[renovate] image: $RENOVATE_IMAGE"
|
||||
echo "[renovate] config: $RENOVATE_CONFIG_FILE"
|
||||
echo "[renovate] log: $LOG_FILE"
|
||||
echo
|
||||
|
||||
# Token wird ueber --env-file uebergeben, damit der Wert weder in
|
||||
# `ps`-Ausgabe noch im docker inspect -Snapshot landet. Das Env-File
|
||||
# liegt unter $RENOVATE_STATE_DIR/.env und wird mit 0600 angelegt.
|
||||
ENV_FILE="$RENOVATE_STATE_DIR/.env"
|
||||
umask 077
|
||||
cat > "$ENV_FILE" <<EFEOF
|
||||
RENOVATE_TOKEN=$(cat "$RENOVATE_TOKEN_FILE")
|
||||
RENOVATE_CONFIG_FILE=/usr/src/app/config.js
|
||||
LOG_LEVEL=${RENOVATE_LOG_LEVEL:-info}
|
||||
EFEOF
|
||||
chmod 600 "$ENV_FILE"
|
||||
|
||||
docker run --rm \
|
||||
--name renovate-run \
|
||||
--add-host "git.kaleschke.info:$GITEA_HOST_LAN_IP" \
|
||||
--dns 1.1.1.1 \
|
||||
--dns 8.8.8.8 \
|
||||
-v "$RENOVATE_CONFIG_FILE":/usr/src/app/config.js:ro \
|
||||
-v "$RENOVATE_STATE_DIR":/tmp/renovate \
|
||||
--env-file "$ENV_FILE" \
|
||||
"$RENOVATE_IMAGE" 2>&1
|
||||
rc=$?
|
||||
shred -u "$ENV_FILE" 2>/dev/null || rm -f "$ENV_FILE"
|
||||
|
||||
echo
|
||||
echo "[renovate] finished rc=$rc"
|
||||
exit $rc
|
||||
} | tee "$LOG_FILE"
|
||||
|
||||
ln -sfn "$LOG_FILE" "$LATEST_SYMLINK"
|
||||
@@ -32,6 +32,11 @@ Ziel:
|
||||
- `paperless-restore-test.sh`: hosttauglicher Paperless-Restore-Job
|
||||
- `paperless-plan.md`: konkreter Paperless-Testplan
|
||||
- `paperless-compose.test.yml`: isolierte Testinstanz fuer Paperless inkl. Test-Postgres und Test-Redis
|
||||
- `immich-restore-test.ps1`: Immich-Mini-Restore-Ablauf als Plan-/Windows-Scaffold
|
||||
- `immich-restore-test.sh`: hosttauglicher Immich-Restore-Job, erster echter Lauf noch offen
|
||||
- `immich-plan.md`: konkreter Immich-Testplan
|
||||
- `immich-runbook.md`: Operator-Runbook fuer den ersten Immich-Lauf
|
||||
- `immich-compose.test.yml`: isolierte Testinstanz fuer Immich inkl. VectorChord/pgvector-Test-Postgres und Test-Redis
|
||||
- `check-restore-freshness.ps1`: woechentlicher Frische-Check fuer Dumps und Reports
|
||||
- `run-restore-checks.ps1`: einfacher Dispatcher fuer Restore-Jobs
|
||||
- `check-restore-freshness.sh`: hosttauglicher Frische-Check
|
||||
@@ -76,9 +81,10 @@ Aktuell ist das erste validierte Muster vorhanden.
|
||||
- echter Vaultwarden-Restore am 2026-05-07 erfolgreich verifiziert
|
||||
- echter Gitea-Restore am 2026-05-07 erfolgreich verifiziert
|
||||
- echter Paperless-Restore am 2026-05-07 erfolgreich verifiziert
|
||||
- Immich-Restore-Test am 2026-05-27 erfolgreich verifiziert; Test-Postgres wurde nach der VectorChord-Migration am 2026-05-31 auf das produktive Immich-Postgres-Image umgestellt
|
||||
- Bash-Dispatcher und Bash-Restore-Jobs am 2026-05-07 erfolgreich hostseitig verifiziert
|
||||
- Restore-Lab und Report-Pfade auf dem Host angelegt
|
||||
- V1-Ablauf weiter ohne `ntfy`, mit Bereinigung nach Erfolg
|
||||
- naechster grosser Kandidat ist ein weiterer datenbankgestuetzter Dienst oder die Automatisierung
|
||||
- naechster grosser Kandidat ist ein erneuter Immich-Lauf nach VectorChord-Migration mit Zeitmessung; danach in die Rotation aufnehmen
|
||||
|
||||
Vor dem ersten echten Testlauf muessen Zielpfade, Quellpfade und Bereinigungsschritte bewusst freigegeben werden.
|
||||
|
||||
@@ -21,11 +21,11 @@ require_path() {
|
||||
}
|
||||
|
||||
latest_archive_name() {
|
||||
docker exec "$BORG_CONTAINER" python3 - <<'PY'
|
||||
docker exec -i "$BORG_CONTAINER" python3 - <<'PY'
|
||||
import sqlite3
|
||||
conn = sqlite3.connect('/data/borg.db')
|
||||
cur = conn.cursor()
|
||||
cur.execute("select archive_name from backup_jobs where status='completed' order by created_at desc limit 1")
|
||||
cur.execute("select archive_name from backup_jobs where status in ('completed', 'completed_with_warnings') order by created_at desc limit 1")
|
||||
row = cur.fetchone()
|
||||
if not row:
|
||||
raise SystemExit("No completed borg archive found")
|
||||
@@ -34,7 +34,7 @@ PY
|
||||
}
|
||||
|
||||
borg_repo_url() {
|
||||
docker exec "$BORG_CONTAINER" python3 - <<'PY'
|
||||
docker exec -i "$BORG_CONTAINER" python3 - <<'PY'
|
||||
import sqlite3
|
||||
conn = sqlite3.connect('/data/borg.db')
|
||||
cur = conn.cursor()
|
||||
@@ -59,10 +59,16 @@ conn = sqlite3.connect('/data/borg.db')
|
||||
cur = conn.cursor()
|
||||
cur.execute("select path from repositories where path is not null and path != '' order by id asc limit 1")
|
||||
repo = cur.fetchone()[0]
|
||||
cur.execute("select archive_name from backup_jobs where status='completed' order by created_at desc limit 1")
|
||||
cur.execute("select archive_name from backup_jobs where status in ('completed', 'completed_with_warnings') order by created_at desc limit 1")
|
||||
archive = cur.fetchone()[0]
|
||||
with open('/local/secrets/borg_repo_passphrase.txt', 'r', encoding='utf-8') as f:
|
||||
os.environ['BORG_PASSPHRASE'] = f.read().strip()
|
||||
known_hosts = '/data/known_hosts'
|
||||
if os.path.exists(known_hosts):
|
||||
os.environ.setdefault(
|
||||
'BORG_RSH',
|
||||
f'ssh -o UserKnownHostsFile={known_hosts} -o StrictHostKeyChecking=yes',
|
||||
)
|
||||
os.makedirs(extract_dir, exist_ok=True)
|
||||
os.chdir(extract_dir)
|
||||
subprocess.run(['borg', 'extract', f'{repo}::{archive}', *paths], check=True)
|
||||
|
||||
Regular → Executable
@@ -0,0 +1,68 @@
|
||||
services:
|
||||
restoretest-immich-postgres:
|
||||
# Gleiches DB-Image wie Produktion, damit VectorChord/pgvector beim
|
||||
# Restore aus immich.dump verfuegbar sind.
|
||||
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0@sha256:bcf63357191b76a916ae5eb93464d65c07511da41e3bf7a8416db519b40b1c23
|
||||
container_name: restoretest-immich-postgres
|
||||
restart: "no"
|
||||
environment:
|
||||
TZ: Europe/Berlin
|
||||
POSTGRES_USER: immich
|
||||
POSTGRES_DB: immich
|
||||
POSTGRES_PASSWORD: restoretest-immich-db
|
||||
PGDATA: /var/lib/postgresql/data
|
||||
shm_size: 128mb
|
||||
volumes:
|
||||
- /mnt/user/backups/restore-lab/immich/postgres:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U immich -d immich"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 12
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
|
||||
restoretest-immich-redis:
|
||||
image: redis:8.8.0-alpine@sha256:09160599abd229764c0fb44cb6be640294e1d360a54b19985ab4843dcf2d90f1
|
||||
container_name: restoretest-immich-redis
|
||||
restart: "no"
|
||||
command:
|
||||
- redis-server
|
||||
- --save
|
||||
- ""
|
||||
- --appendonly
|
||||
- "no"
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
|
||||
restoretest-immich-server:
|
||||
# gleiches Image wie Produktion; ML-Container bleibt bewusst weg,
|
||||
# weil der Smoke-Test nur Login-Page, DB-Restore und Asset-Count prueft.
|
||||
image: ghcr.io/immich-app/immich-server:release@sha256:c15bff75068effb03f4355997d03dc7e0fc58720c2b54ad6f7f10d1bc57efaa5
|
||||
container_name: restoretest-immich-server
|
||||
restart: "no"
|
||||
depends_on:
|
||||
restoretest-immich-postgres:
|
||||
condition: service_healthy
|
||||
restoretest-immich-redis:
|
||||
condition: service_started
|
||||
environment:
|
||||
DB_HOSTNAME: restoretest-immich-postgres
|
||||
DB_USERNAME: immich
|
||||
DB_PASSWORD: restoretest-immich-db
|
||||
DB_DATABASE_NAME: immich
|
||||
REDIS_HOSTNAME: restoretest-immich-redis
|
||||
# ML bewusst deaktiviert: Endpoint zeigt auf eine lokale, nicht
|
||||
# erreichbare URL. Immich-Server startet, ML-Features bleiben aus.
|
||||
IMMICH_MACHINE_LEARNING_URL: http://restoretest-immich-ml-disabled:9999
|
||||
TZ: Europe/Berlin
|
||||
ports:
|
||||
# nur 127.0.0.1 - keine Public-Route, keine Traefik-Labels
|
||||
- "127.0.0.1:12283:2283"
|
||||
volumes:
|
||||
# Test-Upload-Verzeichnis ist leer und liegt im Restore-Lab.
|
||||
# Produktive Assets unter /mnt/user/photos/immich werden NICHT eingebunden,
|
||||
# damit der Smoke-Test keine produktiven Daten anfasst.
|
||||
- /mnt/user/backups/restore-lab/immich/upload:/usr/src/app/upload
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
@@ -0,0 +1,89 @@
|
||||
# Immich Restore Test Plan
|
||||
|
||||
## Ziel
|
||||
|
||||
Nachweisen, dass `immich.dump` aus dem produktiven Borg-Archiv in einer isolierten Testumgebung wieder einspielbar ist und Immich-Server damit anlaufen, einloggen und Asset-Metadaten anzeigen kann.
|
||||
|
||||
Bewusst **nicht** Teil dieses Tests:
|
||||
|
||||
- Wiederherstellung produktiver Foto-Dateien aus `/mnt/user/photos/immich` und `/mnt/user/photos/family_archive`. Der Smoke-Test bleibt DB-/UI-zentriert.
|
||||
- Machine-Learning-Container. Spart Image-Pull-Zeit und Resource-Last; ML-Features sind im Smoke-Test nicht erforderlich.
|
||||
- Echte Browser-Login-Sequenz. Smoke-Test prueft nur, dass die Login-Seite ausgeliefert wird und die DB-Tabellen `asset` und `"user"` lesbar sind.
|
||||
|
||||
## Quelle
|
||||
|
||||
- Backup-Quelle: produktives Borg-Archiv (`hetzner_borg_appdata_critical` oder lokales Mirror)
|
||||
- fachlich relevanter Dump im Archiv:
|
||||
- `local/borg-dumps/latest/immich.dump`
|
||||
- Erzeuger: `ops/borg-ui/scripts/pre-backup-dumps.sh`, Funktion `dump_pg_db immich_postgres ... immich immich` mit `pg_dump -Fc`
|
||||
- produktive Foto-Pfade werden im Smoke-Test bewusst **nicht** angefasst
|
||||
|
||||
## Test-Ziel
|
||||
|
||||
- Restore-Lab: `/mnt/user/backups/restore-lab/immich`
|
||||
- Testdatenpfade:
|
||||
- `/mnt/user/backups/restore-lab/immich/postgres` (Test-Postgres-Datadir)
|
||||
- `/mnt/user/backups/restore-lab/immich/upload` (leeres Upload-Volume, Immich-Server braucht den Pfad nur als Mountpoint)
|
||||
- `/mnt/user/backups/restore-lab/immich/dumps/latest/immich.dump` (extrahierter Dump)
|
||||
- Testcontainer:
|
||||
- `restoretest-immich-server`
|
||||
- `restoretest-immich-postgres` (`ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0` - identisch zur Produktion, weil VectorChord-Backups ein Image mit VectorChord brauchen)
|
||||
- `restoretest-immich-redis` (`redis:8.8.0-alpine`, rebuildbar)
|
||||
- Testport Web: `127.0.0.1:12283:2283`
|
||||
- Report-Ziel: `/mnt/user/backups/restore-reports/immich-YYYY-MM-DD.md`
|
||||
|
||||
## Schutzregeln
|
||||
|
||||
- produktive Pfade `/mnt/user/photos/immich` und `/mnt/user/photos/family_archive` werden **nicht** in den Test-Container gemountet
|
||||
- produktive Domain `immich.kaleschke.info` wird **nicht** uebernommen
|
||||
- keine Traefik-Labels fuer die Testinstanz
|
||||
- keine produktive `immich_postgres`-/`immich_redis`-Instanz fuer den Test verwenden
|
||||
- ML-Container bleibt weg
|
||||
- Testcontainer publishen nur auf `127.0.0.1`, nicht auf LAN- oder Tailscale-Interface
|
||||
- Borg-Passphrase wird aus `/mnt/user/appdata/secrets/borg_repo_passphrase.txt` gelesen und niemals in Logs, Reports oder Doku geschrieben
|
||||
|
||||
## Geplanter Ablauf
|
||||
|
||||
1. Restore-Ziel unter `/mnt/user/backups/restore-lab/immich` vorbereiten (postgres, upload, dumps/latest)
|
||||
2. `local/borg-dumps/latest/immich.dump` aus dem aktuellsten Borg-Archiv extrahieren
|
||||
3. Test-Postgres (Immich-Postgres mit VectorChord) und Test-Redis mit `ops/restore-tests/immich-compose.test.yml` starten
|
||||
4. `immich.dump` in Test-Postgres importieren (`pg_restore -Fc --clean --if-exists --no-owner --no-privileges`)
|
||||
5. Testinstanz `restoretest-immich-server` starten
|
||||
6. lokalen Smoke-Test gegen `http://127.0.0.1:12283` ausfuehren und Asset/User-Count aus DB lesen
|
||||
7. Report unter `/mnt/user/backups/restore-reports/immich-YYYY-MM-DD.md` schreiben
|
||||
8. Testcontainer stoppen und Restore-Lab bereinigen
|
||||
|
||||
## Smoke-Test
|
||||
|
||||
Minimal erfolgreich:
|
||||
|
||||
- Test-Postgres startet `healthy`
|
||||
- `pg_restore -Fc` laeuft ohne Fehler durch
|
||||
- Immich-Server liefert HTTP `200`, `302` oder `303` auf `/`
|
||||
- Response enthaelt mindestens einen der Marker `Immich`, `Login`, `Signin`
|
||||
- `select count(*) from asset;` und `select count(*) from "user";` sind lesbar
|
||||
|
||||
Optional spaeter:
|
||||
|
||||
- Echte Login-Form via API ansprechen
|
||||
- VectorChord-/pgvector-Extensions explizit per `\dx` pruefen
|
||||
- Test mit gemountetem **read-only** Foto-Sample-Pfad und Thumbnail-Rendering
|
||||
- Test inkl. ML-Container, sobald genug Test-Ressourcen verfuegbar
|
||||
|
||||
## Bekannte Komplikationen
|
||||
|
||||
| Risiko | Beschreibung | Mitigation |
|
||||
|---|---|---|
|
||||
| Dump-Groesse unbekannt | `pg_dump -Fc` der Immich-DB kann je nach Asset-/Face-Tabellen mehrere GB sein | Erster Lauf bewusst mit `--what-if`, anschliessend Operator-Test mit Zeitmessung |
|
||||
| `pg_restore`-Dauer unbekannt | Index-/Constraint-Aufbau und VectorChord-Index-Build koennen lange dauern | Test-Postgres mit Health-Polling startet; Lauf nicht abbrechen ohne `pg_restore`-Exit |
|
||||
| VectorChord-/pgvector-Extension-Mismatch | Wenn das Test-Postgres-Image nicht zu Produktion passt, kann der Restore oder Immich-Start fehlschlagen | Compose pinnt denselben Digest wie `apps/immich/docker-compose.yml` |
|
||||
| Immich-Server-Migrations beim Start | Immich fuehrt beim ersten Start DB-Migrations aus; das kann nach Restore noch laufen, bevor Web-UI antwortet | Smoke-Test pollt HTTP bis zu 120 s, bevor er als Fehler markiert |
|
||||
| Asset-Files fehlen | Der Test mountet kein Foto-Volume; Immich zeigt "missing" auf Asset-Detail-Seiten | Smoke-Test prueft nur Login-Page und DB-Counts, nicht Asset-Rendering |
|
||||
| ML-Endpoint unreachable | Immich-Server kann ML-Endpoint nicht erreichen | `IMMICH_MACHINE_LEARNING_URL` zeigt bewusst auf einen nicht erreichbaren Hostnamen; Login bleibt funktional, ML-Features bleiben deaktiviert |
|
||||
|
||||
## Noch offen vor dem ersten echten Lauf
|
||||
|
||||
- Dump-Groesse `immich.dump` auf dem Host bestimmen (`ls -lh /mnt/user/backups/borg/dumps/latest/immich.dump`)
|
||||
- Erwartete Restore-Dauer durch ersten Lauf mit `--keep-data` messen
|
||||
- Pruefen, ob die Immich-Tabellen `assets`/`users` im aktuellen Schema noch existieren (Schema-Drift bei Major-Update wuerde die Asset-Count-Query brechen, das Skript faengt das tolerant ab)
|
||||
- Schedule-Eintrag in `ops/restore-tests/schedule.md`: aktuell ist Immich nur als "spaeter, eigener Sprint" gefuehrt. Erst nach erstem erfolgreichen Lauf in Schedule aufnehmen, z. B. quartalsweise.
|
||||
@@ -0,0 +1,44 @@
|
||||
param(
|
||||
[string]$BackupSource = "/mnt/user/backups/borg",
|
||||
[string]$DumpSource = "/mnt/user/backups/borg/dumps/latest/immich.dump",
|
||||
[string]$RestoreRoot = "/mnt/user/backups/restore-lab/immich",
|
||||
[string]$ReportRoot = "/mnt/user/backups/restore-reports",
|
||||
[string]$BorgPassphraseFile = "/mnt/user/appdata/secrets/borg_repo_passphrase.txt",
|
||||
[switch]$WhatIf
|
||||
)
|
||||
|
||||
$Mode = if ($WhatIf) { "WhatIf" } else { "PlanOnly" }
|
||||
|
||||
Write-Output "Immich restore test scaffold"
|
||||
Write-Output "BackupSource: $BackupSource"
|
||||
Write-Output "DumpSource: $DumpSource"
|
||||
Write-Output "RestoreRoot: $RestoreRoot"
|
||||
Write-Output "ReportRoot: $ReportRoot"
|
||||
Write-Output "BorgPassphraseFile: $BorgPassphraseFile"
|
||||
Write-Output "Expected Borg source paths inside archive:"
|
||||
Write-Output " - local/borg-dumps/latest/immich.dump"
|
||||
Write-Output ""
|
||||
Write-Output "Planned isolation:"
|
||||
Write-Output " - Test Postgres: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0 (same as production)"
|
||||
Write-Output " - Test Redis: redis:8.8.0-alpine (rebuildable, no restore needed)"
|
||||
Write-Output " - Test Server: ghcr.io/immich-app/immich-server:release (pinned digest like production)"
|
||||
Write-Output " - ML container: deliberately omitted"
|
||||
Write-Output " - Test endpoint: 127.0.0.1:12283 (no Traefik, no public domain)"
|
||||
Write-Output " - Productive photo paths under /mnt/user/photos/* will NOT be mounted"
|
||||
Write-Output "Mode: $Mode"
|
||||
Write-Output ""
|
||||
Write-Output "Planned steps:"
|
||||
Write-Output "1. Prepare restore-lab target under /mnt/user/backups/restore-lab/immich"
|
||||
Write-Output "2. Extract immich.dump from current Borg archive into test path"
|
||||
Write-Output ' Template: borg extract "$BORG_REPO" "::ARCHIVE_NAME" local/borg-dumps/latest/immich.dump'
|
||||
Write-Output ' Passphrase source: $(cat /mnt/user/appdata/secrets/borg_repo_passphrase.txt)'
|
||||
Write-Output "3. Start isolated test Postgres (VectorChord/pgvector) and test Redis"
|
||||
Write-Output "4. Import immich.dump into test Postgres with pg_restore -Fc --clean --if-exists --no-owner --no-privileges"
|
||||
Write-Output "5. Start restoretest-immich-server against isolated DB/Redis (ML omitted)"
|
||||
Write-Output "6. Run smoke checks against http://127.0.0.1:12283 and DB asset count"
|
||||
Write-Output "7. Write markdown report under /mnt/user/backups/restore-reports"
|
||||
Write-Output "8. Stop test containers and clean restore data after success"
|
||||
Write-Output ""
|
||||
Write-Output "This script is intentionally a scaffold only."
|
||||
Write-Output "No restore, no dump import, no container start, no file write is executed yet."
|
||||
Write-Output "Actual run happens on the Unraid host via ops/restore-tests/immich-restore-test.sh"
|
||||
Executable
+247
@@ -0,0 +1,247 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
# Immich Restore Smoke Test
|
||||
#
|
||||
# Nicht-destruktiver Restore-Smoke-Test fuer Immich.
|
||||
# - liest immich.dump aus dem produktiven Borg-Archiv
|
||||
# - importiert in eine isolierte Test-Postgres-Instanz mit gleichem
|
||||
# VectorChord-Image wie Produktion
|
||||
# - startet einen isolierten Immich-Server-Container ohne Traefik und
|
||||
# ohne ML-Container
|
||||
# - prueft Login-Page und Asset-Anzahl aus DB
|
||||
# - bereinigt anschliessend
|
||||
#
|
||||
# Produktiver Immich-Stack wird NICHT angefasst.
|
||||
# Produktive Foto-Pfade unter /mnt/user/photos/* werden NICHT gemountet.
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
. "$SCRIPT_DIR/common.sh"
|
||||
|
||||
WHATIF=0
|
||||
KEEP_DATA=0
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--what-if) WHATIF=1 ;;
|
||||
--keep-data) KEEP_DATA=1 ;;
|
||||
*) echo "Unknown argument: $arg" >&2; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
RESTORE_ROOT="/mnt/user/backups/restore-lab/immich"
|
||||
REPORT_ROOT="/mnt/user/backups/restore-reports"
|
||||
EXTRACT_DIR="$BORG_RESTORE_HOST_ROOT/immich-extract"
|
||||
COMPOSE_FILE="$SCRIPT_DIR/immich-compose.test.yml"
|
||||
REPORT_FILE="$REPORT_ROOT/immich-$(date +%F).md"
|
||||
|
||||
if [ "$WHATIF" -eq 1 ]; then
|
||||
cat <<EOF
|
||||
Immich restore test
|
||||
Mode: WhatIf
|
||||
RestoreRoot: $RESTORE_ROOT
|
||||
ReportRoot: $REPORT_ROOT
|
||||
Expected Borg source paths:
|
||||
- local/borg-dumps/latest/immich.dump
|
||||
Planned isolation:
|
||||
- Test-Postgres: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0
|
||||
- Test-Redis: redis:8.8.0-alpine (rebuildbar, kein Restore)
|
||||
- Test-Server: ghcr.io/immich-app/immich-server:release (Image-Pin wie Produktion)
|
||||
- ML-Container bewusst weggelassen
|
||||
- Test-Upload: leer, unter $RESTORE_ROOT/upload
|
||||
- Productive photo paths NOT mounted: /mnt/user/photos/immich, /mnt/user/photos/family_archive
|
||||
- Test endpoint: 127.0.0.1:12283 (no Traefik, no public domain)
|
||||
Smoke-Test:
|
||||
- Test-Postgres healthy
|
||||
- pg_restore -Fc -> immich.dump
|
||||
- HTTP 200/302/3xx von 127.0.0.1:12283
|
||||
- Asset-Count aus DB
|
||||
EOF
|
||||
exit 0
|
||||
fi
|
||||
|
||||
require_cmd docker
|
||||
require_cmd curl
|
||||
require_path "$BORG_PASSPHRASE_FILE_DEFAULT"
|
||||
require_path "$COMPOSE_FILE"
|
||||
|
||||
cleanup() {
|
||||
cleanup_compose "$COMPOSE_FILE"
|
||||
if [ "$KEEP_DATA" -ne 1 ]; then
|
||||
rm -rf "$RESTORE_ROOT"
|
||||
fi
|
||||
rm -rf "$EXTRACT_DIR"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
rm -rf "$EXTRACT_DIR" "$RESTORE_ROOT"
|
||||
mkdir -p "$RESTORE_ROOT/postgres" "$RESTORE_ROOT/upload" "$RESTORE_ROOT/dumps/latest"
|
||||
|
||||
archive="$(latest_archive_name)"
|
||||
repo="$(borg_repo_url)"
|
||||
|
||||
if [ -z "$archive" ] || [ -z "$repo" ]; then
|
||||
echo "Could not resolve Borg repo/archive from borg-ui database" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
borg_extract "/restore/immich-extract" \
|
||||
"local/borg-dumps/latest/immich.dump"
|
||||
|
||||
mv "$EXTRACT_DIR/local/borg-dumps/latest/immich.dump" "$RESTORE_ROOT/dumps/latest/immich.dump"
|
||||
|
||||
# Stufe 1: Test-Postgres und Test-Redis starten
|
||||
docker compose -f "$COMPOSE_FILE" up -d \
|
||||
restoretest-immich-postgres restoretest-immich-redis >/dev/null
|
||||
|
||||
# Warten auf Postgres ready
|
||||
until docker exec restoretest-immich-postgres pg_isready -U immich -d immich >/dev/null 2>&1; do
|
||||
sleep 2
|
||||
done
|
||||
|
||||
# Einige Postgres-Images melden bereits "ready", waehrend die per ENV
|
||||
# gewuenschte Datenbank noch im Entrypoint entsteht. Der Smoke-Test legt
|
||||
# die isolierte Test-DB deshalb defensiv an und akzeptiert nur das Rennen,
|
||||
# in dem die DB parallel bereits erzeugt wurde.
|
||||
db_ok=0
|
||||
for attempt in $(seq 1 12); do
|
||||
if docker exec restoretest-immich-postgres sh -lc \
|
||||
'createdb -U immich immich 2>/tmp/immich-createdb.err || grep -q "already exists" /tmp/immich-createdb.err'; then
|
||||
db_ok=1
|
||||
break
|
||||
fi
|
||||
sleep 5
|
||||
done
|
||||
|
||||
if [ "$db_ok" -ne 1 ]; then
|
||||
docker exec restoretest-immich-postgres sh -lc 'cat /tmp/immich-createdb.err >&2' || true
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Stufe 2: Dump in Test-Postgres importieren.
|
||||
# Der Postgres-Entrypoint kann kurz nach "ready" noch vom Init-Server auf
|
||||
# den finalen Server wechseln; pg_restore toleriert deshalb nur transiente
|
||||
# Start-/Shutdown-Fehler und versucht danach erneut.
|
||||
restore_ok=0
|
||||
for attempt in $(seq 1 12); do
|
||||
if docker exec -i restoretest-immich-postgres \
|
||||
pg_restore -U immich -d immich --clean --if-exists --no-owner --no-privileges \
|
||||
< "$RESTORE_ROOT/dumps/latest/immich.dump" 2>/tmp/immich-pg-restore.err; then
|
||||
restore_ok=1
|
||||
break
|
||||
fi
|
||||
|
||||
if grep -qiE "starting up|shutting down|connection refused|database .* does not exist" /tmp/immich-pg-restore.err; then
|
||||
sleep 5
|
||||
continue
|
||||
fi
|
||||
|
||||
cat /tmp/immich-pg-restore.err >&2
|
||||
exit 1
|
||||
done
|
||||
|
||||
if [ "$restore_ok" -ne 1 ]; then
|
||||
cat /tmp/immich-pg-restore.err >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Immich prueft seit v2 Systemordner-Marker unter UPLOAD_LOCATION.
|
||||
# Da der Smoke-Test bewusst keine produktiven Foto-Pfade mountet, erzeugen
|
||||
# wir eine leere Test-Struktur mit den erwarteten Markern.
|
||||
for dir in thumbs upload backups library profile encoded-video; do
|
||||
mkdir -p "$RESTORE_ROOT/upload/$dir"
|
||||
touch "$RESTORE_ROOT/upload/$dir/.immich"
|
||||
done
|
||||
chmod -R a+rwX "$RESTORE_ROOT/upload"
|
||||
|
||||
# Stufe 3: Immich-Server starten (ohne ML)
|
||||
docker compose -f "$COMPOSE_FILE" up -d restoretest-immich-server >/dev/null
|
||||
|
||||
# Immich-Server braucht beim ersten Start einige Sekunden fuer DB-Migrations-Checks.
|
||||
# Wir geben ihm bis zu 120s und pollen den HTTP-Endpunkt.
|
||||
http_status=""
|
||||
for _ in $(seq 1 60); do
|
||||
http_status="$(curl -s -o /tmp/immich-body.html -w '%{http_code}' -L http://127.0.0.1:12283 || true)"
|
||||
if [ "$http_status" = "200" ] || [ "$http_status" = "302" ] || [ "$http_status" = "303" ]; then
|
||||
break
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
|
||||
# Body-Check: Immich-UI hat typische Marker. Wir matchen tolerant.
|
||||
body_check="ok"
|
||||
if ! grep -qiE "immich|login|signin" /tmp/immich-body.html 2>/dev/null; then
|
||||
body_check="missing-marker"
|
||||
fi
|
||||
|
||||
if [ "$http_status" != "200" ] && [ "$http_status" != "302" ] && [ "$http_status" != "303" ]; then
|
||||
echo "Immich HTTP smoke failed: status=$http_status" >&2
|
||||
docker ps -a --filter name=restoretest-immich >&2 || true
|
||||
docker logs --tail 120 restoretest-immich-server >&2 || true
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$body_check" != "ok" ]; then
|
||||
echo "Immich HTTP smoke failed: body marker=$body_check" >&2
|
||||
docker logs --tail 120 restoretest-immich-server >&2 || true
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Asset-Count aus DB. Immich v2 nutzt Singular-Tabellen (`asset`,
|
||||
# `"user"`); ältere Schema-Staende werden tolerant als Fallback versucht.
|
||||
query_count() {
|
||||
local sql="$1"
|
||||
docker exec restoretest-immich-postgres \
|
||||
psql -U immich -d immich -tAc "$sql" 2>/dev/null \
|
||||
| tr -d '[:space:]' || true
|
||||
}
|
||||
|
||||
asset_count="$(query_count 'select count(*) from asset;')"
|
||||
if [ -z "$asset_count" ]; then
|
||||
asset_count="$(query_count 'select count(*) from assets;')"
|
||||
fi
|
||||
if [ -z "$asset_count" ]; then
|
||||
asset_count="n/a"
|
||||
fi
|
||||
|
||||
# User-Count als zusaetzlicher DB-Sanity-Check
|
||||
user_count="$(query_count 'select count(*) from "user";')"
|
||||
if [ -z "$user_count" ]; then
|
||||
user_count="$(query_count 'select count(*) from users;')"
|
||||
fi
|
||||
if [ -z "$user_count" ]; then
|
||||
user_count="n/a"
|
||||
fi
|
||||
|
||||
write_report "$REPORT_FILE" <<EOF
|
||||
# Immich Restore Test Report - $(date +%F)
|
||||
|
||||
- Service: \`immich\`
|
||||
- Source repo: \`$repo\`
|
||||
- Archive: \`$archive\`
|
||||
- Restore root: \`$RESTORE_ROOT\`
|
||||
- Test containers:
|
||||
- \`restoretest-immich-server\`
|
||||
- \`restoretest-immich-postgres\` (ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0)
|
||||
- \`restoretest-immich-redis\`
|
||||
- Test endpoint: \`http://127.0.0.1:12283\`
|
||||
- ML container: deliberately omitted
|
||||
- Result: \`SUCCESS\`
|
||||
|
||||
## Checks
|
||||
|
||||
- Borg extract of \`immich.dump\`: \`ok\`
|
||||
- Dump import into isolated Postgres: \`ok\`
|
||||
- HTTP status after redirect: \`$http_status\`
|
||||
- Login page marker: \`$body_check\`
|
||||
- Asset count in test DB: \`$asset_count\`
|
||||
- User count in test DB: \`$user_count\`
|
||||
|
||||
## Notes
|
||||
|
||||
- Test ran without Traefik and without the productive domain.
|
||||
- Productive photo paths under /mnt/user/photos/* were NOT mounted.
|
||||
- Test data was cleaned after success: \`$([ "$KEEP_DATA" -eq 1 ] && echo no || echo yes)\`
|
||||
- Restore-Quelle Dump: \`local/borg-dumps/latest/immich.dump\` aus aktuellem Borg-Archiv.
|
||||
EOF
|
||||
|
||||
echo "Immich restore test ok -> $REPORT_FILE"
|
||||
@@ -0,0 +1,128 @@
|
||||
# Immich Restore Runbook
|
||||
|
||||
## Status
|
||||
|
||||
Skript und Test-Compose sind vorbereitet. **Erstlauf 2026-05-27 erfolgreich** (`SUCCESS`, HTTP `200`, `11977` Assets im Test-DB-Check). Report: `/mnt/user/backups/restore-reports/immich-2026-05-27.md`. Folgelaeufe je Quartal gemaess `docs/RESTORE_DRILL_ROUTINE.md` (Q2 = Immich).
|
||||
|
||||
Vor dem ersten Lauf muss Operator entscheiden:
|
||||
|
||||
- ist genug freier Platz unter `/mnt/user/backups/restore-lab/immich` vorhanden (Dump + Test-Postgres-Datadir + Upload-Dummy)?
|
||||
- ist genug freier RAM/CPU verfuegbar, um Immich-Server + Test-Postgres parallel zur produktiven Last laufen zu lassen?
|
||||
- soll der Lauf zuerst mit `--what-if` ausgefuehrt werden, dann mit `--keep-data` zur Zeitmessung?
|
||||
|
||||
## Vorbedingungen
|
||||
|
||||
- Borg-Quelle ist verfuegbar
|
||||
- Borg-UI laeuft (`docker ps | grep borg-ui`)
|
||||
- Borg-Passphrase-Datei vorhanden: `/mnt/user/appdata/secrets/borg_repo_passphrase.txt`
|
||||
- aktueller Dump `immich.dump` ist Teil des letzten Borg-Archivs (siehe `pre-backup-dumps.sh`)
|
||||
- Testpfade unter `/mnt/user/backups/restore-lab/` und `/mnt/user/backups/restore-reports/` sind freigegeben
|
||||
- produktiver Immich-Stack laeuft (oder ist bewusst aus); der Test ist davon unabhaengig
|
||||
|
||||
## Bestaetigter Host-Stand (Soll)
|
||||
|
||||
- produktiver Immich-Server: `immich_server` Container mit Image `ghcr.io/immich-app/immich-server:release@sha256:c15bff75068effb03f4355997d03dc7e0fc58720c2b54ad6f7f10d1bc57efaa5`
|
||||
- produktive Postgres: `immich_postgres` mit `ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0@sha256:bcf63357191b76a916ae5eb93464d65c07511da41e3bf7a8416db519b40b1c23`
|
||||
- produktive Foto-Pfade: `/mnt/user/photos/immich`, `/mnt/user/photos/family_archive`
|
||||
- aktueller Dump-Pfad: `/mnt/user/backups/borg/dumps/latest/immich.dump`
|
||||
- Secret: `/mnt/user/appdata/secrets/immich_postgres_password.txt` (wird vom Test **nicht** gebraucht; Test nutzt eigenes Test-Passwort)
|
||||
|
||||
## Bestaetigter Teststand
|
||||
|
||||
- noch kein echter Mini-Restore gelaufen
|
||||
- Skript-Set vorbereitet:
|
||||
- `ops/restore-tests/immich-compose.test.yml`
|
||||
- `ops/restore-tests/immich-restore-test.sh`
|
||||
- `ops/restore-tests/immich-restore-test.ps1` (Scaffold, kein Live-Run)
|
||||
- `ops/restore-tests/immich-plan.md`
|
||||
- `ops/restore-tests/immich-runbook.md`
|
||||
|
||||
## Erster Lauf - trockene Variante
|
||||
|
||||
```bash
|
||||
bash /mnt/user/services/homelab-infra/ops/restore-tests/immich-restore-test.sh --what-if
|
||||
```
|
||||
|
||||
Erwartete Ausgabe: nur Plan-Output, kein Docker-Start, kein Borg-Extract.
|
||||
|
||||
## Erster Lauf - echter Test (Operator-freigegeben)
|
||||
|
||||
```bash
|
||||
# Optional: laufende Borg-Jobs pruefen, damit Borg-UI nicht parallel ausgelastet ist
|
||||
docker exec borg-ui sqlite3 /data/borg.db \
|
||||
"select status, archive_name, datetime(updated_at,'unixepoch') from backup_jobs order by id desc limit 5;"
|
||||
|
||||
# Lauf mit Datenerhalt fuer Zeitmessung
|
||||
bash /mnt/user/services/homelab-infra/ops/restore-tests/immich-restore-test.sh --keep-data
|
||||
```
|
||||
|
||||
Bei erfolgreichem Lauf:
|
||||
|
||||
- Report unter `/mnt/user/backups/restore-reports/immich-YYYY-MM-DD.md`
|
||||
- Test-Container `restoretest-immich-*` sind nach Lauf bereits `down`
|
||||
- Restore-Lab-Daten bleiben mit `--keep-data` erhalten; ohne Flag werden sie geloescht
|
||||
|
||||
## Smoke-Test-Pruefungen
|
||||
|
||||
Minimal erwartet im Report:
|
||||
|
||||
- `HTTP status after redirect: 200|302|303`
|
||||
- `Login page marker: ok`
|
||||
- `Asset count in test DB`: Zahl, oder `n/a` bei Schema-Drift
|
||||
- `User count in test DB`: Zahl, oder `n/a` bei Schema-Drift
|
||||
- Pre-Dump-Hook-Kette (`pg_dump -Fc`) und Restore-Kette (`pg_restore -Fc`) sind kompatibel
|
||||
- VectorChord-/pgvector-Extensions sind in der Restore-DB sichtbar
|
||||
|
||||
Manuelle Folgepruefung (optional):
|
||||
|
||||
Das Skript stoppt die Test-Container auch bei `--keep-data`; dieses Flag erhaelt nur die Restore-Lab-Daten. Fuer eine manuelle Folgepruefung nach einem erfolgreichen `--keep-data`-Lauf die Testinstanz kurz wieder hochfahren und danach wieder stoppen:
|
||||
|
||||
```bash
|
||||
docker compose -f /mnt/user/services/homelab-infra/ops/restore-tests/immich-compose.test.yml up -d \
|
||||
restoretest-immich-postgres restoretest-immich-redis restoretest-immich-server
|
||||
|
||||
docker exec restoretest-immich-postgres psql -U immich -d immich -c "\dx"
|
||||
docker exec restoretest-immich-postgres psql -U immich -d immich -tAc "select count(*) from asset;"
|
||||
docker logs --tail 100 restoretest-immich-server
|
||||
|
||||
docker compose -f /mnt/user/services/homelab-infra/ops/restore-tests/immich-compose.test.yml down
|
||||
```
|
||||
|
||||
## Cleanup nach Lauf ohne `--keep-data`
|
||||
|
||||
Das Skript bereinigt:
|
||||
|
||||
- Test-Container via `docker compose down`
|
||||
- Restore-Lab unter `/mnt/user/backups/restore-lab/immich`
|
||||
- Extract-Cache unter `/mnt/user/appdata/borg-ui/restore/immich-extract`
|
||||
|
||||
**Vorsicht:** `rm -rf` arbeitet ausschliesslich auf dem festen Restore-Lab-Pfad. Produktive Immich-Pfade unter `/mnt/user/photos/*` werden vom Skript niemals beschrieben.
|
||||
|
||||
## Fehlerfaelle
|
||||
|
||||
| Symptom | Ursache | Massnahme |
|
||||
|---|---|---|
|
||||
| `pg_restore: error: could not find extension ... vector/vchord` | Test-Postgres-Image passt nicht zur Produktion | Compose-Pin im Test-Compose pruefen |
|
||||
| HTTP-Timeout nach 120 s | Immich-Migrations laufen noch | Wartezeit im Skript erhoehen oder Logs pruefen |
|
||||
| `pg_isready` nie healthy | Test-Postgres bricht beim Start ab (Datadir-Konflikt) | Restore-Lab vor Lauf vollstaendig leer; `docker logs restoretest-immich-postgres` |
|
||||
| Body matcht keine Marker | Immich UI hat sich versioniert; Marker-Liste anpassen | Marker im Skript erweitern (`grep -qiE`) |
|
||||
| Disk-Space-Mangel | Dump + Postgres-Datadir + Extract-Cache | mehr Platz freigeben oder Lauf abbrechen |
|
||||
|
||||
## Schedule-Eintrag (geplant, noch nicht aktiv)
|
||||
|
||||
Aktuell in `ops/restore-tests/schedule.md` nur als "spaeter, eigener Sprint" gelistet.
|
||||
|
||||
Nach erstem erfolgreichen Lauf vorschlagen:
|
||||
|
||||
- quartalsweise (`0 9 1 1,4,7,10 *` o. ae.)
|
||||
- ohne `--keep-data`
|
||||
- Report wird automatisch von `monthly-random-restore.sh` mit eingelesen, sobald Immich dort eintragbar ist
|
||||
|
||||
## Festgelegte Entscheidungen
|
||||
|
||||
- Immich-Restore-Test nutzt isoliertes Test-Postgres mit gleichem Image wie Produktion.
|
||||
- ML-Container wird im Smoke-Test **nicht** mitgestartet.
|
||||
- Produktive Foto-Pfade werden **nicht** in den Test gemountet.
|
||||
- Test-Daten werden nach erfolgreichem Lauf geloescht (`--keep-data` ueberschreibt das).
|
||||
- Borg-Passphrase wird aus Host-Secret-Datei gelesen und nirgendwo geloggt.
|
||||
- `ntfy` wird im ersten echten Lauf nicht eingebunden.
|
||||
@@ -0,0 +1,77 @@
|
||||
services:
|
||||
# Wegwerf-Mongo fuer Komodo-Bootstrap-Trockenlauf.
|
||||
# Schreibt in den Restore-Lab-Pfad, NICHT in das produktive
|
||||
# /mnt/user/appdata/komodo/mongo-Volume.
|
||||
restoretest-komodo-mongo:
|
||||
image: mongo:7.0.32@sha256:32979a1189dfdc44da3f5ed40d910495f5ad8f6f7f77556646f890a30b2d3f56
|
||||
container_name: restoretest-komodo-mongo
|
||||
restart: "no"
|
||||
command: --quiet
|
||||
environment:
|
||||
MONGO_INITDB_ROOT_USERNAME: komodo
|
||||
MONGO_INITDB_ROOT_PASSWORD: restoretest-komodo-mongo-pwd
|
||||
volumes:
|
||||
- /mnt/user/backups/restore-lab/komodo/mongo:/data/db
|
||||
healthcheck:
|
||||
test: ["CMD", "mongosh", "--quiet", "--eval", "db.adminCommand('ping').ok"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
start_period: 30s
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
|
||||
restoretest-komodo-core:
|
||||
# Selbes Image wie Produktion, damit Compose-Diff Bootstrap-Kompatibilitaet
|
||||
# nachweist.
|
||||
image: ghcr.io/moghtech/komodo-core:2@sha256:8a7dbba232e4e49797bb412be5f78207c89fcf22cc2727b38631ae30f7518a4c
|
||||
container_name: restoretest-komodo-core
|
||||
init: true
|
||||
restart: "no"
|
||||
depends_on:
|
||||
restoretest-komodo-mongo:
|
||||
condition: service_healthy
|
||||
volumes:
|
||||
- /mnt/user/backups/restore-lab/komodo/core:/repo-cache
|
||||
- /mnt/user/backups/restore-lab/komodo/keys:/config/keys
|
||||
environment:
|
||||
TZ: Europe/Berlin
|
||||
KOMODO_HOST: http://127.0.0.1:19120
|
||||
KOMODO_TITLE: Restore-Test
|
||||
# Wegwerf-Secrets, ausschliesslich fuer den lokalen Trockenlauf.
|
||||
# Niemals produktive Komodo-Secrets in dieses Compose schreiben.
|
||||
KOMODO_SECRET_KEY: restoretest-secret-key-placeholder-32
|
||||
KOMODO_WEBHOOK_SECRET: restoretest-webhook-secret
|
||||
KOMODO_PASSKEY: restoretest-periphery-passkey
|
||||
KOMODO_DATABASE_ADDRESS: restoretest-komodo-mongo:27017
|
||||
KOMODO_DATABASE_USERNAME: komodo
|
||||
KOMODO_DATABASE_PASSWORD: restoretest-komodo-mongo-pwd
|
||||
KOMODO_LOG_LEVEL: info
|
||||
KOMODO_LOCAL_AUTH: "true"
|
||||
KOMODO_JWT_SECRET: restoretest-jwt-secret-placeholder
|
||||
KOMODO_DISABLE_WEBSOCKETS: "true"
|
||||
ports:
|
||||
- "127.0.0.1:19120:9120"
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
|
||||
restoretest-komodo-periphery:
|
||||
image: ghcr.io/moghtech/komodo-periphery:2@sha256:8ac9f2ef9c1461b95c862d445da00253005e7094d1e30f5b7b04b8d60ca7a3d6
|
||||
container_name: restoretest-komodo-periphery
|
||||
init: true
|
||||
restart: "no"
|
||||
depends_on:
|
||||
restoretest-komodo-core:
|
||||
condition: service_started
|
||||
volumes:
|
||||
- /mnt/user/backups/restore-lab/komodo/keys:/config/keys
|
||||
# bewusst KEIN docker.sock-Mount: dieser Test-Periphery darf nicht
|
||||
# versehentlich produktive Container managen.
|
||||
- /mnt/user/backups/restore-lab/komodo/periphery:/etc/komodo
|
||||
environment:
|
||||
PERIPHERY_ROOT_DIRECTORY: /tmp/restoretest-periphery
|
||||
PERIPHERY_PASSKEYS: restoretest-periphery-passkey
|
||||
PERIPHERY_SSL_ENABLED: "false"
|
||||
TZ: Europe/Berlin
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
@@ -0,0 +1,88 @@
|
||||
# Komodo Bootstrap Trockenlauf - Plan
|
||||
|
||||
## Ziel
|
||||
|
||||
Nachweisen, dass `ops/komodo/docker-compose.yml` als Recovery-Anker fuer einen Komodo-Kaltstart tauglich ist, ohne den produktiven Komodo-Stack anzufassen.
|
||||
|
||||
Bewusst **nicht** Teil dieses Tests:
|
||||
|
||||
- Restore aus dem produktiven `komodo-mongo.archive.gz`-Dump (eigene Folgeaufgabe; dieser Test prueft nur das Compose-Bootstrap, nicht den Daten-Restore).
|
||||
- docker.sock-Mount fuer die Test-Periphery (die Test-Periphery darf nie produktive Container managen).
|
||||
- Traefik-Route oder Authelia-Anbindung (Test laeuft ausschliesslich auf `127.0.0.1:19120`).
|
||||
|
||||
## Quelle
|
||||
|
||||
- Bootstrap-Anker: `ops/komodo/docker-compose.yml` (Soll-Stand laut `docs/SERVICES_RECOVERY.md` Stufe A-F).
|
||||
- Image-Digests: identisch zur Produktion fuer komodo-core und komodo-periphery; Mongo-Image identisch.
|
||||
|
||||
## Test-Ziel
|
||||
|
||||
- Restore-Lab: `/mnt/user/backups/restore-lab/komodo`
|
||||
- Wegwerf-Pfade:
|
||||
- `/mnt/user/backups/restore-lab/komodo/mongo` (Test-Mongo-Datadir)
|
||||
- `/mnt/user/backups/restore-lab/komodo/core` (Repo-Cache)
|
||||
- `/mnt/user/backups/restore-lab/komodo/keys` (gemeinsamer Keys-Pfad fuer Core+Periphery)
|
||||
- `/mnt/user/backups/restore-lab/komodo/periphery` (Periphery-Etc)
|
||||
- Testcontainer:
|
||||
- `restoretest-komodo-mongo`
|
||||
- `restoretest-komodo-core` (Test-Port `127.0.0.1:19120`)
|
||||
- `restoretest-komodo-periphery` (ohne docker.sock)
|
||||
- Compose-Project: `restoretest-komodo` (isoliert von Produktions-Project `komodo`)
|
||||
- Report-Ziel: `/mnt/user/backups/restore-reports/komodo-bootstrap-YYYY-MM-DD.md`
|
||||
|
||||
## Schutzregeln
|
||||
|
||||
- produktive Datadirs `/mnt/user/appdata/komodo/{mongo,core,periphery}` werden **nicht** gemountet
|
||||
- produktive Container `komodo-mongo`, `komodo-core`, `komodo-periphery` werden **nicht** gestoppt
|
||||
- produktive `KOMODO_*`-Secrets werden **nicht** verwendet
|
||||
- Test-Compose enthaelt nur Wegwerf-Werte fuer `KOMODO_SECRET_KEY`, `KOMODO_WEBHOOK_SECRET`, `KOMODO_JWT_SECRET`, `KOMODO_PASSKEY` und Mongo-Root-Password
|
||||
- Test-Periphery laeuft ohne docker.sock-Mount und ohne `/mnt/user/services`-Mount
|
||||
- Test-Port nur auf `127.0.0.1:19120`, keine LAN-/Tailscale-Bindung
|
||||
|
||||
## Geplanter Ablauf
|
||||
|
||||
1. Restore-Lab-Pfade leer anlegen
|
||||
2. `docker compose config` auf dem Test-Compose validieren
|
||||
3. Mongo und Core hochfahren, auf Mongo-`healthy` warten
|
||||
4. HTTP-Smoke gegen `http://127.0.0.1:19120` (Login-Seite oder Auth-Redirect erwartet)
|
||||
5. Periphery dazustarten, kurz beobachten
|
||||
6. Mongo-`authenticated ping` mit Test-Credentials
|
||||
7. Report schreiben
|
||||
8. Cleanup `docker compose down -v` und Restore-Lab loeschen (ausser `--keep-data`)
|
||||
|
||||
## Smoke-Test
|
||||
|
||||
Minimal erfolgreich:
|
||||
|
||||
- `docker compose config` valid
|
||||
- Test-Mongo erreicht `healthy`
|
||||
- Mongo-Authentifizierung mit Test-Creds funktioniert (`db.adminCommand({ping:1}).ok = 1`)
|
||||
- Komodo-Core HTTP `200`, `302`, `303` oder `401` (alles ist ein valider Lebenszeichen)
|
||||
- Test-Periphery container state `running`
|
||||
|
||||
Optional spaeter:
|
||||
|
||||
- Periphery-Verbindung gegen Test-Core verifizieren (braucht Periphery-Konfig mit `core_url`)
|
||||
- Echtes Restore aus `komodo-mongo.archive.gz`-Dump in die Test-Mongo
|
||||
- Schreiben einer Wegwerf-Resource (Server/Stack) ueber die API
|
||||
|
||||
## Bekannte Komplikationen
|
||||
|
||||
| Risiko | Beschreibung | Mitigation |
|
||||
|---|---|---|
|
||||
| Image-Drift | Komodo-Images aktualisieren ihre Major-Tag-Digests | Compose pinnt denselben Digest wie Produktion; bei Image-Update auch Test-Compose nachziehen |
|
||||
| Port-Konflikt | wenn 19120 anderweitig belegt ist | nur `127.0.0.1`-Bind; bei Konflikt Port im Compose anpassen |
|
||||
| Volume-Reste | unterbrochener Lauf laesst Wegwerf-Datadir liegen | Skript loescht Restore-Lab vor jedem Lauf; `--keep-data` ueberschreibt das bewusst |
|
||||
| Periphery-Erreichbarkeit | Core sucht Periphery initial nicht aktiv | Test prueft nur Periphery `State.Status=running`; voller Handshake ist optional |
|
||||
|
||||
## Bestaetigte Laeufe
|
||||
|
||||
| Datum | Mode | Ergebnis | Report |
|
||||
|---|---|---|---|
|
||||
| 2026-05-30 | `--what-if` | Plan-Ausgabe wie erwartet | (kein Report, nur stdout) |
|
||||
| 2026-05-30 | `--keep-data` | `SUCCESS`, 5/5 Checks gruen, Core HTTP `200`, Mongo healthy in ~6 s | `/mnt/user/backups/restore-reports/komodo-bootstrap-2026-05-30.md` |
|
||||
|
||||
## Folgeschritte
|
||||
|
||||
- Quartals-Belegung: Komodo-Bootstrap passt zum DR-Sanity-Check (`docs/RESTORE_DRILL_ROUTINE.md` Q2/Q4) und kann ohne Borg-Archiv jederzeit wiederholt werden.
|
||||
- Optional fuer kuenftige Laeufe: echtes Restore aus `komodo-mongo.archive.gz` in die Test-Mongo, danach Schreiben einer Wegwerf-Resource ueber die API.
|
||||
@@ -0,0 +1,95 @@
|
||||
# Komodo Bootstrap Trockenlauf - Runbook
|
||||
|
||||
## Status
|
||||
|
||||
Skript und Test-Compose sind vorbereitet. **Erstlauf 2026-05-30 erfolgreich** (`SUCCESS`, alle 5 Checks gruen, Komodo Core HTTP `200`). Report: `/mnt/user/backups/restore-reports/komodo-bootstrap-2026-05-30.md`. Folgelaeufe quartalsweise empfohlen als Teil des DR-Sanity-Checks (`docs/RESTORE_DRILL_ROUTINE.md`).
|
||||
|
||||
## Vorbedingungen
|
||||
|
||||
- Docker auf dem Unraid-Host
|
||||
- `borg-ui`-Container muss **nicht** laufen (im Gegensatz zum Immich-Test braucht der Komodo-Bootstrap kein Borg-Archiv)
|
||||
- freier Speicher unter `/mnt/user/backups/restore-lab/komodo` (~500 MB reichen)
|
||||
- Port `127.0.0.1:19120` ist frei
|
||||
|
||||
## Bestaetigter Host-Stand (Soll)
|
||||
|
||||
- produktiver Komodo-Stack: `komodo-mongo`, `komodo-core`, `komodo-periphery` unter Project `komodo`
|
||||
- produktive Mongo-Datadir: `/mnt/user/appdata/komodo/mongo`
|
||||
- produktive Secrets: `KOMODO_*` Stack-ENV-only (Restore-Reihenfolge siehe `docs/SECRETS_MAP.md`)
|
||||
- Test isoliert das alles unter Project `restoretest-komodo` mit Restore-Lab-Datadir
|
||||
|
||||
## Erster Lauf - trockene Variante
|
||||
|
||||
```bash
|
||||
bash /mnt/user/services/homelab-infra/ops/restore-tests/komodo-bootstrap-test.sh --what-if
|
||||
```
|
||||
|
||||
Erwartete Ausgabe: nur Plan-Output, kein Docker-Start, kein Verzeichnis angelegt.
|
||||
|
||||
## Erster Lauf - echter Test
|
||||
|
||||
```bash
|
||||
# optional: produktiven Komodo-Stack-Status pruefen, damit nichts kollidiert
|
||||
docker ps --filter name=komodo --format "{{.Names}}\t{{.Status}}"
|
||||
|
||||
# Lauf mit Datenerhalt
|
||||
bash /mnt/user/services/homelab-infra/ops/restore-tests/komodo-bootstrap-test.sh --keep-data
|
||||
```
|
||||
|
||||
Bei Erfolg:
|
||||
|
||||
- Report unter `/mnt/user/backups/restore-reports/komodo-bootstrap-YYYY-MM-DD.md`
|
||||
- Test-Container `restoretest-komodo-*` werden nach Lauf gestoppt und entfernt (auch bei `--keep-data`)
|
||||
- Restore-Lab-Daten bleiben mit `--keep-data` erhalten
|
||||
|
||||
## Smoke-Test-Pruefungen
|
||||
|
||||
Minimal erwartet im Report:
|
||||
|
||||
- `docker compose config valid: ok`
|
||||
- `Test-Mongo healthy: ok`
|
||||
- `Mongo authenticated ping (Test-Creds): ok`
|
||||
- `Komodo Core HTTP status: 200|302|303|401`
|
||||
- `Test-Periphery container state: running`
|
||||
|
||||
Manuelle Folgepruefung (optional):
|
||||
|
||||
```bash
|
||||
docker compose -f /mnt/user/services/homelab-infra/ops/restore-tests/komodo-bootstrap-compose.test.yml \
|
||||
-p restoretest-komodo up -d
|
||||
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:19120
|
||||
docker exec restoretest-komodo-mongo mongosh --quiet -u komodo \
|
||||
-p restoretest-komodo-mongo-pwd --authenticationDatabase admin --eval 'db.adminCommand({ping:1})'
|
||||
docker compose -f /mnt/user/services/homelab-infra/ops/restore-tests/komodo-bootstrap-compose.test.yml \
|
||||
-p restoretest-komodo down -v
|
||||
```
|
||||
|
||||
## Cleanup ohne `--keep-data`
|
||||
|
||||
Skript bereinigt:
|
||||
|
||||
- Test-Container und Test-Volumes ueber `docker compose down -v`
|
||||
- Restore-Lab unter `/mnt/user/backups/restore-lab/komodo`
|
||||
|
||||
Produktive Komodo-Container, Mongo-Datadir und `KOMODO_*`-Secrets werden niemals beruehrt.
|
||||
|
||||
## Fehlerfaelle
|
||||
|
||||
| Symptom | Ursache | Massnahme |
|
||||
|---|---|---|
|
||||
| `Test-Mongo never reported healthy` | mongo-image konnte nicht starten | `docker logs restoretest-komodo-mongo` pruefen; Restore-Lab-Pfad leer? |
|
||||
| HTTP-Timeout nach 120 s | Komodo-Core haengt in Mongo-Connect | `docker logs restoretest-komodo-core` pruefen; Mongo-Auth-Test wiederholen |
|
||||
| `bind: address already in use` | Port 19120 belegt | Compose-Port-Mapping anpassen oder konfligierenden Prozess identifizieren |
|
||||
| Periphery `restarting` | Periphery braucht zusaetzliche ENVs | Logs lesen; Periphery-Verbindung ist optional fuer den Smoke-Test |
|
||||
|
||||
## Schedule
|
||||
|
||||
Aktuell nicht im automatischen Schedule. Empfohlen als Teil des quartalsweisen DR-Sanity-Check (`docs/RESTORE_DRILL_ROUTINE.md`).
|
||||
|
||||
## Festgelegte Entscheidungen
|
||||
|
||||
- Test-Compose nutzt dieselben Image-Digests wie Produktion.
|
||||
- Test-Periphery laeuft bewusst ohne docker.sock-Mount.
|
||||
- Test-Secrets sind Wegwerf-Werte im Compose; niemals produktive Werte einsetzen.
|
||||
- Test-Port nur auf `127.0.0.1`, keine LAN-Bindung.
|
||||
- `restoretest-komodo` als Compose-Project-Name reserviert; Test-Container heissen `restoretest-komodo-*`.
|
||||
@@ -0,0 +1,135 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
# Komodo Bootstrap Trockenlauf
|
||||
#
|
||||
# Verifiziert, dass `ops/komodo/docker-compose.yml` als Recovery-Anker
|
||||
# tauglich ist: Wegwerf-Mongo, Wegwerf-Core, Wegwerf-Periphery werden
|
||||
# isoliert hochgefahren und auf Bootstrap-Faehigkeit geprueft.
|
||||
#
|
||||
# Produktive Komodo-Container, produktive Mongo-Datadir und produktive
|
||||
# Komodo-Secrets werden NICHT angefasst.
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
. "$SCRIPT_DIR/common.sh"
|
||||
|
||||
WHATIF=0
|
||||
KEEP_DATA=0
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--what-if) WHATIF=1 ;;
|
||||
--keep-data) KEEP_DATA=1 ;;
|
||||
*) echo "Unknown argument: $arg" >&2; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
RESTORE_ROOT="/mnt/user/backups/restore-lab/komodo"
|
||||
REPORT_ROOT="/mnt/user/backups/restore-reports"
|
||||
COMPOSE_FILE="$SCRIPT_DIR/komodo-bootstrap-compose.test.yml"
|
||||
PROJECT_NAME="restoretest-komodo"
|
||||
REPORT_FILE="$REPORT_ROOT/komodo-bootstrap-$(date +%F).md"
|
||||
|
||||
if [ "$WHATIF" -eq 1 ]; then
|
||||
cat <<EOF
|
||||
Komodo bootstrap trockenlauf
|
||||
Mode: WhatIf
|
||||
RestoreRoot: $RESTORE_ROOT
|
||||
ReportRoot: $REPORT_ROOT
|
||||
Planned isolation:
|
||||
- Test-Mongo: mongo:7.0.32 (gleicher Digest wie Produktion), Datadir $RESTORE_ROOT/mongo
|
||||
- Test-Core: ghcr.io/moghtech/komodo-core:2 (gleicher Digest wie Produktion), Port 127.0.0.1:19120
|
||||
- Test-Periphery: ghcr.io/moghtech/komodo-periphery:2, ohne docker.sock-Mount
|
||||
- KOMODO_*-Secrets: Wegwerf-Werte ausschliesslich fuer Trockenlauf
|
||||
- Compose-Project: $PROJECT_NAME (isoliert von "komodo")
|
||||
Smoke-Test:
|
||||
- compose config valid
|
||||
- Mongo healthy
|
||||
- Core HTTP 200/4xx auf 127.0.0.1:19120 (Login-Seite erwartet)
|
||||
- Periphery container running
|
||||
EOF
|
||||
exit 0
|
||||
fi
|
||||
|
||||
require_cmd docker
|
||||
require_path "$COMPOSE_FILE"
|
||||
|
||||
cleanup() {
|
||||
docker compose -f "$COMPOSE_FILE" -p "$PROJECT_NAME" down -v >/dev/null 2>&1 || true
|
||||
if [ "$KEEP_DATA" -ne 1 ]; then
|
||||
rm -rf "$RESTORE_ROOT"
|
||||
fi
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
rm -rf "$RESTORE_ROOT"
|
||||
mkdir -p "$RESTORE_ROOT/mongo" "$RESTORE_ROOT/core" "$RESTORE_ROOT/keys" "$RESTORE_ROOT/periphery"
|
||||
|
||||
# Stufe 1: Compose syntaktisch validieren
|
||||
docker compose -f "$COMPOSE_FILE" -p "$PROJECT_NAME" config >/dev/null
|
||||
|
||||
# Stufe 2: Mongo und Core hochfahren
|
||||
docker compose -f "$COMPOSE_FILE" -p "$PROJECT_NAME" up -d \
|
||||
restoretest-komodo-mongo restoretest-komodo-core >/dev/null
|
||||
|
||||
# Stufe 3: Warten auf Mongo healthy
|
||||
mongo_ok=0
|
||||
for _ in $(seq 1 30); do
|
||||
s="$(docker inspect restoretest-komodo-mongo --format '{{.State.Health.Status}}' 2>/dev/null || true)"
|
||||
if [ "$s" = "healthy" ]; then mongo_ok=1; break; fi
|
||||
sleep 2
|
||||
done
|
||||
if [ "$mongo_ok" -ne 1 ]; then
|
||||
echo "Test-Mongo never reported healthy" >&2
|
||||
docker logs --tail 80 restoretest-komodo-mongo >&2 || true
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Stufe 4: Warten bis Core HTTP antwortet
|
||||
http_status=""
|
||||
for _ in $(seq 1 60); do
|
||||
http_status="$(curl -s -o /tmp/komodo-bootstrap-body.html -w '%{http_code}' -L http://127.0.0.1:19120 || true)"
|
||||
if [ "$http_status" = "200" ] || [ "$http_status" = "302" ] || [ "$http_status" = "303" ] || [ "$http_status" = "401" ]; then
|
||||
break
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
|
||||
# Stufe 5: Periphery dazustarten und Health pruefen
|
||||
docker compose -f "$COMPOSE_FILE" -p "$PROJECT_NAME" up -d \
|
||||
restoretest-komodo-periphery >/dev/null
|
||||
sleep 8
|
||||
periphery_state="$(docker inspect restoretest-komodo-periphery --format '{{.State.Status}}' 2>/dev/null || echo missing)"
|
||||
|
||||
# Stufe 6: Mongo-Ping mit Test-Credentials als zusaetzlicher Sanity-Check
|
||||
mongo_ping="n/a"
|
||||
if docker exec restoretest-komodo-mongo mongosh --quiet -u komodo \
|
||||
-p restoretest-komodo-mongo-pwd --authenticationDatabase admin \
|
||||
--eval 'db.adminCommand({ping:1}).ok' 2>/dev/null | grep -q '^1$'; then
|
||||
mongo_ping="ok"
|
||||
fi
|
||||
|
||||
write_report "$REPORT_FILE" <<EOF
|
||||
# Komodo Bootstrap Trockenlauf - $(date +%F)
|
||||
|
||||
- Compose: \`ops/restore-tests/komodo-bootstrap-compose.test.yml\`
|
||||
- Project: \`$PROJECT_NAME\`
|
||||
- Restore root: \`$RESTORE_ROOT\`
|
||||
- Test endpoint: \`http://127.0.0.1:19120\`
|
||||
- Result: \`SUCCESS\`
|
||||
|
||||
## Checks
|
||||
|
||||
- docker compose config valid: \`ok\`
|
||||
- Test-Mongo healthy: \`ok\`
|
||||
- Mongo authenticated ping (Test-Creds): \`$mongo_ping\`
|
||||
- Komodo Core HTTP status: \`$http_status\`
|
||||
- Test-Periphery container state: \`$periphery_state\`
|
||||
|
||||
## Notes
|
||||
|
||||
- Produktive Komodo-Container, Mongo-Datadir und Secrets wurden nicht beruehrt.
|
||||
- Test-Periphery laeuft bewusst ohne docker.sock-Mount.
|
||||
- Test-Daten wurden \`$([ "$KEEP_DATA" -eq 1 ] && echo behalten || echo bereinigt)\`.
|
||||
EOF
|
||||
|
||||
echo "Komodo bootstrap trockenlauf ok -> $REPORT_FILE"
|
||||
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
restoretest-paperless-postgres:
|
||||
image: postgres:17.9@sha256:5b96f1a16bd9768b060dd2ffe55cb6225c4d9ef4d214a8b21eb08134869a97e4
|
||||
image: postgres:18.4@sha256:8ff36f3c66371cba71d20ceedccfc3de9669a68737607888c4ef0af93abe8e39
|
||||
container_name: restoretest-paperless-postgres
|
||||
restart: "no"
|
||||
environment:
|
||||
@@ -8,9 +8,9 @@ services:
|
||||
POSTGRES_USER: paperless
|
||||
POSTGRES_DB: paperless
|
||||
POSTGRES_PASSWORD: restoretest-paperless-db
|
||||
PGDATA: /var/lib/postgresql/data
|
||||
PGDATA: /var/lib/postgresql/18/docker
|
||||
volumes:
|
||||
- /mnt/user/backups/restore-lab/paperless/postgres:/var/lib/postgresql/data
|
||||
- /mnt/user/backups/restore-lab/paperless/postgres:/var/lib/postgresql
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U paperless -d paperless"]
|
||||
interval: 10s
|
||||
@@ -20,7 +20,7 @@ services:
|
||||
- no-new-privileges:true
|
||||
|
||||
restoretest-paperless-redis:
|
||||
image: redis:7-alpine
|
||||
image: redis:8.8.0-alpine@sha256:09160599abd229764c0fb44cb6be640294e1d360a54b19985ab4843dcf2d90f1
|
||||
container_name: restoretest-paperless-redis
|
||||
restart: "no"
|
||||
command:
|
||||
|
||||
Regular → Executable
@@ -1,5 +1,5 @@
|
||||
param(
|
||||
[ValidateSet("freshness","vaultwarden","gitea","paperless")]
|
||||
[ValidateSet("freshness","vaultwarden","gitea","paperless","immich")]
|
||||
[string]$Mode,
|
||||
[switch]$WhatIf
|
||||
)
|
||||
@@ -35,4 +35,12 @@ switch ($Mode) {
|
||||
}
|
||||
exit $LASTEXITCODE
|
||||
}
|
||||
"immich" {
|
||||
if ($WhatIf) {
|
||||
& (Join-Path $base "immich-restore-test.ps1") -WhatIf
|
||||
} else {
|
||||
& (Join-Path $base "immich-restore-test.ps1")
|
||||
}
|
||||
exit $LASTEXITCODE
|
||||
}
|
||||
}
|
||||
|
||||
Regular → Executable
+7
-1
@@ -28,8 +28,14 @@ case "$MODE" in
|
||||
fi
|
||||
exec "$SCRIPT_DIR/paperless-restore-test.sh"
|
||||
;;
|
||||
immich)
|
||||
if [ "$WHATIF" = "--what-if" ]; then
|
||||
exec "$SCRIPT_DIR/immich-restore-test.sh" --what-if
|
||||
fi
|
||||
exec "$SCRIPT_DIR/immich-restore-test.sh"
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 {freshness|vaultwarden|gitea|paperless} [--what-if]" >&2
|
||||
echo "Usage: $0 {freshness|vaultwarden|gitea|paperless|immich} [--what-if]" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
@@ -7,7 +7,7 @@ SUCCESS_TOPIC="${2:-${RESTORE_SUCCESS_TOPIC:-homelab-info}}"
|
||||
FAILURE_TOPIC="${RESTORE_FAILURE_TOPIC:-homelab-alerts}"
|
||||
|
||||
if [ -z "$MODE" ]; then
|
||||
echo "Usage: $0 <freshness|vaultwarden|gitea|paperless> [success_topic]" >&2
|
||||
echo "Usage: $0 <freshness|vaultwarden|gitea|paperless|immich> [success_topic]" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
@@ -26,16 +26,15 @@ Alle 2 Monate:
|
||||
|
||||
Quartalsweise:
|
||||
|
||||
- Restore-/DR-Sanity-Check
|
||||
- Restore-/DR-Sanity-Check gemaess `docs/RESTORE_DRILL_ROUTINE.md`
|
||||
- `immich` Restore-Smoke-Test (DB + UI, ohne produktive Foto-Mounts; Erstlauf 2026-05-27 erfolgreich)
|
||||
- pruefen:
|
||||
- Restore-Lab-Struktur
|
||||
- Reports
|
||||
- Skripte und Pfade
|
||||
- Doku noch passend
|
||||
|
||||
Spaeter:
|
||||
|
||||
- `immich` als eigener Sprint
|
||||
Die Quartals-Belegung (welcher Dienst, welcher Sanity-Fokus) steht in `docs/RESTORE_DRILL_ROUTINE.md` Tabelle "Quartals-Kadenz".
|
||||
|
||||
## Konkreter Kalender
|
||||
|
||||
@@ -51,6 +50,8 @@ Spaeter:
|
||||
- `monthly-random-restore.sh`
|
||||
- Quartalsweise am 1. Werktag des Quartals:
|
||||
- DR-/Restore-Sanity-Check
|
||||
- Quartalsweise am 2. Sonntag im zweiten Quartalsmonat, 08:30:
|
||||
- `immich`
|
||||
|
||||
## Unraid User Scripts Cron
|
||||
|
||||
@@ -60,6 +61,7 @@ Spaeter:
|
||||
| `restore-vaultwarden-monthly` | `0 7 1-7 * 6` | erster Samstag im Monat 07:00 |
|
||||
| `restore-gitea-monthly` | `15 7 15-21 * 6` | dritter Samstag im Monat 07:15 |
|
||||
| `restore-paperless-bimonthly` | `0 8 8-14 1,3,5,7,9,11 *` | zweiter Samstag in ungeraden Monaten 08:00 |
|
||||
| `restore-immich-quarterly` | `30 8 8-14 2,5,8,11 0` | zweiter Sonntag in Feb/Mai/Aug/Nov 08:30 |
|
||||
| `monthly-random-restore` | `0 9 1 * *` | erster Kalendertag im Monat 09:00 |
|
||||
|
||||
## Betriebsmodus
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user