feat(datenbank): Datenmodell mit Datenbankregeln und Seed (#003)

Alle Modelle fuer die fuenf Phasen in einer Migration, damit spaetere
Phasen keine Umbauten an Kerntabellen brauchen. Was auch an der
Service-Schicht vorbei gelten muss, steht als CHECK, partieller Index
oder Trigger in der Datenbank: Personen-Keys ohne Private Key, genau
ein Instanz-Deployment-Key, ein vertrauter Host-Key je Algorithmus,
shell-sichere Zielbenutzer und Pfade.

Das Protokoll ist append-only und per Hash-Kette verbunden; den Hash
berechnet ein Trigger, damit keine Zeile ohne Verkettung entstehen
kann (#005 baut darauf auf).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-12 09:12:06 +02:00
co-authored by Claude Opus 5.5
parent 224386106b
commit 03c7c45576
15 changed files with 1480 additions and 13 deletions
+53
View File
@@ -0,0 +1,53 @@
import { PrismaPg } from "@prisma/adapter-pg";
import "dotenv/config";
import { PrismaClient } from "../src/generated/prisma/client";
import { PASSWORT_MINDESTLAENGE, passwortHashen } from "../src/lib/passwort-hash";
import { EINGEBAUTE_VORLAGEN } from "./vorlagen";
/**
* Grundausstattung (#003): Notfall-Admin und eingebaute Vorlagen.
*
* Laeuft bei jedem Start des migrate-Dienstes und ist deshalb idempotent. Ein bestehender
* Notfall-Admin wird nie angefasst – sonst setzte jeder Neustart ein in der Oberflaeche
* geaendertes Passwort zurueck.
*/
const prisma = new PrismaClient({ adapter: new PrismaPg({ connectionString: process.env["DATABASE_URL"] }) });
async function notfallAdmin(): Promise<void> {
const anmeldename = process.env["SSHKV_BREAKGLASS_USER"]?.trim() || "notfall";
const passwort = process.env["SSHKV_BREAKGLASS_PASSWORD"] ?? "";
if (await prisma.benutzer.findFirst({ where: { istNotfall: true } })) {
console.log("Notfall-Admin vorhanden – unverändert.");
return;
}
if (passwort.length < PASSWORT_MINDESTLAENGE) {
console.warn(
`Kein Notfall-Admin angelegt: SSHKV_BREAKGLASS_PASSWORD fehlt oder ist kürzer als ${PASSWORT_MINDESTLAENGE} Zeichen.`,
);
return;
}
await prisma.benutzer.create({
data: {
anmeldename,
name: "Notfall-Admin",
rolle: "ADMIN",
istNotfall: true,
passwortHash: await passwortHashen(passwort),
},
});
console.log(`Notfall-Admin „${anmeldename}“ angelegt. TOTP wird bei der ersten Anmeldung eingerichtet.`);
}
async function vorlagen(): Promise<void> {
for (const vorlage of EINGEBAUTE_VORLAGEN) {
await prisma.vorlage.upsert({ where: { name: vorlage.name }, create: vorlage, update: {} });
}
console.log(`${EINGEBAUTE_VORLAGEN.length} eingebaute Vorlagen geprüft.`);
}
try {
await notfallAdmin();
await vorlagen();
} finally {
await prisma.$disconnect();
}