.gitignore000066400000000000000000000004531516260143000130460ustar00rootroot00000000000000/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/000077500000000000000000000000001516260143000120345ustar00rootroot00000000000000.idea/codeStyleSettings.xml000066400000000000000000000004251516260143000162330ustar00rootroot00000000000000 .idea/modules.xml000066400000000000000000000004321516260143000142250ustar00rootroot00000000000000 .idea/systemd-manager.iml000066400000000000000000000005201516260143000156340ustar00rootroot00000000000000 .idea/vcs.xml000066400000000000000000000002471516260143000133540ustar00rootroot00000000000000 .npmignore000066400000000000000000000002221516260143000130470ustar00rootroot00000000000000/.idea /artifacts /build /test /node_modules /*.iml /*.ipr /*.iws /.travis.yml /.scrutinizer.yml /Gruntfile.js /*.lock *.log /corifeus-boot.json .scrutinizer.yml000066400000000000000000000021461516260143000142410ustar00rootroot00000000000000checks: 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.yml000066400000000000000000000005201516260143000131620ustar00rootroot00000000000000language: 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.js000066400000000000000000000004501516260143000133500ustar00rootroot00000000000000const 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); } LICENSE000066400000000000000000000021411516260143000120570ustar00rootroot00000000000000MIT License Copyright (c) 2017 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.md000066400000000000000000000151721516260143000123410ustar00rootroot00000000000000[//]: #@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.522-221 This is an open source project. Just code. ### Node Version Requirement ``` >=8.9.0 ``` ### Built on Node ``` v9.2.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.522-221 [![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=_donations&business=LFRV89WPRMMVE&lc=HU&item_name=Patrik%20Laszlo&item_number=patrikx3¤cy_code=HUF&bn=PP%2dDonationsBF%3abtn_donate_SM%2egif%3aNonHosted) [![Contact Corifeus / P3X](https://img.shields.io/badge/Contact-P3X-ff9900.svg)](https://www.patrikx3.com/en/front/contact) [//]: #@corifeus-footer:end artifacts/000077500000000000000000000000001516260143000130345ustar00rootroot00000000000000artifacts/settings.json000066400000000000000000000015461516260143000155750ustar00rootroot00000000000000{ "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.js000066400000000000000000000000421516260143000125150ustar00rootroot00000000000000module.exports = require('./src');notifier000077500000000000000000000003761516260143000126270ustar00rootroot00000000000000#!/usr/bin/env node 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.json000066400000000000000000000024171516260143000133460ustar00rootroot00000000000000{ "name": "p3x-systemd-manager", "corifeus": { "prefix": "p3x-", "publish": true, "type": "p3x", "code": "Flap", "nodejs": "v9.2.0" }, "version": "1.1.522-221", "description": "SystemD Manager, watchdog, notifier and service", "main": "index.js", "dependencies": { "corifeus-utils": "^1.1.680-233", "dbus": "^1.0.2", "millisecond": "^0.1.2", "moment": "^2.19.2", "nodemailer": "^4.4.0" }, "devDependencies": { "corifeus-builder": "^1.7.1043-231" }, "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/000077500000000000000000000000001516260143000116435ustar00rootroot00000000000000src/dbus/000077500000000000000000000000001516260143000126005ustar00rootroot00000000000000src/dbus/Interface.js000066400000000000000000000022261516260143000150400ustar00rootroot00000000000000const 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.js000066400000000000000000000037621516260143000141110ustar00rootroot00000000000000const 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.js000066400000000000000000000002501516260143000142420ustar00rootroot00000000000000module.exports = { systemd: require('./systemd'), manager: require('./interfaces/manager'), job: require('./interfaces/job'), boot: require('./boot') };src/dbus/interfaces/000077500000000000000000000000001516260143000147235ustar00rootroot00000000000000src/dbus/interfaces/index.js000066400000000000000000000002301516260143000163630ustar00rootroot00000000000000module.exports = { job: require('./job'), manager: require('./manager'), properties: require('./properties'), unit: require('./unit'), }src/dbus/interfaces/job.js000066400000000000000000000006441516260143000160370ustar00rootroot00000000000000const 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.js000066400000000000000000000034441516260143000167000ustar00rootroot00000000000000const 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.js000066400000000000000000000011341516260143000174540ustar00rootroot00000000000000const 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.js000066400000000000000000000022401516260143000162360ustar00rootroot00000000000000const 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.js000066400000000000000000000025551516260143000146350ustar00rootroot00000000000000const 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.js000066400000000000000000000002251516260143000133070ustar00rootroot00000000000000module.exports = { watchdog: require('./watchdog'), dbus: require('./dbus'), notifier: require('./notifier'), lib: require('./lib') }src/lib.js000066400000000000000000000044731516260143000127570ustar00rootroot00000000000000require('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.js000066400000000000000000000041311516260143000131220ustar00rootroot00000000000000const 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.js000066400000000000000000000101701516260143000140170ustar00rootroot00000000000000const 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/000077500000000000000000000000001516260143000132365ustar00rootroot00000000000000src/package/preinstall.js000066400000000000000000000021221516260143000157460ustar00rootroot00000000000000const 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.sh000077500000000000000000000001761516260143000157560ustar00rootroot00000000000000#!/usr/bin/env bash DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" npm install corifeus-utils node $DIR/preinstall.jssrc/watchdog.js000066400000000000000000000124171516260143000140060ustar00rootroot00000000000000const 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.iml000066400000000000000000000005171516260143000146620ustar00rootroot00000000000000 watchdog000077500000000000000000000003441516260143000126030ustar00rootroot00000000000000#!/usr/bin/env node const lib = require('./src/lib'); const settings = lib.getSettings(); if (settings === false) { return; } const Watchdog = require('./src/watchdog'); const watchdog = Watchdog(settings); watchdog.run();