modules/daemon/main.js

!function DaemonModuleWrapper() {
  let core; let mod; let o = {}; const pipelines = function() {};

  /**
   * Blackrock Daemon Module
   *
   * @public
   * @class Server.Modules.Daemon
   * @augments Server.Modules.Core.Module
   * @param {Server.Modules.Core} coreObj - The Core Module Singleton
   * @return {Server.Modules.Daemon} module - The Daemon Module Singleton
   *
   * @description This is the Daemon Module of the Blackrock Application Server.
   * It allows the user to run the application server as a system daemon, as
   * opposed to being tied to the current console session. There are currently
   * no accessible methods exposed on this module.
   *
   * @author Darren Smith
   * @copyright Copyright (c) 2021 Darren Smith
   * @license Licensed under the LGPL license.
   */
  module.exports = function DaemonModule(coreObj) {
    if (mod) return mod;
    core = coreObj; mod = new core.Mod('Daemon'); o.log = core.module('logger').log;
    core.on('updateLogFn', function() {
      o.log = core.module('logger').log;
    });
    o.log('debug', 'Daemon > Initialising...', {module: mod.name}, 'MODULE_INIT');
    process.nextTick(function DaemonModuleInitPipeline() {
      pipelines.init();
    });
    return mod;
  };


  /**
   * (Internal > Pipeline [1]) Init Pipeline
   *
   * @private
   * @memberof Server.Modules.Daemon
   * @name init
   * @ignore
   * @function
   *
   * @description
   * Tbc...
   *
   * @example
   * pipelines.init();
   */
  pipelines.init = function DaemonInitPipeline() {
    // noinspection JSUnresolvedFunction
    core.lib.rxPipeline({}).pipe(

        // Fires once on server initialisation:
        pipelines.init.registerWithCLI,
        pipelines.init.listenToStart,

        // Fires on provision of daemon command from CLI:
        pipelines.init.checkNameAndInit,
        pipelines.init.setupCallbacks,
        pipelines.init.checkRoot,
        pipelines.init.startDaemon,
        pipelines.init.stopDaemon,
        pipelines.init.restartDaemon,
        pipelines.init.statusDaemon

    ).subscribe();
  };


  /**
   * (Internal > Init Pipeline Methods [1]) Register With CLI
   *
   * @private
   * @memberof Server.Modules.Daemon
   * @name registerWithCLI
   * @ignore
   * @function
   * @param {observable} source - The Source Observable
   * @return {observable} destination - The Destination Observable
   *
   * @description
   * Tbc...
   *
   * @example
   * Tbc...
   */
  pipelines.init.registerWithCLI = function DaemonIPLRegWithCLI(source) {
    // noinspection JSUnresolvedFunction
    return core.lib.rxOperator(function DaemonIPLRegWithCLIOp(observer, evt) {
      o.log('debug', 'Daemon > [0] Daemon registering with CLI...', {module: mod.name},
          'MODULE_REGISTER_WITH_CLI');
      core.module.isLoaded('cli').then(function DaemonIPLCLIIsModLoaded(cliMod) {
        cliMod.register([
          {'cmd': 'start', 'params': '\t\t', 'info': 'Starts the daemon server',
            'fn': function DaemonIPLCLIRegOne(params) {
            core.emit('DAEMON_INIT_DAEMON', {'command': 'start', 'params': params});
          }},
          {'cmd': 'stop', 'params': '\t\t', 'info': 'Stops the daemon server',
            'fn': function DaemonIPLCLIRegTwo(params) {
            core.emit('DAEMON_INIT_DAEMON', {'command': 'stop', 'params': params});
          }},
          {'cmd': 'status', 'params': '\t\t', 'info': 'Gets the status of the daemon server',
            'fn': function DaemonIPLCLIRegThree(params) {
            core.emit('DAEMON_INIT_DAEMON', {'command': 'status', 'params': params});
          }},
          {'cmd': 'restart', 'params': '\t', 'info': 'Restarts the daemon server',
            'fn': function DaemonIPLCLIRegFour(params) {
            core.emit('DAEMON_INIT_DAEMON', {'command': 'restart', 'params': params});
          }},
        ]);
      }).catch(function DaemonIPLCLIRegFail(err) {
        o.log('error',
            'Daemon > Failed to register with CLI - CLI module not loaded',
            {module: mod.name, error: err}, 'CLI_MOD_NOT_LOADED');
      });
      observer.next(evt);
    }, source);
  };

  /**
   * (Internal > Init Pipeline Methods [2]) Listen to Start Endpoint
   *
   * @private
   * @memberof Server.Modules.Daemon
   * @name listenToStart
   * @ignore
   * @function
   * @param {observable} source - The Source Observable
   * @return {observable} destination - The Destination Observable
   *
   * @description
   * Tbc...
   *
   * @example
   * Tbc...
   */
  pipelines.init.listenToStart = function DaemonIPLListenToStart(source) {
    // noinspection JSUnresolvedFunction
    return core.lib.rxOperator(function DaemonIPLListenToStartOp(observer, evt) {
      o.log('debug',
          'Daemon > [1a] Listener created for \'DAEMON_INIT_DAEMON\' event',
          {module: mod.name}, 'DAEMON_LISTENER_CREATED');
      core.on('DAEMON_INIT_DAEMON', function DaemonIPLListenToStartDaemonCb(daemonParams) {
        core.stopActivation = true;
        o.log('debug',
            'Daemon > [1b] \'DAEMON_INIT_DAEMON\' Event Received',
            {module: mod.name}, 'DAEMON_LISTENER_EVT_RECEIVED');
        evt.name = core.pkg().name;
        evt.isChildProcess = process.send;
        evt.command = daemonParams.command;
        if (!process.send) {
          observer.next(evt);
        } else {
          o.log('fatal',
              'Daemon > Initiated, but running in daemon mode. Terminating...',
              {module: mod.name}, 'DAEMON_RUNNING_IN_DAEMON_MODE_TERM');
        }
      });
    }, source);
  };

  /**
   * (Internal > Init Pipeline Methods [3]) Check Daemon Name And Init Daemon
   *
   * @private
   * @memberof Server.Modules.Daemon
   * @name checkNameAndInit
   * @ignore
   * @function
   * @param {observable} source - The Source Observable
   * @return {observable} destination - The Destination Observable
   *
   * @description
   * Tbc...
   *
   * @example
   * Tbc...
   */
  pipelines.init.checkNameAndInit = function DaemonIPLCheckNameAndInit(source) {
    // noinspection JSUnresolvedFunction
    return core.lib.rxOperator(function DaemonIPLCheckNameAndInitOp(observer, evt) {
      if (!evt.name) {
        o.log('error',
            'Daemon > Name not passed correctly to daemon module',
            {module: mod.name}, 'DAEMON_INCORRECT_NAME');
        process.exit();
      }
      o.daemonize = require('./_support/daemonize');
      const strippedName = evt.name.replace(/-/g, '');
      evt.daemon = o.daemonize.setup({
        main: '../../../' + core.pkg().main,
        name: strippedName,
        pidfile: '/var/run/' + strippedName + '.pid',
        cwd: process.cwd(),
        silent: true,
      });
      o.log('debug',
          'Daemon > [2] Daemon Name Checked & Daemon Initialised',
          {module: mod.name}, 'DAEMON_NAME_CHECKED_AND_INIT');
      observer.next(evt);
    }, source);
  };

  /**
   * (Internal > Init Pipeline Methods [3]) Setup Daemon Callbacks
   *
   * @private
   * @memberof Server.Modules.Daemon
   * @name setupCallbacks
   * @ignore
   * @function
   * @param {observable} source - The Source Observable
   * @return {observable} destination - The Destination Observable
   *
   * @description
   * Tbc...
   *
   * @example
   * Tbc...
   */
  pipelines.init.setupCallbacks = function DaemonIPLSetupCb(source) {
    // noinspection JSUnresolvedFunction
    return core.lib.rxOperator(function DaemonIPLSetupCbOp(observer, evt) {
      evt.daemon
          .on('starting', function DaemonIPLSetupCbOnStarting() {
            console.log('Daemon > Starting...\n');
          })
          .on('started', function DaemonIPLSetupCbOnStarted(pid) {
            console.log('Daemon > Started. PID: ' + pid + '\n');
            process.exit();
          })
          .on('stopping', function DaemonIPLSetupCbOnStopping() {
            console.log('Daemon > Stopping...\n');
          })
          .on('running', function DaemonIPLSetupCbOnRunning(pid) {
            console.log('Daemon > Already running. PID: ' + pid + '\n');
            process.exit();
          })
          .on('notrunning', function DaemonIPLSetupCbOnNotRunning() {
            console.log('Daemon > Not running\n');
            process.exit();
          })
          .on('error', function DaemonIPLSetupCbOnError(err) {
            console.log('Daemon > Failed to start:  ' + err.message + '\n');
            process.exit();
          });
      if (evt.command === 'restart') {
        evt.daemon.on('stopped', function DaemonIPLSetupCbOnStoppedForRestart(pid) {
          console.log('Daemon > Stopped (' + pid + ')\n');
          evt.daemon.start();
        });
      } else {
        evt.daemon.on('stopped', function DaemonIPLSetupCbOnStopped(pid) {
          console.log('Daemon > Stopped(' + pid + ')\n');
          process.exit();
        });
      }
      o.log('debug',
          'Daemon > [3] Setup callbacks for daemon',
          {module: mod.name}, 'DAEMON_CALLBACKS_SETUP');
      observer.next(evt);
    }, source);
  };

  /**
   * (Internal > Init Pipeline Methods [4]) Check Root
   *
   * @private
   * @memberof Server.Modules.Daemon
   * @name checkRoot
   * @ignore
   * @function
   * @param {observable} source - The Source Observable
   * @return {observable} destination - The Destination Observable
   *
   * @description
   * Tbc...
   *
   * @example
   * Tbc...
   */
  pipelines.init.checkRoot = function DaemonIPLCheckRoot(source) {
    // noinspection JSUnresolvedFunction
    return core.lib.rxOperator(function DaemonIPLCheckRootOp(observer, evt) {
      if ((evt.command === 'start' || evt.command === 'stop' || evt.command === 'restart') && process.getuid() !== 0) {
        console.log('Daemon > Expected to run as root\n');
        process.exit();
      } else {
        observer.next(evt);
      }
    }, source);
  };

  /**
   * (Internal > Init Pipeline Methods [5]) Start Daemon
   *
   * @private
   * @memberof Server.Modules.Daemon
   * @name startDaemon
   * @ignore
   * @function
   * @param {observable} source - The Source Observable
   * @return {observable} destination - The Destination Observable
   *
   * @description
   * Tbc...
   *
   * @example
   * Tbc...
   */
  pipelines.init.startDaemon = function DaemonIPLStartDaemon(source) {
    // noinspection JSUnresolvedFunction
    return core.lib.rxOperator(function DaemonIPLStartDaemonOp(observer, evt) {
      if (evt.command === 'start') evt.daemon.start();
      observer.next(evt);
    }, source);
  };

  /**
   * (Internal > Init Pipeline Methods [6]) Stop Daemon
   *
   * @private
   * @memberof Server.Modules.Daemon
   * @name stopDaemon
   * @ignore
   * @function
   * @param {observable} source - The Source Observable
   * @return {observable} destination - The Destination Observable
   *
   * @description
   * Tbc...
   *
   * @example
   * Tbc...
   */
  pipelines.init.stopDaemon = function DaemonIPLStopDaemon(source) {
    // noinspection JSUnresolvedFunction
    return core.lib.rxOperator(function DaemonIPLStopDaemonOp(observer, evt) {
      if (evt.command === 'stop') evt.daemon.stop();
      observer.next(evt);
    }, source);
  };

  /**
   * (Internal > Init Pipeline Methods [7]) Restart Daemon
   *
   * @private
   * @memberof Server.Modules.Daemon
   * @name restartDaemon
   * @ignore
   * @function
   * @param {observable} source - The Source Observable
   * @return {observable} destination - The Destination Observable
   *
   * @description
   * Tbc...
   *
   * @example
   * Tbc...
   */
  pipelines.init.restartDaemon = function DaemonIPLRestartDaemon(source) {
    // noinspection JSUnresolvedFunction
    return core.lib.rxOperator(function DaemonIPLRestartDaemonOp(observer, evt) {
      if (evt.command === 'restart') {
        const status = evt.daemon.status();
        if (status) evt.daemon.stop();
        else evt.daemon.start();
      }
      observer.next(evt);
    }, source);
  };

  /**
   * (Internal > Init Pipeline Methods [8]) Status of Daemon
   *
   * @private
   * @memberof Server.Modules.Daemon
   * @name statusDaemon
   * @ignore
   * @function
   * @param {observable} source - The Source Observable
   * @return {observable} destination - The Destination Observable
   *
   * @description
   * Tbc...
   *
   * @example
   * Tbc...
   */
  pipelines.init.statusDaemon = function DaemonIPLStatusDaemon(source) {
    // noinspection JSUnresolvedFunction
    return core.lib.rxOperator(function DaemonIPLStatusDaemonOp(observer, evt) {
      if (evt.command === 'status') {
        if (evt.daemon.status()) {
          console.log('Daemon > Daemon is running\n');
          process.exit();
        } else {
          console.log('Daemon > Daemon is not running\n');
          process.exit();
        }
      }
      observer.next(evt);
    }, source);
  };
}();