UNPKG

@martinfranek/naught

Version:

zero downtime deployment for your node.js server

279 lines (254 loc) 6.97 kB
var fs = require("fs"); var mkdirp = require("mkdirp"); var path = require("path"); var spawn = require("child_process").spawn; var net = require("net"); var assert = require("assert"); var Pend = require("pend"); var jsonSocket = require("./json_socket"); var createLog = require("./log").create; var own = {}.hasOwnProperty; exports.start = startDaemon; function startDaemon(argv) { var workerCount = parseInt(argv.shift(), 10); var socketPath = argv.shift(); var logNaughtPath = argv.shift(); var logStderrPath = argv.shift(); var logStdoutPath = argv.shift(); var maxLogSize = parseInt(argv.shift(), 10); var script = argv.shift(); var nodeArgsStr = argv.shift(); var pidFile = argv.shift(); var naughtLog = null; var stderrLog = null; var stdoutLog = null; var naughtLogBehavior = null; var stderrBehavior = null; var stdoutBehavior = null; var socket = null; var master = null; var server = null; fs.writeFile(pidFile, process.pid.toString(), function () { createLogsAndIpcServer(function (err) { if (err) { processSend({ event: "Error", value: err.message }); return; } process.on("SIGHUP", function () { handleSocketMessage({ action: "NaughtDeploy", newWorkerCount: 0, environment: {}, timeout: null, cwd: null, }); }); process.on("SIGTERM", function () { handleSocketMessage({ action: "NaughtShutdown" }); }); process.on("SIGINT", function () { handleSocketMessage({ action: "NaughtShutdown" }); }); processSend({ event: "IpcListening" }); spawnMaster(); }); }); function maybeCreateLog(logPath, cb) { // special case /dev/null - disable logging altogether if (logPath === "/dev/null") { cb(null, { behavior: "ignore", log: null, }); } else if (logPath === "-") { cb(null, { behavior: "inherit", log: null, }); } else { createLog(logPath, maxLogSize, function (err, logStream) { if (err) return cb(err); cb(null, { behavior: "pipe", log: logStream, }); }); } } function createLogs(cb) { var pend = new Pend(); pend.go(function (cb) { maybeCreateLog(logNaughtPath, function (err, results) { naughtLog = results.log; naughtLogBehavior = results.behavior; cb(err); }); }); pend.go(function (cb) { maybeCreateLog(logStderrPath, function (err, results) { stderrLog = results.log; stderrBehavior = results.behavior; cb(err); }); }); pend.go(function (cb) { maybeCreateLog(logStdoutPath, function (err, results) { stdoutLog = results.log; stdoutBehavior = results.behavior; cb(err); }); }); pend.wait(function (err) { if (err) return cb(err); if (naughtLogBehavior === "inherit") { naughtLog = process.stderr; } if (stderrBehavior === "pipe") { stderrLog.on("error", function (err) { log("Error writing to " + logStderrPath + ": " + err.stack + "\n"); }); } if (stdoutBehavior === "pipe") { stdoutLog.on("error", function (err) { log("Error writing to " + logStdoutPath + ": " + err.stack + "\n"); }); } if (naughtLogBehavior === "pipe") { naughtLog.on("error", function (err) { process.stderr.write( "Error writing to " + logNaughtPath + ": " + err.stack + "\n" ); }); } cb(); }); } function log(str) { if (naughtLog) naughtLog.write(str); } function workerCountsFromMsg(counts) { return ( "booting: " + counts.booting + ", online: " + counts.online + ", dying: " + counts.dying + ", new_online: " + counts.new_online ); } function onMessage(message) { if (naughtLog) { var str = message.event + "."; if (message.count) str += " " + workerCountsFromMsg(message.count); naughtLog.write(str + "\n"); } if (socket) jsonSocket.send(socket, message); processSend(message); } function createLogsAndIpcServer(cb) { var pend = new Pend(); createLogs(pend.hold()); mkdirp(path.dirname(socketPath), pend.hold()); pend.wait(function (err) { if (err) return cb(err); server = net.createServer(function (newSocket) { if (socket != null) { log( "Warning: Only one connection to daemon allowed. Terminating old connection.\n" ); socket.destroy(); } socket = newSocket; socket.on("error", function (err) { log("Error: ipc channel socket: " + err.stack + "\n"); }); socket.once("end", function () { socket = null; }); jsonSocket.listen(socket, function (msg) { var response = handleSocketMessage(msg); if (response) { jsonSocket.send(response); } }); }); server.listen(socketPath, cb); }); } function spawnMaster() { var nodeArgs = splitCmdLine(nodeArgsStr); var stdoutValue = stdoutBehavior === "inherit" ? process.stdout : stdoutBehavior; var stderrValue = stderrBehavior === "inherit" ? process.stderr : stderrBehavior; master = spawn( process.execPath, nodeArgs .concat([path.join(__dirname, "master.js"), workerCount, script]) .concat(argv), { env: process.env, stdio: [process.stdin, stdoutValue, stderrValue, "ipc"], cwd: process.cwd(), } ); master.on("message", onMessage); if (stdoutBehavior === "pipe") { master.stdout.pipe(stdoutLog, { end: false }); } if (stderrBehavior === "pipe") { master.stderr.pipe(stderrLog, { end: false }); } master.on("close", function () { onMessage({ event: "Shutdown", count: { booting: 0, online: 0, dying: 0, new_online: 0, }, }); try { fs.unlinkSync(pidFile); } catch (e) {} server.close(); }); } function handleSocketMessage(msg) { if (master != null) { if (msg.action === "NaughtDeploy") { extend(process.env, msg.environment); } master.send(msg); return null; } else { processSend({ event: "Error", value: "StillBooting" }); return { event: "ErrorStillBooting", }; } } } function splitCmdLine(str) { if (str.length === 0) { return []; } else { return str.split(/\s+/); } } function extend(obj, src) { for (var key in src) { if (own.call(src, key)) obj[key] = src[key]; } return obj; } function processSend(msg) { try { process.send(msg); } catch (err) { // ignore } }