node-service-linux
Version:
Support daemon creation and management on Linux.
408 lines (373 loc) • 11.2 kB
JavaScript
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;