.gitignore000066400000000000000000000004531516157255500130640ustar00rootroot00000000000000/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 package-lock.json .idea/000077500000000000000000000000001516157255500120525ustar00rootroot00000000000000.idea/codeStyleSettings.xml000066400000000000000000000004251516157255500162510ustar00rootroot00000000000000 .idea/modules.xml000066400000000000000000000004321516157255500142430ustar00rootroot00000000000000 .idea/systemd-manager.iml000066400000000000000000000005201516157255500156520ustar00rootroot00000000000000 .idea/vcs.xml000066400000000000000000000002471516157255500133720ustar00rootroot00000000000000 .npmignore000066400000000000000000000002221516157255500130650ustar00rootroot00000000000000/.idea /artifacts /build /test /node_modules /*.iml /*.ipr /*.iws /.travis.yml /.scrutinizer.yml /Gruntfile.js /*.lock *.log /corifeus-boot.json .scrutinizer.yml000066400000000000000000000021461516157255500142570ustar00rootroot00000000000000checks: javascript: true filter: excluded_paths: - test/* - node_modules/* - build/* - docs/* build: cache: disabled: true environment: google_chrome: use_latest: true variables: CXX: g++-4.8 dependencies: before: - export LATEST=$(nvm ls-remote | tail -1) - nvm install $LATEST # - nvm use $LATEST - sudo apt-get -y install software-properties-common python-software-properties # - sudo apt-cache policy gcc++ # - sudo apt-cache policy g++ # - sudo apt update -y - sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y - sudo apt update -y - sudo apt-get install -y gcc-4.8 - sudo apt-get install -y g++-4.8 - sudo apt-get install -y libdbus-1-dev - sudo apt-get install -y libglib2.0-dev - sudo apt-get install -y dbus - npm install -g node-gyp - npm install -g grunt-cli - git config remote.origin.url - node -v tests: override: - command: 'grunt' coverage: file: 'build/coverage/clover.xml' format: 'clover' .travis.yml000066400000000000000000000005201516157255500132000ustar00rootroot00000000000000language: node_js node_js: - "node" env: - CXX=g++-4.8 addons: apt: sources: - ubuntu-toolchain-r-test packages: # order is important! - dbus - dbus-x11 - gcc-4.8 - g++-4.8 - libdbus-1-dev - libglib2.0-dev before_script: - npm install -g node-gyp - npm install grunt-cli -gGruntfile.js000066400000000000000000000004501516157255500133660ustar00rootroot00000000000000const 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); } LICENSE000066400000000000000000000021411516157255500120750ustar00rootroot00000000000000MIT License Copyright (c) 2018 Patrik Laszlo / patrikx3 / https://patrikx3.com and contributors 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.md000066400000000000000000000165551516157255500123650ustar00rootroot00000000000000[//]: #@corifeus-header [![Build Status](https://travis-ci.org/patrikx3/systemd-manager.svg?branch=master)](https://travis-ci.org/patrikx3/systemd-manager) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/patrikx3/systemd-manager/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/patrikx3/systemd-manager/?branch=master) [![Code Coverage](https://scrutinizer-ci.com/g/patrikx3/systemd-manager/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/patrikx3/systemd-manager/?branch=master) [![NPM](https://nodei.co/npm/p3x-systemd-manager.png?downloads=true&downloadRank=true&stars=true)](https://www.npmjs.com/package/p3x-systemd-manager/) --- # SystemD Manager, watchdog, notifier and service v1.1.674-279 This is an open source project. Just code. If you like this code, please add a star in GitHub and if you really like, you can donate as well. Thanks you so much! Given, I have my own server, with dynamic IP address, it could happen that the server for about max 5 minutes can not be reachable for the dynamic DNS or very rarely I backup with Clonzilla the SSD or something with the electricity (too much hoovering or cleaning - but I worked on it, so should not happen again), but for some reason, it is not reachable please hang on for 5-30 minutes and it will be back for sure. All my domains (patrikx3.com and corifeus.com) could have errors right now, since I am developing in my free time and you can catch glitches, but usually it is stable (imagine updating everything always, which is weird). ### Node Version Requirement ``` >=8.9.0 ``` ### Built on Node ``` v9.8.0 ``` The ```async``` and ```await``` keywords are required. Install NodeJs: https://nodejs.org/en/download/package-manager/ # 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 When you install, it will ask for ```sudo root```, because the dependencies are installed with ```apt```. By now the install is automatic (not needed anymore, but these are the libs that the program uses): ```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 ```javascript #!/usr/bin/env node const systemd = require('p3x-systemd-manager'); const settings = systemd.lib.getSettings(); if (settings === false) {t 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. ```javascript 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" } } } } ``` [//]: #@corifeus-footer --- [**P3X-SYSTEMD-MANAGER**](https://pages.corifeus.com/systemd-manager) Build v1.1.674-279 [![Like Corifeus @ Facebook](https://img.shields.io/badge/LIKE-Corifeus-3b5998.svg)](https://www.facebook.com/corifeus.software) [![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) ## Sponsor [![JetBrains](https://www.patrikx3.com/images/jetbrains-logo.svg)](https://www.jetbrains.com/) [//]: #@corifeus-footer:end artifacts/000077500000000000000000000000001516157255500130525ustar00rootroot00000000000000artifacts/settings.json000066400000000000000000000015461516157255500156130ustar00rootroot00000000000000{ "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" } } } }index.js000066400000000000000000000000421516157255500125330ustar00rootroot00000000000000module.exports = require('./src');notifier000077500000000000000000000007451516157255500126450ustar00rootroot00000000000000#!/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.json000066400000000000000000000024201516157255500133560ustar00rootroot00000000000000{ "name": "p3x-systemd-manager", "corifeus": { "prefix": "p3x-", "publish": true, "type": "p3x", "code": "Flap", "nodejs": "v9.8.0" }, "version": "1.1.674-279", "description": "SystemD Manager, watchdog, notifier and service", "main": "index.js", "dependencies": { "corifeus-utils": "^1.1.854-302", "dbus": "^1.0.3", "millisecond": "^0.1.2", "moment": "^2.21.0", "nodemailer": "^4.6.2" }, "devDependencies": { "corifeus-builder": "^1.7.1241-317" }, "scripts": { "test": "dbus-launch grunt mocha_istanbul", "preinstall": "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://pages.corifeus.com/systemd-manager", "engines": { "node": ">=8.9.0" } } src/000077500000000000000000000000001516157255500116615ustar00rootroot00000000000000src/dbus/000077500000000000000000000000001516157255500126165ustar00rootroot00000000000000src/dbus/Interface.js000066400000000000000000000022261516157255500150560ustar00rootroot00000000000000const 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); }); }) } async 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.js000066400000000000000000000037621516157255500141270ustar00rootroot00000000000000const 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 = bootsrc/dbus/index.js000066400000000000000000000002501516157255500142600ustar00rootroot00000000000000module.exports = { systemd: require('./systemd'), manager: require('./interfaces/manager'), job: require('./interfaces/job'), boot: require('./boot') };src/dbus/interfaces/000077500000000000000000000000001516157255500147415ustar00rootroot00000000000000src/dbus/interfaces/index.js000066400000000000000000000002301516157255500164010ustar00rootroot00000000000000module.exports = { job: require('./job'), manager: require('./manager'), properties: require('./properties'), unit: require('./unit'), }src/dbus/interfaces/job.js000066400000000000000000000006441516157255500160550ustar00rootroot00000000000000const 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.js000066400000000000000000000034441516157255500167160ustar00rootroot00000000000000const 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); }); }) } async 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.js000066400000000000000000000011341516157255500174720ustar00rootroot00000000000000const 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.js000066400000000000000000000022401516157255500162540ustar00rootroot00000000000000const 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.js000066400000000000000000000025551516157255500146530ustar00rootroot00000000000000const 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 let bus; 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 (bus == undefined) { bus = DBus.getBus('system') } return new Promise((resolve, reject) => { bus.getInterface(system, node, _interface, (error, manager) => { if (error) { return reject(error); } resolve(manager); }); }) } module.exports = { defaults: defaults, getInterface: getInterface, }src/index.js000066400000000000000000000002251516157255500133250ustar00rootroot00000000000000module.exports = { watchdog: require('./watchdog'), dbus: require('./dbus'), notifier: require('./notifier'), lib: require('./lib') }src/lib.js000066400000000000000000000044731516157255500127750ustar00rootroot00000000000000require('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.js000066400000000000000000000041311516157255500131400ustar00rootroot00000000000000const nodemailer = require('nodemailer'); const os = require('os'); const moment = require('moment'); let singletonInstance; module.exports = (settings) => { 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) } if (body instanceof Error) { body = JSON.parse(JSON.stringify(body, ["message", "arguments", "type", "name", 'stack'])); console.error(body); } if (typeof body !== 'string') { body = momentify(body); body = JSON.stringify(body, null, 2); } else { body = ` ${moment().format(settings.moment)} ${body} `; } const message = { from: from, to: to, subject: subject, text: body }; console.log(`send new mail subject: ${subject}`); 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.js000066400000000000000000000101701516157255500140350ustar00rootroot00000000000000const Mail = require('./mail') const dbus = require('./dbus'); const interfaces = require('./dbus/interfaces'); const lib = require('./lib'); module.exports = async (settings) => { 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/000077500000000000000000000000001516157255500132545ustar00rootroot00000000000000src/package/preinstall.js000066400000000000000000000021221516157255500157640ustar00rootroot00000000000000const 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.sh000077500000000000000000000001761516157255500157740ustar00rootroot00000000000000#!/usr/bin/env bash DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" npm install corifeus-utils node $DIR/preinstall.jssrc/watchdog.js000066400000000000000000000124171516157255500140240ustar00rootroot00000000000000const 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); } } } systemd-manager.iml000066400000000000000000000006241516157255500146770ustar00rootroot00000000000000 watchdog000077500000000000000000000007121516157255500126200ustar00rootroot00000000000000#!/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();