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>
44 lines
1.5 KiB
TypeScript
44 lines
1.5 KiB
TypeScript
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,
|
|
});
|
|
}
|