diff --git a/bin/forever b/bin/forever index 5de75ffd..0fac847d 100755 --- a/bin/forever +++ b/bin/forever @@ -13,7 +13,7 @@ if (accepts.indexOf(process.argv[2]) !== -1) { var argv = require('optimist').argv; require.paths.unshift(path.join(__dirname, '..', 'lib')); -var forever = require('forever'); +var forever = require('../lib/forever'); var help = [ "usage: forever [start | stop | restart | stopall | list | cleanlogs] [options] SCRIPT [script options]", @@ -26,16 +26,18 @@ var help = [ " list list all running forever scripts", " cleanlogs [CAREFUL] Deletes all historical forever log files", "", - " -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", - " -d SOURCEDIR The source directory for which SCRIPT is relative to", - " -p PATH Base path for all forever related filesĀ (pid files, etc.)", - " -c COMMAND COMMAND to execute (defaults to node)", - " -v, --verbose Turns on the verbose messages from Forever", - " -s, --silent Run the child script silencing stdout and stderr", - " -h, --help You're staring at it", + " -m MAX Only run the specified script MAX times", + " -l LOGFILE Logs the forever output to LOGFILE", + " -a, --append Append logs", + " -o OUTFILE Logs stdout from child script to OUTFILE", + " -e ERRFILE Logs stderr from child script to ERRFILE", + " -d SOURCEDIR The source directory for which SCRIPT is relative to", + " -p PATH Base path for all forever related filesĀ (pid files, etc.)", + " -c COMMAND COMMAND to execute (defaults to node)", + " --pidfile=PIDFILE The pid file", + " -v, --verbose Turns on the verbose messages from Forever", + " -s, --silent Run the child script silencing stdout and stderr", + " -h, --help You're staring at it", "", "[Long Running Process]", " The forever process will continue to run outputting log messages to the console.", @@ -55,9 +57,12 @@ var mappings = { 'e': 'errFile', 'd': 'sourceDir', 'l': 'logFile', + 'a': 'appendLog', + 'append': 'appendLog', 'm': 'max', 'o': 'outFile', 'p': 'path', + 'pidfile': 'pidFile', 's': 'silent', 'silent': 'silent', 'v': 'verbose', @@ -106,6 +111,11 @@ if (!options.sourceDir) { options.sourceDir = file && file[0] !== '/' ? process.cwd() : '/'; } +// Pass the source dir to spawn +options.spawnWith = { + cwd: options.sourceDir +}; + // // Configure winston for forever based on the CLI options // @@ -123,15 +133,16 @@ var config = { function tryStart (callback) { var fullLog, fullScript, uid = forever.randomString(16); options.uid = uid; - options.pidFile = 'forever' + uid + '.pid'; + options.pidFile = options.pidFile || 'forever' + uid + '.pid'; options.logFile = argv.l || 'forever' + uid + '.log'; - fullLog = path.join(forever.config.root, options.logFile); + + fullLog = forever.logFilePath(options.logFile); fullScript = path.join(options.sourceDir, file); - forever.stat(fullLog, fullScript, function (err) { + forever.stat(fullLog, fullScript, options.appendLog, function (err) { if (err) { winston.error('Cannot start forever: ' + err.message); - process.exit(0); + process.exit(-1); } callback(); diff --git a/examples/initd-example b/examples/initd-example new file mode 100644 index 00000000..e2e1818e --- /dev/null +++ b/examples/initd-example @@ -0,0 +1,96 @@ +#!/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. +. /etc/init.d/functions + +NAME=initd-example # Unique name for the application +PORT=1234 # Port (in this case the application uses process.env.PORT to set the port) +INSTANCE_DIR=/var/www/$NAME # Location of the application source +SOURCE_NAME=main.js # Name os the applcation entry point script + +user=apache +pidfile=/var/run/$NAME.pid +logfile=/var/log/$NAME.log +forever_dir=/var/run/forever # Forever root directory. + +node=node +forever=forever +awk=awk +sed=sed + +start() { + echo "Starting $NAME node instance: " + + if [ "$id" = "" ]; then + # Create the log and pid files, making sure that the target use has access to them + touch $logfile + chown $user $logfile + + touch $pidfile + chown $user $pidfile + + # Launch the application + daemon --user=$user \ + env PORT=$PORT \ + $forever start -p $forever_dir --pidfile $pidfile -l $logfile -a -d $INSTANCE_DIR $SOURCE_NAME + RETVAL=$? + else + echo "Instance already running" + RETVAL=0 + fi +} + +restart() { + echo -n "Restarting $NAME node instance : " + if [ "$id" != "" ]; then + $forever restart -p $forever_dir $id + RETVAL=$? + else + start + fi +} + +stop() { + echo -n "Shutting down $NAME node instance : " + if [ "$id" != "" ]; then + $forever stop -p $forever_dir $id + else + echo "Instance is not running"; + fi + RETVAL=$? +} + +getForeverId() { + local pid=$(pidofproc -p $pidfile) + $forever 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) + status -p ${pidfile} + ;; + restart) + restart + ;; + *) + echo "Usage: {start|stop|status|restart}" + exit 1 + ;; +esac +exit $RETVAL diff --git a/lib/forever.js b/lib/forever.js index 4805a735..8becc7ea 100644 --- a/lib/forever.js +++ b/lib/forever.js @@ -76,17 +76,31 @@ 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) { - fs.stat(logFile, function (err, stats) { - if (!err) return callback(new Error('log file ' + logFile + ' exists.')); - fs.stat(script, function (err, stats) { - if (err) return callback(new Error('script ' + script + ' does not exist.')); - callback(null); + var logAppend, + realCallback = callback; + if (arguments.length === 4) { + logAppend = callback; + realCallback = arguments[3]; + } + + fs.stat(script, function (err, stats) { + if (err) return realCallback(new Error('script ' + script + ' does not exist.')); + + if (logAppend) { + realCallback(null); + return; + } + + fs.stat(logFile, function (err, stats) { + if (!err) return realCallback(new Error('log file ' + logFile + ' exists.')); + realCallback(null); }); }); }; @@ -108,13 +122,17 @@ forever.start = function (script, options) { // Starts a script with forever as a daemon // forever.startDaemon = function (script, options) { - options.logFile = path.join(config.root, options.logFile || 'forever.log'); - options.pidFile = path.join(config.pidPath, options.pidFile); + options.logFile = forever.logFilePath(options.logFile); + options.pidFile = forever.pidFilePath(options.pidFile); var runner = new forever.Monitor(script, options); - daemon.daemonize(options.logFile, options.pidFile, function (err, pid) { + + fs.open(options.logFile, options.appendLog ? 'a+' : 'w+', function (err, fd) { if (err) return runner.emit('error', err); - + + var pid = daemon.start(fd); + daemon.lock(options.pidFile); + // // Remark: This should work, but the fd gets screwed up // with the daemon process. @@ -122,7 +140,7 @@ forever.startDaemon = function (script, options) { // process.on('exit', function () { // fs.unlinkSync(options.pidFile); // }); - + process.pid = pid; runner.start(); }); @@ -387,6 +405,32 @@ forever.randomString = function (bits) { return ret; }; +// +// ### function logFilePath (logFile) +// #### @logFile {string} Log file path +// Determines the full logfile path name +// +forever.logFilePath = function(logFile) { + if (logFile && logFile[0] === "/") { + return logFile; + } else { + return path.join(forever.config.root, logFile || "forever.log"); + } +}; + +// +// ### function pidFilePath (pidFile) +// #### @logFile {string} Pid file path +// Determines the full pid file path name +// +forever.pidFilePath = function(pidFile) { + if (pidFile && pidFile[0] === "/") { + return pidFile; + } else { + return path.join(config.pidPath, pidFile); + } +}; + // // ### function checkProcess (pid, callback) // #### @pid {string} pid of the process to check