.github/000077500000000000000000000000001516074440100124205ustar00rootroot00000000000000.github/workflows/000077500000000000000000000000001516074440100144555ustar00rootroot00000000000000.github/workflows/build.yml000066400000000000000000000020631516074440100163000ustar00rootroot00000000000000# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions name: build on: schedule: - cron: '0 0 1 * *' push: branches: [ master ] pull_request: branches: [ master ] jobs: build: runs-on: ubuntu-latest strategy: matrix: node-version: ['lts/*'] # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ steps: - name: Install Ubuntu dependencies run: sudo apt-get install libdbus-1-dev libglib2.0-dev - uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v2 with: node-version: ${{ matrix.node-version }} - run: npm i -g grunt-cli - run: npm install - run: grunt .gitignore000066400000000000000000000004321516074440100130470ustar00rootroot00000000000000/node_modules # need to allow ts for awesome=typscript-loader /dev.json /settings.json /build .idea/workspace.xml .idea/tasks.xml .idea/profiles_settings.xml .idea/inspectionProfiles/Project_Default.xml .idea/inspectionProfiles/profiles_settings.xml node_modules/.yarn-integrity .npmignore000066400000000000000000000002701516074440100130560ustar00rootroot00000000000000/.idea /artifacts /build /test /node_modules /*.iml /*.ipr /*.iws /.travis.yml /.scrutinizer.yml /Gruntfile.js /*.lock *.log /corifeus-boot.json /secure settings.json /.github /.vscodeGruntfile.js000066400000000000000000000004501516074440100133540ustar00rootroot00000000000000const builder = require('corifeus-builder'); module.exports = (grunt) => { const loader = new builder.loader(grunt); loader.js({ replacer: { type: 'p3x', npmio: true } }); grunt.registerTask('default', builder.config.task.build.js); } LICENSE000066400000000000000000000023351516074440100120700ustar00rootroot00000000000000 @license p3x-systemd-manager v2024.4.104 ⌚ SystemD Manager, watchdog, notifier and service https://corifeus.com/systemd-manager Copyright (c) 2024 Patrik Laszlo / P3X / Corifeus and contributors. MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. README.md000066400000000000000000000176371516074440100123550ustar00rootroot00000000000000[//]: #@corifeus-header [![NPM](https://nodei.co/npm-dl/p3x-systemd-manager.png?downloads=true&downloadRank=true)](https://www.npmjs.com/package/p3x-systemd-manager/) [![Donate for Corifeus / P3X](https://img.shields.io/badge/Donate-Corifeus-003087.svg)](https://paypal.me/patrikx3) [![Contact Corifeus / P3X](https://img.shields.io/badge/Contact-P3X-ff9900.svg)](https://www.patrikx3.com/en/front/contact) [![Corifeus @ Facebook](https://img.shields.io/badge/Facebook-Corifeus-3b5998.svg)](https://www.facebook.com/corifeus.software) [![Uptime Robot ratio (30 days)](https://img.shields.io/uptimerobot/ratio/m780749701-41bcade28c1ea8154eda7cca.svg)](https://stats.uptimerobot.com/9ggnzcWrw) # ⌚ SystemD Manager, watchdog, notifier and service v2024.4.104 **Bugs are evident™ - MATRIX️** ### NodeJS LTS is supported ### Built on NodeJs version ```txt v20.11.0 ``` # Description [//]: #@corifeus-header:end ## Use case Get a notification via e-mail when a SystemD service becomes failed. If you enable boot, every times to startup the notifier, it sends failed and not-found (trigger) services so you can remove them from SystemD and get cleaned. Also it is easy to configure additional triggers like running, stopped or all the whole SystemD changes via LoadState / other properties and trigger a result / status and notify or add in different notifier like Twitter / Facebook etc. ### Evolve It is easy to evolve the functions. I just created for my server to get failed services via notify e-mail, but if you need additional functions, please fork and pull. It is easy to add in anything or change services etc... All DBus based and async/await wrappers. ## Detailed It is a Linux/Unix/BSD (tested only in Debian/Testing repo) based SystemD manager. Notifies via e-mail with NodeMailer, it polls via an interval as a watchdog. It also has a wrapper for DBus to manage services and via events as well if you do not like polling. I guess watchdog will be replaced 100%. ## SystemD DBus Manager References: [DBus](https://www.freedesktop.org/wiki/Software/systemd/dbus/), [Node Dbus](https://github.com/Shouqun/node-dbus) ### Prerequisites ```bash #you probably might need a c++11 if it is old, #for additional requirements check out .travis.yml sudo apt-get install libdbus-1-dev libglib2.0-dev ``` ### Using from code Please do not use ```yarn```, because it asks for ```sudo``` and prompt (unless you are ```root```). ```bash npm install p3x-systemd-manager --save ``` #### SystemD DBus Notifier ```js #!/usr/bin/env node const systemd = require('p3x-systemd-manager'); const settings = systemd.lib.getSettings(); if (settings === false) { return; } systemd.boot(settings); systemd.notifier(settings); ``` #### SystemD Watchdog Notify This notifies changes in the SystemD via e-mail. Right now it polls, so that it gets all changes. It task about 30-50 milliseconds per run on my 3.3 GHz Pentium 2 cores, not too much. All automatic, requires email and a few tweaks as you want. ```js const Watchdog = require('p3x-systemd-manager').watchdog; const settings = require('./settings.json'); const watchdog = Watchdog(settings); watchdog.run(); ``` ##### Looks like this ```text Feb 22 11:41:31 server systemd[1]: Started p3x-watchdog. Feb 22 11:41:32 server watchdog[2196]: started Feb 22 11:41:32 server watchdog[2196]: watchdog type(s): service Feb 22 11:41:32 server watchdog[2196]: ping: 2 hours Feb 22 11:41:32 server watchdog[2196]: interval: 10 seconds Feb 22 11:41:32 server watchdog[2196]: command: systemctl --plain --no-pager --no-legend --type=service Feb 22 11:41:32 server watchdog[2196]: ping - 51 items - every 2 hours Feb 22 11:41:32 server watchdog[2196]: Mail is working. ``` ### Using terminal ```bash git clone https://github.com/patrikx3/systemd-manager.git cd systemd-manager sudo apt-get install libdbus-1-dev libglib2.0-dev npm install ./notifier settings.json #it is used to be a watchdog, polling ./watchdog settings.json ``` ### Settings Checkout [```artifacts/setttings.json```](artifacts/settings.json) ```filter.type```: Array, can be empty, actual ```man systemctl``` type. Service is the safest. Not always working when you fine tune, some are weird. ```nodemailer.config```: Exact nodemailer config, any of that. ```interval, ping```: Uses npm ```milliseconds``` framework for turn into actual milliseconds from a string. This is for ```Watchdog```, not needed anymore. ```sudo```: for the watchdog either you need to use root, or via sudo (```true|false```). For SystemD needs root, but you can use another user, and it will use sudo then when polling. For SystemD DBus notifier you need to use root anyway. I think it cannot do anything else so it's safe to take over the system, also it's internal, no web interface for now. ```json { "debug": false, "filter": { "type": [ "service" ], "exclude": [], "include": [], "trigger": { "SubState": [ "failed" ] } }, "boot": { "enabled": true, "trigger": { "SubState": [ "failed" ] } }, "moment": "LLL", "prefix": "P3X-SYSTEMD-NOTIFIER", "dbus": { "address": "unix:path=/run/dbus/system_bus_socket", "display": ":0" }, "interval": "watchdog only", "interval": "10 seconds", "ping": "watchdog only", "ping": "2 hours", "sudo": "watchdog only", "sudo": false, "email": { "to": "system@localhost", "from": "system@localhost" }, "nodemailer": { "singleton": true, "config": { "host": "mail.localhost", "port": 465, "secure": true, "auth": { "user": "system@localhost", "pass": "unknown" } } }, "ignoreErrors": [ "The maximum number of pending replies per connection has been reached", "No introspectable" ] } ``` ### Weird errors There are 2 errors that I could not catch or why it happens, so I created a hack: ```js const settings = { "ignoreErrors": [ "The maximum number of pending replies per connection has been reached", "No introspectable" ] } process.on("unhandledRejection", function (err) { if (err && err.message && settings.ignoreErrors.includes(err.message)) { console.warn('ignoring error', err) } else { // user your own logic console.error(err) process.exit(-1) } }); ``` The two error messages: ```txt "The maximum number of pending replies per connection has been reached" "No introspectable" ``` [//]: #@corifeus-footer --- 🙏 This is an open-source project. Star this repository, if you like it, or even donate to maintain the servers and the development. Thank you so much! Possible, this server, rarely, is down, please, hang on for 15-30 minutes and the server will be back up. All my domains ([patrikx3.com](https://patrikx3.com) and [corifeus.com](https://corifeus.com)) could have minor errors, since I am developing in my free time. However, it is usually stable. **Note about versioning:** Versions are cut in Major.Minor.Patch schema. Major is always the current year. Minor is either 4 (January - June) or 10 (July - December). Patch is incremental by every build. If there is a breaking change, it should be noted in the readme. --- [**P3X-SYSTEMD-MANAGER**](https://corifeus.com/systemd-manager) Build v2024.4.104 [![Donate for Corifeus / P3X](https://img.shields.io/badge/Donate-Corifeus-003087.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=QZVM4V6HVZJW6) [![Contact Corifeus / P3X](https://img.shields.io/badge/Contact-P3X-ff9900.svg)](https://www.patrikx3.com/en/front/contact) [![Like Corifeus @ Facebook](https://img.shields.io/badge/LIKE-Corifeus-3b5998.svg)](https://www.facebook.com/corifeus.software) [//]: #@corifeus-footer:end artifacts/000077500000000000000000000000001516074440100130405ustar00rootroot00000000000000artifacts/settings.json000066400000000000000000000023121516074440100155710ustar00rootroot00000000000000{ "debug": false, "filter": { "type": [ "service" ], "exclude": [], "include": [], "trigger": { "SubState": [ "failed" ] } }, "boot": { "enabled": true, "trigger": { "SubState": [ "failed" ] } }, "moment": "LLL", "prefix": "P3X-SYSTEMD-NOTIFIER", "dbus": { "address": "unix:path=/run/dbus/system_bus_socket", "display": ":0" }, "interval": "watchdog only", "interval": "10 seconds", "ping": "watchdog only", "ping": "2 hours", "sudo": "watchdog only", "sudo": false, "email": { "to": "system@localhost", "from": "system@localhost" }, "nodemailer": { "singleton": true, "config": { "host": "mail.localhost", "port": 465, "secure": true, "auth": { "user": "system@localhost", "pass": "unknown" } } }, "ignoreErrors": [ "The maximum number of pending replies per connection has been reached", "No introspectable" ] } index.js000066400000000000000000000000421516074440100125210ustar00rootroot00000000000000module.exports = require('./src');notifier000077500000000000000000000007541516074440100126330ustar00rootroot00000000000000#!/usr/bin/env node const os = require('os'); /* const process = require('process'); const cores = os.cpus().length < 4 ? 4 : os.cpus().length; process.env.UV_THREADPOOL_SIZE = cores; console.debug(`P3X sets UV_THREADPOOL_SIZE to ${cores} thread pool`) */ const lib = require('./src/lib'); const settings = lib.getSettings(); if (settings === false) { return; } const boot = require('./src/dbus/boot'); boot(settings); const notifier = require('./src/notifier'); notifier(settings); package.json000066400000000000000000000031651516074440100133530ustar00rootroot00000000000000{ "name": "p3x-systemd-manager", "corifeus": { "prefix": "p3x-", "publish": true, "type": "p3x", "code": "Breaking", "nodejs": "v20.11.0", "opencollective": false, "reponame": "systemd-manager", "build": true }, "version": "2024.4.104", "description": "⌚ SystemD Manager, watchdog, notifier and service", "main": "index.js", "saved-dependencies": { "dbus": "git+https://github.com/p3x-robot/node-dbus.git", "fix": "https://github.com/p3x-robot/node-dbus", "fix-repo": "git+https://github.com/p3x-robot/node-dbus.git" }, "dependencies": { "corifeus-utils": "^2024.4.103", "dbus": "^1.0.7", "lodash": "^4.17.21", "millisecond": "^0.1.2", "moment": "^2.30.1", "nan": "^2.18.0", "nodemailer": "^6.9.8" }, "devDependencies": { "corifeus-builder": "^2024.4.103" }, "scripts": { "test": "grunt", "preinstall-save": "src/package/preinstall.sh" }, "repository": { "type": "git", "url": "git+https://github.com/patrikx3/systemd-manager.git" }, "keywords": [ "systemd", "systemd-service", "watchdog", "notify", "linux", "manager", "service", "dbus", "notifier" ], "author": "Patrik Laszlo ", "license": "MIT", "bugs": { "url": "https://github.com/patrikx3/systemd-manager/issues" }, "homepage": "https://corifeus.com/systemd-manager", "engines": { "node": ">=12.13.0" } }src/000077500000000000000000000000001516074440100116475ustar00rootroot00000000000000src/dbus/000077500000000000000000000000001516074440100126045ustar00rootroot00000000000000src/dbus/Interface.js000066400000000000000000000022201516074440100150360ustar00rootroot00000000000000const systemd = require('./systemd'); class Interface { constructor(manager, settings, options = systemd.defaults.options) { this.settings = settings; this.options = options; this.manager = manager; this.summaryProps = {}; } on(event, cb) { this.manager.on(event, cb); } get props() { return new Promise((resolve, reject) => { this.manager.getProperties((err, values) => { if (err) { reject(err) } resolve(values); }); }) } prop(prop) { return new Promise((resolve, reject) => { this.manager.getProperty(prop, (err, value) => { if (err) { reject(err) } resolve(value); }); }) } get summary() { return this.props.then((values) => { const summary = {}; Object.keys(this.summaryProps).forEach((key) => { summary[key] = values[key]; }) return summary; }) } } module.exports = Interface;src/dbus/boot.js000066400000000000000000000037661516074440100141210ustar00rootroot00000000000000const boot = async (settings) => { if (settings.boot !== undefined && settings.boot.enabled !== true) { console.log('Boot is disabled'); return; } const Mail = require('../mail') const lib = require('../lib'); const dbus = require('./index'); const filter = lib.filter(settings); const manager = await dbus.manager.factory(settings); const mail = Mail(settings); /* const eventHelper = (item) => { console.log(`Subscribe Event: ${item}`); manager.on(item, function() { console.log(item); console.log(arguments); }) } Object.keys(manager.event).forEach((ev) => eventHelper(ev)) */ try { console.log(await manager.summary); const units = await manager.listUnits; /* manager.on(manager.event.JobNew, async function(pid, jobNode, unitId) { console.log(arguments); try { const job = await dbus.job(jobNode); console.log(await job.props); } catch (e) { mail.send(e); } }) */ console.log('Boot is enabled'); const unitIds = Object.keys(units); const boot = {}; for (let unitId of unitIds) { if (filter.isValid(unitId)) { const unit = await manager.getUnit(unitId); const props = await unit.props; for (let unitProp of Object.keys(settings.boot.trigger)) { if (settings.boot.trigger[unitProp].includes(props[unitProp])) { boot[unitProp] = boot[unitProp] || {}; boot[unitProp][props[unitProp]] = boot[unitProp][props[unitProp]] || []; boot[unitProp][props[unitProp]].push(unitId); } } } } if (Object.keys(boot).length > 0) { mail.send('boot', boot); console.log(boot); } } catch (e) { mail.send(e); } } module.exports = boot src/dbus/index.js000066400000000000000000000002501516074440100142460ustar00rootroot00000000000000module.exports = { systemd: require('./systemd'), manager: require('./interfaces/manager'), job: require('./interfaces/job'), boot: require('./boot') };src/dbus/interfaces/000077500000000000000000000000001516074440100147275ustar00rootroot00000000000000src/dbus/interfaces/index.js000066400000000000000000000002301516074440100163670ustar00rootroot00000000000000module.exports = { job: require('./job'), manager: require('./manager'), properties: require('./properties'), unit: require('./unit'), }src/dbus/interfaces/job.js000066400000000000000000000006441516074440100160430ustar00rootroot00000000000000const systemd = require('./../systemd'); const Interface = require('./../Interface'); const interfaceName = 'org.freedesktop.systemd1.Job'; module.exports = { factory: async (unit, settings, options = systemd.defaults.options) => { const manager = await systemd.getInterface(settings, unit, interfaceName); return new Interface(manager, settings, options); }, interfaceName: interfaceName }src/dbus/interfaces/manager.js000066400000000000000000000034361516074440100167050ustar00rootroot00000000000000const systemd = require('./../systemd'); const Interface = require('./../Interface'); const unit = require('./unit'); class Manager extends Interface { constructor(manager, settings, options = systemd.defaults.options) { super(manager, settings, options); this.summaryProps = { NNames: 'NNames', NInstalledJobs: 'NInstalledJobs', NFailedJobs: 'NFailedJobs', Progress: 'Progress', ShowStatus: 'ShowStatus' }; } get listUnits() { return new Promise((resolve, reject) => { this.manager.ListUnits(this.options, (error, result) => { if (error) { return reject(error); } const units = result[0]; resolve(units, this.settings, this.options); }); }) } getUnit(unitName, options = this.options) { return new Promise((resolve, reject) => { this.manager.GetUnit(unitName, options, (error, unitNode) => { if (error) { return reject(error); } resolve(unit.factory(unitNode)); }); }) } get event() { return { UnitNew: 'UnitNew', UnitRemoved: 'UnitRemoved', JobNew: 'JobNew', JobRemoved: 'JobRemoved', StartupFinished: 'StartupFinished', UnitFilesChanged: 'UnitFilesChanged', Reloading: 'Reloading' } } } module.exports = { factory: async (settings, options = systemd.defaults.options) => { const manager = await systemd.getInterface(settings); return new Manager(manager, settings, options); }, interfaceName: systemd.defaults._interface._interface }src/dbus/interfaces/properties.js000066400000000000000000000011341516074440100174600ustar00rootroot00000000000000const systemd = require('./../systemd'); const Interface = require('./../Interface'); class Properties extends Interface { constructor(node, manager, options = systemd.defaults.options) { super(manager, options); this.node = node; } } const interfaceName = 'org.freedesktop.DBus.Properties'; module.exports = { factory: async (node, settings, options = systemd.defaults.options) => { const manager = await systemd.getInterface(settings, node, interfaceName); return new Properties(node, manager, settings, options); }, interfaceName: interfaceName }src/dbus/interfaces/unit.js000066400000000000000000000022401516074440100162420ustar00rootroot00000000000000const systemd = require('./../systemd'); const Interface = require('./../Interface'); class Unit extends Interface { constructor(node, manager, options = systemd.defaults.options) { super(manager, options); this.node = node; this.summaryProps = { Id: 'Id', Description: 'Description', LoadState: 'LoadState', ActiveState: 'ActiveState', SubState: 'SubState', OnFailure: 'OnFailure', }; } get notFound() { return this.prop(this.summaryProp.LoadState) .then((value) => { return value === 'not-found' }) } get failed() { return this.prop(this.summaryProp.ActiveState) .then((value) => { return value === 'failed' }) } } const interfaceName = 'org.freedesktop.systemd1.Unit'; module.exports = { factory: async (node, settings, options = systemd.defaults.options) => { const manager = await systemd.getInterface(settings, node, interfaceName); return new Unit(node, manager, settings, options); }, interfaceName: interfaceName }src/dbus/systemd.js000066400000000000000000000026151516074440100146360ustar00rootroot00000000000000const process = require('process'); const DBus = require('dbus'); //gdbus introspect --system --dest org.freedesktop.systemd1 --object-path /org/freedesktop/systemd1 //https://www.freedesktop.org/wiki/Software/systemd/dbus/ //https://github.com/Shouqun/node-dbus const defaults = { _interface: { system: 'org.freedesktop.systemd1', node: '/org/freedesktop/systemd1', _interface: 'org.freedesktop.systemd1.Manager' }, options: { timeout: 1000 } } const getInterface = async ( settings = { 'dbus': { 'address': 'unix:path=/run/dbus/system_bus_socket', 'display': ':0' } }, node = defaults._interface.node, _interface = defaults._interface._interface, system = defaults._interface.system) => { process.env.DISPLAY = settings.dbus.display; // if (process.env.DBUS_SESSION_BUS_ADDRESS === undefined) { process.env.DBUS_SESSION_BUS_ADDRESS = settings.dbus.address; // } if (global.p3xBus === undefined) { global.p3xBus = DBus.getBus('system') } return new Promise((resolve, reject) => { global.p3xBus.getInterface(system, node, _interface, (error, manager) => { if (error) { return reject(error); } resolve(manager); }); }) } module.exports = { defaults: defaults, getInterface: getInterface, } src/index.js000066400000000000000000000002251516074440100133130ustar00rootroot00000000000000module.exports = { watchdog: require('./watchdog'), dbus: require('./dbus'), notifier: require('./notifier'), lib: require('./lib') }src/lib.js000066400000000000000000000044751516074440100127650ustar00rootroot00000000000000require('corifeus-utils'); const process = require('process'); const fs = require('fs'); const path = require('path'); const dirname = path.dirname(process.argv[1]); const basename = path.basename(process.argv[1]); const getSettings = (cli = basename, settingsFile = 'settings.json') => { let settings; const defaultFile = `${dirname}/${settingsFile}`; if (fs.existsSync(defaultFile)) { settings = require(defaultFile); } else if (process.argv.length < 3) { console.log(` Please use an argument for the settings. For example: ${cli} ${settingsFile} `); return false; } else { settings = require(`${dirname}/{process.argv[2]}`); } return settings; } const simpleFilter = (list, result) => { if (typeof list === 'string') { list = [list]; } let listRegexp = list.map((exclude) => { return new RegExp(exclude, 'i'); }) return isList = (filter) => { if (listRegexp.length === 0) { return result; } for (let oneRegexp of listRegexp) { if (oneRegexp.test(filter)) { return !result; } } return result; } } const filter = (settings) => { if (settings.filter === undefined) { settings.filter = { type: [], exclude: [], include: [] } } let types = settings.filter.type || []; if (typeof types === 'string') { types = [types]; } const isType = (unitId) => { if (types.length === 0) { return true; } for (let type of types) { if (unitId.endsWith(`.${type}`)) { return true; } } return false; } const isExcluded = simpleFilter(settings.filter.exclude || [], false); const isIncluded = simpleFilter(settings.filter.included || [], true); return { isType: isType, isExcluded: isExcluded, isIncluded: isIncluded, isValid: (unitId) => { return isType(unitId) && isIncluded(unitId) && !isExcluded(unitId); } } } /** * Simple clone program * @param obj */ const clone = (obj) => { return JSON.parse(JSON.stringify(obj)); } module.exports.clone = clone; module.exports.filter = filter; module.exports.getSettings = getSettings; src/mail.js000066400000000000000000000050011516074440100131230ustar00rootroot00000000000000const nodemailer = require('nodemailer'); const os = require('os'); const moment = require('moment'); let singletonInstance; module.exports = (settings) => { if (!settings.hasOwnProperty('ignoreErrors')) { settings.ignoreErrors = [] } if (settings.nodemailer.singleton && singletonInstance !== undefined) { return singletonInstance; } const send = async (from, to, subject, body) => { if (body === undefined) { body = subject; subject = ''; } const momentify = (js) => { return Object.assign({ timestamp: moment().format(settings.moment) }, js) } console.log('settings.ignoreErrors', settings.ignoreErrors) if (body instanceof Error) { console.error(body); if (settings.ignoreErrors.includes(body.message)) { console.warn('ignoring known messsage', body.message) console.info('not sending e-mail') return } body = JSON.parse(JSON.stringify(body, ["message", "arguments", "type", "name", 'stack'])); } if (typeof body !== 'string') { body = momentify(body); body = JSON.stringify(body, null, 4); } else { body = ` ${moment().format(settings.moment)} ${body} `; } const message = { from: from, to: to, subject: subject, text: body }; console.log(`send new mail subject: ${subject}`); console.log('message', message) try { let transporter = nodemailer.createTransport(settings.nodemailer.config); const info = await transporter.sendMail(message); console.log(info.response); transporter.close(); } catch (e) { console.log(`send error email`, message); console.error(e, message); } } const factory = { send: (subject, body) => { if (body === undefined) { body = subject; if (body instanceof Error) { subject = 'error' } else { subject = 'trigger' } } subject = `${settings.prefix}: ${os.hostname()} - ${subject}`; send(settings.email.from, settings.email.to, subject, body); } }; if (settings.nodemailer.singleton) { singletonInstance = factory; } return factory; } src/notifier.js000066400000000000000000000111331516074440100140230ustar00rootroot00000000000000const Mail = require('./mail') const dbus = require('./dbus'); const interfaces = require('./dbus/interfaces'); const lib = require('./lib'); module.exports = async (settings) => { process.on("unhandledRejection", (err, promise) => { if (!settings.hasOwnProperty('ignoreErrors')) { settings.ignoreErrors = [] } if (settings.ignoreErrors.includes(err.message)) { console.warn('ignoring known messsage', err) console.info('no crash') return } console.error(new Date().toLocaleString(), 'unhandledRejection', err, promise); process.exit(1); }); const filter = lib.filter(settings); const mail = Mail(settings); let managerInterface = await dbus.manager.factory(settings); const debug = () => { const eventHelper = (item) => { console.log(`Subscribe Event: ${item}`); managerInterface.on(item, function () { console.log(item); console.log(arguments); }) } Object.keys(managerInterface.event).forEach((ev) => eventHelper(ev)) } const connect = async () => { const propertyInterfaces = []; let unitDictionary = await managerInterface.listUnits; Object.keys(unitDictionary).forEach(async (unitId) => { if (filter.isValid(unitId)) { const unit = await managerInterface.getUnit(unitId); const properties = await unit.props; const propertyInterface = await interfaces.properties.factory(unit.node, settings); /* let lastSubStates = {}; Object.keys(settings.filter.status).forEach((state) => { lastSubStates[state] = properties[state]; }); */ propertyInterfaces.push(propertyInterface); propertyInterface.on('PropertiesChanged', async function (changedInterface, props, names) { if (changedInterface === interfaces.unit.interfaceName) { let trigger = false; Object.keys(settings.filter.trigger).forEach((state) => { if (settings.filter.trigger[state].includes(props[state])) { trigger = true; } /* if (props[state] !== lastSubStates[state]) { } */ }) if (!trigger) { return; } /* Object.keys(settings.filter.trigger).forEach((state) => { lastSubStates[state] = props[state]; }) */ mail.send(unitId, { summary: await unit.summary, detailed: await unit.props }); } }) } ; }) return () => { propertyInterfaces.forEach((propertyInterface) => { propertyInterface.manager.removeAllListeners('PropertiesChanged'); }); } } if (settings.debug !== undefined && settings.debug === true) { debug(); } let kill; let reloadDebounce; const reload = async function (reloading) { //console.log(`Reloading: ${reloading}`); if (reloading) { return; } console.log('Debounced Reloading services'); clearTimeout(reloadDebounce); reloadDebounce = setTimeout(async () => { try { console.log('Reloading services completed'); (await kill)(); kill = await connect(); } catch (e) { mail.send('reload error', e); } }, 5000) } managerInterface.on(managerInterface.event.UnitFilesChanged, reload); managerInterface.on(managerInterface.event.Reloading, reload); kill = connect(); /* manager.on(manager.event.JobRemoved, async function(id, job, unitId, result) { try { if (filter.isValid(unitId)) { const unit = await manager.getUnit(unitId); mail.send(unitId, { summary: await unit.summary, detailed: await unit.props }); } } catch(e) { mail.send('error - job removed', e); } }) */ } src/package/000077500000000000000000000000001516074440100132425ustar00rootroot00000000000000src/package/preinstall.js000066400000000000000000000021241516074440100157540ustar00rootroot00000000000000const utils = require('corifeus-utils'); const os = require('os'); const pkg = require('../../package.json'); const install = async () => { const arch = os.arch(); const platform = os.platform() let platformSearch; let archSearch; if (platform === 'linux') { platformSearch = 'linux'; if (arch === 'x64') { archSearch = 'amd64'; } } if (platformSearch === undefined || archSearch === undefined) { console.log(`This platform for ${pkg.name} is not implemented: ${platform}/${arch}`); console.log(`The ${pkg.name} will not work, but will work silently`); return } console.log(`Found platform: ${platformSearch} and architecture ${archSearch}`); if (process.env.TRAVIS !== undefined) { console.warn(`This is a Travis build, so apt install is not required.`) } else { console.warn(`You probably might need a c++11 if it is old.`) await utils.childProcess.exec(`sudo apt-get install -y libdbus-1-dev libglib2.0-dev`, true) } console.log(`Install done`); } install(); src/package/preinstall.sh000077500000000000000000000001761516074440100157620ustar00rootroot00000000000000#!/usr/bin/env bash DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" npm install corifeus-utils node $DIR/preinstall.jssrc/watchdog.js000066400000000000000000000124131516074440100140060ustar00rootroot00000000000000const exec = require('child_process').exec; const ms = require('millisecond'); const Mail = require('./mail') const moment = require('moment'); const lib = require('./lib'); const parseRow = /^([^\s]+)([\s]+)([^\s]+)([\s]+)([^\s]+)([\s]+)([^\s]+)([\s]+)(.+)$/; module.exports = (settings) => { /** * Parses systemctl services * @param input * @returns {{}} */ const parseStatus = (input) => { const lines = input.split("\n"); lines.pop(); const dbUpdate = {}; lines.forEach((line) => { const [, unit, , load, , active, , sub, , description] = parseRow.exec(line); dbUpdate[unit] = { unit: unit, load: load, active: active, sub: sub, description: new String(description).trim() }; }) return dbUpdate; } /** * Runs an update of getting current services and parses and returns at once. * @returns {Promise} */ const update = async () => { return new Promise((resolve, reject) => { exec(command, { maxBuffer: 10 * 1024 * 1024 }, (error, stdout, stderr) => { if (error) { reject(error); return; } if (stderr !== '') { reject(new Error({ stderr: stderr, stout: stdout, })); return; } resolve(parseStatus(stdout)); }); }) } /** * The watch program. */ const watch = async () => { try { const dbUpdate = await update(); if (db === undefined) { db = dbUpdate; /* const notFound = []; Object.keys(db).forEach((key) => { if (db[key].load == 'not-found') { notFound.push(db[key]); } }) if (notFound.length > 0) { mail.send(`NOT FOUND`, notFound); } */ return; } const watchUpdate = { changes: {}, added: {}, removed: {} } Object.keys(dbUpdate).forEach((unit) => { if (db[unit] === undefined && filter.isValid(unit)) { watchUpdate.added[unit] = lib.clone(dbUpdate[unit]); } else if (JSON.stringify(db[unit]) !== JSON.stringify(dbUpdate[unit]) && filter.isValid(unit)) { watchUpdate.changes[unit] = { 'old': lib.clone(db[unit]), 'new': lib.clone(dbUpdate[unit]), }; } delete db[unit]; }); watchUpdate.removed = {}; Object.keys(db).forEach((unit) => { if (filter.isValid(unit)) { watchUpdate.removed[unit] = lib.clone(db[unit]); } }) db = dbUpdate; let sendChanges = false; Object.keys(watchUpdate).forEach((watchUpdateKey) => { if (Object.keys(watchUpdate[watchUpdateKey]).length > 0) { sendChanges = true; } }) if (sendChanges === true) { watchUpdate.moment = moment().format(settings.moment); mail.send(`CHANGED`, watchUpdate); } } catch (error) { mail.send(`ERROR`, error); } if (timeLastPing == undefined || Date.now() - timeLastPing > timePing) { console.log(`ping - ${Object.keys(db).length} items - every ${timePingString}`); //console.log(db); timeLastPing = Date.now(); } } const mail = Mail(settings); const filter = lib.filter(settings); //const command = 'sudo systemctl -all --full --plain --no-pager --no-legend '; let types = ''; if (settings.filter.type.length > 0) { types = `--type=${settings.filter.type.join(',')}`; } const options = settings.options || ''; const command = `${settings.sudo ? 'sudo ' : ''}systemctl --plain --no-pager --no-legend ${options} ${types}`; // systemctl --state=not-found --all let db; let timeLastPing; let timePing = ms(settings.ping); let timePingString = settings.ping; let interval = ms(settings.interval) let intervalString = settings.interval; const showStatus = () => { if (settings.filter.type.length === 0) { console.log(`watchdog all`) } else { console.log(`watchdog type(s): ${settings.filter.type.join(',')}`) } console.log(`ping: ${timePingString}`); console.log(`interval: ${intervalString}`); console.log(`command: ${command}`); } console.log(`started`); if (settings.test) { interval = 3000; intervalString = interval + ' ms'; timePing = 3000; timePingString = 3000 + ' ms'; status(`TEST MODE`); } showStatus(); return { run: () => { watch(); setInterval(watch, interval); } } } watchdog000077500000000000000000000007201516074440100126050ustar00rootroot00000000000000#!/usr/bin/env node const os = require('os'); const process = require('process'); /* const cores = os.cpus().length < 4 ? 4 : os.cpus().length; process.env.UV_THREADPOOL_SIZE = cores; console.debug(`P3X sets UV_THREADPOOL_SIZE to ${cores} thread pool`) */ const lib = require('./src/lib'); const settings = lib.getSettings(); if (settings === false) { return; } const Watchdog = require('./src/watchdog'); const watchdog = Watchdog(settings); watchdog.run();