feat(lxc): LXC-Container ausrollen, mit oder ohne Node-SSH

Container aus dem Proxmox-Template (neueste passende Version aus
aplinfo), unprivilegiert, nesting automatisch für docker. Weil Proxmox
keine Exec-API für Container hat, läuft die Provisionierung per SSH:
bevorzugt pct exec über den Node, sonst direkt in den Container mit
einem Bereitstellungsschlüssel, der am Ende wieder entfernt wird.

Auf pve-demo beide Wege geprüft: Debian 13 mit docker per pct exec
(hello-world läuft mit nesting allein) und Ubuntu 24.04 per SSH in den
Container. Dabei gefunden: chrony scheitert ohne CAP_SYS_TIME (jetzt
if: vm), sshd -t auf Ubuntu 24.04 ohne /run/sshd.

Closes #009

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-12 13:19:13 +02:00
co-authored by Claude Opus 5.5
parent 9310164bc2
commit d25ec54ef9
18 changed files with 893 additions and 67 deletions
+13 -4
View File
@@ -14,17 +14,26 @@ export const MARKE = "::proxdeploy::";
export type RenderKontext = {
os: { id: string; version_id: string; codename?: string };
gast: { name: string; benutzer: string };
gast: { name: string; benutzer: string; typ?: "vm" | "lxc" };
};
/** Feste Bedingungen neben den Profil-Variablen: `if: vm` / `if: lxc` (bzw. `!vm`, `!lxc`). */
export const FESTE_BEDINGUNGEN = ["vm", "lxc"] as const;
export type Abschnitt = { profil: string; skript: string };
export type Gerendert = { skript: string; abschnitte: Abschnitt[] };
function bedingungErfuellt(schritt: ProfilSchritt, werte: Record<string, unknown>): boolean {
function bedingungErfuellt(
schritt: ProfilSchritt,
werte: Record<string, unknown>,
typ: "vm" | "lxc" | undefined,
): boolean {
if (!schritt.if) return true;
const verneint = schritt.if.startsWith("!");
const wert = Boolean(werte[schritt.if.replace(/^!/, "")]);
const name = schritt.if.replace(/^!/, "");
// Ohne bekannten Typ (Rendern zum Ansehen) laufen typabhängige Schritte mit.
const wert = name === "vm" || name === "lxc" ? (typ ? typ === name : !verneint) : Boolean(werte[name]);
return verneint ? !wert : wert;
}
@@ -87,7 +96,7 @@ export function profileRendern(
};
const teile: string[] = [];
for (const schritt of profil.families[adapter.familie] ?? []) {
if (!bedingungErfuellt(schritt, werte)) continue;
if (!bedingungErfuellt(schritt, werte, kontext.gast.typ)) continue;
const text = schrittRendern(schritt, adapter, vorlagenKontext);
if ("ssh_harden" in schritt) zumSchluss.push(text);
else teile.push(text);