EVOLUTION-MANAGER
Edit File: ntop-service.js
/* * ntop Service - JS tool implementing the ntop service for controlling software updates, restart, etc. * 2024 © ntop */ const { promisify } = require('util'); const exec = promisify(require('child_process').exec) const execSync = require('child_process').execSync const fs = require('fs') const os = require('os'); const path = require('path'); const COMMANDS_DIR = "/var/tmp/ntop-service" const debug = false; const devmode = false; /* Detect OS */ function get_os() { const LSB_RELEASE="/usr/bin/lsb_release" if (fs.existsSync(LSB_RELEASE)) { let cmd = LSB_RELEASE + " -i"; let output = execSync(cmd).toString(); if (output.includes("CentOS") || output.includes("RedHat") || output.includes("Oracle") || output.includes("Rocky") || output.includes("AlmaLinux")) { return "redhat"; } } else { if (fs.existsSync("/etc/rocky-release") || fs.existsSync("/etc/oracle-release") || fs.existsSync("/etc/redhat-release")) { return "redhat"; } } return "debian"; } const OS = get_os(); function is_epoch(str) { if (typeof str != "string") return false; if (str.length != 10) return false; return !isNaN(str) && !isNaN(parseInt(str)); } async function check_maintenance(product_name) { const APP_BIN = "/usr/bin/" + product_name let cmd = APP_BIN + " --check-maintenance"; // | head -n 1 | tr -d '\n' | cut -d' ' -f 1" let output = await exec(cmd); if (!output || !output.stdout) { return false; } let maintenance_info = output.stdout.split(' '); if (maintenance_info.length < 1) { return false; } const expiry_epoch = maintenance_info[0]; if (expiry_epoch == "Invalid") { // Missing or expired or invalid license // Allow updates on missing license file as this does not // invalidate the license and handle community mode cmd = APP_BIN + " --check-license" let output = await exec(cmd); if (!output || !output.stdout || !output.stdout.includes("Empty")) { // Expired or invalid license console.log(product + " license is expired or invalid"); return false } } else if (is_epoch(expiry_epoch)) { const epoch = parseInt(expiry_epoch); const now = (new Date().getTime()) / 1000; if (epoch > 0 && epoch < now) { // Expired maintenance console.log(product + " maintenance is expired"); return false; } } else { // Unable to read maintenance console.log(product + ": unable to read maintenance"); return false } return true; } async function run_upgrade(product_name, full_unit_name) { let success = false; // Workaround for third-party installers not using absolute paths let CMD_PREFIX = "PATH=\"$PATH:/sbin:/usr/sbin\";"; if (OS == "debian") { // Debian or Ubuntu CMD_PREFIX = CMD_PREFIX + "DEBIAN_FRONTEND=\"noninteractive\";"; const APT_GET = CMD_PREFIX + "/usr/bin/apt-get -qq"; let cmd = APT_GET + " update"; await exec(cmd); try { // Note: using install instead of upgrade to avoid blocking the installation due to 'kept back' packages cmd = APT_GET + " install --assume-yes --fix-broken --allow-unauthenticated " + product_name; await exec(cmd); //success = true; } catch (error) { //success = false; } // Check if installation is successful (we do not trust the return code) cmd = APT_GET + " --just-print install " + product_name + " 2>&1 | grep \"Inst product_name \" | cut -d'(' -f2 | cut -d' ' -f1"; let output = await exec(cmd); if (output && !output.stdout /* empty output */) { success = true; } } else { // CentOS const YUM = CMD_PREFIX + "/usr/bin/yum" // Install let cmd = YUM + " clean all"; await exec(cmd); cmd = YUM + " check-update " + product_name; await exec(cmd); try { cmd = YUM + " -y install " + product_name; await exec(cmd); success = true; } catch (error) { success = false; } } if (success) { if (full_unit_name) { try { // Restart the service if not running (e.g. old prerm was disabling it) let cmd = "/bin/systemctl -q is-active " + full_unit_name + " || /bin/systemctl restart " + full_unit_name; await exec(cmd); } catch (error) { console.log("Unable to restart " + full_unit_name); } } } else { console.log("Unable to update " + product_name); } return success; } function get_gid(name) { const data = fs.readFileSync('/etc/group', 'utf8'); const lines = data.split('\n'); for (let i = 0; i < lines.length; i++) { const [ gname, x, gid, none ] = lines[i].split(':'); if (gname == name) { return parseInt(gid); } } return 0; } const NTOP_GROUP = 'ntop'; const NTOP_GID = get_gid(NTOP_GROUP); async function get_files(dir, files = [], recursive = false) { if (!fs.existsSync(dir)) { return files; } const file_list = fs.readdirSync(dir) for (const file of file_list) { const filepath = `${dir}/${file}`; let stats = fs.statSync(filepath); if (stats.isDirectory()) { if (recursive) { files = await get_files(filepath, files, recursive); } } else { if (stats.gid == NTOP_GID || stats.gid == 0 /* allow root */) { files.push(filepath); } } } return files; } async function get_running_services(application) { let cmd = "systemctl list-units --type=service --state=running | grep " + application + " | tr -d ' ' | cut -d '.' -f 1"; if (debug) { console.log(cmd); } let output = await exec(cmd); return output.stdout.trim().split(/\r?\n/).filter((s) => s.length > 0); } async function get_service_pid(instance) { let cmd = "systemctl show -p MainPID " + instance + " 2>/dev/null | cut -d= -f2"; if (debug) { console.log(cmd); } let output = await exec(cmd); return output.stdout.trim(); } async function restart_service(full_unit_name) { if (!full_unit_name) { return; } let cmd = "systemctl restart " + full_unit_name + ""; if (debug) { console.log(cmd); } try { await exec(cmd); } catch (error) { console.error(error); } } function unit2package(unit_name) { let product_name = undefined; switch (unit_name) { case "cento": case "cento-bridge": case "cento-ids": product_name = "cento" break; case "cluster": product_name = "pfring" break; case "disk2disk": case "disk2disk-ntopng": case "disk2n": case "n2disk": case "n2disk-ntopng": product_name = "n2disk" break; case "nprobe": product_name = "nprobe" break; case "ntap": product_name = "ntap" break; case "ntopng": product_name = "ntopng" break; default: console.log("Unexpected unit name " + unit_name); break; } return product_name; } async function update_package(product_name, full_unit_name) { if (product_name === undefined) { return; } let maintenance_ok = await check_maintenance(product_name); if (!maintenance_ok) { return; } console.log("Updating " + product_name + " ..."); if (await run_upgrade(product_name, full_unit_name)) { console.log(product_name + " installed successfully"); } } async function service_handler() { try { const files = await get_files(COMMANDS_DIR, [], true); /* Remove all files (actions received) */ for (let i = 0; i < files.length; i++) { let filepath = files[i]; if (!devmode) { try { fs.unlinkSync(filepath); } catch (err) {} } } /* Execute requested actions */ for (let i = 0; i < files.length; i++) { let filepath = files[i]; var fileinfo = path.parse(filepath); var filename = fileinfo.base; var full_unit_name = fileinfo.name; const [ unit_name, instance_name ] = full_unit_name.split('@'); var action = path.extname(filename).substring(1); if (debug || devmode) { console.log("---"); console.log("Action: " + action); console.log("Unit: " + unit_name); console.log("Instance: " + (instance_name || '-')); } switch (action) { case "restart": await restart_service(full_unit_name); break; case "update": await update_package(unit2package(unit_name), full_unit_name); break; default: console.log("Unexpected action " + action); break; } } } catch (error) { console.error(error); } if (!devmode) { setTimeout(service_handler, 1000); } } /* Main */ service_handler();