Add a daily Docker image-update notification

Dockhand's pending-update counts were only visible if you happened to
open the Docker page. New "Docker image update available" toggle
under Settings -> Notifications, sharing the same daily
time/timezone as the secret and Tailscale key expiry reminders (same
node-schedule reschedule-on-settings-change pattern as those two).
Reads each enabled Dockhand integration's already-cached update-check
results via listContainers() rather than triggering a fresh
per-container registry lookup, so it costs nothing extra beyond what
the Docker page itself already fetches, and lists every container
with an update pending across all environments/integrations in one
notification.

Verified end-to-end against a fake local Dockhand server (one
environment, two containers, one flagged with a pending update) and a
fake webhook receiver on an isolated scratch database: the check
correctly found only the flagged container (with its newerVersion),
sent exactly one notification with the right title/content, excluded
the up-to-date container, and sent nothing at all when the setting
was toggled off despite still finding the same pending update.
Confirmed the real dev database's mtime was untouched throughout.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
bobbanandClaude Sonnet 5 committed 2026-09-20 23:44:47 +02:00
1 parent 9c07718d2d
commit 1cae35a59e
7 files changed
+107 -3

No files matched your search

+3
View File
@@ -5,6 +5,7 @@ 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 { scheduleDockerUpdateCheck } from "../services/dockerUpdateScheduler.js";
import { scheduleLogRetentionPurge } from "../services/logRetentionScheduler.js";
import { purgeOldLogs } from "../services/logRetention.js";
import { testGotify, testNtfy, testSmtp, testWebhook } from "../services/notify.js";
@@ -62,6 +63,7 @@ const updateSchema = z.object({
dnsDelete: z.boolean(),
secretCheck: z.boolean(),
tailscaleKeyCheck: z.boolean(),
dockerUpdateCheck: z.boolean(),
secretCheckTime: z.string().regex(/^\d{2}:\d{2}$/),
timezone: z.string(),
integrationFailureAlerts: z.boolean(),
@@ -92,6 +94,7 @@ settingsRouter.put("/", requireRole("admin"), asyncHandler(async (req, res) => {
if (parsed.data.notifications) {
await scheduleSecretExpiryCheck();
await scheduleTailscaleKeyExpiryCheck();
await scheduleDockerUpdateCheck();
}
if (parsed.data.logRetention) {
await scheduleLogRetentionPurge();