summaryrefslogtreecommitdiff
path: root/node_modules/forever/lib
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/forever/lib')
-rw-r--r--node_modules/forever/lib/forever.js907
-rw-r--r--node_modules/forever/lib/forever/cli.js564
-rw-r--r--node_modules/forever/lib/forever/service/adapters/adapter.js96
-rw-r--r--node_modules/forever/lib/forever/service/adapters/initd/index.js9
-rw-r--r--node_modules/forever/lib/forever/service/adapters/systemv/foreverd82
-rw-r--r--node_modules/forever/lib/forever/service/adapters/systemv/index.js199
-rw-r--r--node_modules/forever/lib/forever/service/cli.js102
-rw-r--r--node_modules/forever/lib/forever/service/index.js22
-rw-r--r--node_modules/forever/lib/forever/service/service.js268
-rw-r--r--node_modules/forever/lib/forever/worker.js122
10 files changed, 2371 insertions, 0 deletions
diff --git a/node_modules/forever/lib/forever.js b/node_modules/forever/lib/forever.js
new file mode 100644
index 0000000..290be83
--- /dev/null
+++ b/node_modules/forever/lib/forever.js
@@ -0,0 +1,907 @@
+/*
+ * forever.js: Top level include for the forever module
+ *
+ * (C) 2010 Nodejitsu Inc.
+ * MIT LICENCE
+ *
+ */
+
+var fs = require('fs'),
+ path = require('path'),
+ events = require('events'),
+ exec = require('child_process').exec,
+ spawn = require('child_process').spawn,
+ cliff = require('cliff'),
+ nconf = require('nconf'),
+ nssocket = require('nssocket'),
+ timespan = require('timespan'),
+ utile = require('utile'),
+ winston = require('winston'),
+ mkdirp = utile.mkdirp,
+ async = utile.async;
+
+var forever = exports;
+
+//
+// Setup `forever.log` to be a custom `winston` logger.
+//
+forever.log = new (winston.Logger)({
+ transports: [
+ new (winston.transports.Console)()
+ ]
+});
+
+forever.log.cli();
+
+//
+// Setup `forever out` for logEvents with `winston` custom logger.
+//
+forever.out = new (winston.Logger)({
+ transports: [
+ new (winston.transports.Console)()
+ ]
+});
+
+//
+// ### Export Components / Settings
+// Export `version` and important Prototypes from `lib/forever/*`
+//
+forever.initialized = false;
+forever.kill = require('forever-monitor').kill;
+forever.checkProcess = require('forever-monitor').checkProcess;
+forever.root = path.join(process.env.HOME || process.env.USERPROFILE || '/root', '.forever');
+forever.config = new nconf.File({ file: path.join(forever.root, 'config.json') });
+forever.Forever = forever.Monitor = require('forever-monitor').Monitor;
+forever.Worker = require('./forever/worker').Worker;
+forever.cli = require('./forever/cli');
+
+//
+// Expose version through `pkginfo`
+//
+require('pkginfo')(module, 'version');
+
+//
+// Expose the global forever service
+//
+forever.__defineGetter__('service', function () {
+ return require('./forever/service');
+});
+
+//
+// ### function getSockets (sockPath, callback)
+// #### @sockPath {string} Path in which to look for UNIX domain sockets
+// #### @callback {function} Continuation to pass control to when complete
+// Attempts to read the files from `sockPath` if the directory does not exist,
+// then it is created using `mkdirp`.
+//
+function getSockets(sockPath, callback) {
+ var sockets;
+
+ try {
+ sockets = fs.readdirSync(sockPath);
+ }
+ catch (ex) {
+ if (ex.code !== 'ENOENT') {
+ return callback(ex);
+ }
+
+ return mkdirp(sockPath, '0755', function (err) {
+ return err ? callback(err) : callback(null, []);
+ });
+ }
+
+ callback(null, sockets);
+}
+
+//
+// ### function getAllProcess (callback)
+// #### @callback {function} Continuation to respond to when complete.
+// Returns all data for processes managed by forever.
+//
+function getAllProcesses(callback) {
+ var sockPath = forever.config.get('sockPath');
+
+ function getProcess(name, next) {
+ var fullPath = path.join(sockPath, name),
+ socket = new nssocket.NsSocket();
+
+ socket.connect(fullPath, function (err) {
+ if (err) {
+ next(err);
+ }
+
+ socket.dataOnce(['data'], function (data) {
+ data.socket = fullPath;
+ next(null, data);
+ socket.end();
+ });
+
+ socket.send(['data']);
+ });
+
+ socket.on('error', function (err) {
+ if (err.code === 'ECONNREFUSED') {
+ fs.unlink(fullPath, function () {
+ next();
+ });
+ }
+ else {
+ next();
+ }
+ });
+ }
+
+ getSockets(sockPath, function (err, sockets) {
+ if (err || (sockets && sockets.length === 0)) {
+ return callback(err);
+ }
+
+ async.map(sockets, getProcess, function (err, processes) {
+ callback(processes.filter(Boolean));
+ });
+ });
+}
+
+//
+// ### function getAllPids ()
+// Returns the set of all pids managed by forever.
+// e.x. [{ pid: 12345, foreverPid: 12346 }, ...]
+//
+function getAllPids(processes) {
+ return !processes ? null : processes.map(function (proc) {
+ return {
+ pid: proc.pid,
+ foreverPid: proc.foreverPid
+ };
+ });
+}
+
+function stopOrRestart(action, event, format, target) {
+ var emitter = new events.EventEmitter(),
+ results = [];
+
+ function sendAction(proc, next) {
+ var socket = new nssocket.NsSocket();
+
+ socket.connect(proc.socket, function (err) {
+ if (err) {
+ next(err);
+ }
+
+ socket.dataOnce([action, 'ok'], function (data) {
+ next();
+ socket.end();
+ });
+
+ socket.send([action]);
+ });
+
+ socket.on('error', function (err) {
+ next(err);
+ });
+ }
+
+ getAllProcesses(function (processes) {
+ var procs = processes;
+
+ if (target !== undefined && target !== null) {
+ procs = forever.findByIndex(target, processes)
+ || forever.findByScript(target, processes)
+ || forever.findByUid(target, processes);
+ }
+
+ if (procs && procs.length > 0) {
+ async.map(procs, sendAction, function (err, results) {
+ if (err) {
+ emitter.emit('error', err);
+ }
+
+ emitter.emit(event, forever.format(format, procs));
+ });
+ }
+ else {
+ process.nextTick(function () {
+ emitter.emit('error', new Error('Cannot find forever process: ' + target));
+ });
+ }
+ });
+
+ return emitter;
+}
+
+//
+// ### function load (options, [callback])
+// #### @options {Object} Options to load into the forever module
+// Initializes configuration for forever module
+//
+forever.load = function (options) {
+ //
+ // Setup the incoming options with default options.
+ //
+ options = options || {};
+ options.loglength = options.loglength || 100;
+ options.logstream = options.logstream || false;
+ options.root = options.root || forever.root;
+ options.pidPath = options.pidPath || path.join(options.root, 'pids');
+ options.sockPath = options.sockPath || path.join(options.root, 'sock');
+
+ //
+ // If forever is initalized and the config directories are identical
+ // simply return without creating directories
+ //
+ if (forever.initialized && forever.config.get('root') === options.root &&
+ forever.config.get('pidPath') === options.pidPath) {
+ return;
+ }
+
+ forever.config = new nconf.File({ file: path.join(options.root, 'config.json') });
+
+ //
+ // Try to load the forever `config.json` from
+ // the specified location.
+ //
+ try {
+ forever.config.loadSync();
+ }
+ catch (ex) { }
+
+ //
+ // Setup the columns for `forever list`.
+ //
+ options.columns = options.columns || forever.config.get('columns');
+ if (!options.columns) {
+ options.columns = [
+ 'uid', 'command', 'script', 'forever', 'pid', 'logfile', 'uptime'
+ ];
+ }
+
+ forever.config.set('root', options.root);
+ forever.config.set('pidPath', options.pidPath);
+ forever.config.set('sockPath', options.sockPath);
+ forever.config.set('loglength', options.loglength);
+ forever.config.set('logstream', options.logstream);
+ forever.config.set('columns', options.columns);
+
+ //
+ // Setup timestamp to event logger
+ //
+ forever.out.transports.console.timestamp = forever.config.get('timestamp') === 'true';
+
+ //
+ // Attempt to see if `forever` has been configured to
+ // run in debug mode.
+ //
+ options.debug = options.debug || forever.config.get('debug') || false;
+
+ if (options.debug) {
+ //
+ // If we have been indicated to debug this forever process
+ // then setup `forever._debug` to be an instance of `winston.Logger`.
+ //
+ forever._debug();
+ }
+
+ //
+ // Syncronously create the `root` directory
+ // and the `pid` directory for forever. Although there is
+ // an additional overhead here of the sync action. It simplifies
+ // the setup of forever dramatically.
+ //
+ function tryCreate(dir) {
+ try {
+ fs.mkdirSync(dir, '0755');
+ }
+ catch (ex) { }
+ }
+
+ tryCreate(forever.config.get('root'));
+ tryCreate(forever.config.get('pidPath'));
+ tryCreate(forever.config.get('sockPath'));
+
+ //
+ // Attempt to save the new `config.json` for forever
+ //
+ try {
+ forever.config.saveSync();
+ }
+ catch (ex) { }
+
+ forever.initialized = true;
+};
+
+//
+// ### @private function _debug ()
+// Sets up debugging for this forever process
+//
+forever._debug = function () {
+ var debug = forever.config.get('debug');
+
+ if (!debug) {
+ forever.config.set('debug', true);
+ forever.log.add(winston.transports.File, {
+ level: 'silly',
+ filename: path.join(forever.config.get('root'), 'forever.debug.log')
+ });
+ }
+};
+
+//
+// Ensure forever will always be loaded the first time it is required.
+//
+forever.load();
+
+//
+// ### function stat (logFile, script, callback)
+// #### @logFile {string} Path to the log file for this script
+// #### @logAppend {boolean} Optional. True Prevent failure if the log file exists.
+// #### @script {string} Path to the target script.
+// #### @callback {function} Continuation to pass control back to
+// Ensures that the logFile doesn't exist and that
+// the target script does exist before executing callback.
+//
+forever.stat = function (logFile, script, callback) {
+ var logAppend;
+
+ if (arguments.length === 4) {
+ logAppend = callback;
+ callback = arguments[3];
+ }
+
+ fs.stat(script, function (err, stats) {
+ if (err) {
+ return callback(new Error('script ' + script + ' does not exist.'));
+ }
+
+ return logAppend ? callback(null) : fs.stat(logFile, function (err, stats) {
+ return !err
+ ? callback(new Error('log file ' + logFile + ' exists. Use the -a or --append option to append log.'))
+ : callback(null);
+ });
+ });
+};
+
+//
+// ### function start (script, options)
+// #### @script {string} Location of the script to run.
+// #### @options {Object} Configuration for forever instance.
+// Starts a script with forever
+//
+forever.start = function (script, options) {
+ if (!options.uid) {
+ options.uid = options.uid || utile.randomString(4).replace(/^\-/, '_');
+ }
+
+ if (!options.logFile) {
+ options.logFile = forever.logFilePath(options.uid + '.log');
+ }
+
+ //
+ // Create the monitor, log events, and start.
+ //
+ var monitor = new forever.Monitor(script, options);
+ forever.logEvents(monitor);
+ return monitor.start();
+};
+
+//
+// ### function startDaemon (script, options)
+// #### @script {string} Location of the script to run.
+// #### @options {Object} Configuration for forever instance.
+// Starts a script with forever as a daemon
+//
+forever.startDaemon = function (script, options) {
+ options = options || {};
+ options.uid = options.uid || utile.randomString(4).replace(/^\-/, '_');
+ options.logFile = forever.logFilePath(options.logFile || options.uid + '.log');
+ options.pidFile = forever.pidFilePath(options.pidFile || options.uid + '.pid');
+
+ var monitor, outFD, errFD, workerPath;
+
+ //
+ // This log file is forever's log file - the user's outFile and errFile
+ // options are not taken into account here. This will be an aggregate of all
+ // the app's output, as well as messages from the monitor process, where
+ // applicable.
+ //
+ outFD = fs.openSync(options.logFile, 'a');
+ errFD = fs.openSync(options.logFile, 'a');
+ monitorPath = path.resolve(__dirname, '..', 'bin', 'monitor');
+
+ monitor = spawn(process.execPath, [monitorPath, script], {
+ stdio: ['ipc', outFD, errFD],
+ detached: true
+ });
+
+ monitor.on('exit', function (code) {
+ console.error('Monitor died unexpectedly with exit code %d', code);
+ });
+
+ monitor.send(JSON.stringify(options));
+ monitor.unref();
+ return monitor;
+};
+
+//
+// ### function startServer ()
+// #### @arguments {forever.Monitor...} A list of forever.Monitor instances
+// Starts the `forever` HTTP server for communication with the forever CLI.
+// **NOTE:** This will change your `process.title`.
+//
+forever.startServer = function () {
+ var args = Array.prototype.slice.call(arguments),
+ monitors = [],
+ callback;
+
+ args.forEach(function (a) {
+ if (Array.isArray(a)) {
+ monitors = monitors.concat(a.filter(function (m) {
+ return m instanceof forever.Monitor;
+ }));
+ }
+ else if (a instanceof forever.Monitor) {
+ monitors.push(a);
+ }
+ else if (typeof a === 'function') {
+ callback = a;
+ }
+ });
+
+ async.map(monitors, function (monitor, next) {
+ var worker = new forever.Worker({
+ monitor: monitor,
+ sockPath: forever.config.get('sockPath'),
+ exitOnStop: true
+ });
+
+ worker.start(function (err) {
+ return err ? next(err) : next(null, worker);
+ });
+ }, callback || function () {});
+};
+
+
+//
+// ### function stop (target, [format])
+// #### @target {string} Index or script name to stop
+// #### @format {boolean} Indicated if we should CLI format the returned output.
+// Stops the process(es) with the specified index or script name
+// in the list of all processes
+//
+forever.stop = function (target, format) {
+ return stopOrRestart('stop', 'stop', format, target);
+};
+
+//
+// ### function restart (target, format)
+// #### @target {string} Index or script name to restart
+// #### @format {boolean} Indicated if we should CLI format the returned output.
+// Restarts the process(es) with the specified index or script name
+// in the list of all processes
+//
+forever.restart = function (target, format) {
+ return stopOrRestart('restart', 'restart', format, target);
+};
+
+//
+// ### function restartAll (format)
+// #### @format {boolean} Value indicating if we should format output
+// Restarts all processes managed by forever.
+//
+forever.restartAll = function (format) {
+ return stopOrRestart('restart', 'restartAll', format);
+};
+
+//
+// ### function stopAll (format)
+// #### @format {boolean} Value indicating if we should format output
+// Stops all processes managed by forever.
+//
+forever.stopAll = function (format) {
+ return stopOrRestart('stop', 'stopAll', format);
+};
+
+//
+// ### function list (format, procs, callback)
+// #### @format {boolean} If set, will return a formatted string of data
+// #### @callback {function} Continuation to respond to when complete.
+// Returns the list of all process data managed by forever.
+//
+forever.list = function (format, callback) {
+ getAllProcesses(function (processes) {
+ callback(null, forever.format(format, processes));
+ });
+};
+
+//
+// ### function tail (target, length, callback)
+// #### @target {string} Target script to list logs for
+// #### @options {length|stream} **Optional** Length of the logs to tail, boolean stream
+// #### @callback {function} Continuation to respond to when complete.
+// Responds with the latest `length` logs for the specified `target` process
+// managed by forever. If no `length` is supplied then `forever.config.get('loglength`)`
+// is used.
+//
+forever.tail = function (target, options, callback) {
+ if (!callback && typeof options === 'function') {
+ callback = options;
+ options.length = 0;
+ options.stream = false;
+ }
+
+ length = options.length || forever.config.get('loglength');
+ stream = options.stream || forever.config.get('logstream');
+
+ var that = this,
+ blanks = function (e, i, a) { return e !== '' },
+ title = function (e, i, a) { return e.match(/^==>/) },
+ args = ['-n', length],
+ logs;
+
+ if (stream) args.unshift('-f');
+
+ function tailProcess(procs, next) {
+ var map = {},
+ count = 0;
+
+ procs.forEach(function (proc) {
+ args.push(proc.logFile);
+ map[proc.logFile] = { pid: proc.pid, file: proc.file };
+ count++;
+ });
+
+ tail = spawn('tail', args, {
+ stdio: [null, 'pipe', 'pipe'],
+ });
+
+ tail.stdio[1].setEncoding('utf8');
+ tail.stdio[2].setEncoding('utf8');
+
+ tail.stdio[1].on('data', function (data) {
+ chunk = data.split('\n\n');
+ chunk.forEach(function (logs) {
+ var logs = logs.split('\n').filter(blanks),
+ file = logs.filter(title),
+ lines,
+ proc;
+
+ file.length
+ ? proc = map[file[0].split(' ')[1]]
+ : proc = map[procs[0].logFile];
+
+ count === 1
+ ? lines = logs
+ : lines = logs.slice(1);
+
+ lines.forEach(function (line) {
+ callback(null, { file: proc.file, pid: proc.pid, line: line });
+ });
+ });
+ });
+
+ tail.stdio[2].on('data', function (err) {
+ return callback(err);
+ });
+ }
+
+ getAllProcesses(function (processes) {
+ if (!processes) {
+ return callback(new Error('Cannot find forever process: ' + target));
+ }
+
+ var procs = forever.findByIndex(target, processes)
+ || forever.findByScript(target, processes);
+
+ if (!procs) {
+ return callback(new Error('No logs available for process: ' + target));
+ }
+
+ tailProcess(procs, callback);
+ });
+};
+
+//
+// ### function findByIndex (index, processes)
+// #### @index {string} Index of the process to find.
+// #### @processes {Array} Set of processes to find in.
+// Finds the process with the specified index.
+//
+forever.findByIndex = function (index, processes) {
+ var indexAsNum = parseInt(index, 10);
+ if (indexAsNum == index) {
+ var proc = processes && processes[indexAsNum];
+ }
+ return proc ? [proc] : null;
+};
+
+//
+// ### function findByScript (script, processes)
+// #### @script {string} The name of the script to find.
+// #### @processes {Array} Set of processes to find in.
+// Finds the process with the specified script name.
+//
+forever.findByScript = function (script, processes) {
+ if (!processes) return null;
+
+ var procs = processes.filter(function (p) {
+ return p.file === script;
+ });
+
+ if (procs.length === 0) procs = null;
+
+ return procs;
+};
+
+//
+// ### function findByUid (uid, processes)
+// #### @uid {string} The uid of the process to find.
+// #### @processes {Array} Set of processes to find in.
+// Finds the process with the specified uid.
+//
+forever.findByUid = function (script, processes) {
+ return !processes
+ ? null
+ : processes.filter(function (p) {
+ return p.uid === script;
+ });
+};
+
+//
+// ### function format (format, procs)
+// #### @format {Boolean} Value indicating if processes should be formatted
+// #### @procs {Array} Processes to format
+// Returns a formatted version of the `procs` supplied based on the column
+// configuration in `forever.config`.
+//
+forever.format = function (format, procs) {
+ if (!procs || procs.length === 0) {
+ return null;
+ }
+
+ var index = 0,
+ columns = forever.config.get('columns'),
+ rows = [[' '].concat(columns)],
+ formatted;
+
+ function mapColumns(prefix, mapFn) {
+ return [prefix].concat(columns.map(mapFn));
+ }
+
+ if (format) {
+ //
+ // Iterate over the procs to see which has the
+ // longest options string
+ //
+ procs.forEach(function (proc) {
+ rows.push(mapColumns('[' + index + ']', function (column) {
+ return forever.columns[column]
+ ? forever.columns[column].get(proc)
+ : 'MISSING';
+ }));
+
+ index++;
+ });
+
+ formatted = cliff.stringifyRows(rows, mapColumns('white', function (column) {
+ return forever.columns[column]
+ ? forever.columns[column].color
+ : 'white';
+ }));
+ }
+
+ return format ? formatted : procs;
+};
+
+//
+// ### function cleanUp ()
+// Utility function for removing excess pid and
+// config, and log files used by forever.
+//
+forever.cleanUp = function (cleanLogs, allowManager) {
+ var emitter = new events.EventEmitter(),
+ pidPath = forever.config.get('pidPath');
+
+ getAllProcesses(function (processes) {
+ if (cleanLogs) {
+ forever.cleanLogsSync(processes);
+ }
+
+ function unlinkProcess(proc, done) {
+ fs.unlink(path.join(pidPath, proc.uid + '.pid'), function () {
+ //
+ // Ignore errors (in case the file doesnt exist).
+ //
+
+ if (cleanLogs && proc.logFile) {
+ //
+ // If we are cleaning logs then do so if the process
+ // has a logfile.
+ //
+ return fs.unlink(proc.logFile, function () {
+ done();
+ });
+ }
+
+ done();
+ });
+ }
+
+ function cleanProcess(proc, done) {
+ if (proc.child && proc.manager) {
+ return done();
+ }
+ else if (!proc.child && !proc.manager
+ || (!proc.child && proc.manager && allowManager)
+ || proc.dead) {
+ return unlinkProcess(proc, done);
+ }
+
+ //
+ // If we have a manager but no child, wait a moment
+ // in-case the child is currently restarting, but **only**
+ // if we have not already waited for this process
+ //
+ if (!proc.waited) {
+ proc.waited = true;
+ return setTimeout(function () {
+ checkProcess(proc, done);
+ }, 500);
+ }
+
+ done();
+ }
+
+ function checkProcess(proc, next) {
+ proc.child = forever.checkProcess(proc.pid);
+ proc.manager = forever.checkProcess(proc.foreverPid);
+ cleanProcess(proc, next);
+ }
+
+ if (processes && processes.length > 0) {
+ (function cleanBatch(batch) {
+ async.forEach(batch, checkProcess, function () {
+ return processes.length > 0
+ ? cleanBatch(processes.splice(0, 10))
+ : emitter.emit('cleanUp');
+ });
+ })(processes.splice(0, 10));
+ }
+ else {
+ process.nextTick(function () {
+ emitter.emit('cleanUp');
+ });
+ }
+ });
+
+ return emitter;
+};
+
+//
+// ### function cleanLogsSync (processes)
+// #### @processes {Array} The set of all forever processes
+// Removes all log files from the root forever directory
+// that do not belong to current running forever processes.
+//
+forever.cleanLogsSync = function (processes) {
+ var root = forever.config.get('root'),
+ files = fs.readdirSync(root),
+ running,
+ runningLogs;
+
+ running = processes && processes.filter(function (p) {
+ return p && p.logFile;
+ });
+
+ runningLogs = running && running.map(function (p) {
+ return p.logFile.split('/').pop();
+ });
+
+ files.forEach(function (file) {
+ if (/\.log$/.test(file) && (!runningLogs || runningLogs.indexOf(file) === -1)) {
+ fs.unlinkSync(path.join(root, file));
+ }
+ });
+};
+
+//
+// ### function logFilePath (logFile)
+// #### @logFile {string} Log file path
+// Determines the full logfile path name
+//
+forever.logFilePath = function (logFile, uid) {
+ return logFile && (logFile[0] === '/' || logFile[1] === ':')
+ ? logFile
+ : path.join(forever.config.get('root'), logFile || (uid || 'forever') + '.log');
+};
+
+//
+// ### function pidFilePath (pidFile)
+// #### @logFile {string} Pid file path
+// Determines the full pid file path name
+//
+forever.pidFilePath = function (pidFile) {
+ return pidFile && (pidFile[0] === '/' || pidFile[1] === ':')
+ ? pidFile
+ : path.join(forever.config.get('pidPath'), pidFile);
+};
+
+//
+// ### @function logEvents (monitor)
+// #### @monitor {forever.Monitor} Monitor to log events for
+// Logs important restart and error events to `console.error`
+//
+forever.logEvents = function (monitor) {
+ monitor.on('watch:error', function (info) {
+ forever.out.error(info.message);
+ forever.out.error(info.error);
+ });
+
+ monitor.on('watch:restart', function (info) {
+ forever.out.error('restarting script because ' + info.file + ' changed')
+ });
+
+ monitor.on('restart', function () {
+ forever.out.error('Forever restarting script for ' + monitor.times + ' time')
+ });
+
+ monitor.on('exit:code', function (code, signal) {
+ forever.out.error(code
+ ? 'Forever detected script exited with code: ' + code
+ : 'Forever detected script was killed by signal: ' + signal);
+ });
+};
+
+//
+// ### @columns {Object}
+// Property descriptors for accessing forever column information
+// through `forever list` and `forever.list()`
+//
+forever.columns = {
+ uid: {
+ color: 'white',
+ get: function (proc) {
+ return proc.uid;
+ }
+ },
+ command: {
+ color: 'grey',
+ get: function (proc) {
+ return (proc.command || 'node').grey;
+ }
+ },
+ script: {
+ color: 'grey',
+ get: function (proc) {
+ return [proc.file].concat(proc.options).join(' ').grey;
+ }
+ },
+ forever: {
+ color: 'white',
+ get: function (proc) {
+ return proc.foreverPid;
+ }
+ },
+ pid: {
+ color: 'white',
+ get: function (proc) {
+ return proc.pid;
+ }
+ },
+ logfile: {
+ color: 'magenta',
+ get: function (proc) {
+ return proc.logFile ? proc.logFile.magenta : '';
+ }
+ },
+ dir: {
+ color: 'grey',
+ get: function (proc) {
+ return proc.sourceDir.grey;
+ }
+ },
+ uptime: {
+ color: 'yellow',
+ get: function (proc) {
+ return timespan.fromDates(new Date(proc.ctime), new Date()).toString().yellow;
+ }
+ }
+};
diff --git a/node_modules/forever/lib/forever/cli.js b/node_modules/forever/lib/forever/cli.js
new file mode 100644
index 0000000..01107b2
--- /dev/null
+++ b/node_modules/forever/lib/forever/cli.js
@@ -0,0 +1,564 @@
+/*
+ * cli.js: Handlers for the forever CLI commands.
+ *
+ * (C) 2010 Nodejitsu Inc.
+ * MIT LICENCE
+ *
+ */
+
+var fs = require('fs'),
+ path = require('path'),
+ util = require('util'),
+ colors = require('colors'),
+ cliff = require('cliff'),
+ flatiron = require('flatiron'),
+ forever = require('../forever');
+
+var cli = exports;
+
+var help = [
+ 'usage: forever [action] [options] SCRIPT [script-options]',
+ '',
+ 'Monitors the script specified in the current process or as a daemon',
+ '',
+ 'actions:',
+ ' start Start SCRIPT as a daemon',
+ ' stop Stop the daemon SCRIPT',
+ ' stopall Stop all running forever scripts',
+ ' restart Restart the daemon SCRIPT',
+ ' restartall Restart all running forever scripts',
+ ' list List all running forever scripts',
+ ' config Lists all forever user configuration',
+ ' set <key> <val> Sets the specified forever config <key>',
+ ' clear <key> Clears the specified forever config <key>',
+ ' logs Lists log files for all forever processes',
+ ' logs <script|index> Tails the logs for <script|index>',
+ ' columns add <col> Adds the specified column to the output in `forever list`',
+ ' columns rm <col> Removed the specified column from the output in `forever list`',
+ ' columns set <cols> Set all columns for the output in `forever list`',
+ ' cleanlogs [CAREFUL] Deletes all historical forever log files',
+ '',
+ 'options:',
+ ' -m MAX Only run the specified script MAX times',
+ ' -l LOGFILE Logs the forever output to LOGFILE',
+ ' -o OUTFILE Logs stdout from child script to OUTFILE',
+ ' -e ERRFILE Logs stderr from child script to ERRFILE',
+ ' -p PATH Base path for all forever related filesĀ (pid files, etc.)',
+ ' -c COMMAND COMMAND to execute (defaults to node)',
+ ' -a, --append Append logs',
+ ' -f, --fifo Stream logs to stdout',
+ ' -n, --number Number of log lines to print',
+ ' --pidFile The pid file',
+ ' --sourceDir The source directory for which SCRIPT is relative to',
+ ' --minUptime Minimum uptime (millis) for a script to not be considered "spinning"',
+ ' --spinSleepTime Time to wait (millis) between launches of a spinning script.',
+ ' --colors --no-colors will disable output coloring',
+ ' --plain alias of --no-colors',
+ ' -d, --debug Forces forever to log debug output',
+ ' -v, --verbose Turns on the verbose messages from Forever',
+ ' -s, --silent Run the child script silencing stdout and stderr',
+ ' -w, --watch Watch for file changes',
+ ' --watchDirectory Top-level directory to watch from',
+ ' --watchIgnore To ignore pattern when watch is enabled (multiple option is allowed)',
+ ' -h, --help You\'re staring at it',
+ '',
+ '[Long Running Process]',
+ ' The forever process will continue to run outputting log messages to the console.',
+ ' ex. forever -o out.log -e err.log my-script.js',
+ '',
+ '[Daemon]',
+ ' The forever process will run as a daemon which will make the target process start',
+ ' in the background. This is extremely useful for remote starting simple node.js scripts',
+ ' without using nohup. It is recommended to run start with -o -l, & -e.',
+ ' ex. forever start -l forever.log -o out.log -e err.log my-daemon.js',
+ ' forever stop my-daemon.js',
+ ''
+];
+
+var app = flatiron.app;
+
+var actions = [
+ 'start',
+ 'stop',
+ 'stopall',
+ 'restart',
+ 'restartall',
+ 'list',
+ 'config',
+ 'set',
+ 'clear',
+ 'logs',
+ 'columns',
+ 'cleanlogs'
+];
+
+var argvOptions = cli.argvOptions = {
+ 'command': {alias: 'c'},
+ 'errFile': {alias: 'e'},
+ 'logFile': {alias: 'l'},
+ 'append': {alias: 'a', boolean: true},
+ 'fifo': {alias: 'f', boolean: false},
+ 'number': {alias: 'n'},
+ 'max': {alias: 'm'},
+ 'outFile': {alias: 'o'},
+ 'path': {alias: 'p'},
+ 'help': {alias: 'h'},
+ 'silent': {alias: 's', boolean: true},
+ 'verbose': {alias: 'v', boolean: true},
+ 'watch': {alias: 'w', boolean: true},
+ 'debug': {alias: 'd', boolean: true},
+ 'plain': {boolean: true}
+};
+
+app.use(flatiron.plugins.cli, {
+ argv: argvOptions,
+ usage: help
+});
+
+var reserved = ['root', 'pidPath'];
+
+//
+// ### @private function (file, options, callback)
+// #### @file {string} Target script to start
+// #### @options {Object} Options to start the script with
+// #### @callback {function} Continuation to respond to when complete.
+// Helper function that sets up the pathing for the specified `file`
+// then stats the appropriate files and responds.
+//
+function tryStart(file, options, callback) {
+ var fullLog, fullScript;
+
+ if (options.path) forever.config.set('root', options.path);
+ fullLog = forever.logFilePath(options.logFile, options.uid);
+ fullScript = path.join(options.sourceDir, file);
+
+ forever.stat(fullLog, fullScript, options.append, function (err) {
+ if (err) {
+ forever.log.error('Cannot start forever');
+ forever.log.error(err.message);
+ process.exit(-1);
+ }
+
+ callback();
+ });
+}
+
+//
+// ### @private function updateConfig (updater)
+// #### @updater {function} Function which updates the forever config
+// Helper which runs the specified `updater` and then saves the forever
+// config to `forever.config.get('root')`.
+//
+function updateConfig(updater) {
+ updater();
+ forever.config.save(function (err) {
+ if (err) {
+ return forever.log.error('Error saving config: ' + err.message);
+ }
+
+ cli.config();
+ var configFile = path.join(forever.config.get('root'), 'config.json');
+ forever.log.info('Forever config saved: ' + configFile.yellow);
+ });
+}
+
+//
+// ### @private function checkColumn (name)
+// #### @name {string} Column to check
+// Checks if column `name` exists
+//
+function checkColumn(name) {
+ if (!forever.columns[name]) {
+ forever.log.error('Unknown column: ' + name.magenta);
+ return false;
+ }
+ return true;
+}
+
+//
+// ### function getOptions (file)
+// #### @file {string} File to run. **Optional**
+// Returns `options` object for use with `forever.start` and
+// `forever.startDaemon`
+//
+var getOptions = cli.getOptions = function (file) {
+ var options = {};
+ //
+ // First isolate options which should be passed to file
+ //
+ options.options = process.argv.splice(process.argv.indexOf(file) + 1);
+
+ //
+ // Now we have to force optimist to reparse command line options because
+ // we've removed some before.
+ //
+ app.config.stores.argv.store = {};
+ app.config.use('argv', argvOptions);
+
+ [
+ 'pidFile', 'logFile', 'errFile', 'watch', 'minUptime', 'append',
+ 'silent', 'outFile', 'max', 'command', 'path', 'spinSleepTime',
+ 'sourceDir', 'uid', 'watchDirectory', 'watchIgnore', 'killTree', 'killSignal'
+ ].forEach(function (key) {
+ options[key] = app.config.get(key);
+ });
+
+ options.watchIgnore = options.watchIgnore || [];
+ options.watchIgnorePatterns = !Array.isArray(options.watchIgnore)
+ ? options.watchIgnore
+ : [options.watchIgnore];
+
+ if (!options.minUptime) {
+ forever.log.warn('--minUptime not set. Defaulting to: 1000ms');
+ options.minUptime = 1000;
+ }
+
+ if (!options.spinSleepTime) {
+ forever.log.warn([
+ '--spinSleepTime not set. Your script',
+ 'will exit if it does not stay up for',
+ 'at least ' + options.minUptime + 'ms'
+ ].join(' '));
+ }
+
+ options.sourceDir = options.sourceDir || (file && file[0] !== '/' ? process.cwd() : '/');
+ if (options.sourceDir) {
+ options.spawnWith = {cwd: options.sourceDir};
+ }
+
+ return options;
+}
+
+//
+// ### function cleanLogs
+// Deletes all historical forever log files
+//
+app.cmd('cleanlogs', cli.cleanLogs = function () {
+ forever.log.silly('Tidying ' + forever.config.get('root'));
+ forever.cleanUp(true).on('cleanUp', function () {
+ forever.log.silly(forever.config.get('root') + ' tidied.');
+ });
+});
+
+//
+// ### function start (file)
+// #### @file {string} Location of the script to spawn with forever
+// Starts a forever process for the script located at `file` as daemon
+// process.
+//
+app.cmd(/start (.+)/, cli.startDaemon = function () {
+ var file = app.argv._[1],
+ options = getOptions(file);
+
+ forever.log.info('Forever processing file: ' + file.grey);
+ tryStart(file, options, function () {
+ forever.startDaemon(file, options);
+ });
+});
+
+//
+// ### function stop (file)
+// #### @file {string} Target forever process to stop
+// Stops the forever process specified by `file`.
+//
+app.cmd(/stop (.+)/, cli.stop = function (file) {
+ var runner = forever.stop(file, true);
+
+ runner.on('stop', function (process) {
+ forever.log.info('Forever stopped process:');
+ forever.log.data(process);
+ });
+
+ runner.on('error', function (err) {
+ forever.log.error('Forever cannot find process with index: ' + file);
+ process.exit(1);
+ });
+});
+
+//
+// ### function stopall ()
+// Stops all currently running forever processes.
+//
+app.cmd('stopall', cli.stopall = function () {
+ var runner = forever.stopAll(true);
+ runner.on('stopAll', function (processes) {
+ if (processes) {
+ forever.log.info('Forever stopped processes:');
+ processes.split('\n').forEach(function (line) {
+ forever.log.data(line);
+ });
+ }
+ else {
+ forever.log.info('No forever processes running');
+ }
+ });
+
+ runner.on('error', function () {
+ forever.log.info('No forever processes running');
+ process.exit(1);
+ });
+});
+
+//
+// ### function restartall ()
+// Restarts all currently running forever processes.
+//
+app.cmd('restartall', cli.restartAll = function () {
+ var runner = forever.restartAll(true);
+ runner.on('restartAll', function (processes) {
+ if (processes) {
+ forever.log.info('Forever restarted processes:');
+ processes.split('\n').forEach(function (line) {
+ forever.log.data(line);
+ });
+ }
+ else {
+ forever.log.info('No forever processes running');
+ }
+ });
+
+ runner.on('error', function () {
+ forever.log.info('No forever processes running');
+ });
+});
+
+//
+// ### function restart (file)
+// #### @file {string} Target process to restart
+// Restarts the forever process specified by `file`.
+//
+app.cmd(/restart (.+)/, cli.restart = function (file) {
+ var runner = forever.restart(file, true);
+ runner.on('restart', function (processes) {
+ if (processes) {
+ forever.log.info('Forever restarted process(es):');
+ processes.split('\n').forEach(function (line) {
+ forever.log.data(line);
+ });
+ }
+ else {
+ forever.log.info('No forever processes running');
+ }
+ });
+
+ runner.on('error', function (err) {
+ forever.log.error('Error restarting process: ' + file.grey);
+ forever.log.error(err.message);
+ process.exit(1);
+ });
+});
+
+//
+// ### function list ()
+// Lists all currently running forever processes.
+//
+app.cmd('list', cli.list = function () {
+ forever.list(true, function (err, processes) {
+ if (processes) {
+ forever.log.info('Forever processes running');
+ processes.split('\n').forEach(function (line) {
+ forever.log.data(line);
+ });
+ }
+ else {
+ forever.log.info('No forever processes running');
+ }
+ });
+});
+
+//
+// ### function config ()
+// Lists all of the configuration in `~/.forever/config.json`.
+//
+app.cmd('config', cli.config = function () {
+ var keys = Object.keys(forever.config.store),
+ conf = cliff.inspect(forever.config.store);
+
+ if (keys.length <= 2) {
+ conf = conf.replace(/\{\s/, '{ \n')
+ .replace(/\}/, '\n}')
+ .replace('\\033[90m', ' \\033[90m')
+ .replace(/, /ig, ',\n ');
+ }
+ else {
+ conf = conf.replace(/\n\s{4}/ig, '\n ');
+ }
+
+ conf.split('\n').forEach(function (line) {
+ forever.log.data(line);
+ });
+});
+
+//
+// ### function set (key, value)
+// #### @key {string} Key to set in forever config
+// #### @value {string} Value to set for `key`
+// Sets the specified `key` / `value` pair in the
+// forever user config.
+//
+app.cmd(/set ([\w-_]+) (.+)/, cli.set = function (key, value) {
+ updateConfig(function () {
+ forever.log.info('Setting forever config: ' + key.grey);
+ forever.config.set(key, value);
+ });
+});
+
+//
+// ### function clear (key)
+// #### @key {string} Key to remove from `~/.forever/config.json`
+// Removes the specified `key` from the forever user config.
+//
+app.cmd('clear :key', cli.clear = function (key) {
+ if (reserved.indexOf(key) !== -1) {
+ forever.log.warn('Cannot clear reserved config: ' + key.grey);
+ forever.log.warn('Use `forever set ' + key + '` instead');
+ return;
+ }
+
+ updateConfig(function () {
+ forever.log.info('Clearing forever config: ' + key.grey);
+ forever.config.clear(key);
+ });
+});
+
+//
+// ### function logs (target)
+// #### @target {string} Target script or index to list logs for
+// Displays the logs using `tail` for the specified `target`.
+//
+app.cmd('logs :index', cli.logs = function (index) {
+ var options = {
+ stream: app.argv.fifo,
+ length: app.argv.number
+ };
+
+ forever.tail(index, options, function (err, log) {
+ if (err) {
+ return forever.log.error(err.message);
+ }
+
+ forever.log.data(log.file.magenta + ':' + log.pid + ' - ' + log.line);
+
+ });
+});
+
+//
+// ### function logFiles ()
+// Display log files for all running forever processes.
+//
+app.cmd('logs', cli.logFiles = function (index) {
+ if (typeof index !== 'undefined') {
+ return;
+ }
+
+ var rows = [[' ', 'script', 'logfile']];
+ index = 0;
+
+ forever.list(false, function (err, processes) {
+ if (!processes) {
+ return forever.log.warn('No forever logfiles in ' + forever.config.get('root').magenta);
+ }
+
+ forever.log.info('Logs for running Forever processes');
+ rows = rows.concat(processes.map(function (proc) {
+ return ['[' + index++ + ']', proc.file.grey, proc.logFile.magenta];
+ }));
+
+ cliff.putRows('data', rows, ['white', 'grey', 'magenta']);
+ });
+});
+
+
+app.cmd('columns add :name', cli.addColumn = function (name) {
+ if (checkColumn(name)) {
+ var columns = forever.config.get('columns');
+
+ if (~columns.indexOf(name)) {
+ return forever.log.warn(name.magenta + ' already exists in forever');
+ }
+
+ forever.log.info('Adding column: ' + name.magenta);
+ columns.push(name);
+
+ forever.config.set('columns', columns);
+ forever.config.saveSync();
+ }
+});
+
+app.cmd('columns rm :name', cli.rmColumn = function (name) {
+ if (checkColumn(name)) {
+ var columns = forever.config.get('columns');
+
+ if (!~columns.indexOf(name)) {
+ return forever.log.warn(name.magenta + ' doesn\'t exist in forever');
+ }
+
+ forever.log.info('Removing column: ' + name.magenta);
+ columns.splice(columns.indexOf(name), 1);
+
+ forever.config.set('columns', columns);
+ forever.config.saveSync();
+ }
+});
+
+app.cmd(/columns set (.*)/, cli.setColumns = function (columns) {
+ forever.log.info('Setting columns: ' + columns.magenta);
+
+ forever.config.set('columns', columns.split(' '));
+ forever.config.saveSync();
+});
+
+//
+// ### function help ()
+// Shows help
+//
+app.cmd('help', cli.help = function () {
+ util.puts(help.join('\n'));
+});
+
+//
+// ### function start (file)
+// #### @file {string} Location of the script to spawn with forever
+// Starts a forever process for the script located at `file` as non-daemon
+// process.
+//
+// Remark: this regex matches everything. It has to be added at the end to
+// make executing other commands possible.
+//
+cli.run = function () {
+ var file = app.argv._[0],
+ options = getOptions(file);
+
+ tryStart(file, options, function () {
+ var monitor = forever.start(file, options);
+ monitor.on('start', function () {
+ forever.startServer(monitor);
+ });
+ });
+};
+
+cli.start = function () {
+ if (app.argv.v || app.argv.version) {
+ return console.log('v' + forever.version);
+ }
+
+ //
+ // Check for --no-colors/--colors and --plain option
+ //
+ if ((typeof app.argv.colors !== 'undefined' && !app.argv.colors) || app.argv.plain) {
+ colors.mode = 'none';
+ }
+
+ if (app.config.get('help')) {
+ return util.puts(help.join('\n'));
+ }
+
+ app.init(function () {
+ if (app.argv._.length && actions.indexOf(app.argv._[0]) === -1) {
+ return cli.run();
+ }
+
+ app.start();
+ });
+};
+
diff --git a/node_modules/forever/lib/forever/service/adapters/adapter.js b/node_modules/forever/lib/forever/service/adapters/adapter.js
new file mode 100644
index 0000000..bf23411
--- /dev/null
+++ b/node_modules/forever/lib/forever/service/adapters/adapter.js
@@ -0,0 +1,96 @@
+/*
+ * adapter.js: Abstract base class used by foreverd service adapters
+ *
+ * (C) 2010 Nodejitsu Inc.
+ * MIT LICENCE
+ *
+ */
+
+var Adapter = module.exports = function Adapter(service) {
+ this.service = service;
+};
+
+//
+// This should install assets to appropriate places for initialization,
+// configuration, and storage
+//
+// The script will be used on startup to load Service
+//
+// Service should listen on something that the management events
+// can respond to in full duplex
+//
+// The installed adapter should send the following events in nssocket protocol
+// to the Service and invoke methods as appropriate
+//
+Adapter.prototype.install = function install() {
+ throw new Error('not implemented');
+};
+
+//
+// This should do a rollback of install completely except for logs
+//
+Adapter.prototype.uninstall = function uninstall() {
+ throw new Error('not implemented');
+};
+
+//
+// This should call back with an array of [{file:...,options:...},] to pass to Monitors
+// this will be invoked when foreverd is created (not started)
+//
+Adapter.prototype.load = function load(callback) {
+ throw new Error('not implemented');
+};
+
+//
+// This should tell the OS to start the service
+// this will not start any applications
+// make sure the adapter is installed and sending events to foreverd's listener
+//
+Adapter.prototype.start = function start(monitors) {
+ throw new Error('not implemented');
+};
+
+//
+// This should tell the OS to start the service
+// this will not stop any applications
+// make sure the adapter is installed and sending events to foreverd's listener
+//
+Adapter.prototype.stop = function stop(monitors) {
+ throw new Error('not implemented');
+};
+
+//
+// This should tell the OS to reply with info about applications in the service
+// this will not change any applications
+// make sure the adapter is installed and sending events to foreverd's listener
+//
+Adapter.prototype.status = function status(monitors) {
+ throw new Error('not implemented');
+};
+
+//
+// This should tell the OS to restart the service
+// this will not restart any applications
+// make sure the adapter is installed and sending events to foreverd's listener
+//
+Adapter.prototype.restart = function restart(monitors) {
+ throw new Error('not implemented');
+};
+
+//
+// This should tell the OS to pause the service
+// this will prevent any addition or removal of applications
+// make sure the adapter is installed and sending events to foreverd's listener
+//
+Adapter.prototype.pause = function pause(monitors) {
+ throw new Error('not implemented');
+};
+
+//
+// This should tell the OS to resume the service
+// this will enable any addition or removal of applications
+// make sure the adapter is installed and sending events to foreverd's listener
+//
+Adapter.prototype.resume = function resume(monitors) {
+ throw new Error('not implemented');
+}; \ No newline at end of file
diff --git a/node_modules/forever/lib/forever/service/adapters/initd/index.js b/node_modules/forever/lib/forever/service/adapters/initd/index.js
new file mode 100644
index 0000000..1162d37
--- /dev/null
+++ b/node_modules/forever/lib/forever/service/adapters/initd/index.js
@@ -0,0 +1,9 @@
+/*
+ * index.js: Top-level include for the init.d foreverd service adapter.
+ *
+ * (C) 2010 Nodejitsu Inc.
+ * MIT LICENCE
+ *
+ */
+
+module.exports = require('../systemv'); \ No newline at end of file
diff --git a/node_modules/forever/lib/forever/service/adapters/systemv/foreverd b/node_modules/forever/lib/forever/service/adapters/systemv/foreverd
new file mode 100644
index 0000000..bdd9a5c
--- /dev/null
+++ b/node_modules/forever/lib/forever/service/adapters/systemv/foreverd
@@ -0,0 +1,82 @@
+#!/bin/bash
+#
+# initd-example Node init.d
+#
+# chkconfig: 345 80 20
+# description: Node init.d example
+# processname: node
+# pidfile: /var/run/initd-example.pid
+# logfile: /var/log/initd-example.log
+#
+
+# Source function library.
+. /lib/lsb/init-functions
+PATH=$PATH:/usr/local/bin
+
+NAME=foreverd
+pidfile=/var/run/$NAME.pid
+logfile=/var/log/$NAME.log
+forever_dir=/var/local/forever # Forever root directory.
+
+node=node
+foreverd=`which foreverd`
+awk=awk
+sed=sed
+
+start() {
+ echo "Starting $NAME: "
+
+ if [ "$id" = "" ]; then
+ # Launch the application
+ $foreverd -p $forever_dir run
+ else
+ echo "Instance already running"
+ fi
+ RETVAL=$?
+}
+
+restart() {
+ echo -n "Restarting $NAME: "
+ if [ "$id" != "" ]; then
+ $foreverd -p $forever_dir restart
+ RETVAL=$?
+ else
+ start
+ fi
+}
+
+stop() {
+ echo -n "Shutting down $NAME: "
+ if [ "$id" != "" ]; then
+ $foreverd -p $forever_dir stop
+ else
+ echo "Instance is not running";
+ fi
+ RETVAL=$?
+}
+
+getForeverId() {
+ local pid=$(pidofproc -p $pidfile)
+ $foreverd list -p $forever_dir | $sed -e 's/\x1b\[[0-9; ]*m//g' | $awk "\$4 == \"$pid]\" { gsub(/[\[\]]/, \"\", \$1); print \$1; }"
+}
+id=$(getForeverId)
+
+case "$1" in
+ start)
+ start
+ ;;
+ stop)
+ stop
+ ;;
+ status)
+ $foreverd -p $forever_dir list
+ ;;
+ restart)
+ restart
+ ;;
+ *)
+ echo "Usage: {start|stop|status|restart}"
+ exit 1
+ ;;
+esac
+exit $RETVAL
diff --git a/node_modules/forever/lib/forever/service/adapters/systemv/index.js b/node_modules/forever/lib/forever/service/adapters/systemv/index.js
new file mode 100644
index 0000000..2877b77
--- /dev/null
+++ b/node_modules/forever/lib/forever/service/adapters/systemv/index.js
@@ -0,0 +1,199 @@
+/*
+ * index.js: Top-level include for the systemv foreverd service adapter
+ *
+ * (C) 2010 Nodejitsu Inc.
+ * MIT LICENCE
+ *
+ */
+
+var fs = require('fs'),
+ util = require('util'),
+ path = require('path'),
+ spawn = require('child_process').spawn,
+ nssocket = require('nssocket'),
+ forever = require('../../../../forever'),
+ Adapter = require('../adapter');
+
+//
+// Classic init.d script adapter
+// Sometimes called inittab, but its origin is called systemv
+//
+var SystemVAdapter = module.exports = function SystemVAdapter(service) {
+ Adapter.call(this, service);
+ this.daemonized = false;
+};
+
+util.inherits(SystemVAdapter, Adapter);
+
+SystemVAdapter.prototype.install = function install(callback) {
+ //
+ // Copy the init.d script to the right location
+ // TODO Distribution fixes?
+ //
+ forever.config.set('root', path.join('/var', 'local', 'foreverd'));
+ var initdPath = path.join('/etc', 'init.d', 'foreverd'),
+ script,
+ target;
+
+ try {
+ fs.mkdirSync(forever.config.get('root'), '0777');
+ fs.mkdirSync(path.join(forever.config.get('root'), 'services'), '0777');
+ }
+ catch (e) {
+ if (e.code !== 'EEXIST') {
+ return callback && callback(e);
+ }
+ }
+
+ try {
+ script = fs.createReadStream(path.join(__dirname, 'foreverd'));
+ target = fs.createWriteStream(initdPath, { flags: 'w', mode: '0777' });
+
+ script.pipe(target);
+ script.on('end', function () {
+ var directories = fs.readdirSync('/etc');
+ directories.forEach(function (directory) {
+ var match = directory.match(/^rc(\d+)\.d$/),
+ killOrStart;
+
+ if (match) {
+ killOrStart = { 0: true, 1: true, 6: true }[match[1]] ? 'K' : 'S';
+
+ try {
+ fs.symlinkSync(initdPath, path.join('/etc', directory, killOrStart + '20foreverd'));
+ }
+ catch (e) {
+ if (e.code !== 'EEXIST') {
+ return callback && callback(e);
+ }
+ }
+ }
+ });
+
+ return callback && callback();
+ });
+ }
+ catch (e) {
+ if (e.code !== 'EEXIST') {
+ return callback && callback(e);
+ }
+ }
+};
+
+//
+//
+//
+SystemVAdapter.prototype.load = function load(callback) {
+ forever.config.set('root', path.join('/var', 'local', 'foreverd'));
+ var serviceFiles = fs.readdirSync(path.join(forever.config.get('root'), 'services')),
+ services = [];
+
+ if (serviceFiles.length !== 0) {
+ serviceFiles.forEach(function loadServiceFiles(serviceFile, index) {
+ var serviceFilePath = path.join(forever.config.get('root'), 'services', serviceFile),
+ service = JSON.parse(fs.readFileSync(serviceFilePath)),
+ file = service.file,
+ options = service.options;
+
+ options.minUptime = 200;
+ services.push({
+ file: service.file,
+ options: service.options
+ });
+ });
+ }
+
+ callback(services);
+};
+
+SystemVAdapter.prototype.start = function start(callback) {
+ spawn('/etc/init.d/foreverd', ['start']);
+ return callback && callback();
+};
+
+SystemVAdapter.prototype.run = function run(callback) {
+ if (this.daemonized) {
+ return callback();
+ }
+
+ var self = this,
+ pidFilePath = path.join('/var', 'run', 'foreverd.pid'),
+ logFilePath = path.join('/var', 'log', 'foreverd');
+
+ process.on('exit', function removePIDFile() {
+ try {
+ fs.unlinkSync(pidFilePath);
+ }
+ catch (err) {
+ // we are exiting anyway. this may have some noexist error already
+ }
+ });
+
+ fs.open(logFilePath, 'w+', function serviceLogOpened(err, logFile) {
+ if (err) {
+ throw err;
+ }
+
+ self.service.startServer(function () {
+ try {
+ //
+ // TODO: Create a pseudo-daemon to replace this.
+ //
+ // daemon.start(logFile);
+ // daemon.lock(pidFilePath);
+ self.daemonized = true;
+ return callback && callback();
+ }
+ catch (err) {
+ console.error(err);
+ return callback && callback(err);
+ }
+ });
+ });
+};
+
+SystemVAdapter.prototype.add = function add(file, options, callback) {
+ forever.config.set('root', path.join('/var', 'local', 'foreverd'));
+ //
+ // Add descriptor to our service list
+ // this is just a json file in $root/services/*.json
+ //
+ var filePath, service = {
+ file: file,
+ options: options || {}
+ };
+
+ options.appendLog = true;
+ filePath = path.join(forever.config.get('root'), 'services', options.uid + '.json');
+
+ fs.writeFile(filePath, JSON.stringify(service), function (err) {
+ return callback && callback(err);
+ });
+};
+
+SystemVAdapter.prototype.list = function list(callback) {
+ forever.config.set('root', path.join('/var', 'local', 'foreverd'));
+
+ var sockPath = path.join(forever.config.get('root'), 'foreverd.sock'),
+ client;
+
+ client = dnode.connect(sockPath, function onConnect(remote, client) {
+ if (callback) {
+ callback(false, remote.applications);
+ }
+
+ client.end();
+ });
+
+ client.on('error', function onError(err) {
+ if (err.code === 'ECONNREFUSED') {
+ try {
+ fs.unlinkSync(fullPath);
+ }
+ catch (ex) { }
+ return callback && callback(false, []);
+ }
+
+ return callback && callback(err);
+ });
+}; \ No newline at end of file
diff --git a/node_modules/forever/lib/forever/service/cli.js b/node_modules/forever/lib/forever/service/cli.js
new file mode 100644
index 0000000..674e15b
--- /dev/null
+++ b/node_modules/forever/lib/forever/service/cli.js
@@ -0,0 +1,102 @@
+/*
+ * cli.js: Handlers for the foreverd CLI commands.
+ *
+ * (C) 2010 Nodejitsu Inc.
+ * MIT LICENCE
+ *
+ */
+
+var utile = require('utile'),
+ flatiron = require('flatiron'),
+ optimist = require('optimist'),
+ forever = require('../../forever'),
+ Service = require('./service'),
+ argv;
+
+var cli = exports;
+
+var app = flatiron.app;
+
+app.use(flatiron.plugins.cli, {
+ usage: forever.cli.usage
+});
+
+app.config.argv().env();
+
+var service = new Service({
+ adapter: optimist.argv.adapter
+});
+
+app.cmd('install', cli.install = function () {
+ service.install(function onInstall(err) {
+ if (err) {
+ forever.log.error(err);
+ }
+ else {
+ forever.log.info('foreverd installed');
+ }
+ });
+});
+
+//TODO
+app.cmd('run', cli.run = function () {
+ service.load(function () {
+ service.run();
+ });
+});
+
+app.cmd('uninstall', cli.uninstall = function () {
+ service.uninstall();
+});
+
+app.cmd(/add (.*)/, cli.add = function (file) {
+ service.add(file, forever.cli.getOptions(file));
+});
+
+//TODO
+app.cmd(/remove (.*)/, cli.remove = function (file) {
+ service.remove(file, forever.cli.getOptions(file));
+});
+
+app.cmd('start', cli.start = function () {
+ service.start();
+});
+
+//TODO
+app.cmd('stop', cli.stop = function () {
+ service.stop();
+});
+
+app.cmd('restart', cli.restart = function () {
+ service.restart();
+});
+
+app.cmd('list', cli.list = function () {
+ service.list(function (err, applications) {
+ if (err) {
+ app.log.error('Error running command: ' + 'list'.magenta);
+ app.log.error(err.message);
+ err.stack && err.stack.split('\n').forEach(function (line) {
+ app.log.error(line);
+ })
+ return;
+ }
+
+ applications.forEach(function printApplication(application) {
+ console.log(application.monitor.uid, application.monitor.command, application.file, application.monitor.child.pid, application.monitor.logFile, application.monitor.pidFile);
+ });
+ });
+});
+
+app.cmd('pause', cli.pause = function () {
+ service.pause();
+});
+
+app.cmd('resume', cli.resume = function () {
+ service.resume();
+});
+
+cli.startCLI = function () {
+ app.start();
+};
+
diff --git a/node_modules/forever/lib/forever/service/index.js b/node_modules/forever/lib/forever/service/index.js
new file mode 100644
index 0000000..5addced
--- /dev/null
+++ b/node_modules/forever/lib/forever/service/index.js
@@ -0,0 +1,22 @@
+/*
+ * index.js: Top-level include for the `forever.service` module.
+ *
+ * (C) 2010 Nodejitsu Inc.
+ * MIT LICENCE
+ *
+ */
+
+var fs = require('fs'),
+ path = require('path');
+
+var service = exports;
+
+service.Service = require('./service');
+service.adapters = {};
+
+fs.readdirSync(path.join(__dirname, 'adapters')).forEach(function (file) {
+ file = file.replace(/\.js/, '');
+ service.adapters.__defineGetter__(file, function () {
+ return require(__dirname + '/adapters/' + file);
+ });
+});
diff --git a/node_modules/forever/lib/forever/service/service.js b/node_modules/forever/lib/forever/service/service.js
new file mode 100644
index 0000000..2cc878d
--- /dev/null
+++ b/node_modules/forever/lib/forever/service/service.js
@@ -0,0 +1,268 @@
+/*
+ * service.js: Object responsible for managing the foreverd daemon.
+ *
+ * (C) 2010 Nodejitsu Inc.
+ * MIT LICENCE
+ *
+ */
+
+var fs = require('fs'),
+ path = require('path'),
+ util = require('util'),
+ events = require('events'),
+ forever = require('../../forever'),
+ SystemVAdapter = require('./adapters/systemv');
+
+// options
+// directories {log, pid, conf, run, local}
+var Service = module.exports = function Service(options) {
+ events.EventEmitter.call(this);
+ options = options || {};
+
+ var self = this,
+ AdapterType;
+
+ this.applications = [
+ //{
+ //file:
+ //options:
+ //monitor:
+ //}
+ ];
+
+ this.servers = [];
+ if (typeof options.adapter === 'string') {
+ options.adapter = Service.adapter[options.adapter];
+ }
+
+ AdapterType = options.adapter || SystemVAdapter;
+ this.adapter = new AdapterType(this);
+};
+
+util.inherits(Service, events.EventEmitter);
+
+Service.prototype.startServer = function startServer(callback) {
+ var socket = path.join(forever.config.get('sockPath'), 'forever.sock'),
+ monitors = [],
+ self = this,
+ server;
+
+ server = dnode(this);
+ server.on('error', function onServerError() {
+ //
+ // TODO: This is really bad.
+ //
+ });
+
+ server.on('ready', function onServerReady(err) {
+ self.listen(server);
+ if (callback) {
+ if (err) {
+ callback(err);
+ }
+ else {
+ callback(null, server, socket);
+ }
+ }
+ });
+
+ server.listen(socket);
+
+ return this;
+};
+
+Service.prototype.listen = function listen(server) {
+ var dnodeServer = dnode(this);
+
+ this.servers.push(dnodeServer);
+ dnodeServer.listen(server);
+
+ return this;
+};
+
+Service.prototype.load = function load() {
+ var self = this;
+ this.adapter.load(function onLoaded(applications) {
+ applications.forEach(function startApplication(application, index) {
+ var monitor = application.monitor = new forever.Monitor(application.file, application.options);
+
+ monitor.start();
+ self.applications.push(application);
+
+ if (index === applications.length - 1) {
+ self.listen(path.join(forever.config.get('root'), 'foreverd.sock'));
+ }
+
+ self.emit('foreverd::loaded');
+ });
+ });
+ return this;
+};
+
+//
+// Function add(file, options)
+// add the application to the service manager
+// DOES NOT START THE APPLICATION
+// call's the service manager's add method
+//
+Service.prototype.add = function add(file, options, callback) {
+ if (this.paused) {
+ return callback && callback(new Error('foreverd is paused'));
+ }
+
+ this.adapter.add(file, options, callback);
+};
+
+//
+// Function remove(file, options)
+// remove the application from the service manager
+// call's the service manager's remove method
+//
+Service.prototype.remove = function remove(file, options, callback) {
+ if (this.paused) {
+ return callback(new Error('foreverd is paused'));
+ }
+
+ var applicationsToRemove = this.applications,
+ self = this,
+ optionStr,
+ fileStr;
+
+ if (file) {
+ fileStr = JSON.stringify(file);
+ applicationsToRemove = applicationsToRemove.filter(function compareFile(application) {
+ return fileStr !== JSON.stringify(application.file);
+ });
+ }
+
+ if (options) {
+ optionStr = JSON.stringify(options);
+ applicationsToRemove = applicationsToRemove.filter(function compareOptions(application) {
+ return optionStr !== JSON.stringify(application.options);
+ });
+ }
+
+ applicationsToRemove.forEach(function removeApplication(application) {
+ if (application.monitor) {
+ application.monitor.stop();
+ }
+
+ self.applications.splice(self.applications.indexOf(application), 1);
+ });
+
+ if (callback) {
+ callback();
+ }
+
+ return this;
+};
+
+//
+// Function install()
+// installs all the required to run foreverd
+// call's the service manager's install(options)
+//
+Service.prototype.install = function install(callback) {
+ this.adapter.install(callback);
+ return this;
+};
+
+//
+// Function uninstall(options)
+// uninstalls all the required to run foreverd
+// call's the service manager's uninstall(options)
+//
+Service.prototype.uninstall = function uninstall(callback) {
+ this.adapter.uninstall(callback);
+ return this;
+};
+
+//
+// Function start()
+// calls the appropriate OS functionality to start this service
+//
+Service.prototype.start = function start(callback) {
+ this.adapter.start(callback);
+ return this;
+};
+
+//
+// Function run()
+// creates monitors for all the services
+//
+Service.prototype.run = function run(callback) {
+ var self = this;
+ this.adapter.run(function adapterStarted() {
+ self.applications.forEach(function startApplication(application) {
+ application.monitor = new forever.Monitor(application.file, application.options);
+ application.monitor.start();
+ });
+
+ return callback && callback();
+ });
+
+ return this;
+};
+
+//
+// Function stop(monitors)
+//
+Service.prototype.stop = function stop(callback) {
+ var self = this;
+ this.adapter.start(function adapterStopped() {
+ self.applications.forEach(function stopApplication(application) {
+ application.monitor.stop();
+ });
+
+ return callback && callback();
+ });
+
+ return this;
+};
+
+//
+// Function restart()
+//
+Service.prototype.restart = function restart(callback) {
+ var self = this;
+ this.adapter.start(function adapterRestarted() {
+ self.applications.forEach(function restartApplication(application) {
+ application.monitor.restart();
+ });
+
+ return callback && callback();
+ });
+
+ return this;
+};
+
+//
+// Function pause()
+// disables adding / removing applications
+//
+Service.prototype.pause = function pause(callback) {
+ this.paused = true;
+ if (callback) {
+ callback();
+ }
+
+ return this;
+};
+
+//
+// Function resume()
+// reenables adding / removing applications
+//
+Service.prototype.resume = function resume(callback) {
+ this.paused = false;
+ if (callback) {
+ callback();
+ }
+
+ return this;
+};
+
+Service.prototype.list = function list(callback) {
+ this.adapter.list(callback);
+ return this;
+}; \ No newline at end of file
diff --git a/node_modules/forever/lib/forever/worker.js b/node_modules/forever/lib/forever/worker.js
new file mode 100644
index 0000000..895a6d5
--- /dev/null
+++ b/node_modules/forever/lib/forever/worker.js
@@ -0,0 +1,122 @@
+var events = require('events'),
+ fs = require('fs'),
+ path = require('path'),
+ nssocket = require('nssocket'),
+ utile = require('utile'),
+ forever = require(path.resolve(__dirname, '..', 'forever'));
+
+var Worker = exports.Worker = function (options) {
+ events.EventEmitter.call(this);
+ options || (options = {});
+
+ this.monitor = options.monitor;
+ this.sockPath = options.sockPath || forever.config.get('sockPath');
+ this.exitOnStop = options.exitOnStop === true;
+
+ this._socket = null;
+};
+
+utile.inherits(Worker, events.EventEmitter);
+
+Worker.prototype.start = function (callback) {
+ var self = this,
+ err;
+
+ if (this._socket) {
+ err = new Error("Can't start already started worker");
+ if (callback) {
+ return callback(err);
+ }
+
+ throw err;
+ }
+
+ //
+ // Defines a simple `nssocket` protocol for communication
+ // with a parent process.
+ //
+ function workerProtocol(socket) {
+ socket.on('error', function() {
+ socket.destroy();
+ })
+
+ socket.data(['ping'], function () {
+ socket.send(['pong']);
+ });
+
+ socket.data(['data'], function () {
+ socket.send(['data'], self.monitor.data);
+ });
+
+ socket.data(['spawn'], function (data) {
+ if (!data.script) {
+ return socket.send(['spawn', 'error'], { error: new Error('No script given') });
+ }
+
+ if (self.monitor) {
+ return socket.send(['spawn', 'error'], { error: new Error("Already running") });
+ }
+
+ var monitor = new (forever.Monitor)(data.script, data.options);
+ monitor.start();
+
+ monitor.on('start', function () {
+ socket.send(['spawn', 'start'], monitor.data);
+ });
+ });
+
+ socket.data(['stop'], function () {
+ self.monitor.once('stop', function () {
+ socket.send(['stop', 'ok']);
+ self.exitOnStop && process.exit();
+ });
+
+ self.monitor.stop();
+ });
+
+ socket.data(['restart'], function () {
+ self.monitor.once('restart', function () {
+ socket.send(['restart', 'ok']);
+ });
+
+ self.monitor.restart();
+ });
+ }
+
+ function findAndStart() {
+ self._socket = nssocket.createServer(workerProtocol);
+ self._socket.on('listening', function () {
+ //
+ // `listening` listener doesn't take error as the first parameter
+ //
+ self.emit('start');
+ callback && callback(null, self._sockFile);
+ });
+
+ self._socket.on('error', function (err) {
+ if (err.code === 'EADDRINUSE') {
+ return findAndStart();
+ }
+
+ callback && callback(err);
+ });
+
+ //
+ // Create a unique socket file based on the current microtime.
+ //
+ var sock = self._sockFile = path.join(self.sockPath, [
+ 'worker',
+ new Date().getTime() + utile.randomString(3),
+ 'sock'
+ ].join('.'));
+
+ self._socket.listen(sock);
+ }
+
+ //
+ // Attempt to start the server the first time
+ //
+ findAndStart();
+ return this;
+};
+