Add automatic retention purging for the Diagnostic and Audit logs

The diagnostic log already rings-buffer to 500 rows, but the audit
log had no cap at all and would grow forever. Adds an opt-in
age-based purge under Settings -> Logs: keep entries for N days,
checked on a configurable interval (hourly through monthly), plus a
manual "Purge now" button. Reuses the existing node-schedule-style
reschedule-on-settings-change pattern from the secret/Tailscale
expiry checkers, but as a plain setInterval since "how often" here is
an interval rather than a specific daily time.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
bobbanandClaude Sonnet 5 committed 2026-09-19 02:22:32 +02:00
1 parent af3f7e77d2
commit 10d123b18a
9 files changed
+281

No files matched your search

+21
View File
@@ -5,6 +5,8 @@ import { recordAudit } from "../services/audit.js";
import { getSettings, updateSettings } from "../services/settingsStore.js";
import { scheduleSecretExpiryCheck } from "../services/secretExpiryScheduler.js";
import { scheduleTailscaleKeyExpiryCheck } from "../services/tailscaleKeyExpiryScheduler.js";
import { scheduleLogRetentionPurge } from "../services/logRetentionScheduler.js";
import { purgeOldLogs } from "../services/logRetention.js";
import { testGotify, testNtfy, testSmtp, testWebhook } from "../services/notify.js";
import { asyncHandler } from "../utils/asyncHandler.js";
@@ -70,6 +72,10 @@ const updateSchema = z.object({
.object({ dateFormat: z.enum(["ymd", "dmy", "mdy"]), timeFormat: z.enum(["24h", "12h"]), pageSize: z.number().int().min(5).max(500) })
.partial()
.optional(),
logRetention: z
.object({ enabled: z.boolean(), retentionDays: z.number().int().min(1).max(3650), intervalHours: z.number().int().min(1).max(720) })
.partial()
.optional(),
});
settingsRouter.put("/", requireRole("admin"), asyncHandler(async (req, res) => {
@@ -84,6 +90,9 @@ settingsRouter.put("/", requireRole("admin"), asyncHandler(async (req, res) => {
await scheduleSecretExpiryCheck();
await scheduleTailscaleKeyExpiryCheck();
}
if (parsed.data.logRetention) {
await scheduleLogRetentionPurge();
}
await recordAudit({
actor: req.currentUser!,
@@ -151,3 +160,15 @@ settingsRouter.post("/test-webhook", requireRole("admin"), asyncHandler(async (r
res.status(502).json({ error: err instanceof Error ? err.message : String(err) });
}
}));
settingsRouter.post("/purge-logs", requireRole("admin"), asyncHandler(async (req, res) => {
const { logRetention } = await getSettings();
const result = await purgeOldLogs(logRetention.retentionDays);
await recordAudit({
actor: req.currentUser!,
category: "settings",
action: "purge_logs",
detail: { retentionDays: logRetention.retentionDays, ...result },
});
res.json(result);
}));