diff options
Diffstat (limited to 'node_modules/forever/lib/forever')
9 files changed, 1464 insertions, 0 deletions
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; +}; + |
