use caddy if available
This commit is contained in:
parent
e5d9ad8386
commit
e14a6fd651
|
@ -0,0 +1,30 @@
|
|||
'use strict';
|
||||
|
||||
module.exports.create = function (port, promiseApp) {
|
||||
var PromiseA = require('bluebird');
|
||||
|
||||
return new PromiseA(function (resolve, reject) {
|
||||
var server = require('http').createServer();
|
||||
|
||||
server.on('error', reject);
|
||||
server.listen(port, 'localhost', function () {
|
||||
console.log("Listening", server.address());
|
||||
resolve(server);
|
||||
});
|
||||
|
||||
// Get up and listening as absolutely quickly as possible
|
||||
server.on('request', function (req, res) {
|
||||
// TODO move to caddy parser?
|
||||
if (/(^|\.)proxyable\./.test(req.headers.host)) {
|
||||
// device-id-12345678.proxyable.myapp.mydomain.com => myapp.mydomain.com
|
||||
// proxyable.myapp.mydomain.com => myapp.mydomain.com
|
||||
// TODO myapp.mydomain.com.proxyable.com => myapp.mydomain.com
|
||||
req.headers.host = req.headers.host.replace(/.*\.?proxyable\./, '');
|
||||
}
|
||||
|
||||
promiseApp().then(function (app) {
|
||||
app(req, res);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
|
@ -0,0 +1,127 @@
|
|||
'use strict';
|
||||
|
||||
module.exports.create = function (/*config*/) {
|
||||
var PromiseA = require('bluebird');
|
||||
var spawn = require('child_process').spawn;
|
||||
var path = require('path');
|
||||
var caddypath = '/usr/local/bin/caddy';
|
||||
var caddyfilepath = path.join(__dirname, '..', 'Caddyfile');
|
||||
var sitespath = path.join(__dirname, '..', 'sites-enabled');
|
||||
var caddy;
|
||||
var fs = require('fs');
|
||||
|
||||
|
||||
// TODO this should be expanded to include proxies a la proxydyn
|
||||
function writeCaddyfile(conf) {
|
||||
return new PromiseA(function (resolve, reject) {
|
||||
fs.readdir(sitespath, function (err, nodes) {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
|
||||
conf.domains = nodes.filter(function (node) {
|
||||
return /\./.test(node) && !/(^\.)|([\/\:\\])/.test(node);
|
||||
});
|
||||
|
||||
var contents = tplCaddyfile(conf);
|
||||
fs.writeFile(caddyfilepath, contents, 'utf8', function (err) {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function tplCaddyfile(conf) {
|
||||
var contents = [];
|
||||
|
||||
conf.domains.forEach(function (hostname) {
|
||||
var content = "";
|
||||
|
||||
content+= "https://" + hostname + " {\n"
|
||||
+ " gzip\n"
|
||||
+ " tls "
|
||||
+ "/srv/walnut/certs/live/" + hostname + "/fullchain.pem "
|
||||
+ "/srv/walnut/certs/live/" + hostname + "/privkey.pem\n"
|
||||
;
|
||||
|
||||
if (conf.locked) {
|
||||
content += " root /srv/walnut/init.public/\n";
|
||||
} else {
|
||||
content += " root /srv/walnut/sites-enabled/" + hostname + "/\n";
|
||||
}
|
||||
|
||||
content +=
|
||||
" proxy /api http://localhost:" + conf.localPort.toString() + "\n"
|
||||
// # TODO internal
|
||||
+ "}";
|
||||
|
||||
contents.push(content);
|
||||
});
|
||||
|
||||
return contents.join('\n\n');
|
||||
}
|
||||
|
||||
function spawnCaddy(conf) {
|
||||
console.log('[CADDY] start');
|
||||
return writeCaddyfile(conf).then(function () {
|
||||
if (caddy) {
|
||||
caddy.kill('SIGUSR1');
|
||||
return;
|
||||
|
||||
// TODO caddy.kill('SIGKILL'); if SIGTERM fails
|
||||
// https://github.com/mholt/caddy/issues/107
|
||||
// SIGUSR1
|
||||
|
||||
//caddy.kill('SIGTERM');
|
||||
}
|
||||
|
||||
caddy = spawn(caddypath, ['-conf', caddyfilepath], { stdio: ['ignore', 'pipe', 'pipe'] });
|
||||
caddy.stdout.on('data', function (str) {
|
||||
console.error('[Caddy]', str.toString('utf8'));
|
||||
});
|
||||
|
||||
caddy.stderr.on('data', function (errstr) {
|
||||
console.error('[Caddy]', errstr.toString('utf8'));
|
||||
});
|
||||
|
||||
caddy.on('close', function (code, signal) {
|
||||
// TODO catch if caddy doesn't exist
|
||||
console.log('[Caddy]');
|
||||
console.log(code, signal);
|
||||
caddy = null;
|
||||
setTimeout(function () {
|
||||
spawnCaddy(conf);
|
||||
}, 1 * 1000);
|
||||
});
|
||||
|
||||
return caddy;
|
||||
});
|
||||
}
|
||||
|
||||
function sighup() {
|
||||
if (caddy) {
|
||||
caddy.kill('SIGUSR1');
|
||||
return;
|
||||
}
|
||||
|
||||
// sudo kill -s SIGUSR1 `cat caddy.pid`
|
||||
fs.readFileAsync('/srv/walnut/caddy.pid', 'utf8').then(function (pid) {
|
||||
console.log('[caddy] pid', pid);
|
||||
caddy = spawn('/bin/kill', ['-s', 'SIGUSR1', pid]);
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
spawn: spawnCaddy
|
||||
, update: function (conf) {
|
||||
return writeCaddyfile(conf).then(sighup);
|
||||
}
|
||||
, sighup: sighup
|
||||
};
|
||||
};
|
88
master.js
88
master.js
|
@ -3,11 +3,15 @@
|
|||
console.log('\n\n\n[MASTER] Welcome to WALNUT!');
|
||||
|
||||
var PromiseA = require('bluebird');
|
||||
var fs = PromiseA.promisifyAll(require('fs'));
|
||||
var cluster = require('cluster');
|
||||
var numCores = require('os').cpus().length;
|
||||
var securePort = process.argv[2] || 443;
|
||||
var insecurePort = process.argv[3] || 80;
|
||||
var secureServer;
|
||||
var numForks = 0;
|
||||
var numCores = Math.min(2, require('os').cpus().length);
|
||||
var securePort = process.argv[2] || 443; // 443
|
||||
var insecurePort = process.argv[3] || 80; // 80
|
||||
var localPort = securePort;
|
||||
var caddy;
|
||||
var masterServer;
|
||||
var rootMasterKey;
|
||||
|
||||
var redirects = require('./redirects.json');
|
||||
|
@ -17,9 +21,17 @@ var path = require('path');
|
|||
var certPaths = [path.join(__dirname, 'certs', 'live')];
|
||||
var promiseServer;
|
||||
var masterApp;
|
||||
var caddyConf = { localPort: 4080, locked: true };
|
||||
|
||||
//console.log('\n.');
|
||||
|
||||
function fork() {
|
||||
if (numForks < numCores) {
|
||||
numForks += 1;
|
||||
cluster.fork();
|
||||
}
|
||||
}
|
||||
|
||||
// Note that this function will be called async, after promiseServer is returned
|
||||
// it seems like a circular dependency, but it isn't... not exactly anyway
|
||||
function promiseApps() {
|
||||
|
@ -27,17 +39,27 @@ function promiseApps() {
|
|||
return PromiseA.resolve(masterApp);
|
||||
}
|
||||
|
||||
masterApp = promiseServer.then(function (_secureServer) {
|
||||
secureServer = _secureServer;
|
||||
console.log("[MASTER] Listening on https://localhost:" + secureServer.address().port, '\n');
|
||||
masterApp = promiseServer.then(function (_masterServer) {
|
||||
masterServer = _masterServer;
|
||||
console.log("[MASTER] Listening on https://localhost:" + masterServer.address().port, '\n');
|
||||
|
||||
return require('./lib/unlock-device').create().then(function (result) {
|
||||
result.promise.then(function (_rootMasterKey) {
|
||||
var i;
|
||||
caddyConf.locked = false;
|
||||
if (caddy) {
|
||||
caddy.update(caddyConf);
|
||||
}
|
||||
rootMasterKey = _rootMasterKey;
|
||||
|
||||
if (numCores <= 2) {
|
||||
// we're on one core, stagger the remaning
|
||||
fork();
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = 0; i < numCores; i += 1) {
|
||||
cluster.fork();
|
||||
fork();
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -52,30 +74,61 @@ function promiseApps() {
|
|||
// TODO have a fallback server than can download and apply an update?
|
||||
require('./lib/insecure-server').create(securePort, insecurePort, redirects);
|
||||
//console.log('\n.');
|
||||
promiseServer = require('./lib/sni-server').create(certPaths, securePort, promiseApps);
|
||||
promiseServer = fs.existsAsync('/usr/local/bin/caddy').then(function () {
|
||||
console.log("Caddy is not present");
|
||||
// Caddy DOES NOT exist, use our node sni-server
|
||||
return require('./lib/sni-server').create(certPaths, localPort, promiseApps);
|
||||
}, function () {
|
||||
console.log("Caddy is present (assumed running)");
|
||||
// Caddy DOES exist, use our http server without sni
|
||||
localPort = caddyConf.localPort;
|
||||
caddy = require('./lib/spawn-caddy').create();
|
||||
|
||||
return caddy.spawn(caddyConf).then(function () {
|
||||
console.log("caddy has spawned");
|
||||
//return caddy.update(caddyConf).then(function () {
|
||||
// console.log("caddy is updating");
|
||||
|
||||
setInterval(function () {
|
||||
console.log('SIGUSR1 to caddy');
|
||||
return caddy.update(caddyConf);
|
||||
}, 60 * 1000);
|
||||
|
||||
return require('./lib/local-server').create(localPort, promiseApps);
|
||||
//});
|
||||
});
|
||||
});
|
||||
|
||||
//console.log('\n.');
|
||||
|
||||
cluster.on('online', function (worker) {
|
||||
console.log('[MASTER] Worker ' + worker.process.pid + ' is online');
|
||||
if (secureServer) {
|
||||
fork();
|
||||
|
||||
if (masterServer) {
|
||||
// NOTE: it's possible that this could survive idle for a while through keep-alive
|
||||
// should default to connection: close
|
||||
secureServer.close();
|
||||
secureServer = null;
|
||||
masterServer.close();
|
||||
masterServer = null;
|
||||
|
||||
setTimeout(function () {
|
||||
// TODO use `id' to find user's uid / gid and set to file
|
||||
// TODO set immediately?
|
||||
process.setgid(1000);
|
||||
process.setuid(1000);
|
||||
if (!caddy) {
|
||||
// TODO what about caddy
|
||||
process.setgid(1000);
|
||||
process.setuid(1000);
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
console.log("securePort", securePort);
|
||||
worker.send({
|
||||
type: 'init'
|
||||
, securePort: securePort
|
||||
, certPaths: certPaths
|
||||
, securePort: localPort
|
||||
, certPaths: caddy ? null : certPaths
|
||||
});
|
||||
|
||||
worker.on('message', function (msg) {
|
||||
console.log('message from worker');
|
||||
console.log(msg);
|
||||
|
@ -83,8 +136,9 @@ cluster.on('online', function (worker) {
|
|||
});
|
||||
|
||||
cluster.on('exit', function (worker, code, signal) {
|
||||
numForks -= 1;
|
||||
console.log('[MASTER] Worker ' + worker.process.pid + ' died with code: ' + code + ', and signal: ' + signal);
|
||||
cluster.fork();
|
||||
fork();
|
||||
});
|
||||
|
||||
// TODO delegate to workers
|
||||
|
|
|
@ -188,7 +188,11 @@ function init(info) {
|
|||
return workerApp;
|
||||
}
|
||||
|
||||
promiseServer = require('./lib/sni-server').create(info.certPaths, info.securePort, promiseApps);
|
||||
if (info.certPaths) {
|
||||
promiseServer = require('./lib/sni-server').create(info.certPaths, info.securePort, promiseApps);
|
||||
} else {
|
||||
promiseServer = require('./lib/local-server').create(info.securePort, promiseApps);
|
||||
}
|
||||
}
|
||||
|
||||
process.on('message', function (msg) {
|
||||
|
|
Loading…
Reference in New Issue