feat(templates): Profile ins Template einbacken

Beim Template-Bau lassen sich Profile wählen (nur solche ohne
Pflichtvariablen). Nach dem Vorbereitungsstart laufen sie über den
Guest-Agent; danach werden Maschinen-ID, SSH-Host-Keys, Docker-Engine-ID
und apt-Cache entfernt. Ausrollen nimmt das Template mit den meisten
passenden Profilen und überspringt, was schon eingebacken ist.

Auf pve-demo: Provisionierung mit base+docker 141 s ohne, ~30 s mit
Template. Dabei gefunden: docker nimmt den Benutzer per guest.user in
die Gruppe auf – Profile mit guest.* laufen deshalb beim Deploy noch
einmal (idempotent, schnell).

Closes #022

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-12 14:20:32 +02:00
co-authored by Claude Opus 5.5
parent ebcf63100a
commit 00712dc6ce
16 changed files with 541 additions and 154 deletions
+17 -2
View File
@@ -7,6 +7,7 @@ import { TemplateBauAnfrage } from "@/lib/schemas/template";
import { melden } from "@/server/ereignisse/senke";
import { verbindungAus } from "@/server/services/server";
import { nodeBefehlFuer } from "@/server/services/ssh-zugang";
import { templateProfileAufloesen } from "@/server/services/templates";
import { vmidReservieren } from "@/server/services/vmid";
import { ablaufRegistrieren } from "../ausfuehren";
import { aufrufLogger } from "../aufruf-log";
@@ -16,10 +17,15 @@ ablaufRegistrieren("TEMPLATE_BAUEN", async ({ auftrag, senke, abbruchGewuenscht,
const server = await prisma.server.findUniqueOrThrow({ where: { id: auftrag.serverId } });
const zeile = await prisma.distribution.findUniqueOrThrow({ where: { id: anfrage.distribution } });
const distribution = zeile.definition as Distribution;
const profile = await templateProfileAufloesen(anfrage.profile, distribution.family);
await prisma.auftrag.update({
where: { id: auftrag.id },
data: { kopf: `Server ${server.name} · Node ${anfrage.knoten} · Template ${distribution.name}` },
data: {
kopf: `Server ${server.name} · Node ${anfrage.knoten} · Template ${distribution.name}${
anfrage.profile.length > 0 ? ` mit ${anfrage.profile.join(", ")}` : ""
}`,
},
});
await melden(auftrag.id);
@@ -31,6 +37,7 @@ ablaufRegistrieren("TEMPLATE_BAUEN", async ({ auftrag, senke, abbruchGewuenscht,
{
client,
distribution,
profile,
knoten: anfrage.knoten,
storage: anfrage.storage,
bridge: anfrage.bridge,
@@ -65,12 +72,20 @@ ablaufRegistrieren("TEMPLATE_BAUEN", async ({ auftrag, senke, abbruchGewuenscht,
storage: anfrage.storage,
imageDatei: stand.imageDatei!,
pruefsumme: stand.pruefsumme!,
profile: profile.map((p) => p.profil.id),
gebautVonAuftragId: auftrag.id,
},
});
return {
ok: true,
ergebnis: { vmid: stand.vmid, template: templateName(distribution), knoten: anfrage.knoten },
ergebnis: {
vmid: stand.vmid,
template: templateName(
distribution,
profile.map((p) => p.profil.id),
),
knoten: anfrage.knoten,
},
};
} finally {
await client.schliessen();