Files
sshkeyvault/src/server/vorgaenge/warteschlange.ts
T
florian.wildeandClaude Opus 5.5 8f5b31c0cb feat(benachrichtigung): E-Mail und Webhook bei Drift und Ablauf (#028)
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>
2026-10-12 16:54:43 +02:00

66 lines
2.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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);
}