From 81f6049ded26e925aadf8563b6dd1181f6c17eb9 Mon Sep 17 00:00:00 2001 From: Krystie Date: Thu, 9 Jul 2026 02:02:45 -0700 Subject: [PATCH] DC-044 Add X-DashCaddy-HealthCheck marker to batch probe endpoint The dashboard polls /api/v1/services/status (not /probe/:id) for its refresh loop. routes/services.js's requestStatusCode() didn't set the X-DashCaddy-HealthCheck: 1 marker, so the batch endpoint hit the forward_auth gate, got rate-limited by authLimiter (429), and reported 7 services (router, chat, sync, torrent, sonarr, radarr, prowlarr, requests) as down. Same fix in src/app.js /probe/:id (the single-service endpoint) for consistency. Without the marker, every probe from the container IP trips authLimiter within 20 requests and the rest of the batch fails. health-checker.js background poll was already setting the marker correctly, which is why the cached health view showed 15/15 while the live dashboard showed 8/15. --- dashcaddy-api/routes/services.js | 17 +++++++++++++++-- dashcaddy-api/src/app.js | 26 +++++++++++++++++++++----- 2 files changed, 36 insertions(+), 7 deletions(-) diff --git a/dashcaddy-api/routes/services.js b/dashcaddy-api/routes/services.js index 39bca56..0015dab 100644 --- a/dashcaddy-api/routes/services.js +++ b/dashcaddy-api/routes/services.js @@ -86,13 +86,21 @@ module.exports = function({ reject(new Error('Timeout')); }, PROBE_TIMEOUT); + // X-DashCaddy-HealthCheck: 1 — Caddy's (dashcaddy_auth) block matches this + // header (from local container IPs) to bypass the forward_auth gate. + // Without it, every probe hits authLimiter → 429 → marked TIMEOUT. + // See /etc/caddy/Caddyfile (dashcaddy_auth) and the matching logic in + // src/monitoring/health-checker.js (which sets the same marker). const req = lib.request({ hostname: parsed.hostname, port: parsed.port || (isHttps ? 443 : 80), path: parsed.pathname + parsed.search, method, agent: isHttps ? probeHttpsAgent : undefined, - headers: { 'User-Agent': APP.USER_AGENTS.PROBE }, + headers: { + 'User-Agent': APP.USER_AGENTS.PROBE, + 'X-DashCaddy-HealthCheck': '1', + }, }, (response) => { clearTimeout(timer); response.resume(); @@ -111,8 +119,13 @@ module.exports = function({ const pylonConfig = siteConfig?.pylon; if (!pylonConfig?.url) return null; try { + // Forward healthcheck marker to the remote pylon relay in case its Caddy + // is configured to bypass forward_auth on the same header. const probeUrl = `${pylonConfig.url}/probe?url=${encodeURIComponent(targetUrl)}`; - const headers = {}; + const headers = { + 'User-Agent': APP.USER_AGENTS.PROBE, + 'X-DashCaddy-HealthCheck': '1', + }; if (pylonConfig.key) headers['x-pylon-key'] = pylonConfig.key; const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), TIMEOUTS.HTTP_DEFAULT); diff --git a/dashcaddy-api/src/app.js b/dashcaddy-api/src/app.js index 6cc5846..5a2c3a1 100644 --- a/dashcaddy-api/src/app.js +++ b/dashcaddy-api/src/app.js @@ -799,14 +799,22 @@ async function createApp() { const isHttps = parsed.protocol === 'https:'; const lib = isHttps ? https : require('http'); + // X-DashCaddy-HealthCheck: 1 — Caddy's (dashcaddy_auth) block matches + // this header (from local container IPs) to bypass the forward_auth gate. + // Without it, every probe hits authLimiter → 429 → marked TIMEOUT. + // See /etc/caddy/Caddyfile (dashcaddy_auth) and the matching logic in + // src/monitoring/health-checker.js (which sets the same marker). const options = { hostname: parsed.hostname, port: parsed.port || (isHttps ? 443 : 80), path: parsed.pathname + parsed.search, method: 'HEAD', - timeout: 5000, + timeout: 8000, agent: isHttps ? httpsAgent : undefined, - headers: { 'User-Agent': APP.USER_AGENTS.PROBE }, + headers: { + 'User-Agent': APP.USER_AGENTS.PROBE, + 'X-DashCaddy-HealthCheck': '1', + }, }; const makeRequest = (method) => new Promise((resolve, reject) => { @@ -832,7 +840,12 @@ async function createApp() { if (pylonConfig?.url) { try { const pylonUrl = `${pylonConfig.url}/probe?url=${encodeURIComponent(url)}`; - const headers = { 'User-Agent': APP.USER_AGENTS.PROBE }; + // Forward healthcheck marker to the remote pylon relay in case its Caddy + // is configured to bypass forward_auth on the same header. + const headers = { + 'User-Agent': APP.USER_AGENTS.PROBE, + 'X-DashCaddy-HealthCheck': '1', + }; if (pylonConfig.key) headers['x-pylon-key'] = pylonConfig.key; const controller = new AbortController(); const pylonTimeout = setTimeout(() => controller.abort(), 8000); @@ -857,9 +870,12 @@ async function createApp() { port: 443, path: '/', method: 'GET', - timeout: 5000, + timeout: 8000, agent: httpsAgent, - headers: { 'User-Agent': APP.USER_AGENTS.PROBE } + headers: { + 'User-Agent': APP.USER_AGENTS.PROBE, + 'X-DashCaddy-HealthCheck': '1', + } }, (fRes) => { fRes.resume(); resolve(fRes.statusCode);