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>
This commit is contained in:
2026-10-12 16:54:43 +02:00
co-authored by Claude Opus 5.5
parent 83bd9e0922
commit 8f5b31c0cb
26 changed files with 3243 additions and 12 deletions
+29 -1
View File
@@ -5,11 +5,14 @@ import { alleHostsAbgleichen } from "@/server/services/abgleich";
import {
WARTESCHLANGE,
ZEITPLAN_ABGLEICH,
ZEITPLAN_MELDUNG,
ZEITPLAN_MELDUNG_TAKT,
warteschlangeHolen,
type VorgangsJob,
} from "@/server/vorgaenge/warteschlange";
import { vorgangAusfuehren } from "@/worker/ausfuehren";
import { abholungenAufraeumen } from "@/worker/ablaeufe/privatekey";
import { taktVersenden, zusammenfassungVersenden } from "@/worker/benachrichtigung/versand";
import { deploymentKeySicherstellen } from "@/worker/geheimnisse";
import { HANDLER } from "@/worker/handler";
@@ -61,7 +64,32 @@ async function starten(): Promise<void> {
console.log(`Zeitplan: ${anzahl} Abgleich(e) eingereiht.`);
});
console.log(`sshkv-worker ${VERSION} bereit (Abgleich: ${zeitplan}).`);
// Benachrichtigungen (#028): taeglich die Zusammenfassung, alle fuenf Minuten geaenderte
// Host-Keys (dringend) und faellige Wiederholungen. Fehler nur ins Log – das
// Versandprotokoll haelt fest, was nachzuholen ist.
const meldung = process.env["SSHKV_MELDUNG_CRON"]?.trim() || "0 7 * * *";
const melden = (lauf: () => Promise<{ gesendet: number; fehlgeschlagen: number }>) => async () => {
const bilanz = await lauf().catch((fehler: unknown) => {
console.error("Benachrichtigung:", fehler instanceof Error ? fehler.message : fehler);
return null;
});
if (bilanz && bilanz.gesendet + bilanz.fehlgeschlagen > 0)
console.log(`Benachrichtigung: ${bilanz.gesendet} gesendet, ${bilanz.fehlgeschlagen} fehlgeschlagen.`);
};
await boss.createQueue(ZEITPLAN_MELDUNG);
await boss.schedule(ZEITPLAN_MELDUNG, meldung, null, { tz: "Europe/Berlin" });
await boss.work(
ZEITPLAN_MELDUNG,
melden(() => zusammenfassungVersenden()),
);
await boss.createQueue(ZEITPLAN_MELDUNG_TAKT);
await boss.schedule(ZEITPLAN_MELDUNG_TAKT, "*/5 * * * *", null, { tz: "Europe/Berlin" });
await boss.work(
ZEITPLAN_MELDUNG_TAKT,
melden(() => taktVersenden()),
);
console.log(`sshkv-worker ${VERSION} bereit (Abgleich: ${zeitplan}, Meldungen: ${meldung}).`);
const beenden = async (signal: string) => {
console.log(`${signal} empfangen, beende laufende Vorgänge …`);