feat(engine): Adapter debian und Profil-Engine

Profile sind deklarative Schritte je Distributionsfamilie; der Adapter
übersetzt sie in ein idempotentes Shell-Skript. Ein neuer Adapter (rhel,
alpine) ist eine Datei und ein Eintrag in der Registry.

Variablen werden typisiert geprüft, in run-Schritten jeder Wert für die
Shell gequotet. SSH-Härtung läuft zuletzt, damit sich eine Provisionierung
per SSH nicht selbst aussperrt.

Geprüft mit shellcheck und echt in Containern (Debian 12/13, Ubuntu
22.04/24.04/26.04) inklusive zweitem Lauf. Dabei aufgefallen: frische
Images haben keine Paketlisten – der Vorspann macht jetzt apt-get update.

Closes #005

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-12 10:53:38 +02:00
co-authored by Claude Opus 5.5
parent acae1bc2a4
commit 01d22aac3a
15 changed files with 999 additions and 10 deletions
+42
View File
@@ -0,0 +1,42 @@
/**
* Rendert Profile für eine Distribution auf stdout – zum Ansehen und für die Integrationsprüfung
* in einem Container: `pnpm exec tsx scripts/profil-rendern.ts debian-13 base,docker server=zbx.local`
*/
import { adapterFuer } from "@/engine/adapter";
import { katalogLaden } from "@/engine/katalog/laden";
import { profileAufloesen } from "@/engine/profile/aufloesen";
import { profileRendern } from "@/engine/profile/rendern";
const [distroId = "debian-13", auswahl = "base", ...zuweisungen] = process.argv.slice(2);
const katalog = await katalogLaden("catalog");
const distro = katalog.distributionen.find((d) => d.eintrag.id === distroId)?.eintrag;
if (!distro) throw new Error(`Unbekannte Distribution ${distroId}`);
const profile = new Map(katalog.profile.map((p) => [p.eintrag.id, p.eintrag]));
const eingaben: Record<string, Record<string, string>> = {};
for (const z of zuweisungen) {
const [name, wert] = z.split("=");
for (const p of profile.values())
if (p.variables.some((v) => v.name === name)) (eingaben[p.id] ??= {})[name!] = wert!;
}
const aufloesung = profileAufloesen(auswahl.split(","), profile, distro.family, eingaben);
if (!aufloesung.ok) {
console.error(aufloesung.fehler);
process.exit(1);
}
const adapter = adapterFuer(distro.family)!;
const { skript } = profileRendern(
aufloesung.profile,
adapter,
{
os: distro.os,
gast: { name: "testgast", benutzer: "florian" },
},
[
adapter.benutzerAnlegen({
name: "florian",
sshSchluessel: ["ssh-ed25519 AAAAtest florian@test"],
sudo: true,
}),
],
);
process.stdout.write(skript);