- docker-compose.coolify(.prebuilt).yml: Service craftvia-worker (Target worker, Chromium, shm_size 1gb, gleiche Härtung), Craftvia-Variablen für app und worker. - Dockerfile: worker-Stage mit HOME=/home/app (Chromium-Profil für non-root); lokaler docker build der Targets runner und worker erfolgreich, PDF-Erzeugung im Image geprüft. - .env.example/.env.prod.example/.env.coolify.example: alle Craftvia-Variablen inkl. RLS, KI-Provider, PDF_CHROMIUM_PATH, OFFLINE_MAX_DAYS, API_RATE_LIMIT_*, AI_GENERATION_RETENTION_DAYS, AI_MONTHLY_TOKEN_LIMIT. - CI (.github, .gitea): Job gate mit Postgres (pgvector) und Redis als Service: migrate deploy, seed, Passwort für craftvia_app, tsc, lint, build, npm run test. - docs/craftvia/DEPLOY.md (aus den Certvia-Deploy-Docs abgeleitet): Architektur, Domains, Secrets, Worker, Migrationen, RLS-Aktivierung, Backup/Restore, KI, Rate Limits, Aufbewahrung, Smoke, Update/Rollback. build-and-push-images.sh baut craftvia-worker. - Certvia-/ISMS-Dokumente aus docs/ nach docs/_certvia-archiv/ (mit README); Verweise in README.md und Skript-Kommentaren angepasst. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
84 lines
3.0 KiB
TypeScript
84 lines
3.0 KiB
TypeScript
import "dotenv/config";
|
|
import { PrismaClient } from "@prisma/client";
|
|
import { PrismaPg } from "@prisma/adapter-pg";
|
|
import { hashPassword } from "@/server/password";
|
|
import { provisionTenant } from "@/server/provision";
|
|
|
|
/**
|
|
* Erst-Bootstrap für Produktiv (Ersatz für den Demo-Seed, der nur für Test/Dev ist).
|
|
* Ohne diesen Schritt gibt es in Prod keinen Zugang.
|
|
*
|
|
* Legt an:
|
|
* 1. Ersten Mandanten + Mandanten-Admin → Login unter /login
|
|
* 2. Plattform-Admin (Superadmin, eigener Store) → Login unter /platform/login (MFA
|
|
* wird beim ersten Login eingerichtet)
|
|
* Beide mit denselben Zugangsdaten (BOOTSTRAP_ADMIN_*), analog zum Seed.
|
|
*
|
|
* Ausführung: im migrate-Job nach `prisma migrate deploy`, gesteuert per
|
|
* BOOTSTRAP_ADMIN=true (siehe docker-compose.coolify.yml, docs/_certvia-archiv/DEPLOY-PROD-CONTABO.md).
|
|
*
|
|
* Idempotent: provisionTenant und der platformAdmin.upsert nutzen upserts; ein bereits
|
|
* gesetztes Passwort wird beim erneuten Lauf NICHT überschrieben.
|
|
*
|
|
* Erforderliche Umgebungsvariablen:
|
|
* BOOTSTRAP_ADMIN_EMAIL, BOOTSTRAP_ADMIN_PASSWORD, BOOTSTRAP_ADMIN_NAME
|
|
* BOOTSTRAP_TENANT_NAME, BOOTSTRAP_TENANT_SLUG
|
|
* Optional:
|
|
* BOOTSTRAP_TENANT_SHORT, BOOTSTRAP_TENANT_SECTOR
|
|
*/
|
|
|
|
const prisma = new PrismaClient({
|
|
adapter: new PrismaPg({ connectionString: process.env.DATABASE_URL }),
|
|
});
|
|
|
|
function req(name: string): string {
|
|
const v = process.env[name];
|
|
if (!v || v.trim() === "") {
|
|
console.error(`✖ Bootstrap abgebrochen: Umgebungsvariable ${name} fehlt.`);
|
|
process.exit(1);
|
|
}
|
|
return v.trim();
|
|
}
|
|
|
|
async function main() {
|
|
const email = req("BOOTSTRAP_ADMIN_EMAIL").toLowerCase();
|
|
const password = req("BOOTSTRAP_ADMIN_PASSWORD");
|
|
const name = req("BOOTSTRAP_ADMIN_NAME");
|
|
const tenantName = req("BOOTSTRAP_TENANT_NAME");
|
|
const tenantSlug = req("BOOTSTRAP_TENANT_SLUG").toLowerCase();
|
|
const short = process.env.BOOTSTRAP_TENANT_SHORT?.trim() || undefined;
|
|
const sector = process.env.BOOTSTRAP_TENANT_SECTOR?.trim() || undefined;
|
|
|
|
// 1. Mandant + Mandanten-Admin (Zugang /login). Idempotent (upserts).
|
|
const tenant = await provisionTenant(prisma, {
|
|
name: tenantName,
|
|
slug: tenantSlug,
|
|
short,
|
|
sector,
|
|
admin: { email, name, password },
|
|
});
|
|
|
|
// 2. Plattform-Admin / Superadmin (Zugang /platform/login, MFA beim ersten Login).
|
|
// upsert: Passwort nur bei Neuanlage setzen — bei erneutem Lauf nicht zurücksetzen.
|
|
await prisma.platformAdmin.upsert({
|
|
where: { email },
|
|
update: { name, status: "ACTIVE" },
|
|
create: { email, name, passwordHash: await hashPassword(password), status: "ACTIVE" },
|
|
});
|
|
|
|
console.log(
|
|
`✔ Bootstrap fertig: Mandant "${tenant.name}" (${tenantSlug}), Mandanten-Admin + Plattform-Admin ${email} angelegt.`
|
|
);
|
|
console.log(
|
|
" → /platform/login = Superadmin (MFA beim ersten Login), /login = Mandanten-Zugang. Passwort nach erstem Login ändern."
|
|
);
|
|
}
|
|
|
|
main()
|
|
.then(() => prisma.$disconnect())
|
|
.catch(async (e) => {
|
|
console.error(e);
|
|
await prisma.$disconnect();
|
|
process.exit(1);
|
|
});
|