Usar clave SSH global del monitor

This commit is contained in:
2026-06-04 17:52:24 +02:00
parent e29df62f7a
commit d7fe855ec0
5 changed files with 228 additions and 37 deletions
+65 -6
View File
@@ -10,6 +10,8 @@ const DEFAULT_CONFIG_PATH = path.join(ROOT, "config.json");
const PORT = Number(process.env.PORT || 8787);
const AUTH_USERNAME = process.env.MONITOR_USERNAME || "";
const AUTH_PASSWORD = process.env.MONITOR_PASSWORD || "";
const DEFAULT_SSH_PRIVATE_KEY_PATH = "/ssh/monitor_rpi_ed25519";
const DEFAULT_SSH_PUBLIC_KEY_PATH = "/ssh/monitor_rpi_ed25519.pub";
let config = null;
let latestStatus = null;
@@ -42,21 +44,26 @@ async function readConfig() {
parsed.refreshIntervalSeconds = Number(parsed.refreshIntervalSeconds || 30);
parsed.idleScanIntervalSeconds = Number(parsed.idleScanIntervalSeconds || 300);
parsed.sshTimeoutSeconds = Number(parsed.sshTimeoutSeconds || 8);
parsed.sshPrivateKeyPath = String(parsed.sshPrivateKeyPath || DEFAULT_SSH_PRIVATE_KEY_PATH);
parsed.sshPublicKeyPath = String(parsed.sshPublicKeyPath || `${parsed.sshPrivateKeyPath}.pub` || DEFAULT_SSH_PUBLIC_KEY_PATH);
parsed.temperatureThresholdsC = normalizeTemperatureThresholds(parsed.temperatureThresholdsC);
parsed.metricThresholdsPercent = normalizeMetricThresholds(parsed.metricThresholdsPercent);
parsed.devices = Array.isArray(parsed.devices) ? parsed.devices : [];
parsed.devices = Array.isArray(parsed.devices) ? parsed.devices.map(normalizeDeviceConfig) : [];
return parsed;
}
async function writeConfig(nextConfig) {
const sshPrivateKeyPath = String(nextConfig.sshPrivateKeyPath || DEFAULT_SSH_PRIVATE_KEY_PATH);
const normalized = {
...nextConfig,
refreshIntervalSeconds: Number(nextConfig.refreshIntervalSeconds || 30),
idleScanIntervalSeconds: Number(nextConfig.idleScanIntervalSeconds || 300),
sshTimeoutSeconds: Number(nextConfig.sshTimeoutSeconds || 8),
sshPrivateKeyPath,
sshPublicKeyPath: String(nextConfig.sshPublicKeyPath || `${sshPrivateKeyPath}.pub` || DEFAULT_SSH_PUBLIC_KEY_PATH),
temperatureThresholdsC: normalizeTemperatureThresholds(nextConfig.temperatureThresholdsC),
metricThresholdsPercent: normalizeMetricThresholds(nextConfig.metricThresholdsPercent),
devices: Array.isArray(nextConfig.devices) ? nextConfig.devices : []
devices: Array.isArray(nextConfig.devices) ? nextConfig.devices.map(normalizeDeviceConfig) : []
};
await fs.mkdir(path.dirname(CONFIG_PATH), { recursive: true });
await fs.writeFile(CONFIG_PATH, JSON.stringify(normalized, null, 2) + "\n", "utf8");
@@ -64,6 +71,15 @@ async function writeConfig(nextConfig) {
restartScanner();
}
function normalizeDeviceConfig(device = {}) {
const authMethod = device.authMethod || (device.privateKeyPath ? "key" : "password");
return {
...device,
authMethod: authMethod === "key" ? "key" : "password",
privateKeyPath: device.privateKeyPath || ""
};
}
function classifyTemp(tempC) {
const thresholds = config.temperatureThresholdsC || {};
if (tempC >= (thresholds.critical || 80)) return "critical";
@@ -364,8 +380,12 @@ function runCommandWithInput(command, args, input, timeoutMs) {
}
async function sshMetrics(device) {
const hasPrivateKey = Boolean(device.privateKeyPath);
if (!hasPrivateKey && !device.password) {
const usesPrivateKey = device.authMethod === "key" || Boolean(device.privateKeyPath);
const privateKeyPath = device.privateKeyPath || config.sshPrivateKeyPath;
if (usesPrivateKey && !privateKeyPath) {
throw new Error("Clave privada SSH global no configurada");
}
if (!usesPrivateKey && !device.password) {
throw new Error("Credenciales SSH no configuradas");
}
@@ -388,10 +408,10 @@ async function sshMetrics(device) {
let command = "ssh";
let args = sshArgs;
if (hasPrivateKey) {
if (usesPrivateKey) {
args = [
"-i",
device.privateKeyPath,
privateKeyPath,
"-o",
"BatchMode=yes",
"-o",
@@ -627,6 +647,21 @@ function sendJson(res, statusCode, payload) {
res.end(body);
}
function sendText(res, statusCode, body, headers = {}) {
res.writeHead(statusCode, {
"Content-Type": "text/plain; charset=utf-8",
"Cache-Control": "no-store",
...headers
});
res.end(body);
}
async function readSshPublicKey() {
const publicKeyPath = config.sshPublicKeyPath || `${config.sshPrivateKeyPath || DEFAULT_SSH_PRIVATE_KEY_PATH}.pub`;
const publicKey = (await fs.readFile(publicKeyPath, "utf8")).trim();
return { publicKeyPath, publicKey };
}
function isAuthEnabled() {
return Boolean(AUTH_USERNAME && AUTH_PASSWORD);
}
@@ -737,6 +772,30 @@ async function handleRequest(req, res) {
return;
}
if (req.method === "GET" && pathname === "/api/ssh-public-key") {
try {
sendJson(res, 200, await readSshPublicKey());
} catch (error) {
sendJson(res, 404, {
error: `No se pudo leer la clave publica SSH: ${error.message}`,
publicKeyPath: config.sshPublicKeyPath || `${config.sshPrivateKeyPath || DEFAULT_SSH_PRIVATE_KEY_PATH}.pub`
});
}
return;
}
if (req.method === "GET" && pathname === "/api/ssh-public-key/download") {
try {
const { publicKeyPath, publicKey } = await readSshPublicKey();
sendText(res, 200, `${publicKey}\n`, {
"Content-Disposition": `attachment; filename="${path.basename(publicKeyPath)}"`
});
} catch (error) {
sendText(res, 404, `No se pudo leer la clave publica SSH: ${error.message}\n`);
}
return;
}
if (req.method === "POST" && pathname === "/api/config") {
const body = await readBody(req);
await writeConfig(JSON.parse(body));