Drift, ablaufende und abgelaufene Schluessel, unerreichbare Hosts und geaenderte Host-Keys sollen auffallen, ohne dass jemand taeglich in die Oberflaeche schaut. Versand per SMTP (Microsoft 365, STARTTLS) und per Webhook mit HMAC-Signatur, eingestellt unter /einstellungen. Die Anlaesse liest der Worker aus der Datenbank, statt sie im Abgleich auszuloesen: der Abgleich-Ablauf bleibt unberuehrt, und ein verpasster Lauf holt sich der naechste. Ein Versandprotokoll (Zustellung) mit Anlass-Fingerabdruck verhindert Doppelmeldungen und haelt Fehlschlaege fuer begrenzte Wiederholungen fest. Taeglich eine Zusammenfassung, geaenderte Host-Keys im Fuenf-Minuten-Takt sofort. Versand und Testnachricht laufen im Worker, damit der Webprozess keinen Weg zu Mailserver oder Webhook-Ziel braucht. Passwort und Geheimnis liegen nur verschluesselt in der Datenbank; die Seite zeigt "gesetzt". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
66 lines
2.5 KiB
TypeScript
66 lines
2.5 KiB
TypeScript
import { PgBoss } from "pg-boss";
|
||
|
||
/**
|
||
* Gemeinsame Anbindung an pg-boss fuer Web und Worker (#007).
|
||
*
|
||
* Der Webprozess **schickt** nur; abgearbeitet wird ausschliesslich im Worker. Beide
|
||
* teilen die Datenbank, pg-boss legt sein Schema `pgboss` daneben an.
|
||
*/
|
||
|
||
/** Eine Queue je Vorgangsart haelt Wiederholungen und Gleichzeitigkeit getrennt einstellbar. */
|
||
export const WARTESCHLANGE = "sshkv-vorgang";
|
||
export const ZEITPLAN_ABGLEICH = "sshkv-abgleich-zeitplan";
|
||
/** Taegliche Zusammenfassung und Fuenf-Minuten-Takt der Benachrichtigungen (#028). */
|
||
export const ZEITPLAN_MELDUNG = "sshkv-meldung-zeitplan";
|
||
export const ZEITPLAN_MELDUNG_TAKT = "sshkv-meldung-takt";
|
||
|
||
/** Was im Job steht. Absichtlich nur die ID: alles Weitere liegt im `Vorgang`. */
|
||
export type VorgangsJob = {
|
||
readonly vorgangId: string;
|
||
/**
|
||
* Nur fuer den Bootstrap per Passwort (#010): mit SSHKV_APP_SECRET verschluesselt. Der
|
||
* Worker loescht den Job nach dem Lauf, damit auch das Chiffrat nicht liegen bleibt.
|
||
*/
|
||
readonly geheim?: string;
|
||
};
|
||
|
||
let instanz: Promise<PgBoss> | undefined;
|
||
|
||
function verbindung(): string {
|
||
const url = process.env["DATABASE_URL"];
|
||
if (!url) throw new Error("DATABASE_URL ist nicht gesetzt. Siehe .env.example.");
|
||
return url;
|
||
}
|
||
|
||
/**
|
||
* Lazy und einmal je Prozess – wie der Prisma-Client, damit `next build` ohne Datenbank
|
||
* durchlaeuft und Hot Reload keine Verbindungen stapelt.
|
||
*/
|
||
export function warteschlangeHolen(): Promise<PgBoss> {
|
||
const zwischenspeicher = globalThis as typeof globalThis & { sshkvBoss?: Promise<PgBoss> };
|
||
const vorhanden = process.env["NODE_ENV"] !== "production" ? zwischenspeicher.sshkvBoss : instanz;
|
||
if (vorhanden) return vorhanden;
|
||
|
||
const neu = (async () => {
|
||
const boss = new PgBoss({ connectionString: verbindung(), schema: "pgboss" });
|
||
boss.on("error", (fehler: Error) => console.error("pg-boss:", fehler.message));
|
||
await boss.start();
|
||
await boss.createQueue(WARTESCHLANGE, {
|
||
// Kein automatisches Wiederholen: ein halb ausgefuehrter Schreibvorgang auf einem Host
|
||
// soll ein Mensch ansehen, nicht der Worker blind noch einmal starten.
|
||
retryLimit: 0,
|
||
// Abgeschlossene Jobs nicht lange aufheben – der Verlauf steht im Vorgang.
|
||
deleteAfterSeconds: 60 * 60,
|
||
});
|
||
return boss;
|
||
})();
|
||
if (process.env["NODE_ENV"] !== "production") zwischenspeicher.sshkvBoss = neu;
|
||
else instanz = neu;
|
||
return neu;
|
||
}
|
||
|
||
export async function jobSchicken(job: VorgangsJob): Promise<string | null> {
|
||
const boss = await warteschlangeHolen();
|
||
return boss.send(WARTESCHLANGE, job);
|
||
}
|