UNPKG

the-shepherd

Version:
287 lines (262 loc) 8.95 kB
// Generated by CoffeeScript 2.5.1 (function() { //!/usr/bin/env coffee // this is the master process that maintains the herd of processes var $, Actions, Chalk, ChildProcess, Fs, Groups, Net, Output, SlimProcess, _c, basePath, carefulUnlink, cmd, configFile, die, doStart, doStatus, doStop, echo, exists, exit_soon, expandPath, handleMessage, outputFile, pidFile, readConfig, readPid, runDaemon, socketFile, started, verbose, warn; ({$, cmd, echo, warn, verbose} = require('../common')); Fs = require('fs'); Net = require('net'); Chalk = require('chalk'); Output = require('./output'); ({Groups} = require('./groups')); ({Actions} = require('../actions')); ({pidFile, socketFile, outputFile, configFile, basePath, expandPath, readPid, carefulUnlink} = require('../files')); ({readConfig} = require('../util/config')); ChildProcess = require('child_process'); SlimProcess = require('../util/process-slim'); exit_soon = (n, ms = 200) => { return setTimeout((() => { verbose(`Calling process.exit(${n}), after delay of ${ms}ms`); return process.exit(n); }), ms); }; die = function(...msg) { console.error(...msg); return exit_soon(1); }; handleMessage = function(msg, client, cb) { var g, i, ref; if ('auto' in msg) { if (Groups.has(msg.auto)) { msg.g = msg.auto; } else { [g, i] = msg.auto.split('-'); if (Groups.has(g)) { msg.i = g + "-" + parseInt(i, 10); } else { msg.g = msg.auto; } } } return (ref = Actions[msg.c]) != null ? typeof ref.onMessage === "function" ? ref.onMessage(msg, client, cb) : void 0 : void 0; }; exists = function(path) { var stat; try { return (stat = Fs.statSync(expandPath(path))).isFile() || stat.isSocket(); } catch (error) { return false; } }; doStop = function(exit, client, cb) { var err, pid; cb || (cb = function() {}); verbose(`Daemon.doStop(exit=${exit})`); if (!(pid = readPid())) { cb(`Expected PID file: ${pidFile}`, false); if (exit) { return exit_soon(0); } } else { verbose("daemon/index Sending stop message to all groups..."); try { Actions.stop.onMessage({}, null, function(err) { // send a stop command to all running instances verbose("daemon/index All stop messages returned..."); // then kill the pid from the pid file return SlimProcess.killAllChildren(pid, "SIGTERM", function(err) { if (err) { warn("daemon/index Error from killAllChildren", err); } return carefulUnlink(pidFile, function(err) { if (err) { warn(`daemon/index Error while unlinking PID file (${pidFile}):`, err); } return carefulUnlink(socketFile, function(err) { if (err) { warn(`daemon/index Error while unlinking unix socket (${socketFile}):`, err); } cb(null, true); if (exit) { return exit_soon(0); } }); }); }); }); } catch (error) { err = error; cb(err); } } return null; }; doStatus = function() { $.log("Socket:", socketFile, exists(socketFile) ? Chalk.green("(exists)") : Chalk.yellow("(does not exist)")); return $.log("PID File:", pidFile, exists(pidFile) ? Chalk.green("(exists)") : Chalk.yellow("(does not exist)")); }; started = false; runDaemon = () => { // in the foreground var _pidFile, _socketFile; if (!pidFile) { return die("Daemon has no pidFile configured."); } if (!socketFile) { return die("Daemon has no socketFile configured."); } _pidFile = expandPath(pidFile); _socketFile = expandPath(socketFile); if (exists(_pidFile)) { return die("Already running as PID:" + readPid()); } if (exists(_socketFile)) { return die("Socket file still exists:" + socketFile); } return Fs.writeFile(_pidFile, String(process.pid), (err) => { if (err) { return die("Failed to write pid file:", err); } return Output.setOutput(outputFile, (err) => { var j, len, ref, shutdown, sig, socket; if (err) { return die("Failed to set output file:", err); } echo("Opening master socket...", socketFile); socket = Net.Server().listen({ path: _socketFile }); socket.on('error', function(err) { echo("Failed to open local socket:", $.debugStack(err)); return exit_soon(1); }); socket.on('connection', function(client) { client.on('error', function(err) { return warn("client error:", err); }); return client.on('data', function(msg) { var start; start = Date.now(); msg = $.TNET.parse(msg.toString()); return handleMessage(msg, client, (err, acted) => { var _msg, k, v; if (err) { try { warn(msg, "caused", $.debugStack(err)); return client != null ? client.write($.TNET.stringify($.debugStack(err))) : void 0; } catch (error) {} } _msg = Object.create(null); for (k in msg) { v = msg[k]; if (v != null) { _msg[k] = v; } } return echo("Command handled:", _msg, "in", Date.now() - start, "ms"); }); }); }); shutdown = function(signal) { return function() { var progress; echo(`${signal}: Shutting down from signal...`); verbose(`${signal}: Closing master socket...`); try { socket.close(); } catch (error) { err = error; warn("Failed to close socket: ", err); } verbose(`${signal}: Stopping all groups...`); progress = $.Progress(Groups.size + 1); Groups.forEach(function(group) { return group.stop(function(err) { if (err) { return progress.reject(err); } else { return progress.finish(1); } }); }); progress.finish(1).wait(function() { verbose(`${signal}: Unlinking PID file...`); try { Fs.unlinkSync(_pidFile); } catch (error) { err = error; warn(`Failed to unlink pid file (${_pidFile}):`, err); } if (signal !== 'exit') { verbose(`${signal}: Scheduling delayed exit...`); return exit_soon(0, 2000); } }); return null; }; }; ref = ['SIGINT', 'SIGTERM', 'exit']; for (j = 0, len = ref.length; j < len; j++) { sig = ref[j]; verbose(`Signal ${sig}: attaching handler...`); process.on(sig, shutdown(sig)); } readConfig(); return started = true; }); }); }; doStart = (exit) => { // launch the daemon in the background and exit var args, child, devNull, stdio; echo("Starting daemon..."); cmd = process.argv[0]; args = [process.argv[1].replace("client/index", "daemon/index"), "daemon", `--base \"${basePath.replace(/\/.shep$/, '')}\"`, (cmd.verbose ? "--verbose" : ""), (cmd.quiet ? "--quiet" : "")]; devNull = Fs.openSync("/dev/null", 'a+'); stdio = [ devNull, devNull, process.stderr // only let stderr pass through ]; child = ChildProcess.spawn(cmd, args, { detached: true, shell: false, stdio: stdio }); child.on('error', function(err) { return console.error("Child exec error:", err); }); child.unref(); if (exit) { exit_soon(0); } return true; }; if (require.main === module) { switch (_c = cmd._[0]) { case "stop": doStop(true, null, function(err, acted) { return echo("Stopped"); }); break; case "start": doStart(true); break; case "daemon": runDaemon(); break; case "restart": doStop(false, null, function(err, acted) { return doStart(true); }); break; case "status": doStatus(); break; default: console.log("Unknown usage:", cmd); console.log("Usage: shepd <command>"); console.log("Commands: start stop restart status help"); exit_soon(0); } } true; Object.assign(module.exports, {doStart, doStop, doStatus}); }).call(this);