From e5cd8678b05c76523089a644dd9defe009795abd Mon Sep 17 00:00:00 2001 From: Sami Date: Mon, 23 Mar 2026 16:50:24 -0700 Subject: [PATCH] fix: add pylon relay fallback to dashboard status endpoint The dashboard uses /api/v1/services/status (not /api/health/services) for live status cards. This endpoint was missing pylon relay fallback, so services unreachable from the Docker container showed as OFF even when the pylon was running. Also adds Windows VBS startup wrapper for pylon persistence across reboots. Co-Authored-By: Claude Opus 4.6 --- dashcaddy-api/pylon/start-pylon.vbs | 2 ++ dashcaddy-api/routes/services.js | 35 +++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 dashcaddy-api/pylon/start-pylon.vbs diff --git a/dashcaddy-api/pylon/start-pylon.vbs b/dashcaddy-api/pylon/start-pylon.vbs new file mode 100644 index 0000000..26d096b --- /dev/null +++ b/dashcaddy-api/pylon/start-pylon.vbs @@ -0,0 +1,2 @@ +Set WshShell = CreateObject("WScript.Shell") +WshShell.Run """C:\Program Files\nodejs\node.exe"" ""E:\CaddyCerts\sites\dashcaddy-api\pylon\dashcaddy-pylon.js""", 0, False diff --git a/dashcaddy-api/routes/services.js b/dashcaddy-api/routes/services.js index 06fcac8..fcb9569 100644 --- a/dashcaddy-api/routes/services.js +++ b/dashcaddy-api/routes/services.js @@ -65,6 +65,25 @@ module.exports = function(ctx) { }); } + async function probeViaPylon(targetUrl) { + const pylonConfig = ctx.siteConfig?.pylon; + if (!pylonConfig?.url) return null; + try { + const probeUrl = `${pylonConfig.url}/probe?url=${encodeURIComponent(targetUrl)}`; + const headers = {}; + if (pylonConfig.key) headers['x-pylon-key'] = pylonConfig.key; + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), 12000); + const response = await ctx.fetchT(probeUrl, { method: 'GET', signal: controller.signal, headers }); + clearTimeout(timeout); + if (!response.ok) return null; + const data = await response.json(); + return data; + } catch (_) { + return null; + } + } + async function probeServiceStatus(id, service) { const startedAt = process.hrtime.bigint(); let url = resolveProbeUrl(id, service); @@ -92,6 +111,22 @@ module.exports = function(ctx) { } } + // Pylon relay fallback — if direct probes failed, try through the pylon + if (error && ctx.siteConfig?.pylon) { + const pylonResult = await probeViaPylon(url); + if (pylonResult && pylonResult.status) { + const responseTime = Number((process.hrtime.bigint() - startedAt) / 1000000n); + return { + id, + isUp: pylonResult.status === 'healthy', + statusCode: pylonResult.statusCode || 0, + responseTime, + url, + via: 'pylon' + }; + } + } + const responseTime = Number((process.hrtime.bigint() - startedAt) / 1000000n); if (error) { return {