101 Commits

Author SHA1 Message Date
Micha 3bfd065326 Update Scrutiny image digest 2026-06-01 16:42:31 +02:00
Micha eeebeec804 Switch Paperless GPT to OpenAI API 2026-06-01 16:18:58 +02:00
Micha 55fdb13532 Enable Vaultwarden SMTP invites 2026-06-01 15:52:31 +02:00
Micha 8709fe8239 Focus family onboarding on core apps 2026-06-01 15:25:48 +02:00
Micha 89114b1b12 Record append-only operator decision 2026-06-01 15:16:56 +02:00
Micha 3da19421d0 Document hetzner account hygiene 2026-06-01 15:09:37 +02:00
Micha 16e661be87 Document fritzbox config backup 2026-06-01 14:19:13 +02:00
Micha 12c05376d0 Close fritzbox service window docs 2026-06-01 13:02:03 +02:00
Micha dfd0ccbb9a Refine external IPv6 operator check 2026-06-01 12:51:16 +02:00
Micha ae5d4aedfc Prepare external operator checks 2026-06-01 12:48:00 +02:00
Micha 479eb291c4 Prepare final homelab cleanup gates 2026-06-01 12:19:17 +02:00
Micha c3222e800b Validate backup follow-up and harden nearline pull 2026-06-01 08:27:52 +02:00
Micha 4e34582008 Trim documentation to active runbooks 2026-05-31 23:26:12 +02:00
Micha ab8bfea7c8 Close documented backup follow-ups 2026-05-31 23:07:34 +02:00
Micha 92562dfc9c Archive stale documentation 2026-05-31 22:53:10 +02:00
Micha c9c8f9e7ce docs: add post migration burn-in check 2026-05-31 21:45:58 +02:00
Micha 1d98945a67 fix: make restore test scripts executable 2026-05-31 21:44:59 +02:00
Micha 9ffcb4e92e fix: dump active grafana database 2026-05-31 21:41:23 +02:00
Micha 99a0bfd60e docs: record grafana 13 renovate closure 2026-05-31 21:35:52 +02:00
Micha e835dfd6ed fix: let grafana read host secrets 2026-05-31 21:33:09 +02:00
Micha 6e928b6944 chore: harden grafana 13 provisioning 2026-05-31 21:31:58 +02:00
Micha 60015c1e2c chore: upgrade grafana to 13 2026-05-31 21:28:59 +02:00
Micha e1afd08bf3 docs: record closed renovate migration prs 2026-05-31 21:25:45 +02:00
Micha 268df30a13 chore: finish postgres redis stateful migrations 2026-05-31 20:32:25 +02:00
Micha 80a5ad24a2 Document closure of Mongo 8 PR 2026-05-31 14:34:46 +02:00
Micha 28406ae22b Constrain Komodo Mongo Renovate track 2026-05-31 14:33:19 +02:00
Micha 7b6c03b433 Document Komodo Mongo 8 upgrade 2026-05-31 14:31:47 +02:00
Micha 59b93924fb Update Komodo Mongo to 8.0 2026-05-31 14:23:30 +02:00
Micha aecf3b2807 Document Renovate cron follow-up 2026-05-31 13:26:40 +02:00
Micha 8e820ea155 Document Prometheus drift alert reload 2026-05-31 13:19:26 +02:00
Micha 16a266cd79 Add GitOps runtime image drift alert 2026-05-31 13:17:45 +02:00
Micha 69ad9d1d3c Document Renovate PR merge rollout 2026-05-31 13:04:06 +02:00
Micha 96fcacc6f7 Merge Renovate PR #5 postgres 17.10 update
# Conflicts:
#	apps/mealie/docker-compose.yml
#	apps/nextcloud/docker-compose.yml
#	infra/postgresql17/docker-compose.yml
2026-05-31 12:54:50 +02:00
Micha 076676d9b3 Merge Renovate PR #4 mongo 7.0.34 update
# Conflicts:
#	ops/komodo/docker-compose.yml
2026-05-31 12:50:12 +02:00
Micha dde441915a Merge Renovate PR #3 minor and patch updates 2026-05-31 12:43:58 +02:00
Micha db1fa7c3f0 Merge Renovate PR #2 postgres digest update 2026-05-31 12:37:55 +02:00
Micha b8b0af9e27 Merge Renovate PR #1 mongo digest update 2026-05-31 12:36:53 +02:00
Micha 4867d632d2 Document Gitea workspace drift repair 2026-05-31 12:27:07 +02:00
renovate 90ef6374a5 chore(deps): update minor-and-patch-updates 2026-05-31 10:20:19 +00:00
Micha e6a0e9fea4 Document Komodo 5xx client root cause 2026-05-31 11:26:40 +02:00
Micha 10ef703a4e docs: Codex-Prompt fuer Komodo-5xx Root-Cause-Suche
Selbst-enthaltener Stafettenstab nach Glance-Ausschluss (130s-Stop-Test):
Polling-Rate unveraendert mit Glance down. Restkandidaten dokumentiert
(Posture-Check, Periphery, Komodo-Self-Check, LAN-Geraet) plus konkrete
Testreihenfolge und Fix-Erwartung.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 10:56:58 +02:00
Micha 0c08d68d2b monitoring: HomelabPrometheusTargetDown + HomelabDiskCritical
Schliesst die zwei in ALERT_RULES.md identifizierten Hoch-Luecken:
- up==0 (5m) als critical in neuer Gruppe homelab-meta — Scrape-Targets
  (node-exporter/cadvisor/blackbox/traefik) sind nicht laenger stille
  Ausfaelle.
- Disk-Critical bei >95% (5m) als critical, zusaetzlich zum bestehenden
  Warning bei >85% — fuer DB/appdata/Cache-Schreibblockaden.

ALERT_RULES.md Tabellen und Status-Abschnitt aktualisiert.
Wird wirksam nach Prometheus-Reload via Komodo-Redeploy des monitoring-Stacks.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 22:17:51 +02:00
Micha 73120869a7 docs: zentrale ALERT_RULES.md + Luecken-Analyse
Nachschlagetabelle aller Prometheus-Alarmregeln (Trigger/Schwelle/Severity/
Aktion) plus Bewertung der Abdeckung. Identifiziert zwei echte blinde Flecke
(kein up==0 Target-Down, kein Disk-Critical-Tier) mit fertigem PromQL als
Empfehlung. Cross-Ref aus ALERTING_MAP.md.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 16:36:45 +02:00
Micha 1503239881 Strategische Bewertung: sharpen banner + add 2026-05-30 status appendix
The original 2026-05-23 baseline was kept as a historical anchor but
the banner was too soft about how much of the concrete content is
already addressed. Reading the document standalone could mislead it
as a current TODO list.

Two changes, original text untouched:

1. Banner now explicitly says the document is mostly outdated,
   not to be read as a TODO list, and that the per-finding status
   lives in an appendix.

2. New "Status-Anhang 2026-05-30" at the end maps every concrete,
   actionable finding to its current state (erledigt / geparkt /
   entschieden nicht / offen / teilweise), grouped by the original
   sections (Block 1-8) and by the Top-5 lists and Phase-1-to-4
   roadmap.

Summary of what the appendix shows:
- Top 5 sofort: 5/5 erledigt
- Quick Wins: 6/7 erledigt, 1 geparkt
- Phase 1: 4/6 erledigt, 1 geparkt, 1 wartend
- Phase 2: 2/5 erledigt, 2 geparkt, 1 offen
- Phase 3: 1 entschieden-nicht, 1 teilweise, 3 offen
- Auth-Block (F-04/13/14/18): fully parked

Original "Schulnote 2-" no longer reflects reality; new note would
land at 1- to 2 but is not the point.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 12:48:36 +02:00
Micha 5c211faf87 Promote Codex notes to tracked docs with status banners
The three notes from 2026-05-23 had been sitting untracked in docs/
for a week. Variante A from today's review: keep them in docs/ with
explicit status banners and reference them from REPO_MAP.md, so they
stop being silent roommates and become discoverable.

- docs/STRATEGISCHE_BEWERTUNG_2026-05-23.md: historical baseline that
  kicked off the 2026-05-25 audit cycle. Permanent audit anchor and
  "where we stood on 2026-05-23" snapshot. Do not edit further.
- docs/CODEX_KONSOLIDIERUNG_2026-05-23.md: first Codex prompt for the
  audit cycle, content worked through; kept as a Codex-prompt
  template for future consolidation sweeps.
- docs/CODEX_JELLYFIN_REMOVAL_2026-05-23.md: Codex removal pattern,
  task executed 2026-05-25; kept as a template for future stack
  removals (Hermes review 2026-07-25, possibly BentoPDF / paperless-gpt
  follow-ups).

REPO_MAP.md "Wichtige Dokumente" now lists all three with one-line
purpose plus the F-19 prep doc committed earlier today.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 12:43:12 +02:00
Micha f2923aac62 F-19 prep: document mem-limits baseline plan (no compose changes)
ops/policy-checks/mem-limits-baseline.md captures the deliberate
"not today" decision for memory limits plus the plan for when it
becomes relevant:

- Phase 1: 7 days of hourly docker stats snapshots
- Phase 2: derive Tier-1 peak per container
- Phase 3: set limits at peak * 1.5 with documented floors
  (Postgres 1G, Mongo 1G, Redis 256M, etc.)
- Phase 4: roll out smallest-risk containers first, observe 24h
  between stages
- Phase 5: Tier-2 only after a concrete trigger event

Next trigger: family invitation out + 4 weeks stable use, or
first real OOM event in docker-critical-events.sh, or a sudden
Immich/Nextcloud load spike where host swap becomes visible.

Today's policy check is clean (0 Critical, 1 documented Warning
on influxdb3-core user 0, 13 documented Info findings on host
ports / privileged exceptions / latest+digest tags).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 11:58:54 +02:00
Micha 67ec40b762 Docs sweep: reflect Komodo bootstrap first run + clean stale "still open" notes
Six files had outdated status notes that the F-09 first run on
2026-05-30 made wrong:

- ops/restore-tests/komodo-bootstrap-runbook.md: "Erster echter Lauf
  steht noch aus" -> first run confirmed
- ops/restore-tests/komodo-bootstrap-plan.md: "Noch offen vor dem
  ersten echten Lauf" section -> "Bestaetigte Laeufe" table with
  the --what-if and --keep-data runs
- ops/restore-tests/immich-runbook.md: status note still said
  "Erster echter Lauf steht noch aus" although the Immich first run
  was 2026-05-27; correcting in the same sweep
- docs/AUDIT_2026-05-25_TODO.md: Sprint 2 entry on Komodo bootstrap
  path no longer carries the "Trockenlauf-Skript bleibt als offene
  Folgeaufgabe" tail
- docs/SERVICES_RECOVERY.md: replaced the "Trockenlauf-Idee (Doku-only,
  nicht ausgefuehrt)" section with the confirmed repo-script flow and
  marked the two "Naechste Aufgaben" rows about the dry-run as done
- docs/RESTORE_DRILL_ROUTINE.md: Q2 2026 DR-Sanity-Check entry now
  splits Komodo-Bootstrap-Pfad (done) from the two still-open items
  (Gitea bundles, secrets inventory)

No behavior change, only documentation consistency.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 11:18:37 +02:00
Micha abf7137aea F-09 Rest: Komodo bootstrap dry-run first real execution
Result on host: SUCCESS, all 5 smoke checks green.
- docker compose config valid
- Test-Mongo healthy in ~6s
- Mongo authenticated ping ok (Test-Creds)
- Komodo Core HTTP 200 on 127.0.0.1:19120
- Test-Periphery container state running

Production komodo-{mongo,core,periphery} and /mnt/user/appdata/komodo/
were not touched; test ran in isolated project restoretest-komodo with
disposable datadir under /mnt/user/backups/restore-lab/komodo/.
Report at /mnt/user/backups/restore-reports/komodo-bootstrap-2026-05-30.md.

Operator-click pattern preserved: SSH to root@kallilabcore is an action
class that requires explicit instruction per CLAUDE.md; the auto-mode
classifier correctly blocked a non-destructive SSH probe. Operator ran
the command via the Unraid web terminal.

ops/komodo/docker-compose.yml is now demonstrably viable as the recovery
anchor for the bootstrap stages in docs/SERVICES_RECOVERY.md, not just
assumed viable. Image digests (mongo:7.0.32, komodo-core:2,
komodo-periphery:2) and Mongo auth schema verified.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 11:14:20 +02:00
Micha 8095ab8b5d F-10: automated Authelia repo<->host drift check
New services/authelia-diff.sh compares the access_control: section of the
repo baseline against the live host configuration.yml. OIDC clients,
identity providers, and secret values stay out of scope by design.
Exit codes: 0 ok, 1 drift, 2 file missing, 3 section missing, 4 tool missing.

posture-check.sh gains check_authelia_config_drift, which calls the diff
script and reports drift as warning (not critical). SKIP_AUTHELIA_DRIFT=1
opts out; AUTHELIA_DIFF_SCRIPT overrides the path.

WORKFLOW.md gets a dedicated "Ausnahme: Authelia configuration.yml" section
analogous to the Traefik dynamic-config exception, with the mandatory
repo->host merge workflow and the env-variable contract.

Smoke-tested locally: identical files rc=0, ACL change rc=1 with proper
unified diff, non-ACL change (session.default_redirection_url) correctly
ignored.

Operator follow-up: set up a read-only repo mirror at
/mnt/user/services/homelab-infra/ so the check finds a current baseline.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 09:52:16 +02:00
Micha 3bd35434d6 Renovate live: first run produced 5 PRs + dashboard
Setup-Pfad final geworden, vier Reparaturen unterwegs:

1. EAI_AGAIN: Container kann git.kaleschke.info nicht aufloesen ->
   --add-host (analog zur Komodo-extra_hosts)
2. Token-Sichtbarkeit in ps/inspect -> --env-file mit 0600 tempfile
3. EACCES auf State-Mount: Renovate-Image laeuft als uid 12021 ->
   chmod 0777 auf /mnt/user/services/renovate/state
4. "Repository does not permit pull or push": Renovate-Source-
   Code (lib/modules/platform/gitea/index.ts) prueft hardcoded
   repo.permissions.push aus der Gitea-API. Mein initialer
   SQL-INSERT in die collaboration-Tabelle hatte den Gitea-
   In-Memory-Permission-Cache nicht aktualisiert; Operator-
   UI-Klick "Entfernen + neu hinzufuegen" loeste den Cache-
   Refresh.

Konfigurations-Trennung:
- renovate.json (Repo): nur Repo-Settings (extends, packageRules,
  ignorePaths, manager file patterns, labels)
- ops/renovate/bot-config.js: Bot-Settings (platform, endpoint,
  autodiscover=false, repositories=[Micha/homelab-infra],
  Concurrent-Limits)

Bot-Felder in renovate.json fuehren zu "Repository is forbidden,
status: disabled" weil Renovate die Repo-Config nicht als Bot-
Config wertet.

Erstlauf am 2026-05-29: 5 PRs, 1 Dependency-Dashboard, 8 Branches.
Komodo-Major bleibt durch packageRule deaktiviert wie erwartet.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 20:34:32 +02:00
renovate b38b5e2db3 chore(deps): update postgres docker tag to v17.10 2026-05-29 18:30:57 +00:00
renovate 75afde5935 chore(deps): update mongo docker tag to v7.0.34 2026-05-29 18:30:55 +00:00
renovate 70b1ffa190 chore(deps): update postgres:17.9 docker digest to 2a0d0fe 2026-05-29 18:30:12 +00:00
renovate 11a91d8a1e chore(deps): update mongo:7.0.32 docker digest to 8d727b3 2026-05-29 18:30:08 +00:00
Micha ad9267c66a Split renovate config: repo config in renovate.json, bot config in ops/
Renovate liest die repo-eigene renovate.json als REPO-Config, nicht
als BOT-Config. Bot-spezifische Felder (platform, endpoint,
repositories, autodiscover, gitAuthor, prHourlyLimit, ...) gehoeren
nicht hinein und werden als "this repo is forbidden / disabled"
fehlinterpretiert.

Saubere Trennung:
- renovate.json (Repo-Root): nur extends, packageRules,
  ignorePaths, manager file patterns, labels, rangeStrategy
- ops/renovate/bot-config.js: Plattform, Endpoint, Username,
  gitAuthor, autodiscover=false, repositories=[Micha/homelab-infra],
  Concurrent-/Hourly-Limits

bot-config.js statt config.json, weil Renovate Module-exports als
config-file akzeptiert (offizielle Variante).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 20:20:00 +02:00
Micha 489958af18 Use explicit repository list instead of autodiscover
Gitea's /api/v1/user/repos (which Renovate calls during autodiscover)
returns repos where the user is owner or org member, but NOT
collaborator-only repos. Our renovate service account has write
collaborator access on Micha/homelab-infra but no own/org repos,
so autodiscover yielded an empty list.

Switching to explicit "repositories": ["Micha/homelab-infra"] is
the pragmatic fix for a homelab with one repo to scan; avoids
having to create an org just for one service account.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 20:13:54 +02:00
Micha c16d62a04a Remove schedule:weekly, modernize docker-compose file pattern
Renovate 41 migriert schedule:weekly auf einen 5am-Monday-only
Lauf - das verhindert beim manuellen Erstlauf jede PR. Wir wollen
dass Renovate bei jedem User-Script-Tick (alle 6h) tatsaechlich
scannt; die Quartals-/Wochen-Rhythmik regeln wir ueber den Cron.

Auch docker-compose.fileMatch ist in Renovate 41 deprecated;
Renovate migriert es zur Laufzeit auf managerFilePatterns mit
regex-Slash-Wrapping. Wir uebernehmen die migrierte Form direkt,
damit die WARN "Config needs migrating" verschwindet.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 20:10:53 +02:00
Micha bdae014bff Harden renovate runner: env-file, add-host, explicit DNS
Drei Issues beim Erstlauf gefunden und gefixt:

1. EAI_AGAIN: Renovate-Container konnte git.kaleschke.info nicht
   aufloesen. Analog zu Komodos extra_hosts mappen wir den Hostname
   per --add-host auf 192.168.178.58 (LAN-IP des Unraid-Hosts).
   Zusaetzlich --dns 1.1.1.1/8.8.8.8 fuer externe Image-Registries.

2. Token-Leak in ps und docker inspect: -e RENOVATE_TOKEN=... macht
   den Wert in Process-Listing sichtbar. Stattdessen --env-file mit
   einem 0600 tempfile unter $RENOVATE_STATE_DIR/.env, das nach dem
   Lauf via shred bzw. rm geloescht wird.

3. Doppelter rc=$? Block plus return innerhalb einer {}-Subshell
   waren Tot-Code; aufgeraeumt.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 20:04:24 +02:00
Micha 30aa696e61 Prepare Renovate bot against Gitea (F-12) + doc sweep
renovate.json: gitea platform, autodiscover Micha/*, group rules
(major separate, minor+patch+digest grouped, stateful tier-1
individual, komodo-major disabled), pin range strategy, no
automerge, dependency dashboard enabled.

ops/renovate/run-renovate.sh: one-shot docker run wrapper that
reads the Gitea PAT from /mnt/user/appdata/secrets/renovate_token.txt,
runs renovate/renovate:41, logs into /mnt/user/services/renovate/logs/.

docs/RENOVATE.md: 5-step operator setup (Gitea service account,
PAT, token file, first run, six-hourly user script). Explicit
no-automerge stance with notfall-stop checklist.

Cross-doc sweep: SECRETS_MAP entry for renovate_token.txt,
REPO_MAP entry for RENOVATE.md, AUDIT_2026-05-25_TODO new
Sprint 8 with F-15, F-07, F-09 rest, F-12 status, MIGRATION_LOG
captures the four-block sprint in one entry.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 15:29:20 +02:00
Micha e4b0db2af6 Add Komodo bootstrap dry-run scaffold (F-09 rest)
Mirror of the Immich restore-test pattern for the Komodo bootstrap
anchor. Brings up a throwaway komodo-mongo + komodo-core +
komodo-periphery under project restoretest-komodo, isolated from
production:

- same image digests as production (mongo:7.0.32, komodo-core:2,
  komodo-periphery:2) to prove compose-level bootstrap compatibility
- restore-lab paths under /mnt/user/backups/restore-lab/komodo
- 127.0.0.1:19120 only, no LAN bind, no Traefik, no Authelia
- test periphery runs WITHOUT docker.sock mount and WITHOUT
  /mnt/user/services mount; cannot manage productive containers
- KOMODO_* secrets are throwaway placeholders hardcoded in the test
  compose; productive secrets never enter this path

Smoke test: compose config valid, mongo healthy, mongo auth-ping
with test creds, komodo-core HTTP 200/302/303/401, periphery
container running. Report under restore-reports/komodo-bootstrap-*.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 15:25:41 +02:00
Micha 1a4929f9ef Pin monitoring stack images by digest
Reads live RepoDigests of each running monitoring container and
freezes the compose to the exact image manifest. Brings the
monitoring stack to the same digest-pin discipline as the
stateful tier-1 services. influxdb3-core was already pinned.

Affected: prometheus, alertmanager, alertmanager-ntfy-bridge,
blackbox-exporter, loki, promtail, grafana, node-exporter,
cadvisor (plus a second python:3.13-alpine for the bootstrap
dashboard importer).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 15:23:03 +02:00
Micha 2c0076c6a6 Fix vaultwarden + authelia healthcheck commands
Vaultwarden image ships curl, not wget. Switched the CMD-SHELL
test from wget --spider to curl -fsS.

Authelia 4.39.x removed the "helper health-check" subcommand;
use the /api/health endpoint via wget instead (verified inside
the running container).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 15:14:27 +02:00
Micha 7da64ff316 Add healthcheck to Authelia (authelia helper health-check)
Authelia ships its own health-check binary subcommand since 4.37+.
Avoids needing wget/curl in the container.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 15:09:51 +02:00
Micha 12b63531d1 Add healthcheck to Traefik (ping endpoint)
Enable --ping=true and use traefik healthcheck --ping. Lightweight
binary call inside the container, no extra tooling needed.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 15:09:51 +02:00
Micha 3daea94982 Add healthcheck to Gitea (/api/healthz)
Gitea exposes /api/healthz unauthenticated. 60s start_period
because Gitea sqlite migration on cold start can take a while.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 15:09:51 +02:00
Micha 0ca29069c7 Add healthcheck to Vaultwarden (/alive)
Vaultwarden exposes /alive for liveness. wget --spider, 30s
interval, 30s start_period.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 15:09:50 +02:00
Micha eedb08316d Add healthcheck to Redis (redis-cli ping with auth)
Tier-1 health visibility for the shared Redis. Uses redis-cli with
the password from the mounted secret, fails on anything but PONG.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 15:09:50 +02:00
Micha 54a7a0e783 Add healthcheck to postgresql17 (pg_isready)
Tier-1 health visibility for shared Postgres cluster. pg_isready
against the admin DB; 30s interval, 30s start_period.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 15:09:50 +02:00
Micha c677ef0515 Add service removal checklist after stale Borg source finding
Befund vom 2026-05-29: HomelabBorgLastJobCompletedWithWarnings
zuendete vier Tage in Folge mit Borg-Exit-Code 107. Ursache im
Logfile: /local/appdata/homepage wurde am 25.05. entfernt, aber
in der Borg-UI-Source-Liste blieb der Eintrag drin und Borg
warnte taeglich BackupFileNotFoundError. Backups selbst waren
nicht gefaehrdet (alle 23 anderen Quellen sauber archiviert).

Operator hat den Eintrag in der Borg-UI manuell entfernt;
Source-Liste jetzt 23 statt 24, naechster Lauf 2026-05-30 sollte
wieder completed ohne Warning sein.

Erkenntnis: bei Stack-Removal wurde die Borg-Source-Liste nicht
mit-aufgeraeumt. WORKFLOW.md um neuen Abschnitt "Service-Removal-
Checkliste" erweitert mit 9 Pflichtschritten inklusive
Borg-UI-Source-Bereinigung als Schritt 8.

Positiv: die am 2026-05-27 scharfgeschaltete Alert-Pipeline
(Cron Textfile -> node-exporter -> Prometheus -> Alertmanager
-> ntfy-Bridge) hat den Drift binnen 24 h sichtbar gemacht.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 15:01:45 +02:00
Micha 2b60a58753 Activate H drive nearline pull as daily scheduled task
Windows Scheduled Task "KalliLab H Drive Nearline Pull" auf dem
Operator-Windows-PC registriert: taeglich 05:30 nach dem Borg-
Dump-Fenster. RunLevel Limited, StartWhenAvailable, Akku-OK,
Execution-Time-Limit 2h. Naechster Lauf 2026-05-29 05:30.

Repo-Snippet in H_DRIVE_NEARLINE_PULL.md korrigiert: PowerShell-
Enum-Wert ist Limited, nicht LeastPrivilege (alter Snippet haette
beim ersten Register-ScheduledTask einen Parameter-Binding-Fehler
geworfen). Status auf "produktiv" gesetzt.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 20:25:26 +02:00
Micha 7d64248710 Decide against second offsite, keep paperless-gpt and BentoPDF
Operator-Entscheidungen 2026-05-28:

- F-03 zweites Off-site: bewusst NICHT umgesetzt. 3-2-1 ist mit
  Live + lokalem Borg + Hetzner + H:/-Nearline erfuellt; ein
  zweites Off-site deckt nur den Fall "Hetzner-Account verloren"
  ab, Aufwand unverhaeltnismaessig fuer Familien-Homelab.
  Stattdessen drei Folge-TODOs zur Haertung der bestehenden
  Topologie. Hetzner-2FA bewusst ohne (Operator-Praeferenz,
  analog USV-Risiko-Akzeptanz), durch starkes Passwort +
  Backup-Zahlungsweg + Login-Mails ersetzt. Borg-Append-Only-
  Befund: Repo laeuft im Mode 'full', custom_flags leer; Setup
  waere server-seitig in Hetzner-authorized_keys (Folge-Sprint).
  Review-Trigger in OFFSITE_BACKUP_OPTIONS.md dokumentiert.

- paperless-gpt: behalten bis Paperless-NGX 3.0 (erwartete
  native KI-Features). Aktuell 0 Traefik-Zugriffe in 7 Tagen,
  Resource-Footprint 34 MB RAM.

- BentoPDF: behalten als situatives Tool. 0 Traefik-Zugriffe,
  4 MB RAM. Begruendungs-Anker im SERVICE_CATALOG.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 20:19:53 +02:00
Micha edcb34c3f3 Record Plex reclaim and lock to LAN/Tailscale-only
Operator-Befund beim F-17-Versuch: Plex-Server war seit 18.05.
unclaimed (Preferences.xml ohne PlexOnline*) und Library-Sections
leer. Filmdateien unter /mnt/user/media/* blieben unangetastet.

Reclaim als Xeridos via inline PLEX_CLAIM-Env beim docker compose
force-recreate. Token nirgendwo persistiert (kein .env, kein Repo,
keine Komodo-Stack-ENV); zweiter Recreate ohne Token, damit
docker inspect-Snapshot sauber bleibt.

Endstand: PlexOnlineUsername Xeridos, PlexOnlineHome 1,
PublishServerOnPlexOnlineKey 0 (Remote Access aus). Bibliotheken
operator-seitig wieder eingerichtet (/data/movies 1.4 TB,
/data/Heimatfilme 300 GB). Plex bleibt LAN/Tailscale-only,
konsistent zur FRITZBox-Bereinigung vom selben Tag.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 20:06:36 +02:00
Micha 19604e0114 Record FRITZBox WAN cleanup (80/tcp out, VONETS UPnP off)
Operator umgesetzt 2026-05-28:
- 80/tcp aus FRITZBox-UI entfernt; Mobilfunk-validiert: http
  liefert Timeout, https weiter erreichbar.
- 222/tcp bleibt bewusst nicht eingerichtet (Tailscale-only-
  Linie). MASTER Sektion 10 entsprechend praezisiert.
- UPnP-Selbstfreigabe-Recht fuer PC-192-168-178-71 deaktiviert.
  Identifiziert als VONETS-WiFi-Bridge (vermutlich SolarEdge-
  Wechselrichter). SolarEdge-Cloud-Sync ist outbound und
  braucht keine UPnP.

Aktiver WAN-Endstand: ausschliesslich 443/tcp -> 192.168.178.58.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 15:32:10 +02:00
Micha 3c71a66c55 Document monitoring alerts, bundle cron and H/ pull live status
- AUDIT_2026-05-25_TODO: Borg-Stale, Cert-Expiry, Container-Down
  Alerts auf "erledigt" (Cron */5 textfile exporter live,
  Prometheus reload mit 14 Regeln); Gitea-Bundle-Cron auf "erledigt"
  (User-Script gitea-bundle-mirror-6h aktiv, Bundles 644);
  H:/ Nearline-Pull auf "erledigt (Pull live, Scheduled Task offen)"
  mit Zaehlerstaenden 19 Borg-Dumps + 10 Bundle-Files.

- MIGRATION_LOG: neuer Eintrag fasst die drei zusammenhaengenden
  Live-Aktivierungen zusammen, inkl. Befund-Ursprung (Permission-
  Drift), Reparaturen und expliziter Ausklammerung der nicht
  angefassten Themen (Auth, Hermes, USV, FRITZ!Box, Plex).

- H_DRIVE_NEARLINE_PULL: Erstlauf-Befund mit Permission-Issues
  und nachgezogenem Stand; Erwartungs-Liste auf real geliefertes
  Set angepasst; Flash-Config explizit Out-of-Scope.

- pull-critical-backups.ps1: Live-Robocopy-Output an Out-Null,
  damit der Markdown-Report nicht von Robocopy-Strings zerlegt
  wird (PowerShell-Pipeline-Quirk im foreach).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 20:48:04 +02:00
Micha 24d0d90670 Make dump output 0644 by default, exclude flash config from H pull
pre-backup-dumps.sh: atomic_write nimmt jetzt einen optionalen
mode-Parameter (Default 0644). Damit sind alle DB-/SQLite-/BoltDB-
/Mongo-Dumps konsistent 0644 und vom Nearline-Pull lesbar. Die
sensible unraid-flash-config-Familie (.tar.gz, .sha256, .manifest)
ruft explizit mit mode 600 auf und bleibt damit Operator-only.
Loest das Permission-Problem fuer filebrowser.bolt.dump (Source
ist 0640) im naechsten regulaeren Dump-Lauf.

pull-critical-backups.ps1: Jobs koennen ExcludeFiles ueber /XF
mitliefern. borg-dumps-latest schliesst die unraid-flash-config-
Artefakte aus, weil sie bewusst 0600 bleiben sollen und sonst den
Lauf abbrechen lassen. Restore-Quelle fuer Flash-Config bleibt
das Hetzner-Borg-Repo.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 20:44:50 +02:00
Micha 0ae44bd797 Write Prometheus textfile and Gitea bundles world-readable
node-exporter runs as nobody:65534 inside its container and was
hitting node_textfile_scrape_error 1 on homelab.prom, because the
file was 0600 root:root (mktemp default). Set it to 0644 right
before the atomic mv. Bundle inhaltsidentisch zum Git-Repo, ohne
Secrets (.gitignore-abgedeckt) und nicht sensibler als die
uebrigen /mnt/user/backups/borg/dumps/latest/*.dump-Files, die
ebenfalls 0644 sind. So funktioniert auch der Nearline-Pull-Workflow
ueber SMB (docs/H_DRIVE_NEARLINE_PULL.md).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 20:41:07 +02:00
Micha 0723eccca1 Sync repo map, audit TODO and migration log
Pull repo map up to include FAMILY_VIEW_DASHBOARD,
RESTORE_DRILL_ROUTINE, IMMICH_RESTORE_TEST, FRITZBOX_PORT_
CORRECTION_PLAN and OFFSITE_BACKUP_OPTIONS.

Mark Sprint 2 'Komodo bootstrap', Sprint 3 'Family-View',
Sprint 4 'Family onboarding' and Sprint 7 'Quarterly restore drill'
as done with explicit completion notes. Carry the FRITZBox UPnP
finding forward; tag the second offsite item as decision-pending.

Add two doc-only migration log entries for the bootstrap/family/
onboarding/drill sprint and for the FRITZBox/offsite preparation.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 20:21:37 +02:00
Micha 3bfecdd291 Add FRITZBox correction plan and offsite options
Two operator decision documents, doc-only, no live action:

- docs/FRITZBOX_PORT_CORRECTION_PLAN.md prepares the three open
  router items: remove 80/tcp (no HTTP-01 in use), do not add
  222/tcp while Tailscale remains the operator path, deactivate
  the UPnP self-exposure from PC-192-168-178-71. Every step waits
  for operator go.

- docs/OFFSITE_BACKUP_OPTIONS.md compares rsync.net, BorgBase EU2
  and rotating cold disk for a second offsite target. Recommends
  rsync.net or cold disk; BorgBase EU2 is explicitly not
  recommended because it does not separate the provider risk.
  No provider booked, no costs triggered.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 20:21:03 +02:00
Micha c4fd4154db Document quarterly restore drill routine
New docs/RESTORE_DRILL_ROUTINE.md introduces a three-stage model:
weekly freshness check, monthly/bimonthly mini-restores, quarterly
DR sanity check. Tracks confirmed mini-restores (Vaultwarden, Gitea,
Paperless 2026-05-07; Immich 2026-05-27) and rotates services by
quarter Q1-Q4. Includes ten-point DR sanity check and abort rules
that point at the drift runbook. No host schedule is created; the
existing ops/restore-tests/schedule.md now references this routine
as the source for quarterly assignment.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 20:15:43 +02:00
Micha dddb33d900 Finalize family onboarding before invitation
Set status to final pre-invitation, soften the 2FA section to
app-specific 2FA (no SSO promise while Authelia-OIDC stays parked),
add a 'bewusst nicht versprochen' block (no single sign-on, no
24/7 SLA, no hotline support, no data sharing), and refine the
2FA loss guidance.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 20:06:38 +02:00
Micha 8eac93c1a5 Add Family-View dashboard specification
New docs/FAMILY_VIEW_DASHBOARD.md specifies the homelab-family-view
Grafana dashboard: 8 panels covering endpoints up, Borg freshness,
cert days, critical containers, disk usage, endpoint table, cert
table and container status. Includes PromQL queries, thresholds,
layout grid, datasource references, build order and smoke test.
Dashboard JSON is intentionally not created yet because the
Borg-stale / cert-expiry / container-down metrics from Sprint 3
are still pending.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 20:06:30 +02:00
Micha cfa02ce627 Document Komodo bootstrap in linear stages
Add explicit stages A-F to docs/SERVICES_RECOVERY.md: host/docker
baseline, repo source, secrets order, Komodo start, web/GitOps
validation, tier stack rollout. Recovery anchor is ops/komodo/
docker-compose.yml; the self-stack is explicitly not the anchor.
Link DISASTER_RECOVERY Phase 4 stage 3 to the new bootstrap section
and the stack-env-only secrets section in SECRETS_MAP.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 20:01:20 +02:00
Micha 52414c47be Record Immich restore test success 2026-05-27 18:38:14 +02:00
Micha a8c440d4da Read Immich v2 restore counts 2026-05-27 18:33:29 +02:00
Micha 12cf8fb728 Prepare Immich restore upload markers 2026-05-27 18:29:53 +02:00
Micha 5b0782a8fa Harden Immich restore smoke checks 2026-05-27 18:25:30 +02:00
Micha a805f03481 Retry Immich restore during Postgres startup 2026-05-27 18:18:55 +02:00
Micha 4feecf4a8e Make Immich restore database creation idempotent 2026-05-27 18:16:25 +02:00
Micha 2e84700326 Make Immich restore test create database 2026-05-27 18:14:40 +02:00
Micha 8a19c45485 Use Borg known_hosts in restore tests 2026-05-27 18:12:48 +02:00
Micha 6a445094bd Record FRITZBox port exposure drift 2026-05-27 18:06:43 +02:00
Micha fc59e35c57 Record alert metrics host smoke 2026-05-27 06:40:31 +02:00
Micha 8e111d1e04 Prepare monitoring alert rules 2026-05-27 06:38:57 +02:00
Micha 85a0eb4c3a Activate storage layout documentation 2026-05-27 06:31:03 +02:00
Micha 38c3d87722 Prepare H drive nearline pull 2026-05-27 06:25:47 +02:00
Micha c5d231a0db Prepare Immich restore smoke test 2026-05-26 21:33:01 +02:00
Micha 48099fb48d Update audit follow-up documentation 2026-05-26 20:24:50 +02:00
Micha 5c5ca2fcec Fix Gitea bundle mirror host run 2026-05-26 20:16:19 +02:00
Micha 3b438324dc Record UPS risk acceptance 2026-05-26 19:57:00 +02:00
Micha 0625594443 Record offline Borg passphrase backup 2026-05-26 19:53:08 +02:00
Micha 5936a4d9c1 Add Gitea bundle recovery script 2026-05-26 19:50:50 +02:00
109 changed files with 4238 additions and 2957 deletions
+6
View File
@@ -0,0 +1,6 @@
*.sh text eol=lf
*.ps1 text eol=crlf
*.md text
*.json text
*.yml text
*.yaml text
+4 -3
View File
@@ -10,9 +10,10 @@ Claude muss vor jeder fachlichen oder technischen Aenderung mindestens diese Dat
1. `HOMELAB_ARCHITECTURE_MASTER_V2.md` 1. `HOMELAB_ARCHITECTURE_MASTER_V2.md`
2. `docs/WORKFLOW.md` 2. `docs/WORKFLOW.md`
3. `docs/REPO_MAP.md` 3. `docs/README.md`
4. `docs/SERVICE_CATALOG.md` 4. `docs/REPO_MAP.md`
5. die betroffene `docker-compose.yml` 5. `docs/SERVICE_CATALOG.md`
6. die betroffene `docker-compose.yml`
Zusaetzlich je nach Thema: Zusaetzlich je nach Thema:
+26 -3
View File
@@ -271,7 +271,7 @@ Legende Status:
| `immich_server` | ✅ | `immich_default`, `frontend_net` | Traefik | aktiv via `immich.kaleschke.info` | — | | `immich_server` | ✅ | `immich_default`, `frontend_net` | Traefik | aktiv via `immich.kaleschke.info` | — |
| `immich_machine_learning` | ✅ | `immich_default` | intern | bleibt intern | — | | `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 | | `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 ### 7.5 Admin / Operations
@@ -395,7 +395,7 @@ Für den laufenden Betrieb gilt stattdessen:
| `Komodo` | Docker-Socket Zugriff | Stack-Deployments benötigen Socket | | `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 | | `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 | | `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` | | `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 | | `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 | | `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 ## 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) ### Traefik — Wechsel zu reinen Docker-Labels (2026-03-28)
Die statischen File-Provider-Konfigurationen in `/mnt/user/appdata/traefik/dynamic/` wurden vollständig bereinigt: 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` - **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 ohne Redis-Session-Backend (2026-05-04)
- Authelia nutzt PostgreSQL fuer persistente Storage-Daten, aber bewusst kein Redis-Session-Backend. - 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. - 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 ### 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. Bleibt bewusst in `frontend_net` statt `backend_net`, weil `backend_net` `internal: true` ist und ddns-updater die Cloudflare-API erreichen muss.
+9 -7
View File
@@ -8,19 +8,20 @@ Vor jeder Aenderung lesen:
1. `HOMELAB_ARCHITECTURE_MASTER_V2.md` 1. `HOMELAB_ARCHITECTURE_MASTER_V2.md`
2. `docs/WORKFLOW.md` 2. `docs/WORKFLOW.md`
3. `docs/README.md`
Bei Restore-, Host-Ausfall- oder Wiederanlauf-Fragen zusaetzlich: Bei Restore-, Host-Ausfall- oder Wiederanlauf-Fragen zusaetzlich:
3. `docs/DISASTER_RECOVERY.md` 4. `docs/DISASTER_RECOVERY.md`
4. `docs/RESTORE_MATRIX.md` 5. `docs/RESTORE_MATRIX.md`
5. `docs/SERVICES_RECOVERY.md` 6. `docs/SERVICES_RECOVERY.md`
Bei Hardware-, Netzwerk-, Provider- oder Kapazitaetsfragen zusaetzlich: Bei Hardware-, Netzwerk-, Provider- oder Kapazitaetsfragen zusaetzlich:
6. `docs/HARDWARE_INVENTORY.md` 7. `docs/HARDWARE_INVENTORY.md`
7. `docs/NETWORK_INVENTORY.md` 8. `docs/NETWORK_INVENTORY.md`
8. `docs/EXTERNAL_DEPENDENCIES.md` 9. `docs/EXTERNAL_DEPENDENCIES.md`
9. `docs/CAPACITY_AND_LIFECYCLE.md` 10. `docs/CAPACITY_AND_LIFECYCLE.md`
## Architektur ## 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. - 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. - 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 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. - `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 -1
View File
@@ -1,6 +1,6 @@
services: services:
bentopdf: bentopdf:
image: bentopdfteam/bentopdf:2.8.4@sha256:f54b9ed9c56b767e0098b525468206689b666323c2b500b9686c3cf41cdfa348 image: bentopdfteam/bentopdf:2.8.5@sha256:2d867aacb8ab5b196d00ee86944b1899d09d72df355384c5e15cf974737963a0
container_name: bentopdf container_name: bentopdf
restart: unless-stopped restart: unless-stopped
tmpfs: tmpfs:
+4 -3
View File
@@ -43,7 +43,7 @@ services:
redis: redis:
container_name: immich_redis container_name: immich_redis
image: redis:7.4-alpine@sha256:6ab0b6e7381779332f97b8ca76193e45b0756f38d4c0dcda72dbb3c32061ab99 image: redis:8.8.0-alpine@sha256:09160599abd229764c0fb44cb6be640294e1d360a54b19985ab4843dcf2d90f1
restart: unless-stopped restart: unless-stopped
networks: networks:
- immich_default - immich_default
@@ -52,14 +52,15 @@ services:
database: database:
container_name: immich_postgres 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 restart: unless-stopped
environment: environment:
POSTGRES_PASSWORD_FILE: /run/secrets/postgres_password POSTGRES_PASSWORD_FILE: /run/secrets/postgres_password
POSTGRES_USER: immich POSTGRES_USER: immich
POSTGRES_DB: immich POSTGRES_DB: immich
shm_size: 128mb
volumes: 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 - /mnt/user/appdata/secrets/immich_postgres_password.txt:/run/secrets/postgres_password:ro
networks: networks:
- immich_default - immich_default
+1 -1
View File
@@ -1,6 +1,6 @@
services: services:
mail-archiver: mail-archiver:
image: s1t5/mailarchiver@sha256:94d7525db56b13154a14203f8fb7b53fac034f28a914c32da9d2e426b49328ed image: s1t5/mailarchiver@sha256:ea7fd8c2e3e0ef0941e8dd9e726e35a8de33296f5c7b9ed811df5168ae6a9714
container_name: mail-archiver container_name: mail-archiver
restart: unless-stopped restart: unless-stopped
environment: environment:
+4 -4
View File
@@ -1,6 +1,6 @@
services: services:
mealie: 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 container_name: mealie
restart: unless-stopped restart: unless-stopped
@@ -38,7 +38,7 @@ services:
- traefik.http.services.mealie.loadbalancer.server.port=9000 - traefik.http.services.mealie.loadbalancer.server.port=9000
mealie-postgres: mealie-postgres:
image: postgres:17.9@sha256:5b96f1a16bd9768b060dd2ffe55cb6225c4d9ef4d214a8b21eb08134869a97e4 image: postgres:18.4@sha256:8ff36f3c66371cba71d20ceedccfc3de9669a68737607888c4ef0af93abe8e39
container_name: mealie-postgres container_name: mealie-postgres
restart: unless-stopped restart: unless-stopped
@@ -47,10 +47,10 @@ services:
POSTGRES_USER: mealie POSTGRES_USER: mealie
POSTGRES_DB: mealie POSTGRES_DB: mealie
POSTGRES_PASSWORD_FILE: /run/secrets/postgres_password POSTGRES_PASSWORD_FILE: /run/secrets/postgres_password
PGDATA: /var/lib/postgresql/data PGDATA: /var/lib/postgresql/18/docker
volumes: 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 - /mnt/user/appdata/secrets/mealie_postgres_password.txt:/run/secrets/postgres_password:ro
networks: networks:
+5 -5
View File
@@ -1,6 +1,6 @@
services: services:
nextcloud: nextcloud:
image: nextcloud:33.0.2-apache@sha256:39b2ba219271a22851f8409a7b1295d5892aba1696d9193500311c02e60591a4 image: nextcloud:33.0.4-apache@sha256:caa40b8beaf0057ac213d8dfc515c36ce64f7a8f0825b6a287e6f7cf2f4a095d
container_name: nextcloud container_name: nextcloud
restart: unless-stopped restart: unless-stopped
depends_on: depends_on:
@@ -46,7 +46,7 @@ services:
- "traefik.http.services.nextcloud.loadbalancer.server.port=80" - "traefik.http.services.nextcloud.loadbalancer.server.port=80"
nextcloud-postgres: nextcloud-postgres:
image: postgres:17.9@sha256:5b96f1a16bd9768b060dd2ffe55cb6225c4d9ef4d214a8b21eb08134869a97e4 image: postgres:18.4@sha256:8ff36f3c66371cba71d20ceedccfc3de9669a68737607888c4ef0af93abe8e39
container_name: nextcloud-postgres container_name: nextcloud-postgres
restart: unless-stopped restart: unless-stopped
environment: environment:
@@ -54,9 +54,9 @@ services:
POSTGRES_DB: nextcloud POSTGRES_DB: nextcloud
POSTGRES_USER: nextcloud POSTGRES_USER: nextcloud
POSTGRES_PASSWORD_FILE: /run/secrets/postgres_password POSTGRES_PASSWORD_FILE: /run/secrets/postgres_password
PGDATA: /var/lib/postgresql/data PGDATA: /var/lib/postgresql/18/docker
volumes: 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 - /mnt/user/appdata/secrets/nextcloud_postgres_password.txt:/run/secrets/postgres_password:ro
networks: networks:
- nextcloud_internal - nextcloud_internal
@@ -64,7 +64,7 @@ services:
- no-new-privileges:true - no-new-privileges:true
nextcloud-redis: nextcloud-redis:
image: redis:7.4-alpine@sha256:6ab0b6e7381779332f97b8ca76193e45b0756f38d4c0dcda72dbb3c32061ab99 image: redis:8.8.0-alpine@sha256:09160599abd229764c0fb44cb6be640294e1d360a54b19985ab4843dcf2d90f1
container_name: nextcloud-redis container_name: nextcloud-redis
restart: unless-stopped restart: unless-stopped
command: redis-server --save 60 1 --loglevel warning command: redis-server --save 60 1 --loglevel warning
+1 -1
View File
@@ -1,6 +1,6 @@
services: services:
ntfy: ntfy:
image: binwiederhier/ntfy@sha256:2b9e12d56a538f4402da51328eeca02696c4b207ab7fbe031c27e51a22ca9b86 image: binwiederhier/ntfy@sha256:b32b4221a64ec2e7c000f0782b2feef24022e1a09a24e531640f4cbba6cfa1e6
container_name: ntfy container_name: ntfy
restart: unless-stopped restart: unless-stopped
dns: dns:
+11 -8
View File
@@ -1,6 +1,6 @@
services: services:
paperless-gpt: paperless-gpt:
image: icereed/paperless-gpt:v0.24.0@sha256:15bad5d455b98f21bb7b5d6615f56871ff67a8bb379dc0dd7ba411f4633071a6 image: icereed/paperless-gpt:v0.25.1@sha256:c0ce6186028911101a2cfe68353f14a9dbb2653596f3f1cff94de4b6db3114ff
container_name: paperless-gpt container_name: paperless-gpt
restart: unless-stopped restart: unless-stopped
security_opt: security_opt:
@@ -15,15 +15,18 @@ services:
PAPERLESS_API_TOKEN: "${PAPERLESS_API_TOKEN}" PAPERLESS_API_TOKEN: "${PAPERLESS_API_TOKEN}"
MANUAL_TAG: "paperless-gpt" MANUAL_TAG: "paperless-gpt"
AUTO_TAG: "paperless-gpt-auto" AUTO_TAG: "paperless-gpt-auto"
LLM_PROVIDER: "ollama" LLM_PROVIDER: "openai"
LLM_MODEL: "qwen3:8b" LLM_MODEL: "gpt-5.4-mini"
OLLAMA_HOST: "http://192.168.178.103:11434" OPENAI_API_KEY: "${OPENAI_API_KEY}"
OLLAMA_CONTEXT_LENGTH: "4096" OPENAI_BASE_URL: "https://api.openai.com/v1"
TOKEN_LIMIT: "1500" TOKEN_LIMIT: "12000"
LLM_REQUESTS_PER_MINUTE: "30"
LLM_LANGUAGE: "German" LLM_LANGUAGE: "German"
OCR_PROVIDER: "llm" OCR_PROVIDER: "llm"
VISION_LLM_PROVIDER: "ollama" VISION_LLM_PROVIDER: "openai"
VISION_LLM_MODEL: "minicpm-v:latest" VISION_LLM_MODEL: "gpt-5.4-mini"
VISION_LLM_TEMPERATURE: "1.0"
VISION_LLM_REQUESTS_PER_MINUTE: "20"
OCR_PROCESS_MODE: "image" OCR_PROCESS_MODE: "image"
CREATE_NEW_TAGS: "true" CREATE_NEW_TAGS: "true"
AUTO_GENERATE_TITLE: "true" AUTO_GENERATE_TITLE: "true"
+1 -1
View File
@@ -1,6 +1,6 @@
services: services:
paperless: 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 container_name: paperless-ngx
restart: unless-stopped restart: unless-stopped
security_opt: security_opt:
+1 -1
View File
@@ -1,6 +1,6 @@
services: services:
unbound: unbound:
image: shaanmajid/unbound:1.24.2@sha256:d278b71c592b2555cc802911bb0757a6a24f4a8ad7f5848720296c04876eeb63 image: shaanmajid/unbound:1.25.1@sha256:96809ff052e8bd79bba30e067d8b27ed9a2f069b6b2a3484fe1d0eb45aba07c5
container_name: unbound container_name: unbound
restart: unless-stopped restart: unless-stopped
volumes: volumes:
+7 -1
View File
@@ -1,6 +1,6 @@
services: services:
gitea: gitea:
image: docker.gitea.com/gitea:1.25.4@sha256:17d18218be2dad1f8ed402a4f906989505c90ab8b66ee9befcecfb5d470133e7 image: docker.gitea.com/gitea:1.26.2@sha256:7d13848af12645600a5f9d93ee2560daa9c6fa6b5b859b7bff3a5e1c0b661031
container_name: gitea container_name: gitea
restart: unless-stopped restart: unless-stopped
security_opt: security_opt:
@@ -26,6 +26,12 @@ services:
- "222:22" - "222:22"
networks: networks:
- frontend_net - 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: labels:
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.docker.network=frontend_net" - "traefik.docker.network=frontend_net"
+44 -194
View File
@@ -1,213 +1,63 @@
# AI Context # AI Context
Stand: 2026-05-04 Stand: 2026-06-01
Diese Datei ist fuer KI-Agenten gedacht, die das Homelab-Repo schnell verstehen muessen. Sie ersetzt nicht die Detaildokumente, sondern fasst Zielbild, Betriebsmodell und Risiken zusammen. Kurzer Kontext fuer KI-Agenten. Nicht als Ersatz fuer die echten Runbooks lesen.
## Kurzfassung ## Systembild
Dieses Repository beschreibt ein Unraid-basiertes Homelab namens `Kallilabcore`. Der Betrieb folgt GitOps: Gitea `origin/master` ist die Quelle der Wahrheit, der lokale Clone ist Arbeitskopie, Komodo deployed aus Gitea, Docker Runtime und Host sind Ergebnis, nicht Bearbeitungsort. - Host: Unraid `Kallilabcore`
- Betriebsmodell: GitOps mit Gitea `origin/master` als Sollzustand
- Deploy: Komodo zieht aus Gitea und startet Compose-Stacks
- Ingress: Traefik; WAN-seitig bewusst nur `443/tcp`
- Secrets: nie im Repo, meist unter `/mnt/user/appdata/secrets/`
- Backup: Borg plus host-seitige Dumps; Hetzner ist Offsite, H:/ ist lokale Nearline-Kopie
Traefik ist der zentrale Web-Einstieg fuer HTTP(S). Admin-/Ops-UIs liegen entweder hinter Authelia/secure headers oder sind als Ausnahme dokumentiert. Secrets liegen ausserhalb des Repos auf dem Host, meistens unter `/mnt/user/appdata/secrets/`. ## Vor jeder Aenderung lesen
## Zielbild des Homelabs 1. `HOMELAB_ARCHITECTURE_MASTER_V2.md`
2. `docs/WORKFLOW.md`
3. betroffene Compose-Datei
4. bei Service-Fragen `docs/SERVICE_CATALOG.md`
5. bei Restore/DR `docs/DISASTER_RECOVERY.md` und `docs/RESTORE_MATRIX.md`
- stabile Compose-first Infrastruktur ## Harte Regeln
- keine produktiven Dockerman-/Ad-hoc-Container als Dauerzustand
- Traefik als einziger oeffentlicher Web-Eingang
- GitOps ueber Gitea + Komodo
- klare Trennung von Web-Netz, Backend-Netz und app-internen Netzen
- saubere Backup-/Restore-Faehigkeit ueber Borg, Dumps und dokumentierte Pfade
- keine stillen Host-Hotfixes ohne Repo-/Doku-Abgleich
## Architekturblocke
### Ingress / Netzwerk
- `traefik` nimmt 80/443 entgegen.
- Docker-Labels definieren Service-Routing.
- `traefik/dynamic/*` bleibt nur fuer Middlewares, TLS und Dashboards.
- Neue Service-Routen gehoeren nicht in den File-Provider.
### DNS / Remote
- AdGuard Home beantwortet LAN-DNS und nutzt Unbound als Upstream.
- Tailscale stellt Remote-Zugang bereit und nutzt `network_mode: host`.
### GitOps
- Gitea hostet das Repo unter `git.kaleschke.info`.
- Komodo ist Stack-Manager und Deploy-Consumer.
- Komodo Periphery braucht Docker-Socket und `/mnt/user/services` Mount, um Stacks reproduzierbar zu deployen.
- Neue produktive Komodo-Stacks aus `Micha/homelab-infra` muessen einen aktiven Gitea->Komodo-Webhook auf die aktuelle Stack-ID haben; Ausnahmen wie deaktivierte/pausierte Stacks muessen dokumentiert werden.
- Der `komodo`-Self-Stack ist eine dokumentierte Ausnahme ohne aktiven Gitea-Webhook; Bootstrap/Recovery laeuft ueber `docs/SERVICES_RECOVERY.md`.
### Identity / Security
- Authelia stellt ForwardAuth fuer viele Admin-UIs bereit.
- Authelia nutzt GMX SMTP fuer Identity-/2FA-Benachrichtigungen; Passwort liegt als Host-Secret `authelia_smtp_password.txt`.
- Vaultwarden ist ein separater Passwort-Tresor.
- Komodo ist bewusst nicht pauschal hinter Authelia, weil UI, API, Webhooks und Periphery-WebSocket sonst leicht gebrochen werden koennen.
- Komodo-Compose, Komodo-Secrets und Komodo-Runtime nur gemeinsam mit dem Betreiber aendern; `KOMODO_WEBHOOK_SECRET` ist bewusst getrennt von `KOMODO_SECRET_KEY`. Gitea-Webhooks nicht pauschal vereinheitlichen: einzelne Komodo-Stacks koennen eigene per-Stack-Webhook-Secrets haben.
### Apps
Wichtige Apps sind Paperless, Immich, Mealie, Mail Archiver, Nextcloud, ntfy, Vaultwarden und Gitea. Admin-/Ops-Tools sind u. a. Glance, Komodo, Borg UI, Filebrowser, code-server, Glances, Scrutiny, Speedtest, Monitoring Grafana und Hermes Agent.
### Hermes Agent — Architektur und Ops-Monitor
Hermes laeuft nach Model C (siehe `ops/hermes-agent/README.md`):
- `hermes-gateway` als Docker-Container auf dem Unraid-Host, intern auf `hermes_net:8642`
- Terminal-Befehle werden via SSH auf eine dedizierte Linux-VM ausgefuehrt
- VM-IP: `192.168.178.143`, SSH-User: `hermes`
- Repo-Clone auf der VM: `/srv/hermes-workspace/homelab-infra/`
**Fuer KI-Agenten wichtig:** Das Hermes-Terminal laeuft auf der VM, nicht auf dem Unraid-Host.
`/mnt/user/...`-Pfade sind von der VM aus nicht direkt erreichbar.
Docker-CLI ist auf der VM nicht installiert — fuer Homelab-Checks wird `check_health.py` verwendet.
**Ops-Monitor (homelab-ops-monitor):**
- Skill: `ops/hermes-agent/skills/homelab-ops-monitor.md`
- Script: `ops/hermes-agent/scripts/check_health.py` — prueft Services via HTTP, keine externen Deps
- Wissensbasis: `ops/hermes-agent/services.json` — maschinenlesbare Ableitung aus `docs/SERVICE_CATALOG.md`
- Check-Strategie: HTTP GET fuer URL-basierte Services, interne Services (DBs, Redis) als `"internal"` markiert
- ntfy-Topic fuer Alerts: `homelab-alerts` auf `https://ntfy.kaleschke.info`
Nach Aenderungen an `services.json` oder `check_health.py`: `git pull` auf der VM ausfuehren.
- Mail Archiver ist ein Hybrid-Dienst mit `frontend_net` fuer IMAP/Traefik und `backend_net` fuer PostgreSQL; die Web-UI liegt hinter Authelia und behaelt zusaetzlich App-eigene Auth.
### Monitoring / Metriken
- Zielzustand ist ein zentraler Stack `monitoring/` unter `https://monitoring.kaleschke.info`.
- `monitoring-grafana` ist die zentrale UI fuer Prometheus, Loki und InfluxDB 3 Core.
- `monitoring-prometheus` sammelt Infrastruktur-Metriken; `monitoring-loki` + `monitoring-promtail` sammeln Docker-Logs.
- `monitoring-influxdb3-core` ist nicht public und nicht im `frontend_net`.
- Home Assistant schreibt ueber LAN-only Port 8181 nach InfluxDB, gebunden ueber `INFLUXDB_BIND_IP`.
- Ein `401 Unauthorized` von InfluxDB ohne Token ist beim Reachability-Test ein Erfolgssignal.
- Die frueheren Altstaende `ops/loki` und `ops/grafana-influxdb` wurden aus dem aktiven Repo entfernt; fuer Monitoring immer `monitoring/` verwenden, Rollback nur ueber Git-Historie.
- Uptime Kuma ist entfernt; HTTP-Verfuegbarkeit laeuft ueber Blackbox Exporter, Prometheus-Alerts und `Homelab / Availability` in Monitoring Grafana.
## Deployment-Logik
Normalfall:
1. lokaler Clone synchronisieren
2. betroffene Dokumente und Compose-Datei lesen
3. minimal aendern
4. lokal validieren
5. committen und pushen
6. Komodo-Webhook / Deploy beobachten
7. Runtime testen
8. Doku aktualisieren
Wichtig: Komodo-Web-Editor ist nicht der Bearbeitungsort. Wenn Komodo und Git voneinander abweichen, zuerst Git und Komodo Workspace pruefen, nicht live herumprobieren.
Beim Anlegen neuer produktiver Stacks ist der Gitea->Komodo-Webhook Pflicht. Nach dem Anlegen muss ein Test-Push oder Test-Delivery zeigen, dass Gitea die aktuelle Komodo-Stack-ID erreicht.
## Netzwerkmodell
| Netzwerk | Bedeutung |
|---|---|
| `frontend_net` | Web-/Proxy-Netz fuer Traefik-geroutete Dienste und Dienste mit Internetbedarf |
| `backend_net` | internes Netz fuer shared PostgreSQL, Redis und Backends |
| `dns_net` | AdGuard + Unbound |
| app-interne Netze | Isolation von App + DB/Cache, z. B. Immich, Mealie, Nextcloud, Monitoring |
| `host` | nur dokumentierte Sonderfaelle wie Tailscale/Plex |
Regeln:
- Keine Secrets zitieren oder ins Repo schreiben.
- Keine produktiven Host-Hotfixes ohne Repo-Abgleich.
- Datenbanken nie ins `frontend_net`. - Datenbanken nie ins `frontend_net`.
- Admin-UIs nur mit Traefik + Middleware oder dokumentierter Ausnahme. - Direkte Host-Ports sind Ausnahme.
- Direkte Host-Ports sind Ausnahme, nicht Default. - Traefik dynamic config und Authelia Host-Config sind manuelle Sync-Ausnahmen.
- Runtime-Netznamen koennen Compose-Projektpraefixe bekommen, z. B. `monitoring_monitoring_influx_lan`. - Bei Drift zuerst Git, Gitea, Komodo Workspace, Docker Runtime und Host getrennt pruefen.
- Nach zwei fehlgeschlagenen Reparaturversuchen stoppen und `docs/GITOPS_DRIFT_RUNBOOK.md` nutzen.
## Security-Modell ## Bekannte Ausnahmen
- Secrets nie ins Git. - Traefik: Host-Ports 80/443, WAN-Freigabe nur 443
- Werte niemals zitieren, auch nicht aus `.env`, Stack ENV, Logs oder Screenshots. - Gitea: SSH auf Host-Port 222, keine WAN-Freigabe
- Secret-Dateien bevorzugt unter `/mnt/user/appdata/secrets/`. - AdGuard: DNS 53 direkt; Admin nur auf Tailscale-IP `100.80.98.33:8082`
- `_FILE`-Varianten bevorzugen, falls Image sie unterstuetzt. - Tailscale und Plex: Host-Netz
- Wenn `_FILE` nicht unterstuetzt wird, Komodo Stack Environment Variables verwenden.
- Docker-Socket, `privileged: true`, Host-Netz und breite Mounts sind nur mit dokumentierter Begruendung akzeptabel.
Bekannte Ausnahmen:
- Traefik: 80/443
- Gitea: SSH 222
- AdGuard: DNS 53 direkt; Admin 8082 ist bewusst ohne Traefik/2FA, aber auf Tailscale-IP `100.80.98.33` begrenzt
- Tailscale: Host-Netz, `NET_ADMIN`, `NET_RAW`, `/dev/net/tun`
- Scrutiny: privileged - Scrutiny: privileged
- Komodo: Docker-Socket, native Auth - Komodo/Periphery: Docker-Socket-Zugriff
- InfluxDB: LAN-only 8181 fuer Home Assistant Writer - InfluxDB 3 Core: `127.0.0.1:8181`, Root-User-Ausnahme dokumentiert
- `monitoring-influxdb3-core`: `user: "0"` als dokumentierte Host-Appdata-Permissions-Ausnahme
- Traefik dynamic config: manueller Host-Sync
## Backup- und Restore-Modell ## Aktuelle Restpunkte
Borg sichert kritische Appdaten, Secrets, Traefik-State und Dump-Artefakte. Datenbank-Restore soll bevorzugt ueber Dumps laufen, nicht ueber rohe Live-DB-Verzeichnisse. Authoritativ: `docs/AUDIT_2026-05-25_TODO.md`.
Wichtige Pfade: Kurzfassung:
- `/mnt/user/appdata` - Alt-Volumes fruehestens ab 2026-06-02 freigeben
- `/mnt/user/appdata/secrets` - Auth-/OIDC-/CrowdSec-/Hermes-Themen bewusst geparkt
- `/mnt/user/services`
- `/mnt/user/documents`
- `/mnt/user/photos`
- `/mnt/user/backups/borg/dumps/latest`
Dump-Skript: Letzte Bestaetigung:
- `ops/borg-ui/scripts/pre-backup-dumps.sh` - Borg-Nachlauf 2026-06-01 erfolgreich: Archiv `Taegliche-Sicherung-2026-06-01T04:30:26.913`, Freshness Critical 0 / Warnings 0.
- soll auf dem Unraid Host laufen - H:/ Nearline-Pull 2026-06-01 repariert: Borg-Dumps werden kuratiert kopiert, Gitea-Bundles aktuell.
- soll nicht als Borg-UI Inline-Hook behandelt werden, solange die Architektur nicht bewusst geaendert wird - Family-Status-Dashboard liegt als `monitoring/grafana/dashboards/family-status.json` im Repo.
- Alt-Volume-Freigabe ist per `ops/maintenance/release-alt-volumes.sh` vorbereitet; `--execute` nicht vor 2026-06-02.
Disaster Recovery folgt einer Bootstrap-Reihenfolge: - Family-Onboarding ist auf drei Nutzungsziele fokussiert: Vaultwarden, Immich und Mealie; praktischer Ablauf in `docs/FAMILY_ONBOARDING.md`.
- Externer Betreibercheck: `ops/maintenance/check-external-operator.sh`; FRITZ!Box 7590 meldet FRITZ!OS `154.08.25`, DNS fuer Public Apps hat keine AAAA-Records, Host hat keine globale Provider-IPv6.
1. Traefik, AdGuard, Tailscale - FRITZ!Box-UI 2026-06-01: Remote-HTTPS auf FRITZ!Box-UI aus, FTP/FTPS auf Speichermedien aus, WAN-Freigabe nur `443/tcp`, keine aktive IPv6-Freigabe sichtbar, UPnP-Selbstfreigaben aus.
2. PostgreSQL, Authelia, Redis, Gitea - FRITZ!Box-Konfig-Backup 2026-06-01 extern/off-system in Vaultwarden abgelegt; Datei und Kennwort bleiben ausserhalb des Repos.
3. Komodo - Hetzner-Account-Hygiene 2026-06-01 erledigt: 2FA aktiv, Recovery Key offline gedruckt, Zahlung ok; Storage Box SSH-only, Maintenance-Key in Vaultwarden. Append-only forced-command brach Key-Auth und wurde per Passwort-Recovery zurueckgesetzt; Operator-Entscheidung: fuer dieses Homelab bewusst nicht umsetzen.
4. kritische Apps
5. restliche Apps/Ops inklusive Hermes Agent
## Typische Arbeitsweise im Repo
- Fuer Fragen zuerst `HOMELAB_ARCHITECTURE_MASTER_V2.md` lesen.
- Fuer operative Aenderungen `docs/WORKFLOW.md` lesen.
- Fuer Service-Details `docs/SERVICE_CATALOG.md` und die Compose-Datei lesen.
- Fuer Drift `docs/GITOPS_DRIFT_RUNBOOK.md` nutzen.
- Fuer Rollback `docs/ROLLBACK.md` nutzen.
- Fuer Restore `docs/DISASTER_RECOVERY.md` und `docs/RESTORE_MATRIX.md` nutzen.
KI-Agenten sollen konservativ arbeiten: keine indirekten Live-Aenderungen, keine Deployments, keine Commits, keine Host-Schreibbefehle, wenn der Benutzer nur Analyse oder Doku verlangt.
## Bekannte Risiken und Altlasten
- Traefik dynamic config muss manuell auf den Host synchronisiert werden; Komodo deployed diese Dateien nicht automatisch.
- `backend_net` und app-interne Netze muessen bei Runtime-Problemen live geprueft werden, weil Compose-Projektpraefixe Netznamen veraendern koennen.
- 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 OIDC-/Secret-Konfiguration enthalten. Bei Auth-Aenderungen Repo-Baseline, Host-Config und Compose-Middlewares pruefen und nicht blind ueberschreiben.
- Authelia nutzt PostgreSQL, aber bewusst kein Redis-Session-Backend; Redis ist kein Authelia-Bootstrap-Blocker.
- Authelia-Notifier ist SMTP; bei Auth-Aenderungen Host-Config backupen, `authelia validate-config` ausfuehren und erst danach neu starten.
- `paperless-ngx` nutzt fuer DB/Redis bewusst Stack ENV statt `_FILE`.
- `glance-docker-socket-proxy`, `glances` und `komodo-periphery` nutzen Docker-/Socket-Zugriff; Zugriff bewusst behandeln.
- `borg-ui` und `filebrowser` haben breite Mounts; bei Hardening nicht ad hoc, sondern gezielt vorgehen.
- `scrutiny` ist privilegiert und hat Device-Mounts.
- `Plex-Media-Server` ist im Architekturziel als Host-Sonderfall dokumentiert, aber nicht als Repo-Compose-Stack enthalten.
- Echte `stack.env`- und `.env`-Dateien gehoeren nicht ins Repo; fuer Hermes liegt nur `ops/hermes-agent/stack.env.example` im Git.
- Einige Images nutzen mutable Tag plus Digest. Das friert den aktuellen Digest ein, ist aber kein automatisches Upgrade-Modell.
- Stateful Images werden bevorzugt als Minor-/Patch-Tag plus Digest gepinnt; Redis-Caches bleiben bewusst ungedigestet.
## Arbeitsregel bei Unsicherheit
Nicht raten. Erst diese Reihenfolge:
1. Repo-Doku lesen
2. Compose-Datei lesen
3. Git-Stand pruefen
4. Komodo Workspace pruefen
5. Docker Runtime pruefen
6. Host-Listener / echten Request pruefen
7. genau eine abweichende Ebene benennen
Wenn zwei Reparaturversuche scheitern: keine weiteren Schreibbefehle, Pflichtmatrix aus `docs/GITOPS_DRIFT_RUNBOOK.md` ausfuellen.
-80
View File
@@ -1,80 +0,0 @@
# AI Handoff 2026-05-06
Kompakte Quelle fuer einen neuen Chat. Ziel: nicht das ganze Repo neu auditieren, sondern mit dem bekannten Stand weiterarbeiten.
## Aktueller Stand
- Repo: `G:\Gitea_Clone\homelab-infra`
- Remote: `https://git.kaleschke.info/Micha/homelab-infra.git`
- Branch: `master`
- Letzter bekannter Commit: `e0e12f1 Document stale Komodo webhook cleanup`
- Unraid-Host: `ssh root@192.168.178.58`
- Push-Befehl, der zuverlaessig funktioniert: `git -C "G:\Gitea_Clone\homelab-infra" push origin master`
- Nicht anfassen ohne explizite Freigabe: untracked `Homelab_Audit_2026-05-05.pdf` und untracked `ops/hermes-agent/services.yaml`.
## Audit-Arbeit Erledigt
- K1: ungueltige Digests fuer Authelia, ntfy und borg-ui korrigiert und smoke-getestet.
- K2: Authelia nutzt bewusst kein Redis; Doku entsprechend korrigiert.
- K3/M1/M2 alt: Authelia Repo-Baseline geklaert, Homepage/Komodo ACL-Drift bereinigt.
- M3a/M3b: Digest-Pinning fuer stateful/Tier-1 und weitere versionierte Apps umgesetzt; Redis-Caches bewusst ohne Digest, Nextcloud bewusst offen.
- M5/N5: `.gitignore` eingefuehrt, Hermes `stack.env` zu `stack.env.example`.
- M6/M7/M8: Hermes-Domain, Grafana/Influx `user: "0"` und Tailscale-Capabilities dokumentiert.
- M9: Backup Scope / Restore Matrix erledigt.
- N-Aufraeumen: alte Compose-`version:` Felder, leere Env-Beispiele und `.keep`-Platzhalter bereinigt.
- Mail Archiver: `mail.kaleschke.info` liegt hinter `authelia@file,secure-headers@file`; Smoke-Test war 302 zu Authelia.
- Hermes: Restore/DR-Doku ergaenzt.
- Authelia SMTP: GMX SMTP eingerichtet, validiert, deployed und smoke-getestet.
- M10: `KOMODO_WEBHOOK_SECRET` ist von `KOMODO_SECRET_KEY` getrennt.
## Wichtige Runtime-Details
### Authelia SMTP
- Adresse: `submission://mail.gmx.net:587`
- Mailkonto: `michideheld@gmx.de`
- SMTP-Passwort liegt nur auf dem Host: `/mnt/user/appdata/secrets/authelia_smtp_password.txt`
- Host-Config wurde vor Umstellung gesichert: `/mnt/user/appdata/authelia/config/configuration.yml.bak-20260506-smtp`
- Authelia-Compose nutzt explizite DNS-Server, weil der SMTP-Startup-Check externe Namen aufloesen muss.
- Nach Deploy war `authelia` healthy; `auth.kaleschke.info` antwortete 200, geschuetzte Routen 302 zu Authelia.
### Komodo / M10
- Komodo-Runtime nur gemeinsam mit dem Betreiber aendern.
- `KOMODO_SECRET_KEY` wurde nicht geaendert.
- `KOMODO_WEBHOOK_SECRET` wurde geaendert und ist jetzt eigener 64-Zeichen-Wert.
- Neuer Wert liegt nur auf dem Host in `/mnt/user/services/stacks/komodo/.env`.
- Komodo Compose auf Host: `/mnt/user/services/stacks/komodo/compose.yaml`.
- Backups vom M10-Sprint:
- `/mnt/user/appdata/komodo/_m10_backup_20260506-184838`
- `/mnt/user/services/gitea/data/gitea/_m10_backup_20260506-184838/gitea.db.bak`
- `komodo-core` wurde gezielt recreated.
- `komodo-mongo` wurde nicht neu gestartet.
- `komodo-periphery` lief durch und meldete sich wieder am Core-Websocket an.
- Gitea-Komodo-Webhooks: 29 aktive Hooks, 29 zuletzt erfolgreich, 0 aktiv fehlgeschlagen.
- Ein stale Gitea-Webhook auf eine nicht mehr existierende Komodo-Stack-ID wurde deaktiviert, nicht geloescht.
- Eine Warnung `request branch does not match expected` ist ein Branch-Filter-Skip, kein Secret-/Auth-Fehler.
- Fuer neue Gitea-Webhooks im Standardfall den globalen `KOMODO_WEBHOOK_SECRET` aus der Komodo-Host-`.env` nutzen, ausser Komodo zeigt fuer den Stack explizit ein eigenes per-Stack-Secret.
## Sicherheitsregeln Fuer Weitere Arbeit
- Keine Secret-Werte im Chat oder Git ausgeben.
- Bei Host-Pruefungen nur SET/MISSING, Laengen und Pfade zeigen.
- Komodo-Compose, Komodo-Secrets und Komodo-Runtime nur bewusst und kleinschrittig aendern.
- Bei jedem Deploy pro Stack smoke-testen; nicht mehrere kritische Stacks parallel veraendern.
- Untracked Dateien nicht automatisch committen.
- Bei Authelia-Aenderungen: Host-Config sichern, `authelia validate-config` ausfuehren, dann erst neu starten.
- Bei Komodo-Aenderungen: Gitea-Webhooks und Komodo-Core-Secret-Seite zusammen betrachten.
## Naechste Sinnvolle Next-Level-Themen
- Grafana/Influx rootless betreiben statt `user: "0"`; eigener Sprint wegen Volume-Rechten.
- Restore-Test fuer Vaultwarden und Paperless dokumentiert durchfuehren.
- Komodo Periphery von Legacy-Passkey auf Public-Key-Modell haerten.
- Monitoring/Alerting reifer machen: externe Alarme, Restore-Test-Reminder, Backup-Erfolg sichtbar.
- Gitea/Komodo Webhook-Landschaft weiter aufraeumen und per-Stack-Secret-Strategie dokumentieren.
- DR-Test fuer `backend_net`/externe Docker-Netze explizit aufnehmen.
## 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.
-31
View File
@@ -1,31 +0,0 @@
# Alerting Map
Stand: 2026-05-23
Ziel: Alle problemrelevanten Homelab-Meldungen landen auf einem Handy-Topic.
## ntfy Topics
| Topic | Zweck |
|---|---|
| `homelab-alerts` | Alles, was Aufmerksamkeit braucht: Prometheus, Docker-Events, Posture, Zertifikate/Token, Compose-Drift, Borg-Pre-Hook-Fehler und Restore-Fehler |
| `homelab-info` | Optionale Erfolgsmeldungen, z. B. erfolgreiche Restore-Testlaeufe |
## Sender
| Sender | Pfad | Problem-Topic | Hinweis |
|---|---|---|---|
| Prometheus / Alertmanager | `monitoring/alertmanager/alertmanager.yml`, `monitoring/alertmanager-ntfy-bridge/bridge.py` | `homelab-alerts` | Zentrale Monitoring-Alerts via Bridge |
| Posture Check | `services/posture-check/posture-check.sh` | `homelab-alerts` | Warning und Critical gehen auf dasselbe Handy-Topic |
| Cert / Token Check | `services/posture-check/cert-token-check.sh` | `homelab-alerts` | Prueft produktive HTTPS-Domains und Cloudflare Token |
| Compose Runtime Drift | `services/posture-check/compose-runtime-drift.sh` | `homelab-alerts` | Meldet Abweichungen zwischen Repo-Compose und Runtime-Image |
| Docker Critical Events | `services/posture-check/docker-critical-events.sh` | `homelab-alerts` | Meldet Docker `die`, `oom` und `kill` Events |
| Borg Pre-Hook | `ops/borg-ui/scripts/pre-borg.sh` | `homelab-alerts` | Meldet Fehler vor Borg, z. B. Posture-, Dump- oder Restore-Freshness-Fehler |
| Restore Jobs | `ops/restore-tests/run-restore-job-with-ntfy.sh` | `homelab-alerts` | Erfolg geht an `homelab-info`, Fehler immer an `homelab-alerts` |
## Konvention
- `NTFY_BASE_URL` zeigt standardmaessig auf `https://ntfy.kaleschke.info`.
- Neue Problem-Alerts sollen `homelab-alerts` nutzen.
- Erfolgsmeldungen sind optional und sollen nicht in `homelab-alerts` landen, ausser sie sind bewusst als Lebenszeichen gewuenscht.
- Blackbox-Endpoint-Alerts sollen bekannte WAN-/Provider-Sammelausfaelle zusammenfassen, damit kurze DSL-Reconnects keine ntfy-Flut pro Domain erzeugen.
+52
View File
@@ -0,0 +1,52 @@
# Alert Rules
Stand: 2026-05-31
Diese Datei beschreibt die produktiven Alarmwege und wichtigsten Regeln. Die
Konfiguration selbst liegt in `monitoring/prometheus/alerts.yml` und in den
Skripten unter `services/posture-check/`.
## Alarmwege
| Weg | Quelle | Ziel |
|---|---|---|
| Prometheus / Alertmanager | `monitoring/prometheus/alerts.yml` | ntfy `homelab-alerts` |
| Posture Check | `services/posture-check/posture-check.sh` | ntfy `homelab-alerts` |
| Cert / Token Check | `services/posture-check/cert-token-check.sh` | ntfy `homelab-alerts` |
| Compose Runtime Drift | `services/posture-check/compose-runtime-drift.sh` | ntfy `homelab-alerts` |
| Docker Critical Events | `services/posture-check/docker-critical-events.sh` | ntfy `homelab-alerts` |
| Borg Pre-Hook | `ops/borg-ui/scripts/pre-borg.sh` | ntfy `homelab-alerts` |
| Restore Jobs | `ops/restore-tests/run-restore-job-with-ntfy.sh` | Fehler `homelab-alerts`, Erfolg `homelab-info` |
## Prometheus-Regeln
| Alarm | Ausloeser | Severity | Aktion |
|---|---|---|---|
| `HomelabExternalConnectivityDown` | mindestens 5 HTTP-Ziele down | warning | WAN/DNS/Provider pruefen, nicht jede Domain einzeln jagen |
| `HomelabEndpointDown` | einzelnes HTTP-Ziel down | critical | Dienst, Traefik-Route und Backend pruefen |
| `HomelabEndpointSlow` | Endpoint >5s | warning | Dienstlast oder Backend-Latenz pruefen |
| `HomelabCertificateExpiresSoon` | Cert <21 Tage | warning | ACME/Traefik-Renewal beobachten |
| `HomelabCertificateExpiresCritical` | Cert <=7 Tage | critical | Renewal sofort pruefen |
| `HomelabDiskAlmostFull` | Filesystem >85% | warning | Platz schaffen oder Schwelle pruefen |
| `HomelabDiskCritical` | Filesystem >95% | critical | Sofort Platz schaffen |
| `HomelabHighMemoryUsage` | MemAvailable <10% | warning | Speicherfresser identifizieren |
| `HomelabTraefik5xx` | >=5 5xx je Service in 5 Minuten | warning | betroffenes Backend pruefen |
| `HomelabTextfileExporterStale` | Textfile-Exporter >2h alt | warning | Host-Cron pruefen |
| `HomelabBorgMetricsMissing` | Borg-Metrik fehlt | critical | Textfile-Exporter oder Borg-UI pruefen |
| `HomelabBorgBackupStale` | letztes Borg-Backup >30h | warning | Backup-Lauf nachholen/pruefen |
| `HomelabBorgLastJobFailed` | letzter Borg-Job fehlgeschlagen | critical | Borg-UI-Job-Log pruefen |
| `HomelabBorgLastJobCompletedWithWarnings` | letzter Borg-Job mit Warnungen | warning | Warnung im Borg-UI-Job lesen |
| `HomelabCriticalContainerDown` | kritischer Container fehlt | critical | Komodo/Docker-Status pruefen |
| `HomelabPrometheusTargetDown` | Scrape-Ziel down | critical | node-exporter/cadvisor/blackbox/traefik pruefen |
Die Liste der ueberwachten Critical-Container steht in
`services/posture-check/export-prometheus-textfile.sh`.
## Bekannte Luecken
- Kein externer Dead-Man's-Switch fuer Prometheus/ntfy-Bridge. Optional spaeter
ueber Uptime-Kuma Push-Monitor oder Healthchecks.io.
- Kein Inode-Alarm. Bei Paperless/Immich spaeter sinnvoll, aber aktuell kein
dokumentierter Vorfall.
- Container-Memory-Limits werden erst nach realen Peak-Daten gesetzt; OOM/kill
wird bereits ueber `docker-critical-events.sh` gemeldet.
-369
View File
@@ -1,369 +0,0 @@
# 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. 97139 (`dump_sqlite_container`), Z. 253258 (Nextcloud `pg_dump`), Z. 261264 (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. 2325). |
| **(2) Filebrowser entschaerfen** — `/mnt/user/appdata:/srv/appdata` weg, gezielte RW-Subpfade | offen | ✅ erledigt | `ops/filebrowser/docker-compose.yml` Z. 1116. 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. 1725. 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 | 12 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 | 12 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/<stack-id>/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.
-22
View File
@@ -1,22 +0,0 @@
# Homelab Audit Final - 2026-05-23
Stand: 2026-05-25 07:33 CEST. Ergebnis nach Push, Live-Messung, Doku-Sync, Repo-Hygiene und erneuter Live-Nachmessung.
| 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. |
| 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. |
| 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
- Keine offenen Punkte aus der Audit-Restliste.
- Nicht Teil des Audits: Nach Disk1 Phase 2 laeuft seit 2026-05-25 eine nicht korrigierende Parity-Pruefung (`NOCORRECT`) im Hintergrund; Zwischenstand der Nachmessung: `mdResyncAction=check P`, `mdResyncCorr=0`.
## Schlussbewertung
Das Homelab ist fuer die Audit-Restliste in der Endstufe. Es gibt keine kritischen Repo-, GitOps- oder Live-Befunde aus diesem Audit. Monitoring ist produktiv und ready, alte Altstaende sind down, Backups und Dumps sind frisch.
-88
View File
@@ -1,88 +0,0 @@
# 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.
-895
View File
@@ -1,895 +0,0 @@
# 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`.
## 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:
- Jellyfin entfernt (MASTER 7.8)
- Homepage entfernt (MASTER 7.8)
- Uptime-Kuma entfernt (MASTER 7.8, SECRETS_MAP 29)
- Monitoring-Stack produktiv (AUDIT_FINAL 9), Altstaende-Container down
- Disk1 NTFS -> XFS Phase 2 abgeschlossen am 2026-05-25 (STORAGE_LAYOUT 3)
- Unraid-Flash-Backup live (`unraid-flash-config.tar.gz` im Borg-Lauf)
- GitHub-Push-Mirror `michaelkaleschke-spec/homelab-infra` aktiv (DR 10, MASTER 7.1)
Diese geloesten Punkte werden hier nicht wiederholt. Dieser Audit konzentriert sich auf das, was nach dem 23.05.-Sprint **noch offen ist** und auf das, was die strategische Bewertung **nicht oder nur kurz angerissen** hat.
---
# Phase 1: Repo-Inventar
## Ordnerstruktur (Ist-Zustand)
```
homelab-infra/
├── apps/ 9 Stacks (bentopdf, immich, mail-archiver, mealie, nextcloud, ntfy, paperless, paperless-gpt, unbound)
├── core/ 1 Stack (gitea)
├── docs/ 28 Markdown-Dokumente
├── env/ 2 *.example
├── host-services/ 3 Stacks (Adguard, plex, tailscale)
├── infra/ 3 Stacks (ddns-updater, postgresql17, redis)
├── monitoring/ 1 Compose mit 10 Services + Provisioning
├── ops/ 17 Verzeichnisse (Semaphore, borg-ui, code-server, filebrowser, glance, glances, grafana-influxdb [Altstand], hermes-agent, komodo, loki [Altstand], policy-checks, restore-tests, scrutiny, speedtest, uptime-kuma [Altrest], windows-reinstall)
├── security/ 2 Stacks (authelia, vaultwarden)
├── services/ 1 posture-check (Host-Skripte)
└── traefik/ 1 Compose + dynamic/ (3 Files)
```
**Inventar-Befund:**
- ~30 Compose-Dateien, 1 zentraler Compose-Multi-Service (`monitoring/`).
- 29 Composes wurden vom Policy-Checker validiert (`ops/policy-checks/last-report.md`): **0 Critical, 4 Warnings, 9 Info**.
- Doku-Dichte ist hoch (REPO_MAP, SERVICE_CATALOG, RESTORE_MATRIX, DISASTER_RECOVERY, SECRETS_MAP, WORKFLOW, STORAGE_LAYOUT, GITOPS_DRIFT_RUNBOOK, ALERTING_MAP).
- Restore-Tests sind als echte Scripts versioniert (`ops/restore-tests/`). Ueberdurchschnittlich.
## Gut dokumentierte Bereiche (Belegt)
| Bereich | Quelle |
|---|---|
| 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` |
| Secret-Inventur | `docs/SECRETS_MAP.md` |
| Alert-Pfade | `docs/ALERTING_MAP.md` |
## Luecken / Unklar (vermutet bzw. Rueckfrage noetig)
| 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/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. |
| Familien-/User-Onboarding-Doku | Keine Doku, die deine Frau/Kinder lesen muessten ("So loggst du dich in Nextcloud ein"). Aktuell ist alles Operator-Doku. |
## Fuer den Audit besonders wichtige Dateien (verwendet)
- `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`
- `ops/policy-checks/last-report.md`
- `monitoring/docker-compose.yml`, `monitoring/prometheus/alerts.yml`
- `traefik/docker-compose.yml`, `traefik/dynamic/middlewares.yml`
- `security/authelia/configuration.yml`, `security/authelia/docker-compose.yml`
- `apps/paperless/docker-compose.yml`, `apps/immich/docker-compose.yml`, `apps/nextcloud/docker-compose.yml`
- `host-services/Adguard/docker-compose.yml`
---
# A. Executive Summary
**Was schon stark ist:**
- GitOps-Disziplin: Gitea als Sollzustand, Komodo als Consumer, dokumentierter Drift-Runbook, Stop-Regel ("zwei Fehlversuche -> Pflichtmatrix"). Seltene Reife.
- Backup-Architektur: Pre-Backup-Dumps + Borg + Restore-Tests mit echtem Smoke-Test-Kriterium ("Container startet ≠ Erfolg"). 15 frische Dumps < 24 h alt (`AUDIT_LIVE 9.4`).
- Architektur-Klarheit: `frontend_net` / `backend_net` / app-interne Netze, keine Sammelnetze, dokumentierte Ausnahmen.
- Image-Pinning: Tier-1-Stateful mit `<minor>@sha256:...`. Konsequent durchgezogen.
- Secrets-Hygiene: Keine Secret-Werte im Repo, `_FILE`-Mounts + Komodo Stack ENV, explizit dokumentierte Ausnahmen.
- Policy-as-Code: `check_repo.ps1` mit 0 Critical und sauber dokumentierten Exceptions.
**Was kritisch ist:**
- **AdGuard Admin-Port 8082 ohne Authelia/2FA am LAN gebunden** (`host-services/Adguard/docker-compose.yml:16`) — dokumentierte "Operator-Entscheidung" 2026-05-25. Im Heim-LAN tolerierbar, mit IoT/Gaeste-WLAN potenziell ein Pfad zur DNS-Manipulation. Niedrigster Aufwand: Bind nur auf Tailscale-Interface.
- **Authelia ACL: 2FA nur fuer `files.kaleschke.info` und `scrutiny.kaleschke.info`** (`security/authelia/configuration.yml:44-48`). Borg-UI, Code-Server, Filebrowser, Glance — alles nur `one_factor`. Bei Pwd-Kompromittierung des Operator-Accounts ist Borg-UI + Code-Server der direkteste Pfad zur Datenexfiltration.
- **Authelia-Repo-Baseline ↔ Host-Config-Drift "by design"** (`docs/REPO_MAP.md:48`, `SERVICE_CATALOG 23`). User-DB, OIDC-Clients und Secrets sind hostseitig, Manual-Merge-Pflicht. Stelle, an der Drift mit Anlauf passiert.
- **Komodo Self-Bootstrap-Problem ist nur dokumentiert, nicht geloest** (MASTER 13: Self-Stack Drift-Recovery 2026-05-04). Bei Recovery vom kalten Host musst du Komodo aus `compose.yaml` neu erzeugen — dafuer brauchst du die `.env` mit `KOMODO_*`-Secrets, die nur auf Host und ggf. Vaultwarden liegen.
- **Backup-Off-Site-Diversitaet:** BorgBase Hetzner ist Single-Provider; Borg-Passphrase analog gesichert ist als TODO markiert (`docs/DISASTER_RECOVERY.md:64,401`). Wenn Hetzner-Account verloren geht, ist das halbe DR-Versprechen weg.
**Was unnoetig komplex ist:**
- Drei dokumentierte Monitoring-/Logging-Pfade gleichzeitig im Repo: `ops/grafana-influxdb` (Altstand), `ops/loki` (Altstand), `monitoring/` (Ziel). Die Altstaende sind als Container down, aber **Verzeichnisse noch im Repo** — Doppelpflege-Risiko. Der versprochene Repo-Cleanup (`git rm`) fehlt.
- Hermes-Agent: NAS-Stack bewusst deaktiviert ("VM-seitig offen"), aber Stack-Verzeichnis und Compose mit Dashboard-Domain bleiben im Repo. Mehr "Schwebezustand" als operativer Wert.
- BentoPDF: "vorbereitet", noch nie produktiv abgenommen (`SERVICE_CATALOG 52`, `MASTER 7.5`).
- `infra/redis` ist als shared Cache deklariert, wird de facto nur von Paperless genutzt (Authelia nicht, Immich/Nextcloud/Mealie haben eigene Redis). Das "shared" stimmt im Repo nicht mit der Realitaet ueberein.
**Groesster Hebel:**
**Authelia OIDC-Provider aktivieren** — wenn Nextcloud, Immich, Grafana (und perspektivisch Mealie via OIDC-Bridge) per SSO laufen, gewinnst du gleichzeitig:
- (a) Familien-Onboarding-Komfort (ein Login),
- (b) zentrale Brute-Force-Regulation und Audit,
- (c) Voraussetzung fuer sinnvolles CrowdSec/Fail2Ban,
- (d) zentrale 2FA-Pflicht statt App-by-App.
Das ist ein Sprint, nicht ein Quartal, und macht aus deinem "Admin-Authelia" ein echtes Identity-System.
**Was ein erfahrener Homelabber sofort aendern wuerde:**
1. AdGuard-Admin-Port nur auf Tailscale-Interface binden (5 Min, Compose-Edit).
2. Borg-Passphrase auf Papier in Bankschliessfach (15 Min, off-system).
3. `scrutiny` und `ddns-updater` `no-new-privileges` Warning aufraeumen (10 Min) — kosmetisch, aber Policy-Check sollte clean sein.
4. Altstaende `ops/grafana-influxdb/` und `ops/loki/` aus Repo entfernen (Backup-Branch dann `git rm`).
5. Renovate-Bot gegen Gitea einrichten — beendet manuelle Digest-Pflicht.
---
# B. Scorecard (1 = exzellent, 10 = ungenuegend)
| Bereich | Note | Begruendung |
|---|---:|---|
| Hardware | **nicht bewertbar** | Keine Inventar-Doku im Repo. Nur Cache-NVMe genannt. Siehe Phase H. |
| Ordnerstruktur | **2** | Klare Trennung apps/infra/ops/security/core; konsistente Namenskonvention (mit Migrationsplan in STORAGE_LAYOUT 6). Kleinerer Haenger: `host-services/Adguard/` mit Grossbuchstabe. |
| Storage | **3** (Repo-Stand) / **2** (mit STORAGE_LAYOUT Active) | Cache-XFS, Disk1-XFS jetzt erreicht. Pfad-Disziplin via `/mnt/user/...`. Posture-Check etabliert. Note durch Draft-Status und fehlendes `SERVICES_RECOVERY.md` gedrueckt. |
| Docker-Architektur | **2** | Netzmodell klar, Healthchecks fehlen grossflaechig, `latest@sha256` als bewusster Kompromiss bei einigen Images dokumentiert. Keine Memory-Limits. |
| Reverse Proxy / Zugriff | **2** | DNS-Challenge, Wildcard-faehig, Authelia ForwardAuth, dynamic config sauber getrennt. Manuelle Host-Sync-Ausnahme ist pragmatisch. |
| Security | **3** | Solides Fundament (Authelia Argon2id, no-new-privileges-Standard, Webhook-Allowlist, `cloudflare_dns_api_token` als Docker Secret), aber: nur 2 Domains mit 2FA, AdGuard-Admin direkt am LAN, kein WAF/Bouncer, Authelia Regulation 5-Min-Ban ist gentil. |
| Netzwerk / DNS | **2** | AdGuard + Unbound + Tailscale-Trias ist Best-of-Class-Homelab. FritzBox als Router nicht im Repo dokumentiert, daher Note nicht 1. |
| Backup | **2** | Borg, Pre-Dumps, Tier-Modell, dokumentierter Scope. Punkt-Abzug: Single-Provider Off-Site, Passphrase nicht analog, Komodo-Mongo-Dump-Verifikation nicht im Auto-Cron. |
| Restore-Faehigkeit | **2** | RESTORE_MATRIX mit Smoke-Test je Dienst, Restore-Test-Schedule + Validierungen fuer Vaultwarden/Gitea/Paperless dokumentiert. Punkt-Abzug: Immich-Restore noch nie geuebt — groesster Datentopf. |
| GitOps | **1-2** | Webhook-Pflicht fuer neue Stacks, Source-of-Truth-Hierarchie, Drift-Runbook. Self-Bootstrap-Problem von Komodo zieht von 1 auf 2. |
| Monitoring | **3** | Stack produktiv, aber Altstaende noch im Repo, Family-View-Dashboard fehlt, Alerts (`alerts.yml`) sehr knapp (5 Regeln), keine Cert-Expiry-Alert auf Prometheus-Ebene (Cert-Token-Check laeuft separat). |
| Dokumentation | **1** | Aussergewoehnlich. SERVICE_CATALOG ist Gold. Einziger Punkt: kein End-User-/Familien-Onboarding. |
| Automatisierung | **3** | Borg-Dumps automatisiert, posture-check Host-Cron, Alert->ntfy-Pipe. Aber: keine CI gegen Repo, kein Renovate, kein automatisches Image-Update-Tracking. |
| Wartbarkeit | **2** | Doku traegt; Sprintpflege-Disziplin sichtbar (MIGRATION_LOG, AUDIT_FINAL). Risiko: Authelia-Drift, Hermes-Schwebezustand. |
| Nerd-Faktor | **2-** | Komodo + Borg-UI + ntfy-Bridge + Posture-Check + Restore-Lab + Hermes-Experiment + Push-Mirror + Digest-Pinning. Liegt zwischen "Solider Senior" und "Spielwiese halten lernen". |
**Gesamteindruck: 2 (gut).** Strukturell weit ueber durchschnittlichem Homelab; konkrete Luecken sind klar benennbar und nicht systemisch.
---
# C. Top-20 Findings
> Format: priorisiert nach Risiko-zu-Aufwand-Hebel. Jeder Eintrag hat Fundstelle, Empfehlung und Prio.
### F-01 · AdGuard-Admin am LAN ohne Auth
- **Kategorie:** Security / Zugriff
- **Fundstelle:** `host-services/Adguard/docker-compose.yml:16`, `MASTER 10`, `docs/SERVICE_CATALOG.md:14`
- **Beobachtung:** Port `8082:80` direkt auf alle Interfaces. Bewusste Operator-Entscheidung 2026-05-25.
- **Risiko:** Jedes Geraet im LAN kann DNS-Filterregeln, Upstream und Logging manipulieren. IoT-Kompromittierung oder Gast-WLAN -> DNS-Hijack moeglich.
- **Best Practice:** Admin-UIs nicht im LAN ohne Auth. Entweder hinter Traefik+Authelia mit `two_factor` oder Bind auf Tailscale-Interface (z. B. `100.x.y.z:8082:80`).
- **Empfehlung:** Schritt 1 — Bind auf Tailscale-IP (S, 5 Min). Schritt 2 — optional spaeter Traefik-Route hinter Authelia.
- **Prioritaet:** Sollte zeitnah
- **Aufwand:** S
- **Validierung:** `ss -ltnp | grep :8082` zeigt nur Tailscale-IP; LAN-Browser-Zugriff schlaegt fehl.
- **Rollback:** Compose-Diff zurueck, Komodo redeploy.
### F-02 · Borg-Passphrase nicht analog gesichert
- **Kategorie:** Backup / DR
- **Fundstelle:** `docs/DISASTER_RECOVERY.md:64,401`, `docs/SECRETS_MAP.md:48`
- **Beobachtung:** `borg_repo_passphrase.txt` liegt im Host-Filesystem unter `/mnt/user/appdata/secrets/`. Doku weist explizit darauf hin, dass eine externe analoge Sicherung Operator-Aufgabe ist.
- **Risiko:** Wenn Unraid-Host und ggf. Vaultwarden gleichzeitig defekt sind, ist das verschluesselte Borg-Repo bei Hetzner nutzlos.
- **Empfehlung:** Auf Papier ausdrucken, in Bankschliessfach oder bei vertrauter Person versiegelt. Zusaetzlich in Vaultwarden hinterlegen (aber Vaultwarden hilft nicht, wenn es selbst restauriert werden muss).
- **Prioritaet:** Muss sofort
- **Aufwand:** S
- **Validierung:** Du kannst den Wert ohne Host wiederherstellen.
### F-03 · Single-Provider Off-Site Backup
- **Kategorie:** Backup
- **Fundstelle:** `ops/borg-ui/BACKUP_SCOPE.md`, `docs/RESTORE_MATRIX.md:77-96`, `STORAGE_LAYOUT 8.1`
- **Beobachtung:** Hetzner Storage Box als alleiniges Off-Site-Borg-Ziel. STORAGE_LAYOUT 8.1 sieht zusaetzlich lokales Borg-Repo auf `/mnt/user/backups/borg/` vor (gleicher Host) und eine externe Wechselplatte (manuell rotiert).
- **Risiko:** Hetzner-Account-Verlust (Payment-Issue, Account-Hack, Provider-Outage) = halbes 3-2-1.
- **Best Practice:** Zweites Off-Site-Ziel mit unterschiedlichem Provider oder Cold-Wechselplatte mit fester Rotationskadenz.
- **Empfehlung:** (a) Wechselplatten-Rotation in fester Kadenz dokumentieren (zwei Platten, monatlicher Tausch). Oder (b) zweites Borg-Repo bei rsync.net / BorgBase EU2 / privatem 2. Standort.
- **Prioritaet:** Sollte zeitnah
- **Aufwand:** M
- **Validierung:** `borg list` gegen beide Repos, beide < 7 Tage alt.
### F-04 · Authelia 2FA-Pflicht zu schmal
- **Kategorie:** Security
- **Fundstelle:** `security/authelia/configuration.yml:44-53`
- **Beobachtung:** Nur `files.kaleschke.info` und `scrutiny.kaleschke.info` sind `two_factor`. Tier-1-Operator-UIs wie Borg-UI, Code-Server, Filebrowser (zweite Route?), Komodo (eigene Auth), Glance, Grafana laufen mit `one_factor`.
- **Risiko:** Operator-Passwort-Kompromittierung (Phishing, Keylogger, Browser-Save-Leak) gibt ohne 2FA Vollzugriff auf Code-Server (Repo + Workspace), Borg-UI (Restore-Pfade), Filebrowser (Documents/Photos).
- **Empfehlung:** ACL erweitern: `two_factor` fuer `borg.kaleschke.info`, `code.kaleschke.info`, `files.kaleschke.info` (schon), `glance.kaleschke.info` (debattierbar), `traefik.kaleschke.info`. Komodo bleibt Ausnahme.
- **Prioritaet:** Muss sofort
- **Aufwand:** S
- **Validierung:** Nach Login auf `borg.kaleschke.info` wird 2FA-Prompt erzwungen.
- **Rollback:** ACL-Block zurueck.
### F-05 · Repo-Altstaende `ops/grafana-influxdb/` und `ops/loki/` nicht entfernt
- **Kategorie:** Wartbarkeit / GitOps
- **Fundstelle:** Repo-Wurzeln `ops/grafana-influxdb/`, `ops/loki/`; `MASTER 7.6`, `SERVICE_CATALOG 68-70,80-81`, `AUDIT_FINAL 9`
- **Beobachtung:** Container down, aber Compose-Dateien + Provisioning bleiben im Repo. Doku referenziert beide gleichzeitig. Risiko: jemand (zukuenftiges Ich, KI) deployt versehentlich den Altstand.
- **Empfehlung:** `git rm` der beiden Verzeichnisse, Tag `pre-monitoring-cleanup` fuer Rollback, MIGRATION_LOG-Eintrag.
- **Prioritaet:** Sollte zeitnah
- **Aufwand:** S
- **Validierung:** `policy-checks` laeuft clean, Repo enthaelt nur noch `monitoring/`.
### F-06 · Hermes-Agent im Schwebezustand
- **Kategorie:** App-Landschaft / Wartbarkeit
- **Fundstelle:** `ops/hermes-agent/docker-compose.yml`, `MASTER 7.5`, `SERVICE_CATALOG 82-83`
- **Beobachtung:** "NAS-Stack bewusst deaktiviert" wegen offener VM-Seite. Dashboard-Domain (`hermes.kaleschke.info`) + Authelia-ACL + Secret-Pfade dokumentiert.
- **Risiko:** Schleichender Verfall — in 6 Monaten verstehst du Model-C nicht mehr ohne `ops/hermes-agent/README.md`. Bei jeder Authelia-/Compose-Aenderung musst du Hermes mitpruefen, obwohl es nichts tut.
- **Empfehlung:** Operator-Entscheidung mit 60-Tage-Deadline ehrlich treffen. Wenn nicht produktiv bis 2026-07-25: `git rm ops/hermes-agent/`, Domain aus DNS, ACL-Eintrag raus.
- **Prioritaet:** Sollte zeitnah (Entscheidung)
- **Aufwand:** S (Entfernen) / L (echte Produktiv-Aktivierung)
- **Validierung:** Entweder Smoke-Test auf `hermes.kaleschke.info` mit funktionalem Use-Case-Beleg, oder Repo-clean.
### F-07 · Monitoring-Stack ohne Digest-Pin
- **Kategorie:** Reproduzierbarkeit / GitOps
- **Fundstelle:** `monitoring/docker-compose.yml:3,28,66,84,100,118,276,296`
- **Beobachtung:** Prometheus, Alertmanager, Blackbox, Loki, Promtail, Grafana, node-exporter, cAdvisor sind alle nur per Tag gepinnt (`prom/prometheus:v3.7.3`, `grafana/grafana:12.4.3`, ...). Nur `influxdb3-core` hat `@sha256:`. Das widerspricht der Image-Versionierungs-Disziplin der Tier-1-Stateful-Dienste.
- **Risiko:** Wenn upstream einen Tag erneut pushed (Versionsdrift, Supply Chain), wird beim Rebuild ein anderer Container deployed — gerade Monitoring sollte stabil sein.
- **Empfehlung:** Beim naechsten Komodo-Redeploy aktuellen Digest auslesen und einpinnen. Vorbereitung fuer Renovate (F-12).
- **Prioritaet:** Nice to have
- **Aufwand:** S
- **Validierung:** `grep '@sha256' monitoring/docker-compose.yml` listet alle 10 Services.
### F-08 · Alert-Regeln zu duenn
- **Kategorie:** Monitoring
- **Fundstelle:** `monitoring/prometheus/alerts.yml`
- **Beobachtung:** Exakt 5 Regeln: ExternalConnectivityDown, EndpointDown, EndpointSlow, DiskAlmostFull, MemoryHighUsage, Traefik5xx. Es fehlen:
- Borg-Lauf-Frische (ueber `node_exporter` textfile collector oder Pushgateway).
- Zertifikatslaufzeit (Blackbox kann `probe_ssl_earliest_cert_expiry`, aber keine Alert-Regel dafuer).
- Container-down-Alert (cAdvisor liefert `container_last_seen`).
- PostgreSQL-Connection-Saturation.
- Loki ingestion-rate / log-volume spike.
- InfluxDB-Disk-Pressure.
- Backup-Job-Failure.
- **Risiko:** Du siehst Probleme nicht, bevor sie weh tun. Cert-Expiry und Borg-Stale sind die schmerzhaftesten Blind-Spots.
- **Empfehlung:** Mindestens zwei Regeln nachziehen: `BorgArchiveStale` (>30 h, Pushgateway oder textfile) und `TLSCertExpiryNear` (<14 Tage). Rest als Folge-Sprint.
- **Prioritaet:** Sollte zeitnah
- **Aufwand:** M
- **Validierung:** Alerts feuern in Test-Bedingung (Borg-Dump-File touch -d backwards).
### F-09 · Komodo-Self-Bootstrap-Problem
- **Kategorie:** GitOps / DR
- **Fundstelle:** `MASTER 13: Komodo Self-Stack Drift-Recovery 2026-05-04`
- **Beobachtung:** Du hattest schon einen Drift-Vorfall (Komodo-Core ran aus `/tmp/*repair.yml`, Mongo-Pfad fehlte). Recovery-ENV liegt als "temporaeres Tier-1-Secret-Material" unter `/mnt/user/appdata/secrets/_komodo_stack_env_recovery_2026-05-04.env` (Doku-Stand).
- **Risiko:** Bei Totalausfall musst du Komodo aus Compose-Datei wiederbeleben, dafuer brauchst du die Stack-ENV mit `KOMODO_SECRET_KEY`, `KOMODO_MONGO_PASSWORD`, `KOMODO_PERIPHERY_PASSKEY` etc., die nur als Komodo Stack ENV existieren. Klassisches Henne-Ei.
- **Empfehlung:** Komodo-Self-Stack aus Komodos eigener Verwaltung herausnehmen und als handgepflegten `docker compose`-Service in `services/komodo-bootstrap/` halten. Stack-ENV als versiegelte Datei unter `/mnt/user/appdata/secrets/` mit deterministischem Restore-Pfad in RESTORE_MATRIX.
- **Prioritaet:** Sollte zeitnah
- **Aufwand:** M
- **Validierung:** Komodo-Stack-Datei lebt im Repo unter `services/`, nicht in Komodos eigener Workspace-Sicht.
### F-10 · Authelia Repo↔Host Drift "by design"
- **Kategorie:** GitOps / Security
- **Fundstelle:** `docs/REPO_MAP.md:48`, `security/authelia/configuration.yml`, `SERVICE_CATALOG 23`
- **Beobachtung:** Repo enthaelt Baseline ohne Secrets, OIDC, Users-DB. Manuelles Merge auf den Host noetig. Es gibt keine automatische Konsistenz-Pruefung.
- **Risiko:** Repo-Aenderung (z. B. neue ACL-Regel) wird gepusht, aber nie auf den Host gemerged -> Drift, Authelia hinkt der Wahrheit hinterher.
- **Empfehlung:** Symmetrisch zum Traefik-Dynamic-Workflow (manueller Sync explizit als Pflicht in WORKFLOW.md). Zusaetzlich ein einfaches Diff-Script `services/authelia-diff.sh`, das `diff` zwischen Repo-Baseline und Host-Datei zeigt, und das im posture-check als Warning auftaucht, wenn die ACL-Sektion differiert.
- **Prioritaet:** Sollte zeitnah
- **Aufwand:** S
- **Validierung:** Script laeuft, posture-check kennt einen neuen Check `authelia_config_drift`.
### F-11 · Immich-Restore noch nie geuebt
- **Kategorie:** Backup / Restore
- **Fundstelle:** `docs/RESTORE_MATRIX.md:49`, Restore-Test-Schedule
- **Beobachtung:** Vaultwarden / Gitea / Paperless haben Mini-Restore-Tests (2026-05-07). Immich nicht. Immich ist der groesste Datentopf (Familien-Fotos).
- **Risiko:** Silent Corruption in Postgres-pgvecto-rs-Daten bemerkst du erst beim Restore-Versuch.
- **Empfehlung:** Eigener Sprint: Immich-Restore-Test gegen `/mnt/user/backups/restore-lab/immich/` mit Sub-Set der `immich.dump` und einem Foto-Sample. Smoke-Test = "10 Fotos im Browser sichtbar nach Restore".
- **Prioritaet:** Sollte zeitnah
- **Aufwand:** M
- **Validierung:** Report unter `/mnt/user/backups/restore-reports/immich-<datum>.json`.
### F-12 · Keine Image-Update-Automatik (Renovate o. ae.)
- **Kategorie:** Wartbarkeit
- **Fundstelle:** Repo-weit; `docs/WORKFLOW.md:282-288` (Image-Versionierung)
- **Beobachtung:** Digest-Pinning ist konsequent, aber rein manuell. Bei ~30 Images bedeutet das, du musst monatlich fuer Patch-Updates manuell Digests auslesen — oder es bleibt liegen.
- **Risiko:** CVE-Patches werden nicht eingespielt, weil "der laufende Stand ist stabil".
- **Empfehlung:** Renovate Bot (self-hosted, gegen Gitea), Gitea-Actions-Runner. Renovate oeffnet PRs fuer Patch-/Minor-Updates; Major-Updates werden mit Labels separiert.
- **Prioritaet:** Sollte zeitnah (oder Nice to have, je nach Schmerz)
- **Aufwand:** M (Initial-Setup ist substantiell)
- **Validierung:** Renovate hat erste PRs in Gitea geoeffnet, du mergst eines davon kontrolliert.
### F-13 · Keine OIDC-SSO fuer User-Apps
- **Kategorie:** Security / UX
- **Fundstelle:** `security/authelia/configuration.yml`, `docs/SECRETS_MAP.md`
- **Beobachtung:** Authelia kann OIDC, ist aber nur als ForwardAuth konfiguriert. Nextcloud, Immich, Grafana, Mealie laufen mit eigenen User-DBs.
- **Risiko:** N getrennte Passwortspeicher, N getrennte 2FA-Setups, keine zentrale Sperrung bei Account-Kompromittierung. Familie hat keinen einfachen Onboarding-Pfad.
- **Empfehlung:** OIDC-Provider in Authelia aktivieren, Nextcloud (via Plugin), Immich (nativer OIDC-Support), Grafana (nativer OIDC-Support) als Clients konfigurieren. Vaultwarden via OIDC-Bridge nur, wenn der Aufwand klar mehrwertig ist — sonst bewusst auslassen.
- **Prioritaet:** Sollte zeitnah (groesster Hebel laut Executive)
- **Aufwand:** L
- **Validierung:** Familienkonto kann sich mit einem Login bei Nextcloud + Immich + Grafana anmelden.
### F-14 · Kein WAF / Bouncer vor oeffentlichen Apps
- **Kategorie:** Security
- **Fundstelle:** `traefik/docker-compose.yml`, oeffentliche Hosts in `docs/REPO_MAP.md:127-152`
- **Beobachtung:** Sechs oeffentliche Apps mit nativer Auth (vault, paperless, mealie, ntfy, git, immich, cloud) ohne IP-Bouncer. Authelia-Regulation greift nur fuer die ForwardAuth-Pfade; Apps mit eigener Auth bekommen den vollen Traffic.
- **Risiko:** Credential-Stuffing-Bot-Wellen treffen die App selbst (Nextcloud, Immich) — Logs sind im Loki, aber kein Sperr-Mechanismus.
- **Empfehlung:** CrowdSec als Bouncer fuer Traefik (`crowdsecurity/traefik-bouncer`). Nutzt Loki/Logs fuer Erkennung, sperrt IPs auf Traefik-Ebene, bevor sie die Apps treffen.
- **Prioritaet:** Sollte zeitnah
- **Aufwand:** M
- **Validierung:** CrowdSec-Dashboard zeigt erste Sperren; Test-Brute-Force gegen `nextcloud.kaleschke.info` wird bei N Versuchen geblockt.
### F-15 · Healthchecks fehlen grossflaechig
- **Kategorie:** Docker / Operations
- **Fundstelle:** Spot-checks: `apps/paperless/docker-compose.yml`, `apps/immich/docker-compose.yml`, `security/authelia/docker-compose.yml`, `traefik/docker-compose.yml` — keiner hat `healthcheck:`-Block.
- **Beobachtung:** Restart-Policy ist ueberall `unless-stopped`, aber ohne Healthcheck kann Docker keinen Crash-Loop bei "Container laeuft, aber App tot" erkennen.
- **Risiko:** Bei Soft-Failure (Postgres-Connection-Pool tot, Authelia haengt im Storage-Connect) merkst du nichts, weil Container "running" bleibt.
- **Empfehlung:** Fuer Tier-1 (Traefik `wget /ping`, Authelia `/api/health`, PostgreSQL `pg_isready`, Komodo `wget /api/healthcheck`) Healthchecks ergaenzen. Fuer Tier-2 schrittweise.
- **Prioritaet:** Sollte zeitnah
- **Aufwand:** M (pro Stack 515 Min)
- **Validierung:** `docker ps` zeigt `(healthy)` neben den Tier-1-Containern.
### F-16 · `infra/redis` als "shared" deklariert, faktisch nur Paperless
- **Kategorie:** Architektur-Konsistenz
- **Fundstelle:** `infra/redis/docker-compose.yml`, `docs/SERVICE_CATALOG.md:31` ("shared Redis Cache")
- **Beobachtung:** Immich, Nextcloud, Mealie haben jeweils eigene Redis-Instanzen. Authelia nutzt bewusst kein Redis (MASTER 13). Paperless nutzt es laut Compose. Effektiv "Paperless-Redis im Frack des shared-Caches".
- **Risiko:** Niedrig. Aber: Wenn du `infra/redis` fuer etwas anderes wegnimmst, denkst du, es kostet Paperless was — und das waere der Fall.
- **Empfehlung:** Doku-Update: SERVICE_CATALOG 31 praezisieren ("dediziertes Redis fuer Paperless; andere Stacks haben eigene Redis-Instanzen"). Architektur bleibt, nur Etikett ehrlich machen. Alternativ: in `apps/paperless/` als App-internes Netz konsolidieren wie Mealie.
- **Prioritaet:** Nice to have
- **Aufwand:** S (Doku) / M (Architektur)
### F-17 · Plex bleibt als Host-Net-Stack
- **Kategorie:** Security / Architektur
- **Fundstelle:** `host-services/plex/docker-compose.yml`, `MASTER 7.4`
- **Beobachtung:** Plex laeuft als Host-Net wegen Discovery/GDM. Dokumentierte Ausnahme.
- **Risiko:** Plex hat hoehere Angriffsoberflaeche als Apps mit Traefik. Plex-Login wurde mehrfach Ziel von Account-Uebernahmen (Plex.tv-Auth-Issues 2024/25). Bei Plex.tv-Kompromittierung greift Authelia nicht — Plex authentifiziert gegen Plex.tv.
- **Empfehlung:** Plex bewusst beibehalten (Doku stuetzt), aber: (a) "Remote Access" in Plex-UI deaktivieren, wenn nur lokal/Tailscale genutzt. (b) Plex-Server nicht in `frontend_net` (waere schaedlich) — bleibt Host-Net, korrekt.
- **Prioritaet:** Nice to have
- **Aufwand:** S
- **Validierung:** Plex `Remote Access` UI zeigt "disabled".
### F-18 · Nextcloud ohne ForwardAuth, ohne dedizierte Brute-Force-Doku
- **Kategorie:** Security
- **Fundstelle:** `apps/nextcloud/docker-compose.yml`, `MASTER 13: Nextcloud-Entscheidung`
- **Beobachtung:** Bewusste Ausnahme (WebDAV/CardDAV). In Nextcloud selbst sind Brute-Force-Schutz, 2FA-Pflicht und App-Passwords konfigurierbar, aber nicht im Repo dokumentiert.
- **Risiko:** Familien-Konto mit schwachem Passwort + Nextcloud oeffentlich = direkter Pfad zu Dokumenten/Fotos.
- **Empfehlung:** `apps/nextcloud/POST_INSTALL.md` mit Pflicht-Checkliste: Brute-Force-Plugin aktiv, 2FA-Provider TOTP installiert, Admin-Account hat 2FA, "Enforce 2FA for admin group" gesetzt. Optional: `OCC`-Befehle als Skript in `services/nextcloud-policy/`.
- **Prioritaet:** Sollte zeitnah
- **Aufwand:** S
- **Validierung:** Test-Login ohne 2FA als Admin schlaegt fehl.
### F-19 · Keine Container-Memory-Limits
- **Kategorie:** Docker / Hardware-Schutz
- **Fundstelle:** Spot-checks aller Composes
- **Beobachtung:** Keine `mem_limit:` oder `deploy.resources.limits` Sektion in Tier-1- oder Tier-2-Stacks.
- **Risiko:** Bei Image-Bug oder Memory-Leak (z. B. Immich-ML, Paperless-OCR-Loop) kann ein Container den Host in OOM treiben. Posture-Check + Docker-Critical-Events sehen das nachher, aber praeventiver waere Container-Limit + Docker-OOM-Kill fuer den richtigen Prozess.
- **Empfehlung:** Fuer Tier-1 (Postgres, Authelia, Traefik, Komodo) sanfte Limits setzen (z. B. Postgres 2 GB, Authelia 256 MB, Traefik 256 MB). Fuer Immich-ML-Container ein hartes Limit, das Verhungern verhindert.
- **Prioritaet:** Nice to have
- **Aufwand:** M
- **Validierung:** `docker stats` zeigt `MEM USAGE / LIMIT` ungleich `unlimited`.
### F-20 · Paperless-DBPass weiter als Stack-ENV (dokumentierte Ausnahme)
- **Kategorie:** Secrets
- **Fundstelle:** `MASTER 13: Secrets in Komodo Stacks`, `docs/SECRETS_MAP.md:25`
- **Beobachtung:** Paperless unterstuetzt `_FILE` nicht fuer DB-Pass. Bewusste Ausnahme.
- **Risiko:** Stack-ENV liegt in Komodo-DB (Mongo), nicht im Repo. Bei Komodo-Mongo-Backup-Luecke fehlt das Passwort beim Restore.
- **Empfehlung:** Erweiterung der Disaster-Recovery-Doku: explizite Liste aller "Stack-ENV-only"-Secrets mit Zeiger, dass `komodo-mongo.archive.gz` fuer Restore zwingend ist, oder die ENV manuell vorgehalten werden muss (in Vaultwarden + externer Notiz).
- **Prioritaet:** Nice to have
- **Aufwand:** S
- **Validierung:** DR-Doc Abschnitt "Stack-ENV-Werte" referenziert konkrete Restore-Pfade.
---
# D. Risiko-Matrix
| Risiko | Bereich | Wahrscheinlichkeit | Auswirkung | Prioritaet | Massnahme |
|---|---|---|---|---|---|
| Borg-Passphrase weg -> Restore unmoeglich | Backup | niedrig | katastrophal | P0 | F-02 analoge Sicherung |
| Hetzner-Account-Verlust -> halbes 3-2-1 | Backup | niedrig-mittel | hoch | P0/P1 | F-03 Zweitziel |
| AdGuard-Admin-Manipulation aus LAN | Security | niedrig | hoch (DNS-Hijack) | P0 | F-01 Bind auf Tailscale |
| Operator-Pwd-Leak -> 2FA fehlt fuer Borg-UI/Code-Server | Security | mittel | hoch | P0 | F-04 2FA-ACL erweitern |
| Komodo-Self-Bootstrap-Failure nach Totalausfall | DR | niedrig | hoch | P1 | F-09 Bootstrap-Datei in `services/` |
| Authelia Repo↔Host Drift unbemerkt | GitOps/Security | mittel | mittel | P1 | F-10 Diff-Check |
| Immich Silent Corruption -> kein Restore-Test belegt | Backup | niedrig | sehr hoch (Familien-Fotos) | P1 | F-11 Restore-Test |
| Cert-Expiry unbemerkt -> Public Apps down | Operations | niedrig | mittel | P1 | F-08 Alert-Regel |
| Nextcloud Brute-Force ohne Bouncer | Security | mittel | mittel-hoch | P1 | F-14 CrowdSec / F-18 Nextcloud-Haerten |
| Image-Update-Stillstand -> CVE bleibt | Security | mittel | mittel | P1 | F-12 Renovate |
| Hermes-Wartungsschuld | Wartbarkeit | hoch | niedrig | P1 | F-06 Entscheidung |
| Repo-Altstaende ueberleben -> Doppel-Deploy | GitOps | mittel | niedrig | P1 | F-05 Cleanup |
| OOM durch unlimitierte Container | Hardware | niedrig | mittel | P2 | F-19 mem_limit |
| Healthcheck-Luecke -> Soft-Failure stumm | Operations | mittel | niedrig | P2 | F-15 Healthchecks |
| Monitoring-Stack ohne Digest-Pin | Reproduzierbarkeit | niedrig | niedrig | P2 | F-07 Digests + Renovate |
| Hardware-SPOF (kein zweiter Host) | Hardware | niedrig | sehr hoch | P3 | Cold-Standby / 2. Host |
---
# E. Zielarchitektur (realistisch fuer privates Homelab)
**Hardware**
- 1× Unraid-Host (bestehend) als Production. CPU mit AVX2/AVX-512 fuer Immich-ML. ≥ 32 GB RAM (fuer 2× Postgres + Immich-ML + Loki/Prometheus + 2 VMs).
- 2× NVMe als BTRFS-RAID1-Cache, sobald Cache-Auslastung > 70 % (STORAGE_LAYOUT 15.3).
- Parity-Disk ≥ groesste Daten-Disk.
- USV mit USB-Steuerung (NUT-faehig: APC Back-UPS RS 700+, Eaton 3S, CyberPower CP1500EPFCLCD). Direkter Shutdown bei Power-Loss.
- Optional: zweiter alter Mini-PC oder NUC als Cold-Standby mit Tailscale, der den letzten Komodo-Bootstrap + Gitea-Mirror tragen kann.
**Netzwerk**
- FritzBox (bestehend) als Router + NAT.
- VLANs nur wenn IoT-WLAN dazukommt (FritzBox-Gast-WLAN reicht fuer Anfang).
- DNS: AdGuard -> Unbound (bestehend). Admin-UI nur Tailscale.
- Tailscale (bestehend): Operator-Pfad. Subnet-Router optional fuer LAN-Devices ueber Tailscale.
**Storage**
- Cache `only` fuer `appdata`, `system`, `domains` (bestehend STORAGE_LAYOUT 4).
- Disk1 (XFS) fuer `services`, `documents`, `photos`, `backups`, `media`, `finance`, `projekte`.
- Externe Wechselplatte (XFS) fuer Cold-Off-Site mit fester monatlicher Rotation.
**Ordnerstruktur (Repo)**
- Beibehalten. Nur Cleanup von Altstaenden (F-05). Naming `kebab-case`-Migration aus STORAGE_LAYOUT 6 schrittweise.
**Docker**
- Compose-only via Komodo (bestehend).
- Digest-Pin fuer alle Images (F-07).
- Healthchecks fuer Tier-1 (F-15).
- Mem-Limits fuer Tier-1 + Immich-ML (F-19).
- App-interne Netze fuer Stack-Isolation (bestehend).
**Reverse Proxy**
- Traefik v3 (bestehend), DNS-Challenge, Wildcard.
- Dynamic Config nur fuer Middlewares, TLS, Dashboard (bestehend).
- CrowdSec-Bouncer (F-14) fuer oeffentliche Apps.
**Auth**
- Authelia als ForwardAuth **und** OIDC-Provider (F-13).
- Nextcloud, Immich, Grafana via OIDC.
- 2FA-Pflicht fuer alle Operator-Dienste (F-04).
- Komodo bewusste Ausnahme (bestehend).
**Backup**
- Borg lokal (`/mnt/user/backups/borg/`) + Borg Hetzner + Wechselplatte.
- Pre-Dump-Hooks (bestehend).
- Borg-Passphrase off-system analog (F-02).
- Restore-Tests automatisiert (F-11 Immich, dann andere via CI).
**Monitoring**
- `monitoring/`-Stack als alleinige Quelle. Altstaende raus (F-05).
- Family-View-Dashboard in Grafana (alles gruen, Backup-Frische, Cert-Tage).
- Alerts ausgebaut (F-08).
- Posture-Check + Docker-Critical-Events -> ntfy `homelab-alerts` (bestehend).
**Dokumentation**
- Aktuelle Doku-Tiefe halten.
- `SERVICES_RECOVERY.md` und `STORAGE_LAYOUT.md` (Active) finalisieren.
- Familien-/User-Onboarding-Doku als eigenes kleines Dokument.
**GitOps**
- Gitea + Komodo (bestehend).
- GitHub-Push-Mirror (umgesetzt, bestaetigt durch MASTER 7.1).
- Renovate-Bot gegen Gitea (F-12).
- Optional: Staging-Branch + zweites Komodo-Ziel in Tailscale-VM (Phase 3).
**Restore**
- RESTORE_MATRIX bleibt fuehrend.
- Restore-Lab unter `/mnt/user/backups/restore-lab/` (bestehend).
- Immich-Restore als Luecke schliessen (F-11).
- Komodo-Self-Bootstrap raus aus Komodo (F-09).
---
# F. Priorisierte Massnahmenliste
| # | Aufgabe | Warum | Kategorie | Prio | Aufwand | Risiko (bei Nicht-Tun) | Mehrwert | Abhaengigkeiten | Validierung |
|---|---|---|---|---|---|---|---|---|---|
| 1 | Borg-Passphrase analog sichern | DR-SPOF schliessen | Backup | P0 | S | katastrophal | DR-Sicherheit | — | Wert ohne Host abrufbar |
| 2 | AdGuard-Admin auf Tailscale-IP binden | LAN-Angriffsflaeche | Security | P0 | S | hoch | LAN-IoT-Haertung | — | `ss -ltnp` zeigt nur Tailscale |
| 3 | 2FA-ACL erweitern (borg, code, files, traefik) | Operator-Pwd-Leak | Security | P0 | S | hoch | 2FA-Coverage | Authelia-TOTP-Setup | Login erzwingt 2FA |
| 4 | Altstaende `ops/grafana-influxdb`+`ops/loki` `git rm` | Repo-Hygiene, kein Re-Deploy | GitOps | P0 | S | niedrig | Klarheit | Tag setzen | Policy-Check clean |
| 5 | Hermes 60-Tage-Deadline | Wartungsschuld | App | P1 | S/L | mittel | Komplexitaet raus | Operator-Entscheidung | Entweder produktiv oder weg |
| 6 | Immich-Restore-Test einrichten | Groesster Datentopf ungeprueft | Backup | P1 | M | hoch | Restore-Vertrauen | Restore-Lab-Pfad | Smoke-Test-Report |
| 7 | Renovate-Bot in Gitea | manuelle Digest-Pflege | Wartung | P1 | M | mittel | Update-Hygiene | Gitea-Runner | erste PR offen |
| 8 | Alert-Regeln (Borg-Frische, Cert-Expiry) | Blind-Spot Operations | Monitoring | P1 | M | mittel | echte Alerts | Pushgateway o. textfile | Alert in Test |
| 9 | Family-View-Dashboard Grafana | Morgens 30 s Check | Monitoring | P1 | M | niedrig | Uebersicht | Datasources stehen | Dashboard funktioniert |
| 10 | Komodo-Self-Bootstrap als `services/komodo-bootstrap/` | Henne-Ei-Problem | GitOps/DR | P1 | M | mittel | sauberer Recovery-Pfad | Komodo-Stack-Doku | Bootstrap aus Repo allein moeglich |
| 11 | Authelia-Drift-Diff-Check in posture-check | Repo↔Host Drift | GitOps | P1 | S | mittel | Drift-Detektion | posture-check-Erweiterung | neuer Check sichtbar |
| 12 | Healthchecks Tier-1 | Soft-Failure-Erkennung | Docker | P1 | M | niedrig | Self-Healing-Trigger | — | `docker ps` zeigt `healthy` |
| 13 | CrowdSec-Bouncer vor Traefik | oeffentliche Apps schuetzen | Security | P1 | M | mittel | Brute-Force-Stop | Traefik-Middleware | Test-IP wird geblockt |
| 14 | Nextcloud-Haertung dokumentieren | Public App + native Auth | Security | P1 | S | mittel | App-Haertung | Plugin-Install | 2FA-erzwingt |
| 15 | Authelia OIDC-Provider + Nextcloud/Immich/Grafana | SSO, Familien-Onboarding | Security/UX | P2 | L | niedrig | hoher Mehrwert | Authelia-OIDC-Setup | SSO-Login funktioniert |
| 16 | Immich-Smartphone-Auto-Backup fuer Familie | Killer-App fuer Familie | App | P2 | S | niedrig | hoher Mehrwert | — | Familien-Foto in Immich |
| 17 | Monitoring-Stack Digests + Renovate-Pin | Reproduzierbarkeit | GitOps | P2 | S | niedrig | konsistent | Renovate optional | `@sha256` an allen Images |
| 18 | Mem-Limits Tier-1 + Immich-ML | OOM-Schutz | Hardware/Docker | P2 | M | niedrig | Schutz | — | `docker stats` zeigt Limits |
| 19 | Off-Site-Zweitziel (rsync.net o. Wechselplatte) | Single-Provider | Backup | P2 | M | mittel | 3-2-1 echt | Borg-Config | beide Repos < 7d |
| 20 | Staging-Branch + 2. Komodo-Ziel | Risiko-Aenderung testbar | GitOps | P3 | L | niedrig | Reife | 2. VM/Host | Deploy auf staging klappt |
---
# G. Refactoring-Plan (Sprints)
## Sprint 0 — Sicherheitsnetz und Ist-Zustand sichern (1 Tag)
- **Ziel:** Du kannst danach im schlimmsten Fall alles, was du jetzt aenderst, sicher zurueckrollen.
- **Aufgaben:**
- Git-Tag `audit-2026-05-25-baseline` auf `master` setzen und nach Gitea + GitHub-Mirror pushen.
- Borg-Lauf manuell ausloesen ("freshen up"), Erfolg im Log dokumentieren.
- Aktuellen Komodo-Mongo-Dump verifizieren (`mongorestore --dry-run`).
- `docs/MIGRATION_LOG.md` Eintrag "Audit-Sprint-Start".
- **Erfolgskriterium:** Tag pushed, Borg-Lauf gruen, Mongo-Dump verifiziert.
- **Validierung:** `git fetch && git tag | grep audit-2026-05-25-baseline` und `ls /mnt/user/backups/borg/dumps/latest/` zeigt fresh.
- **Rollback:** N/A (rein additiv).
- **Risiko bei Nichtumsetzung:** Keine Notbremse fuer Sprint 1.
## Sprint 1 — Offensichtliche Risiken entschaerfen (1 Woche)
- **Ziel:** P0-Risiken weg, Repo-Hygiene wieder gruen.
- **Aufgaben (in dieser Reihenfolge):**
1. F-02 Borg-Passphrase analog sichern (off-system, kein Code-Change).
2. F-01 AdGuard-Admin-Port auf Tailscale-IP — Edit `host-services/Adguard/docker-compose.yml:16`.
3. F-04 Authelia ACL erweitern (`two_factor` fuer borg, code, files, traefik) — Edit `security/authelia/configuration.yml` + Host-Sync.
4. F-05 Altstaende `ops/grafana-influxdb/`, `ops/loki/` entfernen — `git rm`, MIGRATION_LOG.
5. Policy-Check-Warnings `SEC001` (ddns-updater, scrutiny) aufraeumen.
- **Erfolgskriterium:** Policy-Check 0 Warnings fuer SEC001, AdGuard-Admin nur via Tailscale, 2FA-Login auf borg.kaleschke.info.
- **Validierung:** Policy-Check-Report; Browser-Test mit/ohne 2FA-Cookie.
- **Rollback:** Commit-Revert pro Block.
## Sprint 2 — GitOps-Robustheit (12 Wochen)
- **Ziel:** Self-Bootstrap-Problem entschaerft, Drift-Detektion automatisiert.
- **Aufgaben:**
1. F-09 Komodo-Bootstrap-Compose nach `services/komodo-bootstrap/` extrahieren + dokumentierter Standalone-Restore-Pfad.
2. F-10 Authelia-Drift-Diff in posture-check ergaenzen.
3. F-11 Immich-Restore-Test einrichten (analog zu vaultwarden/gitea/paperless).
4. F-06 Hermes-Entscheidung mit 60-Tage-Deadline schriftlich.
- **Erfolgskriterium:** Komodo laesst sich aus Repo allein wiederherstellen. Posture-Check zeigt `authelia_config_drift: false`. Immich-Restore-Report unter `/mnt/user/backups/restore-reports/`.
- **Validierung:** Trockenversuch (Komodo-Container stoppen, `docker compose up -d` aus `services/komodo-bootstrap/`).
- **Rollback:** Bootstrap-Verzeichnis loeschen, Komodo-Self-Stack wie vorher.
## Sprint 3 — Backup & Restore belastbar machen (23 Wochen)
- **Ziel:** 3-2-1 echt, Restore-Tests breiter, Stack-ENV im DR-Pfad.
- **Aufgaben:**
1. F-03 Zweitziel: Wechselplatten-Rotation dokumentieren ODER zweites Borg-Repo (rsync.net / BorgBase EU2).
2. F-20 Stack-ENV-Liste in DR-Doc explizit machen (Restore-Reihenfolge).
3. Borg-Verifikation Cron fuer `borg check --repository-only` weekly (STORAGE_LAYOUT 8.4).
4. Quartalsweise End-to-End-Restore-Drill in Schedule aufnehmen.
- **Erfolgskriterium:** Beide Off-Site-Ziele < 7 Tage alt; DR-Doc enthaelt "ENV-Restore-Reihenfolge".
- **Validierung:** `borg list` gegen beide Repos.
## Sprint 4 — Monitoring & Alerting ausbauen (2 Wochen)
- **Ziel:** Sichtbarkeit auf das, was wirklich weh tut.
- **Aufgaben:**
1. F-08 Alert-Regeln: `BorgArchiveStale`, `TLSCertExpiryNear`, `ContainerDown`, `PostgresConnSaturation`.
2. F-15 Healthchecks fuer Traefik, Authelia, Postgres, Komodo, Gitea.
3. F-07 Digest-Pin in `monitoring/docker-compose.yml`.
4. Family-View-Dashboard in Grafana (1 Panel: Service-Up, 1 Panel: Backup-Frische, 1 Panel: Cert-Tage, 1 Panel: Disk-Fuellung).
- **Erfolgskriterium:** Family-View zeigt alles gruen; Cert-Alert feuert in Test (Datum vorgespult).
- **Validierung:** Dashboard sichtbar unter `monitoring.kaleschke.info/d/family-view`.
## Sprint 5 — Auth-Konsolidierung & Frontdoor-Haertung (34 Wochen)
- **Ziel:** SSO fuer die Familie, Brute-Force-Bouncer vor oeffentlichen Apps.
- **Aufgaben:**
1. F-13 Authelia OIDC-Provider aktivieren.
2. Nextcloud OIDC-Plugin + Test-Login.
3. Immich OIDC + Test-Login.
4. Grafana OIDC + Test-Login.
5. F-14 CrowdSec-Bouncer vor Traefik.
6. F-18 Nextcloud-Haertung-Dokument + 2FA-Pflicht.
- **Erfolgskriterium:** Familien-Konto loggt sich mit einem Login bei drei Apps ein; CrowdSec sperrt Test-IP nach N fehlerhaften Versuchen.
- **Validierung:** OIDC-Sequenz im Browser ohne Eingabe-Wiederholung; CrowdSec-Dashboard zeigt Sperre.
## Sprint 6 — Automatisierung und Nerd-Level (laufend)
- **Ziel:** Image-Update-Pipeline, optional Staging.
- **Aufgaben:**
1. F-12 Renovate-Bot gegen Gitea.
2. F-19 Mem-Limits Tier-1.
3. Restore-Test-CI via Gitea Actions (P3).
4. Optional: Staging-Branch + zweites Komodo-Ziel in Tailscale-VM (P3).
5. Optional: Firefly III / Actual Budget fuer `/mnt/user/finance`.
- **Erfolgskriterium:** Renovate-PRs erscheinen woechentlich; mindestens ein automatisches Patch-Update gemerged.
---
# H. Fehlende Informationen
> Nur, was den Audit konkret schaerfer machen wuerde.
| Frage | Warum | Bereich | Kommando / Datei |
|---|---|---|---|
| CPU-Modell, RAM-Groesse, Mainboard | Hardware-Bewertung, OOM-Risiko, Immich-ML-Eignung, AVX-Verfuegbarkeit | Hardware | `cat /proc/cpuinfo \| grep -E 'model name\|flags'`, `free -h`, `dmidecode -t baseboard \| head -20` |
| USV vorhanden? Modell? | DR-Beurteilung Power-Loss, Shutdown-Pfad | Hardware/DR | physische Sichtpruefung; `apcaccess` falls APC mit NUT |
| Stromverbrauch idle / Last | Betriebskosten, Sizing | Hardware | Smartmeter / Tibber-API |
| NIC-Speed (1 GbE? 2.5 GbE?) | Backup-Durchsatz, Plex-Streaming | Netzwerk | `ip -br link`, `ethtool eth0` |
| Disk-Inventar (Anzahl, Modelle, Alter) | Storage-Health, Replacement-Plan | Storage | `lsblk -o NAME,SIZE,MODEL,SERIAL`, Scrutiny-UI |
| Aktueller Cache-Fuellstand | Wann zweite NVMe? | Storage | `df -h /mnt/cache` |
| FritzBox-Modell + Firmware | Net-Sicherheit, VLAN-Faehigkeit | Netzwerk | FritzBox-UI / `fritzconnection` |
| Tatsaechlich genutzte Plex- vs. ungenutzte App | Konsolidierungs-Belege | App-Landschaft | Plex-Server-Logs, ggf. Glances-Container-CPU pro Stack |
| Existiert `/mnt/user/finance/`-Share schon? | Ist Firefly-Vorbereitung trivial? | Storage | `ls /mnt/user/finance/` |
| Authelia Live-User-DB-Tiefe (Anzahl User, 2FA-Status) | 2FA-Coverage-Bewertung | Security | `cat /mnt/user/appdata/authelia/config/users_database.yml` (nur Strukturansicht, keine Hashes hier zitieren) |
| Komodo-Mongo-Dump letzter Integrity-Check | F-09-Vorbereitung | Backup | `mongorestore --dry-run --archive=komodo-mongo.archive.gz --gzip` |
| Aktuelle Cert-Restlaufzeit | F-08-Test-Vorbereitung | Operations | `openssl s_client -connect git.kaleschke.info:443 -servername git.kaleschke.info < /dev/null \| openssl x509 -noout -dates` |
---
# I. Pruefkommandos (Linux / Unraid / Docker / Windows)
> Strukturiert nach Bereich. Sicher zum Ausfuehren am Host.
### Hardware
```bash
# CPU + Flags (AVX fuer Immich-ML)
cat /proc/cpuinfo | awk '/model name|flags/ {print; if(/flags/) exit}'
# RAM
free -h
dmidecode -t memory | grep -E "Size|Speed" | head -20
# Mainboard
dmidecode -t baseboard | head -20
# PCI / SATA / NVMe
lspci
nvme list
lsblk -o NAME,SIZE,MODEL,SERIAL,FSTYPE,MOUNTPOINT,VENDOR
# SMART
smartctl -a /dev/nvme0n1 | head -40
smartctl -a /dev/sdb | head -40
# Stromverbrauch (Unraid Plugin oder ipmitool falls IPMI)
sensors | head -30
```
### Filesystem / Storage / Mounts
```bash
# Filesystem-Typen (Hard Rule 12.1)
findmnt -no FSTYPE /mnt/cache /mnt/disk1 /boot
mount | grep -E "ntfs3|fuseblk" # darf leer sein
# Share-Settings
ls -la /boot/config/shares/
# Cache-Fuellstand
df -h /mnt/cache /mnt/disk1 /mnt/user
du -sh /mnt/user/appdata/* | sort -hr | head -20
```
### Docker
```bash
# Container-Inventur
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Image}}" | sort
docker ps -a --filter "status=exited" --format "table {{.Names}}\t{{.Image}}\t{{.Status}}"
# Netzwerke
docker network ls
docker network inspect frontend_net | jq '.[0].Containers | keys'
docker network inspect backend_net | jq '.[0].Internal'
# Volumes ohne Container (Waisen)
docker volume ls -qf dangling=true
# Effektive Ports
ss -ltnp | sort -k4
# Healthchecks
docker ps --format "{{.Names}}\t{{.Status}}" | grep -E "healthy|unhealthy|starting"
```
### Security
```bash
# Privileged-Container und Docker-Socket-Mounts
for c in $(docker ps -q); do
docker inspect "$c" --format '{{.Name}}: priv={{.HostConfig.Privileged}}; sock={{range .HostConfig.Binds}}{{println .}}{{end}}'
done | grep -E "priv=true|docker.sock"
# Direkte Host-Ports
docker ps --format "{{.Names}}: {{.Ports}}" | grep -v "^[^:]*: $"
# Secret-Datei-Rechte
ls -la /mnt/user/appdata/secrets/
stat -c "%a %n" /mnt/user/appdata/secrets/*.txt
```
### Backup / Restore
```bash
# Borg-Frische
ls -lat /mnt/user/backups/borg/dumps/latest/ | head
find /mnt/user/backups/borg/dumps/latest -mmin +1440 -type f # aelter 24h
# Borg-Repo (Passphrase per File)
export BORG_PASSPHRASE=$(cat /mnt/user/appdata/secrets/borg_repo_passphrase.txt)
borg list ssh://... --short | tail -5
borg info ssh://... ::Taegliche-Sicherung-2026-05-25T05:52:44.157
# Posture-Check
cat /mnt/user/services/posture-check/last.json | jq '.warning_count, .critical_count'
```
### Netzwerk / DNS
```bash
# Tailscale
tailscale status
# DNS auf AdGuard testen
dig @127.0.0.1 git.kaleschke.info
dig @127.0.0.1 example.com # Unbound-Recursion
# Cert-Restlaufzeit
for h in vault git immich cloud paperless mealie ntfy; do
echo -n "$h.kaleschke.info: "
openssl s_client -connect ${h}.kaleschke.info:443 -servername ${h}.kaleschke.info </dev/null 2>/dev/null \
| openssl x509 -noout -dates 2>/dev/null
done
```
### GitOps-Konsistenz
```bash
# Komodo Stack-Workspace vs. Repo
cd /mnt/user/services/stacks/<stackname>
git rev-parse HEAD
git status --short
# Webhook-Liveness
docker logs komodo-core 2>&1 | grep -i "webhook\|deploy" | tail -20
```
### Windows (lokal)
```powershell
# Repo-Status
git status --short
git fetch origin
git log origin/master..HEAD --oneline # ungepushte Commits
git log HEAD..origin/master --oneline # ungepullte Commits
# Policy-Check
.\ops\policy-checks\check_repo.ps1
# Restore-Freshness
.\ops\restore-tests\check-restore-freshness.ps1
```
---
# J. Community- / Best-Practice-Abgleich
> Nur fuer die Architekturentscheidungen, bei denen der Markt eindeutig ist oder Gegenpositionen relevant sind.
| Entscheidung | Markt-Best-Practice | Stuetzende Quellen | Bewertung |
|---|---|---|---|
| Traefik mit Docker-Labels statt File-Provider | Standard in Selfhosted (siehe `awesome-selfhosted-docker`, Smarthome-Beginner Templates) | Traefik-Doc v3 docs.traefik.io/providers/docker | passt zu MASTER 13 (Wechsel 2026-03-28) |
| DNS-Challenge mit Cloudflare statt HTTP-01 | Standard fuer Wildcard und reduzierte Angriffsflaeche | acme.sh / lego docs | passt, korrekt |
| Authelia ForwardAuth statt Authentik | Authelia ist leichtgewichtiger, Authentik maechtiger; beide valide | r/selfhosted-Konsens 2024-25 | Authelia richtig fuer Single-Family-Setup |
| Authelia ohne Redis-Session-Backend | Markt-Standard ist mit Redis; deine Vereinfachung ist begruendet (MASTER 13 2026-05-04) | Authelia-Doc | Trade-off klar; Bewertung: vertretbar fuer Homelab |
| Komodo statt Portainer/Dockge | Komodo ist neuer (2024), Dockge etabliert, Portainer kommerziell | Selfh.st 2025 Comparison | Komodo legitim, mehr GitOps-nativ als Dockge |
| Borg statt Restic/Kopia | Borg ist klassische Wahl fuer Linux-Backup mit Deduplikation; Kopia/Restic gewinnen mit Multi-Backend | r/datahoarder, ServeTheHome 2024 | Borg passt zu Unraid-Stack; bewusste Vereinfachung |
| Glance statt Homepage als Single-Dashboard | beide auf Augenhoehe; Homepage etablierter, Glance moderner, schneller, mit Live-Widgets | github.com/glanceapp/glance vs. github.com/gethomepage/homepage | Glance legitim; Bewertung: deine Wahl ist verteidigbar |
| Immich nicht hinter Authelia ForwardAuth | offizielle Immich-Doku raet bei nativer App-Auth davon ab, weil Sync-Clients OIDC oder direkte Auth brauchen | immich.app/docs/administration/reverse-proxy | korrekt; OIDC spaeter (F-13) ist der Weg |
| Nextcloud klassisch statt AIO | NC-AIO ist offizielle Empfehlung fuer Neuaufbau, klassisch hat mehr Flexibilitaet fuer GitOps | NC-Blog 2024 | bewusste Ausnahme MASTER 13; vertretbar, da GitOps-Anbindung wichtiger |
| Single-Host + Borg statt Proxmox-Cluster + ZFS-Send | fuer Familien-Homelab ist Cluster Overkill | r/homelab, LTT-Forum | Single-Host korrekt; Cold-Standby (Sprint 6) ist die richtige naechste Stufe |
| AdGuard + Unbound statt Pi-hole | aequivalent; AdGuard hat moderne UI, Unbound recursion | gowri/networkchuck Tutorials 2024 | passt |
| Posture-Check als Skript statt Goss/InSpec | fuer Single-Host pragmatisch | Goss ist maechtiger, aber Overkill | Skript-Loesung legitim |
| Renovate gegen Gitea | mehrere Erfahrungsberichte in Gitea-Issues + Renovate-Docs | docs.renovatebot.com/modules/platform/gitea/ | Standard-Pfad |
| CrowdSec vor Traefik | starker Trend 2024-25 in Selfhosted-Community | crowdsec.net/blog, Marius-Hosting-Tutorials | sinnvolle Haertung |
**Gegenpositionen, die du kennen solltest:**
- **"Authelia OIDC ist kompliziert, lieber Authentik."** Korrekt, wenn du auch B2B-SAML brauchst. Fuer reine Familien-OIDC ist Authelia leichter wartbar.
- **"CrowdSec laedt zentrale Reputation-Listen -> Privacy-Bedenken."** Stimmt, du kannst Local-Only-Mode fahren. Fuer Homelab egal.
- **"Renovate-Bot erzeugt Laerm."** Mit Group/Schedule-Rules zaehmbar. Wert > Laerm.
- **"Komodo ist zu jung."** Gegenargument: du benutzt es seit Q1/2026 produktiv, Major-Inzidenz war beherrschbar. Der Wechsel zurueck zu Portainer/Dockge waere hoehere Kosten als der Reifegrad-Nachteil.
---
# K. Endziel — "Nerd-Level Homelab"
So sieht dein Homelab aus, wenn es wirklich auf Senior-Level ist:
**Betrieb im Alltag**
- Morgens 30 Sekunden auf `monitoring.kaleschke.info`: Family-View zeigt 7 Tier-1-Services gruen, Backup-Job in der Nacht hat 100 % Files erfasst, alle Certs > 30 Tage, Disk < 80 %.
- Push-Benachrichtigung auf dem Handy nur, wenn wirklich etwas brennt (Posture-Check critical, Borg > 30 h, Endpoint down ≥ 8 Min, Cert < 14 Tage).
- Familienmitglieder loggen sich mit einem Login bei Nextcloud, Immich, Mealie ein. 2FA per TOTP-App, kein App-by-App-Passwortzettel mehr.
**Updates**
- Renovate oeffnet woechentlich 510 Pull-Requests in Gitea fuer Patch-Versionen. Du siehst sie im Web-UI, pruefst Release Notes, klickst Merge. Komodo deployt automatisch via Webhook. Smoke-Test in der naechsten Glance-Seite.
- Major-Updates kommen separat mit Label `major`, behandelst du in einem geplanten Slot mit Restore-Snapshot davor.
**Backups**
- Borg lokal alle 6 h, Borg Hetzner taeglich, Wechselplatte monatlich. Borg-Passphrase auf Papier im Bankschliessfach. Alle drei Ziele juenger als 36 h Alert-Schwelle.
- Pre-Dump-Hooks erzeugen 15 konsistente Dump-Artefakte pro Lauf, automatische Posture-Check-Vor-Hook bricht Backup ab, wenn FS oder Mount sich veraendert haben.
**Restore**
- Monatlicher Mini-Restore-Test fuer Vaultwarden/Gitea/Paperless/Immich nach `/mnt/user/backups/restore-lab/<dienst>/` laeuft automatisiert per Gitea Actions, Erfolgs-Report landet als Datei + ntfy-Info.
- Quartalsweise End-to-End-Drill: ein kompletter Stack restauriert, App startet, Smoke-Test passt, Doku validiert. Komodo-Bootstrap-Pfad ist getestet.
- Im Ernstfall folgst du `docs/DISASTER_RECOVERY.md` Phase 05 und bist nach < 8 h wieder im Vollbetrieb. Repo-Bootstrap aus GitHub-Mirror, Stacks in Stufen 15, Verifikation pro Stufe.
**Monitoring**
- Prometheus + Loki + Grafana + Alertmanager-ntfy-Bridge. ~15 Alert-Regeln, alle in `alerts.yml` versioniert.
- Family-View, Host-Overview, Containers+Logs, Traefik-Standalone, Backup-Frische, Cert-Tage. Sechs Dashboards mehr braucht keine Familie.
- Loki sammelt 30 Tage Logs aus allen Containern via Promtail. cAdvisor + node-exporter liefern Container- und Host-Metriken. Blackbox testet oeffentliche Endpoints alle 60 s.
**Security**
- Authelia OIDC fuer Nextcloud, Immich, Grafana, Mealie. ForwardAuth fuer Operator-UIs mit 2FA-Pflicht ab Tier-2.
- CrowdSec sperrt Brute-Force-IPs auf Traefik-Ebene bevor sie die Apps treffen.
- AdGuard-Admin nur via Tailscale. Operator-Pfad ausschliesslich Tailscale.
- Authelia-Repo-Baseline und Host-Config sind per Diff-Check im posture-check abgesichert.
- Secrets-Mounts mode 600, Borg-Passphrase analog.
**Dokumentation**
- SERVICE_CATALOG, RESTORE_MATRIX, DISASTER_RECOVERY, STORAGE_LAYOUT, SECRETS_MAP, WORKFLOW, GITOPS_DRIFT_RUNBOOK, ALERTING_MAP, HOMELAB_ARCHITECTURE_MASTER bleiben aktuelle Single-Source-of-Truth.
- `services/komodo-bootstrap/` loest das Henne-Ei. `SERVICES_RECOVERY.md` ist final, nicht Draft.
- Familien-Onboarding-Doku als Markdown: "So nutzt du Nextcloud-Web", "So aktivierst du Immich-Foto-Backup auf dem Handy", "So loggst du dich neu per 2FA ein".
**Taegliche Nutzung**
- Familie scannt Briefe per ASN-Barcode in `scans_inbox/`, Paperless tagged via paperless-gpt automatisch, alles durchsuchbar.
- Immich erfasst Smartphone-Fotos aller Familienmitglieder automatisch, Familie blaettert per Web/App, Tagging per ML.
- Nextcloud traegt Kalender, Kontakte, geteilte Familienordner per WebDAV/CardDAV — kein Google/Apple-Lock-In.
- Mealie speichert Rezepte, Einkaufsliste auf dem Handy.
- Vaultwarden ist der einzige Passwort-Tresor der Familie; Familien-Organisation aktiv.
- Plex streamt Heim-Medien an alle Endgeraete.
- ntfy schickt dir Vorfaelle aufs Handy — und sonst nichts.
- Optional: Firefly III fuer die Familien-Finanzen, Ecowitt-Wetter-Dashboard, Home-Assistant-Automationen fuer Strom-Eigenverbrauch.
**Was bewusst weggelassen ist**
- Kein Kubernetes. Komodo + Compose reicht.
- Kein zweiter Medienserver neben Plex (Jellyfin-Entscheidung 2026-05-25).
- Kein zweites Dashboard neben Glance (Homepage-Entscheidung 2026-05-25).
- Kein Uptime-Kuma neben Blackbox (Entscheidung 2026-05-25).
- Kein Hermes-Agent, wenn er bis 2026-07-25 keinen klaren Alltagsnutzen liefert.
- Kein BentoPDF/paperless-gpt 24/7, wenn nicht aktiv genutzt.
- Kein Self-Stack-Komodo (durch `services/komodo-bootstrap/` ersetzt).
---
## Schlussbemerkung
Das Setup ist naeher an Senior-Reife als an Bastel-Niveau. Der groesste Hebel der naechsten drei Monate ist **Konsolidieren statt erweitern** (Hermes-Entscheidung, Altstaende raus, Auth-SSO, Off-Site-Diversitaet), kombiniert mit der einen Aktivierung, die das Setup vom Operator-Tool zum **Familien-Tool** macht: Immich-Smartphone-Backup fuer alle.
Das vorhandene 2026-05-23-Audit hat die richtigen Sprintziele bereits identifiziert. Diese externe Audit-Sicht ergaenzt:
- **2FA-Pflicht auf Tier-1-Operator-UIs** (F-04) — fehlt in der Bewertung 2026-05-23 in dieser Klarheit.
- **Healthcheck-Luecke** (F-15) und **fehlende Mem-Limits** (F-19) — operative Detail-Findings, die in der strategischen Bewertung nicht auftauchen.
- **Komodo-Self-Bootstrap als konkreter Code-Vorschlag** (F-09) statt nur als Risiko-Erwaehnung.
- **Authelia-Drift-Detection automatisieren** (F-10) statt nur "manuell merge".
- **Monitoring-Stack ohne Digest-Pin** (F-07) — Inkonsistenz mit der eigenen Image-Pinning-Disziplin.
- **`infra/redis` ist faktisch nicht shared** (F-16) — Etikett-Realitaet-Drift.
- **Alert-Regeln deutlich zu duenn** (F-08) — Sichtbarkeitsluecken bei Cert/Borg/Container-Down.
Wenn Sprint 13 in 46 Wochen sitzen, bist du auf einer 1-Note. Wenn dann Sprint 45 in weiteren 68 Wochen kommen, hat die Familie ein echtes Self-Hosting-System, kein "Container-Sammlung im Keller". Das ist der Unterschied, den der Audit-Auftrag adressiert.
+34 -87
View File
@@ -1,95 +1,42 @@
# Audit TODO 2026-05-25 # Audit-Restliste 2026-05-25
Quelle: `docs/AUDIT_2026-05-25.md` Status: **kompakte Restliste**. Die erledigten Sprint-Tabellen und langen
Audit-Snapshots wurden aus der Arbeitskopie entfernt; Detailhistorie liegt in Git.
Status: Arbeitsliste fuer die Umsetzung. Authelia-2FA/OIDC bleibt bewusst spaet, weil die Ziel-Policy noch nicht final entschieden ist. ## Aktuell offene Punkte
## Leitplanken | Prioritaet | Punkt | Naechster Schritt |
- Keine Authelia-2FA-ACL-Aenderungen in den ersten Sprints.
- Keine Live-riskanten Bind-/Port-Aenderungen ohne vorher erfasste Host-Werte, insbesondere Tailscale-IP.
- Erst Inventar und Baseline, dann Aenderungen.
- 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.
## 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 | | P0 | Alt-Volumes nach Burn-in freigeben | Ab 2026-06-02 `ops/maintenance/release-alt-volumes.sh --dry-run` pruefen, danach nur bei sauberem Ergebnis mit `--execute` freigeben |
| in Arbeit | Netzwerk-Inventar ausfuellen | Host-IP, Gateway, Tailscale-IP und AdGuard-Bind erfasst; Router-/VLAN-Details offen | | P2 | Family-Onboarding praktisch starten | Fokus: Vaultwarden als Passwortbasis, Immich-Mobile-Backup auf jedem Handy, Mealie mit erstem Rezept/Einkaufsliste; Ablauf steht in `docs/FAMILY_ONBOARDING.md` |
| 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-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 |
## Sprint 1 - Nicht-kontroverse Sicherheits- und Repo-Hygiene ## Bewusst geparkt
| Status | Aufgabe | Ergebnis | | Punkt | Entscheidung |
|---|---|---| |---|---|
| offen | Borg-Passphrase analog sichern | Passphrase ist ohne Host/Vaultwarden wiederherstellbar | | Authelia 2FA fuer Operator-UIs | In diesem Zyklus nicht umgesetzt; erst mit finaler Auth-Policy |
| erledigt (repo) | AdGuard Admin-Bind vorbereiten | Tailscale-IP `100.80.98.33` erfasst, Compose-Soll geaendert | | Authelia OIDC fuer Apps | Geparkt bis klare Familien-/SSO-Entscheidung |
| 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 | | CrowdSec vor Traefik | Erst nach Auth-Policy neu bewerten |
| erledigt | Alte Monitoring-Verzeichnisse entfernen | `ops/grafana-influxdb/` und `ops/loki/` sind aus dem aktiven Repo entfernt; Rollback erfolgt ueber Git-Historie | | Nextcloud 2FA/Brute-Force-Haertung | Gemeinsam mit OIDC/Familienkonten entscheiden |
| erledigt | Komodo/Gitea-Restdrift bereinigen | alter Komodo-Stack `grafana` ist inert und ohne Repo-Pfad/Webhook; Gitea-Hook `35` und `komodo`-Self-Hook `11` sind inaktiv; aktive Gitea-Hooks haben keine Fehlstatus | | Hermes-Agent | NAS-Stack bleibt deaktiviert; Review-Deadline 2026-07-25 |
| erledigt | Policy-Warnings triagieren | Plex Host-Netz und digest-gepinnte mutable Tags sind dokumentierte Info-Ausnahmen; `monitoring-influxdb3-core` als Root-Ausnahme bleibt bewusst als Warning sichtbar | | USV | Anschaffung verschoben; Power-Loss-Risiko bewusst akzeptiert |
| Zweites Off-site-Ziel | Bewusst nicht umgesetzt; neu bewerten bei Hetzner-Problemen, stark wachsendem Datenwert oder geaenderter Betreiber-Praeferenz |
| Borg `append-only` auf Hetzner | Operator-Entscheidung 2026-06-01: nicht umgesetzt. Der forced-command-Test auf der Storage Box brach Key-Auth und wurde per Passwort-Recovery zurueckgesetzt; Nutzen steht fuer dieses Homelab nicht im Verhaeltnis zum Betriebsrisiko. |
## Sprint 2 - Storage und Recovery verbindlich machen ## Zuletzt geschlossen
| Status | Aufgabe | Ergebnis | - Externer Betreibercheck vorbereitet: `docs/EXTERNAL_OPERATOR_RUNBOOK.md` und `ops/maintenance/check-external-operator.sh`; Live-Baseline am 2026-06-01: FRITZ!OS `154.08.25`, keine Public-AAAA-Records fuer `*.kaleschke.info`, Host ohne globale Provider-IPv6, WAN `443/tcp` offen und `80/tcp`/`222/tcp` geschlossen.
|---|---|---| - FRITZ!Box-Servicefenster UI-seitig abgeschlossen: FRITZ!Box-Dienste aus dem Internet sind aus (HTTPS auf FRITZ!Box-UI, FTP/FTPS auf Speichermedien), aktive WAN-Freigabe bleibt nur `443/tcp -> 192.168.178.58`, keine aktive IPv6-Freigabe sichtbar, UPnP-Selbstfreigaben aus.
| offen | `docs/STORAGE_LAYOUT.draft.md` finalisieren | Datei wird als `docs/STORAGE_LAYOUT.md` Active gefuehrt | - FRITZ!Box-Konfig-Backup exportiert und extern/off-system in Vaultwarden abgelegt: `Einstellungen_FRITZ.Box_7590_154.08.25_01.06.26_1318.export`; Kennwort und Datei bleiben ausserhalb des Repos.
| offen | Disk- und Share-TBDs eintragen | Modelle, Groessen, Seriennummern, Filesysteme und Cache-Settings sind dokumentiert | - Hetzner-Account-Hygiene erledigt: externe Kontakt-/Rechnungs-Mail bestaetigt, Zahlung ok, 2FA mit Google Authenticator aktiv, Recovery Key offline ausgedruckt.
| offen | Gitea-Repo-Mirror-Mechanik definieren | Mirror fuer `/mnt/user/services/gitea/git/repositories/` mit Frequenz <= 6 h ist spezifiziert | - Hetzner Storage Box geprueft: `storage-box-1`, `u565255.your-storagebox.de`, SSH-Port `23`, SSH aktiv, SMB/WebDAV aus, 64,94 GB / 1 TB belegt; Borg-UI-Key und separater Maintenance-Key funktionieren wieder nach Passwort-Recovery. Borg `append-only` ist bewusst nicht umgesetzt.
| offen | Komodo-Bootstrap-Pfad beschreiben | Kaltstart ohne laufendes Komodo ist dokumentiert | - Family-View Dashboard ist repo-seitig gebaut: `monitoring/grafana/dashboards/family-status.json` zeigt Family-App-Uptime, Backup-Alter, TLS-Restlaufzeit, Critical-Container und Image-Drift.
| offen | Immich-Restore-Test planen | Testumfang, Datenpfade und Smoke-Test-Kriterium stehen fest | - Alt-Volume-Freigabe ist vorbereitet: `ops/maintenance/release-alt-volumes.sh --dry-run` validiert aktive Pfade, Container-Health, Restore-Freshness und gemountete Altpfade; Test am 2026-06-01 fand vier Kandidaten und keine Blocker, Ausfuehrung bleibt wegen Cutoff bis 2026-06-02 gesperrt.
- Borg-Nachlauf nach dem 2026-05-31-Sprint ist belegt: Archiv `Taegliche-Sicherung-2026-06-01T04:30:26.913`, 101669 Dateien, `rc=0`; Freshness-Check am 2026-06-01: Critical 0, Warnings 0.
## Sprint 3 - Restore und Monitoring - H:/ Nearline-Pull am 2026-06-01 repariert und manuell validiert: kuratierte Borg-Dumps Exit 0, Gitea-Bundles Exit 1 (Robocopy-Erfolg mit Kopien), Report `nearline-pull-2026-06-01-082553.md`.
- Immich-, Paperless-, Gitea- und Vaultwarden-Restore-Pfade sind belegt.
| Status | Aufgabe | Ergebnis | - H:/ Nearline-Pull laeuft seit 2026-05-28 als Windows Scheduled Task.
|---|---|---| - FRITZ!Box-Portfreigaben sind bereinigt: WAN-seitig bleibt `443/tcp`.
| offen | Immich-Restore-Test implementieren | Restore-Report landet unter `/mnt/user/backups/restore-reports/` | - InfluxDB 3 Core ist effektiv nur auf `127.0.0.1:8181` gebunden.
| offen | Borg-Stale-Alert bauen | Alarm feuert, wenn Borg-Archiv zu alt ist | - Renovate ist produktiv, Major-Updates werden bewusst manuell entschieden.
| offen | TLS-Cert-Expiry-Alert bauen | Alarm feuert bei Restlaufzeit unter Schwellwert | - Policy-Check bleibt ohne Criticals; bekannte Root-Ausnahmen sind dokumentiert.
| 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 |
## 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 |
## Sprint 5 - Auth und Frontdoor, bewusst zuletzt
| 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 |
## Offene Host-Werte
Diese Werte muessen am Unraid-Host erhoben werden, bevor die entsprechenden Aenderungen sauber umgesetzt werden:
```bash
hostname
cat /proc/cpuinfo | awk '/model name|flags/ {print; if(/flags/) exit}'
free -h
dmidecode -t baseboard | head -30
ip -br link
tailscale ip -4
lsblk -o NAME,SIZE,MODEL,SERIAL,FSTYPE,MOUNTPOINT,VENDOR
df -h /mnt/cache /mnt/disk1 /mnt/user
smartctl -a /dev/nvme0n1 | head -80
smartctl -a /dev/sdb | head -80
```
-80
View File
@@ -1,80 +0,0 @@
# Audit Report - KalliLab CORE
Datum: 2026-05-16
Gepruefte Compose-Dateien: 30
## Kritische Befunde
Keine kritischen Befunde im Repo-Sollzustand gefunden.
- Keine Datenbank und kein Cache haengt in `frontend_net`.
- Keine produktive Compose-Datei ohne explizites `networks:`-Feld gefunden.
- Keine produktive `.env`- oder `stack.env`-Datei ohne `.example`-Suffix im Repository gefunden.
- Keine Klartext-Passwoerter, Tokens oder API-Keys in Compose-`environment:`-Bloecken gefunden.
## Mittlere Befunde
- Docker-Socket ausserhalb der im Audit-Prompt genannten Allowlist: `traefik` mountet `/var/run/docker.sock:/var/run/docker.sock:ro` in `traefik/docker-compose.yml:34`. Der Socket ist fuer den Docker-Provider fachlich nachvollziehbar, aber in `HOMELAB_ARCHITECTURE_MASTER_V2.md` Abschnitt 10 nicht explizit als Docker-Socket-Ausnahme aufgefuehrt.
- Docker-Socket ausserhalb der im Audit-Prompt genannten Allowlist: `alloy` mountet `/var/run/docker.sock:/var/run/docker.sock:ro` in `ops/loki/docker-compose.yml:26`. [AUSNAHME - dokumentiert] in `HOMELAB_ARCHITECTURE_MASTER_V2.md` Abschnitt 10 als `alloy` Docker-Socket read-only, aber nicht in der Prompt-Allowlist genannt.
- `komodo-periphery` haengt ohne Web-UI im `frontend_net` in `ops/komodo/docker-compose.yml:92-94`. Die Compose-Datei dokumentiert an `ops/komodo/docker-compose.yml:77-79` den Grund als Git-Zugriff auf internes Gitea und Docker-Agent-Sonderfall. [AUSNAHME - lokal in Compose dokumentiert]
- `hermes_net` ist ein app-internes Netz ohne `internal: true`: `ops/hermes-agent/docker-compose.yml:91-93`. Das ist nicht Teil der explizit geforderten `internal: true`-Liste, aber strukturell auffaellig, weil Gateway und Dashboard intern ueber dieses Netz sprechen.
- `grafana` nutzt zusaetzlich `backend_net` in `ops/grafana-influxdb/docker-compose.yml:27-30`, obwohl die Architektur das Grafana/Influx-Paar primaer als `frontend_net` + `grafana_influx_internal` beschreibt. Grund ist vermutlich Loki-Datasource-Zugriff; `docs/REPO_MAP.md` nennt `backend_net` fuer Grafana-Loki-Datasource. [AUSNAHME - dokumentiert]
- `paperless-gpt` verwendet `PAPERLESS_API_TOKEN` als Stack-ENV in `apps/paperless-gpt/docker-compose.yml:15`, taucht aber in `docs/SECRETS_MAP.md` nicht als eigener aktiver Secret-Eintrag auf.
## Hinweise
- Direkte Host-Ports sind nur bei dokumentierten Ausnahmen vorhanden:
- [AUSNAHME - dokumentiert] `traefik`: `80:80`, `443:443` in `traefik/docker-compose.yml:30-32`.
- [AUSNAHME - dokumentiert] `gitea`: `222:22` in `core/gitea/docker-compose.yml:17-18`.
- [AUSNAHME - dokumentiert] `adguard`: `53:53/tcp`, `53:53/udp`, `8082:80` in `host-services/Adguard/docker-compose.yml:13-16`.
- [AUSNAHME - dokumentiert] `influxdb3-core`: `${INFLUXDB_BIND_IP:-127.0.0.1}:8181:8181` in `ops/grafana-influxdb/docker-compose.yml:54-55`.
- `backend_net` wird in allen Compose-Dateien als `external: true` referenziert, z. B. `infra/postgresql17/docker-compose.yml:25-26`, `infra/redis/docker-compose.yml:22-23`, `traefik/docker-compose.yml:59-60`. Ob das externe Docker-Netz live wirklich `internal: true` ist, ist aus dem Repo allein nicht verifizierbar.
- `grafana_influx_lan` hat bewusst kein `internal: true`: `ops/grafana-influxdb/docker-compose.yml:88-89`. [AUSNAHME - dokumentiert]
- Mutable Tags sind mit Digests gepinnt, aber semantisch weiterhin mutable:
- `immich-server`: `release@sha256` in `apps/immich/docker-compose.yml:4`.
- `immich-machine-learning`: `release@sha256` in `apps/immich/docker-compose.yml:35`.
- `tailscale`: `stable@sha256` in `host-services/tailscale/docker-compose.yml:3`.
- `ddns-updater`: `latest@sha256` in `infra/ddns-updater/docker-compose.yml:3`.
- `komodo-core`: Major-Tag `2@sha256` in `ops/komodo/docker-compose.yml:36`.
- `komodo-periphery`: Major-Tag `2@sha256` in `ops/komodo/docker-compose.yml:82`.
- `uptime-kuma`: Major-Tag `1@sha256` in `ops/uptime-kuma/docker-compose.yml:3`.
- Reines `image: name:latest` ohne Digest wurde in produktiven Compose-Dateien nicht gefunden.
- Alle Services haben `restart: unless-stopped`.
- Alle Services haben `security_opt: no-new-privileges:true`.
- [AUSNAHME - dokumentiert] `scrutiny` nutzt `privileged: true` in `ops/scrutiny/docker-compose.yml:6`.
- [AUSNAHME - dokumentiert] `tailscale` nutzt `network_mode: host` in `host-services/tailscale/docker-compose.yml:6`.
## Dokumentations-Abweichungen
- Service-Namen in Compose vs. `docs/SERVICE_CATALOG.md` weichen bei Immich ab: Compose nutzt `immich-server`, `immich-machine-learning`, `database`, `redis` in `apps/immich/docker-compose.yml:2`, `:33`, `:44`, `:53`; der Katalog dokumentiert `immich_server`, `immich_machine_learning`, `immich_postgres`, `immich_redis`.
- Service-Name in Compose vs. `docs/SERVICE_CATALOG.md` weicht bei Paperless ab: Compose nutzt `paperless` in `apps/paperless/docker-compose.yml:2`; der Katalog dokumentiert `paperless-ngx`.
- Service-Name in Compose vs. `docs/SERVICE_CATALOG.md` weicht bei Redis ab: Compose nutzt `redis` in `infra/redis/docker-compose.yml:2`; der Katalog dokumentiert `Redis`.
- Traefik-Host bei Hermes ist im Compose variabel: `Host(`${HERMES_DASHBOARD_HOST}`)` in `ops/hermes-agent/docker-compose.yml:81`; `docs/REPO_MAP.md` dokumentiert konkret `hermes.kaleschke.info`. Das ist plausibel, aber maschinell nicht eindeutig abgleichbar.
- `docs/REPO_MAP.md` Volumes-Tabelle ist fuer mehrere Mounts nur zusammenfassend, nicht pfadgenau. Beispiele mit Compose-Pfaden, die dort nicht wortgleich auftauchen:
- `/mnt/user/appdata/unbound/config` in `apps/unbound/docker-compose.yml:7`.
- `/mnt/user/appdata/ddns-updater` in `infra/ddns-updater/docker-compose.yml:17`.
- `/mnt/user/appdata/filebrowser/database` und `/mnt/user/appdata/filebrowser/config` in `ops/filebrowser/docker-compose.yml:15-16`.
- `/mnt/user/appdata/borg-ui/restore` in `ops/borg-ui/docker-compose.yml:27`.
- Netzwerke in Compose sind alle in `docs/REPO_MAP.md` aufgefuehrt.
- Traefik-Hosts aus Compose sind in `docs/REPO_MAP.md` aufgefuehrt; einzige Besonderheit ist der variable Hermes-Host.
- `.example`-Dateien haben passende Gegenspieler in `.gitignore`: `.env`, `*.env`, `!*.env.example`, `**/stack.env`, `!**/stack.env.example`.
- Keine `.keep`-Platzhalter-Dateien gefunden.
## Offene Architektur-Punkte: aktueller Stand
- `immich_redis`: weiterhin kein named volume. Compose-Service `redis` in `apps/immich/docker-compose.yml:44-50` hat aktuell gar keinen `volumes:`-Block. Architekturpunkt bleibt offen.
- `AdGuard Home`: Admin-Port ist weiterhin direkt veroeffentlicht: `8082:80` in `host-services/Adguard/docker-compose.yml:15`; keine Traefik-Labels vorhanden. Block F bleibt offen.
- `filebrowser`: Appdata-Breitmount ist entfernt; aktuelle Mounts sind `/mnt/user/documents`, `/mnt/user/photos`, `/mnt/user/projekte` plus eigene DB/Config in `ops/filebrowser/docker-compose.yml:12-16`. Langfristiges Hardening bleibt moeglich, aber der groesste alte Breitmount ist erledigt.
- `bentopdf`: Compose ist vorhanden und Traefik-abgesichert in `apps/bentopdf/docker-compose.yml:2-27`. Runtime-Deploy und fachliche Abnahme koennen aus dem Repo allein nicht bestaetigt werden; Architekturpunkt bleibt als Live-Pruefung offen.
- `grafana` und `influxdb3-core`: laufen weiterhin als `user: "0"` in `ops/grafana-influxdb/docker-compose.yml:6` und `ops/grafana-influxdb/docker-compose.yml:53`. [AUSNAHME - dokumentiert]
- `plex-media-server`: keine Compose-Datei im Repo gefunden; bleibt Dockerman-/Host-Sonderfall laut Architektur.
## Bestanden
- 30 produktive `docker-compose.yml` wurden geprueft.
- Keine Datenbanken oder Caches im `frontend_net`: `postgresql17` nur `backend_net` in `infra/postgresql17/docker-compose.yml:18`; shared `redis` nur `backend_net` in `infra/redis/docker-compose.yml:15`; `mealie-postgres` nur `mealie_internal` in `apps/mealie/docker-compose.yml:56`; Immich `database` und `redis` nur `immich_default` in `apps/immich/docker-compose.yml:48` und `:64`; Nextcloud DB/Redis nur `nextcloud_internal` in `apps/nextcloud/docker-compose.yml:61` und `:73`; `komodo-mongo` nur `komodo_net` in `ops/komodo/docker-compose.yml:16`.
- App-interne Pflichtnetze sind korrekt `internal: true`: `immich_default` in `apps/immich/docker-compose.yml:73-75`, `mealie_internal` in `apps/mealie/docker-compose.yml:66-68`, `nextcloud_internal` in `apps/nextcloud/docker-compose.yml:82-84`, `grafana_influx_internal` in `ops/grafana-influxdb/docker-compose.yml:90-91`.
- `frontend_net` und `backend_net` werden in Compose-Dateien als `external: true` referenziert.
- Alle Services mit `traefik.enable=true` setzen explizit `traefik.docker.network=frontend_net`, `entrypoints=websecure`, `tls=true` und `tls.certresolver=le`.
- Kein Traefik-Label mit `yourdomain.tld` gefunden.
- Admin-/Ops-Router haben Middleware `authelia@file,secure-headers@file`, u. a. `homepage` in `apps/homepage/docker-compose.yml:31`, `filebrowser` in `ops/filebrowser/docker-compose.yml:27`, `scrutiny` in `ops/scrutiny/docker-compose.yml:32`, `grafana` in `ops/grafana-influxdb/docker-compose.yml:46`. [AUSNAHME - dokumentiert] `komodo-core` hat keine ForwardAuth-Middleware in `ops/komodo/docker-compose.yml:64-71`; [AUSNAHME - dokumentiert] `nextcloud` nutzt native Auth und nur Redirect-Middleware in `apps/nextcloud/docker-compose.yml:42-46`.
- Web-UIs ohne Traefik-Labels wurden nur als dokumentierte Sonderfaelle gefunden: `adguard` mit LAN-Admin-Port in `host-services/Adguard/docker-compose.yml:13-16`; `ddns-updater` hat keine Web-UI und braucht Internet in `infra/ddns-updater/docker-compose.yml:8`; `komodo-periphery` ist Agent ohne Traefik-Route in `ops/komodo/docker-compose.yml:81-103`.
+43 -10
View File
@@ -1,6 +1,6 @@
# Capacity and Lifecycle - KalliLab CORE # 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 ## 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 | | 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 | | 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 | | 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 | | 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 | TBD | TBD | TBD | TBD | TBD | | Externe Cold-Platte | nicht vorhanden | - | - | Review nur bei Trigger | bewusst nicht beschafft; zweites Off-site erst bei Hetzner-Problemen, stark wachsendem Datenwert oder geaenderter Betreiber-Praeferenz |
Pruefkommando: 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 | | Medien | aktuell ca. 1.7T | groesster Speicherblock | Array-Erweiterung vor 80 % planen |
| Immich Fotos/Videos | aktuell ca. 23G | hoechster privater Datentopf | Restore-Test priorisieren | | 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 | | 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 | | 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 | | Borg Dumps | aktuell ca. 2.2G lokale Backups | Retention und Excludes pruefen | Borg-Stale + Groessenprofil |
@@ -47,19 +47,52 @@ 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 | | RAM >90 % ueber 10 Minuten regelmaessig | RAM-Ausbau oder Service-Limits pruefen |
| Borg-Laufzeit deutlich steigend | Scope, Netzwerk und Ziel pruefen | | Borg-Laufzeit deutlich steigend | Scope, Netzwerk und Ziel pruefen |
| SMART-Warnung | Ersatz planen, Restore-/Backup-Frische 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 ## Restore-Zeitziele
| Tier | Beispiel | Zielzeit | Status | | Tier | Beispiel | Zielzeit | Status |
|---|---|---:|---| |---|---|---:|---|
| Tier 0 | Repo, Secrets, Traefik, DNS | TBD | offen | | Tier 0 | Repo, Secrets, Traefik, DNS | 2-4 h | Zielwert, per DR-Sanity-Check bestaetigen |
| Tier 1 | Gitea, Vaultwarden, Paperless, Immich | TBD | offen | | Tier 1 | Gitea, Vaultwarden, Paperless, Immich | 4-8 h | Zielwert, einzelne Restore-Tests vorhanden |
| Tier 2 | Nextcloud, Mealie, Monitoring | TBD | offen | | Tier 2 | Nextcloud, Mealie, Monitoring | < 24 h | Zielwert, Restore-Pfade dokumentiert |
| Tier 3 | Komfort-/Ops-Tools | TBD | offen | | Tier 3 | Komfort-/Ops-Tools | Best effort / rebuildbar | Zielwert, keine harte SLA |
## Review-Log ## Review-Log
| Datum | Befund | Entscheidung | | 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 |
| 2026-06-01 | H:/ Pull nach Redis-/Major-Cutover-Artefakten gehaertet | Borg-Dumps-Job kopiert nur kuratierte Pflichtdateien; manueller Kontrolllauf erzeugte Report `nearline-pull-2026-06-01-082553.md` |
-32
View File
@@ -1,32 +0,0 @@
# 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.
+83 -5
View File
@@ -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 | | 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 | | 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-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 | | Secrets-Dateien | ueber Borg bzw. Restore-Quellen abgedeckt |
| Komodo Stack ENV-Werte | extern dokumentiert, z. B. Vaultwarden | | Komodo Stack ENV-Werte | extern dokumentiert, z. B. Vaultwarden |
| Services-Recovery | `docs/SERVICES_RECOVERY.md` gepflegt, insbesondere Gitea-Repo-Mirror und Komodo-Bootstrap | | 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` - `KOMODO_PERIPHERY_PASSKEY`
- `APP_KEY` und `ADMIN_PASSWORD` fuer `speedtest-tracker` - `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 ### 6.3 Rechte
Nach einem Restore oder manuellem Rueckkopieren: Nach einem Restore oder manuellem Rueckkopieren:
@@ -239,7 +261,7 @@ Ziel:
### Stufe 2 - Gemeinsame Backends und Identity ### 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/` 5. `security/authelia/`
6. `infra/redis/` 6. `infra/redis/`
7. `core/gitea/` 7. `core/gitea/`
@@ -249,12 +271,18 @@ Ziel:
- gemeinsame DB verfuegbar - gemeinsame DB verfuegbar
- zentrale Auth laeuft; Authelia nutzt bewusst kein Redis-Session-Backend - zentrale Auth laeuft; Authelia nutzt bewusst kein Redis-Session-Backend
- Authelia SMTP-Notifier kann GMX erreichen - 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 - Git-Zugriff wiederhergestellt
### Stufe 3 - Deploy-System ### 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: Ziel:
@@ -368,6 +396,12 @@ Vor dem Start muessen vorhanden sein:
Zusaetzlich muss der Nutzdatenpfad `/mnt/user/documents/nextcloud-data` erreichbar 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 ### Borg-Dumps
Die Dump-Erzeugung ist host-seitig gedacht, nicht als Borg-UI-Inline-Hook. 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` - Skript: `ops/borg-ui/scripts/pre-backup-dumps.sh`
- Unraid-Flash-Artefakt: `unraid-flash-config.tar.gz` plus `.sha256` und Manifest im selben Zielpfad - 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 Agent
Hermes nutzt einen lokalen Build und hostseitige Runtime-Daten. 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 ## 11. Offene Vorbereitungs-To-dos
- Unraid-USB-/Flash-Backup regelmaessig ueber `unraid-flash-config.tar.gz` und optional Unraid Connect pruefen - 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 - Komodo Stack-ENV-Werte zentral ausserhalb von Komodo dokumentieren
- regelmaessige automatisierte Restore-Smoke-Tests fuer Vaultwarden, Gitea und Paperless etablieren - regelmaessige automatisierte Restore-Smoke-Tests fuer Vaultwarden, Gitea und Paperless etablieren
- `komodo-mongo`-Dump nach Major-Upgrades gezielt kontrollieren - `komodo-mongo`-Dump nach Major-Upgrades gezielt kontrollieren
-77
View File
@@ -1,77 +0,0 @@
# Disk1 Phase 2 - NTFS to XFS Migration
Stand: 2026-05-25 06:15 CEST. Ziel erreicht: `/mnt/disk1` wurde von `ntfs3` auf XFS migriert, ohne produktive Compose-Pfade zu aendern. Container nutzen weiter `/mnt/user/...`.
## Preflight
| Check | Ergebnis |
|---|---|
| Disk1 Mount | `/dev/md1p1` auf `/mnt/disk1`, `ntfs3`, 5.5T, 1.7T genutzt, 3.8T frei |
| Cache Mount | `/dev/nvme0n1p1` auf `/mnt/cache`, `xfs`, 1.9T, 100G genutzt |
| H: Backup-Ziel | `H:\`, Label `Externe HDD`, NTFS, 8T, 5.96T frei, healthy |
| Compose-Binds | Keine Treffer fuer direkte `/mnt/disk1`, `/mnt/cache`, `/mnt/disks`, `/mnt/remotes` Binds |
| SMB-Zugriff | `\\Kallilabcore\services`, `documents`, `photos`, `media` erreichbar |
## Zu sichernde Shares
| Share | Groesse laut Preflight |
|---|---:|
| `services` | 451M |
| `documents` | 196M |
| `photos` | 23G |
| `backups` | 2.2G |
| `media` | 1.7T |
| `finance` | 0 |
| `projekte` | 92K |
Zusaetzliche Disk1-Top-Level-Pfade: `scripts` 3.3M, `isos` 0, `System Volume Information` 0.
## Backup-Strategie
- Zielroot: `H:\kallilab-recovery\disk1-phase2-2026-05-23`.
- Kritischer Linux-/GitOps-Pfad `services` wird zusaetzlich als Tar-Archiv ueber SSH gesichert, damit Linux-Metadaten erhalten bleiben.
- User-Shares werden per SMB/Robocopy kopiert, mit Logs und anschliessender Zaehler-/Groessenverifikation.
- Keine produktiven Datenpfade werden geloescht.
## Gates
1. Backup komplett und verifiziert.
2. Dienste-Freeze vorbereitet und letzte Dumps frisch.
3. Direkt vor Format/Array-Prozedur: Operator-Bestaetigung im konkreten Moment.
## Backup-Ergebnis
Stand: 2026-05-24 13:07 CEST.
| Bereich | Sicherungsart | Ergebnis |
|---|---|---|
| `media` | Robocopy/SMB nach `H:\kallilab-recovery\disk1-phase2-2026-05-23\shares\media` | 2722 Dateien, 1677.11 GiB, Manifestvergleich: 0 missing, 0 size mismatch, 0 extra |
| `services` | Host-Tar auf Unraid Cache, danach binaer per `scp` nach H: | `services.tar`, 0.441 GiB, `tar -tf` gueltig |
| `documents` | Host-Tar auf Unraid Cache, danach binaer per `scp` nach H: | `documents.tar`, 0.192 GiB, `tar -tf` gueltig |
| `photos` | Host-Tar auf Unraid Cache, danach binaer per `scp` nach H: | `photos.tar`, 22.876 GiB, `tar -tf` gueltig |
| `backups` | Host-Tar auf Unraid Cache, danach binaer per `scp` nach H: | `backups.tar`, 2.099 GiB, `tar -tf` gueltig |
| `finance` | Host-Tar auf Unraid Cache, danach binaer per `scp` nach H: | `finance.tar`, leerer Share, `tar -tf` gueltig |
| `projekte` | Host-Tar auf Unraid Cache, danach binaer per `scp` nach H: | `projekte.tar`, klein, `tar -tf` gueltig |
| Disk1-Extras `scripts`, `isos` | Host-Tar auf Unraid Cache, danach binaer per `scp` nach H: | `disk1-extra.tar`, 0.003 GiB, `tar -tf` gueltig |
Hinweis: Erste Tar-Versuche per PowerShell-Redirect wurden verworfen, weil PowerShell den binaeren SSH-Stream als UTF-16 geschrieben hatte. Die ungueltige `media.tar` und unvollstaendige SMB-Teilkopien fuer `services`/`documents` wurden vom Backup-Ziel entfernt, damit nur verwertbare Sicherungen uebrig bleiben.
## Abschluss
Stand: 2026-05-25 06:15 CEST.
| Check | Ergebnis |
|---|---|
| Freeze-Dumps | `pre-backup-dumps.sh` vor Format ausgefuehrt; nach Wiederanlauf erneut erfolgreich, 15 kanonische Dump-Artefakte juenger als 24 h |
| Disk1 Filesystem | `/dev/md1p1` auf `/mnt/disk1`, `xfs`, 5.5T, 1.8T genutzt, 3.7T frei |
| Restore `media` | 2722 Dateien, 1,800,782,188,226 Bytes; finaler Manifestvergleich: 0 missing, 0 size mismatch, 0 extra |
| Restore Tar-Shares | `services`, `documents`, `photos`, `backups`, `finance`, `projekte` und Disk1-Extras aus H:-Freeze-Archiven nach `/mnt/disk1` extrahiert |
| Docker/Services | 49 Container laufend, 0 stopped, 0 unhealthy, 0 starting |
| Smoke-Tests | `git.kaleschke.info` 200, `komodo.kaleschke.info` 200, `borg.kaleschke.info` 302, `jellyfin.kaleschke.info` 302, `monitoring.kaleschke.info` 302 |
| Service-Mounts | Gitea SSH `:222` offen; Jellyfin und Plex sehen `/media`; Prometheus readiness ok |
| Backup-Lauf | Borg-UI Repository `appdata-critical`, letzter Job `completed`, Archiv `Taegliche-Sicherung-2026-05-25T05:52:44.157`, `nfiles=100221` |
| Temp-Cleanup | `/mnt/cache/disk1-phase2-tmp/*.tar` nach H:-Verifikation geloescht; Cache weiter XFS mit ca. 1.7T frei |
Hinweis zum Docker-Wiederanlauf: Nach dem manuellen Docker-Start liefen die Container, aber Healthcheck-Execs scheiterten wegen `dockerd` mit `XDG_RUNTIME_DIR=/run/user/0`. Ein gezielter Docker-Neustart mit unsetztem `XDG_RUNTIME_DIR` behob den Runtime-Fehler; danach wurden alle Healthchecks gruen. `monitoring-prometheus` war durch den geplanten Docker-Stop sauber beendet und wurde als bestehender Container wieder gestartet.
Offener Nachlauf: Die Array-Parity-Anzeige zeigte nach Abschluss weiter `mdNumDisabled=1`, `mdNumInvalid=1` und `mdResyncAction=check P`, waehrend beide beteiligten Devices `rdevStatus=DISK_OK` und `rdevNumErrors=0` melden. Parity-Zustand separat in Unraid pruefen und keinen Parity-/Disk-Slot ohne Operator-Entscheid aendern.
+25 -8
View File
@@ -1,6 +1,6 @@
# External Dependencies - KalliLab CORE # External Dependencies - KalliLab CORE
Status: Initiale Betreiber-Baseline 2026-05-26; konkrete Account-Recovery-Codes und Besitznachweise muessen ausserhalb des Repos bestaetigt werden. Status: Betreiber-Baseline 2026-06-01; Account-Recovery, Schluessel und Besitznachweise bleiben ausserhalb des Repos.
## Zweck ## Zweck
@@ -10,12 +10,15 @@ Dieses Dokument beschreibt externe Anbieter und Konten, von denen Betrieb, Recov
| Anbieter / System | Zweck | Kritikalitaet | Recovery-Auswirkung | Zugang / Besitz | Notfallplan | | 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-Backup vom 2026-06-01 liegt extern/off-system in Vaultwarden; Reset-Pin und Account-Pfad bereithalten; Remote-HTTPS/FTP/FTPS aus dem Internet sind aus |
| 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 | | 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 | | 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; Hetzner 2FA/Recovery/Zahlung sind bestaetigt; Storage Box ist SSH-only, Maintenance-Key liegt in Vaultwarden; Borg `append-only` wird per Operator-Entscheidung nicht umgesetzt |
| 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 | | 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 | | 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 | | GMX SMTP | Authelia Notifier, Vaultwarden-Einladungen, Ops-Report-Mail | mittel | Mail-Notifier und Vaultwarden-Einladungen fallen aus; Login selbst nicht zwingend | GMX-Konto; SMTP-Secrets liegen hostseitig | ntfy/zweiter SMTP als Fallback pruefen |
| OpenAI API | Paperless-GPT LLM und Vision-OCR | mittel | Automatische Dokument-Titel, Tags, Korrespondenten und LLM-OCR fallen aus; Paperless selbst laeuft weiter | OpenAI-Projekt/API-Key ausserhalb Repo | Key in Vaultwarden/Komodo sichern, bei Offenlegung rotieren; Kosten/Usage im OpenAI-Projekt beobachten |
| Let's Encrypt | TLS-Zertifikate | hoch | Cert-Erneuerung faellt aus | automatisch via Traefik und Cloudflare DNS-Challenge | Cert-Expiry Alert einrichten; Cloudflare-Token und Traefik-Storage pruefen | | Let's Encrypt | TLS-Zertifikate | hoch | Cert-Erneuerung faellt aus | automatisch via Traefik und Cloudflare DNS-Challenge | Cert-Expiry Alert einrichten; Cloudflare-Token und Traefik-Storage pruefen |
| Container Registries | Image Pulls von Docker Hub, GHCR, LSCR, Gitea Registry u. a. | mittel | Redeploy/Update blockiert | ueberwiegend oeffentlich; keine produktiven Registry-Tokens im Repo | Gepinnte Digests und lokale Runtime helfen kurzfristig; Updates geplant und einzeln deployen | | Container Registries | Image Pulls von Docker Hub, GHCR, LSCR, Gitea Registry u. a. | mittel | Redeploy/Update blockiert | ueberwiegend oeffentlich; keine produktiven Registry-Tokens im Repo | Gepinnte Digests und lokale Runtime helfen kurzfristig; Updates geplant und einzeln deployen |
| Plex Konto/Remote Access | Plex native Auth, ggf. Remote Access und Claim | mittel | Plex-Clients/Remote-Funktionen koennen ausfallen | Plex-Konto ausserhalb Repo; `PLEX_CLAIM` nur fuer Setup | LAN-Medienpfade bleiben lokal; Konto-Recovery separat sichern | | Plex Konto/Remote Access | Plex native Auth, ggf. Remote Access und Claim | mittel | Plex-Clients/Remote-Funktionen koennen ausfallen | Plex-Konto ausserhalb Repo; `PLEX_CLAIM` nur fuer Setup | LAN-Medienpfade bleiben lokal; Konto-Recovery separat sichern |
@@ -27,13 +30,14 @@ Authoritativ ist `docs/SECRETS_MAP.md`. Diese Liste markiert nur externe Abhaeng
| Secret | Zweck | Recovery-Hinweis | | 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 | | Cloudflare DNS API Token | ACME DNS-Challenge | Token-Rotation und Scope pruefen |
| GitHub Mirror Token | Push-Mirror | In Gitea/GitHub verwaltet, nicht im Repo | | GitHub Mirror Token | Push-Mirror | In Gitea/GitHub verwaltet, nicht im Repo |
| Tailscale Account Recovery | Tailnet-Zugang | Account-2FA/Recovery Codes sichern | | Tailscale Account Recovery | Tailnet-Zugang | Account-2FA/Recovery Codes sichern |
| SMTP Passwort | Authelia Mail | In Host-Secret, Fallback pruefen | | SMTP Passwort | Authelia Mail, Vaultwarden-Einladungen, Ops-Report-Mail | In Host-Secrets, Fallback pruefen |
| Domain-Registrar Recovery | Domain-Besitz und Zahlung | Account, 2FA und Zahlungsweg ausserhalb des Homelabs sichern | | Domain-Registrar Recovery | Domain-Besitz und Zahlung | Account, 2FA und Zahlungsweg ausserhalb des Homelabs sichern |
| Hetzner Storage Box Zugang | Off-site Backup-Ziel | SSH-/Web-Zugang und Zahlungsweg extern sichern | | Hetzner Storage Box Zugang | Off-site Backup-Ziel | Account 2FA aktiv, Recovery Key offline gedruckt, Zahlungsweg ok; Maintenance-Key und Storage-Box-Passwort in Vaultwarden |
| OpenAI API Key | Paperless-GPT GPT-Zugriff | Als Stack ENV / Vaultwarden-Eintrag sichern; bei Verdacht auf Leak rotieren |
## Ausfall-Szenarien ## Ausfall-Szenarien
@@ -41,7 +45,8 @@ Authoritativ ist `docs/SECRETS_MAP.md`. Diese Liste markiert nur externe Abhaeng
- Lokales Borg-Repo und aktuelle Dumps pruefen. - Lokales Borg-Repo und aktuelle Dumps pruefen.
- Keine destruktiven Host-Aenderungen starten, solange Off-site unklar ist. - 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 neu bewerten bei Hetzner-Problemen, stark wachsendem Datenwert oder geaenderter Betreiber-Praeferenz.
### Cloudflare Account/DNS gestoert ### Cloudflare Account/DNS gestoert
@@ -56,6 +61,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. - 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. - 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 ist am 2026-06-01 auf 8.25 (`154.08.25`) beobachtet; weitere Updates nur in einem geplanten Service-Fenster einspielen, weil Reboot WAN/Tailscale-Aufbau unterbricht.
### Domain verloren oder Registrar-Zugriff verloren ### Domain verloren oder Registrar-Zugriff verloren
- Gitea/GitHub Mirror und lokale IP/Tailscale-Pfade fuer Recovery nutzen. - Gitea/GitHub Mirror und lokale IP/Tailscale-Pfade fuer Recovery nutzen.
@@ -65,4 +77,9 @@ Authoritativ ist `docs/SECRETS_MAP.md`. Diese Liste markiert nur externe Abhaeng
| Datum | Ergebnis | Naechste Aktion | | 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 (damals FRITZ!OS 8.21) als WAN-/Router-Abhaengigkeit aufgenommen; Ausfallschutz nicht eingerichtet | FRITZ!OS-Update am 2026-06-01 als `154.08.25` beobachtet |
| 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-/Dienste-Review am 2026-06-01 nachgezogen |
| 2026-06-01 | Externer Betreibercheck vorbereitet: `docs/EXTERNAL_OPERATOR_RUNBOOK.md` und `ops/maintenance/check-external-operator.sh`; FRITZ!Box meldet per TR-064 FRITZ!OS `154.08.25`, Public DNS hat keine AAAA-Records, Host hat keine globale Provider-IPv6 | Account-Hygiene am 2026-06-01 nachgezogen |
| 2026-06-01 | FRITZ!Box-UI gegengeprueft und Konfig-Backup extern/off-system in Vaultwarden abgelegt; Remote-HTTPS auf FRITZ!Box-UI aus, FTP/FTPS auf Speichermedien aus, nur `443/tcp -> 192.168.178.58`, keine aktive IPv6-Freigabe sichtbar, UPnP-Selbstfreigaben aus | Bei naechstem Router-Update erneut exportieren |
| 2026-06-01 | Hetzner-Account-Hygiene erledigt: externe Mail ok, Zahlung ok, 2FA aktiv, Recovery Key offline gedruckt. Storage Box: SSH aktiv, SMB/WebDAV aus, Maintenance-Key in Vaultwarden, Borg-Repo-Zugriff nach Recovery geprueft. Borg `append-only` wird bewusst nicht umgesetzt. | Keine Folgeaktion |
+143
View File
@@ -0,0 +1,143 @@
# External Operator Runbook
Stand: 2026-06-01
Dieses Runbook schliesst die Betreiber-Aufgaben, die nicht vollstaendig aus dem
Repo automatisierbar sind: Hetzner-Account-Hygiene, Borg-Append-Only und
FRITZ!Box-Servicefenster. Keine Secret-Werte ins Repo schreiben.
## 1. Vorher pruefen
Auf dem Unraid-Host:
```bash
bash /mnt/user/services/homelab-infra/ops/maintenance/check-external-operator.sh
```
Erwarteter Stand vom 2026-06-01:
- FRITZ!Box 7590 meldet FRITZ!OS `154.08.25`.
- FRITZ!Box IPv6-Firewall meldet `FirewallEnabled=1`; `InboundPinholeAllowed=1` bedeutet, dass IPv6-Freigaben technisch moeglich sind und in der UI gegengeprueft werden muessen.
- Public DNS fuer `*.kaleschke.info` liefert A-Records auf `217.249.115.154`, keine AAAA-Records.
- Host hat keine globale Provider-IPv6-Adresse; sichtbar ist nur Tailscale-IPv6 `fd7a:115c:a1e0::2c01:62b2`.
- WAN-Smoke gegen die Public-IP: `443/tcp` offen, `80/tcp` und `222/tcp` geschlossen.
- FRITZ!Box-UI-Gegencheck vom 2026-06-01: Remote-HTTPS auf die FRITZ!Box ist aus, FTP/FTPS auf Speichermedien ist aus, nur `443/tcp -> 192.168.178.58` ist als WAN-Freigabe sichtbar, keine aktive IPv6-Freigabe sichtbar, UPnP-Selbstfreigaben aus.
- FRITZ!Box-Konfig-Backup vom 2026-06-01 ist extern/off-system in Vaultwarden abgelegt; Datei und Kennwort nicht ins Repo schreiben.
- Borg UI nutzt `borg 1.4.x`; Repository `appdata-critical` liegt auf Hetzner Storage Box `ssh://...your-storagebox.de:23/./hetzner_borg_appdata_critical`.
- Hetzner-Account-Hygiene vom 2026-06-01: 2FA aktiv, Recovery Key offline gedruckt, Zahlung ok.
- Storage Box vom 2026-06-01: SSH aktiv, SMB/WebDAV aus, separater Maintenance-Key in Vaultwarden, produktiver Borg-UI-Key und Maintenance-Key nach Passwort-Recovery getestet.
- Restore-Freshness: `Critical 0`, `Warnings 0`.
## 2. Hetzner Account-Hygiene
Im Hetzner-/Storage-Box-Konto pruefen und extern/off-system dokumentieren:
| Punkt | Soll |
|---|---|
| Passwort | stark, eindeutig, im Passwortmanager |
| 2FA | aktiv, Recovery Key offline auffindbar |
| Kontakt-E-Mail | aktuell und ohne Homelab-Abhaengigkeit erreichbar |
| Zahlungsweg | gueltig, Fallback bekannt |
| Storage Box | Produkt, Benutzer und Rechnungsstatus sichtbar |
| SSH/SFTP/WebDAV/SMB | nur benoetigte Protokolle aktiv |
| Recovery | Kundennummer, Login-Pfad und Support-Pfad extern notiert |
Im Repo nur das Datum der Bestaetigung dokumentieren, nie Zugangsdaten.
## 3. Borg Append-Only
Status: **bewusst nicht umgesetzt**.
Ziel der Haertung waere gewesen: Der produktive Backup-Client darf neue Archive
schreiben, aber nicht normal prune/delete/compact als unbeschraenkter Client
ausfuehren.
Hetzner dokumentiert Borg-Zugriff auf Storage Boxen inklusive `--remote-path`
fuer Borg-Versionen; fuer Borg 1.4 wird `--remote-path=borg-1.4` empfohlen.
Hetzner bestaetigt auch, dass append-only moeglich ist. Borg selbst setzt
append-only pro SSH-Key typischerweise ueber einen forced command in
`authorized_keys` um.
Getestetes Zielmodell, aber **nicht auf der produktiven Storage Box aktiv**:
```text
command="borg-1.4 serve --append-only --restrict-to-repository /home/hetzner_borg_appdata_critical",restrict ssh-ed25519 <backup-public-key> borg-ui-append-only
ssh-ed25519 <maintenance-public-key> borg-maintenance
```
Hinweise:
- Stand 2026-06-01: Ein forced-command-Versuch auf der produktiven
Storage-Box-`authorized_keys` brach die Key-Authentifizierung. Recovery
erfolgte per Storage-Box-Passwort und Upload einer bereinigten
`authorized_keys` mit Borg-UI-Key und Maintenance-Key.
- Operator-Entscheidung 2026-06-01: Append-only wird fuer dieses Homelab nicht
umgesetzt. Der zusaetzliche Schutz steht hier nicht im Verhaeltnis zum
Betriebsrisiko und zur Komplexitaet.
- Pfad auf der Storage Box vor dem Eintragen pruefen. Bei Hetzner werden Pfade
im Borg-Repo haeufig relativ als `./repo-name` verwendet; in
`authorized_keys` muss der serverseitige Pfad zur Storage-Box-Home-Struktur
passen.
- Der produktive Borg-UI-Key bleibt bewusst uneingeschraenkt, damit die
produktiven Backups laufen.
- Ein separater Maintenance-Key bleibt fuer bewusste Retention/Prune/Compact
noetig und liegt in Vaultwarden; lokale temporare Key-Dateien wurden geloescht.
- Append-only verhindert nicht, dass ein kompromittierter Client Archive als
geloescht markiert; es verhindert die unmittelbare physische Entfernung.
Nach einem Vorfall keine unbeschraenkte Schreiboperation ausfuehren, bevor
die Borg-Transaktionen bewertet wurden.
Nach Aenderung:
1. Einen regulaeren Borg-Lauf abwarten oder manuell starten.
2. `check-external-operator.sh` ausfuehren.
3. In `docs/AUDIT_2026-05-25_TODO.md` nur das Ergebnis dokumentieren.
## 4. FRITZ!Box-Servicefenster
Vor dem Fenster:
1. Familie informieren: Internet/Telefonie koennen kurz weg sein.
2. Aktuellen Repo-Stand und Borg-Freshness pruefen.
3. FRITZ!Box-Konfig exportieren: `System -> Sicherung -> Sichern`.
4. Sicherungsdatei nicht ins Repo legen; im Passwortmanager/off-system ablegen.
In der FRITZ!Box:
| Bereich | Soll |
|---|---|
| `System -> Update` | FRITZ!OS aktuell; am 2026-06-01 per TR-064 `154.08.25` beobachtet |
| `Internet -> Freigaben -> Portfreigaben` | nur `443/tcp -> 192.168.178.58:443` |
| `Internet -> Freigaben -> FRITZ!Box-Dienste` | Remote-HTTPS auf FRITZ!Box-UI aus; FTP/FTPS auf Speichermedien aus |
| IPv6-Portfreigaben | keine aktiven Freigaben; insbesondere kein `222/tcp`, kein Admin-Port |
| Selbststaendige Portfreigaben/UPnP | fuer `Kallilabcore` aus; neue Geraete nur bewusst erlauben |
| Gastnetz | bleibt aus, solange keine Gastnetz-Policy gepflegt wird |
| Ausfallschutz | bewusst aus; nur neu bewerten, wenn ein Mobilfunk-Fallback gewuenscht ist |
Nach dem Fenster:
```bash
bash /mnt/user/services/homelab-infra/ops/maintenance/check-external-operator.sh
```
Dann in `docs/NETWORK_INVENTORY.md` aktualisieren:
- FRITZ!OS-Version
- IPv6-Status
- aktive Portfreigaben
- FRITZ!Box-Dienste aus dem Internet
- Datum der Konfig-Sicherung
## Quellen
- Hetzner Docs: Storage Box Zugriff mit SSH/rsync/BorgBackup, inklusive
Borg-Versionen, `--remote-path` und Append-Only-Hinweis:
<https://docs.hetzner.com/storage/storage-box/access/access-ssh-rsync-borg/>
- BorgBackup Docs: `borg serve --append-only` und forced commands in
`authorized_keys`:
<https://borgbackup.readthedocs.io/en/stable/deployment/pull-backup.html>
- AVM FRITZ!Box Hilfe: IPv6-Portfreigaben werden separat verwaltet; eingehende
Zugriffe sind standardmaessig nicht offen:
<https://help.avm.de/fritzbox.php?hardware=145&language=en&oem=avme&set=009&topic=hilfe_internet_freigabe_ipv6>
- AVM FRITZ!Box Hilfe: Sicherung der FRITZ!Box-Einstellungen:
<https://help.avm.de/fritzbox.php?hardware=145&language=en&oem=avme&set=009&topic=hilfe_system_export>
+206 -26
View File
@@ -1,38 +1,218 @@
# Family Onboarding - KalliLab CORE # Familien-Willkommen - KalliLab CORE
Status: Entwurf. Zielgruppe sind Familienmitglieder, nicht Operatoren. Status: **Praxis-Onboarding** (2026-06-01). 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 | ## Was wir zuhause selbst betreiben
|---|---|---|---|---|
| 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 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 | 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.
|---|---|
| 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 |
## 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.
---
## Unser erster gemeinsamer Ablauf
Wir richten nicht alles auf einmal perfekt ein. Wichtig ist, dass drei Dinge
wirklich benutzt werden:
1. **Vaultwarden**: Passwoerter landen dort, nicht im Browser.
2. **Immich**: Handy-Fotos werden automatisch gesichert.
3. **Mealie**: Rezepte und Einkaufsliste werden gemeinsam ausprobiert.
Wenn diese drei Dinge laufen, ist das Familien-Onboarding praktisch erfolgreich.
## Vaultwarden zuerst
Vaultwarden ist die Grundlage fuer alle anderen Logins.
1. App **Bitwarden Passwortmanager** auf Handy und ggf. PC installieren.
2. Beim ersten Start die Server-URL auf `https://vault.kaleschke.info` setzen.
3. Mit dem persoenlichen Konto anmelden.
4. Master-Passwort gemeinsam festlegen und merken. Das Master-Passwort wird
nicht bei Michi gespeichert.
5. Browser-Passwortspeicher fuer neue Homelab-Passwoerter nicht verwenden.
6. Test: Einen neuen Eintrag "Test KalliLab" anlegen und wiederfinden.
Was in Vaultwarden gehoert:
- Homelab-App-Passwoerter
- wichtige Familien-Logins
- Recovery-Codes, wenn eine App welche zeigt
- keine losen Passwort-Zettel und keine Screenshots von Passwoertern
---
## 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. Das richtige Album / die normale Kamera-Galerie auswaehlen.
6. App einmal offen lassen, bis erste Fotos hochgeladen wurden.
7. Test: In der Weboberflaeche `https://immich.kaleschke.info` pruefen, ob die
ersten Fotos sichtbar sind.
8. 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.
---
## Rezepte und Einkaufsliste einrichten (Mealie)
Mealie soll nicht nur "auch da" sein, sondern wirklich genutzt werden.
1. `https://mealie.kaleschke.info` oeffnen.
2. Mit dem persoenlichen Konto anmelden.
3. Gemeinsam ein erstes echtes Rezept anlegen oder importieren.
4. Rezept mindestens einer Kategorie geben, z. B. `Alltag`, `Schnell`,
`Wochenende`, `Vegetarisch`.
5. Aus dem Rezept Zutaten auf die Einkaufsliste setzen.
6. Test: Einkaufsliste auf dem Handy oeffnen und einen Eintrag abhaken.
7. Optional: Einen Wochenplan fuer die naechsten 2-3 Tage anlegen.
Start-Regeln fuer Mealie:
- Rezepte nur dann speichern, wenn wir sie wirklich kochen wuerden.
- Namen schlicht halten: `Chili`, `Bolognese`, `Kartoffelsuppe`.
- Zutaten so eintragen, dass sie beim Einkaufen verstaendlich sind.
- Wenn ein Rezept nicht schmeckt: loeschen oder klar als "nicht wieder" markieren.
---
## 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.
---
## Onboarding-Checkliste fuer Michi
Diese Punkte gehoeren in das erste echte Familien-Onboarding. Keine Secret-Werte
in diese Datei schreiben.
| Status | Aufgabe | | Status | Aufgabe |
|---|---| |---|---|
| offen | Pro Dienst kurze Schritt-fuer-Schritt-Anleitung schreiben | | offen | Pro Familienmitglied Konto/Start-Passwort persoenlich uebergeben |
| offen | Konto-/2FA-Policy final entscheiden | | offen | Vaultwarden/Bitwarden-App auf Handy einrichten |
| offen | Immich Mobile Backup fuer alle Geraete testen | | offen | Testeintrag in Vaultwarden anlegen |
| offen | Vaultwarden Familienorganisation pruefen | | offen | Immich-App auf jedem Familien-Handy einrichten |
| offen | Immich-Backup mit ersten Fotos sichtbar pruefen |
| offen | Mealie mit erstem Rezept und Einkaufsliste praktisch ausprobieren |
| offen | Danach entscheiden, ob Nextcloud/Paperless/Plex direkt mitkommen oder spaeter |
## 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.
+7 -6
View File
@@ -1,6 +1,6 @@
# Hardware Inventory - KalliLab CORE # 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` Host: `Kallilabcore`
Letzte Pruefung: 2026-05-26 Letzte Pruefung: 2026-05-26
Naechster Review: 2026-08-26 Naechster Review: 2026-08-26
@@ -19,7 +19,7 @@ Dieses Dokument beschreibt die physische Basis des Homelabs. Es ist die Grundlag
| Unraid-Version | 7.2.4 | | Unraid-Version | 7.2.4 |
| Rolle | Single-Host Homelab, Docker Compose via Komodo | | Rolle | Single-Host Homelab, Docker Compose via Komodo |
| Boot-Medium | Samsung Flash Drive, 59.8G, FAT32 | | Boot-Medium | Samsung Flash Drive, 59.8G, FAT32 |
| Flash-Backup | In Borg-Scope aufgenommen, siehe `docs/MIGRATION_LOG.md` | | Flash-Backup | In Borg-Scope aufgenommen, siehe `docs/RESTORE_MATRIX.md` |
## CPU ## CPU
@@ -126,7 +126,7 @@ smartctl -a /dev/sdc
| Feld | Wert | | Feld | Wert |
|---|---| |---|---|
| USV vorhanden | Nicht validiert / keine erkannte USV | | USV vorhanden | Nein / keine erkannte USV |
| Modell | Kein APC/Eaton/CyberPower-Geraet per `lsusb` erkannt | | Modell | Kein APC/Eaton/CyberPower-Geraet per `lsusb` erkannt |
| Verbindung | `apcupsd` ist auf USB vorkonfiguriert, aber kein passendes USB-USV-Geraet sichtbar | | Verbindung | `apcupsd` ist auf USB vorkonfiguriert, aber kein passendes USB-USV-Geraet sichtbar |
| Software | `apcaccess` vorhanden; `apcupsd` laeuft nicht, `localhost:3551` liefert Connection refused | | 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. - Aktueller Befund 2026-05-26: keine funktionierende USV-Absicherung nachgewiesen.
- `apcupsd` ist zwar auf dem System vorhanden, aber nicht aktiv. - `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. - Operator-Entscheidung 2026-05-26: aktuell keine USV-Anschaffung.
- Naechste Entscheidung: echte USV anschliessen und Shutdown testen oder Risiko bewusst akzeptieren und dokumentieren. - 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 ## Stromverbrauch
@@ -159,7 +160,7 @@ Bewertung:
| Parity | Kleiner als neue groesste Datenplatte | Parity-Upgrade vor Datenplatten-Upgrade | | Parity | Kleiner als neue groesste Datenplatte | Parity-Upgrade vor Datenplatten-Upgrade |
| Boot-USB | Lesefehler oder Alter TBD | Flash-Backup verifizieren, Ersatzstick vorbereiten | | Boot-USB | Lesefehler oder Alter TBD | Flash-Backup verifizieren, Ersatzstick vorbereiten |
| RAM | Swap/OOM oder Immich/Nextcloud-Druck | Ausbau planen | | 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 ## Audit-Kommandos
+131
View File
@@ -0,0 +1,131 @@
# 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:/.
## Befund 2026-06-01
- Der Scheduled Task um 05:30 kopierte die aktuellen Dumps, brach aber mit Robocopy Exit-Code 8 ab, weil im Dump-Root alte `*-pre-*` Dateien und Migration-/Cutover-Verzeichnisse mit restriktiven Rechten lagen.
- Fix: `ops/h-drive-nearline/pull-critical-backups.ps1` kopiert fuer `borg-dumps-latest` nur noch die kuratierte Pflichtdatei-Liste und schliesst Migration-/Cutover-Verzeichnisse aus.
- Manueller Kontrolllauf 2026-06-01 08:25 erfolgreich: `borg-dumps-latest` Exit-Code 0, `gitea-bundles` Exit-Code 1 (Robocopy-Erfolg mit Kopien), Report `H:\kallilab-nearline-backups\_reports\nearline-pull-2026-06-01-082553.md`.
## 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 kuratierte Dumps ohne 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.
Der Borg-Dumps-Job ist eine Whitelist der aktuellen Nearline-Pflichtartefakte. Einmalige Migrations-Sicherungen, Pre-Major-Snapshots und Redis-Cutover-Verzeichnisse bleiben ueber Borg/Hetzner abgedeckt, sind aber kein H:/-Nearline-Pflichtbestand.
## 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.
-355
View File
@@ -1,355 +0,0 @@
# 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`.
## Aktueller Endstand
- Gitea Online ist der verbindliche Sollzustand.
- Komodo ist der einzige produktive Stack-Manager.
- Portainer CE ist entfernt.
- Firefly, Firefly-Fints und Semaphore sind entfernt.
- `monitoring/` ist der einzige aktive Observability-Stack; alte Repo-Pfade `ops/grafana-influxdb` und `ops/loki` sind entfernt.
- Borg UI ist produktiv, Dump-Automatisierung laeuft host-seitig und ein Restore-Smoke-Test wurde erfolgreich durchgefuehrt.
- GitHub Desktop ist der bevorzugte lokale Workflow fuer `Fetch`, `Pull`, `Commit` und `Push`.
- Mutable Image-Tags sind auf die aktuell laufenden Digests eingefroren.
---
## Historische Meilensteine
### 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.
### 2026-05-26 - Externe Abhaengigkeiten und Services-Recovery baseline dokumentiert
- `docs/EXTERNAL_DEPENDENCIES.md` von Template auf Betreiber-Baseline angehoben: Domain, Cloudflare, Hetzner, GitHub-Mirror, Tailscale, GMX, Let's Encrypt, Registries, Plex und mobile Push-Pfade sind mit Ausfallwirkung und Notfallplan dokumentiert.
- `docs/SERVICES_RECOVERY.md` finalisiert den Komodo-Bootstrap-Anker: `ops/komodo/docker-compose.yml` bleibt verbindlich; der `komodo`-Self-Stack hat keinen aktiven Gitea-Webhook und ist nicht der Recovery-Anker.
- Offene Off-Repo-Betreiberchecks bleiben Account-Besitz, 2FA-Recovery-Codes, Zahlungswege, Borg-Passphrase-Hinterlegung und Gitea-Bundle-/Mirror-Mechanik.
### 2026-05-26 - Policy-Warnings triagiert
- Plex `network_mode: host` wurde in den Policy-Ausnahmen als dokumentierte Discovery-Ausnahme erfasst.
- Mutable Tags bei `ddns-updater`, `glances` und `scrutiny` bleiben wegen vorhandener SHA256-Digests reproduzierbar gepinnt und werden im Policy-Report als Info-Ausnahmen sichtbar gehalten.
- `monitoring-influxdb3-core` bleibt als dokumentierte `user: "0"`-Ausnahme bewusst eine Warning, damit der Hardening-Punkt nicht aus dem Blick faellt.
### 2026-05-26 - Hardware-/Capacity-Baseline abgeschlossen
- Hardware-Inventar auf Host-Befund aktualisiert: BIOS AMI F21 vom 2025-06-19, Intel Raptor Lake SATA AHCI, Samsung NVMe Controller und Realtek RTL8125 2.5GbE mit aktuellem 1G-Link.
- RAM-Baseline dokumentiert: 4x 8 GB DDR4 ohne ECC, gemischte Module, aktuell 2133 MT/s konfiguriert.
- Capacity-Baseline dokumentiert: Cache 1.9T mit 97G genutzt (6 %), Disk1/User-Shares 5.5T mit 1.8T genutzt (33 %), lokale Backups 2.2G unter `/mnt/user/backups`.
- USV-Befund dokumentiert: `apcupsd` ist vorhanden und auf USB vorkonfiguriert, laeuft aber nicht; `apcaccess status` liefert Connection refused und `lsusb` zeigt keine erkannte USV. Power-Loss bleibt damit eine offene Betreiberentscheidung.
### 2026-05-26 - Komodo/Gitea-Restdrift bereinigt
- Der alte Komodo-Stack `grafana` wurde als historischer Altstand inert gemacht: keine Repo-Dateipfade, kein Webhook, keine alte Stack-ENV, keine `missing_files`/`remote_errors`. Rollback bleibt Git-Historie, nicht der alte Komodo-Stack.
- Der Gitea-Hook `35` fuer den alten `grafana`-Stack bleibt inaktiv. Der nicht sinnvolle `komodo`-Self-Hook `11` wurde deaktiviert, weil Komodo selbst nicht per Gitea-Webhook auf `master` deployed wird.
- Ein kurz sichtbarer Komodo-DB-Typfehler durch `updated_at` als Float wurde im selben Kontrollfenster auf nativen Mongo `Long` korrigiert; danach traten keine neuen `invalid type`-Fehler mehr auf.
- Nach der Bereinigung: aktive Gitea-Komodo-Hooks haben `0` Fehlstatus; `komodo-core`, `komodo-periphery`, `komodo-mongo`, `nextcloud` und die aktuellen `monitoring-*` Container laufen weiter.
### 2026-05-26 - Monitoring-Altstaende aus aktivem Repo entfernt
- Die abgeloesten Pfade `ops/grafana-influxdb` und `ops/loki` wurden per `git rm` aus dem aktiven Repo entfernt. `monitoring/` bleibt der einzige Observability-Zielstack.
- Live-Check vor dem Cleanup: nur `monitoring-grafana`, `monitoring-promtail`, `monitoring-influxdb3-core` und `monitoring-loki` laufen; alte Container `grafana`, `influxdb3-core`, `loki` und `alloy` sind nicht vorhanden.
- Rollback erfolgt bei Bedarf ueber Git-Historie, nicht ueber parallel gepflegte Compose-Verzeichnisse.
- Im selben GitOps-Kontrollfenster wurde Gitea-Webhook `35` fuer den alten `grafana`-Rollback-Stack als inaktiv bestaetigt. Der aktive Nextcloud-Hook `36` hatte einen Signaturfehler; sein Secret wurde ohne Ausgabe des Werts aus der Komodo-Stack-Konfiguration zurueck nach Gitea synchronisiert.
### 2026-05-26 - AdGuard Admin-Port auf Tailscale-Soll begrenzt
- Host-Audit per SSH gegen `Kallilabcore` durchgefuehrt: Tailscale IPv4 ist `100.80.98.33`, LAN-IP ist `192.168.178.58/24`, Gateway `192.168.178.1`.
- Repo-Soll fuer `host-services/Adguard/docker-compose.yml` geaendert: DNS `53/tcp+udp` bleibt unveraendert, die Admin-UI bindet nun auf `100.80.98.33:8082:80`.
- Architektur, Service-Katalog, Repo-Map, Netzwerk-Inventar und AI-Kontext wurden an das neue Modell angepasst: keine Traefik-/Authelia-2FA-Umstellung, aber keine LAN-weite Admin-Bindung mehr.
- Live-Deploy wurde nach Fast-Forward des AdGuard-Workspaces auf `5cb4017` mit `docker compose -p adguard ... up -d` ausgefuehrt. Validierung erfolgreich: `ss -ltnp` zeigt `100.80.98.33:8082`, DNS via `@127.0.0.1` und `@192.168.178.58` funktioniert, `http://100.80.98.33:8082/` liefert HTTP 302, `http://192.168.178.58:8082/` ist nicht mehr erreichbar.
- Nachpruefung des GitOps-Pfads: Gitea-Hook `1` zeigt auf Komodo-Stack `69c7b9e26b77cd827811b9d0` und lieferte HTTP 200. Komodo-Deploys fuer AdGuard scheiterten zunaechst im `Git pull` einmal an `.git/index.lock` und danach an `fatal: Cannot rebase onto multiple branches`; der Workspace wurde aufgeraeumt und steht sauber auf `origin/master`.
### 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.
- 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.
### 2026-05-25 - Unraid Flash-Backup in Borg-Scope aufgenommen
- `pre-backup-dumps.sh` erzeugt zusaetzlich zu den DB-Dumps ein sensibles `unraid-flash-config.tar.gz` aus `/boot/config` inklusive SHA256 und Manifest unter `/mnt/user/backups/borg/dumps/latest`.
- Da `/local/borg-dumps` bereits Teil des Borg-Scopes ist, wird das Flash-Konfigurationsartefakt mit dem bestehenden Hetzner/Borg-Backup historisiert. Downloadbare Plugin-Paketarchive unter `/boot/config/plugins/*/` werden aus dem Artefakt ausgeschlossen; Restore-relevante Konfiguration bleibt enthalten.
- Live-Erstlauf erfolgreich: `pre-borg.sh` lieferte `critical_count=0`, Freshness `Critical: 0`, `unraid-flash-config.tar.gz` ist 297 KiB gross, `0600 root:root`, SHA256-Pruefung `OK`, 356 Archiv-Eintraege, Manifest fuer Unraid `7.2.4`. Der Borg-UI-Job `Taegliche Sicherung` ist aktiv und umfasst `/local/borg-dumps`; der naechste planmaessige Hetzner-Lauf nimmt das neue Flash-Artefakt mit. Der Host-Repo-Clone unter `/mnt/user/services/homelab-infra` wurde wegen eines fehlgeschlagenen Fast-Forward-Checkouts frisch geklont; der vorherige Stand liegt archiviert unter `/mnt/user/services/_archive/homelab-infra-pre-refresh-20260525-194209`.
### 2026-05-25 - Monitoring-Zielstack finalisiert und Uptime Kuma entfernt
- `monitoring` und `glance` wurden auf Commit `b6bbca4` deployed; Komodo zeigt fuer beide `latest_hash` = `deployed_hash` = `b6bbca4` ohne `remote_errors`. Die zehn `monitoring-*` Container laufen, `monitoring.kaleschke.info` und `glance.kaleschke.info` leiten anonym zu Authelia, Prometheus ist ready und Loki `/ready` liefert `ready`.
- Alte Monitoring-Altcontainer `grafana`, `influxdb3-core`, `loki` und `alloy` sind in Docker nicht vorhanden; `ops/grafana-influxdb` und `ops/loki` bleiben nur als Rollback-/Migrationsreferenz im Repo. Der noch aktive Gitea-Hook `35` des alten `grafana`-Rollback-Stacks wurde deaktiviert, damit zukuenftige Pushes den Altstand nicht reaktivieren.
- Uptime Kuma wurde durch Blackbox/Prometheus/Grafana ersetzt: aktive Blackbox-Zielliste enthaelt 19 HTTPS-Ziele, `uptime.kaleschke.info` ist dort nicht mehr enthalten und liefert nach Stack-Removal 404. Der Komodo-Stack `uptime-kuma` wurde gestoppt/destroyed/geloescht, Gitea-Webhook `23` deaktiviert, Appdata nach `/mnt/user/appdata/_archive/uptime-kuma-removed-2026-05-25` und der alte Stack-Workspace nach `/mnt/user/services/stacks/_archive/uptime-kuma-removed-2026-05-25` verschoben.
- Authelia-Hostconfig wurde mit Backup `configuration.yml.pre-uptime-removal-20260525-164343.bak` um den toten `uptime.kaleschke.info`-Eintrag bereinigt, validiert und neu gestartet. Prometheus wurde wegen eines `Stale NFS file handle` auf der gebundenen Konfigurationsdatei per Komodo-Restart neu gemountet.
### 2026-05-25 - AdGuard Admin-Port bewusst LAN-direkt belassen
- Strategische Option `adguard.kaleschke.info` hinter Traefik/Authelia-2FA wurde bewertet, aber vom Operator bewusst verworfen, weil der Betriebsweg einfach bleiben soll. AdGuard bleibt als dokumentierte Ausnahme mit DNS `53/tcp+udp` und Admin `8082:80` LAN-direkt; keine Live-Aenderung an AdGuard, Authelia oder Traefik wurde vorgenommen.
### 2026-05-25 - Borg-Passphrase Host-Secret verifiziert
- Erwartete Host-Secret-Datei `/mnt/user/appdata/secrets/borg_repo_passphrase.txt` aus der bestehenden Borg-UI-Repo-Konfiguration erzeugt, mit `root:root` und Modus `600` gesichert und per `borg info` gegen das Hetzner-Borg-Repo verifiziert. Analoge Offline-Hinterlegung bleibt bewusste Operator-Aufgabe; Secret-Wert wurde nicht ausgegeben oder dokumentiert.
### 2026-05-25 - Dashboard auf Glance konsolidiert
- Glance bleibt das einzige Homelab-Dashboard; Homepage wurde aus dem Zielbild entfernt. Authelia-Default-Redirect, Monitoring-Blackbox-Ziele, Cert-Check-Domains und Glance-Konfiguration zeigen nicht mehr auf `home.kaleschke.info`; Homepage wurde via Komodo-API gestoppt/destroyed, der Komodo-Stack geloescht, der alte Gitea-Webhook deaktiviert und Appdata nach `/mnt/user/appdata/_archive/homepage-removed-2026-05-25` verschoben.
### 2026-05-25 - Jellyfin aus Zielbild entfernt
- Plex-Smoke-Test erfolgreich (`/identity` HTTP 200, Container healthy, `/data/movies` und `/photos` sichtbar). Jellyfin wurde repo-seitig entfernt, aus Authelia-Baseline und Zielbild-Doku ausgetragen, via Komodo-API gestoppt/destroyed, der Komodo-Stack geloescht und Appdata nach `/mnt/user/appdata/_archive/jellyfin-removed-2026-05-25` verschoben; Plex bleibt einziger Medienserver.
### 2026-05-25 - Externer Repo-Mirror eingerichtet
- Gitea erlaubt fuer Repo-Migrationen und Mirror-Targets gezielt `github.com` und nutzt explizite externe DNS-Resolver. `Micha/homelab-infra` spiegelt nun als privater GitHub-Push-Mirror nach `michaelkaleschke-spec/homelab-infra`; erster manueller Sync erfolgreich, Gitea `push_mirror.last_error` leer. Token-Werte bleiben ausschliesslich in Gitea/GitHub und werden nicht dokumentiert.
### 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.
### 2026-05-25 - Disk1 Phase 2 abgeschlossen
- Disk1 wurde nach H:-Freeze-Backup und finalem Service-Freeze von NTFS/`ntfs3` auf XFS migriert.
- Restore verifiziert: `media` final 2722 Dateien und 1,800,782,188,226 Bytes mit 0 missing/extra/size mismatch; Tar-Shares und Disk1-Extras aus den H:-Freeze-Archiven wiederhergestellt.
- Docker/Services nach XDG-Runtime-Fix wieder stabil: 49 Container laufend, 0 stopped, 0 unhealthy, 0 starting; Gitea, Komodo, Borg, Jellyfin und Monitoring per Smoke-Test erreichbar.
- Borg-UI meldet den letzten Backup-Job `completed`; `pre-backup-dumps.sh` wurde nach Wiederanlauf erneut ausgefuehrt und 15 kanonische Dump-Artefakte sind juenger als 24 h.
- `posture-check` erwartet Disk1 nun standardmaessig als XFS (`ALLOW_DISK1_NTFS=0`).
### 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.
- Keine Hinweise auf erfolgreichen Zugriff: Gitea-Container ohne Restart/OOM, nur User `micha`, keine neuen User der letzten 30 Tage, keine neuen Repos, SSH-Keys oder Access-Tokens im Untersuchungsfenster.
- Live-Prometheus lief noch mit der alten Regel `rate(...[5m]) > 0`; die bereits im Repo vorbereitete Regel `increase(...[5m]) >= 5` wurde auf den Live-Mount kopiert und per Prometheus-Reload aktiviert.
- Gitea-Registrierung und OpenID-Signup wurden geschlossen: `DISABLE_REGISTRATION=true`, `REGISTER_EMAIL_CONFIRM=true`, `ENABLE_OPENID_SIGNIN=false`, `ENABLE_OPENID_SIGNUP=false`; Signup-Seite zeigt danach "Registration is disabled", OpenID-Login liefert 403.
### 2026-05-18 - Komodo Webhooks vollstaendig abgeglichen
- Live-Befund auf `Kallilabcore`: Komodo hatte fuer mehrere aktuelle Stacks `webhook_enabled: true`, aber Gitea enthielt noch nicht fuer alle aktuellen Stack-IDs aktive Webhooks.
- In der Gitea-Datenbank wurden aktive Webhooks fuer `monitoring` (`6a08d5297707b0930ab95c72`), `glance` (`6a09d7347707b0930ab96eae`), `grafana` (`69f31ecdf65eb72b757c497d`) und `nextcloud` (`69e519085fd5e8bc51f121f0`) nach dem bestehenden Komodo-Hook-Muster angelegt.
- Stale aktive Gitea-Hooks auf nicht mehr vorhandene bzw. alte Komodo-Stack-IDs wurden deaktiviert.
- Abgleich danach: 30 aktive Gitea-Komodo-Hooks fuer 30 Komodo-Stacks mit aktiviertem Webhook; `hermes` bleibt in Komodo bewusst `webhook_enabled: false`.
- Netzwerkpfad aus dem `gitea`-Container zu `komodo-core:9120` wurde erfolgreich verifiziert; `last_status=0` fuer neue Hooks bleibt bis zum ersten Push erwartbar.
### 2026-05-19 - Posture-Check Host-Version verifiziert
- Ursache fuer wiederholte ntfy-Warnings war nicht mehr die Repo-Logik allein, sondern dass auf dem Unraid-Host noch die alte Skriptversion unter `/mnt/user/services/homelab-infra/services/posture-check/posture-check.sh` ausgefuehrt wurde.
- Host-Skript wurde mit Backup ersetzt und mit `SEND_NTFY=0` direkt auf dem Host verifiziert.
- Ergebnis des echten Host-Laufs: `status: ok`, `critical_count: 0`, `warning_count: 0`.
- Betriebsregel daraus: Bei Host-User-Scripts nach Repo-Aenderungen immer den tatsaechlich ausgefuehrten Host-Pfad und den Live-Output pruefen.
### 2026-05-19 - Borg-Scope fuer GitOps Host Automation erweitert
- Nach den Gitea-/Komodo-Webhook- und Posture-Check-Aenderungen wurde der Backup-Scope um Host-GitOps-Pfade erweitert.
- Borg UI mountet kuenftig `/mnt/user/services` read-only als `/local/services`.
- In `all-important-sources.txt` wurden `/local/services/homelab-infra`, `/local/services/stacks` und `/local/services/posture-check` aufgenommen.
- `pre-backup-dumps.sh` wurde auf dem Host ausgefuehrt; frische Dumps fuer `gitea.sqlite.dump` und `komodo-mongo.archive.gz` liegen unter `/mnt/user/backups/borg/dumps/latest`.
- Wirksam wird der neue `/local/services`-Mount nach Redeploy/Recreate des `borg-ui`-Stacks.
### 2026-05-19 - Traefik-5xx Alert entstoert
- `HomelabTraefik5xx` hatte auf einzelne 5xx-Antworten reagiert, weil die Regel `rate(...[5m]) > 0` nutzte.
- Live-Befund fuer `gitea@docker`: zwei kurze `POST /` mit HTTP 500 von einer externen IP, danach durchgehend erfolgreiche Gitea-Checks; kein Container-Restart.
- Prometheus-Regel auf `increase(...[5m]) >= 5` geaendert, damit einzelne externe Fehlrequests keinen ntfy-Alarm ausloesen.
### 2026-05-17 - Glance Homelab-Dashboard vorbereitet
- `ops/glance` als geschuetztes Homelab-Dashboard unter `glance.kaleschke.info` vorbereitet.
- Glance zeigt HTTP-Monitore fuer Core, Apps und Ops, Docker-Containergruppen, Host-Snapshot und Bookmarks.
- Docker-Status laeuft nicht ueber einen direkten Socket-Mount in Glance, sondern ueber `glance-docker-socket-proxy` auf einem internen `glance_socket_net`.
- Die HTTP-Monitore nutzen oeffentliche URLs als Klickziel und interne `check-url`-Endpunkte auf `frontend_net`, damit Glance nicht vom externen Hairpin-/Auth-Pfad abhaengt.
- Das Immich Community-Widget wurde ergaenzt. Der API-Zugriff nutzt eine interne Service-URL und ein Stack-ENV-Token. Paperless, Scrutiny und Speedtest bleiben Kandidaten fuer einen spaeteren Widget-Pass, sobald die konkrete API-Ausgabe im Glance-Kontext sauber verifiziert ist.
- Das Dashboard-Layout wurde an `ginesjunior11/glance-dashboard-config` angelehnt: dunkleres blaues Theme, Zeitfortschrittsgruppe, farbige Dashboard-Icons, dichter `Homelab Status`, Server-Stats im Hauptbereich und eine zweite Seite `Infrastructure and Media`. Die rechte Home-Spalte zeigt WAN-Infos aus Speedtest Tracker, Speedtest-Livewerte, AdGuard-DNS-Stats, DNS/Ingress-Monitore und eine separate Netzwerk-Containergruppe.
### 2026-05-17 - Monitoring-Zielstack konsolidiert
- `monitoring/` als zentraler Observability-Zielstack fuer Prometheus, Loki, Promtail, Grafana, node-exporter, cAdvisor und InfluxDB 3 Core vorbereitet.
- `monitoring-grafana` nutzt den Repo-Standard `authelia@file,secure-headers@file` und Secrets per Datei statt Klartext-Stack-ENV.
- `monitoring-influxdb3-core` uebernimmt den LAN-only Writer-Endpunkt fuer Home Assistant (`8181` via `INFLUXDB_BIND_IP`).
- `ops/loki` und `ops/grafana-influxdb` sind abgeloeste Altstaende und bleiben nur als Rollback-/Migrationsreferenz im Repo.
### 2026-05-07 - Vaultwarden Restore-Test praktisch verifiziert
- Erster echter Vaultwarden-Mini-Restore gegen das produktive Borg-Repo `hetzner_borg_appdata_critical` erfolgreich durchgefuehrt.
- Restore lief isoliert nach `/mnt/user/backups/restore-lab/vaultwarden`, nicht gegen produktive Pfade.
- Testinstanz `restoretest-vaultwarden` wurde lokal auf `127.0.0.1:18080` gestartet; HTTP 200 und Login-Seite wurden erfolgreich bestaetigt.
- Report wurde unter `/mnt/user/backups/restore-reports/vaultwarden-2026-05-07.md` geschrieben.
- Fuer den praktischen Restore-Pfad wurden zwei hostseitige Voraussetzungen sichtbar und umgesetzt:
- `known_hosts` fuer das Hetzner-Ziel im `borg-ui`-Container
- Host-Secret-Datei `/mnt/user/appdata/secrets/borg_repo_passphrase.txt` fuer kuenftige Restore-Tests
- Testdaten unter `/mnt/user/backups/restore-lab/vaultwarden/data` wurden nach erfolgreichem Lauf wieder bereinigt.
### 2026-05-07 - Gitea Restore-Test praktisch verifiziert
- Erster echter Gitea-Mini-Restore gegen das produktive Borg-Repo `hetzner_borg_appdata_critical` erfolgreich durchgefuehrt.
- Restore lief isoliert nach `/mnt/user/backups/restore-lab/gitea`, nicht gegen produktive Pfade.
- Testinstanz `restoretest-gitea` wurde lokal auf `127.0.0.1:13000` und `127.0.0.1:12222` gestartet.
- HTTP 200, HTML-Titel und lokaler SSH-Port wurden erfolgreich bestaetigt.
- Report wurde unter `/mnt/user/backups/restore-reports/gitea-2026-05-07.md` geschrieben.
- Testdaten unter `/mnt/user/backups/restore-lab/gitea/data` wurden nach erfolgreichem Lauf wieder bereinigt.
### 2026-05-07 - Paperless Restore-Test praktisch verifiziert
- Erster echter Paperless-Mini-Restore gegen das produktive Borg-Repo `hetzner_borg_appdata_critical` erfolgreich durchgefuehrt.
- Restore umfasste sowohl die Dateipfade als auch `postgresql17-paperless.dump` aus dem Borg-Archiv.
- Testinstanzen `restoretest-paperless`, `restoretest-paperless-postgres` und `restoretest-paperless-redis` liefen isoliert ohne Traefik.
- Login-Seite war lokal auf `127.0.0.1:18120` erreichbar.
- Der Dump-Import in Test-Postgres war erfolgreich; die Test-Datenbank enthielt `25` Dokumente.
- Report wurde unter `/mnt/user/backups/restore-reports/paperless-2026-05-07.md` geschrieben.
- Testdaten unter `/mnt/user/backups/restore-lab/paperless` wurden nach erfolgreichem Lauf wieder bereinigt.
### 2026-05-06 - Komodo Webhook Secret getrennt
- `KOMODO_WEBHOOK_SECRET` von `KOMODO_SECRET_KEY` getrennt und als eigene Stack-ENV-Variable dokumentiert.
- Gitea-Komodo-Webhooks mit bisherigem Core-Secret wurden auf den neuen `KOMODO_WEBHOOK_SECRET` umgestellt; bereits individuelle per-Stack-Webhook-Secrets wurden beibehalten.
- Host-`.env`, persistente Komodo-Compose und Gitea-Webhooks wurden als ein gemeinsamer Runtime-Schritt behandelt, damit Auto-Deploys nicht auseinanderlaufen.
- Ein stale Gitea-Webhook auf eine nicht mehr vorhandene Komodo-Stack-ID wurde deaktiviert, nicht geloescht.
### 2026-05-06 - Authelia GMX SMTP Notifier
- Authelia-Notifier von Filesystem-Log auf GMX SMTP (`submission://mail.gmx.net:587`) umgestellt.
- SMTP-Passwort bleibt ausserhalb des Repos unter `/mnt/user/appdata/secrets/authelia_smtp_password.txt`.
- Authelia-Compose erhaelt explizite DNS-Server, weil der SMTP-Startup-Check externe Namen wie `mail.gmx.net` aufloesen muss.
- Repo-Baseline und Host-Config muessen bei Auth-Aenderungen weiter bewusst gemerged und vor Restart validiert werden.
### 2026-05-06 - Hermes DR und Mail-Archiver Authelia
- Hermes Agent in `docs/RESTORE_MATRIX.md` und `docs/DISASTER_RECOVERY.md` mit Restore-Pfaden, Secret-/ENV-Hinweisen und Smoke-Test ergaenzt.
- Mail-Archiver Web-UI hinter `authelia@file,secure-headers@file` gelegt; App-eigene Auth bleibt als zweite Schutzschicht bestehen.
- M10/Komodo blieb unveraendert.
### 2026-05-05 - N-Aufraeum-Sprint
- Obsolete Compose-Top-Level-Felder `version: "3.9"` aus Immich, Mail Archiver und Paperless entfernt.
- Leere `env/domains.env.example` und `env/global.env.example` mit nicht geheimen Beispielwerten gefuellt.
- Veraltete `.keep`-Platzhalter aus Verzeichnissen mit echten Compose-/Repo-Inhalten sowie zwei reine Geister-Verzeichnisse (`host-services/plex`, `infra/dns`) entfernt.
### 2026-05-16 - Backup-Konsistenz und erster Hardening-Schnitt
- SQLite-Dumps fuer Gitea, Vaultwarden, Speedtest Tracker und Filebrowser werden containerseitig als `*.sqlite.dump` erzeugt und per Freshness-Check geprueft; Uptime Kuma wurde am 2026-05-25 aus dem aktiven Dump-Scope entfernt.
- `nextcloud.dump` und die Nextcloud-Userdaten sind als Option A im Borg-Scope dokumentiert.
- Filebrowser mountet keine breite `/mnt/user/appdata`-Flaeche mehr, sondern nur noch Documents, Photos, Projekte sowie eigenen App-State.
- Authelia Argon2id-Parameter in der Repo-Baseline auf `iterations: 3`, `memory: 65536`, `parallelism: 4` gesetzt; produktive Host-Config muss kontrolliert gemerged und mit Test-User validiert werden.
- Redis-Caches wurden auf `redis:7.4-alpine@sha256:...` vereinheitlicht; Nextcloud wurde mit Registry-validiertem Digest gepinnt.
- Eindeutig aufloesbare `latest@sha256`-Images wurden auf konkrete Tags umgestellt: Homepage `v1.12.3`, code-server `4.116.0`, Filebrowser `v2.63.2`, Speedtest Tracker `1.13.12`.
### 2026-05-05 - M3b versionierte App-Images digest-gepinnt
- Versionierte Nicht-Komodo-Images fuer BentoPDF, Mealie, Paperless, Paperless-GPT, AdGuard Home, Grafana, InfluxDB 3 Core und Traefik auf die am Host laufenden, manifest-validierten Digests gepinnt.
- `nextcloud:33.0.2-apache` wurde bewusst nicht in diesem Schritt gepinnt, weil der lokal gelistete Digest nicht als Registry-Manifest fuer `tag@sha256` validierbar war.
- Redis-Caches und Komodo/M10 blieben unveraendert.
### 2026-05-05 - M6/M7/M8 Doku-Konsolidierung
- `hermes.kaleschke.info` als produktive Hermes-Dashboard-Route hinter Traefik + Authelia in Architektur, Repo-Map und Service-Katalog ergaenzt.
- `grafana` und `influxdb3-core` laufen weiterhin als `user: "0"`; das wurde als Host-Appdata-Permissions-Ausnahme dokumentiert und nicht nebenbei geaendert.
- Tailscale-Ausnahme um `NET_ADMIN`, `NET_RAW` und `/dev/net/tun` ergaenzt.
- Komodo-Secret-/Webhook-Themen wurden bewusst nicht geaendert; Komodo-Aenderungen erfolgen nur gemeinsam mit dem Betreiber.
### 2026-05-05 - M3a stateful Digest-Pinning
- PostgreSQL 17 Datenhalter auf `postgres:17.9@sha256:5b96f1a16bd9768b060dd2ffe55cb6225c4d9ef4d214a8b21eb08134869a97e4` gepinnt (`postgresql17`, `mealie-postgres`, `nextcloud-postgres`).
- Immich pgvector-Postgres auf `tensorchord/pgvecto-rs:pg14-v0.2.0@sha256:739cdd626151ff1f796dc95a6591b55a714f341c737e27f045019ceabf8e8c52` gepinnt.
- Komodo Mongo auf `mongo:7.0.32@sha256:32979a1189dfdc44da3f5ed40d910495f5ad8f6f7f77556646f890a30b2d3f56` sowie Komodo Core/Periphery und Gitea auf die am Host laufenden Digests gepinnt.
- Redis-Caches wurden am 2026-05-16 auf `redis:7.4-alpine@sha256:...` vereinheitlicht; Redeploys erfolgen stackweise mit Smoke-Test, nicht parallel.
### 2026-05-04 - Komodo Self-Stack Drift auf persistenten Pfad zurueckgefuehrt
- Drift-Befund: `komodo-core` und `komodo-periphery` liefen aus `/tmp/komodo-core-repair.yml` bzw. `/tmp/komodo-periphery-repair.yml`; `komodo-mongo` verwies auf `/mnt/user/services/stacks/komodo/compose.yaml`, obwohl dieser Pfad fehlte.
- Vor Eingriff wurden die Repair-Dateien und zugehoerigen `/tmp/*.env`-Dateien unter `/mnt/user/appdata/komodo/_drift_backup_2026-05-04/` gesichert.
- Zusaetzlich wurde eine geschuetzte Recovery-ENV unter `/mnt/user/appdata/secrets/_komodo_stack_env_recovery_2026-05-04.env` abgelegt; diese Datei enthaelt Tier-1-Secret-Material und ist kein Dauerzustand.
- Vor dem Reconcile wurde das host-seitige Dump-Skript ausgefuehrt; `komodo-mongo.archive.gz` wurde frisch unter `/mnt/user/backups/borg/dumps/latest/` erzeugt.
- Persistenter Self-Stack wurde unter `/mnt/user/services/stacks/komodo/compose.yaml` aus `ops/komodo/docker-compose.yml` wiederhergestellt; `.env` wurde hostseitig aus der bestehenden Runtime-ENV abgeleitet.
- Der vollstaendige Dry-run haette auch `komodo-mongo` recreated und wurde daher nicht ausgefuehrt. Stattdessen wurden nur `komodo-core` und `komodo-periphery` gezielt mit `--no-deps --force-recreate` aus dem persistenten Pfad neu erstellt; `komodo-mongo` blieb unveraendert healthy.
- Smoke-Tests: `docker compose ls` zeigt fuer `komodo` nur noch `/mnt/user/services/stacks/komodo/compose.yaml`, Mongo pingt `{ ok: 1 }`, `https://komodo.kaleschke.info` liefert HTTP 200, und Periphery meldet sich am Core an.
- Die `/tmp/*repair.yml`-Dateien bleiben vorerst als Altlast erhalten und duerfen erst nach stabiler Laufzeit bewusst entfernt oder ins Drift-Backup verschoben werden.
### 2026-05-04 - Authelia ACL-Drift hostseitig gemerged
- Die produktive Authelia-Config ist groesser als die Repo-Datei, weil sie hostseitige OIDC-/Secret-Konfiguration enthaelt. Die Repo-Datei wurde daher als nicht geheime Baseline eingeordnet und nicht blind auf den Host kopiert.
- Host-Backup vor Aenderung: `/mnt/user/appdata/authelia/config/configuration.yml.bak-20260504-acl-sync`.
- Minimaler Host-Merge: `homepage.kaleschke.info` wurde aus der bypass-Liste entfernt, `komodo.kaleschke.info` aus der 2FA-Liste entfernt, und `default_redirection_url` wurde auf `https://home.kaleschke.info` gesetzt.
- `authelia validate-config` war erfolgreich; Authelia wurde neu gestartet und war danach healthy.
- Smoke-Tests: `home.kaleschke.info` liefert fuer anonyme Requests eine Authelia-Weiterleitung, `komodo.kaleschke.info` bleibt ueber native Komodo-Auth erreichbar.
### 2026-05-04 - Home Assistant InfluxDB LAN-Port und Drift-Runbook
- `influxdb3-core` fuer Home-Assistant-Writer auf LAN-Port `8181` vorbereitet und deployed.
- InfluxDB bleibt ohne Traefik-/Public-Route und haengt nicht im `frontend_net`.
- Fuer aktives Docker Host-Port-Publishing wurde zusaetzlich zum internen `grafana_influx_internal` das Compose-Netz `grafana_influx_lan` ergaenzt.
- Komodo Periphery dauerhaft um `/mnt/user/services:/mnt/user/services` und `frontend_net` ergaenzt, damit Stack-Workspaces und Gitea-Zugriff reproduzierbar funktionieren.
- `docs/GITOPS_DRIFT_RUNBOOK.md` angelegt, um lokale Git-Kopie, Gitea, Komodo Workspace, Docker Runtime und Host-Listener getrennt zu pruefen.
### 2026-03-28 - GitOps-Konsolidierung
- Komodo als primaeren Stack-Manager eingefuehrt.
- Portainer aus dem Zielbild herausgenommen.
- Traefik auf 100% Docker-Labels konsolidiert.
- `diun` entfernt; Update-Monitoring wird ueber Komodo abgedeckt.
### 2026-03-29 - Portainer abgeschaltet
- Portainer CE aus dem produktiven Betrieb entfernt.
- Komodo als alleinigen Stack-Manager festgezogen.
### 2026-04-13 bis 2026-04-15 - Borg-Rollout abgeschlossen
- `critical_infra` erfolgreich nach Borg gesichert.
- Pre-Backup-Dumps host-seitig ueber Unraid User Scripts etabliert.
- Dump-Zielpfad auf `/mnt/user/backups/borg/dumps` umgestellt.
- Restore-Smoke-Test fuer `postgresql17-globals.sql` und `gitea.db` erfolgreich nachgewiesen.
- Monitoring fuer Borg war historisch ueber `ntfy` und Uptime Kuma eingerichtet; seit 2026-05-25 ersetzt durch `ntfy`, Blackbox/Prometheus und Monitoring Grafana.
### 2026-04-15 - Repo- und Betriebsbereinigung
- Firefly, Firefly-Fints und Semaphore aus Repo und Homelab entfernt.
- GitHub Desktop als Standard-Workflow fuer den lokalen Sync festgelegt.
### 2026-04-17 - Sicherheits- und Doku-Abgleich
- `code-server` hinter `authelia@file,secure-headers@file` abgesichert.
- Traefik-Dashboard von `dashboard-auth@file` auf `authelia@file,secure-headers@file` umgestellt; BasicAuth-Hash aus dem Repo entfernt.
- Redis von Klartext in der Compose auf Secret-Datei unter `/mnt/user/appdata/secrets/redis_password.txt` umgestellt.
- Redis-Passwort bewusst **nicht** rotiert; Live-Passwort bleibt vorerst unveraendert.
- `mail-archiver` in der Architektur-Doku an den realen Traefik-Betrieb angepasst.
- `paperless-gpt` von `LOG_LEVEL=debug` auf `info` umgestellt.
- `speedtest-tracker` von `APP_DEBUG=true` auf `false` umgestellt.
- Mutable Image-Tags fuer produktive Stacks auf die aktuell laufenden Digests eingefroren, um Deployments reproduzierbar zu machen.
- `paperless-ngx` bleibt fuer `PAPERLESS_DBPASS` und `PAPERLESS_REDIS` vorerst bewusst bei Stack Environment Variables; keine Live-Migration auf `_FILE`, solange der aktuelle Stand stabil laeuft.
- Disaster-Recovery-Runbook und Restore-Matrix fuer den Totalausfall-/Wiederanlauf-Fall neu dokumentiert.
### 2026-04-19 - paperless-gpt Digest-Pin zurueckgenommen
- Der fuer `paperless-gpt` eingetragene Digest war syntaktisch ungueltig (63 statt 64 Hex-Zeichen) und wurde daher wieder auf `icereed/paperless-gpt:latest` zurueckgesetzt.
- Diese Ruecknahme ist bewusst eng auf einen einzelnen defekten Pin begrenzt und aendert keine anderen Digest-Festschreibungen.
- Die zwischenzeitlichen OCR-/Versions-Experimente fuer `paperless-gpt` wurden wieder auf den einfachen vorherigen Stand zurueckgenommen (`icereed/paperless-gpt:latest`, `VISION_LLM_MODEL=cnshenyang/qwen3-nothink:14b`), um den letzten bekannten Alltagszustand wiederherzustellen.
### 2026-04-19 - Nextcloud und Stirling-PDF vorbereitet
- `apps/nextcloud/docker-compose.yml` als offizieller Docker-Microservice-Stack mit `nextcloud:apache`, eigener PostgreSQL-Datenbank und eigenem Redis vorbereitet.
- Nextcloud folgt dem Repo-Standard `frontend_net` + app-internes Netz, nutzt `_FILE`-Secrets fuer Admin- und DB-Passwort und ist bewusst **nicht** hinter zentraler ForwardAuth, damit WebDAV/CardDAV und native Clients sauber funktionieren.
- `apps/stirling-pdf/docker-compose.yml` als geschuetzter Tool-Stack hinter `authelia@file,secure-headers@file` vorbereitet.
- Stirling-PDF nutzt persistente Pfade fuer `/configs`, `/logs`, `/pipeline`, `/customFiles` und `/usr/share/tessdata`; interne Stirling-Login-Funktion bleibt zugunsten des zentralen Traefik-/Authelia-Zugangs deaktiviert.
### 2026-04-30 - BentoPDF und Grafana/InfluxDB vorbereitet
- `stirling-pdf` repo-seitig durch `bentopdf` ersetzt; Domain `pdf.kaleschke.info` bleibt erhalten.
- BentoPDF laeuft als geschuetztes browserseitiges PDF-Tool hinter `authelia@file,secure-headers@file` und setzt zusaetzlich COOP/COEP-Header fuer SharedArrayBuffer-basierte Office-Konvertierung.
- `ops/grafana-influxdb` als neuer Monitoring-Stack vorbereitet und spaeter in Betrieb genommen.
- Grafana laeuft hinter Traefik + Authelia unter `grafana.kaleschke.info`.
- InfluxDB 3 Core bleibt ohne Public Route und wird ueber eine provisionierte Grafana-Datenquelle angebunden.
- Secrets fuer Grafana-Admin-Passwort, InfluxDB-Admin-Token und Grafana-Datasource-Token sind als Host-Dateien unter `/mnt/user/appdata/secrets/` dokumentiert.
---
## Dauerhafte Learnings
- Kein Live-Editing in Komodo; Git gewinnt immer gegen manuelle Drift.
- Webhooks koennen nach einem Push sofort einen Deploy ausloesen.
- Rollback soll bevorzugt ueber saubere Git-Commits und bekannte Good States erfolgen, nicht ueber History-Rewrites auf `master`.
- Doku soll Endzustaende beschreiben, nicht veraltete Zwischenstaende konservieren.
+75 -21
View File
@@ -1,7 +1,7 @@
# Network Inventory - KalliLab CORE # Network Inventory - KalliLab CORE
Status: Initialer Host-Audit erfasst, Router-/VLAN-Details offen. Status: Host-Audit erfasst; Router-Baseline und Portfreigaben-UI bereinigt; FRITZ!Box-Remote-Dienste aus; IPv6-Exposure technisch und per UI entschaerft.
Letzte Pruefung: 2026-05-26 Letzte Pruefung: 2026-06-01
## Zweck ## Zweck
@@ -11,14 +11,27 @@ Dieses Dokument beschreibt Router, DNS, Tailscale, Portfreigaben und Netztrennun
| Feld | Wert | | Feld | Wert |
|---|---| |---|---|
| Anschluss / Provider | TBD | | Anschluss / Provider | DSL, Telekom |
| Router-Modell | TBD | | Bandbreite (FRITZ!Box-UI) | ca. 87,3 Mbit/s Download, ca. 36 Mbit/s Upload |
| Firmware | TBD | | Router-Modell | FRITZ!Box 7590 |
| Firmware | FRITZ!OS 8.25 (`154.08.25` per TR-064 am 2026-06-01) |
| Router-IP | 192.168.178.1 | | 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 | | Lokales Subnetz | 192.168.178.0/24 |
| IPv6 aktiv | TBD | | IPv6 aktiv | Windows-Client hat Provider-IPv6; Host hat keine globale Provider-IPv6, nur Tailscale-ULA |
| DynDNS / DDNS | Cloudflare via `ddns-updater`, Details TBD | | DynDNS / DDNS | Cloudflare via `ddns-updater` (kein FRITZ!Box-DynDNS in Nutzung) |
| Heimnetz-Geraete (FRITZ!Box-UI) | 35 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 ist am 2026-06-01 per TR-064 auf `154.08.25` beobachtet; FRITZ!Box-Konfig-Backup `Einstellungen_FRITZ.Box_7590_154.08.25_01.06.26_1318.export` wurde extern/off-system in Vaultwarden abgelegt.
- `Internet -> Freigaben -> FRITZ!Box-Dienste` ist am 2026-06-01 geprueft: Internetzugriff auf die FRITZ!Box per HTTPS ist aus, FTP/FTPS-Zugriff auf Speichermedien ist aus.
## DNS ## DNS
@@ -50,14 +63,50 @@ tailscale ip -6
## Portfreigaben und Exposure ## Portfreigaben und Exposure
### FRITZ!Box (WAN -> Host)
Aktiver Soll-Stand nach Operator-Bereinigung und UI-Gegencheck 2026-06-01:
| 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-06-01 erneut geprueft und 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 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-06-01 deaktiviert**; danach nur noch `Kallilabcore` in der Portfreigabenliste sichtbar |
### Host (lokal beobachtbar)
| Port | Ziel | Zweck | Bewertung | | Port | Ziel | Zweck | Bewertung |
|---:|---|---|---| |---:|---|---|---|
| 80/tcp | Traefik | HTTP->HTTPS / ACME | erwartet | | 80/tcp | Traefik | HTTP->HTTPS / ACME | nur LAN, keine WAN-Freigabe noetig |
| 443/tcp | Traefik | HTTPS | erwartet | | 443/tcp | Traefik | HTTPS | WAN-Freigabe in FRITZ!Box erwartet |
| 222/tcp | Gitea SSH | Git SSH | dokumentierte Ausnahme | | 222/tcp | Gitea SSH | Git SSH | nur LAN/Tailscale; keine WAN-Freigabe |
| 53/tcp+udp | AdGuard | DNS | dokumentierte Ausnahme | | 53/tcp+udp | AdGuard | DNS | LAN-only, dokumentierte Ausnahme |
| 8082/tcp | AdGuard Admin | Admin UI | Repo-Soll: nur `100.80.98.33:8082`, DNS-Port 53 unveraendert | | 8082/tcp | AdGuard Admin | Admin UI | Bind nur `100.80.98.33:8082` (Tailscale), nicht im LAN exponiert |
| 8181/tcp | InfluxDB 3 Core | LAN Writer fuer Home Assistant | LAN-only, Bind-IP pruefen | | 8181/tcp | InfluxDB 3 Core | Home Assistant / Ecowitt Writer | 2026-05-31 effektiv nur `127.0.0.1:8181`, nicht LAN-exponiert |
Pruefkommando: Pruefkommando:
@@ -70,11 +119,12 @@ docker ps --format "{{.Names}}: {{.Ports}}" | sort
| Netz | Status | Bemerkung | | Netz | Status | Bemerkung |
|---|---|---| |---|---|---|
| LAN | 192.168.178.0/24 | Hauptnetz, Host `192.168.178.58` | | LAN | 192.168.178.0/24 | Hauptnetz, Host `192.168.178.58`, FRITZ!Box meldet 35 aktive Geraete |
| Gast-WLAN | TBD | Zugriff auf AdGuard Admin muss ausgeschlossen sein | | WLAN 2,4 / 5 GHz | aktiv, SSID `Fritzi` | Standard-WLAN, im LAN-Adressbereich, kein eigener Adressraum |
| IoT-Netz | TBD | Zugriff auf AdGuard Admin muss ausgeschlossen sein | | 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` | | 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 ## Docker-Netze
@@ -101,6 +151,10 @@ docker network inspect backend_net | jq '.[0].Internal'
| Thema | Status | Naechster Schritt | | 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 | | 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 | | FRITZ!Box-Portfreigaben mit Repo-Soll abgleichen | **erledigt 2026-06-01** | 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-Selbstfreigaben sind aus. Aktiver Soll-Stand: ausschliesslich `443/tcp -> 192.168.178.58`. |
| IPv6 Exposure | offen | Router und Traefik/Cloudflare pruefen | | FRITZ!Box-Dienste aus dem Internet | **erledigt 2026-06-01** | `Internet -> Freigaben -> FRITZ!Box-Dienste`: HTTPS-Zugriff auf die FRITZ!Box aus dem Internet aus; FTP/FTPS auf Speichermedien aus. |
| Home Assistant InfluxDB Bind | offen | Effektive Listener-Adresse pruefen | | FRITZ!OS Update und Konfig-Backup | **erledigt 2026-06-01** | TR-064 meldet `154.08.25`; Konfig-Export liegt extern/off-system in Vaultwarden, Kennwort und Datei bleiben ausserhalb des Repos. |
| 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 | technisch und per UI entschaerft | Public DNS liefert keine AAAA-Records fuer `*.kaleschke.info`; Host hat keine globale Provider-IPv6. TR-064 meldet IPv6-Firewall aktiv und Pinholes grundsaetzlich erlaubt; FRITZ!Box-UI zeigt keine aktiven IPv6-Freigaben, keine Admin-/SSH-Freigaben. |
| 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. |
-41
View File
@@ -1,41 +0,0 @@
# Aktuelle Restliste - KalliLab CORE
Stand: 2026-05-17
Diese Datei ersetzt die alte Sprint-Liste vom 2026-05-16. Die damaligen Backup-, Posture-, Logging- und Hardening-Bloecke sind weitgehend erledigt oder dokumentiert. Sie bleibt nur als kurze Restliste fuer die naechsten bewussten Arbeitspakete bestehen.
## Erledigt / nicht mehr offen
- Filebrowser-Hardening: breiter Appdata-Mount ist entfernt; Filebrowser mountet nur noch Documents, Photos, Projekte und eigenen App-State.
- Authelia Argon2id-Haertung: `iterations: 3`, `memory: 65536`, `parallelism: 4`, `key_length: 32`, `salt_length: 16` sind gesetzt.
- Gitea Webhook-Allowlist: `GITEA__webhook__ALLOWED_HOST_LIST` ist auf `komodo-core,localhost,127.0.0.1,192.168.178.0/24` eingeschraenkt.
- Backup-Konsistenz: SQLite-/Nextcloud-Dumps, Borg-Scope fuer Nextcloud-Daten, Restore-Matrix und Freshness-Checks sind umgesetzt.
- Posture-/Cert-/Drift-Checks: Skripte und Unraid User Scripts sind vorhanden und geplant.
- Monitoring-Zielstack: `monitoring/` buendelt Prometheus, Loki, Promtail, Grafana, node-exporter, cAdvisor und InfluxDB 3 Core im Repo-Zielzustand.
- Docker-Log-Rotation: Unraid-native Rotation ist dokumentiert; keine separate `/etc/docker/daemon.json` setzen.
- Disk1-NTFS-Migration Phase 2: am 2026-05-25 abgeschlossen; Disk1 ist XFS, `posture-check` akzeptiert NTFS nicht mehr als Standard.
## Morgen / bewusst spaeter
- Monitoring live finalisieren:
- Secrets `monitoring_grafana_admin_password.txt`, `monitoring_grafana_influxdb_token.txt`, `influxdb3_admin_token.json` auf dem Host pruefen/anlegen
- `monitoring` in Komodo deployen
- alte Stacks `ops/loki` und `ops/grafana-influxdb` nach erfolgreichem Smoke-Test stoppen
- `https://monitoring.kaleschke.info`, Prometheus Targets, Loki-Logs und InfluxDB-Datasource pruefen
- Home Assistant -> InfluxDB finalisieren:
- HA-Token/Writer final pruefen
- erste Messwerte in InfluxDB verifizieren
- Grafana-HA-/Wetter-Dashboard in `monitoring-grafana` aufbauen
- Hermes VM-Seite:
- Runner-VM, echte `.env`, SSH-Key und Dashboard/Gateway final zusammenfuehren
- NAS-Stack erst starten, wenn VM-Seite bereit ist
## Verbleibende bekannte Warnings
- `ddns-updater`, `glances`, `scrutiny`: nutzen noch `latest...@sha256`; spaeter durch konkrete Versionstags ersetzen, sofern upstream sinnvoll versioniert.
- `ops/grafana-influxdb` und `ops/loki`: bleiben nur noch als Rollback-/Migrationsreferenz im Repo, nach Live-Migration nicht parallel betreiben.
- `scrutiny`: bleibt `privileged: true`; dokumentierte SMART-Ausnahme, spaeter erneut pruefen.
## Regel
Neue Arbeit erst starten, wenn klar ist, ob sie eines der drei Morgen-Themen betrifft oder eine der bekannten Warnings bewusst abbaut.
+57
View File
@@ -0,0 +1,57 @@
# Documentation Index
Stand: 2026-06-01
Diese Datei trennt aktive Betriebsdokumentation von historischer Arbeitsdoku. 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 bleiben in der Git-Historie, aber nicht als dauerhafte Arbeitskopie.
## 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 |
| `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 |
| `EXTERNAL_OPERATOR_RUNBOOK.md` | Hetzner-/Borg-/FRITZ!Box-Betreibercheck |
| `CAPACITY_AND_LIFECYCLE.md` | Kapazitaet, Wachstum und Upgrade-Trigger |
## Monitoring und Automatisierung
| Datei | Zweck |
|---|---|
| `ALERT_RULES.md` | Prometheus-/ntfy-Regeln und Handlungslogik |
| `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 |
## Nutzer- und Planungsdoku
| Datei | Zweck |
|---|---|
| `FAMILY_ONBOARDING.md` | familienverstaendliche Nutzungsdoku |
| `AUDIT_2026-05-25_TODO.md` | kompakte Restliste aus dem Audit-Zyklus |
| `AI_CONTEXT.md` | kompakter Kontext fuer KI-Agenten |
Windows-Neuaufsetzen-Dokumente liegen nicht mehr in `docs/`, sondern im fachlich passenden Ordner `../ops/windows-reinstall/docs/`.
-89
View File
@@ -1,89 +0,0 @@
# Recovery Handoff - KalliLab CORE - 2026-05-15
Zweck: Startpunkt fuer einen neuen Chat, ohne das komplette Repo erneut zu lesen.
## Kontext
- 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.
- Keine Stacks starten, wenn ein Pfad/Setting gegen Storage Layout, Restore Matrix oder Architecture Master verstoesst.
## Host-Zustand
- Cache wurde erfolgreich von NTFS auf XFS neu formatiert.
- Verifiziert: `/mnt/cache` ist XFS auf `/dev/nvme0n1p1`.
- Disk1 bleibt vorerst NTFS auf `/mnt/disk1`; Migration ist Phase 2 nach stabilem Cache-Betrieb.
- Docker und Libvirt wurden nach dem Format wieder gestoppt.
- `/mnt/user/appdata` ist leer bzw. nur Basisverzeichnis; produktive Appdaten sind noch nicht restored.
- Share-Settings wurden nach Storage Layout korrigiert:
- `appdata`, `system`, `domains`: cache `only`
- `services`, `documents`, `photos`, `backups`, `media`, `finance`, `projekte`: cache `no`, include `disk1`
- `isos`: cache `yes`
- Backup alter Share-Configs: `/boot/config/shares.bak-20260515-pre-storage-layout`
## Image und Backups
- Full NVMe image liegt auf Windows `H:\kallilab-recovery\2026-05-14\nvme0n1-full-20260514.img`.
- `dd` exit code war `0`; Image-Groesse/Padding geprueft; Source-Raw-Hash war fertig.
- Image-Data-Hash wurde aus Zeitgruenden bewusst abgebrochen. Risiko wurde als ca. 1-3 Prozent eingeschaetzt.
- Hetzner-Borg-Archiv `Taegliche-Sicherung-2026-05-10T04:30:52.050` wurde als lesbare Recovery-Quelle verifiziert.
- Verifiziert wurden u. a. Vaultwarden SQLite, Gitea SQLite, Postgres-Dumps und Komodo Mongo-Archiv-Header.
- Lokaler Verify-Auszug liegt unter `H:\kallilab-recovery\2026-05-14\borg-verify-may10`.
## Entscheidungen seit dem Cache-Rebuild
- WD MyBookLive Duo wird komplett aus dem Setup entfernt.
- Backrest wird komplett aus dem aktiven Setup entfernt.
- Borg ist alleinige Backup-Technologie.
- Appdata Backup Plugin bleibt deaktiviert; WD-Ziele wurden aus aktiver Host-Konfiguration geleert.
- Unassigned Devices SMB-Remote fuer `//MYBOOKLIVEDUO/Public` wurde aus aktiver Host-Konfiguration entfernt.
- Backrest User Script `check_backrest_hetzner` wurde aus Schedule/Cron entfernt.
- Host-Konfig-Backup fuer diese Bereinigung: `/boot/config/cleanup-backup-20260515-remove-wd-backrest`
## Repo-Aenderungen im aktuellen Arbeitsbaum
Backrest wurde aus dem aktiven Zielbild entfernt:
- `ops/backrest/docker-compose.yml` geloescht
- `HOMELAB_ARCHITECTURE_MASTER_V2.md` aktualisiert
- `docs/REPO_MAP.md` aktualisiert
- `docs/SERVICE_CATALOG.md` aktualisiert
- `docs/RESTORE_MATRIX.md` aktualisiert
- `docs/AI_CONTEXT.md` aktualisiert
- `docs/DISASTER_RECOVERY.md` aktualisiert
- `ops/borg-ui/BACKUP_SCOPE.md` aktualisiert
- `ops/hermes-agent/services.json` aktualisiert
- `ops/hermes-agent/services.yaml` aktualisiert
- `ops/policy-checks/last-report.md` aktualisiert
Verifikation:
- `rg "/mnt/(cache|disk1|disks|remotes)" -g docker-compose.yml -g compose.yaml -g *.yml -g *.yaml` findet keine aktiven Compose/YAML-Treffer.
- `rg "ops/backrest|backrest.kaleschke|/mnt/user/appdata/backrest|192.168.178.86|MYBOOKLIVEDUO|WD-DUO"` findet nur historische/gewollte Hinweise.
- `python -m json.tool ops/hermes-agent/services.json` ok.
- `ops/hermes-agent/services.yaml` YAML ok.
- `ops/policy-checks/check_repo.ps1` ok: 29 Compose-Dateien, 0 Critical, 4 Warnings.
## Wichtigste Stop-Regeln
- Keine Container starten, solange Core-Pfade oder Share-Settings nicht gegen Storage Layout geprueft sind.
- Keine Backrest-/WD-Referenzen reaktivieren.
- Keine Bind-Mounts auf `/mnt/cache`, `/mnt/disk1`, `/mnt/disks`, `/mnt/remotes`.
- Keine Schreibaktionen auf Disk1 ausser bewusst noetig; Disk1 ist noch NTFS.
- Komodo nur gemeinsam und explizit anfassen.
- Erst Daten/Secrets restoren, dann Stacks einzeln starten und smoke-testen.
## Naechster sinnvoller Schritt
1. Repo-Aenderungen kurz reviewen und committen/pushen, bevor Komodo wieder produktiv wird.
2. DNS-Basis wiederherstellen:
- AdGuard: `/mnt/user/appdata/adguard/conf` aus Borg oder Image restoren; `work` kann frisch sein.
- Unbound: `/mnt/user/appdata/unbound/config` aus Borg oder Image restoren.
- Danach nur AdGuard + Unbound starten und DNS testen.
3. Danach Traefik + Authelia + Gitea/Vaultwarden in kleinen Schritten.
## 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.
+147
View File
@@ -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: Mai-2026-Audit 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)
+38 -229
View File
@@ -1,246 +1,55 @@
# Repository Map # 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. Kurzkarte des Repositories. Diese Datei ist bewusst kein zweites Handbuch; fuer
Details gilt immer die betroffene Compose-Datei oder das jeweilige Runbook.
Secret-Werte werden hier nicht dokumentiert. Aufgefuehrt werden nur Variablennamen, Secret-Namen und Pfade. ## Top-Level
## Ordnerstruktur
| Pfad | Zweck | | Pfad | Zweck |
|---|---| |---|---|
| `apps/` | Produktive Anwendungen und vorbereitete App-Stacks | | `apps/` | produktive Anwendungen und vorbereitete App-Stacks |
| `core/` | Basisdienste, aktuell Gitea | | `core/` | Basisdienste, aktuell Gitea |
| `docs/` | Betriebsdokumentation, Restore, Rollback, GitOps-Regeln | | `docs/` | aktive Betriebsdoku, Restore, Inventare, Arbeitsregeln |
| `env/` | globale nicht geheime Beispiel-Env-Dateien | | `env/` | nicht geheime Beispiel-Env-Dateien |
| `host-services/` | Host-nahe Dienste mit direkten Ports oder Host-Netz | | `host-services/` | host-nahe Dienste mit direkten Ports oder Host-Netz |
| `infra/` | technische Infrastruktur wie PostgreSQL, Redis, DDNS | | `infra/` | technische Infrastruktur wie PostgreSQL, Redis, DDNS |
| `ops/` | Operations-, Backup-, Monitoring- und Admin-Tools | | `monitoring/` | Prometheus, Grafana, Loki, InfluxDB 3 Core |
| `services/` | Host-seitige Betriebsskripte und Recovery-kritische Service-Hilfen | | `ops/` | Admin-, Backup-, Restore- und Wartungswerkzeuge |
| `security/` | Identity/Security-Dienste wie Authelia und Vaultwarden | | `security/` | Authelia, Vaultwarden und Security-Konfiguration |
| `traefik/` | Reverse Proxy Compose und dynamic File-Provider-Konfiguration | | `services/` | Host-seitige Betriebsskripte und Recovery-Hilfen |
| `traefik/` | Reverse Proxy und dynamic File-Provider-Konfiguration |
## Wichtige Dokumente ## Einstiegspunkte
| Datei | Bedeutung | | Datei | Wann lesen |
|---|---| |---|---|
| `README.md` | Einstieg und Kurzueberblick | | `README.md` | Repo-Einstieg |
| `HOMELAB_ARCHITECTURE_MASTER_V2.md` | operative Architektur-Quelle fuer Netzwerk, Zugriff und Ausnahmen | | `HOMELAB_ARCHITECTURE_MASTER_V2.md` | Architektur, Netzmodell, Ausnahmen |
| `docs/WORKFLOW.md` | GitOps-/No-Drift-Arbeitsregeln | | `docs/WORKFLOW.md` | vor operativen Aenderungen |
| `docs/GITOPS_DRIFT_RUNBOOK.md` | Pflichtmatrix fuer Git/Gitea/Komodo/Docker/Host-Drift | | `docs/SERVICE_CATALOG.md` | Service-Zweck, Pfade, Besonderheiten |
| `docs/DISASTER_RECOVERY.md` | Wiederanlauf nach Host-/Systemausfall | | `docs/DISASTER_RECOVERY.md` | echter Wiederanlauf |
| `docs/RESTORE_MATRIX.md` | Restore-Quellen, Dump-Artefakte und Smoke-Tests je Dienst | | `docs/RESTORE_MATRIX.md` | Restore-Quelle je Dienst |
| `docs/SERVICES_RECOVERY.md` | Recovery-kritische `/mnt/user/services`-Pfade, Gitea-Mirror und Komodo-Bootstrap | | `docs/SECRETS_MAP.md` | Secret-Namen und Pfade ohne Werte |
| `docs/HARDWARE_INVENTORY.md` | Hardware-, Disk-, SMART-, USV- und Strom-Inventar | | `docs/GITOPS_DRIFT_RUNBOOK.md` | Git/Gitea/Komodo/Docker/Host-Drift |
| `docs/NETWORK_INVENTORY.md` | Router, DNS, Tailscale, Portfreigaben und Netztrennung | | `docs/AUDIT_2026-05-25_TODO.md` | aktuelle Restliste |
| `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/AUDIT_2026-05-25_TODO.md` | Operative Arbeitsliste aus dem Audit vom 2026-05-25; Authelia-2FA bewusst geparkt |
| `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 |
## Relevante Nicht-Compose-Dateien ## Wichtige Skripte
| Datei | Zweck / Hinweis | | Datei | Zweck |
|---|---| |---|---|
| `traefik/dynamic/middlewares.yml` | zentrale `secure-headers` und `authelia` ForwardAuth Middleware; manuelle Host-Sync-Ausnahme | | `ops/borg-ui/scripts/pre-backup-dumps.sh` | Dump-Erzeugung vor Borg |
| `traefik/dynamic/dashboards.yml` | leer; File-Provider-Platzhalter | | `ops/borg-ui/scripts/gitea-bundle-mirror.sh` | Gitea-Bundles fuer DR |
| `traefik/dynamic/tls.yml` | leer; File-Provider-Platzhalter | | `ops/restore-tests/run-restore-checks.sh` | Restore-Test-Einstieg |
| `security/authelia/configuration.yml` | versionierte Authelia-Baseline fuer nicht geheime ACL-/Session-/Storage-Einstellungen; manuelle Host-Merge-Pflicht, User-Daten, OIDC-Client-Konfiguration und Secret-Werte bleiben ausserhalb von Git | | `ops/restore-tests/schedule.md` | Restore-Test-Kadenz |
| `monitoring/prometheus/prometheus.yml` | Prometheus Scrape-Konfiguration fuer dedizierten Monitoring-Stack | | `services/posture-check/posture-check.sh` | Host-Posture-Check |
| `monitoring/loki/loki-config.yml` | Loki Filesystem/Retention-Konfiguration fuer dedizierten Monitoring-Stack | | `services/posture-check/export-prometheus-textfile.sh` | Borg-/Container-/Drift-Metriken |
| `monitoring/promtail/promtail-config.yml` | Promtail Docker-Socket-Discovery fuer dedizierten Monitoring-Stack | | `services/authelia-diff.sh` | Authelia ACL Repo-zu-Host-Vergleich |
| `monitoring/grafana/provisioning/*` | Grafana Datasource-/Dashboard-Provisioning fuer Prometheus und Loki | | `ops/h-drive-nearline/pull-critical-backups.ps1` | H:/ Nearline-Pull |
| `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/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` |
| `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 |
| `monitoring/stack.env.example` | `INFLUXDB_BIND_IP` Default `127.0.0.1`; im Zielzustand fuer Home Assistant auf LAN-IP setzen |
| `ops/komodo/stack.env.example` | Komodo Stack-ENV-Beispiel, Secret-Werte nicht enthalten |
## Stack-Inventar ## Arbeitsregel
### Apps Neue Doku nur anlegen, wenn sie dauerhaft als Runbook, Inventar oder Restliste
gebraucht wird. Einmalige Audits, Prompt-Kopien und lange Verlaufsprotokolle
| Stack | Compose | Services / Images | Traefik Hosts | Networks | Ports | Abhaengigkeiten | gehoeren in Git-Commits, nicht als neue Markdown-Dateien.
|---|---|---|---|---|---|---|
| BentoPDF | `apps/bentopdf/docker-compose.yml` | `bentopdf` -> `bentopdfteam/bentopdf:2.8.4` | `pdf.kaleschke.info` | `frontend_net` | keine | Traefik + Authelia; COOP/COEP Middleware |
| 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` |
| 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 |
| ntfy | `apps/ntfy/docker-compose.yml` | `ntfy` -> `binwiederhier/ntfy:latest@sha256:...` | `ntfy.kaleschke.info` | `frontend_net` | keine | mobile Push via upstream `ntfy.sh` |
| Paperless-ngx | `apps/paperless/docker-compose.yml` | `paperless` -> `ghcr.io/paperless-ngx/paperless-ngx:2.20.10` | `paperless.kaleschke.info` | `frontend_net`, `backend_net` | keine | shared PostgreSQL + Redis; DB/Redis via Stack ENV |
| Paperless-GPT | `apps/paperless-gpt/docker-compose.yml` | `paperless-gpt` -> `icereed/paperless-gpt:v0.24.0` | `paperless-gpt.kaleschke.info` | `frontend_net` | keine | Paperless API, Ollama/LLM config |
| Unbound | `apps/unbound/docker-compose.yml` | `unbound` -> `shaanmajid/unbound:latest@sha256:...` | keine | `dns_net` | keine | Upstream Resolver fuer AdGuard |
### Core / Security / Infra
| 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 |
| 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 |
### Host Services
| 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`, `100.80.98.33:8082:80/tcp` | Unbound in `dns_net`; DNS-Port 53 ist direkte Ausnahme; Admin 8082 ist auf Tailscale-IP begrenzt |
| 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
| Stack | Compose | Services / Images | Traefik Hosts | Networks | Ports | Abhaengigkeiten |
|---|---|---|---|---|---|---|
| Borg UI | `ops/borg-ui/docker-compose.yml` | `borg-ui` -> `ainullcode/borg-ui:latest@sha256:...` | `borg.kaleschke.info` | `frontend_net` | keine | Borg repo, Dump-Scope, Restore-Ziel |
| code-server | `ops/code-server/docker-compose.yml` | `code-server` -> `lscr.io/linuxserver/code-server:4.116.0@sha256:...` | `code.kaleschke.info` | `frontend_net` | keine | Passwort-Datei, Workspace-Mounts |
| Filebrowser | `ops/filebrowser/docker-compose.yml` | `filebrowser` -> `filebrowser/filebrowser:v2.63.2@sha256:...` | `files.kaleschke.info` | `frontend_net` | keine | Documents/Photos/Projekte-Mounts, Admin-UI hinter Authelia |
| Glance | `ops/glance/docker-compose.yml` | `glance` -> `glanceapp/glance:v0.8.4`, `glance-docker-socket-proxy` -> `tecnativa/docker-socket-proxy:v0.4.2` | `glance.kaleschke.info` | `frontend_net`, `glance_socket_net` | keine | Homelab-Dashboard mit Home- und Infrastructure-Seite, Monitor-, Community-, Docker-, Internet-/DNS-/VPN- und Server-Stats-Widgets; aktives Community-Widget: Immich; Docker-API nur ueber internen Socket-Proxy |
| Glances | `ops/glances/docker-compose.yml` | `glances` -> `nicolargo/glances:latest-full@sha256:...` | `glances.kaleschke.info` | `frontend_net` | keine | Rootfs/Docker-Socket fuer Monitoring |
| Monitoring | `monitoring/docker-compose.yml` | `monitoring-prometheus`, `monitoring-alertmanager`, `monitoring-alertmanager-ntfy-bridge`, `monitoring-blackbox-exporter`, `monitoring-loki`, `monitoring-promtail`, `monitoring-grafana`, `monitoring-node-exporter`, `monitoring-cadvisor`, `monitoring-influxdb3-core`, optional `monitoring-grafana-dashboard-importer` | `monitoring.kaleschke.info` | `frontend_net`, `monitoring_net`, `monitoring_influx_lan` | `monitoring-influxdb3-core`: `${INFLUXDB_BIND_IP:-127.0.0.1}:8181:8181` | zentraler Zielstack fuer Prometheus/Loki/Grafana/InfluxDB; Alertmanager sendet via ntfy-Bridge nach `homelab-alerts`; Blackbox ersetzt Uptime-Kuma-Checks nach Parallelphase; Promtail nutzt Docker socket read-only; Dashboard-Importer nur via `bootstrap`-Profil |
| Hermes Agent | `ops/hermes-agent/docker-compose.yml` | `hermes-gateway`, `hermes-dashboard` -> local build from Dockerfile | `hermes.kaleschke.info` via `${HERMES_DASHBOARD_HOST}` | `hermes_net`, dashboard zusaetzlich `frontend_net` | `8642` nur expose intern | SSH runner, Home Assistant optional, LLM provider env; Dashboard hinter Authelia |
| Komodo | `ops/komodo/docker-compose.yml` | `komodo-core`, `komodo-mongo`, `komodo-periphery` | `komodo.kaleschke.info` | `frontend_net`, `komodo_net` | keine | Mongo, Docker socket, `/mnt/user/services` workspace mount, Gitea DNS override |
| Scrutiny | `ops/scrutiny/docker-compose.yml` | `scrutiny` -> `ghcr.io/starosdev/scrutiny:latest-omnibus@sha256:...` | `scrutiny.kaleschke.info` | `frontend_net` | keine | `privileged: true`, device mounts fuer SMART |
| Speedtest Tracker | `ops/speedtest/docker-compose.yml` | `speedtest-tracker` -> `lscr.io/linuxserver/speedtest-tracker:1.13.12@sha256:...` | `speedtest.kaleschke.info` | `frontend_net` | keine | App key/admin env, SQLite/config path |
### Traefik
| Stack | Compose | Service / Image | Hosts | Networks | Ports | Abhaengigkeiten |
|---|---|---|---|---|---|---|
| Traefik | `traefik/docker-compose.yml` | `traefik` -> `traefik:v3.6` | `traefik.kaleschke.info` | `frontend_net`, `backend_net` | `80:80/tcp`, `443:443/tcp` | Docker provider, Cloudflare DNS token secret, dynamic config |
## Traefik Hosts
| Host | Service | Zugriff |
|---|---|---|
| `auth.kaleschke.info` | Authelia | Auth provider / bypass fuer eigene Domain |
| `borg.kaleschke.info` | Borg UI | Traefik + Authelia |
| `cloud.kaleschke.info` | Nextcloud | Traefik, native App-Auth |
| `code.kaleschke.info` | code-server | Traefik + Authelia |
| `files.kaleschke.info` | Filebrowser | Traefik + Authelia |
| `git.kaleschke.info` | Gitea Web | Traefik |
| `glance.kaleschke.info` | Glance | Traefik + Authelia |
| `glances.kaleschke.info` | Glances | Traefik + Authelia |
| `hermes.kaleschke.info` | Hermes Dashboard | Traefik + Authelia |
| `immich.kaleschke.info` | Immich | 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 |
| `monitoring.kaleschke.info` | Monitoring Grafana | Traefik + Authelia |
| `ntfy.kaleschke.info` | ntfy | Traefik |
| `paperless.kaleschke.info` | Paperless-ngx | Traefik |
| `paperless-gpt.kaleschke.info` | Paperless-GPT | Traefik + Authelia |
| `pdf.kaleschke.info` | BentoPDF | Traefik + Authelia + COOP/COEP |
| `scrutiny.kaleschke.info` | Scrutiny | Traefik + Authelia |
| `speedtest.kaleschke.info` | Speedtest Tracker | Traefik + Authelia |
| `traefik.kaleschke.info` | Traefik Dashboard | Traefik + Authelia |
| `vault.kaleschke.info` | Vaultwarden | Traefik |
## Networks
| 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 |
| `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 |
| `nextcloud_internal` | Compose-intern | Nextcloud, Nextcloud Postgres, Nextcloud Redis |
| `monitoring_net` | Compose-/Stack-Netz bridge | Prometheus, Loki, Promtail, Monitoring-Grafana, node-exporter, cAdvisor; Traefik fuer Metrics-Scrape |
| `monitoring_influx_lan` | Compose-intern bridge | InfluxDB Host-Port-Publishing fuer LAN Writer im zentralen Monitoring-Stack |
| `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 als Repo-Compose-Stack unter `host-services/plex/` |
## Volumes und Datenpfade
| Bereich | Wichtige Pfade |
|---|---|
| Traefik | `/mnt/user/appdata/traefik/dynamic`, `/mnt/user/appdata/traefik/letsencrypt`, Cloudflare Secret |
| 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` |
| 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` |
| 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` |
| 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` |
| AdGuard | `/mnt/user/appdata/adguard/work`, `/mnt/user/appdata/adguard/conf` |
| Tailscale | `/mnt/user/appdata/tailscale` |
| Borg UI | `/mnt/user/appdata/borg-ui/data`, `/mnt/user/appdata/borg-ui/cache`, `/mnt/user/backups/borg/dumps`, selected restore/source mounts |
| code-server | `/mnt/user/appdata/code-server`, `/mnt/user/services/dev` |
| Filebrowser | `/mnt/user/documents`, `/mnt/user/photos`, `/mnt/user/projekte`, Filebrowser database/config paths |
| Glance | Repo-Konfiguration unter `ops/glance/config/glance.yml`; keine produktive Datenpersistenz; Docker-Socket nur am internen Proxy |
| Glances | `/`, Docker socket, `/etc/os-release` |
| Scrutiny | `/mnt/user/appdata/scrutiny/*`, `/run/udev`, selected `/dev/...` disks |
| Speedtest | `/mnt/user/appdata/speedtest-tracker/config` |
| Monitoring | named volumes `prometheus_data`, `loki_data`, `promtail_positions`, `grafana_data`; InfluxDB-Persistenz unter `/mnt/user/appdata/influxdb3/data` und `/mnt/user/appdata/influxdb3/plugins`; Provisioning im Repo unter `monitoring/grafana/provisioning`; Dashboards unter `monitoring/grafana/dashboards`; historische Altstandsdaten unter `/mnt/user/appdata/grafana`, `/mnt/user/appdata/loki` und `/mnt/user/appdata/alloy` nicht blind loeschen |
| Hermes Agent | `/mnt/user/appdata/hermes-agent/data`, `/mnt/user/appdata/hermes-agent/ssh`, SSH private key path |
| Komodo | `komodo_keys`, `/mnt/user/appdata/komodo/core`, `/mnt/user/appdata/komodo/mongo`, `/mnt/user/appdata/komodo/periphery`, `/mnt/user/services` |
## Secrets und Env-Hinweise
| Dienst / Stack | Secret-/Env-Hinweise ohne Werte |
|---|---|
| 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` |
| 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 |
| Mail Archiver | `MAILARCHIVER_DB_CONNECTION`, `MAILARCHIVER_AUTH_PASSWORD` als Stack ENV |
| Glance | `GLANCE_IMMICH_API_KEY`, `GLANCE_ADGUARD_USERNAME`, `GLANCE_ADGUARD_PASSWORD`, `GLANCE_SPEEDTEST_API_KEY` als Stack ENV fuer Community-/Live-Widgets |
| Speedtest | `APP_KEY`, `ADMIN_PASSWORD` als Stack ENV |
| Nextcloud | Admin User, Admin Password, Postgres Password via Secret-Dateien |
| Komodo | `KOMODO_SECRET_KEY`, `KOMODO_WEBHOOK_SECRET`, `KOMODO_JWT_SECRET`, `KOMODO_MONGO_PASSWORD`, `KOMODO_PERIPHERY_PASSKEY`; Mongo Passwort-Datei |
| Borg UI | Borg Credentials, Admin Login, SSH Keys in persistentem Appdata, nicht im Git |
| Hermes Agent | provider keys, API server key, messaging tokens, Home Assistant token in Host `.env`; SSH private key als Host-Secret |
| Monitoring | `monitoring_grafana_admin_password.txt`, `monitoring_grafana_influxdb_token.txt`, `influxdb3_admin_token.json` fuer zentrale Grafana-/InfluxDB-Funktionen; ersetzt Uptime Kuma fuer HTTP-Verfuegbarkeit |
## Skripte
| Skript | Ausfuehrungsort | Zweck |
|---|---|---|
| `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/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.
## Unsicherheiten / TODOs aus Repo-Sicht
- Echte `stack.env`- und `.env`-Dateien sind per `.gitignore` ausgeschlossen; nur `*.example`-Dateien gehoeren ins Repo.
- Hardware-, Netzwerk- und Provider-Inventare sind initiale Templates und muessen nach einem Host-Audit mit echten Werten gefuellt werden.
- Authelia-2FA-/OIDC-Aenderungen sind nach Audit 2026-05-25 bewusst geparkt und werden nicht als Sofortmassnahme behandelt.
- 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.
- `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.
- Leere `.keep`-Platzhalter wurden entfernt; neue Verzeichnisse sollen erst mit konkretem Inhalt ins Repo.
- `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`.
+18 -10
View File
@@ -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 | | 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 | | 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 | | 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 | | 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 | Share / Host | `/mnt/user/appdata/redis` | keine | `redis_password.txt` | `backend_net` | Redis startet, Apps verbinden sich | | 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, 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 | | 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 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 | | 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 | | 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 | | 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 | | 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,14 +44,14 @@ Sie ist die fachliche Ergaenzung zu `docs/DISASTER_RECOVERY.md`.
| Dienst | Fuehrende Quelle | Datei-Restore | Dump / DB | Secrets / ENV | Abhaengigkeiten | Smoke-Test | | 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 | | 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`, optional `/mnt/user/appdata/mealie/postgres` bei lokalem Share-Weiterbetrieb | `mealie.dump` | `mealie_postgres_password.txt` | `mealie-postgres`, Traefik | UI startet, Rezepte vorhanden | | 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` | `immich.dump` | `IMMICH_DB_PASSWORD`, `immich_postgres_password.txt` | `immich_postgres`, `immich_redis`, Traefik | UI startet, Medienbibliothek sichtbar | | 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 17, Traefik, Authelia | Authelia-Weiterleitung greift; nach Login startet die Web-UI und das Archiv laesst sich oeffnen | | 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` | `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 | | 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` | | 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 | | 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 | | Paperless-GPT | Borg / Share | `/mnt/user/appdata/paperless-gpt` | keine eigene DB | `PAPERLESS_API_TOKEN`, `OPENAI_API_KEY` | Traefik, Paperless, OpenAI API | UI startet, Konfiguration vorhanden; LLM-Provider zeigt `openai` / `gpt-5.4-mini` |
--- ---
@@ -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. 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 ## Praktische Restore-Regeln
+41 -2
View File
@@ -17,13 +17,15 @@ Dieses Dokument listet sensible Daten, deren Ablageorte und die vorgesehene Einb
| Service | Secret | Datei / Methode | Status | | Service | Secret | Datei / Methode | Status |
|---|---|---|---| |---|---|---|---|
| Vaultwarden | `ADMIN_TOKEN` | `/mnt/user/appdata/secrets/vaultwarden_admin_token.txt` -> `ADMIN_TOKEN_FILE` | aktiv | | Vaultwarden | `ADMIN_TOKEN` | `/mnt/user/appdata/secrets/vaultwarden_admin_token.txt` -> `ADMIN_TOKEN_FILE` | aktiv |
| Vaultwarden | SMTP Password | `/mnt/user/appdata/secrets/homelab_smtp_password.txt` -> `SMTP_PASSWORD_FILE` fuer Einladungen/Benachrichtigungen | aktiv |
| Traefik | Cloudflare DNS API Token | `/mnt/user/appdata/traefik/secrets/cloudflare_dns_api_token` -> Docker Secret `cloudflare_dns_api_token` | 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 | | 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 | 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 | | mealie-postgres | DB Password | `/mnt/user/appdata/secrets/mealie_postgres_password.txt` -> `POSTGRES_PASSWORD_FILE` | aktiv |
| Paperless-ngx | DB Password | Stack ENV `${PAPERLESS_DBPASS}` | aktiv | | Paperless-ngx | DB Password | Stack ENV `${PAPERLESS_DBPASS}` | aktiv |
| Paperless-ngx | Redis URL | Stack ENV `${PAPERLESS_REDIS}` | aktiv | | Paperless-ngx | Redis URL | Stack ENV `${PAPERLESS_REDIS}` | aktiv |
| Paperless-GPT | OpenAI API Key | Stack ENV `${OPENAI_API_KEY}`; nicht im Repo, nicht in Logs | aktiv |
| code-server | Passwort | `/mnt/user/appdata/code-server/secrets/password` -> `FILE__PASSWORD` | aktiv | | code-server | Passwort | `/mnt/user/appdata/code-server/secrets/password` -> `FILE__PASSWORD` | aktiv |
| Filebrowser | Admin Password | `/mnt/user/appdata/secrets/filebrowser_admin_password.txt` -> initialisierte SQLite-DB | aktiv | | Filebrowser | Admin Password | `/mnt/user/appdata/secrets/filebrowser_admin_password.txt` -> initialisierte SQLite-DB | aktiv |
| Immich (server) | DB Password | Stack ENV `${IMMICH_DB_PASSWORD}` | aktiv | | Immich (server) | DB Password | Stack ENV `${IMMICH_DB_PASSWORD}` | aktiv |
@@ -52,6 +54,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 | 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 | | 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 | | 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`) |
--- ---
@@ -88,6 +91,7 @@ Dieses Dokument listet sensible Daten, deren Ablageorte und die vorgesehene Einb
|-- borg_repo_passphrase.txt |-- borg_repo_passphrase.txt
|-- influxdb3_admin_token.json |-- influxdb3_admin_token.json
|-- filebrowser_admin_password.txt |-- filebrowser_admin_password.txt
|-- homelab_smtp_password.txt
`-- vaultwarden_admin_token.txt `-- vaultwarden_admin_token.txt
``` ```
@@ -97,14 +101,49 @@ Weitere dokumentierte Secret-Pfade:
- `/mnt/user/appdata/secrets/hermes_runner_id_ed25519` - `/mnt/user/appdata/secrets/hermes_runner_id_ed25519`
- `/mnt/user/appdata/traefik/secrets/cloudflare_dns_api_token` - `/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. - 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. - 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. - `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. **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 |
| `paperless-gpt` | `PAPERLESS_API_TOKEN`, `OPENAI_API_KEY` | Komodo-Mongo-Dump -> Vaultwarden -> externe Notiz | Paperless-Token kann in Paperless neu erzeugt werden; OpenAI-Key muss im OpenAI-Projekt rotiert/neu erstellt werden |
| `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 ## Regel
Wenn `_FILE` nicht unterstuetzt wird -> Stack Environment Variable in Komodo verwenden. Wenn `_FILE` nicht unterstuetzt wird -> Stack Environment Variable in Komodo verwenden.
Secrets niemals direkt in die Compose-Datei schreiben. 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.
+136 -21
View File
@@ -1,7 +1,7 @@
# Services Recovery - KalliLab CORE # Services Recovery - KalliLab CORE
Status: Initiale Recovery-Baseline 2026-05-26, aus dem Audit 2026-05-25 abgeleitet. 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 ## Zweck
@@ -31,11 +31,13 @@ Optionen:
Empfohlener Start: Empfohlener Start:
1. `git bundle`-Job fuer alle Gitea-Repositories definieren. 1. `ops/borg-ui/scripts/gitea-bundle-mirror.sh` auf dem Host ausfuehren.
2. Ziel auf zweitem physischen Medium oder separatem Off-site-Ziel ablegen. 2. Ziel `/mnt/user/backups/git-bundles/gitea` in Borg/off-site Scope aufnehmen.
3. Job alle 6 Stunden ausfuehren. 3. Job alle 6 Stunden oder mindestens vor Borg ausfuehren.
4. Stichprobe: ein Bundle in Wegwerfpfad klonen. 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: Erfolgskriterium:
```bash ```bash
@@ -45,30 +47,141 @@ git -C /tmp/repo-restore-test fsck
## Komodo Bootstrap ## 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). **Anker:** `ops/komodo/docker-compose.yml` aus dem Repo.
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.
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. **Was der Anker bewusst NICHT ist:**
- Offen bleibt nur ein spaeterer Trockenlauf, bei dem die Komodo-Startkommandos gegen echte Restore-Pfade getestet werden.
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 ```bash
docker compose -f ops/komodo/docker-compose.yml config
docker compose -f ops/komodo/docker-compose.yml up -d 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 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`,
`ops/restore-tests/komodo-bootstrap-test.sh`,
`ops/restore-tests/komodo-bootstrap-plan.md` und
`ops/restore-tests/komodo-bootstrap-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 ## Secrets Recovery Reihenfolge
@@ -88,13 +201,15 @@ Authoritativ ist `docs/SECRETS_MAP.md`. Fuer den Kaltstart ist diese Reihenfolge
- Keine Secret-Werte in Git oder Tickets kopieren. - Keine Secret-Werte in Git oder Tickets kopieren.
- Restore-Tests laufen in Wegwerfpfaden, nie direkt gegen produktive Pfade. - 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 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 ## Naechste Aufgaben
| Status | Aufgabe | | 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 | | 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 | | erledigt | Services-Recovery in `docs/DISASTER_RECOVERY.md` verlinken |
+18 -18
View File
@@ -1,6 +1,6 @@
# Service Catalog # Service Catalog
Stand: 2026-05-23 Stand: 2026-06-01
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. 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 | | 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 | | `vaultwarden` | Passwort-Tresor | `security/vaultwarden/docker-compose.yml` | `https://vault.kaleschke.info` | Traefik, `frontend_net`, GMX SMTP | `/mnt/user/appdata/vaultwarden` | Tier 1, `vaultwarden.sqlite.dump` + Share | ja | `ADMIN_TOKEN_FILE`; SMTP ueber `homelab_smtp_password.txt` fuer Einladungen/Benachrichtigungen; keine direkten Ports |
## Shared Infrastructure ## Shared Infrastructure
| Service | Zweck | Autoritativer Pfad | URL / Zugang | Abhaengigkeiten | Datenpfade | Backup / Restore | Traefik | Besonderheiten / TODOs | | 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 | | `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` | 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 | | `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 | | `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 ## Public / User Apps
| Service | Zweck | Autoritativer Pfad | URL / Zugang | Abhaengigkeiten | Datenpfade | Backup / Restore | Traefik | Besonderheiten / TODOs | | 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-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 | | `paperless-gpt` | KI-Ergaenzung fuer Paperless | `apps/paperless-gpt/docker-compose.yml` | `https://paperless-gpt.kaleschke.info` | Paperless API, OpenAI API, Traefik | `/mnt/user/appdata/paperless-gpt/data`, `/mnt/user/appdata/paperless-gpt/prompts` | Tier 2 | ja + Authelia | `PAPERLESS_API_TOKEN` und `OPENAI_API_KEY` als Stack ENV; LLM und Vision-OCR laufen ueber `gpt-5.4-mini`, kein Zugriff mehr auf lokale Ollama-VM. **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_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_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 | Architektur nennt anonymes Volume -> named volume als offenes Thema | | `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 | | `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` | 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 | | `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 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 | | `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` | 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-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 | | `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 / 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 | | `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` | | `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 ## Operations / Monitoring / Admin
@@ -65,7 +65,7 @@ Secret-Werte sind nicht enthalten. Es werden nur Secret-Namen, Env-Key-Namen und
| `speedtest-tracker` | Speedtest-Monitoring | `ops/speedtest/docker-compose.yml` | `https://speedtest.kaleschke.info` | Traefik + Authelia | `/mnt/user/appdata/speedtest-tracker/config` | Tier 3, `speedtest-tracker.sqlite.dump` | ja + Authelia | `APP_KEY`, `ADMIN_PASSWORD` Stack ENV | | `speedtest-tracker` | Speedtest-Monitoring | `ops/speedtest/docker-compose.yml` | `https://speedtest.kaleschke.info` | Traefik + Authelia | `/mnt/user/appdata/speedtest-tracker/config` | Tier 3, `speedtest-tracker.sqlite.dump` | ja + Authelia | `APP_KEY`, `ADMIN_PASSWORD` Stack ENV |
| `filebrowser` | Datei-Browser fuer Documents/Photos/Projekte | `ops/filebrowser/docker-compose.yml` | `https://files.kaleschke.info` | Traefik + Authelia | `/mnt/user/appdata/filebrowser/*`, `/mnt/user/documents`, `/mnt/user/photos`, `/mnt/user/projekte` | Tier 3, `filebrowser.bolt.dump` + Share | ja + Authelia | Breiter Appdata-Mount entfernt; Secrets und Traefik-Dynamic-Config sind nicht mehr ueber Filebrowser gemountet | | `filebrowser` | Datei-Browser fuer Documents/Photos/Projekte | `ops/filebrowser/docker-compose.yml` | `https://files.kaleschke.info` | Traefik + Authelia | `/mnt/user/appdata/filebrowser/*`, `/mnt/user/documents`, `/mnt/user/photos`, `/mnt/user/projekte` | Tier 3, `filebrowser.bolt.dump` + Share | ja + Authelia | Breiter Appdata-Mount entfernt; Secrets und Traefik-Dynamic-Config sind nicht mehr ueber Filebrowser gemountet |
| `code-server` | Web-Editor / Operations Workspace | `ops/code-server/docker-compose.yml` | `https://code.kaleschke.info` | Traefik + Authelia | `/mnt/user/appdata/code-server`, `/mnt/user/services/dev` | Tier 3 | ja + Authelia | Passwort ueber LSIO `FILE__PASSWORD`; Workspaces beachten | | `code-server` | Web-Editor / Operations Workspace | `ops/code-server/docker-compose.yml` | `https://code.kaleschke.info` | Traefik + Authelia | `/mnt/user/appdata/code-server`, `/mnt/user/services/dev` | Tier 3 | ja + Authelia | Passwort ueber LSIO `FILE__PASSWORD`; Workspaces beachten |
| `monitoring-grafana` | zentrale Observability-UI fuer Metriken, Logs und InfluxDB | `monitoring/docker-compose.yml` | `https://monitoring.kaleschke.info` | Traefik + Authelia, Prometheus, Loki, InfluxDB 3 Core | named volume `grafana_data`, Provisioning unter `monitoring/grafana/provisioning`, Dashboards unter `monitoring/grafana/dashboards` | Tier 3, named volume | ja + Authelia | Admin-Passwort ueber `monitoring_grafana_admin_password.txt`; Zielbestand: `Homelab / Availability`, `Homelab / Host Overview`, `Homelab / Containers + Logs`, `Traefik Official Standalone Dashboard`; Dashboard-Importer ist optionales `bootstrap`-Profil fuer Traefik | | `monitoring-grafana` | zentrale Observability-UI fuer Metriken, Logs und InfluxDB | `monitoring/docker-compose.yml` | `https://monitoring.kaleschke.info` | Traefik + Authelia, Prometheus, Loki, InfluxDB 3 Core | named volume `grafana_data`, Provisioning unter `monitoring/grafana/provisioning`, Dashboards unter `monitoring/grafana/dashboards` | Tier 3, named volume | ja + Authelia | Admin-Passwort ueber `monitoring_grafana_admin_password.txt`; Zielbestand: `Homelab / Availability`, `Homelab / Host Overview`, `Homelab / Containers + Logs`, `Homelab / Family Status`, `Traefik Official Standalone Dashboard`; Dashboard-Importer ist optionales `bootstrap`-Profil fuer Traefik |
| `monitoring-prometheus` | Metrik-Speicher fuer Homelab-Monitoring | `monitoring/docker-compose.yml`, `monitoring/prometheus/prometheus.yml`, `monitoring/prometheus/alerts.yml` | intern `http://prometheus:9090` | `monitoring_net`, node-exporter, cAdvisor, Traefik-Metrics, Blackbox Exporter, Alertmanager | named volume `prometheus_data` | Tier 3, transiente Metriken mit 30 Tagen Retention | nein | Scrapes: Prometheus, node-exporter, cAdvisor, Traefik `:8082`, `blackbox-http`; Prometheus-Regeln senden an Alertmanager und von dort nach ntfy | | `monitoring-prometheus` | Metrik-Speicher fuer Homelab-Monitoring | `monitoring/docker-compose.yml`, `monitoring/prometheus/prometheus.yml`, `monitoring/prometheus/alerts.yml` | intern `http://prometheus:9090` | `monitoring_net`, node-exporter, cAdvisor, Traefik-Metrics, Blackbox Exporter, Alertmanager | named volume `prometheus_data` | Tier 3, transiente Metriken mit 30 Tagen Retention | nein | Scrapes: Prometheus, node-exporter, cAdvisor, Traefik `:8082`, `blackbox-http`; Prometheus-Regeln senden an Alertmanager und von dort nach ntfy |
| `monitoring-alertmanager` | Alert-Routing fuer Prometheus-Regeln | `monitoring/docker-compose.yml`, `monitoring/alertmanager/alertmanager.yml` | intern `:9093` | Prometheus, ntfy Bridge | named volume `alertmanager_data` | Tier 3 | nein | sendet firing und resolved Alerts an `monitoring-alertmanager-ntfy-bridge` | | `monitoring-alertmanager` | Alert-Routing fuer Prometheus-Regeln | `monitoring/docker-compose.yml`, `monitoring/alertmanager/alertmanager.yml` | intern `:9093` | Prometheus, ntfy Bridge | named volume `alertmanager_data` | Tier 3 | nein | sendet firing und resolved Alerts an `monitoring-alertmanager-ntfy-bridge` |
| `monitoring-alertmanager-ntfy-bridge` | Alertmanager-Webhook nach ntfy Push | `monitoring/docker-compose.yml`, `monitoring/alertmanager-ntfy-bridge/bridge.py` | intern `:8080` | Alertmanager, `https://ntfy.kaleschke.info/homelab-alerts` | kein kritischer Zustand | rebuildbar | nein | formatiert Alertmanager JSON als ntfy Titel, Nachricht, Priority und Tags; keine Secrets | | `monitoring-alertmanager-ntfy-bridge` | Alertmanager-Webhook nach ntfy Push | `monitoring/docker-compose.yml`, `monitoring/alertmanager-ntfy-bridge/bridge.py` | intern `:8080` | Alertmanager, `https://ntfy.kaleschke.info/homelab-alerts` | kein kritischer Zustand | rebuildbar | nein | formatiert Alertmanager JSON als ntfy Titel, Nachricht, Priority und Tags; keine Secrets |
@@ -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-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-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-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-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 | | `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 | | 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` | | `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 ## Backup- und Restore-Hinweise
@@ -2,9 +2,9 @@
| Feld | Wert | | Feld | Wert |
|------|------| |------|------|
| Version | 1.3 | | Version | 1.4 |
| Status | **Active** — bindend, commit-reif als `docs/STORAGE_LAYOUT.md` | | Status | **Active** — bindend als `docs/STORAGE_LAYOUT.md` |
| Datum | 2026-05-15 | | Datum | 2026-05-27 |
| Geltungsbereich | Unraid-Host `Kallilabcore`, alle Pools, Array-Disks, User-Shares, Appdata-Strukturen | | Geltungsbereich | Unraid-Host `Kallilabcore`, alle Pools, Array-Disks, User-Shares, Appdata-Strukturen |
| Verbindlichkeit | Bindend ab Inkraftsetzung. Abweichungen nur dokumentiert als Ausnahme (siehe Abschnitt 12) | | Verbindlichkeit | Bindend ab Inkraftsetzung. Abweichungen nur dokumentiert als Ausnahme (siehe Abschnitt 12) |
| Vorgänger | keiner; entstanden aus Incident 2026-05-11 (NTFS-Cache-Korruption) | | 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 | | 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 | | 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) | HDD (Modell TBD) | **XFS** | TBD | Nutzdaten, Backups, Services | NTFS-zu-XFS-Migration Phase 2 abgeschlossen am 2026-05-25 | | 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 | HDD (Modell TBD) | — (keine FS) | TBD | Redundanz für Array | Unverändert | | Parity | TOSHIBA HDWG480 (`2460A03VFA3H`) | — (keine FS) | 7.3T | Redundanz für Array | Unverändert |
| Boot | USB-Stick | FAT32 | klein | Unraid-OS, Konfiguration | Unverändert, regelmäßig per Flash-Backup gesichert | | Boot | Samsung Flash Drive (`0375125090000587`) | FAT32 | 59.8G | Unraid-OS, Konfiguration | 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 | | 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:** **Begründung Filesystem-Wahl:**
@@ -378,38 +378,10 @@ Wenn Hermes-Worker auf weiteren Hosts skaliert: dieser Storage-Layout-Plan gilt
- `docs/DISASTER_RECOVERY.md` — Konkrete Recovery-Pläne, inkl. NTFS-Migration - `docs/DISASTER_RECOVERY.md` — Konkrete Recovery-Pläne, inkl. NTFS-Migration
- `docs/GITOPS_DRIFT_RUNBOOK.md` — Drift-Erkennung und -Behebung - `docs/GITOPS_DRIFT_RUNBOOK.md` — Drift-Erkennung und -Behebung
- `docs/AI_CONTEXT.md` — Kontext für AI-Assistenten - `docs/AI_CONTEXT.md` — Kontext für AI-Assistenten
- `docs/HARDWARE_INVENTORY.md` — physische Host-, Disk- und Health-Baseline
## 18. Changelog ## 18. Status
| Version | Datum | Änderung | Autor | Status: **Active v1.4 seit 2026-05-27**.
|---------|-------|----------|-------|
| 1.0 (Draft) | 2026-05-15 | Initialfassung nach Incident 2026-05-11. Ursprung: NTFS-Cache-Korruption, fehlende Posture-Checks, ungeplante Backup-Strategie. | Operator + AI-Assistenten |
| 1.1 (Draft) | 2026-05-15 | Operator-Review-Feedback eingearbeitet: `system`/`domains` auf `only`, `services` als recovery-kritisch markiert, `data/`-Behandlung pro Stack klassifiziert (statt blanket Exclude), Backup-Tooling Ist/Soll explizit getrennt, Posture-Check zusätzlich bei Boot/vor Backup/nach Mover, Hard Rules 11+12 ergänzt (Restore-Pfad-Pflicht, Posture-vor-Backup), Alarmziel-Optionen benannt, Review-Items in eigene Sektion §20 verschoben. | Operator + AI-Assistenten |
| 1.2 (Draft) | 2026-05-15 | Operator-Entscheidungen #3, #4, #6, #9, #11 eingearbeitet: Backrest abgeschaltet (Borg alleinige Backup-Technologie), persönliche Daten vollständig im Pflicht-Backup-Scope, ntfy als Alarmziel verbindlich, kebab-case-Migration im Rahmen der Recovery-Phase, Mirror-Backup für Gitea-Repo-Inhalte als verbindliche Spec (Implementierung in `SERVICES_RECOVERY.md` zu detaillieren). Offen: Items #1, #2, #5, #7, #8, #10. | Operator + AI-Assistenten |
| 1.3 (Draft) | 2026-05-15 | Operator-Entscheidungen #1, #7, #8 eingearbeitet: Disk-Größen/-Modelle als Deferred via Posture-Check-Detection (kein Blocker), optionale Stacks (Filebrowser, code-server, Speedtest, Scrutiny, Uptime-Kuma) bleiben im Layout und sind produktiv, Network-Verweis auf MASTER_V2 bestätigt. Damit alle akuten Items entschieden. Verbleibend: Items #2, #5, #10 (Retention, Schwellen-Kalibrierung, RESTORE_MATRIX-Klassifikation) — alle als Folge-Aufgaben über Inkraftsetzung hinaus, kein Commit-Blocker. | Operator + AI-Assistenten |
## 19. Inkraftsetzung Detailhistorie und alte Review-Tabellen liegen in der Git-Historie. Aktuelle Folgepunkte stehen nicht mehr hier, sondern in `docs/AUDIT_2026-05-25_TODO.md`.
Dieses Dokument tritt in Kraft mit dem Commit der finalen Fassung in `master`-Branch des Repos `homelab-infra`. Ab Inkraftsetzung gelten alle Hard Rules; Soft Rules werden im Rahmen der laufenden Recovery-Arbeit etabliert; Posture-Check muss innerhalb von 14 Tagen nach Inkraftsetzung produktiv laufen.
Erste Audit-Review dieses Dokuments: spätestens 90 Tage nach Inkraftsetzung. Danach jährlich oder bei jeder strukturellen Änderung des Storage-Layouts.
## 20. Open Review Items (vor finalem Commit zu entscheiden)
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.**
| 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 |
| 2 | Retention-Werte in §8.1 (Borg-Repos lokal/remote) — abhängig von tatsächlicher Storage-Kapazität | Vorschlag steht, anzupassen | Operator |
| 3 | Backup-Tooling: Backrest abschalten, Borg alleinige Backup-Technologie | **ENTSCHIEDEN 2026-05-15** | erledigt (siehe §8.0) |
| 4 | Backup-Scope für persönliche Daten: `documents`, `photos`, `finance`, `projekte` (und `media` falls behalten) **vollständig** im Pflicht-Scope | **ENTSCHIEDEN 2026-05-15** | erledigt (siehe §8.2) |
| 5 | Posture-Check-Schwellen in §11 — Vorschlag konservativ, nach erstem Monitoring kalibrieren | Vorschlag steht | Operator nach 30 Tagen |
| 6 | Alarmziel: ntfy als primärer Push-Kanal, Mail-Fallback als Folge-Erweiterung optional | **ENTSCHIEDEN 2026-05-15** | erledigt (siehe §11) |
| 7 | Optionale Stacks (Filebrowser, code-server, Speedtest, Scrutiny, Uptime-Kuma) — bleiben im Layout, sind produktiv im Scope, folgen den Standard-Konventionen aus §5 und Backup-Pflichten aus §8.2 | **ENTSCHIEDEN 2026-05-15** | erledigt |
| 8 | Verweis auf `HOMELAB_ARCHITECTURE_MASTER_V2.md` für Network-Architektur (§10) — Net-Architektur steht dort authoritativ, Verweis ist ausreichend | **ENTSCHIEDEN 2026-05-15** | erledigt (siehe §10) |
| 9 | Naming-Konvention: kebab-case durchziehen, Migration im Rahmen der Recovery-Phase | **ENTSCHIEDEN 2026-05-15** | erledigt (siehe §6); pro Stack in RESTORE_MATRIX.md zu dokumentieren |
| 10 | Pro-Stack-Klassifizierung in `RESTORE_MATRIX.md` (DB-Typ, Nutzdaten in `data/`, Dump-Verfahren, letzter Restore-Test, kebab-case-Migrationsname) — als Folge-Aufgabe aus Hard Rule §12.11 | Folge-Aufgabe | Operator + Recovery-Phase |
| 11 | Mirror-Backup für `services/gitea/git/repositories/` auf zweites Medium, ≤ 6 h Frequenz, konkrete Implementierung in `docs/SERVICES_RECOVERY.md` zu erstellen | **ENTSCHIEDEN 2026-05-15**, Implementierungs-Doc offen | 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.
+57 -2
View File
@@ -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-Regeln
- Secrets liegen niemals im Repository - Secrets liegen niemals im Repository
@@ -311,11 +347,30 @@ 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` und `HOMELAB_ARCHITECTURE_MASTER_V2.md` Sektion 7.8 (Entfernt) nachziehen.
Wenn ein Stack `webhook_enabled` in Komodo hatte, zusaetzlich pruefen, ob der zugehoerige Gitea-Hook deaktiviert oder geloescht wurde.
---
## Dokumentationspflicht ## Dokumentationspflicht
Nach jeder erfolgreichen Migration oder relevanten Aenderung muessen diese Dateien geprueft werden: Nach jeder erfolgreichen Migration oder relevanten Aenderung muessen diese Dateien geprueft werden:
- `docs/MIGRATION_LOG.md`
- `docs/SECRETS_MAP.md` - `docs/SECRETS_MAP.md`
- `docs/ROLLBACK.md` - `docs/ROLLBACK.md`
- `docs/SERVICES_RECOVERY.md` falls `/mnt/user/services`, Gitea, Komodo oder Host-Automation betroffen sind - `docs/SERVICES_RECOVERY.md` falls `/mnt/user/services`, Gitea, Komodo oder Host-Automation betroffen sind
@@ -347,7 +402,7 @@ Wenn mit einer KI gearbeitet wird, gilt immer:
> 1. `HOMELAB_ARCHITECTURE_MASTER_V2.md` > 1. `HOMELAB_ARCHITECTURE_MASTER_V2.md`
> 2. `docs/WORKFLOW.md` > 2. `docs/WORKFLOW.md`
> 3. die betroffene Compose-Datei > 3. die betroffene Compose-Datei
> 4. ggf. `docs/MIGRATION_LOG.md` > 4. die relevante Betriebsdoku aus `docs/README.md`
Erst danach duerfen Aenderungen vorgeschlagen werden. Erst danach duerfen Aenderungen vorgeschlagen werden.
+1 -1
View File
@@ -1,6 +1,6 @@
services: services:
adguard: adguard:
image: adguard/adguardhome:v0.107.52@sha256:d16cc7517ab96f843e7f8bf8826402dba98f5e6b175858920296243332391589 image: adguard/adguardhome:v0.107.76@sha256:7157eb1dc3b26c7af1d6898759a7b3f7d0fa09891fbd2d3caa6abc1057a9179b
container_name: adguard container_name: adguard
restart: unless-stopped restart: unless-stopped
volumes: volumes:
+1 -1
View File
@@ -1,6 +1,6 @@
services: services:
tailscale: tailscale:
image: tailscale/tailscale:stable@sha256:dbeff02d2337344b351afac203427218c4d0a06c43fc10a865184063498472a6 image: tailscale/tailscale:stable@sha256:25cde9ad76020b0e29229136d0c38b5962e9a0e1774ffac9b0df68e4a37d6cf0
container_name: Tailscale-Docker container_name: Tailscale-Docker
restart: unless-stopped restart: unless-stopped
network_mode: host network_mode: host
+1 -1
View File
@@ -1,6 +1,6 @@
services: services:
ddns-updater: ddns-updater:
image: ghcr.io/qdm12/ddns-updater:latest@sha256:ee16ab4f6203bf9e5b0925d38a0b4ebf2d9f23771f933cfb2f5a2dbd5f9a2f88 image: ghcr.io/qdm12/ddns-updater:latest@sha256:9313e1c31f366c89dc0819e5eff85576cb23821424c0c267fa66cfa39aabde83
container_name: ddns-updater container_name: ddns-updater
restart: unless-stopped restart: unless-stopped
security_opt: security_opt:
+10 -3
View File
@@ -1,6 +1,6 @@
services: services:
postgresql17: postgresql17:
image: postgres:17.9@sha256:5b96f1a16bd9768b060dd2ffe55cb6225c4d9ef4d214a8b21eb08134869a97e4 image: postgres:18.4@sha256:8ff36f3c66371cba71d20ceedccfc3de9669a68737607888c4ef0af93abe8e39
container_name: postgresql17 container_name: postgresql17
restart: unless-stopped restart: unless-stopped
@@ -9,10 +9,10 @@ services:
POSTGRES_USER: mailarchiver POSTGRES_USER: mailarchiver
POSTGRES_DB: mailarchiver POSTGRES_DB: mailarchiver
POSTGRES_PASSWORD_FILE: /run/secrets/postgres_password POSTGRES_PASSWORD_FILE: /run/secrets/postgres_password
PGDATA: /var/lib/postgresql/data PGDATA: /var/lib/postgresql/18/docker
volumes: 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 - /mnt/user/appdata/secrets/postgres_password.txt:/run/secrets/postgres_password:ro
networks: networks:
@@ -21,6 +21,13 @@ services:
security_opt: security_opt:
- no-new-privileges:true - no-new-privileges:true
healthcheck:
test: ["CMD-SHELL", "pg_isready -U mailarchiver -d mailarchiver"]
interval: 30s
timeout: 5s
retries: 5
start_period: 30s
networks: networks:
backend_net: backend_net:
external: true external: true
+8 -1
View File
@@ -1,6 +1,6 @@
services: services:
redis: redis:
image: redis:7.4-alpine@sha256:6ab0b6e7381779332f97b8ca76193e45b0756f38d4c0dcda72dbb3c32061ab99 image: redis:8.8.0-alpine@sha256:09160599abd229764c0fb44cb6be640294e1d360a54b19985ab4843dcf2d90f1
container_name: Redis container_name: Redis
restart: unless-stopped restart: unless-stopped
command: command:
@@ -18,6 +18,13 @@ services:
security_opt: security_opt:
- no-new-privileges:true - 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: networks:
backend_net: backend_net:
external: true external: true
+11 -2
View File
@@ -17,7 +17,7 @@ Zielzustand: ein zentraler Observability-Stack fuer KalliLab CORE.
Die alten Pfade `ops/loki` und `ops/grafana-influxdb` wurden am 2026-05-26 aus dem aktiven Repo entfernt. Rollback erfolgt bei Bedarf ueber Git-Historie, nicht ueber parallel gepflegte Compose-Verzeichnisse. Die alten Pfade `ops/loki` und `ops/grafana-influxdb` wurden am 2026-05-26 aus dem aktiven Repo entfernt. Rollback erfolgt bei Bedarf ueber Git-Historie, nicht ueber parallel gepflegte Compose-Verzeichnisse.
Live-Stand 2026-05-25: die zehn `monitoring-*` Container laufen produktiv, die alten Container `grafana`, `influxdb3-core`, `loki` und `alloy` sind in Docker nicht mehr vorhanden. Uptime Kuma ist durch Blackbox Exporter, Prometheus-Alerts und das Dashboard `Homelab / Availability` abgeloest. Live-Stand 2026-06-01: die zehn `monitoring-*` Container laufen produktiv, die alten Container `grafana`, `influxdb3-core`, `loki` und `alloy` sind in Docker nicht mehr vorhanden. Uptime Kuma ist durch Blackbox Exporter, Prometheus-Alerts und das Dashboard `Homelab / Availability` abgeloest.
## Secrets ## Secrets
@@ -62,6 +62,7 @@ INFLUXDB_BIND_IP=192.168.178.58
- `https://monitoring.kaleschke.info` leitet zu Authelia. - `https://monitoring.kaleschke.info` leitet zu Authelia.
- Grafana-Datasources `Prometheus`, `Loki` und `InfluxDB 3 Core` testen erfolgreich. - Grafana-Datasources `Prometheus`, `Loki` und `InfluxDB 3 Core` testen erfolgreich.
- Prometheus Targets: `prometheus`, `node-exporter`, `cadvisor`, `traefik`, `blackbox-http`. - 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`. - 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`. - Loki zeigt Container-Logs mit Labels `container`, `compose_project`, `compose_service`.
- InfluxDB 3 Core enthaelt die Datenbank `homelab`. - InfluxDB 3 Core enthaelt die Datenbank `homelab`.
@@ -71,7 +72,7 @@ INFLUXDB_BIND_IP=192.168.178.58
- Dozzle bleibt abgeloest: `Homelab / Containers + Logs` ersetzt Live-Logs und Error-Rate. - Dozzle bleibt abgeloest: `Homelab / Containers + Logs` ersetzt Live-Logs und Error-Rate.
- Glances erst stoppen, wenn `Homelab / Host Overview` und `Homelab / Containers + Logs` fuer CPU, RAM, Disk, Network, Container-CPU und Container-RAM passen. - Glances erst stoppen, wenn `Homelab / Host Overview` und `Homelab / Containers + Logs` fuer CPU, RAM, Disk, Network, Container-CPU und Container-RAM passen.
- Uptime Kuma ist entfernt; `Homelab / Availability`, Blackbox Exporter und Prometheus-Alerts sind der Zielzustand fuer HTTP-Verfuegbarkeit. - Uptime Kuma ist entfernt; `Homelab / Availability`, Blackbox Exporter und Prometheus-Alerts sind der Zielzustand fuer HTTP-Verfuegbarkeit.
- Dashboard-Zielbestand: `Homelab / Availability`, `Homelab / Containers + Logs`, `Homelab / Host Overview`, `Traefik Official Standalone Dashboard`. - Dashboard-Zielbestand: `Homelab / Availability`, `Homelab / Containers + Logs`, `Homelab / Host Overview`, `Homelab / Family Status`, `Traefik Official Standalone Dashboard`.
## Alerting ## Alerting
@@ -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. - `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. - `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: Test:
```bash ```bash
curl -fsS http://alertmanager-ntfy-bridge:8080/healthz 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
```
+15 -11
View File
@@ -1,6 +1,6 @@
services: services:
prometheus: prometheus:
image: prom/prometheus:v3.7.3 image: prom/prometheus:v3.12.0@sha256:69f5241418838263316593f7274a304b095c40bcf22e57272865da91bd60a8ac
container_name: monitoring-prometheus container_name: monitoring-prometheus
restart: unless-stopped restart: unless-stopped
command: command:
@@ -25,7 +25,7 @@ services:
- cadvisor - cadvisor
alertmanager: alertmanager:
image: prom/alertmanager:v0.28.1 image: prom/alertmanager:v0.32.1@sha256:51a825c2a40acc3e338fdd00d622e01ec090f72be2b3ea46be0839cd47a4d286
container_name: monitoring-alertmanager container_name: monitoring-alertmanager
restart: unless-stopped restart: unless-stopped
command: command:
@@ -42,7 +42,7 @@ services:
- no-new-privileges:true - no-new-privileges:true
alertmanager-ntfy-bridge: alertmanager-ntfy-bridge:
image: python:3.13-alpine image: python:3.14-alpine@sha256:5a824eb82cc75361f98611f3cfc5091ea33f10a6ccea4d4ebdabbc523b9a1614
container_name: monitoring-alertmanager-ntfy-bridge container_name: monitoring-alertmanager-ntfy-bridge
restart: unless-stopped restart: unless-stopped
dns: dns:
@@ -63,7 +63,7 @@ services:
- no-new-privileges:true - no-new-privileges:true
blackbox-exporter: blackbox-exporter:
image: prom/blackbox-exporter:v0.27.0 image: prom/blackbox-exporter:v0.28.0@sha256:e753ff9f3fc458d02cca5eddab5a77e1c175eee484a8925ac7d524f04366c2fc
container_name: monitoring-blackbox-exporter container_name: monitoring-blackbox-exporter
restart: unless-stopped restart: unless-stopped
dns: dns:
@@ -81,7 +81,7 @@ services:
- no-new-privileges:true - no-new-privileges:true
loki: loki:
image: grafana/loki:3.7.2 image: grafana/loki:3.7.2@sha256:191d4fdfb7264f16989f0a57f320872620a5a7c2ceeec6229212c4190ec49b86
container_name: monitoring-loki container_name: monitoring-loki
restart: unless-stopped restart: unless-stopped
command: command:
@@ -97,7 +97,7 @@ services:
- no-new-privileges:true - no-new-privileges:true
promtail: promtail:
image: grafana/promtail:3.6.10 image: grafana/promtail:3.6.11@sha256:a761cb834cfaeee29745440d4884d6748f0a08d8f68928db1d707018c1dcfbe9
container_name: monitoring-promtail container_name: monitoring-promtail
restart: unless-stopped restart: unless-stopped
command: command:
@@ -115,8 +115,9 @@ services:
- loki - loki
grafana: grafana:
image: grafana/grafana:12.4.3 image: grafana/grafana:13.0.1@sha256:0f86bada30d65ef9d0183b90c1e2682ac92d53d95da8bed322b984ea78a4a73a
container_name: monitoring-grafana container_name: monitoring-grafana
user: "0"
restart: unless-stopped restart: unless-stopped
dns: dns:
- 1.1.1.1 - 1.1.1.1
@@ -127,6 +128,7 @@ services:
GF_SECURITY_ADMIN_PASSWORD__FILE: /run/secrets/monitoring_grafana_admin_password GF_SECURITY_ADMIN_PASSWORD__FILE: /run/secrets/monitoring_grafana_admin_password
GF_USERS_ALLOW_SIGN_UP: "false" GF_USERS_ALLOW_SIGN_UP: "false"
GF_AUTH_ANONYMOUS_ENABLED: "false" GF_AUTH_ANONYMOUS_ENABLED: "false"
GF_PLUGINS_PREINSTALL_DISABLED: "true"
entrypoint: entrypoint:
- /bin/sh - /bin/sh
- -c - -c
@@ -162,7 +164,7 @@ services:
- traefik.http.services.monitoring-grafana.loadbalancer.server.port=3000 - traefik.http.services.monitoring-grafana.loadbalancer.server.port=3000
grafana-dashboard-importer: grafana-dashboard-importer:
image: python:3.13-alpine image: python:3.14-alpine
container_name: monitoring-grafana-dashboard-importer container_name: monitoring-grafana-dashboard-importer
restart: "no" restart: "no"
profiles: profiles:
@@ -273,18 +275,20 @@ services:
echo "Dashboard import complete." echo "Dashboard import complete."
node-exporter: node-exporter:
image: prom/node-exporter:v1.9.1 image: prom/node-exporter:v1.11.1@sha256:e9cff4fc67b1818f8c97adb115b9f12c9a54b533de86765d4a0effc01b357205
container_name: monitoring-node-exporter container_name: monitoring-node-exporter
restart: unless-stopped restart: unless-stopped
command: command:
- --path.procfs=/host/proc - --path.procfs=/host/proc
- --path.sysfs=/host/sys - --path.sysfs=/host/sys
- --path.rootfs=/rootfs - --path.rootfs=/rootfs
- --collector.textfile.directory=/textfile
- --collector.filesystem.mount-points-exclude=^/(dev|proc|sys|run|var/lib/docker/.+|var/lib/containers/storage/.+)($|/) - --collector.filesystem.mount-points-exclude=^/(dev|proc|sys|run|var/lib/docker/.+|var/lib/containers/storage/.+)($|/)
volumes: volumes:
- /proc:/host/proc:ro - /proc:/host/proc:ro
- /sys:/host/sys:ro - /sys:/host/sys:ro
- /:/rootfs:ro - /:/rootfs:ro
- /mnt/user/services/posture-check/textfile:/textfile:ro
networks: networks:
- monitoring_net - monitoring_net
expose: expose:
@@ -293,7 +297,7 @@ services:
- no-new-privileges:true - no-new-privileges:true
cadvisor: cadvisor:
image: ghcr.io/google/cadvisor:v0.53.0 image: ghcr.io/google/cadvisor:v0.57.0@sha256:e75bdb03b74b0b6995f208f166fead2e6e555dde73e44200113bb26f41b1981d
container_name: monitoring-cadvisor container_name: monitoring-cadvisor
restart: unless-stopped restart: unless-stopped
command: command:
@@ -314,7 +318,7 @@ services:
- no-new-privileges:true - no-new-privileges:true
influxdb3-core: influxdb3-core:
image: influxdb:3.9.1-core@sha256:1d58c8b9ac90153ae3a020ede2810c8284933dda50ac71e7573389ab6f012128 image: influxdb:3.9.2-core@sha256:31ad94df2248134989b2cf73d965e51dd5f35dfae22d7ed8f4776b12e6f69f4e
container_name: monitoring-influxdb3-core container_name: monitoring-influxdb3-core
user: "0" user: "0"
restart: unless-stopped restart: unless-stopped
@@ -0,0 +1,295 @@
{
"uid": "homelab-family-status",
"title": "Homelab / Family Status",
"tags": ["homelab", "family", "status"],
"timezone": "browser",
"schemaVersion": 39,
"version": 1,
"refresh": "30s",
"time": {
"from": "now-24h",
"to": "now"
},
"panels": [
{
"id": 1,
"type": "stat",
"title": "Family Apps Up",
"datasource": "Prometheus",
"gridPos": {"h": 5, "w": 6, "x": 0, "y": 0},
"targets": [
{
"refId": "A",
"expr": "sum(probe_success{job=\"blackbox-http\", instance=~\"https://(vault|immich|cloud|paperless|mealie|ntfy|glance)\\\\.kaleschke\\\\.info\"})"
}
],
"fieldConfig": {
"defaults": {
"unit": "short",
"thresholds": {
"mode": "absolute",
"steps": [
{"color": "red", "value": null},
{"color": "yellow", "value": 5},
{"color": "green", "value": 7}
]
}
},
"overrides": []
},
"options": {"reduceOptions": {"calcs": ["lastNotNull"], "fields": "", "values": false}}
},
{
"id": 2,
"type": "stat",
"title": "Family Apps Down",
"datasource": "Prometheus",
"gridPos": {"h": 5, "w": 6, "x": 6, "y": 0},
"targets": [
{
"refId": "A",
"expr": "count(probe_success{job=\"blackbox-http\", instance=~\"https://(vault|immich|cloud|paperless|mealie|ntfy|glance)\\\\.kaleschke\\\\.info\"}) - sum(probe_success{job=\"blackbox-http\", instance=~\"https://(vault|immich|cloud|paperless|mealie|ntfy|glance)\\\\.kaleschke\\\\.info\"})"
}
],
"fieldConfig": {
"defaults": {
"unit": "short",
"thresholds": {
"mode": "absolute",
"steps": [
{"color": "green", "value": null},
{"color": "red", "value": 1}
]
}
},
"overrides": []
},
"options": {"reduceOptions": {"calcs": ["lastNotNull"], "fields": "", "values": false}}
},
{
"id": 3,
"type": "stat",
"title": "Backup Age",
"datasource": "Prometheus",
"gridPos": {"h": 5, "w": 6, "x": 12, "y": 0},
"targets": [
{
"refId": "A",
"expr": "(time() - homelab_borg_last_completed_timestamp_seconds) / 3600"
}
],
"fieldConfig": {
"defaults": {
"unit": "h",
"decimals": 1,
"thresholds": {
"mode": "absolute",
"steps": [
{"color": "green", "value": null},
{"color": "yellow", "value": 24},
{"color": "red", "value": 30}
]
}
},
"overrides": []
},
"options": {"reduceOptions": {"calcs": ["lastNotNull"], "fields": "", "values": false}}
},
{
"id": 4,
"type": "stat",
"title": "TLS Days Left",
"datasource": "Prometheus",
"gridPos": {"h": 5, "w": 6, "x": 18, "y": 0},
"targets": [
{
"refId": "A",
"expr": "min((probe_ssl_earliest_cert_expiry{job=\"blackbox-http\"} - time()) / 86400)"
}
],
"fieldConfig": {
"defaults": {
"unit": "d",
"decimals": 0,
"thresholds": {
"mode": "absolute",
"steps": [
{"color": "red", "value": null},
{"color": "yellow", "value": 7},
{"color": "green", "value": 21}
]
}
},
"overrides": []
},
"options": {"reduceOptions": {"calcs": ["lastNotNull"], "fields": "", "values": false}}
},
{
"id": 5,
"type": "stat",
"title": "Critical Containers Down",
"datasource": "Prometheus",
"gridPos": {"h": 5, "w": 6, "x": 0, "y": 5},
"targets": [
{
"refId": "A",
"expr": "count(homelab_critical_container_running) - sum(homelab_critical_container_running)"
}
],
"fieldConfig": {
"defaults": {
"unit": "short",
"thresholds": {
"mode": "absolute",
"steps": [
{"color": "green", "value": null},
{"color": "red", "value": 1}
]
}
},
"overrides": []
},
"options": {"reduceOptions": {"calcs": ["lastNotNull"], "fields": "", "values": false}}
},
{
"id": 6,
"type": "stat",
"title": "Image Drift",
"datasource": "Prometheus",
"gridPos": {"h": 5, "w": 6, "x": 6, "y": 5},
"targets": [
{
"refId": "A",
"expr": "count(homelab_gitops_runtime_image_match) - sum(homelab_gitops_runtime_image_match)"
}
],
"fieldConfig": {
"defaults": {
"unit": "short",
"thresholds": {
"mode": "absolute",
"steps": [
{"color": "green", "value": null},
{"color": "yellow", "value": 1},
{"color": "red", "value": 3}
]
}
},
"overrides": []
},
"options": {"reduceOptions": {"calcs": ["lastNotNull"], "fields": "", "values": false}}
},
{
"id": 7,
"type": "stat",
"title": "Last Backup OK",
"datasource": "Prometheus",
"gridPos": {"h": 5, "w": 6, "x": 12, "y": 5},
"targets": [
{
"refId": "A",
"expr": "homelab_borg_last_success"
}
],
"fieldConfig": {
"defaults": {
"unit": "bool",
"thresholds": {
"mode": "absolute",
"steps": [
{"color": "red", "value": null},
{"color": "green", "value": 1}
]
}
},
"overrides": []
},
"options": {"reduceOptions": {"calcs": ["lastNotNull"], "fields": "", "values": false}}
},
{
"id": 8,
"type": "stat",
"title": "Metrics Age",
"datasource": "Prometheus",
"gridPos": {"h": 5, "w": 6, "x": 18, "y": 5},
"targets": [
{
"refId": "A",
"expr": "(time() - homelab_textfile_exporter_last_run_timestamp_seconds) / 60"
}
],
"fieldConfig": {
"defaults": {
"unit": "m",
"decimals": 0,
"thresholds": {
"mode": "absolute",
"steps": [
{"color": "green", "value": null},
{"color": "yellow", "value": 60},
{"color": "red", "value": 120}
]
}
},
"overrides": []
},
"options": {"reduceOptions": {"calcs": ["lastNotNull"], "fields": "", "values": false}}
},
{
"id": 9,
"type": "timeseries",
"title": "Family App Availability",
"datasource": "Prometheus",
"gridPos": {"h": 8, "w": 12, "x": 0, "y": 10},
"targets": [
{
"refId": "A",
"expr": "probe_success{job=\"blackbox-http\", instance=~\"https://(vault|immich|cloud|paperless|mealie|ntfy|glance)\\\\.kaleschke\\\\.info\"}",
"legendFormat": "{{instance}}"
}
],
"fieldConfig": {"defaults": {"unit": "bool", "min": 0, "max": 1}, "overrides": []},
"options": {"legend": {"displayMode": "list", "placement": "bottom"}}
},
{
"id": 10,
"type": "timeseries",
"title": "Family App Response Time",
"datasource": "Prometheus",
"gridPos": {"h": 8, "w": 12, "x": 12, "y": 10},
"targets": [
{
"refId": "A",
"expr": "probe_duration_seconds{job=\"blackbox-http\", instance=~\"https://(vault|immich|cloud|paperless|mealie|ntfy|glance)\\\\.kaleschke\\\\.info\"}",
"legendFormat": "{{instance}}"
}
],
"fieldConfig": {"defaults": {"unit": "s"}, "overrides": []},
"options": {"legend": {"displayMode": "list", "placement": "bottom"}}
},
{
"id": 11,
"type": "table",
"title": "Public Endpoint Status",
"datasource": "Prometheus",
"gridPos": {"h": 9, "w": 24, "x": 0, "y": 18},
"targets": [
{
"refId": "A",
"expr": "probe_http_status_code{job=\"blackbox-http\"}",
"format": "table",
"instant": true
}
],
"transformations": [
{
"id": "organize",
"options": {
"excludeByName": {"Time": true, "__name__": true, "job": true},
"renameByName": {"Value": "status_code", "instance": "target"}
}
}
]
}
]
}
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+103
View File
@@ -28,6 +28,24 @@ groups:
summary: "{{ $labels.instance }} is slow" summary: "{{ $labels.instance }} is slow"
description: "Blackbox probe duration is above 5 seconds for {{ $labels.instance }}." 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 - name: homelab-host
rules: rules:
- alert: HomelabDiskAlmostFull - alert: HomelabDiskAlmostFull
@@ -39,6 +57,15 @@ groups:
summary: "Disk usage high on {{ $labels.mountpoint }}" summary: "Disk usage high on {{ $labels.mountpoint }}"
description: "{{ $labels.mountpoint }} is above 85% used." 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 - alert: HomelabHighMemoryUsage
expr: 100 * (1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) > 90 expr: 100 * (1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) > 90
for: 10m for: 10m
@@ -56,3 +83,79 @@ groups:
annotations: annotations:
summary: "Traefik 5xx responses for {{ $labels.service }}" summary: "Traefik 5xx responses for {{ $labels.service }}"
description: "Traefik reports at least 5 5xx responses for {{ $labels.service }} within 5 minutes." 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."
+7 -3
View File
@@ -1,6 +1,6 @@
# Borg Backup Scope for KalliLabcore # 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. 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` | | 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 | | 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` | | 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` | | 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` | | 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` | | 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 ## Database Dumps Required
### Shared PostgreSQL (`postgresql17`) ### Shared PostgreSQL (`postgresql17`, runtime PostgreSQL 18)
- `mailarchiver` - `mailarchiver`
- `paperless` - `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 ## Explicitly Not Backed Up as Raw Live DB Files
- `/mnt/user/appdata/postgresql17` - `/mnt/user/appdata/postgresql17`
- `/mnt/user/appdata/postgresql18`
- `/mnt/user/appdata/mealie/postgres` - `/mnt/user/appdata/mealie/postgres`
- `/mnt/user/appdata/mealie/postgres18`
- `/mnt/user/appdata/immich_postgres` - `/mnt/user/appdata/immich_postgres`
- `/mnt/user/appdata/immich_postgres_vectorchord`
- `/mnt/user/appdata/nextcloud/postgres` - `/mnt/user/appdata/nextcloud/postgres`
- `/mnt/user/appdata/nextcloud/postgres18`
- `/mnt/user/appdata/komodo/mongo` - `/mnt/user/appdata/komodo/mongo`
- `/mnt/user/appdata/redis` - `/mnt/user/appdata/redis`
- `/mnt/user/appdata/scrutiny/influxdb` - `/mnt/user/appdata/scrutiny/influxdb`
+1 -1
View File
@@ -1,6 +1,6 @@
services: services:
borg-ui: borg-ui:
image: ainullcode/borg-ui@sha256:867c73983e5bef5491cdee1c34acf85fe8a9fe4f6ad5a9381e7ca2c382359ce6 image: ainullcode/borg-ui@sha256:b44c0a92b650d80f215a986dadda5c2604c61eb28a7571e19c046eff41d761e7
container_name: borg-ui container_name: borg-ui
restart: unless-stopped restart: unless-stopped
security_opt: security_opt:
+9
View File
@@ -5,6 +5,7 @@ These scripts are intended to run on the Unraid host before a Borg backup starts
## Current script ## Current script
- `pre-backup-dumps.sh` - `pre-backup-dumps.sh`
- `gitea-bundle-mirror.sh`
## Output ## Output
@@ -12,7 +13,13 @@ Fresh dump artifacts are written to:
- `/mnt/user/backups/borg/dumps/latest` - `/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. 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 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 archive of `/boot/config` plus checksum and manifest. Treat this archive as
@@ -21,6 +28,8 @@ secret backup material.
## Notes ## Notes
- The script is written for host execution where `docker` is available. - 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 does not assume Backrest.
- It keeps only the latest dump set because Borg itself provides history. - It keeps only the latest dump set because Borg itself provides history.
+153
View File
@@ -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 "$@"
+13 -8
View File
@@ -37,7 +37,13 @@ ensure_dirs() {
atomic_write() { atomic_write() {
target="$1" target="$1"
tmp="$2" tmp="$2"
mode="${3:-644}"
mkdir -p "$(dirname "$target")" 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" mv "$tmp" "$target"
} }
@@ -179,15 +185,15 @@ backup_unraid_flash_config() {
--exclude='config/plugins/*/*.zip' \ --exclude='config/plugins/*/*.zip' \
--exclude='config/plugins/*/*.md5' \ --exclude='config/plugins/*/*.md5' \
-czf "$tmp" config -czf "$tmp" config
chmod 600 "$tmp" # Flash-Config ist sensibel (enthaelt /boot/config inkl. Plugin-/SMB-/Network-Settings);
atomic_write "$output" "$tmp" # bewusst 0600, damit der Nearline-Pull ueber SMB sie nicht versehentlich greift.
atomic_write "$output" "$tmp" 600
( (
cd "$LATEST_DIR" cd "$LATEST_DIR"
sha256sum "$(basename "$output")" sha256sum "$(basename "$output")"
) > "$tmp_checksum" ) > "$tmp_checksum"
chmod 600 "$tmp_checksum" atomic_write "$checksum" "$tmp_checksum" 600
atomic_write "$checksum" "$tmp_checksum"
{ {
printf 'created_utc=%s\n' "$(date -u '+%Y-%m-%dT%H:%M:%SZ')" 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 '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/*/' printf 'excluded=%s\n' 'downloadable plugin package archives under /boot/config/plugins/*/'
} > "$tmp_manifest" } > "$tmp_manifest"
chmod 600 "$tmp_manifest" atomic_write "$manifest" "$tmp_manifest" 600
atomic_write "$manifest" "$tmp_manifest"
} }
dump_optional_pg_db() { dump_optional_pg_db() {
@@ -273,7 +278,7 @@ main() {
need_cmd sha256sum need_cmd sha256sum
ensure_dirs ensure_dirs
# Shared PostgreSQL 17 # Shared PostgreSQL 18 (historischer Containername: postgresql17)
if need_container "postgresql17"; then if need_container "postgresql17"; then
# Use the cluster admin/superuser for all shared-cluster dumps. The # Use the cluster admin/superuser for all shared-cluster dumps. The
# application roles exist, but they can have different passwords from 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. # 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/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 # MongoDB
dump_mongo_container "komodo-mongo" "$LATEST_DIR/komodo-mongo.archive.gz" dump_mongo_container "komodo-mongo" "$LATEST_DIR/komodo-mongo.archive.gz"
+1 -1
View File
@@ -1,6 +1,6 @@
services: services:
code-server: 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 container_name: code-server
restart: unless-stopped restart: unless-stopped
security_opt: security_opt:
+1 -1
View File
@@ -1,6 +1,6 @@
services: services:
filebrowser: filebrowser:
image: filebrowser/filebrowser:v2.63.2@sha256:4dce87308b9f9cfbcf8d0a284fc9565d2b515530a6bae2d920b388161e093f26 image: filebrowser/filebrowser:v2.63.5@sha256:aefb0c20de10ef8b617995ca5522479ad40d41e6386bd01946a345c6026ff31c
container_name: filebrowser container_name: filebrowser
restart: unless-stopped restart: unless-stopped
security_opt: security_opt:
+1 -1
View File
@@ -473,7 +473,7 @@ pages:
category: core category: core
hide: false hide: false
postgresql17: postgresql17:
name: PostgreSQL 17 name: PostgreSQL 18
icon: si:postgresql icon: si:postgresql
description: Shared DB description: Shared DB
category: core category: core
+1 -1
View File
@@ -1,6 +1,6 @@
services: services:
glance: glance:
image: glanceapp/glance:v0.8.4 image: glanceapp/glance:v0.8.5
container_name: glance container_name: glance
restart: unless-stopped restart: unless-stopped
environment: environment:
+1 -1
View File
@@ -1,6 +1,6 @@
services: services:
glances: glances:
image: nicolargo/glances:latest-full@sha256:b4b0f059fa8064a0e8dae5530ce9334834ab07205269cfbf405d16b4d40c0c66 image: nicolargo/glances:latest-full@sha256:60872a1af0e40a3150975617c7e811ad7ad48f95bc45d033fb0c1737a037e4d2
container_name: glances container_name: glances
restart: unless-stopped restart: unless-stopped
pid: host pid: host
@@ -0,0 +1,187 @@
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")
Files = @(
"borg-ui.sqlite",
"filebrowser.bolt.dump",
"gitea.sqlite.dump",
"grafana.sqlite",
"immich.dump",
"komodo-mongo.archive.gz",
"mealie.dump",
"nextcloud.dump",
"postgresql17-authelia.dump",
"postgresql17-globals.sql",
"postgresql17-mailarchiver.dump",
"postgresql17-paperless.dump",
"speedtest-tracker.sqlite.dump",
"vaultwarden.sqlite.dump"
)
# Migration-/Cutover-Arbeitsverzeichnisse bleiben im Borg-Scope, sind aber
# keine Nearline-Pflichtartefakte und enthalten teils root-only Dateien.
ExcludeDirs = @(".tmp", "immich-vectorchord-*", "nextcloud-redis-pre-redis8-*", "pg18-major-*", "redis8-*", "shared-redis-pre-redis8-*")
},
@{
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"
Files = @("*.*")
ExcludeFiles = @()
ExcludeDirs = @(".tmp")
}
)
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
$files = @("*.*")
if ($Job.ContainsKey("Files") -and $Job.Files.Count -gt 0) {
$files = $Job.Files
}
$args = @(
$Job.Source,
$Job.Destination
)
$args += $files
$args += @(
"/E",
"/COPY:DAT",
"/DCOPY:DAT",
"/R:2",
"/W:5",
"/FFT",
"/XJ",
"/NP",
"/TEE",
"/LOG:$logPath"
)
if ($Job.ContainsKey("ExcludeDirs") -and $Job.ExcludeDirs.Count -gt 0) {
$args += "/XD"
$args += $Job.ExcludeDirs
}
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 -1
View File
@@ -1,4 +1,4 @@
FROM nousresearch/hermes-agent:v2026.4.16 FROM nousresearch/hermes-agent:v2026.5.29
USER root USER root
+7 -7
View File
@@ -90,16 +90,16 @@
"notes": "ADMIN_TOKEN_FILE; keine direkten Host-Ports" "notes": "ADMIN_TOKEN_FILE; keine direkten Host-Ports"
}, },
"postgresql17": { "postgresql17": {
"description": "Shared PostgreSQL Cluster", "description": "Shared PostgreSQL 18 Cluster (historischer Containername)",
"tier": 1, "tier": 1,
"category": "infra", "category": "infra",
"container_name": "postgresql17", "container_name": "postgresql17",
"dependencies": [], "dependencies": [],
"url": null, "url": null,
"dump_file": 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?", "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": { "komodo-core": {
"description": "GitOps UI / API / Stack-Manager", "description": "GitOps UI / API / Stack-Manager",
@@ -200,9 +200,9 @@
"dependencies": [], "dependencies": [],
"url": null, "url": null,
"dump_file": "immich.dump", "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?", "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": { "immich_redis": {
"description": "Immich Cache", "description": "Immich Cache",
@@ -248,7 +248,7 @@
"dependencies": [], "dependencies": [],
"url": null, "url": null,
"dump_file": "mealie.dump", "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?", "first_check": "mealie_internal Netz? Disk-Space?",
"notes": "interne DB; mealie_internal Netz" "notes": "interne DB; mealie_internal Netz"
}, },
@@ -287,7 +287,7 @@
"dependencies": [], "dependencies": [],
"url": null, "url": null,
"dump_file": 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?", "first_check": "nextcloud_internal Netz? Disk-Space?",
"notes": "interne DB" "notes": "interne DB"
}, },
+10 -10
View File
@@ -1,7 +1,7 @@
# services.yaml — Maschinenlesbare Wissensbasis fuer Hermes Alert Enrichment # services.yaml — Maschinenlesbare Wissensbasis fuer Hermes Alert Enrichment
# #
# Abgeleitet aus docs/SERVICE_CATALOG.md # Abgeleitet aus docs/SERVICE_CATALOG.md
# Stand: 2026-05-06 # Stand: 2026-05-31
# #
# Zweck: Hermes laedt diese Datei beim Alert-Anreichern, um Abhaengigkeiten, # Zweck: Hermes laedt diese Datei beim Alert-Anreichern, um Abhaengigkeiten,
# Dump-Zeitstempel und den ersten Diagnoseschritt nachzuschlagen. # Dump-Zeitstempel und den ersten Diagnoseschritt nachzuschlagen.
@@ -128,7 +128,7 @@ services:
notes: "ADMIN_TOKEN_FILE; keine direkten Host-Ports" notes: "ADMIN_TOKEN_FILE; keine direkten Host-Ports"
postgresql17: 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 tier: 1
category: infra category: infra
container_name: postgresql17 container_name: postgresql17
@@ -136,9 +136,9 @@ services:
url: null url: null
dump_file: null dump_file: null
data_paths: 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?" 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: komodo-core:
description: GitOps UI / API / Stack-Manager description: GitOps UI / API / Stack-Manager
@@ -231,8 +231,8 @@ services:
data_paths: data_paths:
- /mnt/user/appdata/paperless-gpt/data - /mnt/user/appdata/paperless-gpt/data
- /mnt/user/appdata/paperless-gpt/prompts - /mnt/user/appdata/paperless-gpt/prompts
first_check: "Paperless API erreichbar? LLM/Ollama erreichbar? API Token gesetzt?" first_check: "Paperless API erreichbar? OpenAI API Key gesetzt? Provider/Model auf openai/gpt-5.4-mini?"
notes: "API Token als Stack ENV; abhaengig von laufendem Paperless" notes: "PAPERLESS_API_TOKEN und OPENAI_API_KEY als Stack ENV; kein lokaler Ollama-Zugriff"
immich_server: immich_server:
description: Foto-/Video-App description: Foto-/Video-App
@@ -261,9 +261,9 @@ services:
url: null url: null
dump_file: immich.dump dump_file: immich.dump
data_paths: data_paths:
- /mnt/user/appdata/immich_postgres - /mnt/user/appdata/immich_postgres_vectorchord
first_check: "immich_default Netz? Disk-Space? pg_isready?" 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: immich_redis:
description: Immich Cache description: Immich Cache
@@ -314,7 +314,7 @@ services:
url: null url: null
dump_file: mealie.dump dump_file: mealie.dump
data_paths: data_paths:
- /mnt/user/appdata/mealie/postgres - /mnt/user/appdata/mealie/postgres18
first_check: "mealie_internal Netz? Disk-Space?" first_check: "mealie_internal Netz? Disk-Space?"
notes: "interne DB; mealie_internal Netz" notes: "interne DB; mealie_internal Netz"
@@ -360,7 +360,7 @@ services:
url: null url: null
dump_file: null dump_file: null
data_paths: data_paths:
- /mnt/user/appdata/nextcloud/postgres - /mnt/user/appdata/nextcloud/postgres18
first_check: "nextcloud_internal Netz? Disk-Space?" first_check: "nextcloud_internal Netz? Disk-Space?"
notes: "interne DB" notes: "interne DB"
+3 -3
View File
@@ -4,7 +4,7 @@ services:
# Netz: komodo_net (internal: true) niemals frontend_net # Netz: komodo_net (internal: true) niemals frontend_net
# ────────────────────────────────────────────────────────────────── # ──────────────────────────────────────────────────────────────────
komodo-mongo: komodo-mongo:
image: mongo:7.0.32@sha256:32979a1189dfdc44da3f5ed40d910495f5ad8f6f7f77556646f890a30b2d3f56 image: mongo:8.0.23@sha256:44aa79ae28ff80b56fe58681b66cda9336706df408a5175a6c04988aa54610d3
container_name: komodo-mongo container_name: komodo-mongo
labels: labels:
komodo.skip: komodo.skip:
@@ -33,7 +33,7 @@ services:
# Admin-Dienst: bewusst ohne pauschale ForwardAuth-Middleware; dokumentierte Ausnahme # Admin-Dienst: bewusst ohne pauschale ForwardAuth-Middleware; dokumentierte Ausnahme
# ────────────────────────────────────────────────────────────────── # ──────────────────────────────────────────────────────────────────
komodo-core: komodo-core:
image: ghcr.io/moghtech/komodo-core:2@sha256:8a7dbba232e4e49797bb412be5f78207c89fcf22cc2727b38631ae30f7518a4c image: ghcr.io/moghtech/komodo-core:2@sha256:7afbcfa99674bf3f51539ec3aa7235795e9b994af9b7099a6c4c654d5d8a5b6b
container_name: komodo-core container_name: komodo-core
init: true init: true
restart: unless-stopped restart: unless-stopped
@@ -79,7 +79,7 @@ services:
# Ausnahme: Docker-Socket ohne :ro (Periphery startet/stoppt Container) # Ausnahme: Docker-Socket ohne :ro (Periphery startet/stoppt Container)
# ────────────────────────────────────────────────────────────────── # ──────────────────────────────────────────────────────────────────
komodo-periphery: komodo-periphery:
image: ghcr.io/moghtech/komodo-periphery:2@sha256:8ac9f2ef9c1461b95c862d445da00253005e7094d1e30f5b7b04b8d60ca7a3d6 image: ghcr.io/moghtech/komodo-periphery:2@sha256:7fb1a4807d125ce036a17d37c940b4001402afcaf342a2c720c98d096b1b54da
container_name: komodo-periphery container_name: komodo-periphery
init: true init: true
restart: unless-stopped restart: unless-stopped
+106
View File
@@ -0,0 +1,106 @@
#!/usr/bin/env bash
set -euo pipefail
HOST_IP="${HOST_IP:-192.168.178.58}"
FRITZBOX_URL="${FRITZBOX_URL:-http://192.168.178.1:49000/tr64desc.xml}"
BORG_DB="${BORG_DB:-/mnt/user/appdata/borg-ui/data/borg.db}"
REPO_ROOT="${REPO_ROOT:-/mnt/user/services/homelab-infra}"
section() {
printf '\n## %s\n\n' "$1"
}
section "FRITZBox"
if fritz_xml="$(curl -fsS --max-time 5 "$FRITZBOX_URL")"; then
printf '%s\n' "$fritz_xml" | grep -E '<friendlyName>|<modelName>|<Display>' | sed -E 's/^[[:space:]]+//'
else
echo "FRITZBox TR-064 descriptor not reachable at $FRITZBOX_URL"
fi
if ipv6_fw="$(
curl -fsS --max-time 5 \
-H 'Content-Type: text/xml; charset="utf-8"' \
-H 'SOAPACTION: "urn:schemas-upnp-org:service:WANIPv6FirewallControl:1#GetFirewallStatus"' \
--data '<?xml version="1.0"?><s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><s:Body><u:GetFirewallStatus xmlns:u="urn:schemas-upnp-org:service:WANIPv6FirewallControl:1" /></s:Body></s:Envelope>' \
http://192.168.178.1:49000/igd2upnp/control/WANIPv6Firewall1
)"; then
firewall_enabled="$(printf '%s\n' "$ipv6_fw" | sed -n 's:.*<FirewallEnabled>\(.*\)</FirewallEnabled>.*:\1:p')"
pinhole_allowed="$(printf '%s\n' "$ipv6_fw" | sed -n 's:.*<InboundPinholeAllowed>\(.*\)</InboundPinholeAllowed>.*:\1:p')"
echo "IPv6 FirewallEnabled: ${firewall_enabled:-unknown}"
echo "IPv6 InboundPinholeAllowed: ${pinhole_allowed:-unknown}"
else
echo "FRITZBox IPv6 firewall status not reachable via TR-064."
fi
section "Host IPv6"
global_ipv6="$(
ip -6 addr show scope global \
| awk '/inet6 / {print $2}' \
| grep -Ev '^(fd|fe80:)' || true
)"
if [[ -n "$global_ipv6" ]]; then
echo "Provider/global IPv6 addresses present:"
printf '%s\n' "$global_ipv6"
else
echo "No provider/global IPv6 address on host; only ULA/link-local/Tailscale may be present."
fi
tailscale ip -4 2>/dev/null | sed 's/^/Tailscale IPv4: /' || true
tailscale ip -6 2>/dev/null | sed 's/^/Tailscale IPv6: /' || true
section "DNS"
for name in \
kaleschke.info \
vault.kaleschke.info \
git.kaleschke.info \
cloud.kaleschke.info \
traefik.kaleschke.info; do
echo "$name"
dig +short @1.1.1.1 "$name" A | sed 's/^/ public A /' || true
dig +short @1.1.1.1 "$name" AAAA | sed 's/^/ public AAAA /' || true
done
section "Host Listeners"
ss -ltnp | awk '
/:(80|443|222|53|8082|8181)[[:space:]]/ ||
/100\.80\.98\.33:8082/ ||
/127\.0\.0\.1:8181/ {print}
' | sort -k4
section "External Port Smoke"
public_ipv4="$(curl -4 -fsS --max-time 5 https://ifconfig.co 2>/dev/null || true)"
if [[ -z "$public_ipv4" ]]; then
public_ipv4="$(dig +short @1.1.1.1 kaleschke.info A | tail -n 1)"
fi
for check in \
"$public_ipv4 443 expected-open" \
"$public_ipv4 80 expected-closed" \
"$public_ipv4 222 expected-closed"; do
set -- $check
target="$1"
port="$2"
expected="$3"
if timeout 5 bash -c "cat < /dev/null > /dev/tcp/$target/$port" 2>/dev/null; then
result="open"
else
result="closed"
fi
printf '%s:%s %s (%s)\n' "$target" "$port" "$result" "$expected"
done
section "Borg UI Repository"
if [[ -f "$BORG_DB" ]]; then
sqlite3 -header -csv "$BORG_DB" \
"select name,repository_type,path,remote_path,last_backup,last_check,borg_version,custom_flags from repositories order by id;"
sqlite3 -header -csv "$BORG_DB" \
"select id,repository,status,archive_name,started_at,completed_at,nfiles from backup_jobs order by id desc limit 3;"
else
echo "Borg UI database not found: $BORG_DB"
fi
section "Restore Freshness"
if [[ -x "$REPO_ROOT/ops/restore-tests/run-restore-checks.sh" ]]; then
"$REPO_ROOT/ops/restore-tests/run-restore-checks.sh" freshness
else
echo "Restore freshness script not executable: $REPO_ROOT/ops/restore-tests/run-restore-checks.sh"
fi
+104
View File
@@ -0,0 +1,104 @@
#!/usr/bin/env bash
set -euo pipefail
MODE="dry-run"
CUTOFF_DATE="2026-06-02"
if [[ "${1:-}" == "--execute" ]]; then
MODE="execute"
elif [[ "${1:-}" != "" && "${1:-}" != "--dry-run" ]]; then
echo "Usage: $0 [--dry-run|--execute]" >&2
exit 2
fi
if [[ "$(id -u)" -ne 0 ]]; then
echo "Must run as root on the Unraid host." >&2
exit 1
fi
today="$(date +%F)"
if [[ "$MODE" == "execute" && "$today" < "$CUTOFF_DATE" ]]; then
echo "Refusing: cutoff is $CUTOFF_DATE, today is $today." >&2
exit 1
fi
declare -a CANDIDATES=(
"/mnt/user/appdata/postgresql17|/mnt/user/appdata/postgresql18|shared PostgreSQL 17 rollback"
"/mnt/user/appdata/mealie/postgres|/mnt/user/appdata/mealie/postgres18|Mealie PostgreSQL 17 rollback"
"/mnt/user/appdata/nextcloud/postgres|/mnt/user/appdata/nextcloud/postgres18|Nextcloud PostgreSQL 17 rollback"
"/mnt/user/appdata/immich_postgres|/mnt/user/appdata/immich_postgres_vectorchord|Immich pgvecto.rs rollback"
)
require_container_healthy() {
local name="$1"
local state
local health
state="$(docker inspect "$name" --format '{{.State.Status}}' 2>/dev/null || true)"
health="$(docker inspect "$name" --format '{{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}' 2>/dev/null || true)"
if [[ "$state" != "running" ]]; then
echo "Container $name is not running (state=$state)." >&2
exit 1
fi
if [[ "$health" != "healthy" && "$health" != "none" ]]; then
echo "Container $name is not healthy (health=$health)." >&2
exit 1
fi
}
echo "Alt-volume release check"
echo "Mode: $MODE"
echo "Date: $today"
echo
require_container_healthy postgresql17
require_container_healthy mealie-postgres
require_container_healthy nextcloud-postgres
require_container_healthy immich_postgres
if docker ps --filter health=unhealthy --format '{{.Names}}' | grep -q .; then
echo "Refusing: unhealthy containers exist." >&2
docker ps --filter health=unhealthy --format ' {{.Names}} {{.Status}}' >&2
exit 1
fi
if [[ -x /mnt/user/services/homelab-infra/ops/restore-tests/run-restore-checks.sh ]]; then
/mnt/user/services/homelab-infra/ops/restore-tests/run-restore-checks.sh freshness
fi
mapfile -t active_mounts < <(docker inspect $(docker ps -q) --format '{{range .Mounts}}{{println .Source}}{{end}}' 2>/dev/null || true)
for entry in "${CANDIDATES[@]}"; do
IFS='|' read -r old_path active_path label <<< "$entry"
if [[ ! -d "$active_path" ]]; then
echo "Missing active path for $label: $active_path" >&2
exit 1
fi
if [[ ! -d "$old_path" ]]; then
echo "Already absent: $old_path ($label)"
continue
fi
if printf '%s\n' "${active_mounts[@]}" | grep -Fxq "$old_path"; then
echo "Refusing: old path is still mounted by a running container: $old_path" >&2
exit 1
fi
size="$(du -sh "$old_path" 2>/dev/null | awk '{print $1}')"
echo "Candidate: $old_path ($label, $size)"
echo "Active: $active_path"
if [[ "$MODE" == "execute" ]]; then
rm -rf --one-file-system "$old_path"
echo "Removed: $old_path"
else
echo "Dry-run: would remove $old_path"
fi
echo
done
echo "Alt-volume release check completed."
+107
View File
@@ -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: Mai-2026-Audit F-19 "Keine Container-Memory-Limits".
## Warum nicht heute
Audit-TODO 2026-05-30: F-19 ist nicht akut. Es gibt keinen dokumentierten OOM-/Memory-Vorfall. `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. F-19 bleibt dann 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.
+26
View File
@@ -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,
};
+85
View File
@@ -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"
+7 -1
View File
@@ -32,6 +32,11 @@ Ziel:
- `paperless-restore-test.sh`: hosttauglicher Paperless-Restore-Job - `paperless-restore-test.sh`: hosttauglicher Paperless-Restore-Job
- `paperless-plan.md`: konkreter Paperless-Testplan - `paperless-plan.md`: konkreter Paperless-Testplan
- `paperless-compose.test.yml`: isolierte Testinstanz fuer Paperless inkl. Test-Postgres und Test-Redis - `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 - `check-restore-freshness.ps1`: woechentlicher Frische-Check fuer Dumps und Reports
- `run-restore-checks.ps1`: einfacher Dispatcher fuer Restore-Jobs - `run-restore-checks.ps1`: einfacher Dispatcher fuer Restore-Jobs
- `check-restore-freshness.sh`: hosttauglicher Frische-Check - `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 Vaultwarden-Restore am 2026-05-07 erfolgreich verifiziert
- echter Gitea-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 - 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 - Bash-Dispatcher und Bash-Restore-Jobs am 2026-05-07 erfolgreich hostseitig verifiziert
- Restore-Lab und Report-Pfade auf dem Host angelegt - Restore-Lab und Report-Pfade auf dem Host angelegt
- V1-Ablauf weiter ohne `ntfy`, mit Bereinigung nach Erfolg - 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. Vor dem ersten echten Testlauf muessen Zielpfade, Quellpfade und Bereinigungsschritte bewusst freigegeben werden.
+10 -4
View File
@@ -21,11 +21,11 @@ require_path() {
} }
latest_archive_name() { latest_archive_name() {
docker exec "$BORG_CONTAINER" python3 - <<'PY' docker exec -i "$BORG_CONTAINER" python3 - <<'PY'
import sqlite3 import sqlite3
conn = sqlite3.connect('/data/borg.db') conn = sqlite3.connect('/data/borg.db')
cur = conn.cursor() 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() row = cur.fetchone()
if not row: if not row:
raise SystemExit("No completed borg archive found") raise SystemExit("No completed borg archive found")
@@ -34,7 +34,7 @@ PY
} }
borg_repo_url() { borg_repo_url() {
docker exec "$BORG_CONTAINER" python3 - <<'PY' docker exec -i "$BORG_CONTAINER" python3 - <<'PY'
import sqlite3 import sqlite3
conn = sqlite3.connect('/data/borg.db') conn = sqlite3.connect('/data/borg.db')
cur = conn.cursor() cur = conn.cursor()
@@ -59,10 +59,16 @@ conn = sqlite3.connect('/data/borg.db')
cur = conn.cursor() cur = conn.cursor()
cur.execute("select path from repositories where path is not null and path != '' order by id asc limit 1") cur.execute("select path from repositories where path is not null and path != '' order by id asc limit 1")
repo = cur.fetchone()[0] 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] archive = cur.fetchone()[0]
with open('/local/secrets/borg_repo_passphrase.txt', 'r', encoding='utf-8') as f: with open('/local/secrets/borg_repo_passphrase.txt', 'r', encoding='utf-8') as f:
os.environ['BORG_PASSPHRASE'] = f.read().strip() 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.makedirs(extract_dir, exist_ok=True)
os.chdir(extract_dir) os.chdir(extract_dir)
subprocess.run(['borg', 'extract', f'{repo}::{archive}', *paths], check=True) subprocess.run(['borg', 'extract', f'{repo}::{archive}', *paths], check=True)
View File
+68
View File
@@ -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
+89
View File
@@ -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.
+44
View File
@@ -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"
+247
View File
@@ -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"
+128
View 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 `ops/restore-tests/schedule.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 (`ops/restore-tests/schedule.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 (`ops/restore-tests/schedule.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 (`ops/restore-tests/schedule.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-*`.
+135
View File
@@ -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"
+4 -4
View File
@@ -1,6 +1,6 @@
services: services:
restoretest-paperless-postgres: restoretest-paperless-postgres:
image: postgres:17.9@sha256:5b96f1a16bd9768b060dd2ffe55cb6225c4d9ef4d214a8b21eb08134869a97e4 image: postgres:18.4@sha256:8ff36f3c66371cba71d20ceedccfc3de9669a68737607888c4ef0af93abe8e39
container_name: restoretest-paperless-postgres container_name: restoretest-paperless-postgres
restart: "no" restart: "no"
environment: environment:
@@ -8,9 +8,9 @@ services:
POSTGRES_USER: paperless POSTGRES_USER: paperless
POSTGRES_DB: paperless POSTGRES_DB: paperless
POSTGRES_PASSWORD: restoretest-paperless-db POSTGRES_PASSWORD: restoretest-paperless-db
PGDATA: /var/lib/postgresql/data PGDATA: /var/lib/postgresql/18/docker
volumes: volumes:
- /mnt/user/backups/restore-lab/paperless/postgres:/var/lib/postgresql/data - /mnt/user/backups/restore-lab/paperless/postgres:/var/lib/postgresql
healthcheck: healthcheck:
test: ["CMD-SHELL", "pg_isready -U paperless -d paperless"] test: ["CMD-SHELL", "pg_isready -U paperless -d paperless"]
interval: 10s interval: 10s
@@ -20,7 +20,7 @@ services:
- no-new-privileges:true - no-new-privileges:true
restoretest-paperless-redis: restoretest-paperless-redis:
image: redis:7-alpine image: redis:8.8.0-alpine@sha256:09160599abd229764c0fb44cb6be640294e1d360a54b19985ab4843dcf2d90f1
container_name: restoretest-paperless-redis container_name: restoretest-paperless-redis
restart: "no" restart: "no"
command: command:
View File
+9 -1
View File
@@ -1,5 +1,5 @@
param( param(
[ValidateSet("freshness","vaultwarden","gitea","paperless")] [ValidateSet("freshness","vaultwarden","gitea","paperless","immich")]
[string]$Mode, [string]$Mode,
[switch]$WhatIf [switch]$WhatIf
) )
@@ -35,4 +35,12 @@ switch ($Mode) {
} }
exit $LASTEXITCODE exit $LASTEXITCODE
} }
"immich" {
if ($WhatIf) {
& (Join-Path $base "immich-restore-test.ps1") -WhatIf
} else {
& (Join-Path $base "immich-restore-test.ps1")
}
exit $LASTEXITCODE
}
} }
+7 -1
View File
@@ -28,8 +28,14 @@ case "$MODE" in
fi fi
exec "$SCRIPT_DIR/paperless-restore-test.sh" 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 exit 1
;; ;;
esac esac
@@ -7,7 +7,7 @@ SUCCESS_TOPIC="${2:-${RESTORE_SUCCESS_TOPIC:-homelab-info}}"
FAILURE_TOPIC="${RESTORE_FAILURE_TOPIC:-homelab-alerts}" FAILURE_TOPIC="${RESTORE_FAILURE_TOPIC:-homelab-alerts}"
if [ -z "$MODE" ]; then 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 exit 1
fi fi
+27 -2
View File
@@ -27,15 +27,24 @@ Alle 2 Monate:
Quartalsweise: Quartalsweise:
- Restore-/DR-Sanity-Check - Restore-/DR-Sanity-Check
- `immich` Restore-Smoke-Test (DB + UI, ohne produktive Foto-Mounts; Erstlauf 2026-05-27 erfolgreich)
- pruefen: - pruefen:
- Restore-Lab-Struktur - Restore-Lab-Struktur
- Reports - Reports
- Skripte und Pfade - Skripte und Pfade
- Doku noch passend - Doku noch passend
Spaeter: Quartals-Belegung:
- `immich` als eigener Sprint | Quartal | Mini-Restore | Sanity-Fokus |
|---|---|---|
| Q1 | `paperless` | Tier-1-Reihenfolge, Posture-Check, Borg-Frische |
| Q2 | `immich` | Komodo-Bootstrap, Gitea-Bundles, Secrets-Inventur |
| Q3 | `mealie` oder `nextcloud` | DNS-Pfad und Cert-Expiry-Sicht |
| Q4 | `vaultwarden` oder `gitea` | Externe Abhaengigkeiten, Hetzner, GitHub-Mirror |
Bestaetigte Mini-Restores: Vaultwarden, Gitea und Paperless am 2026-05-07;
Immich am 2026-05-27; Paperless erneut am 2026-05-31.
## Konkreter Kalender ## Konkreter Kalender
@@ -51,6 +60,8 @@ Spaeter:
- `monthly-random-restore.sh` - `monthly-random-restore.sh`
- Quartalsweise am 1. Werktag des Quartals: - Quartalsweise am 1. Werktag des Quartals:
- DR-/Restore-Sanity-Check - DR-/Restore-Sanity-Check
- Quartalsweise am 2. Sonntag im zweiten Quartalsmonat, 08:30:
- `immich`
## Unraid User Scripts Cron ## Unraid User Scripts Cron
@@ -60,6 +71,7 @@ Spaeter:
| `restore-vaultwarden-monthly` | `0 7 1-7 * 6` | erster Samstag im Monat 07:00 | | `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-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-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 | | `monthly-random-restore` | `0 9 1 * *` | erster Kalendertag im Monat 09:00 |
## Betriebsmodus ## Betriebsmodus
@@ -98,6 +110,19 @@ Manuell:
- Restore-Lab: `/mnt/user/backups/restore-lab` - Restore-Lab: `/mnt/user/backups/restore-lab`
- Reports: `/mnt/user/backups/restore-reports` - Reports: `/mnt/user/backups/restore-reports`
## Quartals-Sanity
Kurz pruefen:
- `docs/DISASTER_RECOVERY.md` Phase 1-5 passt noch zum Repo.
- `docs/RESTORE_MATRIX.md` Tier-Klassifizierung und letzte Restore-Erfolge stimmen.
- `docs/SECRETS_MAP.md` enthaelt die noetigen Secret-Pfade ohne Werte.
- Gitea-Bundles sind frisch und klonbar.
- GitHub-Mirror ist erreichbar und aktuell.
- ntfy-Testnachricht an `homelab-info` kommt an.
- Offline-Kopie der Borg-Passphrase ist auffindbar.
- Capacity-Stand passt zu `docs/CAPACITY_AND_LIFECYCLE.md`.
## Erfolgsregel ## Erfolgsregel
Ein Test gilt erst dann als erfolgreich, wenn: Ein Test gilt erst dann als erfolgreich, wenn:
View File
+1 -1
View File
@@ -1,6 +1,6 @@
services: services:
scrutiny: scrutiny:
image: ghcr.io/starosdev/scrutiny:latest-omnibus@sha256:9f77acf1a567802bbefe0f0e7510cb2ecc20d319276cf183512c7e843214abd8 image: ghcr.io/starosdev/scrutiny:latest-omnibus@sha256:41c5faefb96766d27d58a829fa19b3f4f27da4160926de3255cf142a85a90c12
container_name: scrutiny container_name: scrutiny
restart: unless-stopped restart: unless-stopped
privileged: true privileged: true
+1 -1
View File
@@ -1,6 +1,6 @@
services: services:
speedtest-tracker: speedtest-tracker:
image: lscr.io/linuxserver/speedtest-tracker:1.13.12@sha256:eb3d249f16177964daa4fff7f6a90bbf6645f4e23158d92f5cddb133728d0804 image: lscr.io/linuxserver/speedtest-tracker:1.14.3@sha256:79c00631575dec6d91c10ed904c211224f00813013a305c2284324e195a538bb
container_name: speedtest-tracker container_name: speedtest-tracker
restart: unless-stopped restart: unless-stopped
security_opt: security_opt:
+1
View File
@@ -5,5 +5,6 @@ Diese Skripte sind bewusst versionierte Operator-Hilfen fuer den Windows-Neuaufs
- `backup-delta-after-2026-05-07.ps1` kopiert nach einem definierten Cutoff lokale Nutzdaten in ein Backup-Ziel. - `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. - `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. - `cleanup-dualboot-bcd.ps1` bereinigt BCD-Bootmenueeintraege und verlangt eine explizite Textbestaetigung.
- `ops/windows-reinstall/docs/windows-neuaufsetzen-masterplan.md` und `ops/windows-reinstall/docs/postinstall-erstes-ziel-codex.md` enthalten die zugehoerigen Operator-Notizen.
Die Skripte enthalten keine Secrets, arbeiten aber mit lokalen Windows-Datentraegern und duerfen nur interaktiv und mit vorheriger Sichtpruefung ausgefuehrt werden. Die Skripte enthalten keine Secrets, arbeiten aber mit lokalen Windows-Datentraegern und duerfen nur interaktiv und mit vorheriger Sichtpruefung ausgefuehrt werden.
@@ -82,7 +82,7 @@ Wichtige Dateien:
H:\Windows-Neuaufsetzen-Backup\12_Exportierte_Listen\installierte_programme_lesbar.md H:\Windows-Neuaufsetzen-Backup\12_Exportierte_Listen\installierte_programme_lesbar.md
H:\Windows-Neuaufsetzen-Backup\12_Exportierte_Listen\kritische_programme_lizenz_check.md H:\Windows-Neuaufsetzen-Backup\12_Exportierte_Listen\kritische_programme_lizenz_check.md
H:\Windows-Neuaufsetzen-Backup\09_Programme_Settings_Lizenzen\keys_exporte\banking4_license_private.txt H:\Windows-Neuaufsetzen-Backup\09_Programme_Settings_Lizenzen\keys_exporte\banking4_license_private.txt
G:\Gitea_Clone\homelab-infra\docs\windows-neuaufsetzen-masterplan.md G:\Gitea_Clone\homelab-infra\ops\windows-reinstall\docs\windows-neuaufsetzen-masterplan.md
``` ```
Dann Codex/ChatGPT sagen: Dann Codex/ChatGPT sagen:
@@ -100,4 +100,3 @@ Reihenfolge:
3. Microsoft 365 ueber Microsoft-Konto installieren. 3. Microsoft 365 ueber Microsoft-Konto installieren.
4. WISO Steuer installieren und Steuerdateien aus `WISO_Steuer_Dokumente` pruefen. 4. WISO Steuer installieren und Steuerdateien aus `WISO_Steuer_Dokumente` pruefen.
5. Restliche Programme mit UniGetUI/WinGet/Installern wieder aufbauen. 5. Restliche Programme mit UniGetUI/WinGet/Installern wieder aufbauen.

Some files were not shown because too many files have changed in this diff Show More