UNPKG

node-service-linux

Version:
426 lines (388 loc) 11.7 kB
var mu = require('mu2'), os = require('os'), p = require('path'), fs = require('fs'), exec = require('child_process').exec, linuxFlavorCommand = "[ ! -e /etc/redhat-release ]; echo $?"; // Determine Linux flavor from existence of "/etc/redhat-release" file var getLinuxFlavor = function(stdout) { // Use Debian by default (or "/etc/debian-version" file exists) var _os = 'debian'; stdout = stdout.trim(); if (stdout === '1') { _os = 'redhat'; } return _os; }; /** * @class nodelinux.systemv * A class used to create systemv 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', 'systemv') }, 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/init.d/' + this.label; } }, /** * @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 systemv 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(linuxFlavorCommand, function(error, stdout, stderr) { var _os = getLinuxFlavor(stdout); /** * @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.piddir || '/var/run', logroot: config.logpath || '/var/log', env: '', created: new Date(), execpath: process.execPath, }; // escape the double quotes in the wrappercode args here if we are on a redhat system // because we will put them inside an su -c "expandedcommand" // used by redhat systemv script to run service as specific user // DON'T escape the quotes on debian though, since its service script is implemented differently if (_os === "redhat") { // see http://stackoverflow.com/a/22837870 opt.wrappercode = JSON.stringify(opt.wrappercode).slice(1, -1); } var _path = config.template == undefined ? p.join(me.templateRoot, _os) : 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) throw err; fs.chmod(filepath, '755', function(_err) { if (_err) throw _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 = this._configFilePath() + ' start'; console.log('Running %s...', cmd); exec(cmd, function(err) { if (err) throw 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 = this._configFilePath() + ' stop'; 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 * Stop the process. */ enable: { enumerable: true, writable: true, configurable: false, value: function(callback) { if (!this.exists) { this.emit('doesnotexist'); callback && callback(); return; } var me = this; exec(linuxFlavorCommand, function(error, stdout, stderr) { var _os = getLinuxFlavor(stdout); var cmd; if (_os === 'debian') { cmd = '/usr/sbin/update-rc.d ' + me.label + ' defaults'; } else { cmd = '/sbin/chkconfig ' + me.label + ' on'; } var me2 = me; exec(cmd, function(err) { if (!err) { /** * @event enable * Fired when the #enable method completes. */ me2.emit('enable'); callback && callback(); } else { me2.emit('error', err); } }); }); } }, /** * @method disable * Stop the process. */ disable: { enumerable: true, writable: true, configurable: false, value: function(callback) { if (!this.exists) { this.emit('doesnotexist'); callback && callback(); return; } var me = this; exec(linuxFlavorCommand, function(error, stdout, stderr) { var _os = getLinuxFlavor(stdout); var cmd; if (_os === 'debian') { cmd = '/usr/sbin/update-rc.d ' + this.label + ' remove'; } else { cmd = '/sbin/chkconfig ' + this.label + ' off'; } var me2 = me; exec(cmd, function(err) { if (!err) { /** * @event enable * Fired when the #enable method completes. */ me2.emit('disable'); callback && callback(); } else { me2.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;