Simplified and secured Local Service monitor

This commit is contained in:
iotux
2025-12-14 16:39:00 +01:00
parent 2ffc06d950
commit d76ce4e28d
6 changed files with 41 additions and 120 deletions
+29 -41
View File
@@ -1,60 +1,48 @@
const { MonitorType } = require("./monitor-type");
const { exec } = require("child_process");
const { DOWN, UP, evaluateJsonQuery } = require("../../src/util");
const { execFile } = require("child_process");
const { DOWN, UP } = require("../../src/util");
class LocalServiceMonitorType extends MonitorType {
name = "local-service";
description = "Checks if a local service is running by executing a command.";
/**
* @inheritdoc
* Check a local systemd service status.
* Uses `systemctl is-running` to determine if the service is active.
* @param {object} monitor The monitor object containing serviceName.
* @param {object} heartbeat The heartbeat object to update.
* @param {object} server The server object (unused in this specific check).
* @returns {Promise<object>} A promise that resolves with the updated heartbeat.
* @throws {Error} If the serviceName is invalid or the command execution fails.
*/
async check(monitor, heartbeat, server) {
// This is the name of the service to check e.g. "nginx.service"
const serviceName = monitor.local_service_name;
// Basic sanitization to prevent argument injection.
// This regex allows for standard service names, including those with instances like "sshd@.service".
if (!serviceName || !/^[a-zA-Z0-9._\-@]+$/.test(serviceName)) {
heartbeat.status = DOWN;
heartbeat.msg = "Invalid service name provided.";
throw new Error(heartbeat.msg);
}
return new Promise((resolve, reject) => {
exec(monitor.local_service_command, async (error, stdout, stderr) => {
execFile("systemctl", [ "is-active", serviceName ], (error, stdout, stderr) => {
// systemctl is-active exits with 0 if the service is active,
// and a non-zero code if it is inactive, failed, or not found.
if (error) {
heartbeat.status = DOWN;
heartbeat.msg = `Error executing command: ${error.message}`;
// stderr often contains useful info like "service not found"
heartbeat.msg = stderr || stdout || `Service '${serviceName}' is not running.`;
reject(new Error(heartbeat.msg));
return;
}
const output = stdout.trim();
if (monitor.local_service_check_type === "keyword") {
if (monitor.local_service_expected_output) {
if (output.includes(monitor.local_service_expected_output)) {
heartbeat.status = UP;
heartbeat.msg = `OK - Output contains "${monitor.local_service_expected_output}"`;
resolve();
} else {
heartbeat.status = DOWN;
heartbeat.msg = `Output did not contain "${monitor.local_service_expected_output}"`;
reject(new Error(heartbeat.msg));
}
} else {
heartbeat.status = UP;
heartbeat.msg = "OK - Command executed successfully";
resolve();
}
} else if (monitor.local_service_check_type === "json-query") {
try {
const data = JSON.parse(output);
const { status, response } = await evaluateJsonQuery(data, monitor.jsonPath, monitor.jsonPathOperator, monitor.expectedValue);
if (status) {
heartbeat.status = UP;
heartbeat.msg = `JSON query passes (comparing ${response} ${monitor.jsonPathOperator} ${monitor.expectedValue})`;
resolve();
} else {
throw new Error(`JSON query does not pass (comparing ${response} ${monitor.jsonPathOperator} ${monitor.expectedValue})`);
}
} catch (e) {
heartbeat.status = DOWN;
heartbeat.msg = e.message;
reject(e);
}
}
// If there's no error, the service is running.
heartbeat.status = UP;
heartbeat.msg = `Service '${serviceName}' is running.`;
resolve(heartbeat);
});
});
}