node-linux
Version:
Support daemon creation and management on Linux.
445 lines (403 loc) • 12.4 kB
JavaScript
var mu = require('mu2'),
os = require('os'),
p = require('path'),
fs = require('fs'),
exec = require('child_process').exec,
supportedos = ['debian','centos','redhat','fedora','ubuntu','amzn'];
// Collapse the supported linux flavors down to just redhat and debian
var getLinuxFlavor = function(stdout) {
var _os = supportedos.filter(function(i){
return stdout.indexOf(i) >= 0;
})[0];
switch(_os){
// Use RedHat for CentOS
case 'centos':
case 'fedora':
case 'amzn':
case 'redhat':
_os = 'redhat';
break;
// Use debian for Ubuntu & default
case 'ubuntu':
case 'debian':
default:
_os = 'debian';
break;
}
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('cat /proc/version',function(error, stdout, stderr){
stdout = stdout.toLowerCase();
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('cat /proc/version',function(error, stdout, stderr){
stdout = stdout.toLowerCase();
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('cat /proc/version',function(error, stdout, stderr){
stdout = stdout.toLowerCase();
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;