Quick node.js Daemonizer For FreeBSD (Probably Linux Too)

Quick node.js Daemonizer For FreeBSD (Probably Linux Too)

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();
};

About the Author