feat(auftraege): worker, Phasen und Live-Fortschritt per SSE

Aufträge laufen als pg-boss-Jobs im worker. Der Phasen-Runner der Engine
kennt weder Proxmox noch Datenbank; eine Senke schreibt Phasen und Log
in die DB und meldet per NOTIFY, die app streamt per SSE an Web und CLI
(Last-Event-ID, Herzschlag, Polling-Fallback im Browser).

VMIDs vergibt eine Advisory-Sperre je Server zusammen mit einer
Reservierungstabelle; zehn parallele Reservierungen im Test bekommen
zehn verschiedene IDs. Der Proxmox-Client hat einen Trockenmodus für
den Dry-Run: Lesendes echt, Schreibendes nur aufgezeichnet.

Live-Ansicht im Aufbau der CLI-Ausgabe. Im echten Betrieb aufgefallen:
ssh2 und pg sind CommonJS und brauchen im worker Default-Importe.

Closes #006

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-12 10:54:41 +02:00
co-authored by Claude Opus 5.5
parent 01d22aac3a
commit 76bc0e4269
38 changed files with 2274 additions and 22 deletions
+2 -2
View File
@@ -1,4 +1,4 @@
import { utils as sshUtils } from "ssh2";
import ssh2 from "ssh2";
import type { Server } from "@/generated/prisma/client";
import { ProxmoxClient, type ProxmoxVerbindung } from "@/engine/proxmox/client";
import { erkunden, type VerbindungsBericht } from "@/engine/proxmox/erkundung";
@@ -170,7 +170,7 @@ export async function sshSchluesselErzeugen(
ip: string | null,
): Promise<ServerSicht> {
const server = await serverHolen(name);
const paar = sshUtils.generateKeyPairSync("ed25519", { comment: `proxdeploy@${server.name}` });
const paar = ssh2.utils.generateKeyPairSync("ed25519", { comment: `proxdeploy@${server.name}` });
const neu = await prisma.$transaction(async (tx) => {
const aktualisiert = await tx.server.update({
where: { id: server.id },