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:
@@ -1,6 +1,8 @@
|
||||
import type { Server } from "@/generated/prisma/client";
|
||||
import { phasenAusfuehren, type EreignisSenke } from "@/engine/ablauf/ablauf";
|
||||
import { lxcAusrollSchritte, type LxcAusrollStand } from "@/engine/ablaeufe/lxc-ausrollen";
|
||||
import { vmAusrollSchritte, type VmAusrollStand } from "@/engine/ablaeufe/vm-ausrollen";
|
||||
import type { PhasenSchritt } from "@/engine/ablauf/ablauf";
|
||||
import type { Distribution, Profil } from "@/engine/katalog/schema";
|
||||
import { profileAufloesen, type AufgeloestesProfil } from "@/engine/profile/aufloesen";
|
||||
import { ProxmoxClient, type Aufruf } from "@/engine/proxmox/client";
|
||||
@@ -11,6 +13,7 @@ import { AnwendungsFehler } from "@/server/fehler";
|
||||
import { auftragAnlegen } from "@/server/services/auftraege";
|
||||
import { serverHolen, verbindungAus } from "@/server/services/server";
|
||||
import { nodeBefehlFuer } from "@/server/services/ssh-zugang";
|
||||
import { bereitstellungsschluessel } from "@/server/services/bereitstellung";
|
||||
import { vmidVorschlagen } from "@/server/services/vmid";
|
||||
|
||||
/**
|
||||
@@ -44,14 +47,6 @@ export async function ausrollenVorbereiten(roh: unknown): Promise<Vorbereitet> {
|
||||
`${distribution.name} gibt es nicht als ${anfrage.typ === "vm" ? "VM" : "LXC-Container"}.`,
|
||||
);
|
||||
}
|
||||
if (anfrage.typ === "lxc") {
|
||||
throw new AnwendungsFehler(
|
||||
400,
|
||||
"nicht_umgesetzt",
|
||||
"LXC-Container folgen mit #009.",
|
||||
"Vorerst als VM ausrollen.",
|
||||
);
|
||||
}
|
||||
const verfuegbar = new Map(
|
||||
(await prisma.profil.findMany({ where: { aktiv: true, entferntAm: null } })).map((p) => [
|
||||
p.id,
|
||||
@@ -92,6 +87,66 @@ export async function ausrollenStarten(roh: unknown, aufrufer: Aufrufer, ip: str
|
||||
);
|
||||
}
|
||||
|
||||
export type AusrollStand = VmAusrollStand & LxcAusrollStand;
|
||||
|
||||
/** Container-Features, die die gewählten Profile verlangen (z. B. docker → nesting). */
|
||||
export function profilFeatures(profile: AufgeloestesProfil[]): { nesting: boolean; keyctl: boolean } {
|
||||
return {
|
||||
nesting: profile.some((p) => p.profil.lxc.features?.nesting),
|
||||
keyctl: profile.some((p) => p.profil.lxc.features?.keyctl),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Die Phasen für VM oder LXC – dieselben im Trockenlauf und im worker. Was sich unterscheidet
|
||||
* (reservieren oder nur vorschlagen, Auftrags-ID), kommt von außen.
|
||||
*/
|
||||
export function ausrollSchritte(
|
||||
v: Vorbereitet,
|
||||
o: {
|
||||
client: ProxmoxClient;
|
||||
vmidReservieren: (belegt: Set<number>, wunsch?: number) => Promise<number>;
|
||||
templateVmidReservieren: (belegt: Set<number>) => Promise<number>;
|
||||
angelegt: (eintrag: Record<string, unknown>) => Promise<void>;
|
||||
auftragKurzId: string;
|
||||
},
|
||||
stand: AusrollStand,
|
||||
): PhasenSchritt[] {
|
||||
const sshFuer = (knoten: string) => nodeBefehlFuer(v.server, knoten);
|
||||
if (v.anfrage.typ === "lxc") {
|
||||
return lxcAusrollSchritte(
|
||||
{
|
||||
client: o.client,
|
||||
anfrage: v.anfrage,
|
||||
distribution: v.distribution,
|
||||
profile: v.profile,
|
||||
profilFeatures: profilFeatures(v.profile),
|
||||
vmidReservieren: o.vmidReservieren,
|
||||
sshFuer,
|
||||
bereitstellung: bereitstellungsschluessel,
|
||||
angelegt: o.angelegt,
|
||||
auftragKurzId: o.auftragKurzId,
|
||||
},
|
||||
stand,
|
||||
);
|
||||
}
|
||||
return vmAusrollSchritte(
|
||||
{
|
||||
client: o.client,
|
||||
anfrage: v.anfrage,
|
||||
distribution: v.distribution,
|
||||
profile: v.profile,
|
||||
vmidReservieren: o.vmidReservieren,
|
||||
templateBau: v.anfrage.templateBauen
|
||||
? { sshFuer, auftragKurzId: o.auftragKurzId, vmidReservieren: o.templateVmidReservieren }
|
||||
: undefined,
|
||||
angelegt: o.angelegt,
|
||||
auftragKurzId: o.auftragKurzId,
|
||||
},
|
||||
stand,
|
||||
);
|
||||
}
|
||||
|
||||
export type Plan = {
|
||||
kopf: string;
|
||||
phasen: Array<{
|
||||
@@ -138,25 +193,17 @@ export async function ausrollenPlanen(roh: unknown): Promise<Plan> {
|
||||
if (angaben?.phase === "provisionierung" && nachricht.startsWith("#!/bin/bash")) skript = nachricht;
|
||||
},
|
||||
};
|
||||
const stand: VmAusrollStand = {};
|
||||
const stand: AusrollStand = {};
|
||||
try {
|
||||
const ergebnis = await phasenAusfuehren(
|
||||
vmAusrollSchritte(
|
||||
ausrollSchritte(
|
||||
v,
|
||||
{
|
||||
client,
|
||||
anfrage: v.anfrage,
|
||||
distribution: v.distribution,
|
||||
profile: v.profile,
|
||||
vmidReservieren: (belegt, wunsch) =>
|
||||
vmidVorschlagen({ serverId: v.server.id, belegt, von: v.server.gastVmidVon, wunsch }),
|
||||
templateBau: v.anfrage.templateBauen
|
||||
? {
|
||||
sshFuer: (knoten) => nodeBefehlFuer(v.server, knoten),
|
||||
auftragKurzId: "dry-run",
|
||||
vmidReservieren: (belegt) =>
|
||||
vmidVorschlagen({ serverId: v.server.id, belegt, von: v.server.templateVmidVon }),
|
||||
}
|
||||
: undefined,
|
||||
templateVmidReservieren: (belegt) =>
|
||||
vmidVorschlagen({ serverId: v.server.id, belegt, von: v.server.templateVmidVon }),
|
||||
angelegt: async () => {},
|
||||
auftragKurzId: "dry-run",
|
||||
},
|
||||
@@ -178,11 +225,16 @@ export async function ausrollenPlanen(roh: unknown): Promise<Plan> {
|
||||
}
|
||||
}
|
||||
|
||||
export function kopfzeile(v: Pick<Vorbereitet, "server" | "distribution">, stand: VmAusrollStand): string {
|
||||
export function kopfzeile(
|
||||
v: Pick<Vorbereitet, "server" | "distribution" | "anfrage">,
|
||||
stand: AusrollStand,
|
||||
): string {
|
||||
return [
|
||||
`Server ${v.server.name}`,
|
||||
stand.knoten ? `Node ${stand.knoten}` : null,
|
||||
`${v.distribution.name}${stand.template ? ` (Template ${stand.template.vmid})` : ""}`,
|
||||
v.anfrage.typ === "lxc"
|
||||
? `${v.distribution.name} (LXC)`
|
||||
: `${v.distribution.name}${stand.template ? ` (Template ${stand.template.vmid})` : ""}`,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" · ");
|
||||
|
||||
Reference in New Issue
Block a user