diff options
Diffstat (limited to 'node_modules/forever/lib')
| -rw-r--r-- | node_modules/forever/lib/forever.js | 907 | ||||
| -rw-r--r-- | node_modules/forever/lib/forever/cli.js | 564 | ||||
| -rw-r--r-- | node_modules/forever/lib/forever/service/adapters/adapter.js | 96 | ||||
| -rw-r--r-- | node_modules/forever/lib/forever/service/adapters/initd/index.js | 9 | ||||
| -rw-r--r-- | node_modules/forever/lib/forever/service/adapters/systemv/foreverd | 82 | ||||
| -rw-r--r-- | node_modules/forever/lib/forever/service/adapters/systemv/index.js | 199 | ||||
| -rw-r--r-- | node_modules/forever/lib/forever/service/cli.js | 102 | ||||
| -rw-r--r-- | node_modules/forever/lib/forever/service/index.js | 22 | ||||
| -rw-r--r-- | node_modules/forever/lib/forever/service/service.js | 268 | ||||
| -rw-r--r-- | node_modules/forever/lib/forever/worker.js | 122 |
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; +}; + |
