UNPKG

node-service-linux

Version:
408 lines (373 loc) 11.2 kB
var mu = require('mu2'), os = require('os'), p = require('path'), fs = require('fs'), exec = require('child_process').exec, supportedos = ['archlinux']; /** * @class nodelinux.systemd * A class used to create systemd init scripts to run a Node.js script as a background daemon/service. * @param {Object} config */ var init = function(config) { config = config || {}; Object.defineProperties(this, { /** * @property {String} templateRoot * The root directory where initd templates are stored. */ templateRoot: { enumerable: true, writable: false, configurable: false, value: p.join(__dirname, 'templates', 'systemd') }, tpl: { enumerable: false, writable: true, configurable: false, value: null }, _label: { enumerable: false, writable: true, configurable: false, value: null }, _configFilePath: { enumerable: false, writable: true, configurable: false, value: function() { return '/etc/systemd/system/' + this.label + '.service'; } }, /** * @property {String} label * The label used for the daemon (file name). */ label: { enumerable: true, get: function() { return this._label; } }, exists: { enumerable: false, get: function() { return fs.existsSync(this._configFilePath()); } }, /** * @method generate * Generate a systemd init script for the current operating system. * @param {Function} callback * The callback is fired after the script is generated. * @param {Object} callback.script * The content of the initd script file. */ generate: { enumerable: true, writable: false, configurable: false, value: function(callback) { callback = callback || function() {}; var me = this; exec('cat /proc/version', function(error, stdout, stderr) { stdout = stdout.toLowerCase(); /** * @cfg {String} name required * The title of the process */ /** * @cfg {String} description * A description of what the service does. */ /** * @cfg {String} [author='Unknown'] * The author of the process. */ /** * @cfg {String} [user='root'] * The user account under which the process should run. */ /** * @cfg {String} [group='root'] * The user group under which the process should run. */ /** * @cfg {String} [pidroot='/var/run'] * The root directory where the PID file will be created (if applicable to the OS environment). */ /** * @cfg {String} [logroot='/var/log'] * The root directory where the log file will be created. */ /** * @cfg {Object} [env] * A key/value object containing environment variables that should be passed to the process. */ /** * @cfg {String} [template] * Use this template with the #generate method to create custom output for the initd script. * This should be an absolute filepath. */ opt = { label: me.label, servicesummary: config.name, servicedescription: config.description || config.name, author: config.author || 'Unknown', script: p.join(__dirname, 'wrapper.js'), nodescript: config.script || '', wrappercode: (config.wrappercode || ''), description: config.description, user: config.user || 'root', group: config.group || 'root', pidroot: config.pidroot || '/var/run', logroot: config.logroot || '/var/log', env: '', created: new Date(), execpath: process.execPath, stopparentfirst: config.stopparentfirst, stoptimeout: config.stoptimeout }; var _env = []; if (config.env) { for (var i = 0; i < config.env.length; i++) { for (var el in config.env[i]) { _env.push('\'' + el + '=' + config.env[i][el] + '\''); } } opt.env = _env.join(' '); } var _usewrapper = (config.usewrapper !== undefined ? config.usewrapper : false); var _template = (_usewrapper ? 'service-wrapper' : 'service'); var _path = config.template == undefined ? p.join(me.templateRoot, _template) : p.resolve(config.template); mu.compile(_path, function(err, tpl) { var stream = mu.render(tpl, opt), chunk = ""; stream.on('data', function(data) { chunk += data; }); stream.on('end', function() { callback(chunk); }); }); }); } }, /** * @event install * Fired when the #install completes. */ /** * @event alreadyinstalled * Fired when an #install is attempted but the process already exists. */ /** * @method createProcess * Generate the physical daemon/process file. * @param {Function} [callback] * An optional callback fired when the process file has been completed. * This constitutes an "installation" */ createProcess: { enumerable: true, writable: false, configurable: false, value: function(callback) { var filepath = p.join(this._configFilePath()), me = this; console.log("Installing service on", filepath); fs.exists(filepath, function(exists) { if (!exists) { me.generate(function(script) { fs.writeFile(filepath, script, function(err) { if (err) return me.emit('error', err); fs.chmod(filepath, '644', function(_err) { if (_err) return me.emit('error', _err); var cmd = 'systemctl daemon-reload'; console.log('Running %s...', cmd); exec(cmd, function(err) { if (err) return me.emit('error', err); me.emit('install'); }); }) }); }); } else { me.emit('alreadyinstalled'); } }); } }, /** * @method removeProcess * Remove the process files, including the main init.d script and log files. * @param {Function} [callback] * An optional callback fired when the files have been removed. */ removeProcess: { enumerable: true, writable: false, configurable: false, value: function(callback) { if (!fs.existsSync(this._configFilePath())) { this.emit('doesnotexist'); return; } var me = this; // Remove the main process file first fs.unlink(this._configFilePath(), function() { var lr = require('path').join(me.logroot || '/var/log', this.label + '.log'), er = require('path').join(me.logroot || '/var/log', this.label + '-error.log'), pr = require('path').join(me.pidroot || '/var/run', this.label + '.pid'); // Remove the PID fs.exists(pr, function(exists) { exists && fs.unlink(pr); }); // Remove any logs if they exist fs.exists(lr, function(exists) { if (exists) { fs.unlinkSync(lr); } fs.exists(er, function(exists) { if (exists) { fs.unlinkSync(er); } me.emit('uninstall'); callback && callback(); }); }); }); } }, /** * @method start * Start the process. */ start: { enumerable: true, writable: true, configurable: false, value: function(callback) { if (!this.exists) { this.emit('doesnotexist'); callback && callback(); return; } var me = this; var cmd = 'systemctl start ' + this.label; console.log('Running %s...', cmd); exec(cmd, function(err) { if (err) return me.emit('error', err); /** * @event start * Fired when the #start method completes. */ me.emit('start'); callback && callback(); }); } }, /** * @method stop * Stop the process. */ stop: { enumerable: true, writable: true, configurable: false, value: function(callback) { if (!this.exists) { this.emit('doesnotexist'); callback && callback(); return; } var me = this; var cmd = 'systemctl stop ' + this.label; console.log('Running %s...', cmd); exec(cmd, function(err) { if (!err) { /** * @event stop * Fired when the #stop method completes. */ me.emit('stop'); callback && callback(); } else { me.emit('error', err); } }); } }, /** * @method enable * Enable the service. */ enable: { enumerable: true, writable: true, configurable: false, value: function(callback) { if (!this.exists) { this.emit('doesnotexist'); callback && callback(); return; } var me = this; var cmd = 'systemctl enable ' + this.label; console.log('Running %s...', cmd); exec(cmd, function(err) { if (!err) { /** * @event enable * Fired when the #enable method completes. */ me.emit('enable'); callback && callback(); } else { me.emit('error', err); } }); } }, /** * @method disable * Disable the service. */ disable: { enumerable: true, writable: true, configurable: false, value: function(callback) { if (!this.exists) { this.emit('doesnotexist'); callback && callback(); return; } var me = this; var cmd = 'systemctl disable ' + this.label; console.log('Running %s...', cmd); exec(cmd, function(err) { if (!err) { /** * @event disable * Fired when the #disable method completes. */ me.emit('disable'); callback && callback(); } else { me.emit('error', err); } }); } } }); this._label = (config.name || '').replace(/[^a-zA-Z0-9\-]/, '').toLowerCase(); }; var util = require('util'), EventEmitter = require('events').EventEmitter; // Inherit Events util.inherits(init, EventEmitter); module.exports = init;