From 8e400fb3c30c29ae17d898107288805c194f2970 Mon Sep 17 00:00:00 2001 From: Micha Date: Sat, 23 May 2026 11:29:08 +0200 Subject: [PATCH] Finalize homelab audit end state --- .gitignore | 1 + HOMELAB_ARCHITECTURE_MASTER_V2.md | 9 +- docs/AUDIT_2026-05-23.md | 369 ++++++++++++++++++ docs/AUDIT_2026-05-23_FINAL.md | 22 ++ docs/AUDIT_2026-05-23_LIVE.md | 88 +++++ docs/CODEX_ENDSTUFE_PROMPT_2026-05-23.md | 32 ++ docs/MIGRATION_LOG.md | 7 + docs/REPO_MAP.md | 11 +- docs/SERVICE_CATALOG.md | 4 +- ops/windows-reinstall/README.md | 9 + .../backup-delta-after-2026-05-07.ps1 | 118 ++++++ .../cleanup-dualboot-bcd.ps1 | 141 +++++++ .../repair-disk0-boot-to-new-windows.ps1 | 120 ++++++ 13 files changed, 924 insertions(+), 7 deletions(-) create mode 100644 docs/AUDIT_2026-05-23.md create mode 100644 docs/AUDIT_2026-05-23_FINAL.md create mode 100644 docs/AUDIT_2026-05-23_LIVE.md create mode 100644 docs/CODEX_ENDSTUFE_PROMPT_2026-05-23.md create mode 100644 ops/windows-reinstall/README.md create mode 100644 ops/windows-reinstall/backup-delta-after-2026-05-07.ps1 create mode 100644 ops/windows-reinstall/cleanup-dualboot-bcd.ps1 create mode 100644 ops/windows-reinstall/repair-disk0-boot-to-new-windows.ps1 diff --git a/.gitignore b/.gitignore index 69446d7..61b6700 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,4 @@ Thumbs.db *.tmp *.log +.serena/ diff --git a/HOMELAB_ARCHITECTURE_MASTER_V2.md b/HOMELAB_ARCHITECTURE_MASTER_V2.md index f55f67e..71a2b33 100644 --- a/HOMELAB_ARCHITECTURE_MASTER_V2.md +++ b/HOMELAB_ARCHITECTURE_MASTER_V2.md @@ -3,7 +3,7 @@ > **Single Source of Truth** für Docker-Netzwerkarchitektur, Sicherheitsregeln, Zielbild und Migration des Kallilabcore-Homelabs. > **Arbeitsregel für KI-Assistenten:** Dieses Dokument immer zuerst lesen, bevor Fragen zu Containern, Netzwerken, Traefik, Tailscale, Migration oder Security beantwortet werden. -**Stand:** 2026-05-16 | **Aktueller Schwerpunkt:** GitOps / Doku-Synchronisierung / Reproduzierbare Deployments +**Stand:** 2026-05-23 | **Aktueller Schwerpunkt:** GitOps / Doku-Synchronisierung / Reproduzierbare Deployments --- @@ -105,7 +105,7 @@ Internet traefik (80/443) │ └── frontend_net - ├── öffentliche Apps (vaultwarden, mealie, paperless, immich, gitea, ntfy, mail-archiver, nextcloud) + ├── öffentliche Apps (vaultwarden, mealie, paperless, immich, gitea, ntfy, mail-archiver, nextcloud, jellyfin) ├── geschützte UIs mit Middleware (homepage, glance, paperless-gpt, uptime-kuma, filebrowser, scrutiny, code-server, borg-ui, glances, speedtest, bentopdf, grafana) ├── Admin-UI mit nativer Auth (komodo) └── Dienste mit Internetbedarf ohne öffentliche UI (ddns-updater) @@ -149,6 +149,7 @@ Diese Dienste sind über echte `*.kaleschke.info`-Domains erreichbar: - `gitea` (Web) — git.kaleschke.info - `immich_server` — immich.kaleschke.info - `nextcloud` — cloud.kaleschke.info +- `jellyfin` — jellyfin.kaleschke.info ### 4.2 Nicht öffentlich / nur Tailscale oder Traefik + Middleware Diese Dienste sind **keine Public Apps**: @@ -278,6 +279,8 @@ 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 | +| `jellyfin` | ✅ | `frontend_net` | Traefik | aktiv via `jellyfin.kaleschke.info`, native Jellyfin-Auth, Media-/Photos-Mounts read-only | — | +| `plex` | ✅ | `host` | Plex native / Host-Netz | Compose-Stack unter `host-services/plex/`; Host-Netz bleibt fuer Discovery / Plex GDM dokumentierte Ausnahme | — | ### 7.5 Admin / Operations @@ -311,7 +314,7 @@ Legende Status: | Container | Status | Ziel | |---|---|---| -| `Plex-Media-Server` | ⏳ Dockerman | Compose-Migration, `host`-Netz bleibt (Discovery) | +| — | — | Plex ist nicht mehr offen: der Dienst ist als Repo-Compose-Stack unter `host-services/plex/` dokumentiert; `host`-Netz bleibt als Discovery-Ausnahme. | ### 7.8 Entfernte Container diff --git a/docs/AUDIT_2026-05-23.md b/docs/AUDIT_2026-05-23.md new file mode 100644 index 0000000..3d0e9a4 --- /dev/null +++ b/docs/AUDIT_2026-05-23.md @@ -0,0 +1,369 @@ +# Homelab Audit - 2026-05-23 + +Stand: 2026-05-23, repo-basiert. Erstellt nach `docs/WORKFLOW.md` und `docs/GITOPS_DRIFT_RUNBOOK.md`. Quellebasis: `origin/master` plus lokaler Clone, ohne Schreibbefehle, ohne Deploy. + +Dieser Audit ist eine punktuelle Sollzustands-Bewertung, kein Live-Status. Die Live-Verifikations-Schritte stehen am Ende in Abschnitt 9; alle dortigen Outputs ersetzen Vermutungen durch Messwerte. + +## 0. Executive Summary + +Ampel-Bewertung pro Bereich: + +| Bereich | Ampel | Kernaussage | +|---|---|---| +| GitOps-Konsistenz (lokal/Gitea) | 🟡 | Lokaler Clone ist **1 Commit voraus** auf `master` (`cd650b1`, Haertungs-Commit). Bis zum Push existiert dieser Stand nur lokal, nicht in Gitea — bei einem Clone-Verlust ist er weg. | +| 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. | +| 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). | + +**Kernfazit:** Das Homelab ist sehr nah an der "Endstufe". Es gibt keine kritischen Befunde. Die einzige Pflichtaktion vor dem naechsten geplanten Schritt ist der **Push des lokalen Commits `cd650b1` nach Gitea**, damit `origin/master` wieder die Quelle der Wahrheit ist. Danach sind nur noch zwei priorisierte Pakete offen: Monitoring-Stack live finalisieren und Doku auf den Stand der neuen Stacks (Jellyfin/Plex/...) nachziehen. + +--- + +## 1. Methodik und Quellen + +Diese Audit-Quellen wurden gelesen (repo-seitig): + +- `HOMELAB_ARCHITECTURE_MASTER_V2.md` +- `docs/WORKFLOW.md` +- `docs/REPO_MAP.md` +- `docs/SERVICE_CATALOG.md` +- `docs/RESTORE_MATRIX.md` +- `docs/GITOPS_DRIFT_RUNBOOK.md` +- `docs/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` +- `ops/filebrowser/docker-compose.yml` +- `security/authelia/configuration.yml` +- `core/gitea/docker-compose.yml` +- `apps/jellyfin/docker-compose.yml` +- `host-services/plex/docker-compose.yml` +- `ops/policy-checks/last-report.md` + +Schreibbefehle: keine. Deploys: keine. Containerlaufzeit, Komodo-Webhook-Status, Borg-Lauf-Frische und Host-Listener wurden bewusst nicht angetastet — dafuer steht der Live-Daten-Block in Abschnitt 9. + +--- + +## 2. Schicht A — GitOps und Konsistenz + +### 2.1 Lokaler Clone vs. `origin/master` + +``` +## master...origin/master [ahead 1] +HEAD = cd650b19ac057a1b74ac63503e5dba50eaf5b8ea +origin/master = af231dd4e835b19005cc0842509199d480af00d9 +``` + +- **Befund:** Lokaler Clone ist 1 Commit voraus. +- **Commit:** `cd650b1 Close Gitea signup, dedup posture-check alerts, extend Borg scope` (Sat May 23 11:01:24 2026 +0200). +- **Inhalt** (laut Commit-Message und betroffenen Dateien): + - Gitea: `DISABLE_REGISTRATION=true`, `ENABLE_OPENID_SIGNIN=false`, `ENABLE_OPENID_SIGNUP=false` + - Repo-Pflicht-Doku ergaenzt: Komodo-Stack-Webhook-Pflicht in `CLAUDE.md`, `AI_CONTEXT.md`, `WORKFLOW.md` + - `posture-check.sh`: Disk1-NTFS-Funktion ausgelagert, Inode-Check auf NTFS uebersprungen, ntfy-Dedup via Fingerprint-State + `ALERT_REPEAT_SECONDS` + - `docker-critical-events.sh`: JSON-Parsing, `die exit=0` gefiltert, strukturierte ntfy-Message + - `borg-ui`: `/mnt/user/services` als `/local/services:ro` gemountet, `all-important-sources.txt` ergaenzt + - Unraid User Scripts dokumentiert (daily report) + - `MIGRATION_LOG.md`, `RESTORE_MATRIX.md`, `DISASTER_RECOVERY.md` aktualisiert +- **Risiko:** Bei Verlust des Windows-Clones (Reinstall, Diskcrash) ist dieser Stand verloren, weil er nicht in Gitea liegt. Komodo deployt ausserdem aus Gitea und kennt diese Aenderungen noch nicht. +- **Empfohlene Aktion (Pflicht vor weiterer Arbeit):** In GitHub Desktop `Push origin` ausfuehren. Danach Komodo-Reaktion fuer die betroffenen Stacks (`gitea`, `borg-ui`) pruefen und Smoke-Tests laufen lassen. + +### 2.2 Working-Tree-Status + +``` +$ git status --short + M CLAUDE.md + M HOMELAB_ARCHITECTURE_MASTER_V2.md + M apps/homepage/docker-compose.yml + ... +(47 Dateien als modified gemeldet, plus 4 Untracked) +``` + +- **Bewertung:** Die 47 "modified files" sind mit hoher Wahrscheinlichkeit **Mount-Artefakte** durch CRLF/LF zwischen Windows-Clone und Linux-Mount. Stichprobe `git diff -w --stat CLAUDE.md HOMELAB_ARCHITECTURE_MASTER_V2.md` lieferte leer — d. h. keine inhaltlichen Diffs. +- **Aktion:** Bitte am Windows-Host in GitHub Desktop `git status --short` ausfuehren. Wenn dort der Tree leer ist (nur die 4 Untracked), gibt es keinen echten Working-Tree-Drift. Wenn dort echte Diffs erscheinen, hier bitte zurueckmelden — dann ist das ein eigener Befund. +- **Optional (nicht Pflicht):** `.gitattributes` mit `* text=auto eol=lf` haerten, damit dieser Mount-Effekt fuer KI-Audits aus dem Weg geht. Das ist ein eigener kleiner Commit, kein Audit-Output. + +### 2.3 Untracked Files + +``` +?? .serena/ +?? ops/windows-reinstall/backup-delta-after-2026-05-07.ps1 +?? ops/windows-reinstall/cleanup-dualboot-bcd.ps1 +?? ops/windows-reinstall/repair-disk0-boot-to-new-windows.ps1 +``` + +- `.serena/` ist das Working-Directory des Serena Code-Search-Tools. Hat eigene `.gitignore` intern, aber das `.serena/`-Verzeichnis selbst ist nicht in der Repo-`.gitignore`. +- **Aktion (klein):** `.serena/` in `.gitignore` aufnehmen, damit es nicht versehentlich committet wird. +- Die drei PowerShell-Scripts unter `ops/windows-reinstall/` sind Windows-Reinstall-Helfer. Entscheidung offen: ins Repo aufnehmen (mit Kontextkommentar warum sie dort liegen) oder lokal halten und in `.gitignore` aufnehmen. Vorschlag: aufnehmen, weil `ops/` der dokumentierte Ort fuer Ops-Skripte ist. + +### 2.4 Letzte Commit-Historie (Top 10) + +``` +cd650b1 Close Gitea signup, dedup posture-check alerts, extend Borg scope [LOKAL, NICHT GEPUSHT] +af231dd Fix zero-count noise pattern handling +428223d Mark posture report scripts executable +b6d3ed4 Tune homelab availability alerts +9e7bebb Add daily operations report with hardened log-noise filtering +b7cbbe5 Fix Jellyfin external DNS +71ac18b Fix Jellyfin native auth routing +90f270b Fix Jellyfin config permissions +e28f8da Add Jellyfin media server stack +edfec5b Add Plex media server stack +``` + +- Die letzten Tage waren sichtbar: Jellyfin/Plex hinzugefuegt, Availability-Alerts feinjustiert, Posture-Check-Skripte produktiv gemacht, dann der grosse Haertungs-Commit gestern (2026-05-23 11:01). + +### 2.5 Compose-Inventar vs. Doku + +Repo hat folgende Compose-Stacks, die in den Doku-Quellen (`HOMELAB_ARCHITECTURE_MASTER_V2.md`, `docs/SERVICE_CATALOG.md`, `docs/REPO_MAP.md`) **nicht oder nur teilweise** aufgefuehrt sind: + +| Stack | Status im Repo | Status in Master-Doku | +|---|---|---| +| `apps/jellyfin/docker-compose.yml` | produktiv vorhanden, gepinnt `jellyfin:10.11.8@sha256:...`, Traefik `jellyfin.kaleschke.info`, `secure-headers@file`, native Auth, `/mnt/user/media:ro` + `/mnt/user/photos:ro` | **fehlt** in 7.4 Apps; Authelia-ACL kennt aber bereits `jellyfin.kaleschke.info` als bypass — Doku hinkt hinterher | +| `host-services/plex/docker-compose.yml` | produktiv vorhanden, gepinnt `plexinc/pms-docker:1.43.1.10611-1e34174b1@sha256:...`, `network_mode: host`, `/mnt/user/media:ro` + `/mnt/user/photos:ro` | Master-Doku sagt explizit "Plex-Media-Server ist historischer Host-Sonderfall, nicht als Repo-Compose-Stack enthalten" — **das stimmt nicht mehr**, Plex ist jetzt ein Repo-Compose-Stack | +| `host-services/docker/` | leeres Verzeichnis | nicht erwaehnt | +| `infra/dns/` | leeres Verzeichnis | nicht erwaehnt | +| `ops/Semaphore/` | Skripten/Playbooks aber kein Compose | nicht erwaehnt | +| `ops/backrest/` | leeres Verzeichnis (Stack laut Master-Doku am 2026-05-15 entfernt) | korrekt als entfernt dokumentiert; Verzeichnis sollte leer bleiben oder weg | +| `apps/firefly/`, `apps/firefly-fints/` | leere Verzeichnisse | nicht erwaehnt | +| `apps/stirling-pdf/` | leeres Verzeichnis (durch `bentopdf` abgeloest) | korrekt als abgeloest dokumentiert | + +- **Aktion (Doku-Synchronisierung):** `HOMELAB_ARCHITECTURE_MASTER_V2.md` Abschnitt 7 (Container-Zielbild), `docs/SERVICE_CATALOG.md` und `docs/REPO_MAP.md` um Jellyfin und Plex erweitern. Plex-Doku im Master umschreiben: nicht mehr "historisch ausserhalb Repo", sondern "Compose-Stack mit `network_mode: host` als VPN-Discovery-Ausnahme". +- **Aktion (Repo-Hygiene):** Die leeren Verzeichnisse `apps/firefly`, `apps/firefly-fints`, `apps/stirling-pdf`, `host-services/docker`, `infra/dns`, `ops/backrest`, `ops/grafana-influxdb/scripts`, `ops/Semaphore/playbooks`, `ops/Semaphore/Scripts` aufraeumen — Master-Doku sagt: "Leere `.keep`-Platzhalter wurden entfernt; neue Verzeichnisse sollen erst mit konkretem Inhalt ins Repo." Diese Verzeichnisse verletzen diese Regel passiv. + +### 2.6 Image-Pinning + +Lt. `docs/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` + +Das ist bewusst dokumentiert und kein Audit-Befund. + +--- + +## 3. Schicht B — Hardening-Sprint 2026-05 (Sitrep) + +Dies war der Sprint, der nach dem 2026-05 Restore explizit gesetzt wurde. Stand im Repo: + +| Sprint-Item | Stand 2026-05-16 (Plan) | Stand 2026-05-23 (Repo) | Beleg | +|---|---|---|---| +| **(1) Backup-Konsistenz** — `dump_sqlite_container` fuer Gitea/Vaultwarden/Uptime-Kuma/Speedtest/Filebrowser + `pg_dump` Nextcloud | offen | ✅ erledigt | `ops/borg-ui/scripts/pre-backup-dumps.sh` Z. 97–139 (`dump_sqlite_container`), Z. 253–258 (Nextcloud `pg_dump`), Z. 261–264 (alle SQLite-Container mit Host-Fallback), Z. 267 (Filebrowser BoltDB). Borg-Scope erweitert um `/mnt/user/services` (Borg-UI Compose Z. 26 + `all-important-sources.txt` Z. 23–25). | +| **(2) Filebrowser entschaerfen** — `/mnt/user/appdata:/srv/appdata` weg, gezielte RW-Subpfade | offen | ✅ erledigt | `ops/filebrowser/docker-compose.yml` Z. 11–16. Keine Appdata-Mounts mehr. Nur noch `/mnt/user/documents`, `/mnt/user/photos`, `/mnt/user/projekte` als Datenmounts plus eigener `/database` und `/config`. | +| **(3) Authelia Argon2id haerten** — iterations 3, memory 65536, parallelism 4 | offen | ✅ erledigt | `security/authelia/configuration.yml` Z. 17–25. Exakt die geplanten Parameter sind aktiv. | +| **(4) Gitea Webhook-Allowlist** — `ALLOWED_HOST_LIST=*` einschraenken | offen | ✅ erledigt | `core/gitea/docker-compose.yml` Z. 18: `GITEA__webhook__ALLOWED_HOST_LIST=komodo-core,localhost,127.0.0.1,192.168.178.0/24`. Zusatz aus heutigem Commit: Public Registration und OpenID-Signup/Signin sind deaktiviert. | + +Alle vier Items sind **im Repo abgeschlossen**. Live-Wirksamkeit haengt am Komodo-Deploy aus Gitea — und genau da haengt aktuell der ungepushte Commit `cd650b1` davor (siehe 2.1). Solange er nicht in Gitea ist, ist insbesondere die Gitea-Signup-Schliessung im Live-Stand nicht garantiert. + +**Bewusst nicht angefasste Liste (Operator-Entscheidung 2026-05-16) ist weiterhin gueltig:** +- Hermes — bleibt VM-seitig offen, NAS-Stack bewusst nicht starten +- Disk1 NTFS — Phase-2-Migration nach Plan +- Komodo native Auth ohne ForwardAuth +- Grafana/Influxdb3-core `user: "0"` +- Image-Pinning-Vereinheitlichung (`:latest@sha256:`) fuer ddns-updater/glances/scrutiny + +--- + +## 4. Schicht C — "Endstufe?"-Bewertung + +### 4.1 Backup/Restore-Readiness + +- **Dump-Coverage:** `pre-backup-dumps.sh` deckt 14 Quellen ab: PostgreSQL-Globals + 3 Shared-DBs + 3 dedizierte Postgres (mealie, immich, nextcloud) + 4 SQLite (gitea, vaultwarden, uptime-kuma, speedtest-tracker) + Filebrowser-BoltDB + Borg-UI + Grafana + Komodo-Mongo. Deckt 1:1 die Restore-Matrix-Eintraege ab. +- **Borg-Scope:** `all-important-sources.txt` enthaelt 27 Eintraege inkl. neuer `services/homelab-infra`, `services/stacks`, `services/posture-check` und `secrets`. +- **Restore-Validierungen:** Laut `docs/RESTORE_MATRIX.md` sind am 2026-05-07 Mini-Restores fuer `gitea`, `vaultwarden` und `paperless` validiert worden — dokumentierter Stand. +- **Live offen:** Wann lief der letzte Borg-Lauf? Sind alle Dumps unter `/mnt/user/backups/borg/dumps/latest` frischer als 24h? Siehe Live-Checkliste 9.4. + +### 4.2 Monitoring-Migration + +- Repo-Zielzustand `monitoring/docker-compose.yml` (337 Zeilen Compose) existiert mit Prometheus, Alertmanager, ntfy-Bridge, Blackbox-Exporter, Loki, Promtail, Grafana, node-exporter, cAdvisor, InfluxDB3 Core. +- Provisioning unter `monitoring/grafana/provisioning/` und `monitoring/prometheus/`, `monitoring/loki/`, `monitoring/promtail/`, `monitoring/alertmanager/`, `monitoring/blackbox/` vollstaendig vorhanden. +- Alte Stacks `ops/grafana-influxdb/` und `ops/loki/` bewusst noch im Repo (dokumentierter Altstand, Rollback-Referenz). +- **Live offen:** Ist `monitoring` schon als Komodo-Stack deployed? Laufen die Container? Sind die Secret-Dateien `monitoring_grafana_admin_password.txt`, `monitoring_grafana_influxdb_token.txt`, `influxdb3_admin_token.json` auf dem Host? Siehe Live-Checkliste 9.5. + +### 4.3 Repo-Hygiene + +| Befund | Schwere | Aktion | +|---|---|---| +| 8 leere Verzeichnisse (`apps/firefly`, `apps/firefly-fints`, `apps/stirling-pdf`, `host-services/docker`, `infra/dns`, `ops/backrest`, `ops/grafana-influxdb/scripts`, `ops/Semaphore/playbooks`, `ops/Semaphore/Scripts`) | klein | Aufraeumen, danach committen | +| `.serena/` untracked, nicht in `.gitignore` | klein | `.serena/` zu `.gitignore` hinzufuegen | +| 3 `ops/windows-reinstall/*.ps1` untracked | klein | Entscheidung treffen: ins Repo oder ignorieren | + +### 4.4 Bekannte dokumentierte Ausnahmen + +Aus `HOMELAB_ARCHITECTURE_MASTER_V2.md` Abschnitt 10 — alle weiterhin gueltig und durch den Policy-Check abgedeckt (`ops/policy-checks/last-report.md` 0 Critical): + +- Traefik 80/443 +- Tailscale Host-Netz + Capabilities +- AdGuard Port 53 + 8082 (Admin-Port LAN-only, dokumentiert; **offener Punkt im Master:** "Traefik-Absicherung ausstehend (Block F)" — bewusst spaeter) +- Plex Host-Netz (aber Master-Doku-Eintrag jetzt falsch, siehe 2.5) +- Scrutiny `privileged: true` +- Komodo Docker-Socket + keine pauschale Middleware +- glance-docker-socket-proxy Read-only Socket +- Gitea SSH 222 +- ddns-updater `frontend_net` +- mail-archiver Hybrid-Netze +- `traefik/dynamic/*` manueller Host-Sync +- nextcloud native Auth +- monitoring-influxdb3-core LAN 8181 + `user: "0"` +- monitoring-promtail Docker-Socket read-only + +Keine ungeplanten neuen Ausnahmen. + +### 4.5 Endstufen-Definition + +"Endstufe" ist erreicht, wenn alle folgenden Punkte gruen sind: + +1. **Gitea = Quelle der Wahrheit** — kein lokaler Commit ohne Push 🟡 (heute: `cd650b1` ungepusht) +2. **Hardening-Sprint im Repo abgeschlossen** 🟢 +3. **Backup-Konsistenz live verifiziert (Borg laeuft, Dumps frisch)** ❓ Live +4. **Monitoring-Stack live, alte Altstaende gestoppt** 🟡 +5. **Doku synchron mit Repo (Jellyfin/Plex, leere Verzeichnisse, ...)** 🟠 +6. **Policy-Check 0 Critical** 🟢 (4 Warnings sind dokumentierte Ausnahmen) +7. **Restore-Lab gepflegt (`mail-archiver` als naechste Uebung empfohlen)** 🟡 dokumentiert offen + +Sechs der sieben Punkte sind in Reichweite ohne neue Architekturentscheidungen. Punkt 3 und 4 brauchen Live-Daten (Abschnitt 9). Punkt 1 ist 1 Push entfernt. + +--- + +## 5. Priorisierte Restliste + +| Prio | Aktion | Begruendung | Aufwand | +|---|---|---|---| +| **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 | +| **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 | +| **P3** | Naechster Restore-Lab-Lauf: `mail-archiver` (empfohlen in `RESTORE_MATRIX.md`) | Restore-Routine ueben, bevor sie gebraucht wird | 1 Stunde | +| **P4** | `.gitattributes` mit `* text=auto eol=lf` hinzufuegen, um CRLF/LF-Mount-Effekte bei KI-Audits zu vermeiden | klein, kosmetisch fuer kuenftige Audits | 5 Minuten | +| **bleibt** | Hermes VM-Seite, Disk1-NTFS Phase 2, AdGuard Admin-Port hinter Traefik (Block F), Image-Pinning ddns/glances/scrutiny | bewusste Operator-Entscheidung, kein Audit-Beduerfnis | nicht jetzt | + +--- + +## 6. Was bewusst NICHT angetastet wurde (Audit-Verzicht) + +Konsistent mit der bekannten Nicht-Anfassen-Liste: + +- Hermes (VM-seitig offen) +- Disk1 NTFS (Phase-2-Migration nach Plan) +- Komodo native Auth ohne ForwardAuth +- Grafana / influxdb3-core `user: "0"` Uebergangsausnahme +- Image-Pinning-Vereinheitlichung fuer ddns-updater/glances/scrutiny + +--- + +## 7. Risiken und Drift-Indikatoren + +| Risiko | Wahrscheinlichkeit | Wirkung | Migitation | +|---|---|---|---| +| Lokaler Clone-Verlust (Disk, Reinstall) bevor `cd650b1` gepusht wurde | gering, aber real (Du bist im Reinstall-Kontext, siehe `ops/windows-reinstall/`!) | Verlust von Gitea-Signup-Closure und Posture-Check-Verbesserungen | **Sofort pushen** | +| Komodo deployt aus Gitea, `gitea` und `borg-ui` laufen aktuell ohne die heutigen Verbesserungen | mittel | Gitea Signup steht noch offen, Borg-Scope umfasst `/mnt/user/services` noch nicht | Push + Komodo-Reaktion pruefen | +| 47 vermeintlich modified files koennten doch echte Diffs sein, wenn der Windows-Host etwas anderes zeigt | gering | falsche Audit-Aussage | Punkt 9.1 auf dem Windows-Host pruefen | +| Doku-Drift wird groesser, wenn weitere Stacks ohne Doku-Update hinzukommen | mittel | KI-Audits und Onboarding leiden | P2-Doku-Sync nicht aufschieben | +| Monitoring-Stack-Migration unfertig, alter und neuer Stack koennten parallel werden | mittel | Doppelte Metric-/Log-Pipeline, Verwirrung bei Diagnose | Live-Status klaeren bevor Deploy | + +--- + +## 8. Sources of Truth — Schnellzugriff + +- Operative Quelle der Wahrheit: Gitea `origin/master` (https://git.kaleschke.info/Micha/homelab-infra) +- Architektur-Master: `HOMELAB_ARCHITECTURE_MASTER_V2.md` +- Workflow / GitOps-Regeln: `docs/WORKFLOW.md` +- 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` + +--- + +## 9. Live-Daten-Checkliste — bitte ausfuehren und zurueckspielen + +Fuehre die folgenden Bloecke am Unraid-Host (per SSH oder Web-Terminal) und am Windows-Host (Git Bash / PowerShell in `G:\Gitea_Clone\homelab-infra`) aus und pastiere die Outputs zurueck. Ich integriere sie dann in diesen Report. + +### 9.1 Windows-Host: Echter Working-Tree-Status + +```powershell +cd G:\Gitea_Clone\homelab-infra +git status --short +git log origin/master..HEAD --oneline +``` + +Erwartet: Working tree leer (oder nur die 4 Untracked). `cd650b1` als einziger lokaler Commit. + +### 9.2 Unraid-Host: Gitea online + +```bash +curl -sI --max-time 5 https://git.kaleschke.info/ | head -5 +docker exec gitea sh -lc 'gitea --version' +``` + +Erwartet: `HTTP/2 200` (oder ein Auth-Code, der den Erreichbarkeitstest erfuellt). Gitea-Version stimmt mit Image-Tag `1.25.4` ueberein. + +### 9.3 Unraid-Host: Komodo-Webhook-Status + +In Komodo UI fuer jeden produktiven Stack aus `Micha/homelab-infra` pruefen: +- `webhook_enabled: true` +- Gitea-Hook auf `http://komodo-core:9120/listener/github/stack//deploy` aktiv +- `last_status` der letzten Webhook-Delivery in Gitea (Repository -> Settings -> Webhooks) + +Pflicht-Stacks zum Pruefen: `traefik`, `gitea`, `authelia`, `vaultwarden`, `postgresql17`, `redis`, `paperless-ngx`, `immich`, `nextcloud`, `mealie`, `mail-archiver`, `ntfy`, `homepage`, `paperless-gpt`, `borg-ui`, `filebrowser`, `code-server`, `uptime-kuma`, `glance`, `glances`, `scrutiny`, `speedtest-tracker`, `bentopdf`, `ddns-updater`, `komodo`, `jellyfin`, `plex`, `adguard`, `tailscale`, `monitoring`, `hermes-agent` (sofern produktiv). + +Bei Stacks **ohne** aktiven Webhook bitte den Grund vermerken (dokumentierte Ausnahme oder Nachholbedarf). + +### 9.4 Unraid-Host: Borg-Lauf-Frische und Dump-Coverage + +```bash +ls -lah /mnt/user/backups/borg/dumps/latest/ +stat -c '%y %n' /mnt/user/backups/borg/dumps/latest/*.dump /mnt/user/backups/borg/dumps/latest/*.sql /mnt/user/backups/borg/dumps/latest/*.archive.gz /mnt/user/backups/borg/dumps/latest/*.sqlite 2>/dev/null | sort +docker exec borg-ui borg list --short 2>&1 | tail -10 +``` + +Erwartet: Alle 14 Artefakte aus 4.1 sind vorhanden, mtime juenger als 24h. Borg-Archive-Liste zeigt regelmaessige Laeufe. + +### 9.5 Unraid-Host: Monitoring-Stack live? + +```bash +docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Image}}" | grep monitoring +ls -la /mnt/user/appdata/secrets/ | grep -E 'monitoring|influxdb3' +curl -sI --max-time 5 https://monitoring.kaleschke.info/ | head -5 +ss -ltnp 2>/dev/null | grep -E ':8181|:9090|:3100' | head -10 +``` + +Erwartet: Entweder alle `monitoring-*` Container laufen (dann ist 4.2 🟢) oder gar nicht (dann ist 4.2 🟡 wie aktuell bewertet). Halb-Zustand ist ein Audit-Befund. + +### 9.6 Unraid-Host: GitOps-Pflichtmatrix Spot-Check fuer einen Stack + +Beispiel `gitea` (weil der heutige Commit ihn betrifft): + +```bash +cd /mnt/user/services/stacks/gitea +git rev-parse --short HEAD +git status -sb +docker inspect gitea --format '{{.Image}}' +docker exec gitea sh -lc 'env | grep -E "ALLOWED_HOST_LIST|DISABLE_REGISTRATION|ENABLE_OPENID"' +``` + +Erwartet: Komodo-Workspace `HEAD` zeigt entweder auf `cd650b1` (wenn Push schon erfolgt + Komodo deployed) oder auf `af231dd` (vor dem Push). ENV-Vars in der Live-Runtime spiegeln den Commit, der Komodo zuletzt deployed hat. + +### 9.7 Unraid-Host: Host-Listener-Spot-Check + +```bash +ss -ltnp 2>/dev/null | grep -E ':80|:443|:53|:222|:8082|:8181' | sort +``` + +Erwartet exakt die dokumentierten Ausnahmen aus `HOMELAB_ARCHITECTURE_MASTER_V2.md` Abschnitt 10. Andere Listener = Befund. + +--- + +## 10. Nachhalte-Vorschlag + +Wenn Du moechtest, halte ich diesen Audit in zwei Schritten zu Ende: + +1. Du fuehrst Abschnitt 9 aus und pastierst die Outputs zurueck. +2. Ich aktualisiere diesen Report mit den Live-Ergebnissen, ergaenze die Ampel und schliesse die offenen 🟡/🟠 Punkte oder benenne sie als echte Restliste. + +Bis dahin gilt der Stand dieses Reports als Repo-Audit, nicht als Endstufen-Zertifikat. diff --git a/docs/AUDIT_2026-05-23_FINAL.md b/docs/AUDIT_2026-05-23_FINAL.md new file mode 100644 index 0000000..1aa44a1 --- /dev/null +++ b/docs/AUDIT_2026-05-23_FINAL.md @@ -0,0 +1,22 @@ +# Homelab Audit Final - 2026-05-23 + +Stand: 2026-05-23 11:30 CEST. Ergebnis nach Push, Live-Messung, Doku-Sync und Repo-Hygiene. + +| Punkt | Ampel | Beleg | +|---|---|---| +| P0 `cd650b1` nach Gitea pushen | gruen | Push `af231dd..cd650b1 master -> master`; `gitea` und `borg-ui` Workspaces auf `cd650b1`, beide `## master...origin/master`. | +| P0 Live-Daten ablegen | gruen | `docs/AUDIT_2026-05-23_LIVE.md` angelegt, keine Secret-Werte dokumentiert. | +| P1 Monitoring live / Altstaende down | gelb-gruen | `monitoring-*` Container laufen, alte `grafana`/`influxdb3-core`/`loki`/`alloy` Container sind nicht vorhanden. Grafana, Prometheus und Traefik-Route sind ok; Loki nimmt Daten an, aber `/ready` liefert 503. | +| 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 am 2026-05-23 02:30 UTC; 15 aktuelle Dump-/Archive-Artefakte von 04:00/04:01 CEST und damit juenger als 24 h. | +| P3 Repo-Hygiene | gruen | `.serena/` in `.gitignore`; leere Verzeichnisse entfernt; `ops/windows-reinstall/*.ps1` bewusst ins Repo aufgenommen. | +| Policy-Check | gruen | `ops/policy-checks/check_repo.ps1`: 0 Critical, dokumentierte Warnings zu Plex Host-Netz, InfluxDB/Grafana `user: "0"` und bekannten mutable-tag-Ausnahmen. | + +## Bewusst offen + +- `monitoring-loki` `/ready` meldet trotz laufendem Container und aktiven Metrics-/Query-Zaehlungen `503`. Da keine Altcontainer mehr laufen und Loki Logs annimmt, wurde kein blinder Reparaturversuch gestartet. Naechster sauberer Schritt waere ein eigener Loki-Readiness-Check gegen Konfiguration, Ring/Single-Binary-Modus und Grafana-Datasource, nicht Teil dieses Endstufen-Commits. +- Host-Workspace `monitoring` enthaelt untracked `monitoring/prometheus/alerts.yml.bak-20260520-incident`. Nicht angefasst, weil es Host-/Komodo-Workspace-Drift ist und nicht im lokalen Repo liegt. + +## Schlussbewertung + +Das Homelab ist fuer die Audit-Restliste in der Endstufe. Es gibt keine kritischen Repo- oder GitOps-Befunde. Einzige gelbe Restkante ist Loki-Readiness als separater Monitoring-Hardening-Punkt; der produktive Stack ist deployed, alte Altstaende sind down, Backups und Dumps sind frisch. diff --git a/docs/AUDIT_2026-05-23_LIVE.md b/docs/AUDIT_2026-05-23_LIVE.md new file mode 100644 index 0000000..c14755b --- /dev/null +++ b/docs/AUDIT_2026-05-23_LIVE.md @@ -0,0 +1,88 @@ +# Homelab Audit Live-Daten - 2026-05-23 + +Stand: 2026-05-23 11:27 CEST. Quelle: lokaler Windows-Clone und SSH auf `Kallilabcore`. Secret-Werte wurden nicht ausgelesen oder redaktiert; dokumentiert sind nur Status, Dateinamen, Modi, Env-Key-Namen und nicht geheime Bool-/Host-Werte. + +## 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`. +- `cd650b1` wurde nach `origin/master` gepusht: `af231dd..cd650b1 master -> master`. + +## 9.2 Gitea online + +- `curl -sI https://git.kaleschke.info/`: `HTTP/2 200`. +- `docker exec gitea gitea --version`: `1.25.4`. +- Signup-Smoke: `/user/sign_up` meldet Registration disabled. + +## 9.3 Komodo / Workspace-Reaktion + +| Stack | Workspace HEAD | Status | Live-Beleg | +|---|---:|---|---| +| `gitea` | `cd650b1` | `## master...origin/master` | Container neu gestartet, Env-Keys aktiv | +| `borg-ui` | `cd650b1` | `## master...origin/master` | Container healthy, `/mnt/user/services -> /local/services` gemountet | +| `monitoring` | `cd650b1` | `## master...origin/master`, untracked Host-Backup `monitoring/prometheus/alerts.yml.bak-20260520-incident` | Stack laeuft seit 4 Tagen | + +Gitea Live-Env ohne Secrets: + +```text +GITEA__service__DISABLE_REGISTRATION=true +GITEA__openid__ENABLE_OPENID_SIGNIN=false +GITEA__openid__ENABLE_OPENID_SIGNUP=false +GITEA__webhook__ALLOWED_HOST_LIST=komodo-core,localhost,127.0.0.1,192.168.178.0/24 +``` + +## 9.4 Borg-Lauf-Frische und Dump-Coverage + +- Borg UI DB: Repository `appdata-critical`, `last_backup=2026-05-23 02:30:12 UTC`, `archive_count=30`, letzter Job `completed`, `nfiles=100056`. +- Schedule: `Taegliche Sicherung` enabled, letzter Lauf `2026-05-23 02:30:11 UTC`, naechster Lauf `2026-05-24 02:30:00 UTC`. +- Hinweis: `docker exec borg-ui borg list --short` funktioniert ohne Repository-Parameter/Env nicht (`Invalid location format: ""`). Der Borg-UI-Status wurde deshalb ueber die lokale Borg-UI-Datenbank ohne Secret-Spalten ausgewertet. + +Frische Artefakte in `/mnt/user/backups/borg/dumps/latest`: + +```text +2026-05-23 04:00 postgresql17-globals.sql +2026-05-23 04:00 postgresql17-mailarchiver.dump +2026-05-23 04:00 postgresql17-paperless.dump +2026-05-23 04:01 postgresql17-authelia.dump +2026-05-23 04:01 mealie.dump +2026-05-23 04:01 gitea.sqlite.dump +2026-05-23 04:01 immich.dump +2026-05-23 04:01 nextcloud.dump +2026-05-23 04:01 uptime-kuma.sqlite.dump +2026-05-23 04:01 vaultwarden.sqlite.dump +2026-05-23 04:01 speedtest-tracker.sqlite.dump +2026-05-23 04:01 filebrowser.bolt.dump +2026-05-23 04:01 borg-ui.sqlite +2026-05-23 04:01 grafana.sqlite +2026-05-23 04:01 komodo-mongo.archive.gz +``` + +Bewertung: 15 aktuelle Dump-/Archive-Artefakte sind juenger als 24 h. Aeltere nackte `.sqlite`-Dateien vom 2026-05-16 liegen noch im Verzeichnis, sind aber Legacy-Kopien ohne `.dump`-Suffix und nicht Teil der aktuellen Dump-Serie. + +## 9.5 Monitoring-Stack + +- Aktive Container: `monitoring-grafana`, `monitoring-promtail`, `monitoring-prometheus`, `monitoring-cadvisor`, `monitoring-influxdb3-core`, `monitoring-loki`, `monitoring-blackbox-exporter`, `monitoring-alertmanager-ntfy-bridge`, `monitoring-alertmanager`, `monitoring-node-exporter`. +- Alte Altcontainer `grafana`, `influxdb3-core`, `loki`, `alloy`: nicht vorhanden in `docker ps -a`. +- Secret-Dateien vorhanden und mode `600`: `monitoring_grafana_admin_password.txt`, `monitoring_grafana_influxdb_token.txt`, `influxdb3_admin_token.json`. +- `https://monitoring.kaleschke.info/`: `HTTP/2 302` zu Authelia, wie erwartet. +- Host-Listener fuer Monitoring: nur `127.0.0.1:8181`; keine Host-Listener fuer `:9090` oder `:3100`. +- Prometheus readiness: `Prometheus Server is Ready.` +- Grafana health: `database=ok`, Version `12.4.3`. +- Loki: Container laeuft und API/metrics liefert `loki_build_info`; `/ready` liefert aktuell `503 Service Unavailable`, waehrend Query-/Metrics-Endpunkte 200-Zaehler zeigen. Kein Reparaturversuch gestartet, weil der produktive Logpfad nachweislich Daten annimmt und die Stop-Regel keine blinden Eingriffe erlaubt. + +## 9.6 GitOps Spot-Check Gitea + +- `/mnt/user/services/stacks/gitea`: `cd650b1`, `## master...origin/master`. +- Docker-Image: `docker.gitea.com/gitea:1.25.4`. +- Live-ENV spiegelt `cd650b1` fuer Registrierung, OpenID und Webhook-Allowlist. + +## 9.7 Host-Listener + +Dokumentierte Listener gefunden: + +- `:80`, `:443` Traefik +- `:53` AdGuard DNS +- `:222` Gitea SSH +- `:8082` AdGuard Admin +- `127.0.0.1:8181` Monitoring InfluxDB + +Zusaetzlich sichtbar: mehrere `wsdd2` Listener auf `:5355` je Interface. Das ist Host-/Unraid-Service-Discovery, kein Compose-Webdienst und kein GitOps-Stack-Port. diff --git a/docs/CODEX_ENDSTUFE_PROMPT_2026-05-23.md b/docs/CODEX_ENDSTUFE_PROMPT_2026-05-23.md new file mode 100644 index 0000000..b31c73d --- /dev/null +++ b/docs/CODEX_ENDSTUFE_PROMPT_2026-05-23.md @@ -0,0 +1,32 @@ +# Codex-Prompt: KalliLab Endstufe + +Du hast Vollzugriff auf `G:\Gitea_Clone\homelab-infra`, Gitea-Push, Komodo, und SSH auf Unraid `Kallilabcore`. + +## Lies zuerst +1. `CLAUDE.md` +2. `docs/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). +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. +6. **P3** — Repo-Hygiene: 8 leere Verzeichnisse weg, `.serena/` in `.gitignore`, Entscheidung zu `ops/windows-reinstall/*.ps1`. + +## Regeln (aus CLAUDE.md, nicht verhandelbar) +- Secrets nie im Klartext ausgeben. +- Keine Aenderungen direkt in Komodo, nur ueber Git → Push → Komodo. +- Kein `push --force`, kein blindes Loeschen von `/mnt/user/{appdata,documents,photos,services,backups}`. +- Working-Tree-Status nur aus `git status --short` ableiten, nie aus `git diff` ueber Linux-Mount. +- Traefik dynamic config wird nicht von Komodo deployed — Aenderungen dort manuell auf `/mnt/user/appdata/traefik/dynamic/` syncen. +- Nicht anfassen: Hermes, Disk1 NTFS Phase 2, Komodo-Auth, Grafana/InfluxDB `user: "0"`, Image-Pinning ddns/glances/scrutiny. +- Wenn zwei Reparaturversuche scheitern: stoppen, Drift-Runbook Pflichtmatrix, Operator fragen. + +## Arbeitsmodus pro Block +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. diff --git a/docs/MIGRATION_LOG.md b/docs/MIGRATION_LOG.md index d054c01..b21ba38 100644 --- a/docs/MIGRATION_LOG.md +++ b/docs/MIGRATION_LOG.md @@ -16,6 +16,13 @@ Dieses Dokument ist nur noch ein historischer Verlauf. Der aktuelle operative Ab ## Historische Meilensteine +### 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. +- 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. + ### 2026-05-20 - Gitea 5xx-Bursts untersucht und Signup geschlossen - Live-Befund zu `HomelabTraefik5xx`: kurze externe `POST /`-Bursts auf `gitea@docker` von `103.153.183.69` und `103.153.183.73`, jeweils HTTP 500 in unter 10 ms; normale Gitea-Checks und Git-Reads liefen parallel mit HTTP 200. diff --git a/docs/REPO_MAP.md b/docs/REPO_MAP.md index 117976a..b04c426 100644 --- a/docs/REPO_MAP.md +++ b/docs/REPO_MAP.md @@ -1,6 +1,6 @@ # Repository Map -Stand: 2026-05-04 +Stand: 2026-05-23 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. @@ -75,6 +75,7 @@ Secret-Werte werden hier nicht dokumentiert. Aufgefuehrt werden nur Variablennam | BentoPDF | `apps/bentopdf/docker-compose.yml` | `bentopdf` -> `bentopdfteam/bentopdf:2.8.4` | `pdf.kaleschke.info` | `frontend_net` | keine | Traefik + Authelia; COOP/COEP Middleware | | Homepage | `apps/homepage/docker-compose.yml` | `homepage` -> `ghcr.io/gethomepage/homepage:v1.12.3@sha256:...` | `home.kaleschke.info` | `frontend_net` | keine | Docker-Socket read-only fuer Widgets; viele `HOMEPAGE_VAR_*` Env-Keys | | Immich | `apps/immich/docker-compose.yml` | `immich-server`, `immich-machine-learning`, `database`, `redis` | `immich.kaleschke.info` | `frontend_net`, `immich_default` | keine | `immich-server` depends on `database`, `redis` | +| Jellyfin | `apps/jellyfin/docker-compose.yml` | `jellyfin` -> `jellyfin/jellyfin:10.11.8@sha256:...` | `jellyfin.kaleschke.info` | `frontend_net` | keine | native Jellyfin-Auth; `/mnt/user/media` und `/mnt/user/photos` read-only | | Mail Archiver | `apps/mail-archiver/docker-compose.yml` | `mail-archiver` -> `s1t5/mailarchiver@sha256:...` | `mail.kaleschke.info` | `frontend_net`, `backend_net` | keine | shared PostgreSQL via env connection string; Internet fuer IMAP | | Mealie | `apps/mealie/docker-compose.yml` | `mealie`, `mealie-postgres` | `mealie.kaleschke.info` | `frontend_net`, `mealie_internal` | keine | eigene PostgreSQL im internen Netz | | Nextcloud | `apps/nextcloud/docker-compose.yml` | `nextcloud`, `nextcloud-postgres`, `nextcloud-redis` | `cloud.kaleschke.info` | `frontend_net`, `nextcloud_internal` | keine | native Nextcloud-Auth; eigene DB und Redis | @@ -99,6 +100,7 @@ Secret-Werte werden hier nicht dokumentiert. Aufgefuehrt werden nur Variablennam | Stack | Compose | Services / Images | Hosts | Networks | Ports / Mode | Abhaengigkeiten | |---|---|---|---|---|---|---| | AdGuard Home | `host-services/Adguard/docker-compose.yml` | `adguard` -> `adguard/adguardhome:v0.107.52` | keine Traefik-Route | `dns_net`, `frontend_net` | `53/tcp`, `53/udp`, `8082:80/tcp` | Unbound in `dns_net`; direkte Ports sind dokumentierte Ausnahme | +| Plex | `host-services/plex/docker-compose.yml` | `plex` -> `plexinc/pms-docker:1.43.1.10611-1e34174b1@sha256:...` | keine Traefik-Route | `network_mode: host` | host network | Medienserver; Host-Netz bleibt fuer Discovery / Plex GDM dokumentierte Ausnahme | | Tailscale | `host-services/tailscale/docker-compose.yml` | `Tailscale-Docker` -> `tailscale/tailscale:stable@sha256:...` | keine | `network_mode: host` | host network | VPN/Remote-Zugang | ### Operations @@ -141,6 +143,7 @@ Secret-Werte werden hier nicht dokumentiert. Aufgefuehrt werden nur Variablennam | `hermes.kaleschke.info` | Hermes Dashboard | Traefik + Authelia | | `home.kaleschke.info` | Homepage | Traefik + Authelia; faellt in Authelia unter die 1FA-Wildcard-Regel | | `immich.kaleschke.info` | Immich | Traefik, native App-Auth | +| `jellyfin.kaleschke.info` | Jellyfin | Traefik, native App-Auth | | `komodo.kaleschke.info` | Komodo | Traefik, native Komodo-Auth; keine pauschale ForwardAuth | | `mail.kaleschke.info` | Mail Archiver | Traefik + Authelia + App-Auth | | `mealie.kaleschke.info` | Mealie | Traefik | @@ -172,7 +175,7 @@ Secret-Werte werden hier nicht dokumentiert. Aufgefuehrt werden nur Variablennam | `glance_socket_net` | Compose-intern, `internal: true` | Glance und `glance-docker-socket-proxy`; keine Traefik-Anbindung | | `komodo_net` | Compose-intern, `internal: true` | Komodo Core, Mongo, Periphery | | `hermes_net` | Compose-intern bridge | Hermes Gateway/Dashboard | -| `host` | Host-Netz | Tailscale; Plex historisch ausserhalb Repo | +| `host` | Host-Netz | Tailscale; Plex als Repo-Compose-Stack unter `host-services/plex/` | ## Volumes und Datenpfade @@ -189,6 +192,8 @@ Secret-Werte werden hier nicht dokumentiert. Aufgefuehrt werden nur Variablennam | Mealie | `/mnt/user/appdata/mealie/data`, `/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` | +| Jellyfin | `/mnt/user/appdata/jellyfin/config`, `/mnt/user/appdata/jellyfin/cache`, `/mnt/user/media`, `/mnt/user/photos` | +| Plex | `/mnt/user/appdata/plex/config`, `/mnt/user/appdata/plex/transcode`, `/mnt/user/media`, `/mnt/user/photos` | | Homepage | `/mnt/user/appdata/homepage`, `/mnt/user/appdata/homepage/images`, Docker socket read-only | | ntfy | `/mnt/user/appdata/ntfy` | | Paperless-GPT | `/mnt/user/appdata/paperless-gpt/data`, `/mnt/user/appdata/paperless-gpt/prompts` | @@ -252,5 +257,5 @@ Das Skript liest Secret-Dateien auf dem Host und schreibt Dump-Artefakte. Bei An - `tailscale` nutzt Host-Netz, `NET_ADMIN`, `NET_RAW` und `/dev/net/tun` als dokumentierte VPN-Ausnahme. - `grafana`, `influxdb3-core` und `monitoring-influxdb3-core` laufen aktuell als `user: "0"`; UID/GID-Hardening nur als eigener Sprint. - Leere `.keep`-Platzhalter wurden entfernt; neue Verzeichnisse sollen erst mit konkretem Inhalt ins Repo. -- `Plex-Media-Server` ist als historischer Host-Sonderfall dokumentiert, aber nicht als Repo-Compose-Stack enthalten. +- `plex` ist als Repo-Compose-Stack unter `host-services/plex/` enthalten; `network_mode: host` bleibt die dokumentierte Discovery-Ausnahme. - BentoPDF kann je nach Live-Stand vorbereitet statt produktiv sein; Hermes Dashboard ist produktiv unter `hermes.kaleschke.info`. diff --git a/docs/SERVICE_CATALOG.md b/docs/SERVICE_CATALOG.md index 47b07a3..5b8e0ca 100644 --- a/docs/SERVICE_CATALOG.md +++ b/docs/SERVICE_CATALOG.md @@ -1,6 +1,6 @@ # Service Catalog -Stand: 2026-05-04 +Stand: 2026-05-23 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. @@ -47,6 +47,8 @@ Secret-Werte sind nicht enthalten. Es werden nur Secret-Namen, Env-Key-Namen und | `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 | +| `jellyfin` | Medienserver | `apps/jellyfin/docker-compose.yml` | `https://jellyfin.kaleschke.info` | Traefik, `frontend_net` | `/mnt/user/appdata/jellyfin/config`, `/mnt/user/appdata/jellyfin/cache`, `/mnt/user/media`, `/mnt/user/photos` | Tier 2, Appdata + Medienpfade im Borg-/Share-Scope | ja | native Jellyfin-Auth; Medien- und Fotomounts read-only; keine direkten Host-Ports | +| `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 | | `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 | diff --git a/ops/windows-reinstall/README.md b/ops/windows-reinstall/README.md new file mode 100644 index 0000000..2d9ebd1 --- /dev/null +++ b/ops/windows-reinstall/README.md @@ -0,0 +1,9 @@ +# Windows Reinstall Helpers + +Diese Skripte sind bewusst versionierte Operator-Hilfen fuer den Windows-Neuaufsetzen-/Dual-Boot-Kontext vom Mai 2026. + +- `backup-delta-after-2026-05-07.ps1` kopiert nach einem definierten Cutoff lokale Nutzdaten in ein Backup-Ziel. +- `repair-disk0-boot-to-new-windows.ps1` repariert EFI/Bootdateien fuer das neue Windows auf der Intel-SSD und verlangt Adminrechte. +- `cleanup-dualboot-bcd.ps1` bereinigt BCD-Bootmenueeintraege und verlangt eine explizite Textbestaetigung. + +Die Skripte enthalten keine Secrets, arbeiten aber mit lokalen Windows-Datentraegern und duerfen nur interaktiv und mit vorheriger Sichtpruefung ausgefuehrt werden. diff --git a/ops/windows-reinstall/backup-delta-after-2026-05-07.ps1 b/ops/windows-reinstall/backup-delta-after-2026-05-07.ps1 new file mode 100644 index 0000000..ac4b06b --- /dev/null +++ b/ops/windows-reinstall/backup-delta-after-2026-05-07.ps1 @@ -0,0 +1,118 @@ +param( + [datetime]$Cutoff = "2026-05-07T15:30:00", + [string]$BackupRoot = "H:\Windows-Neuaufsetzen-Backup", + [string]$UserProfilePath = "C:\Users\michi" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +function Invoke-RobocopyDelta { + param( + [string]$Source, + [string]$Destination, + [string]$LogName + ) + + if (-not (Test-Path $Source)) { + Write-Host "SKIP missing source: $Source" + return + } + + New-Item -ItemType Directory -Force -Path $Destination | Out-Null + $LogPath = Join-Path $DeltaLogDir $LogName + + Write-Host "DELTA COPY $Source" + Write-Host " -> $Destination" + + robocopy $Source $Destination /E /COPY:DAT /DCOPY:DAT /R:2 /W:2 /XJ /MAXAGE:$CutoffString /TEE /LOG:$LogPath + $ExitCode = $LASTEXITCODE + if ($ExitCode -gt 7) { + throw "Robocopy failed with exit code $ExitCode for source: $Source" + } +} + +if (-not (Test-Path $BackupRoot)) { + throw "BackupRoot does not exist: $BackupRoot" +} + +if (-not (Test-Path $UserProfilePath)) { + throw "UserProfilePath does not exist: $UserProfilePath" +} + +$DeltaRoot = Join-Path $BackupRoot "_Delta_2026-05-19" +$DeltaLogDir = Join-Path $DeltaRoot "00_Logs" +New-Item -ItemType Directory -Force -Path $DeltaLogDir | Out-Null + +$CutoffString = $Cutoff.ToString("yyyyMMdd") + +$Manifest = [ordered]@{ + CreatedAt = (Get-Date).ToString("s") + Cutoff = $Cutoff.ToString("s") + CutoffNote = "Robocopy /MAXAGE uses date granularity, so files changed on or after the cutoff day are included." + RunningSystemDrive = $env:SystemDrive + RunningWinDir = $env:windir + BackupRoot = $BackupRoot + DeltaRoot = $DeltaRoot +} + +$Manifest.GetEnumerator() | + ForEach-Object { "$($_.Key)=$($_.Value)" } | + Out-File (Join-Path $DeltaRoot "delta_manifest.txt") -Encoding UTF8 + +$Jobs = @( + @{ Source = Join-Path $UserProfilePath "Desktop"; Destination = Join-Path $DeltaRoot "01_Desktop"; Log = "delta_current_desktop.log" }, + @{ Source = Join-Path $UserProfilePath "Documents"; Destination = Join-Path $DeltaRoot "02_Dokumente"; Log = "delta_current_documents.log" }, + @{ Source = Join-Path $UserProfilePath "Pictures"; Destination = Join-Path $DeltaRoot "03_Bilder"; Log = "delta_current_pictures.log" }, + @{ Source = Join-Path $UserProfilePath "Videos"; Destination = Join-Path $DeltaRoot "04_Videos"; Log = "delta_current_videos.log" }, + @{ Source = Join-Path $UserProfilePath "Downloads"; Destination = Join-Path $DeltaRoot "05_Downloads"; Log = "delta_current_downloads.log" }, + @{ Source = Join-Path $UserProfilePath ".ssh"; Destination = Join-Path $DeltaRoot "09_Programme_Settings_Lizenzen\ssh"; Log = "delta_current_ssh.log" }, + @{ Source = Join-Path $UserProfilePath "AppData\Local\Subsembly"; Destination = Join-Path $DeltaRoot "09_Programme_Settings_Lizenzen\Subsembly_Local"; Log = "delta_subsembly_local.log" }, + @{ Source = Join-Path $UserProfilePath "AppData\Local\Buhl"; Destination = Join-Path $DeltaRoot "09_Programme_Settings_Lizenzen\Buhl_Local"; Log = "delta_buhl_local.log" }, + @{ Source = Join-Path $UserProfilePath "AppData\Local\Buhl Data Service GmbH"; Destination = Join-Path $DeltaRoot "09_Programme_Settings_Lizenzen\Buhl_Data_Service_Local"; Log = "delta_buhl_data_service_local.log" }, + @{ Source = "C:\ProgramData\Buhl Data Service GmbH"; Destination = Join-Path $DeltaRoot "09_Programme_Settings_Lizenzen\Buhl_Data_Service_ProgramData"; Log = "delta_buhl_data_service_programdata.log" }, + @{ Source = "G:\Gitea_Clone"; Destination = Join-Path $DeltaRoot "06_Projekte\Gitea_Clone"; Log = "delta_g_gitea_clone.log" }, + @{ Source = "G:\open-webui"; Destination = Join-Path $DeltaRoot "09_Programme_Settings_Lizenzen\open-webui"; Log = "delta_g_open_webui.log" }, + @{ Source = "G:\Treiber"; Destination = Join-Path $DeltaRoot "13_Treiber_Windows\G_Treiber"; Log = "delta_g_treiber.log" }, + @{ Source = "F:\BMW Leasing"; Destination = Join-Path $DeltaRoot "07_Banking_Finanzen\BMW Leasing"; Log = "delta_f_bmw_leasing.log" }, + @{ Source = "F:\Marina Handy 2025"; Destination = Join-Path $DeltaRoot "03_Bilder\Marina Handy 2025"; Log = "delta_f_marina_handy_2025.log" } +) + +foreach ($Job in $Jobs) { + Invoke-RobocopyDelta -Source $Job.Source -Destination $Job.Destination -LogName $Job.Log +} + +$ExplicitBankingDir = Join-Path $DeltaRoot "07_Banking_Finanzen\Banking4_Datentresor_explizit" +New-Item -ItemType Directory -Force -Path $ExplicitBankingDir | Out-Null + +$BankingFiles = @( + (Join-Path $UserProfilePath "Documents\Mein Datentresor.sub"), + (Join-Path $UserProfilePath "Documents\.Mein Datentresor.sub.att") +) + +foreach ($File in $BankingFiles) { + if (Test-Path $File) { + Copy-Item -LiteralPath $File -Destination (Join-Path $ExplicitBankingDir (Split-Path $File -Leaf)) -Force + } +} + +$SummaryRoots = Get-ChildItem $DeltaRoot -Directory -ErrorAction SilentlyContinue | Where-Object { $_.Name -ne "00_Logs" } +$Summary = foreach ($Root in $SummaryRoots) { + $Files = @(Get-ChildItem $Root.FullName -Recurse -Force -File -ErrorAction SilentlyContinue) + $Measure = $Files | Measure-Object Length -Sum + [pscustomobject]@{ + Path = $Root.FullName + Files = $Files.Count + SizeGB = [math]::Round(($Measure.Sum / 1GB), 4) + Newest = ($Files | Sort-Object LastWriteTime -Descending | Select-Object -First 1 -ExpandProperty LastWriteTime) + } +} + +$Summary | Export-Csv (Join-Path $DeltaLogDir "delta_summary.csv") -NoTypeInformation -Encoding UTF8 + +Write-Host "" +Write-Host "Delta backup finished:" +Write-Host " $DeltaRoot" +Write-Host "" +Write-Host "Summary:" +$Summary | Format-Table -AutoSize diff --git a/ops/windows-reinstall/cleanup-dualboot-bcd.ps1 b/ops/windows-reinstall/cleanup-dualboot-bcd.ps1 new file mode 100644 index 0000000..3f5c9c0 --- /dev/null +++ b/ops/windows-reinstall/cleanup-dualboot-bcd.ps1 @@ -0,0 +1,141 @@ +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$BackupDir = "H:\Windows-Neuaufsetzen-Backup\12_Exportierte_Listen" +$BeforeFile = Join-Path $BackupDir "bcdedit_enum_before_dualboot_cleanup.txt" +$AfterFile = Join-Path $BackupDir "bcdedit_enum_after_dualboot_cleanup.txt" + +function Assert-Admin { + $Identity = [Security.Principal.WindowsIdentity]::GetCurrent() + $Principal = [Security.Principal.WindowsPrincipal]::new($Identity) + if (-not $Principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { + throw "Dieses Skript muss in PowerShell als Administrator ausgefuehrt werden." + } +} + +function Get-BcdOsLoaderEntries { + $Text = & bcdedit /enum osloader /v + if ($LASTEXITCODE -ne 0) { + throw "bcdedit /enum osloader /v ist fehlgeschlagen." + } + + $Entries = @() + $Current = $null + + foreach ($Line in $Text) { + if ($Line -match "^-{5,}$") { + if ($Current -and $Current.Identifier) { + $Entries += [pscustomobject]$Current + } + $Current = @{ + Identifier = $null + Device = $null + OsDevice = $null + Path = $null + Description = $null + } + continue + } + + if (-not $Current) { + continue + } + + if ($Line -match "^\s*(identifier|Bezeichner)\s+(.+)$") { $Current.Identifier = $Matches[2].Trim(); continue } + if ($Line -match "^\s*device\s+(.+)$") { $Current.Device = $Matches[1].Trim(); continue } + if ($Line -match "^\s*osdevice\s+(.+)$") { $Current.OsDevice = $Matches[1].Trim(); continue } + if ($Line -match "^\s*path\s+(.+)$") { $Current.Path = $Matches[1].Trim(); continue } + if ($Line -match "^\s*description\s+(.+)$") { $Current.Description = $Matches[1].Trim(); continue } + } + + if ($Current -and $Current.Identifier) { + $Entries += [pscustomobject]$Current + } + + return $Entries +} + +Assert-Admin + +New-Item -ItemType Directory -Force -Path $BackupDir | Out-Null + +& bcdedit /enum all /v | Out-File $BeforeFile -Encoding UTF8 + +$Entries = Get-BcdOsLoaderEntries + +Write-Host "Gefundene Windows-Boot-Eintraege:" +$Entries | Select-Object Identifier, Description, Device, OsDevice, Path | Format-Table -AutoSize + +$NewWindows = $Entries | Where-Object { + $_.OsDevice -eq "partition=D:" -and + $_.Path -match "winload\.efi$" -and + (Test-Path "D:\Windows\System32\winload.efi") +} | Select-Object -First 1 + +$OldWindows = $Entries | Where-Object { + $_.OsDevice -eq "partition=C:" -and + $_.Path -match "winload\.efi$" -and + (Test-Path "C:\Windows\System32\winload.efi") +} | Select-Object -First 1 + +if (-not $NewWindows) { + throw "Abbruch: Neuer Windows-Eintrag auf D:\Windows wurde nicht eindeutig gefunden." +} + +if (-not $OldWindows) { + throw "Abbruch: Alter Windows-Eintrag auf C:\Windows wurde nicht eindeutig gefunden." +} + +$KeepIds = @($NewWindows.Identifier, $OldWindows.Identifier) +$DeleteEntries = $Entries | Where-Object { $KeepIds -notcontains $_.Identifier } + +Write-Host "" +Write-Host "Bleibt erhalten:" +Write-Host " NEU: $($NewWindows.Identifier) -> $($NewWindows.OsDevice) -> D:\Windows" +Write-Host " ALT: $($OldWindows.Identifier) -> $($OldWindows.OsDevice) -> C:\Windows" + +Write-Host "" +Write-Host "Wird aus dem Bootmenue entfernt:" +if ($DeleteEntries) { + $DeleteEntries | Select-Object Identifier, Description, Device, OsDevice, Path | Format-Table -AutoSize +} else { + Write-Host " Keine ueberfluessigen Eintraege gefunden." +} + +Write-Host "" +Write-Host "Es werden nur Bootmenue-Eintraege geloescht, keine Partitionen und keine Windows-Ordner." +$Confirmation = Read-Host "Tippe exakt BOOTCLEAN um fortzufahren" +if ($Confirmation -ne "BOOTCLEAN") { + throw "Abbruch: Bestaetigung wurde nicht eingegeben." +} + +& bcdedit /set $NewWindows.Identifier description "Windows 11 Neu" +if ($LASTEXITCODE -ne 0) { throw "Konnte neuen Windows-Eintrag nicht umbenennen." } + +& bcdedit /set $OldWindows.Identifier description "Windows 11 Alt" +if ($LASTEXITCODE -ne 0) { throw "Konnte alten Windows-Eintrag nicht umbenennen." } + +& bcdedit /default $NewWindows.Identifier +if ($LASTEXITCODE -ne 0) { throw "Konnte Standard-Boot-Eintrag nicht setzen." } + +& bcdedit /timeout 5 +if ($LASTEXITCODE -ne 0) { throw "Konnte Timeout nicht setzen." } + +foreach ($Entry in $DeleteEntries) { + Write-Host "Loesche Boot-Eintrag $($Entry.Identifier) ($($Entry.Description))" + & bcdedit /delete $Entry.Identifier /f + if ($LASTEXITCODE -ne 0) { + throw "Konnte Boot-Eintrag $($Entry.Identifier) nicht loeschen." + } +} + +& bcdedit /enum all /v | Out-File $AfterFile -Encoding UTF8 + +Write-Host "" +Write-Host "Bootmenue bereinigt." +Write-Host "Vorher gesichert: $BeforeFile" +Write-Host "Nachher gesichert: $AfterFile" +Write-Host "" +Write-Host "Bitte neu starten. Es sollten nur noch zwei Eintraege erscheinen:" +Write-Host " Windows 11 Neu" +Write-Host " Windows 11 Alt" diff --git a/ops/windows-reinstall/repair-disk0-boot-to-new-windows.ps1 b/ops/windows-reinstall/repair-disk0-boot-to-new-windows.ps1 new file mode 100644 index 0000000..0835835 --- /dev/null +++ b/ops/windows-reinstall/repair-disk0-boot-to-new-windows.ps1 @@ -0,0 +1,120 @@ +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$TargetDiskNumber = 0 +$TargetWindowsDrive = "D" +$TargetDiskFriendlyName = "INTEL SSDSC2BW180A3L" +$TargetDiskSerialNumber = "CVCV3105053K180EGN" +$EfiSizeMB = 260 +$EfiLabel = "SYSTEM" + +function Assert-Admin { + $Identity = [Security.Principal.WindowsIdentity]::GetCurrent() + $Principal = [Security.Principal.WindowsPrincipal]::new($Identity) + if (-not $Principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { + throw "Dieses Skript muss in PowerShell als Administrator ausgefuehrt werden." + } +} + +function Get-FreeDriveLetter { + $Used = (Get-Volume | Where-Object DriveLetter | Select-Object -ExpandProperty DriveLetter) + foreach ($Letter in "S","T","U","V","W","X","Y","Z") { + if ($Used -notcontains $Letter) { + return $Letter + } + } + throw "Kein freier Laufwerksbuchstabe fuer die EFI-Partition gefunden." +} + +Assert-Admin + +$Disk = Get-Disk -Number $TargetDiskNumber +$TargetWindowsPath = "$TargetWindowsDrive`:\Windows" +$TargetWinload = "$TargetWindowsDrive`:\Windows\System32\winload.efi" + +Write-Host "Aktuell gestartetes Windows:" +Write-Host " SystemDrive: $env:SystemDrive" +Write-Host " windir: $env:windir" +Write-Host "" + +Write-Host "Zieldatentraeger:" +$Disk | Select-Object Number, FriendlyName, SerialNumber, HealthStatus, OperationalStatus, @{Name="SizeGB";Expression={[math]::Round($_.Size/1GB,2)}}, PartitionStyle | Format-Table -AutoSize + +Write-Host "Partitionen auf Datentraeger ${TargetDiskNumber}:" +Get-Partition -DiskNumber $TargetDiskNumber | Sort-Object PartitionNumber | Select-Object DiskNumber, PartitionNumber, DriveLetter, Type, @{Name="SizeGB";Expression={[math]::Round($_.Size/1GB,3)}}, GptType | Format-Table -AutoSize + +if ($Disk.FriendlyName -ne $TargetDiskFriendlyName) { + throw "Abbruch: Datentraeger 0 ist nicht die erwartete Intel-SSD." +} + +if ($Disk.SerialNumber -ne $TargetDiskSerialNumber) { + throw "Abbruch: Seriennummer von Datentraeger 0 passt nicht." +} + +if (-not (Test-Path $TargetWinload)) { + throw "Abbruch: Ziel-Windows wurde nicht gefunden: $TargetWinload" +} + +if ($env:SystemDrive -eq "$TargetWindowsDrive`:") { + throw "Abbruch: Du bist bereits im Ziel-Windows. Dieses Skript ist fuer den Zustand gedacht, in dem neues Windows als D: sichtbar ist." +} + +$ExistingEfiOnDisk0 = Get-Partition -DiskNumber $TargetDiskNumber | Where-Object { + $_.GptType -eq "{c12a7328-f81f-11d2-ba4b-00a0c93ec93b}" +} + +if ($ExistingEfiOnDisk0) { + Write-Host "EFI-Systempartition auf Datentraeger 0 existiert bereits." + $EfiPartition = $ExistingEfiOnDisk0 | Select-Object -First 1 +} else { + Write-Host "" + Write-Host "Auf Datentraeger 0 existiert keine EFI-Systempartition." + Write-Host "Das Skript verkleinert $TargetWindowsDrive`: um $EfiSizeMB MB und erstellt daraus eine neue EFI-Systempartition." + Write-Host "" + $Confirmation = Read-Host "Tippe exakt EFI um fortzufahren" + if ($Confirmation -ne "EFI") { + throw "Abbruch: Bestaetigung wurde nicht eingegeben." + } + + $TargetPartition = Get-Partition -DiskNumber $TargetDiskNumber | Where-Object DriveLetter -eq $TargetWindowsDrive + if (-not $TargetPartition) { + throw "Abbruch: Windows-Partition $TargetWindowsDrive`: wurde auf Datentraeger 0 nicht gefunden." + } + + $Supported = Get-PartitionSupportedSize -DiskNumber $TargetDiskNumber -PartitionNumber $TargetPartition.PartitionNumber + $NewSize = $TargetPartition.Size - ($EfiSizeMB * 1MB) + if ($NewSize -lt $Supported.SizeMin) { + throw "Abbruch: Partition kann nicht um $EfiSizeMB MB verkleinert werden." + } + + Resize-Partition -DiskNumber $TargetDiskNumber -PartitionNumber $TargetPartition.PartitionNumber -Size $NewSize + + $EfiPartition = New-Partition -DiskNumber $TargetDiskNumber -Size ($EfiSizeMB * 1MB) -GptType "{c12a7328-f81f-11d2-ba4b-00a0c93ec93b}" + Format-Volume -Partition $EfiPartition -FileSystem FAT32 -NewFileSystemLabel $EfiLabel -Confirm:$false | Out-Null +} + +$EfiLetter = Get-FreeDriveLetter +Set-Partition -DiskNumber $TargetDiskNumber -PartitionNumber $EfiPartition.PartitionNumber -NewDriveLetter $EfiLetter +$EfiDrive = "$EfiLetter`:" + +Write-Host "" +Write-Host "Schreibe Bootdateien fuer $TargetWindowsPath nach $EfiDrive ..." +bcdboot "$TargetWindowsPath" /s $EfiDrive /f UEFI +if ($LASTEXITCODE -ne 0) { + throw "bcdboot ist fehlgeschlagen." +} + +Write-Host "" +Write-Host "Setze Bootmenue-Timeout auf 3 Sekunden." +bcdedit /timeout 3 + +Write-Host "" +Write-Host "Aktuelle BCD-Eintraege:" +bcdedit /enum + +Write-Host "" +Write-Host "Fertig. Naechster Schritt:" +Write-Host "1. Neustarten." +Write-Host "2. Im UEFI/BIOS Windows Boot Manager der Intel-SSD/Datentraeger 0 als erste Bootoption waehlen, falls angeboten." +Write-Host "3. Im Bootmenue den neuen Windows-11-Eintrag starten." +Write-Host "4. Wenn das neue Windows laeuft, sollte SystemDrive C: sein und der alte Windows-Datentraeger einen anderen Buchstaben haben."