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; optional Demo-Hosts (#008). * * 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 { 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 { 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.`); } /** * Demo-Hosts fuer Entwicklung, Screenshots und Handbuch (#008) – nur mit SSHKV_SEED_DEMO=1, * damit eine echte Instanz nie Phantom-Hosts bekommt. Upsert nach Name: ein zweiter Lauf * aendert nichts, und in der Oberflaeche geaenderte Demo-Hosts bleiben, wie sie sind. * Bewusst ohne Protokolleintraege: Seed-Daten sind keine Handlung eines Menschen. */ const DEMO_GRUPPEN = [ { name: "Proxmox", beschreibung: "Virtualisierungs-Cluster im Rechenzentrum" }, { name: "DMZ", beschreibung: "Von außen erreichbare Systeme" }, { name: "Kunde X", beschreibung: "Systeme, die wir für Kunde X betreiben" }, ] as const; const DEMO_HOSTS: { name: string; adresse: string; port?: number; tags: string[]; beschreibung?: string; anmeldeart?: "DIENSTBENUTZER" | "ROOT"; kunde?: string; gruppen: string[]; }[] = [ { name: "srv-pve01", adresse: "10.0.10.11", tags: ["pve", "produktion"], gruppen: ["Proxmox"] }, { name: "srv-pve02", adresse: "10.0.10.12", tags: ["pve", "produktion"], gruppen: ["Proxmox"] }, { name: "srv-backup01", adresse: "10.0.10.30", tags: ["backup"], beschreibung: "Proxmox Backup Server, zieht nachts von pve01/pve02", gruppen: ["Proxmox"], }, { name: "srv-app01", adresse: "app01.kunde-x.example", port: 2222, tags: ["web"], kunde: "Kunde X", gruppen: ["Kunde X"], }, { name: "fw-edge01", adresse: "192.168.1.1", tags: ["firewall"], anmeldeart: "ROOT", beschreibung: "Appliance ohne sudo – Anmeldung direkt als root", gruppen: ["DMZ"], }, { name: "srv-rz-claude-linux", adresse: "fd00:10::42", tags: ["test"], gruppen: [] }, ]; async function demoDaten(): Promise { if (process.env["SSHKV_SEED_DEMO"] !== "1") return; const gruppen = new Map(); for (const g of DEMO_GRUPPEN) { const gruppe = await prisma.hostgruppe.upsert({ where: { name: g.name }, create: g, update: {} }); gruppen.set(g.name, gruppe.id); } for (const h of DEMO_HOSTS) { const kunde = h.kunde ? await prisma.kunde.upsert({ where: { name: h.kunde }, create: { name: h.kunde }, update: {} }) : null; await prisma.host.upsert({ where: { name: h.name }, update: {}, create: { name: h.name, adresse: h.adresse, port: h.port ?? 22, tags: h.tags, beschreibung: h.beschreibung ?? null, anmeldeart: h.anmeldeart ?? "DIENSTBENUTZER", kundeId: kunde?.id ?? null, gruppen: { create: h.gruppen.map((name) => ({ hostgruppeId: gruppen.get(name) ?? "" })) }, }, }); } console.log(`Demo-Daten geprüft: ${DEMO_HOSTS.length} Hosts, ${DEMO_GRUPPEN.length} Gruppen.`); } try { await notfallAdmin(); await vorlagen(); await demoDaten(); } finally { await prisma.$disconnect(); }