Files
dashboard/api/update-health.js
T

128 lines
4.2 KiB
JavaScript

// ---------------------------------------------------------------------------
// Health checks for all dashboard services.
// Reads the service URLs from script.js (single source of truth), pings each
// one in parallel and writes health.json. Scheduled every minute by cron.
//
// Status classification:
// online - got any HTTP response below 500 (401/404 still means "up")
// error - server responded with 5xx
// offline - timeout / connection refused / DNS failure
// TLS certificate validity is ignored (reachability check only).
// ---------------------------------------------------------------------------
const http = require("http");
const https = require("https");
const fs = require("fs");
const path = require("path");
const { URL } = require("url");
const { proxyFor, connectThroughProxy, proxiedRequestOptions } = require("./proxy");
const OUT_FILE = process.env.OUT_FILE || "/usr/share/nginx/html/health.json";
const TIMEOUT = parseInt(process.env.HEALTH_TIMEOUT || "5000", 10);
// Reachability only — don't fail on self-signed certificates.
const httpAgent = new http.Agent({ rejectUnauthorized: false });
const httpsAgent = new https.Agent({ rejectUnauthorized: false });
function serviceUrls() {
const candidates = [
process.env.SERVICES_JS,
"/usr/share/nginx/html/script.js",
path.join(__dirname, "..", "script.js"),
].filter(Boolean);
for (const p of candidates) {
try {
const src = fs.readFileSync(p, "utf8");
const urls = [...src.matchAll(/url: *"(https?:\/\/[^"]+)"/g)].map((m) => m[1]);
if (urls.length) return [...new Set(urls)];
} catch {
// try next candidate
}
}
return [];
}
function checkUrl(url) {
return new Promise((resolve) => {
const started = Date.now();
let settled = false;
const done = (result) => {
if (settled) return;
settled = true;
resolve(result);
};
(async () => {
let req;
try {
const u = new URL(url);
const client = u.protocol === "https:" ? https : http;
const options = {
method: "GET",
timeout: TIMEOUT,
};
// Honor the outbound proxy (GITLAB_PROXY) so the GitLab badge checks
// the same path the git stats actually take (see api/proxy.js).
const proxy = proxyFor(url);
if (proxy) {
let socket = null;
if (u.protocol === "https:") {
socket = await connectThroughProxy(proxy, url, TIMEOUT, { rejectUnauthorized: false });
}
Object.assign(options, proxiedRequestOptions(proxy, url, socket));
} else {
options.agent = u.protocol === "https:" ? httpsAgent : httpAgent;
}
req = client.request(
u,
options,
(res) => {
const code = res.statusCode;
res.destroy(); // response headers are all we need
done({
status: code >= 500 ? "error" : "online",
code,
latencyMs: Date.now() - started,
error: null,
});
}
);
} catch (e) {
return done({ status: "offline", code: null, latencyMs: null, error: e.message });
}
req.on("timeout", () => {
req.destroy();
done({ status: "offline", code: null, latencyMs: null, error: "Timeout" });
});
req.on("error", (e) => {
done({ status: "offline", code: null, latencyMs: null, error: e.message });
});
req.end();
})();
});
}
async function main() {
const urls = serviceUrls();
if (urls.length === 0) {
console.error("No service URLs found — is script.js available?");
process.exit(1);
}
const results = await Promise.all(urls.map(async (u) => [u, await checkUrl(u)]));
const services = Object.fromEntries(results);
const payload = { updatedAt: new Date().toISOString(), services };
fs.writeFileSync(OUT_FILE, JSON.stringify(payload, null, 2));
const count = (s) => results.filter(([, r]) => r.status === s).length;
console.log(`Health: ${urls.length} services — ${count("online")} online, ${count("error")} error, ${count("offline")} offline`);
}
main().catch((err) => {
console.error(err);
process.exit(1);
});