const http = require('http'); const https = require('https'); const fs = require('fs'); const os = require('os'); const unzip = require('unzip-stream'); const express = require('express'); const diskspace = require('diskspace'); const bodyParser = require('body-parser'); const multer = require('multer'); const getos = require('getos'); const sysinfo = require('systeminformation'); const { exec } = require('child_process'); const superagent = require('superagent'); let config = process.env.PICLUSTER_CONFIG ? JSON.parse(fs.readFileSync(process.env.PICLUSTER_CONFIG, 'utf8')) : JSON.parse(fs.readFileSync('../config.json', 'utf8')); const app = express(); if (config.ssl_self_signed) { process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; } app.use(express.json({ limit: '20mb' })); const upload = multer({ dest: '../' }); const scheme = config.ssl ? 'https://' : 'http://'; const ssl_self_signed = config.ssl_self_signed === false; let server = config.web_connect; let { server_port } = config; const { agent_port } = config; const node = os.hostname(); let { token } = config; const noop = () => {}; let vip = ''; let vip_slave = ''; let ip_add_command = ''; let ip_delete_command = ''; let vip_ping_time = ''; let cpu_percent = 0; let os_type = ''; let disk_percentage = 0; let total_running_containers = 0; let container_uptime = ''; let network_rx = 0; let network_tx = 0; let running_containers = ''; let container_mem_stats = ''; let container_cpu_stats = ''; let cpu_cores = 0; let memory_buffers = 0; let memory_swap = 0; let memory_total = 0; let memory_used = 0; let memory_percentage = 0; let images = ''; let network_device = ''; sysinfo.networkInterfaces(data => { network_device = data[1].iface; }); function monitoring() { sysinfo.networkStats(network_device, data => { network_tx = Math.round(data[0].tx_sec / 1000); network_rx = Math.round(data[0].rx_sec / 1000); }); sysinfo.mem(data => { memory_total = data.total; memory_buffers = data.buffcache; memory_used = data.used; memory_swap = data.swapused; const this_os = os.platform(); if (this_os.indexOf('linux') > -1) { memory_percentage = Math.round((memory_used - memory_buffers) / memory_total * 100); } else { memory_percentage = Math.round((memory_swap + memory_buffers) / memory_total * 100); } }); exec('docker container ps -q', (err, stdout) => { if (err) { console.error(err); } total_running_containers = stdout.split('\n').length - 1; }); exec('docker ps --format "{{.Names}}"', (err, stdout) => { if (err) { console.error(err); } running_containers = stdout.split('\n'); }); exec('docker stats --no-stream --format "{{.CPUPerc}}"', (err, stdout) => { if (err) { console.error(err); } container_cpu_stats = stdout.replace(/%/gi, '').split('\n'); }); exec('docker stats --no-stream --format "{{.MemPerc}}"', (err, stdout) => { if (err) { console.error(err); } container_mem_stats = stdout.replace(/%/gi, '').split('\n'); }); exec('docker ps --format "{{.Status}}"', (err, stdout) => { if (err) { console.error(err); } container_uptime = stdout.split('\n'); }); exec('docker images --format "table {{.Repository}}\t{{.CreatedSince}}\t{{.Size}}"', (err, stdout) => { if (err) { console.error(err); } images = stdout.split('\n'); for (const i in images) { if ((images[i].indexOf('REPOSITORY') > -1) || images[i].indexOf('') > -1) { images[i] = ''; } } images = images.filter((e, pos) => { return e.length > 0 && images.indexOf(e) === pos; }); images = images.sort(); }); setTimeout(() => { getos((e, os) => { os_type = (e) ? '' : os.dist || os.os; }); diskspace.check('/', (err, result) => { if (!err) { disk_percentage = Math.round(result.used / result.total * 100); } }); require('cpu-stats')(1000, (error, result) => { cpu_cores = 0; let usage = 0; result.forEach(e => { usage += e.cpu; cpu_cores++; }); cpu_percent = usage; }); monitoring(); }, 3000); } function send_ping() { setTimeout(() => { superagent .post(`${scheme}${vip_slave}:${agent_port}/pong`) .send({ token: token }) .set('accept', 'json') .end((err, res) => { let found_vip = false; if (err) { const cmd = ip_add_command; exec(cmd, (error, stdout, stderr) => {}, err => { if (err) { console.error('error:', err); } // Console.log('output', output); }); } else { const interfaces = require('os').networkInterfaces(); Object.keys(interfaces).forEach(devName => { const iface = interfaces[devName]; iface.forEach(alias => { if (alias.address === vip) { found_vip = true; } }); }); const json_object = JSON.parse(res.text); if (json_object.vip_detected === 'false' && found_vip === false) { console.log('\nVIP not detected on either machine. Bringing up the VIP on this host.'); const cmd = ip_add_command; exec(cmd, (error, stdout, stderr) => {}, err => { if (err) { console.error('error:', err); } // Console.log('output', output); }); } if ((json_object.vip_detected === 'true' && found_vip === true)) { console.log('\nVIP detected on boths hosts! Stopping the VIP on this host.'); const cmd = ip_delete_command; exec(cmd, (error, stdout, stderr) => {}, err => { if (err) { console.error('error:', err); } // Console.log('output', output); }); } } }); send_ping(); }, vip_ping_time); } app.get('/rsyslog', (req, res) => { const check_token = req.query.token; if ((check_token !== token) || (!check_token)) { res.end('\nError: Invalid Credentials'); } else { res.sendFile(config.rsyslog_logfile); } }); app.get('/node-status', (req, res) => { const check_token = req.query.token; if ((check_token !== token) || (!check_token)) { res.end('\nError: Invalid Credentials'); } else { const json_output = JSON.stringify({ cpu_percent, hostname: node, os_type: (os_type === '') ? os.platform() : os_type, disk_percentage, total_running_containers, running_containers, container_mem_stats, container_cpu_stats, container_uptime, network_rx, network_tx, images, cpu_cores, memory_percentage }); res.send(json_output); } }); app.post('/pong', (req, res) => { const check_token = req.body.token; if (check_token !== token) { return res.status(500).send('Something broke!'); } let vip_status = 'false'; const interfaces = require('os').networkInterfaces(); Object.keys(interfaces).forEach(devName => { const iface = interfaces[devName]; iface.forEach(alias => { if (alias.address === vip) { vip_status = 'true'; } }); }); const body = { vip_detected: vip_status }; res.send(body); }); function unzipFile(file) { fs.createReadStream(file).pipe(new unzip.Extract({ path: config.docker })); } function reloadConfig() { try { config = process.env.PICLUSTER_CONFIG ? JSON.parse(fs.readFileSync(process.env.PICLUSTER_CONFIG, 'utf8')) : JSON.parse(fs.readFileSync('../config.json', 'utf8')); token = config.token; server = config.web_connect; server_port = config.server_port; } catch (error) { console.log(process.env.PICLUSTER_CONFIG); console.log(error); } } app.post('/receive-file', upload.single('file'), (req, res) => { const check_token = req.body.token; const get_config_file = req.body.config_file; let data = ''; if ((check_token !== token) || (!check_token)) { res.end('\nError: Invalid Credentials'); } else { let newPath = req.body.name; let config_file = ''; if (get_config_file) { if (process.env.PICLUSTER_CONFIG) { config_file = process.env.PICLUSTER_CONFIG; } else { config_file = '../config.json'; } newPath = config_file; data = req.body.data; } else if (req.body.name.indexOf('hosts') > -1) { data = req.body.data; newPath = req.body.name; } else { data = req.body.data.formData.file.data; newPath = config.docker + '/' + req.body.name; } setTimeout(() => { if (req.body.name.indexOf('hosts') > -1) { fs.writeFile(newPath, data, err => { if (err) { console.log('Hosts Write Error:' + err); } }); } else { let buff = new Buffer.from(data, 'binary'); fs.writeFile(newPath, buff, err => { if (!err) { if (newPath.indexOf('.zip') > -1) { unzipFile(newPath); fs.unlink(newPath, error => { if (error) { console.log(error); } }); } } }); } }, 5000); res.end('Done'); } }); app.post('/run', (req, res) => { const output = { output: "", node }; const check_token = req.body.token; if (check_token !== token) { return res.status(401).json({ output: 'Not Authorized to connect to this agent!' }); } exec(req.body.command, (error, stdout, stderr) => { if (error) { output.output = stderr; } else { output.output = stdout; if (config.autostart_containers) { if (req.body.command.indexOf('docker container run') > -1) { systemd(req.body.command); } else if (req.body.command.indexOf('docker container rm') > -1) { systemd_remove(req.body.command); } } res.json(output); } }, err => { if (err) { console.error('error:', err); } Console.log('output', output); }); }); function systemd(data) { const systemd = ["[Unit]", "Description=Container", "After=podman.service", "[Service]", "Type=oneshot", "RemainAfterExit=yes", "Environment=\"NAME=", "Environment=\"ARGUMENTS=", "ExecStartPre=/bin/sh -c \"/usr/bin/podman rm -f ${NAME}; exit 0;\"", "ExecStartPre=/bin/sh -c \"/usr/bin/podman build -t ${NAME} /docker/${NAME}; exit 0;\"", "ExecStart=/bin/sh -c \"podman run -d --name ${NAME} ${ARGUMENTS} localhost/${NAME}; exit 0;\"", "ExecStart=/bin/sh -c \"systemctl restart firewalld.service; exit 0;\"", "ExecStart=/bin/sh -c \"podman network reload -a; exit 0;\"", "ExecStop=/usr/bin/podman rm -f ${NAME}\"", "[Install]", "WantedBy=multi-user.target"]; var container_name = data.split(' '); var name = container_name[container_name.length - 1]; for (const unit_file of systemd) { if (unit_file.indexOf('Unit') > -1) { console.log(unit_file); fs.writeFile('/etc/systemd/system/picluster-' + name + '.service', unit_file + '\n', err => { if (err) { console.log(err); } }); } else if (unit_file.indexOf('NAME=') > -1) { let analyze_unit_file = 'Environment=\"NAME=' + name + '"'; fs.appendFile('/etc/systemd/system/picluster-' + name + '.service', analyze_unit_file + '\n', err => { if (err) { console.log(err); } }); } else if (unit_file.indexOf('ARGUMENTS') > -1) { final_arguments = data.split(';'); final_line = final_arguments[1].split(name); end_line = 'Environment=\"ARGUMENTS="' + final_line[1] + '"'; fs.appendFile('/etc/systemd/system/picluster-' + name + '.service', end_line + '\n', err => { if (err) { console.log(err); } }); } else { fs.appendFile('/etc/systemd/system/picluster-' + name + '.service', unit_file + '\n', err => { if (err) { console.log(err); } }); console.log(unit_file); } } exec('systemctl enable picluster-' + name + '.service', (error, stdout, stderr) => { if (error) { console.log(error); } if (stdout) { console.log(stdout); } }); } function systemd_remove(data) { var container_name = data.split(' '); var name = container_name[container_name.length - 1]; exec('systemctl disable picluster-' + name + '.service', (error, stdout, stderr) => { if (error) { console.log(error); } else { console.log('\nRemoving picluster-' + name + '.service'); fs.unlink('/etc/systemd/system/picluster-' + name + '.service', error => { if (error) { console.log(error); } }); } }); } if (config.ssl && config.ssl_cert && config.ssl_key) { console.log('SSL Agent API enabled'); const ssl_options = { cert: fs.readFileSync(config.ssl_cert), key: fs.readFileSync(config.ssl_key) }; const agent = https.createServer(ssl_options, app); agent.listen(agent_port, () => { console.log('Listening on port %d', agent_port); }); } else { console.log('Non-SSL Agent API enabled'); const agent = http.createServer(app); agent.listen(agent_port, () => { console.log('Listening on port %d', agent_port); }); } function bootstrapNode() { setTimeout(() => { console.log('Attempting to bootstrap node to server......'); try { superagent .post(`${scheme}${server}:${server_port}/bootstrap`) .send({ token: token, host: node }) .set('accept', 'json') .end((err, res) => { if (err) { console.log('Bootstrap failed due to an error or another bootstrap operation already in progress.\n'); console.log(err); bootstrapNode(); } else { try { const status = JSON.parse(res.text); if (status.output > 0) { console.log('Bootstrap successful.'); additional_services(); } else { console.log('\nAnother bootstrap is in progress. Will try again soon.....'); bootstrapNode(); } } catch (error2) { console.log('\n' + error2 + '\n' + JSON.stringify(res.text)); bootstrapNode(); } } }); } catch (error) { console.log('\nAn error has occurred with bootstrapping. Trying again.......' + '\n' + error); bootstrapNode(); } }, 3000); } bootstrapNode(); function additional_services() { monitoring(); if (config.vip_ip && config.vip) { vip = config.vip_ip; Object.keys(config.vip).forEach(i => { const _node = config.vip[i].node; Object.keys(config.vip[i]).forEach(key => { if (!config.vip[i].hasOwnProperty(key)) { return; } const interfaces = require('os').networkInterfaces(); Object.keys(interfaces).forEach(devName => { const iface = interfaces[devName]; iface.forEach(alias => { if (alias.address !== _node) { return; } vip_slave = config.vip[i].slave; const { vip_eth_device } = config.vip[i]; ip_add_command = 'ip addr add ' + config.vip_ip + '/32 dev ' + vip_eth_device; ip_delete_command = 'ip addr del ' + config.vip_ip + '/32 dev ' + vip_eth_device; vip_ping_time = config.vip[i].vip_ping_time; exec(ip_delete_command, (error, stdout, stderr) => { send_ping(); }, err => { if (err) { console.error('error:', err); } // Console.log('output', output); }); }); }); }); }); } }