99 lines
3.6 KiB
JavaScript
99 lines
3.6 KiB
JavaScript
const fs = require("fs");
|
|
const path = require("path");
|
|
|
|
const root = path.join(__dirname, "..");
|
|
const allowedChecks = new Set(["system_health", "update_state", "plugins", "recent_errors", "benchmark"]);
|
|
const check = String(process.argv[2] || "system_health").trim();
|
|
if (!allowedChecks.has(check)) {
|
|
console.error(`Choose one of: ${Array.from(allowedChecks).join(", ")}`);
|
|
process.exit(2);
|
|
}
|
|
|
|
const defaultSecretPath = path.join(root, ".secrets");
|
|
const configPath = path.resolve(process.env.LUMI_DIAGNOSTICS_CONFIG || (
|
|
fs.statSync(defaultSecretPath, { throwIfNoEntry: false })?.isFile()
|
|
? defaultSecretPath
|
|
: path.join(defaultSecretPath, "production-diagnostics.json")
|
|
));
|
|
let fileConfig = {};
|
|
try {
|
|
fileConfig = parseConfig(fs.readFileSync(configPath, "utf8"));
|
|
} catch (error) {
|
|
if (!(process.env.LUMI_DIAGNOSTICS_URL || process.env.LUMI_PROD_URL) ||
|
|
!(process.env.LUMI_DIAGNOSTICS_KEY || process.env.LUMI_DIAG_KEY)) {
|
|
console.error(`Diagnostics configuration was not found or is invalid: ${configPath}`);
|
|
process.exit(2);
|
|
}
|
|
}
|
|
|
|
const baseUrl = String(process.env.LUMI_DIAGNOSTICS_URL || process.env.LUMI_PROD_URL || fileConfig.base_url || "").trim();
|
|
const key = String(process.env.LUMI_DIAGNOSTICS_KEY || process.env.LUMI_DIAG_KEY || fileConfig.key || "").trim();
|
|
let endpoint;
|
|
try {
|
|
const parsed = new URL(baseUrl);
|
|
const isLoopback = ["localhost", "127.0.0.1", "::1", "[::1]"].includes(parsed.hostname);
|
|
if (parsed.protocol !== "https:" && !(parsed.protocol === "http:" && isLoopback)) {
|
|
throw new Error("Production diagnostics require HTTPS (HTTP is allowed only for loopback).");
|
|
}
|
|
if (parsed.username || parsed.password) throw new Error("Do not put credentials in the diagnostics URL.");
|
|
endpoint = new URL("/api/diagnostics/v1/run", parsed);
|
|
} catch (error) {
|
|
console.error(error.message || "Configure a valid production diagnostics URL.");
|
|
process.exit(2);
|
|
}
|
|
if (!key.startsWith("lumi_diag_")) {
|
|
console.error("The production diagnostics key is missing or invalid.");
|
|
process.exit(2);
|
|
}
|
|
|
|
const controller = new AbortController();
|
|
const timeout = setTimeout(() => controller.abort(), 15000);
|
|
fetch(endpoint, {
|
|
method: "POST",
|
|
headers: {
|
|
authorization: `Bearer ${key}`,
|
|
"content-type": "application/json",
|
|
accept: "application/json"
|
|
},
|
|
body: JSON.stringify({ check }),
|
|
signal: controller.signal
|
|
})
|
|
.then(async (response) => {
|
|
const body = await response.json().catch(() => null);
|
|
if (!response.ok || body?.ok !== true) {
|
|
throw new Error(body?.error || `Diagnostics request failed with HTTP ${response.status}.`);
|
|
}
|
|
console.log(JSON.stringify(body, null, 2));
|
|
})
|
|
.catch((error) => {
|
|
console.error(error.name === "AbortError" ? "Diagnostics request timed out." : error.message);
|
|
process.exitCode = 1;
|
|
})
|
|
.finally(() => clearTimeout(timeout));
|
|
|
|
function parseConfig(raw) {
|
|
const source = String(raw || "").trim();
|
|
try {
|
|
const parsed = JSON.parse(source);
|
|
return {
|
|
base_url: parsed.base_url || parsed.url || "",
|
|
key: parsed.key || ""
|
|
};
|
|
} catch {
|
|
const values = {};
|
|
for (const line of source.split(/\r?\n/)) {
|
|
const match = line.trim().match(/^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/);
|
|
if (!match) continue;
|
|
let value = match[2].trim();
|
|
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
|
|
value = value.slice(1, -1);
|
|
}
|
|
values[match[1]] = value;
|
|
}
|
|
return {
|
|
base_url: values.LUMI_DIAGNOSTICS_URL || values.LUMI_PROD_URL || "",
|
|
key: values.LUMI_DIAGNOSTICS_KEY || values.LUMI_DIAG_KEY || ""
|
|
};
|
|
}
|
|
}
|