This is a quick node.js daemonizer I put together for debugging websockets, it can be called from the command line (without arguments) to run without daemonization, and also accepts start/stop/restart/stat with verbose outputs plus –daemonize/–kill/–proc for scripted calls. The actual usage is 1 call which takes a callback but the use case included below is clustered so it’s a bit longer. No non-node.js dependencies (external modules) required.
I make no guarantee that this will work or even that you won’t destroy your system with it, use at your own risk, I’m not liable.
PHP
The –daemonize option is a bit special, as it was designed explicitly for easy use from PHP (so the daemon can be called on demand to do things like create a web socket listener then shut itself down when no longer needed.)
<?php echo shell_exec('/usr/local/bin/node /full/path/to/main.js --daemonize'); ?>
./main.js
require('./inc/daemonizer.js').daemonize(() => { const cluster = require('cluster'); const numCPUs = require('os').cpus().length; if (cluster.isMaster) { //daemon starting process.on('SIGTERM', () => { for (const id in cluster.workers) { cluster.workers[id].send('shutdown'); } process.exit(0); }); for (var i = 0; i < numCPUs; i++) { cluster.fork({ 'ID': i }); } cluster.on('exit', (worker, code, signal) => { //worker died }); } else { //worker starting process.on('message', (msg) => { if (msg === 'shutdown') { process.exit(0); } }); for (var i = 0; i < 10000000000; i++) { if ((i % 100000000) == 0) { console.log(i); } } } });
./inc/daemonizer.js
function getDaemonPID(stdout, callback) { let c, o = false; stdout = stdout.split('\n'); let pid = 0; for (let l = 0; l < stdout.length; l++) { c = 0; o = !(stdout[l][0] == ' '); for (let i = 0; i < stdout[l].length; i++) { if (o && (stdout[l][i] != ' ')) { o = !o; c++; if (c == 2) { pid = stdout[l].substr(i).split(' ')[0]; } else if ((c == 3) && (stdout[l].substr(i).split(' ')[0].toString() == '1')) { callback(pid); return; } } else if ((!o) && (stdout[l][i] == ' ')) { o = !o; } } } callback(0); } function __start(mode) { if (mode == 'restart') { console.log('Restarting...'); } var spawn = require('child_process').spawn; var d = spawn(process.execPath, [process.argv[1], '--daemon'], { detached: true, stdio: ['ignore', 'ignore', 'ignore', 'ipc'] }); d.unref(); d.disconnect(); console.log((mode == 'daemonize' ? '' : 'Started with pid: ') + d.pid.toString()); } function start(mode) { const exec = require('child_process').exec; exec('ps -xwwl | grep "' + process.argv[1] + ' --daemon" | grep -Ev "ps -xwwl " | grep -Ev "grep" | grep -Ev "daemonize"', (err, stdout, stderr) => { getDaemonPID(stdout, (pid) => { if (pid == 0) { // spawn the daemon if (mode == 'start') { console.log('Not started, starting...'); } __start(mode); } else { console.log((mode == 'daemonize' ? '' : 'Already started with pid: ') + pid.toString()); } }); }); } function stop(mode, restart) { const exec = require('child_process').exec; exec('ps -xwwl | grep "' + process.argv[1] + ' --daemon" | grep -Ev "ps -xwwl " | grep -Ev "grep" | grep -Ev "daemonize"', (err, stdout, stderr) => { let c, o = false; stdout = stdout.split('\n'); let pid = 0; let promises = []; for (let l = 0; l < stdout.length; l++) { c = 0; o = !(stdout[l][0] == ' '); for (let i = 0; i < stdout[l].length; i++) { if (o && (stdout[l][i] != ' ')) { o = !o; c++; if (c == 2) { (function (pid) { if (parseInt(pid) > 1) { promises.push(new Promise((resolve, reject) => { exec('kill ' + pid, (err, stdout, stderr) => { console.log((mode != 'kill' ? 'Stopped pid: ' : '') + pid.toString()); resolve(pid); }); })); } })(stdout[l].substr(i).split(' ')[0]); break; } } else if ((!o) && (stdout[l][i] == ' ')) { o = !o; } } } if (promises.length < 1) { console.log((mode != 'kill' ? 'Already stopped' + (restart ? ', starting' : '') + '.' : '0')); if (restart) { __start('start'); } } else if (restart) { Promise.all(promises).then((values) => { __start('restart'); }); } }); } exports.daemonize = function (daemonCode) { let startMode = ''; let stopMode = ''; if (process.argv.indexOf('--help') >= 0) { console.log('Usage: ' + process.execPath + ' ' + process.argv[1] + '[ option]'); console.log('Available options:'); console.log('\tstart Starts the daemon with verbose output.'); console.log('\t--daemonize Starts the daemon and outputs the pid, or just outputs the pid if it is already running.'); console.log('\tstop Stops the daemon with verbose output.'); console.log('\tstat Determine the state of the daemon.'); console.log('\t--kill Stops the daemon and outputs pids when complete.'); console.log('\t--proc Return the pid of the daemon if it is running or zero if it is not running.'); console.log('\trestart Restarts the daemon with verbose output.'); console.log('\t--help Outputs this set of help messages.'); return; } else if (process.argv.indexOf('start') >= 0) { startMode = 'start'; } else if (process.argv.indexOf('--daemonize') >= 0) { startMode = 'daemonize'; } else if (process.argv.indexOf('stop') >= 0) { stopMode = 'stop'; } else if (process.argv.indexOf('--kill') >= 0) { stopMode = 'kill'; } else if (process.argv.indexOf('restart') >= 0) { stopMode = 'stop'; startMode = 'restart'; } else if (process.argv.indexOf('stat') >= 0) { console.log('Checking status...'); const exec = require('child_process').exec; exec('ps -xwwl | grep "' + process.argv[1] + ' --daemon" | grep -Ev "ps -xwwl " | grep -Ev "grep" | grep -Ev "daemonize"', (err, stdout, stderr) => { getDaemonPID(stdout, (pid) => { if (pid == 0) { // spawn the daemon console.log('Not started.'); } else { console.log('Started with pid: ' + pid.toString()); } }); }); return; } else if (process.argv.indexOf('--proc') >= 0) { const exec = require('child_process').exec; exec('ps -xwwl | grep "' + process.argv[1] + ' --daemon" | grep -Ev "ps -xwwl " | grep -Ev "grep" | grep -Ev "daemonize"', (err, stdout, stderr) => { getDaemonPID(stdout, (pid) => { if (pid == 0) { console.log('0'); } else { console.log(pid.toString()); } }); }); return; } if ((startMode.length > 0) && (stopMode.length > 0) && (startMode != 'daemonize') && (stopMode != 'kill')) { console.log('Checking status...'); } if (stopMode.length > 0) { stop(stopMode, startMode == 'restart'); return; } else if (startMode.length > 0) { start(startMode); return; } daemonCode(); };