lazy loading walnut apps
This commit is contained in:
parent
7d98aa1551
commit
59a8f5235d
|
@ -0,0 +1,30 @@
|
||||||
|
.*.sw*
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
|
||||||
|
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directory
|
||||||
|
# Commenting this out is preferred by some people, see
|
||||||
|
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git-
|
||||||
|
node_modules
|
||||||
|
|
||||||
|
# Users Environment Variables
|
||||||
|
.lock-wscript
|
|
@ -0,0 +1,88 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var http = require('http');
|
||||||
|
var escapeRe = require('escape-string-regexp');
|
||||||
|
var insecureServer;
|
||||||
|
|
||||||
|
module.exports.create = function (securePort, insecurePort, redirects) {
|
||||||
|
function redirectHttps(req, res) {
|
||||||
|
var insecureRedirects;
|
||||||
|
var host = req.headers.host || '';
|
||||||
|
var url = req.url;
|
||||||
|
|
||||||
|
// because I have domains for which I don't want to pay for SSL certs
|
||||||
|
insecureRedirects = redirects.sort(function (a, b) {
|
||||||
|
var hlen = b.from.hostname.length - a.from.hostname.length;
|
||||||
|
var plen;
|
||||||
|
if (!hlen) {
|
||||||
|
plen = b.from.path.length - a.from.path.length;
|
||||||
|
return plen;
|
||||||
|
}
|
||||||
|
return hlen;
|
||||||
|
}).forEach(function (redirect) {
|
||||||
|
var origHost = host;
|
||||||
|
// TODO if '*' === hostname[0], omit '^'
|
||||||
|
host = host.replace(
|
||||||
|
new RegExp('^' + escapeRe(redirect.from.hostname))
|
||||||
|
, redirect.to.hostname
|
||||||
|
);
|
||||||
|
if (host === origHost) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
url = url.replace(
|
||||||
|
new RegExp('^' + escapeRe(redirect.from.path))
|
||||||
|
, redirect.to.path
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
var newLocation = 'https://'
|
||||||
|
+ host.replace(/:\d+/, ':' + securePort) + url
|
||||||
|
;
|
||||||
|
|
||||||
|
var metaRedirect = ''
|
||||||
|
+ '<html>\n'
|
||||||
|
+ '<head>\n'
|
||||||
|
+ ' <style>* { background-color: white; color: white; text-decoration: none; }</style>\n'
|
||||||
|
+ ' <META http-equiv="refresh" content="0;URL=' + newLocation + '">\n'
|
||||||
|
+ '</head>\n'
|
||||||
|
+ '<body style="display: none;">\n'
|
||||||
|
+ ' <p>You requested an insecure resource. Please use this instead: \n'
|
||||||
|
+ ' <a href="' + newLocation + '">' + newLocation + '</a></p>\n'
|
||||||
|
+ '</body>\n'
|
||||||
|
+ '</html>\n'
|
||||||
|
;
|
||||||
|
|
||||||
|
// DO NOT HTTP REDIRECT
|
||||||
|
/*
|
||||||
|
res.setHeader('Location', newLocation);
|
||||||
|
res.statusCode = 302;
|
||||||
|
*/
|
||||||
|
|
||||||
|
// BAD NEWS BEARS
|
||||||
|
//
|
||||||
|
// When people are experimenting with the API and posting tutorials
|
||||||
|
// they'll use cURL and they'll forget to prefix with https://
|
||||||
|
// If we allow that, then many users will be sending private tokens
|
||||||
|
// and such with POSTs in clear text and, worse, it will work!
|
||||||
|
// To minimize this, we give browser users a mostly optimal experience,
|
||||||
|
// but people experimenting with the API get a message letting them know
|
||||||
|
// that they're doing it wrong and thus forces them to ensure they encrypt.
|
||||||
|
res.setHeader('Content-Type', 'text/html');
|
||||||
|
res.end(metaRedirect);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO localhost-only server shutdown mechanism
|
||||||
|
// that closes all sockets, waits for them to finish,
|
||||||
|
// and then hands control over completely to respawned server
|
||||||
|
|
||||||
|
//
|
||||||
|
// Redirect HTTP ot HTTPS
|
||||||
|
//
|
||||||
|
// This simply redirects from the current insecure location to the encrypted location
|
||||||
|
//
|
||||||
|
insecureServer = http.createServer();
|
||||||
|
insecureServer.on('request', redirectHttps);
|
||||||
|
insecureServer.listen(insecurePort, function(){
|
||||||
|
console.log("\nRedirecting all http traffic to https\n");
|
||||||
|
});
|
||||||
|
};
|
|
@ -0,0 +1,286 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var https = require('https');
|
||||||
|
var PromiseA = require('bluebird').Promise;
|
||||||
|
var forEachAsync = require('foreachasync').forEachAsync.create(PromiseA);
|
||||||
|
var fs = require('fs');
|
||||||
|
var path = require('path');
|
||||||
|
var crypto = require('crypto');
|
||||||
|
var connect = require('connect');
|
||||||
|
var vhost = require('vhost');
|
||||||
|
|
||||||
|
module.exports.create = function (securePort, certsPath, vhostsdir) {
|
||||||
|
// connect / express app
|
||||||
|
var app = connect();
|
||||||
|
|
||||||
|
// SSL Server
|
||||||
|
var secureContexts = {};
|
||||||
|
var dummyCerts;
|
||||||
|
var secureOpts;
|
||||||
|
var secureServer;
|
||||||
|
|
||||||
|
// the ssl domains I have
|
||||||
|
// TODO read vhosts minus
|
||||||
|
var domains = fs.readdirSync(vhostsdir).filter(function (node) {
|
||||||
|
// not a hidden or private file
|
||||||
|
return '.' !== node[0] && '_' !== node[0];
|
||||||
|
}).map(function (apppath) {
|
||||||
|
var parts = apppath.split(/[#%]+/);
|
||||||
|
var hostname = parts.shift();
|
||||||
|
var pathname = parts.join('/').replace(/\/+/g, '/').replace(/^\//, '');
|
||||||
|
|
||||||
|
return {
|
||||||
|
hostname: hostname
|
||||||
|
, pathname: pathname
|
||||||
|
, dirname: apppath
|
||||||
|
, isRoot: apppath === hostname
|
||||||
|
};
|
||||||
|
}).sort(function (a, b) {
|
||||||
|
var hlen = b.hostname.length - a.hostname.length;
|
||||||
|
var plen = b.pathname.length - a.pathname.length;
|
||||||
|
|
||||||
|
// A directory could be named example.com, example.com# example.com##
|
||||||
|
// to indicate order of preference (for API addons, for example)
|
||||||
|
var dlen = b.dirname.length - a.dirname.length;
|
||||||
|
if (!hlen) {
|
||||||
|
if (!plen) {
|
||||||
|
return dlen;
|
||||||
|
}
|
||||||
|
return plen;
|
||||||
|
}
|
||||||
|
return plen;
|
||||||
|
});
|
||||||
|
var rootDomains = domains.filter(function (domaininfo) {
|
||||||
|
return domaininfo.isRoot;
|
||||||
|
});
|
||||||
|
var domainMergeMap = {};
|
||||||
|
var domainMerged = [];
|
||||||
|
|
||||||
|
function loadDummyCerts() {
|
||||||
|
var certs = {
|
||||||
|
key: fs.readFileSync(path.join(certsPath, 'server', 'dummy-server.key.pem'))
|
||||||
|
, cert: fs.readFileSync(path.join(certsPath, 'server', 'dummy-server.crt.pem'))
|
||||||
|
, ca: fs.readdirSync(path.join(certsPath, 'ca')).filter(function (node) {
|
||||||
|
return /crt\.pem$/.test(node);
|
||||||
|
}).map(function (node) {
|
||||||
|
console.log('[Add CA]', node);
|
||||||
|
return fs.readFileSync(path.join(certsPath, 'ca', node));
|
||||||
|
})
|
||||||
|
};
|
||||||
|
return certs
|
||||||
|
}
|
||||||
|
dummyCerts = loadDummyCerts();
|
||||||
|
|
||||||
|
function createSecureContext(certs) {
|
||||||
|
// workaround for v0.12 / v1.2 backwards compat
|
||||||
|
try {
|
||||||
|
return require('tls').createSecureContext(certs);
|
||||||
|
} catch(e) {
|
||||||
|
return require('crypto').createCredentials(certs).context;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
secureContexts.dummy = createSecureContext(dummyCerts);
|
||||||
|
|
||||||
|
function getAppContext(domaininfo) {
|
||||||
|
var localApp;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// TODO live reload required modules
|
||||||
|
localApp = require(path.join(vhostsdir, domaininfo.dirname, 'app.js'));
|
||||||
|
if (localApp.create) {
|
||||||
|
// TODO read local config.yml and pass it in
|
||||||
|
// TODO pass in websocket
|
||||||
|
localApp = localApp.create(/*config*/);
|
||||||
|
if (!localApp) {
|
||||||
|
return getDummyAppContext(null, "[ERROR] no app was returned by app.js for " + domaininfo.driname);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!localApp.then) {
|
||||||
|
localApp = PromiseA.resolve(localApp);
|
||||||
|
} else {
|
||||||
|
return localApp.catch(function (e) {
|
||||||
|
return getDummyAppContext(e, "[ERROR] initialization failed during create() for " + domaininfo.dirname);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch(e) {
|
||||||
|
localApp = getDummyAppContext(e, "[ERROR] could not load app.js for " + domaininfo.dirname);
|
||||||
|
localApp = PromiseA.resolve(localApp);
|
||||||
|
|
||||||
|
return localApp;
|
||||||
|
}
|
||||||
|
|
||||||
|
return localApp;
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadDummyCerts() {
|
||||||
|
var certs = {
|
||||||
|
key: fs.readFileSync(path.join(certsPath, 'server', 'dummy-server.key.pem'))
|
||||||
|
, cert: fs.readFileSync(path.join(certsPath, 'server', 'dummy-server.crt.pem'))
|
||||||
|
, ca: fs.readdirSync(path.join(certsPath, 'ca')).filter(function (node) {
|
||||||
|
return /crt\.pem$/.test(node);
|
||||||
|
}).map(function (node) {
|
||||||
|
console.log('[log dummy ca]', node);
|
||||||
|
return fs.readFileSync(path.join(certsPath, 'ca', node));
|
||||||
|
})
|
||||||
|
};
|
||||||
|
secureContexts.dummy = crypto.createCredentials(certs).context;
|
||||||
|
dummyCerts = certs;
|
||||||
|
}
|
||||||
|
loadDummyCerts();
|
||||||
|
|
||||||
|
function loadCerts(domainname) {
|
||||||
|
// TODO make async
|
||||||
|
// WARNING: This must be SYNC until we KNOW we're not going to be running on v0.10
|
||||||
|
// Also, once we load Let's Encrypt, it's lights out for v0.10
|
||||||
|
|
||||||
|
var certsPath = path.join(vhostsdir, domainname, 'certs');
|
||||||
|
|
||||||
|
try {
|
||||||
|
var nodes = fs.readdirSync(path.join(certsPath, 'server'));
|
||||||
|
var keyNode = nodes.filter(function (node) { return /\.key\.pem$/.test(node); })[0];
|
||||||
|
var crtNode = nodes.filter(function (node) { return /\.crt\.pem$/.test(node); })[0];
|
||||||
|
var secOpts = {
|
||||||
|
key: fs.readFileSync(path.join(certsPath, 'server', keyNode))
|
||||||
|
, cert: fs.readFileSync(path.join(certsPath, 'server', crtNode))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fs.existsSync(path.join(certsPath, 'ca'))) {
|
||||||
|
secOpts.ca = fs.readdirSync(path.join(certsPath, 'ca')).filter(function (node) {
|
||||||
|
console.log('[log ca]', node);
|
||||||
|
return /crt\.pem$/.test(node);
|
||||||
|
}).map(function (node) {
|
||||||
|
return fs.readFileSync(path.join(certsPath, 'ca', node));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch(err) {
|
||||||
|
// TODO Let's Encrypt / ACME HTTPS
|
||||||
|
console.error("[ERROR] Couldn't READ HTTPS certs from '" + certsPath + "':");
|
||||||
|
// this will be a simple file-read error
|
||||||
|
console.error(err.message);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
secureContexts[domainname] = crypto.createCredentials(secOpts).context;
|
||||||
|
} catch(err) {
|
||||||
|
console.error("[ERROR] Certificates in '" + certsPath + "' could not be used:");
|
||||||
|
console.error(err);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return secureContexts[domainname];
|
||||||
|
}
|
||||||
|
|
||||||
|
app.use(function (req, res, next) {
|
||||||
|
console.log('[log] request for ' + req.headers.host + req.url);
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO load these once the server has started
|
||||||
|
// return forEachAsync(rootDomains, loadCerts);
|
||||||
|
return forEachAsync(domains, function (domaininfo) {
|
||||||
|
var appContext;
|
||||||
|
|
||||||
|
// should order and group by longest domain, then longest path
|
||||||
|
if (!domainMergeMap[domaininfo.hostname]) {
|
||||||
|
// create an connect / express app exclusive to this domain
|
||||||
|
// TODO express??
|
||||||
|
domainMergeMap[domaininfo.hostname] = { hostname: domaininfo.hostname, apps: connect() };
|
||||||
|
domainMerged.push(domainMergeMap[domaininfo.hostname]);
|
||||||
|
}
|
||||||
|
|
||||||
|
domainMergeMap[domaininfo.hostname].apps.use(
|
||||||
|
'/' + domaininfo.pathname
|
||||||
|
, function (req, res, next) {
|
||||||
|
if (appContext) {
|
||||||
|
console.log('[log] has appContext');
|
||||||
|
appContext(req, res, next);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('[log] no appContext');
|
||||||
|
getAppContext(domaininfo).then(function (localApp) {
|
||||||
|
// Note: pathname should NEVER have a leading '/' on its own
|
||||||
|
// we always add it explicitly
|
||||||
|
try {
|
||||||
|
domainMergeMap[domaininfo.hostname].apps.use('/' + domaininfo.pathname, localApp);
|
||||||
|
console.info('Loaded ' + domaininfo.hostname + ':' + securePort + '/' + domaininfo.pathname);
|
||||||
|
appContext = localApp;
|
||||||
|
appContext(req, res, next);
|
||||||
|
} catch(e) {
|
||||||
|
console.error('[ERROR] ' + domaininfo.hostname + ':' + securePort + '/' + domaininfo.pathname);
|
||||||
|
console.error(e);
|
||||||
|
res.send('{ "error": { "message": "[ERROR] could not load '
|
||||||
|
+ domaininfo.hostname + ':' + securePort + '/' + domaininfo.pathname
|
||||||
|
+ 'or default error app." } }');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return PromiseA.resolve();
|
||||||
|
}).then(function () {
|
||||||
|
domainMerged.forEach(function (domainApp) {
|
||||||
|
console.log('[log] merged ' + domainApp.hostname);
|
||||||
|
app.use(vhost(domainApp.hostname, domainApp.apps));
|
||||||
|
app.use(vhost('www.' + domainApp.hostname, domainApp.apps));
|
||||||
|
});
|
||||||
|
}).then(runServer);
|
||||||
|
|
||||||
|
function runServer() {
|
||||||
|
//provide a SNICallback when you create the options for the https server
|
||||||
|
secureOpts = {
|
||||||
|
// fallback / default dummy certs
|
||||||
|
key: dummyCerts.key
|
||||||
|
, cert: dummyCerts.cert
|
||||||
|
, ca: dummyCerts.ca
|
||||||
|
};
|
||||||
|
|
||||||
|
function addSniWorkaroundCallback() {
|
||||||
|
//SNICallback is passed the domain name, see NodeJS docs on TLS
|
||||||
|
secureOpts.SNICallback = function (domainname, cb) {
|
||||||
|
console.log('[log] SNI:', domainname);
|
||||||
|
|
||||||
|
var secureContext = secureContexts[domainname]
|
||||||
|
|| loadCerts(domainname)
|
||||||
|
|| secureContexts.dummy
|
||||||
|
//|| createSecureContext(dummyCerts)
|
||||||
|
//|| createSecureContext(loadDummyCerts())
|
||||||
|
;
|
||||||
|
|
||||||
|
if (!secureContext) {
|
||||||
|
// testing with shared dummy
|
||||||
|
//secureContext = secureContexts.dummy;
|
||||||
|
// testing passing bad argument
|
||||||
|
//secureContext = createSecureContext(loadDummyCerts);
|
||||||
|
// testing with fresh dummy
|
||||||
|
secureContext = createSecureContext(loadDummyCerts());
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('[log]', secureContext);
|
||||||
|
|
||||||
|
// workaround for v0.12 / v1.2 backwards compat bug
|
||||||
|
if ('function' === typeof cb) {
|
||||||
|
console.log('using sni callback callback');
|
||||||
|
cb(null, secureContext);
|
||||||
|
} else {
|
||||||
|
console.log('NOT using sni callback callback');
|
||||||
|
return secureContext;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
addSniWorkaroundCallback();
|
||||||
|
|
||||||
|
secureServer = https.createServer(secureOpts);
|
||||||
|
secureServer.on('request', function (req, res) {
|
||||||
|
console.log('[log] request');
|
||||||
|
app(req, res);
|
||||||
|
});
|
||||||
|
secureServer.listen(securePort, function () {
|
||||||
|
console.log("Listening on https://localhost:" + secureServer.address().port);
|
||||||
|
});
|
||||||
|
|
||||||
|
return PromiseA.resolve();
|
||||||
|
}
|
||||||
|
}
|
11
package.json
11
package.json
|
@ -37,15 +37,18 @@
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/Daplie/walnut",
|
"homepage": "https://github.com/Daplie/walnut",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bluebird": "^2.9.3",
|
"bluebird": "^2.9.9",
|
||||||
"check-ip-address": "^1.0.0",
|
"check-ip-address": "^1.1.0",
|
||||||
"cli": "^0.6.5",
|
"cli": "^0.6.5",
|
||||||
"connect": "^3.3.4",
|
"connect": "^3.3.4",
|
||||||
"escape-string-regexp": "^1.0.2",
|
"escape-string-regexp": "^1.0.2",
|
||||||
"express": "^4.11.2",
|
"express": "^4.11.2",
|
||||||
"foreachasync": "^5.0.5",
|
"foreachasync": "^5.0.5",
|
||||||
"request": "^2.51.0",
|
"nat-pmp": "0.0.3",
|
||||||
|
"node-acme": "0.0.1",
|
||||||
|
"request": "^2.53.0",
|
||||||
"ssl-root-cas": "^1.1.7",
|
"ssl-root-cas": "^1.1.7",
|
||||||
"vhost": "^3.0.0"
|
"vhost": "^3.0.0",
|
||||||
|
"xml2js": "^0.4.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
#!/bin/bash
|
||||||
|
mkdir /mnt/data
|
||||||
|
mount /dev/sda1 /mnt/data
|
||||||
|
fallocate -l 100G /mnt/data/WALNUT_ENCRYPTED.virtual.disk
|
||||||
|
apt-get update
|
||||||
|
apt-get install --yes cryptsetup
|
||||||
|
cryptsetup -y luksFormat /mnt/data/WALNUT_ENCRYPTED.virtual.disk
|
||||||
|
# you'll be asked to type YES in all caps
|
||||||
|
# Then you'll be asked for a passphrase
|
||||||
|
|
||||||
|
file /mnt/data/WALNUT_ENCRYPTED.virtual.disk
|
||||||
|
|
||||||
|
cryptsetup luksOpen /mnt/data/WALNUT_ENCRYPTED.virtual.disk WALNUT_ENCRYPTED
|
||||||
|
# you'll be asked for your passphrase
|
||||||
|
|
||||||
|
mkfs.ext4 -j /dev/mapper/WALNUT_ENCRYPTED
|
||||||
|
mkdir /mnt/WALNUT_ENCRYPTED
|
||||||
|
mount /dev/mapper/WALNUT_ENCRYPTED /mnt/WALNUT_ENCRYPTED
|
||||||
|
|
||||||
|
#pi@pi /s/walnut> time sudo mv /mnt/WALNUT_ENCRYPTED/vhosts/ /mnt/data/vhosts
|
||||||
|
#0.49user 4.02system 0:18.60elapsed 24%CPU (0avgtext+0avgdata 2812maxresident)k
|
||||||
|
#71160inputs+66152outputs (1major+455minor)pagefaults 0swaps
|
||||||
|
#pi@pi /s/walnut> time sudo rsync -a /mnt/data/vhosts /mnt/WALNUT_ENCRYPTED/vhosts
|
||||||
|
#2.75user 5.93system 0:22.03elapsed 39%CPU (0avgtext+0avgdata 5200maxresident)k
|
||||||
|
#54816inputs+66152outputs (3major+2786minor)pagefaults 0swaps
|
||||||
|
#pi@pi /s/walnut> time sudo rsync -a /mnt/data/vhosts /mnt/data/vhosts-2
|
||||||
|
#2.64user 5.98system 0:13.36elapsed 64%CPU (0avgtext+0avgdata 5364maxresident)k
|
||||||
|
#44416inputs+66152outputs (1major+3059minor)pagefaults 0swaps
|
||||||
|
#pi@pi /s/walnut> time sudo rsync -a /mnt/WALNUT_ENCRYPTED/vhosts /mnt/WALNUT_ENCRYPTED/vhosts-2
|
||||||
|
#2.48user 6.19system 0:30.81elapsed 28%CPU (0avgtext+0avgdata 5328maxresident)k
|
||||||
|
#66264inputs+66152outputs (3major+2683minor)pagefaults 0swaps
|
||||||
|
|
||||||
|
#pi@pi /s/walnut> time sudo rm -rf /mnt/data/vhosts*
|
||||||
|
#0.02user 0.04system 0:00.21elapsed 28%CPU (0avgtext+0avgdata 2804maxresident)k
|
||||||
|
#120inputs+0outputs (3major+372minor)pagefaults 0swaps
|
||||||
|
#pi@pi /s/walnut> time sudo rm -rf /mnt/WALNUT_ENCRYPTED/vhosts-2/
|
||||||
|
#0.07user 0.74system 0:00.86elapsed 93%CPU (0avgtext+0avgdata 2768maxresident)k
|
||||||
|
#0inputs+0outputs (0major+402minor)pagefaults 0swaps
|
|
@ -0,0 +1,24 @@
|
||||||
|
var acme = require("node-acme");
|
||||||
|
var acmeServer = "www.letsencrypt-demo.org";
|
||||||
|
var desiredIdentifier = "testssl.coolaj86.com";
|
||||||
|
var authzURL = "https://" + acmeServer + "/acme/new-authz";
|
||||||
|
var certURL = "https://" + acmeServer + "/acme/new-cert";
|
||||||
|
|
||||||
|
acme.getMeACertificate(authzURL, certURL, desiredIdentifier, function(x) {
|
||||||
|
console.log("Result of getMeACertificate:");
|
||||||
|
console.log(x);
|
||||||
|
/*
|
||||||
|
if (acmeServer.match(/localhost/)) {
|
||||||
|
server.close();
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
if (acmeServer.match(/localhost/)) {
|
||||||
|
// TODO for internal peers?
|
||||||
|
acme.enableLocalUsage();
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var dgram = require('dgram')
|
||||||
|
, fs = require('fs')
|
||||||
|
, socket
|
||||||
|
, ssdpPort = 1900
|
||||||
|
, sourcePort = 61900
|
||||||
|
, ssdpAddress = '239.255.255.250'
|
||||||
|
, myIface = '192.168.1.4'
|
||||||
|
, mySt = 'urn:schemas-upnp-org:device:InternetGatewayDevice:1'
|
||||||
|
;
|
||||||
|
|
||||||
|
function broadcastSsdp() {
|
||||||
|
var query
|
||||||
|
;
|
||||||
|
|
||||||
|
query = new Buffer(
|
||||||
|
'M-SEARCH * HTTP/1.1\r\n'
|
||||||
|
+ 'HOST: ' + ssdpAddress + ':' + ssdpPort + '\r\n'
|
||||||
|
+ 'MAN: "ssdp:discover"\r\n'
|
||||||
|
+ 'MX: 1\r\n'
|
||||||
|
+ 'ST: ' + mySt + '\r\n'
|
||||||
|
+ '\r\n'
|
||||||
|
);
|
||||||
|
fs.writeFileSync('upnp-search.txt', query, null);
|
||||||
|
|
||||||
|
// Send query on each socket
|
||||||
|
socket.send(query, 0, query.length, ssdpPort, ssdpAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO test interface.family === 'IPv4'
|
||||||
|
socket = dgram.createSocket('udp4');
|
||||||
|
socket.on('listening', function () {
|
||||||
|
console.log('socket ready...');
|
||||||
|
console.log(myIface + ':' + ssdpPort);
|
||||||
|
|
||||||
|
broadcastSsdp();
|
||||||
|
});
|
||||||
|
socket.on('message', function (chunk, info) {
|
||||||
|
var message = chunk.toString();
|
||||||
|
console.log('[incoming] UDP message');
|
||||||
|
console.log(message);
|
||||||
|
console.log(info);
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('binding to', sourcePort);
|
||||||
|
socket.bind(sourcePort, myIface);
|
|
@ -0,0 +1,71 @@
|
||||||
|
// characters that generally can't be used in a url: # %
|
||||||
|
// more: @ ! $ &
|
||||||
|
// Have special meaning to some FSes: : \ /
|
||||||
|
function methodA(apps) {
|
||||||
|
apps.map(function (apppath) {
|
||||||
|
var parts = apppath.split(/[#%]+/);
|
||||||
|
var hostname = parts.shift();
|
||||||
|
var pathname = parts.join('/');
|
||||||
|
return [hostname, pathname];
|
||||||
|
}).sort(function (a, b) {
|
||||||
|
var hlen = b[0].length - a[0].length;
|
||||||
|
var plen = plen = b[1].length - a[1].length;
|
||||||
|
if (!plen) {
|
||||||
|
return hlen;
|
||||||
|
}
|
||||||
|
return plen;
|
||||||
|
}).forEach(function (pair, i) {
|
||||||
|
// should print ordered by longest path, longest domain
|
||||||
|
console.log('app.use("/' + pair[1] + '", vhost("' + pair[0] + '"), app' + i + ')');
|
||||||
|
});
|
||||||
|
console.log('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
function methodB(apps) {
|
||||||
|
var mergeMap = {};
|
||||||
|
var merged = [];
|
||||||
|
|
||||||
|
apps.map(function (apppath) {
|
||||||
|
var parts = apppath.split(/[#%]+/);
|
||||||
|
var hostname = parts.shift();
|
||||||
|
var pathname = parts.join('/');
|
||||||
|
|
||||||
|
return [hostname, pathname];
|
||||||
|
}).sort(function (a, b) {
|
||||||
|
var hlen = b[0].length - a[0].length;
|
||||||
|
var plen = plen = b[1].length - a[1].length;
|
||||||
|
if (!hlen) {
|
||||||
|
return plen;
|
||||||
|
}
|
||||||
|
return plen;
|
||||||
|
}).forEach(function (pair, i) {
|
||||||
|
var apps;
|
||||||
|
var hostname = pair[0];
|
||||||
|
var pathname = pair[1];
|
||||||
|
|
||||||
|
// should order and group by longest domain, then longest path
|
||||||
|
if (!mergeMap[hostname]) {
|
||||||
|
mergeMap[hostname] = { hostname: hostname, apps: 'express()' };
|
||||||
|
merged.push(mergeMap[hostname]);
|
||||||
|
}
|
||||||
|
|
||||||
|
mergeMap[hostname].apps += '.use("/' + pathname + '", app' + i + ')';
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('\n');
|
||||||
|
merged.forEach(function (vhost) {
|
||||||
|
console.log("app.use(vhost('" + vhost.hostname + "', " + vhost.apps + ")");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var apps;
|
||||||
|
apps = [
|
||||||
|
'coolaj86.com'
|
||||||
|
, 'coolaj86.com#demos#tel-carrier'
|
||||||
|
, 'blog.coolaj86.com#demos#tel-carrier'
|
||||||
|
, 'blog.coolaj86.com%social'
|
||||||
|
, 'blog.coolaj86.com'
|
||||||
|
];
|
||||||
|
|
||||||
|
methodA(apps);
|
||||||
|
methodB(apps);
|
|
@ -0,0 +1,10 @@
|
||||||
|
var redirects = require('./redirects.json');
|
||||||
|
|
||||||
|
redirects.forEach(function (r) {
|
||||||
|
var frompath = "'" + r.from.hostname + r.from.path + "'";
|
||||||
|
var topath = "'" + r.to.hostname + r.to.path.replace(/\//g, '#') + "'";
|
||||||
|
|
||||||
|
if (frompath !== topath) {
|
||||||
|
console.log("mv", frompath, " ", topath);
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,7 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
require('netroute')
|
||||||
|
, require('netroute').getInfo()
|
||||||
|
, require('netroute').getGateway()
|
||||||
|
);
|
|
@ -0,0 +1,59 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var escapeRe = require('escape-string-regexp');
|
||||||
|
|
||||||
|
function redirect(host, url) {
|
||||||
|
var insecureRedirects;
|
||||||
|
// because I have domains for which I don't want to pay for SSL certs
|
||||||
|
insecureRedirects = [
|
||||||
|
{ "from": { "hostname": "coolaj86.org" , "path": "" }
|
||||||
|
, "to": { "hostname": "coolaj86.com", "path": "" }
|
||||||
|
}
|
||||||
|
, { "from": { "hostname": "blog.coolaj86.org" , "path": "" }
|
||||||
|
, "to": { "hostname": "coolaj86.com", "path": "" }
|
||||||
|
}
|
||||||
|
, { "from": { "hostname": "coolaj86.info" , "path": "" }
|
||||||
|
, "to": { "hostname": "coolaj86.com", "path": "" }
|
||||||
|
}
|
||||||
|
, { "from": { "hostname": "blog.coolaj86.info" , "path": "" }
|
||||||
|
, "to": { "hostname": "coolaj86.com", "path": "" }
|
||||||
|
}
|
||||||
|
, { "from": { "hostname": "blog.coolaj86.com" , "path": "" }
|
||||||
|
, "to": { "hostname": "coolaj86.com", "path": "" }
|
||||||
|
}
|
||||||
|
, { "from": { "hostname": "example.org" , "path": "/blog" }
|
||||||
|
, "to": { "hostname": "blog.example.com", "path": "" }
|
||||||
|
}
|
||||||
|
].sort(function (a, b) {
|
||||||
|
var hlen = b.from.hostname.length - a.from.hostname.length;
|
||||||
|
var plen;
|
||||||
|
if (!hlen) {
|
||||||
|
plen = b.from.path.length - a.from.path.length;
|
||||||
|
return plen;
|
||||||
|
}
|
||||||
|
return hlen;
|
||||||
|
}).forEach(function (redirect) {
|
||||||
|
// TODO if '*' === hostname[0], omit '^'
|
||||||
|
host = host.replace(
|
||||||
|
new RegExp('^' + escapeRe(redirect.from.hostname))
|
||||||
|
, redirect.to.hostname
|
||||||
|
);
|
||||||
|
url = url.replace(
|
||||||
|
new RegExp('^' + escapeRe(redirect.from.path))
|
||||||
|
, redirect.to.path
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return [host, url];
|
||||||
|
}
|
||||||
|
|
||||||
|
[
|
||||||
|
[ "blog.coolaj86.info", "/articles/awesome.html" ]
|
||||||
|
, [ "example.org", "/blog" ]
|
||||||
|
].forEach(function (pair) {
|
||||||
|
var host = pair[0];
|
||||||
|
var url = pair[1];
|
||||||
|
|
||||||
|
console.log(host, url);
|
||||||
|
console.log(redirect(host, url));
|
||||||
|
});
|
|
@ -0,0 +1,52 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var dgram = require('dgram')
|
||||||
|
, fs = require('fs')
|
||||||
|
, ssdpAddress = '239.255.255.250'
|
||||||
|
, ssdpPort = 1900
|
||||||
|
, sourceIface = '0.0.0.0' // or ip (i.e. '192.168.1.101', '10.0.1.2')
|
||||||
|
, sourcePort = 0 // chosen at random
|
||||||
|
, searchTarget = 'urn:schemas-upnp-org:device:InternetGatewayDevice:1'
|
||||||
|
, socket
|
||||||
|
;
|
||||||
|
|
||||||
|
function broadcastSsdp() {
|
||||||
|
var query
|
||||||
|
;
|
||||||
|
|
||||||
|
// described at bit.ly/1zjVJVW
|
||||||
|
query = new Buffer(
|
||||||
|
'M-SEARCH * HTTP/1.1\r\n'
|
||||||
|
+ 'HOST: ' + ssdpAddress + ':' + ssdpPort + '\r\n'
|
||||||
|
+ 'MAN: "ssdp:discover"\r\n'
|
||||||
|
+ 'MX: 1\r\n'
|
||||||
|
+ 'ST: ' + searchTarget + '\r\n'
|
||||||
|
+ '\r\n'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Send query on each socket
|
||||||
|
socket.send(query, 0, query.length, ssdpPort, ssdpAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createSocket() {
|
||||||
|
socket = dgram.createSocket('udp4');
|
||||||
|
|
||||||
|
socket.on('listening', function () {
|
||||||
|
console.log('socket ready...');
|
||||||
|
|
||||||
|
broadcastSsdp();
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('message', function (chunk, info) {
|
||||||
|
var message = chunk.toString();
|
||||||
|
|
||||||
|
console.log('[incoming] UDP message');
|
||||||
|
console.log(info);
|
||||||
|
console.log(message);
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('binding to', sourceIface + ':' + sourcePort);
|
||||||
|
socket.bind(sourcePort, sourceIface);
|
||||||
|
}
|
||||||
|
|
||||||
|
createSocket();
|
|
@ -1,277 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var https = require('https');
|
|
||||||
var http = require('http');
|
|
||||||
var PromiseA = require('bluebird').Promise;
|
|
||||||
var forEachAsync = require('foreachasync').forEachAsync.create(PromiseA);
|
|
||||||
var fs = require('fs');
|
|
||||||
var path = require('path');
|
|
||||||
var crypto = require('crypto');
|
|
||||||
var connect = require('connect');
|
|
||||||
var vhost = require('vhost');
|
|
||||||
var escapeRe = require('escape-string-regexp');
|
|
||||||
|
|
||||||
// connect / express app
|
|
||||||
var app = connect();
|
|
||||||
|
|
||||||
// SSL Server
|
|
||||||
var secureContexts = {};
|
|
||||||
var secureOpts;
|
|
||||||
var secureServer;
|
|
||||||
var securePort = process.argv[2] || 443;
|
|
||||||
|
|
||||||
// force SSL upgrade server
|
|
||||||
var insecureServer;
|
|
||||||
var insecurePort = process.argv[3] || 80;
|
|
||||||
|
|
||||||
// the ssl domains I have
|
|
||||||
// TODO read vhosts minus
|
|
||||||
var domains = fs.readdirSync(path.join(__dirname, 'vhosts')).filter(function (node) {
|
|
||||||
// not a hidden or private file
|
|
||||||
return '.' !== node[0] && '_' !== node[0];
|
|
||||||
}).map(function (apppath) {
|
|
||||||
var parts = apppath.split(/[#%]+/);
|
|
||||||
var hostname = parts.shift();
|
|
||||||
var pathname = parts.join('/').replace(/\/+/g, '/').replace(/^\//, '');
|
|
||||||
|
|
||||||
return {
|
|
||||||
hostname: hostname
|
|
||||||
, pathname: pathname
|
|
||||||
, dirname: apppath
|
|
||||||
, isRoot: apppath === hostname
|
|
||||||
};
|
|
||||||
}).sort(function (a, b) {
|
|
||||||
var hlen = b.hostname.length - a.hostname.length;
|
|
||||||
var plen = b.pathname.length - a.pathname.length;
|
|
||||||
|
|
||||||
// A directory could be named example.com, example.com# example.com##
|
|
||||||
// to indicate order of preference (for API addons, for example)
|
|
||||||
var dlen = b.dirname.length - a.dirname.length;
|
|
||||||
if (!hlen) {
|
|
||||||
if (!plen) {
|
|
||||||
return dlen;
|
|
||||||
}
|
|
||||||
return plen;
|
|
||||||
}
|
|
||||||
return plen;
|
|
||||||
});
|
|
||||||
var rootDomains = domains.filter(function (domaininfo) {
|
|
||||||
return domaininfo.isRoot;
|
|
||||||
});
|
|
||||||
var domainMergeMap = {};
|
|
||||||
var domainMerged = [];
|
|
||||||
|
|
||||||
require('ssl-root-cas')
|
|
||||||
.inject()
|
|
||||||
;
|
|
||||||
|
|
||||||
function getDummyAppContext(err, msg) {
|
|
||||||
if (err) {
|
|
||||||
console.error(err);
|
|
||||||
}
|
|
||||||
return connect().use(function (req, res) {
|
|
||||||
res.end('{ "error": { "message": "' + msg.replace(/"/g, '\\"') + '" } }');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
function getAppContext(domaininfo) {
|
|
||||||
var localApp;
|
|
||||||
|
|
||||||
try {
|
|
||||||
localApp = require(path.join(__dirname, 'vhosts', domaininfo.dirname, 'app.js'));
|
|
||||||
if (localApp.create) {
|
|
||||||
// TODO read local config.yml and pass it in
|
|
||||||
// TODO pass in websocket
|
|
||||||
localApp = localApp.create(/*config*/);
|
|
||||||
if (!localApp) {
|
|
||||||
return getDummyAppContext(null, "[ERROR] no app was returned by app.js for " + domaininfo.driname);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!localApp.then) {
|
|
||||||
localApp = PromiseA.resolve(localApp);
|
|
||||||
} else {
|
|
||||||
return localApp.catch(function (e) {
|
|
||||||
return getDummyAppContext(e, "[ERROR] initialization failed during create() for " + domaininfo.dirname);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch(e) {
|
|
||||||
localApp = getDummyAppContext(e, "[ERROR] could not load app.js for " + domaininfo.dirname);
|
|
||||||
localApp = PromiseA.resolve(localApp);
|
|
||||||
|
|
||||||
return localApp;
|
|
||||||
}
|
|
||||||
|
|
||||||
return localApp;
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadDummyCerts() {
|
|
||||||
var certsPath = path.join(__dirname, 'certs');
|
|
||||||
var certs = {
|
|
||||||
key: fs.readFileSync(path.join(certsPath, 'server', 'dummy-server.key.pem'))
|
|
||||||
, cert: fs.readFileSync(path.join(certsPath, 'server', 'dummy-server.crt.pem'))
|
|
||||||
, ca: fs.readdirSync(path.join(certsPath, 'ca')).map(function (node) {
|
|
||||||
return fs.readFileSync(path.join(certsPath, 'ca', node));
|
|
||||||
})
|
|
||||||
};
|
|
||||||
secureContexts.dummy = crypto.createCredentials(certs).context;
|
|
||||||
secureContexts.dummy.certs = certs;
|
|
||||||
}
|
|
||||||
loadDummyCerts();
|
|
||||||
|
|
||||||
function loadCerts(domaininfo) {
|
|
||||||
var certsPath = path.join(__dirname, 'vhosts', domaininfo.dirname, 'certs');
|
|
||||||
|
|
||||||
try {
|
|
||||||
var nodes = fs.readdirSync(path.join(certsPath, 'server'));
|
|
||||||
var keyNode = nodes.filter(function (node) { return /\.key\.pem$/.test(node); })[0];
|
|
||||||
var crtNode = nodes.filter(function (node) { return /\.crt\.pem$/.test(node); })[0];
|
|
||||||
|
|
||||||
secureContexts[domaininfo.hostname] = crypto.createCredentials({
|
|
||||||
key: fs.readFileSync(path.join(certsPath, 'server', keyNode))
|
|
||||||
, cert: fs.readFileSync(path.join(certsPath, 'server', crtNode))
|
|
||||||
, ca: fs.readdirSync(path.join(certsPath, 'ca')).map(function (node) {
|
|
||||||
return fs.readFileSync(path.join(certsPath, 'ca', node));
|
|
||||||
})
|
|
||||||
}).context;
|
|
||||||
} catch(err) {
|
|
||||||
// TODO Let's Encrypt / ACME HTTPS
|
|
||||||
console.error("[ERROR] Couldn't load HTTPS certs from '" + certsPath + "':");
|
|
||||||
console.error(err);
|
|
||||||
secureContexts[domaininfo.hostname] = secureContexts.dummy;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
forEachAsync(rootDomains, loadCerts).then(function () {
|
|
||||||
// fallback / default domain
|
|
||||||
/*
|
|
||||||
app.use('/', function (req, res) {
|
|
||||||
res.statusCode = 404;
|
|
||||||
res.end("<html><body><h1>Hello, World... This isn't the domain you're looking for.</h1></body></html>");
|
|
||||||
});
|
|
||||||
*/
|
|
||||||
return forEachAsync(domains, function (domaininfo) {
|
|
||||||
// should order and group by longest domain, then longest path
|
|
||||||
if (!domainMergeMap[domaininfo.hostname]) {
|
|
||||||
// create an connect / express app exclusive to this domain
|
|
||||||
// TODO express??
|
|
||||||
domainMergeMap[domaininfo.hostname] = { hostname: domaininfo.hostname, apps: connect() };
|
|
||||||
domainMerged.push(domainMergeMap[domaininfo.hostname]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return getAppContext(domaininfo).then(function (localApp) {
|
|
||||||
// Note: pathname should NEVER have a leading '/' on its own
|
|
||||||
// we always add it explicitly
|
|
||||||
try {
|
|
||||||
domainMergeMap[domaininfo.hostname].apps.use('/' + domaininfo.pathname, localApp);
|
|
||||||
console.info('Loaded ' + domaininfo.hostname + ':' + securePort + '/' + domaininfo.pathname);
|
|
||||||
} catch(e) {
|
|
||||||
console.error('[ERROR] ' + domaininfo.hostname + ':' + securePort + '/' + domaininfo.pathname);
|
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}).then(function () {
|
|
||||||
domainMerged.forEach(function (domainApp) {
|
|
||||||
app.use(vhost(domainApp.hostname, domainApp.apps));
|
|
||||||
app.use(vhost('www.' + domainApp.hostname, domainApp.apps));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}).then(runServer);
|
|
||||||
|
|
||||||
function runServer() {
|
|
||||||
//provide a SNICallback when you create the options for the https server
|
|
||||||
secureOpts = {
|
|
||||||
//SNICallback is passed the domain name, see NodeJS docs on TLS
|
|
||||||
SNICallback: function (domainname) {
|
|
||||||
//console.log('SNI:', domain);
|
|
||||||
return secureContexts[domainname] || secureContexts.dummy;
|
|
||||||
}
|
|
||||||
// fallback / default dummy certs
|
|
||||||
, key: secureContexts.dummy.certs.key
|
|
||||||
, cert: secureContexts.dummy.certs.cert
|
|
||||||
, ca: secureContexts.dummy.certs.ca
|
|
||||||
};
|
|
||||||
|
|
||||||
secureServer = https.createServer(secureOpts);
|
|
||||||
secureServer.on('request', app);
|
|
||||||
secureServer.listen(securePort, function () {
|
|
||||||
console.log("Listening on https://localhost:" + secureServer.address().port);
|
|
||||||
});
|
|
||||||
|
|
||||||
// TODO localhost-only server shutdown mechanism
|
|
||||||
// that closes all sockets, waits for them to finish,
|
|
||||||
// and then hands control over completely to respawned server
|
|
||||||
|
|
||||||
//
|
|
||||||
// Redirect HTTP ot HTTPS
|
|
||||||
//
|
|
||||||
// This simply redirects from the current insecure location to the encrypted location
|
|
||||||
//
|
|
||||||
insecureServer = http.createServer();
|
|
||||||
insecureServer.on('request', function (req, res) {
|
|
||||||
var insecureRedirects;
|
|
||||||
var host = req.headers.host || '';
|
|
||||||
var url = req.url;
|
|
||||||
|
|
||||||
// because I have domains for which I don't want to pay for SSL certs
|
|
||||||
insecureRedirects = require('./redirects.json').sort(function (a, b) {
|
|
||||||
var hlen = b.from.hostname.length - a.from.hostname.length;
|
|
||||||
var plen;
|
|
||||||
if (!hlen) {
|
|
||||||
plen = b.from.path.length - a.from.path.length;
|
|
||||||
return plen;
|
|
||||||
}
|
|
||||||
return hlen;
|
|
||||||
}).forEach(function (redirect) {
|
|
||||||
var origHost = host;
|
|
||||||
// TODO if '*' === hostname[0], omit '^'
|
|
||||||
host = host.replace(
|
|
||||||
new RegExp('^' + escapeRe(redirect.from.hostname))
|
|
||||||
, redirect.to.hostname
|
|
||||||
);
|
|
||||||
if (host === origHost) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
url = url.replace(
|
|
||||||
new RegExp('^' + escapeRe(redirect.from.path))
|
|
||||||
, redirect.to.path
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
var newLocation = 'https://'
|
|
||||||
+ host.replace(/:\d+/, ':' + securePort) + url
|
|
||||||
;
|
|
||||||
|
|
||||||
var metaRedirect = ''
|
|
||||||
+ '<html>\n'
|
|
||||||
+ '<head>\n'
|
|
||||||
+ ' <style>* { background-color: white; color: white; text-decoration: none; }</style>\n'
|
|
||||||
+ ' <META http-equiv="refresh" content="0;URL=' + newLocation + '">\n'
|
|
||||||
+ '</head>\n'
|
|
||||||
+ '<body style="display: none;">\n'
|
|
||||||
+ ' <p>You requested an insecure resource. Please use this instead: \n'
|
|
||||||
+ ' <a href="' + newLocation + '">' + newLocation + '</a></p>\n'
|
|
||||||
+ '</body>\n'
|
|
||||||
+ '</html>\n'
|
|
||||||
;
|
|
||||||
|
|
||||||
// DO NOT HTTP REDIRECT
|
|
||||||
/*
|
|
||||||
res.setHeader('Location', newLocation);
|
|
||||||
res.statusCode = 302;
|
|
||||||
*/
|
|
||||||
|
|
||||||
// BAD NEWS BEARS
|
|
||||||
//
|
|
||||||
// When people are experimenting with the API and posting tutorials
|
|
||||||
// they'll use cURL and they'll forget to prefix with https://
|
|
||||||
// If we allow that, then many users will be sending private tokens
|
|
||||||
// and such with POSTs in clear text and, worse, it will work!
|
|
||||||
// To minimize this, we give browser users a mostly optimal experience,
|
|
||||||
// but people experimenting with the API get a message letting them know
|
|
||||||
// that they're doing it wrong and thus forces them to ensure they encrypt.
|
|
||||||
res.setHeader('Content-Type', 'text/html');
|
|
||||||
res.end(metaRedirect);
|
|
||||||
});
|
|
||||||
insecureServer.listen(insecurePort, function(){
|
|
||||||
console.log("\nRedirecting all http traffic to https\n");
|
|
||||||
});
|
|
||||||
}
|
|
51
walnut.js
51
walnut.js
|
@ -1,35 +1,56 @@
|
||||||
var holepunch = require('./holepunch/beacon');
|
//var holepunch = require('./holepunch/beacon');
|
||||||
var config = require('./device.json')
|
//var config = require('./device.json');
|
||||||
|
var securePort = process.argv[2] || 443;
|
||||||
|
var insecurePort = process.argv[3] || 80;
|
||||||
|
var redirects = require('./redirects.json');
|
||||||
|
var path = require('path');
|
||||||
|
|
||||||
|
// force SSL upgrade server
|
||||||
|
var certsPath = path.join(__dirname, 'certs');
|
||||||
|
// require('ssl-root-cas').inject();
|
||||||
|
var vhostsdir = path.join(__dirname, 'vhosts');
|
||||||
|
|
||||||
|
require('./lib/insecure-server').create(securePort, insecurePort, redirects);
|
||||||
|
require('./lib/vhost-sni-server.js').create(securePort, certsPath, vhostsdir).then(function () {
|
||||||
var ports ;
|
var ports ;
|
||||||
|
|
||||||
ports = [
|
ports = [
|
||||||
{ private: 22
|
{ private: 22
|
||||||
, public: 65022
|
, public: 22
|
||||||
, protocol: 'tcp'
|
, protocol: 'tcp'
|
||||||
, ttl: 0
|
, ttl: 0
|
||||||
, test: { service: 'ssh' }
|
, test: { service: 'ssh' }
|
||||||
, testable: false
|
, testable: false
|
||||||
}
|
}
|
||||||
, { private: 65443
|
, { private: 443
|
||||||
, public: 65443
|
, public: 443
|
||||||
, protocol: 'tcp'
|
, protocol: 'tcp'
|
||||||
, ttl: 0
|
, ttl: 0
|
||||||
, test: { service: 'https' }
|
, test: { service: 'https' }
|
||||||
}
|
}
|
||||||
, { private: 65080
|
, { private: 80
|
||||||
, public: 65080
|
, public: 80
|
||||||
, protocol: 'tcp'
|
, protocol: 'tcp'
|
||||||
, ttl: 0
|
, ttl: 0
|
||||||
, test: { service: 'http' }
|
, test: { service: 'http' }
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
holepunch.run([
|
/*
|
||||||
'aj.daplie.com'
|
// TODO return a middleware
|
||||||
, 'coolaj86.com'
|
holepunch.run(require('./redirects.json').reduce(function (all, redirect) {
|
||||||
, 'prod.coolaj86.com'
|
if (!all[redirect.from.hostname]) {
|
||||||
, 'production.coolaj86.com'
|
all[redirect.from.hostname] = true;
|
||||||
], ports).then(function () {
|
all.push(redirect.from.hostname)
|
||||||
// TODO use as module
|
}
|
||||||
require('./vhost-sni-server.js');
|
if (!all[redirect.to.hostname]) {
|
||||||
|
all[redirect.to.hostname] = true;
|
||||||
|
all.push(redirect.to.hostname)
|
||||||
|
}
|
||||||
|
|
||||||
|
return all;
|
||||||
|
}, []), ports).catch(function () {
|
||||||
|
console.error("Couldn't phone home. Oh well");
|
||||||
|
});
|
||||||
|
*/
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue