docs: README im Root, Backend/Frontend-READMEs, Statuszeilen (2026-10-16)
Neues README im Root mit Zweck, Aufbau, Start, Prüfbefehlen, Betrieb und Projektstand (Issues #44, #43). Backend- und Frontend-README ersetzen die Framework-Vorlagen. CLAUDE.md: Ordnerstruktur und echte Arbeitsweise (main, gebündelt pushen, Rollout nur mit Freigabe). ARCHITECTURE §7/§8: Live-ConfigMap, ensure-secrets.sh, korrektes Admin-Seed-Verhalten. .env.example um BACKUP_MAX_IMPORT_BYTES und SEED_ADMIN_* ergänzt. Statuszeilen in Plänen Rückgrat, T3–T6 und Specs Fahrplan, T7. Refs #42 Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
1 parent
b48fb157c8
commit
ec8c95056d
15 files changed
+194
-125
No files matched your search
+4
-3
@@ -233,7 +233,8 @@ Fachliche Annahmen, getroffen weil das Lastenheft an der Stelle keine eindeutige
|
||||
- `pod-hardening`: Frontend mit `readOnlyRootFilesystem`, `emptyDir` auf `/tmp` und `/app/.next/cache`, ohne Service-Account-Token, Ressourcen laut Spec §7.
|
||||
- `backend-roles`: `APICORE_ROLE=api` samt initContainern `migrate` und `seed` im Deployment `apicore-backend`, Deployment `apicore-worker`, CronJobs `apicore-job-<name>`. Das neue Backend-Image (CMD nur `node dist/main.js`) geht nur zusammen mit dieser Komponente live. Ohne `APICORE_ROLE` bricht es den Start in Produktion ab, und ohne die initContainer liefe keine Migration.
|
||||
- `api-hpa`: HPA `apicore-backend` (`autoscaling/v2`, 1–2 Pods bei 70 % CPU); entfernt `replicas` aus dem Deployment.
|
||||
- `overlays/encry/`: Namespace `apicore`, ConfigMaps, Ingresses (Traefik, Middleware `apicore-cloudflare-only@kubernetescrd`, cert-manager `letsencrypt-prod` mit HTTP-01), Image-Digests. Hosts, ConfigMap-Werte und Digests sind bis zum Rollout Platzhalter (`example.com`, `sha256:000…`).
|
||||
- `overlays/encry/`: Namespace `apicore`, ConfigMaps, Ingresses (Traefik, Middleware `apicore-cloudflare-only@kubernetescrd`, cert-manager `letsencrypt-prod` mit HTTP-01), Image-Digests. Die ConfigMap-Werte sind seit 2026-10-15 die Live-Werte aus dem Cluster (Runbook Schritt 1, Commit `6e3e9ae`). Hosts und TLS in `ingress-*.yaml` und die Image-Digests sind bis zum Rollout noch Platzhalter (`example.com`, `sha256:000…`).
|
||||
- `scripts/ensure-secrets.sh`: ergänzt fehlende Schlüssel in `apicore-backend-secret` und `apicore-telegram`, ohne vorhandene Werte zu ändern, und gibt nur Schlüsselnamen aus (`--dry-run` zeigt nur an). Die Secrets selbst legt der Inhaber an.
|
||||
- `tests/`: `manifest-policy.test.mjs` prüft das gerenderte Overlay (NetworkPolicies nach Kubernetes-Semantik, Pod-Härtung, Ressourcen, CronJobs, Migration, Digests, Live-Selektoren) und jeden Rollout-Schritt (DNS, Ingress, api → Postgres und alle DB-Clients bleiben erreichbar). `render-stages.mjs` rendert die Schritte 1, 2, 3 und 5 aus dem Overlay. Die CI (`.gitea/workflows/manifests.yml`, nur `push` und `workflow_dispatch`, kein `pull_request`, weil der Runner im Cluster läuft) rendert mit `kubectl kustomize`, validiert mit `kubeconform -strict` und führt den Test aus; kubectl, kubeconform und yq mit festen Versionen und SHA-256.
|
||||
- **Telegram-Secret.** `apicore-telegram` (Schlüssel `TELEGRAM_BOT_TOKEN`) bindet nur `apicore-worker` ein: als einzelne `secretKeyRef`-Variable mit `optional: true` am Haupt-Container, ohne `envFrom`, ohne Volume, nicht im initContainer. Die api bekommt das Secret nie. Fehlt es, startet der Worker normal, nur die Telegram-Schleifen laufen nicht. `TELEGRAM_BOT_USERNAME` steht in der ConfigMap `apicore-backend-config` (api und Worker lesen sie per `envFrom`; leerer Platzhalter, bis der Bot angelegt ist); das Token steht nie in einer ConfigMap. `manifest-policy.test.mjs` prüft für den Zielstand und jede Rollout-Stufe mit Worker: genau ein Worker-Pod, genau eine Token-Variable ohne Klartextwert, kein anderer Pod mit `TELEGRAM_BOT_TOKEN`. Einrichtung bei BotFather: je Umgebung ein eigener Bot, Gruppen aus (`/setjoingroups` → Disable), kein Webhook (der Worker ruft `getUpdates`). Der Worker läuft mit `replicas: 1`, weil der Versand das annimmt (Annahme 46); Claims und Empfangs-Lease bleiben auch mit mehr Pods korrekt.
|
||||
|
||||
@@ -279,7 +280,7 @@ initContainer haben die Ressourcen der CronJob-Pods. Alle App-Pods laufen ohne S
|
||||
|
||||
**Images:** Das Base-Image `node:22-alpine` ist in beiden Dockerfiles per Digest gepinnt; ein Update ist ein bewusster Commit mit neuem Digest. Die CI taggt nur noch `sha-<commit>` und Versionen, kein `latest`. Das Overlay setzt die Digests der gebauten Images. Bis Schritt 3 bleibt beim Backend der Digest des laufenden Images.
|
||||
|
||||
**Rollout:** nur nach dem Runbook im Plan `docs/superpowers/plans/2026-10-13-plattform-rueckgrat.md`, jeder Schritt mit Freigabe des Inhabers: 0 Digests, 1 Overlay auf den Ist-Stand (Diff leer), 2 NetworkPolicies, 3 Rollen und Pod-Härtung, 4 Schlüssel trennen, 5 HPA.
|
||||
**Rollout:** nur nach dem Runbook im Plan `docs/superpowers/plans/2026-10-13-plattform-rueckgrat.md`, jeder Schritt mit Freigabe des Inhabers: 0 Digests, 1 Overlay auf den Ist-Stand (Diff leer), 2 NetworkPolicies, 3 Rollen und Pod-Härtung, 4 Schlüssel trennen, 5 HPA. Stand 2026-10-16: Im Cluster wurde noch kein Schritt angewendet; vorbereitet sind nur die ConfigMap-Werte aus Schritt 1. Live laufen das Backend-Image `sha-f5b1964` und ein Frontend-Image vom 2026-10-12, im Namespace gelten nur die drei Policies aus `rke2-cluster`. Fortschritt: Gitea-Issue „Rollout in den Cluster“ (#28).
|
||||
|
||||
## 8. Backup & Restore
|
||||
|
||||
@@ -293,7 +294,7 @@ Der Seed (`node dist/seed.js`) läuft als zweiter initContainer `seed` nur im `a
|
||||
|
||||
Scheitert die Migration, scheitert der initContainer und der neue Pod startet nicht. Wegen `maxSurge: 1` und `maxUnavailable: 0` läuft der alte Pod weiter. Die Hauptprozesse starten ohne Prisma-CLI. Die `startupProbe` der api (bis 5 Minuten) bleibt für das alte Image, das die Migration noch im `CMD` ausführt.
|
||||
|
||||
**Erster Admin-Zugang:** `prisma/seed.ts` legt beim allerersten Start (leere `User`-Tabelle) einen Administrator an (`SEED_ADMIN_EMAIL`/`SEED_ADMIN_PASSWORD`, Default `[email protected]`/`ChangeMe123!`) und gibt URL (`FRONTEND_ORIGIN`), Benutzername und Passwort einmalig im Container-Log aus. Existiert der User schon (auch deaktiviert), passiert nichts – kein erneutes Log, kein erneutes Anlegen, damit spätere Passwortänderungen nicht durch veraltete Logs verwirrend überschrieben wirken und Zugangsdaten nicht bei jedem Neustart in die Logs wandern. Ersetzen: neuen Admin über `/settings/users` anlegen, dann den alten deaktivieren (Löschen gibt es nicht, siehe "Historie statt Löschen") – die Lockout-Sperre in `UsersService.assertNotLastSystemRoleUser` verhindert ohnehin, den letzten aktiven Admin zu deaktivieren.
|
||||
**Erster Admin-Zugang:** `prisma/seed.ts` legt einen Administrator an, wenn es noch keinen Benutzer mit der Adresse `SEED_ADMIN_EMAIL` gibt (Standard `[email protected]`). Das Passwort kommt aus `SEED_ADMIN_PASSWORD`; fehlt es, erzeugt der Seed ein zufälliges (kein fest eingebautes Standardpasswort). Er gibt URL (`FRONTEND_ORIGIN`), Benutzername und Passwort einmalig im Container-Log aus. Existiert der User schon (auch deaktiviert), passiert nichts – kein erneutes Log, kein erneutes Anlegen, damit spätere Passwortänderungen nicht durch veraltete Logs verwirrend überschrieben wirken und Zugangsdaten nicht bei jedem Neustart in die Logs wandern. Ersetzen: neuen Admin über `/settings/users` anlegen, dann den alten deaktivieren (Löschen gibt es nicht, siehe "Historie statt Löschen") – die Lockout-Sperre in `UsersService.assertNotLastSystemRoleUser` verhindert ohnehin, den letzten aktiven Admin zu deaktivieren.
|
||||
|
||||
**Datensicherung:** Seit der Entscheidung, PostgreSQL selbst im Cluster zu betreiben (siehe CLAUDE.md), liegt die Backup-Verantwortung bei diesem Projekt, nicht mehr bei einem externen Cluster-/DBA-Betrieb. Umgesetzt als anwendungsseitiges `BackupModule` (`ApiCore-Backend/src/backup/`) statt der ursprünglich hier vorgesehenen reinen `pg_dump`/PITR-Strategie:
|
||||
|
||||
|
||||
Reference in new issue
Block a user