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:
@@ -0,0 +1,43 @@
|
||||
import { PgBoss } from "pg-boss";
|
||||
import { logger } from "@/lib/logger";
|
||||
|
||||
/**
|
||||
* Die Auftragswarteschlange (pg-boss auf derselben PostgreSQL, Schema `pgboss`). Die app stellt
|
||||
* nur ein, der worker arbeitet ab. Ein Auftrag wird nie automatisch wiederholt: halb angelegte
|
||||
* Gäste soll ein Mensch ansehen, nicht eine Schleife.
|
||||
*/
|
||||
export const WARTESCHLANGE = "auftrag";
|
||||
|
||||
/** Template-Bau kann bei langsamem Download dauern; danach gilt ein Job als verwaist. */
|
||||
const LAUFZEIT_SEKUNDEN = 4 * 3600;
|
||||
|
||||
export type AuftragsJob = { auftragId: string };
|
||||
|
||||
let instanz: Promise<PgBoss> | undefined;
|
||||
|
||||
export function boss(rolle: "app" | "worker"): Promise<PgBoss> {
|
||||
instanz ??= (async () => {
|
||||
const url = process.env["DATABASE_URL"];
|
||||
if (!url) throw new Error("DATABASE_URL ist nicht gesetzt.");
|
||||
const b = new PgBoss({
|
||||
connectionString: url,
|
||||
application_name: `proxdeploy-${rolle}`,
|
||||
// Wartung und Zeitpläne macht nur der worker; die app stellt nur ein.
|
||||
supervise: rolle === "worker",
|
||||
schedule: false,
|
||||
});
|
||||
b.on("error", (fehler) => logger.error({ err: fehler }, "pg-boss"));
|
||||
await b.start();
|
||||
await b.createQueue(WARTESCHLANGE, { retryLimit: 0, expireInSeconds: LAUFZEIT_SEKUNDEN });
|
||||
return b;
|
||||
})();
|
||||
return instanz;
|
||||
}
|
||||
|
||||
export async function auftragEinstellen(auftragId: string): Promise<void> {
|
||||
const b = await boss("app");
|
||||
await b.send(WARTESCHLANGE, { auftragId } satisfies AuftragsJob, {
|
||||
retryLimit: 0,
|
||||
expireInSeconds: LAUFZEIT_SEKUNDEN,
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user