switch over to commander
This commit is contained in:
parent
20c7bc977c
commit
4267955286
|
@ -1,687 +1,67 @@
|
|||
#!/usr/bin/env node
|
||||
'use strict';
|
||||
|
||||
//var PromiseA = global.Promise;
|
||||
var PromiseA = require('bluebird');
|
||||
var tls = require('tls');
|
||||
var https = require('httpolyglot');
|
||||
var http = require('http');
|
||||
var path = require('path');
|
||||
var DDNS = require('ddns-cli');
|
||||
var httpPort = 80;
|
||||
var httpsPort = 443;
|
||||
var lrPort = 35729;
|
||||
var portFallback = 8443;
|
||||
var insecurePortFallback = 4080;
|
||||
|
||||
function showError(err, port) {
|
||||
if ('EACCES' === err.code) {
|
||||
console.error(err);
|
||||
console.warn("You do not have permission to use '" + port + "'.");
|
||||
console.warn("You can probably fix that by running as Administrator or root.");
|
||||
}
|
||||
else if ('EADDRINUSE' === err.code) {
|
||||
console.warn("Another server is already running on '" + port + "'.");
|
||||
console.warn("You can probably fix that by rebooting your computer (or stopping it if you know what it is).");
|
||||
}
|
||||
}
|
||||
|
||||
function createInsecureServer(port, _delete_me_, opts) {
|
||||
return new PromiseA(function (realResolve) {
|
||||
var server = http.createServer();
|
||||
|
||||
function resolve() {
|
||||
realResolve(server);
|
||||
}
|
||||
|
||||
server.on('error', function (err) {
|
||||
if (opts.errorInsecurePort || opts.manualInsecurePort) {
|
||||
showError(err, port);
|
||||
process.exit(1);
|
||||
return;
|
||||
}
|
||||
|
||||
opts.errorInsecurePort = err.toString();
|
||||
|
||||
return createInsecureServer(insecurePortFallback, null, opts).then(resolve);
|
||||
});
|
||||
|
||||
server.on('request', opts.redirectApp);
|
||||
|
||||
server.listen(port, function () {
|
||||
opts.insecurePort = port;
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function createServer(port, _delete_me_, content, opts) {
|
||||
function approveDomains(params, certs, cb) {
|
||||
// This is where you check your database and associated
|
||||
// email addresses with domains and agreements and such
|
||||
var domains = params.domains;
|
||||
//var p;
|
||||
console.log('approveDomains');
|
||||
console.log(domains);
|
||||
|
||||
|
||||
// The domains being approved for the first time are listed in opts.domains
|
||||
// Certs being renewed are listed in certs.altnames
|
||||
if (certs) {
|
||||
params.domains = certs.altnames;
|
||||
//p = PromiseA.resolve();
|
||||
}
|
||||
else {
|
||||
//params.email = opts.email;
|
||||
if (!opts.agreeTos) {
|
||||
console.error("You have not previously registered '" + domains + "' so you must specify --agree-tos to agree to both the Let's Encrypt and Daplie DNS terms of service.");
|
||||
process.exit(1);
|
||||
return;
|
||||
}
|
||||
params.agreeTos = opts.agreeTos;
|
||||
}
|
||||
|
||||
// ddns.token(params.email, domains[0])
|
||||
params.email = opts.email;
|
||||
params.refreshToken = opts.refreshToken;
|
||||
params.challengeType = 'dns-01';
|
||||
params.cli = opts.argv;
|
||||
|
||||
cb(null, { options: params, certs: certs });
|
||||
}
|
||||
|
||||
return new PromiseA(function (realResolve) {
|
||||
var app = require('../lib/app.js');
|
||||
var ipaddr = require('ipaddr.js');
|
||||
var addresses = [];
|
||||
|
||||
Object.keys(opts.ifaces).forEach(function (ifacename) {
|
||||
var iface = opts.ifaces[ifacename];
|
||||
iface.ipv4.forEach(function (ip) {
|
||||
addresses.push(ip);
|
||||
});
|
||||
iface.ipv6.forEach(function (ip) {
|
||||
addresses.push(ip);
|
||||
});
|
||||
});
|
||||
|
||||
addresses.sort(function (a, b) {
|
||||
if (a.family !== b.family) {
|
||||
return 'IPv4' === a.family ? 1 : -1;
|
||||
}
|
||||
|
||||
return a.address > b.address ? 1 : -1;
|
||||
});
|
||||
|
||||
addresses.forEach(function (addr) {
|
||||
addr.range = ipaddr.parse(addr.address).range();
|
||||
});
|
||||
|
||||
var Oauth3 = require('oauth3-cli');
|
||||
var oauth3 = Oauth3.create({ device: { hostname: opts.device } });
|
||||
return Oauth3.Devices.one(oauth3).then(function (device) {
|
||||
return Oauth3.Devices.all(oauth3).then(function (devices) {
|
||||
return { devices: devices, device: device.device || device };
|
||||
});
|
||||
}).then(function (devices) {
|
||||
devices.device.secret = undefined;
|
||||
console.log('devices');
|
||||
console.log(devices);
|
||||
var directive = {
|
||||
global: opts.global
|
||||
, sites: opts.sites
|
||||
, defaults: opts.defaults
|
||||
, cwd: process.cwd()
|
||||
, ifaces: opts.ifaces
|
||||
, addresses: addresses
|
||||
, devices: devices.devices
|
||||
, device: devices.device
|
||||
, net: {
|
||||
createConnection: function (opts, cb) {
|
||||
// opts = { host, port, data
|
||||
// , /*proprietary to tunneler*/ servername, remoteAddress, remoteFamily, remotePort
|
||||
// , secure (tls already terminated by a proxy) }
|
||||
// // http://stackoverflow.com/questions/10348906/how-to-know-if-a-request-is-http-or-https-in-node-js
|
||||
// var packerStream = require('tunnel-packer').Stream;
|
||||
// TODO here we will have the tls termination (or re-forward)
|
||||
}
|
||||
}
|
||||
};
|
||||
var server;
|
||||
var insecureServer;
|
||||
|
||||
function resolve() {
|
||||
realResolve({
|
||||
plainServer: insecureServer
|
||||
, server: server
|
||||
});
|
||||
}
|
||||
|
||||
// returns an instance of node-letsencrypt with additional helper methods
|
||||
var webrootPath = require('os').tmpdir();
|
||||
var leChallengeFs = require('le-challenge-fs').create({ webrootPath: webrootPath });
|
||||
//var leChallengeSni = require('le-challenge-sni').create({ webrootPath: webrootPath });
|
||||
var leChallengeDdns = require('le-challenge-ddns').create({ ttl: 1 });
|
||||
var lex = require('greenlock-express').create({
|
||||
// set to https://acme-v01.api.letsencrypt.org/directory in production
|
||||
server: opts.debug ? 'staging' : 'https://acme-v01.api.letsencrypt.org/directory'
|
||||
|
||||
// If you wish to replace the default plugins, you may do so here
|
||||
//
|
||||
, challenges: {
|
||||
'http-01': leChallengeFs
|
||||
, 'tls-sni-01': leChallengeFs // leChallengeSni
|
||||
, 'dns-01': leChallengeDdns
|
||||
}
|
||||
, challengeType: (opts.tunnel ? 'http-01' : 'dns-01')
|
||||
, store: require('le-store-certbot').create({
|
||||
webrootPath: webrootPath
|
||||
, configDir: path.join((opts.homedir || '~'), 'letsencrypt', 'etc')
|
||||
, homedir: opts.homedir
|
||||
})
|
||||
, webrootPath: webrootPath
|
||||
|
||||
// You probably wouldn't need to replace the default sni handler
|
||||
// See https://git.daplie.com/Daplie/le-sni-auto if you think you do
|
||||
//, sni: require('le-sni-auto').create({})
|
||||
|
||||
, approveDomains: approveDomains
|
||||
});
|
||||
|
||||
var secureContexts = {
|
||||
'localhost.daplie.me': null
|
||||
};
|
||||
opts.httpsOptions.SNICallback = function (sni, cb ) {
|
||||
var tlsOptions;
|
||||
console.log('[https] sni', sni);
|
||||
|
||||
// Static Certs
|
||||
if (/.*localhost.*\.daplie\.me/.test(sni.toLowerCase())) {
|
||||
// TODO implement
|
||||
if (!secureContexts[sni]) {
|
||||
tlsOptions = require('localhost.daplie.me-certificates').mergeTlsOptions(sni, {});
|
||||
}
|
||||
if (tlsOptions) {
|
||||
secureContexts[sni] = tls.createSecureContext(tlsOptions);
|
||||
}
|
||||
cb(null, secureContexts[sni]);
|
||||
return;
|
||||
}
|
||||
|
||||
// Dynamic Certs
|
||||
lex.httpsOptions.SNICallback(sni, cb);
|
||||
};
|
||||
server = https.createServer(opts.httpsOptions);
|
||||
|
||||
server.on('error', function (err) {
|
||||
if (opts.errorPort || opts.manualPort) {
|
||||
showError(err, port);
|
||||
process.exit(1);
|
||||
return;
|
||||
}
|
||||
|
||||
opts.errorPort = err.toString();
|
||||
|
||||
return createServer(portFallback, null, content, opts).then(resolve);
|
||||
});
|
||||
|
||||
server.listen(port, function () {
|
||||
opts.port = port;
|
||||
opts.redirectOptions.port = port;
|
||||
|
||||
if (opts.livereload) {
|
||||
opts.lrPort = opts.lrPort || lrPort;
|
||||
var livereload = require('livereload');
|
||||
var server2 = livereload.createServer({
|
||||
https: opts.httpsOptions
|
||||
, port: opts.lrPort
|
||||
, exclusions: [ 'node_modules' ]
|
||||
});
|
||||
|
||||
console.info("[livereload] watching " + opts.pubdir);
|
||||
console.warn("WARNING: If CPU usage spikes to 100% it's because too many files are being watched");
|
||||
// TODO create map of directories to watch from opts.sites and iterate over it
|
||||
server2.watch(opts.pubdir);
|
||||
}
|
||||
|
||||
// if we haven't disabled insecure port
|
||||
if ('false' !== opts.insecurePort) {
|
||||
// and both ports are the default
|
||||
if ((httpsPort === opts.port && httpPort === opts.insecurePort)
|
||||
// or other case
|
||||
|| (httpPort !== opts.insecurePort && opts.port !== opts.insecurePort)
|
||||
) {
|
||||
return createInsecureServer(opts.insecurePort, null, opts).then(function (_server) {
|
||||
insecureServer = _server;
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
opts.insecurePort = opts.port;
|
||||
resolve();
|
||||
return;
|
||||
});
|
||||
|
||||
if ('function' === typeof app) {
|
||||
app = app(directive);
|
||||
} else if ('function' === typeof app.create) {
|
||||
app = app.create(directive);
|
||||
}
|
||||
|
||||
server.on('request', function (req, res) {
|
||||
console.log('[' + req.method + '] ' + req.url);
|
||||
if (!req.socket.encrypted && !/\/\.well-known\/acme-challenge\//.test(req.url)) {
|
||||
opts.redirectApp(req, res);
|
||||
return;
|
||||
}
|
||||
|
||||
if ('function' === typeof app) {
|
||||
app(req, res);
|
||||
return;
|
||||
}
|
||||
|
||||
res.end('not ready');
|
||||
});
|
||||
|
||||
return PromiseA.resolve(app).then(function (_app) {
|
||||
app = _app;
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports.createServer = createServer;
|
||||
|
||||
function run() {
|
||||
var defaultServername = 'localhost.daplie.me';
|
||||
var minimist = require('minimist');
|
||||
var argv = minimist(process.argv.slice(2));
|
||||
var port = parseInt(argv.p || argv.port || argv._[0], 10) || httpsPort;
|
||||
var livereload = argv.livereload;
|
||||
var defaultWebRoot = path.normalize(argv['default-web-root'] || argv.d || argv._[1] || '.');
|
||||
var assetsPath = path.join(__dirname, '..', 'packages', 'assets');
|
||||
var content = argv.c;
|
||||
var letsencryptHost = argv['letsencrypt-certs'];
|
||||
var yaml = require('js-yaml');
|
||||
var fs = PromiseA.promisifyAll(require('fs'));
|
||||
var configFile = argv.c || argv.conf || argv.config;
|
||||
function readConfigAndRun(args) {
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var cwd = args.cwd || process.cwd();
|
||||
var text;
|
||||
var filename;
|
||||
var config;
|
||||
console.log('defaultWebRoot', defaultWebRoot);
|
||||
|
||||
try {
|
||||
config = fs.readFileSync(configFile || 'Goldilocks.yml');
|
||||
} catch(e) {
|
||||
if (configFile) {
|
||||
console.error('Failed to read config:', e);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (config) {
|
||||
try {
|
||||
config = yaml.safeLoad(config);
|
||||
} catch(e) {
|
||||
console.error('Failed to parse config:', e);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (argv.V || argv.version || argv.v) {
|
||||
if (argv.v) {
|
||||
console.warn("flag -v is reserved for future use. Use -V or --version for version information.");
|
||||
}
|
||||
console.info('v' + require('../package.json').version);
|
||||
return;
|
||||
}
|
||||
|
||||
argv.sites = argv.sites;
|
||||
|
||||
// letsencrypt
|
||||
var httpsOptions = require('localhost.daplie.me-certificates').merge({});
|
||||
var secureContext;
|
||||
|
||||
var opts = {
|
||||
agreeTos: argv.agreeTos || argv['agree-tos']
|
||||
, debug: argv.debug
|
||||
, device: argv.device
|
||||
, provider: (argv.provider && 'false' !== argv.provider) ? argv.provider : 'oauth3.org'
|
||||
, email: argv.email
|
||||
, httpsOptions: {
|
||||
key: httpsOptions.key
|
||||
, cert: httpsOptions.cert
|
||||
//, ca: httpsOptions.ca
|
||||
}
|
||||
, homedir: argv.homedir
|
||||
, argv: argv
|
||||
};
|
||||
var peerCa;
|
||||
var p;
|
||||
|
||||
opts.PromiseA = PromiseA;
|
||||
opts.httpsOptions.SNICallback = function (sni, cb) {
|
||||
if (!secureContext) {
|
||||
secureContext = tls.createSecureContext(opts.httpsOptions);
|
||||
}
|
||||
cb(null, secureContext);
|
||||
return;
|
||||
};
|
||||
|
||||
if (letsencryptHost) {
|
||||
// TODO remove in v3.x (aka goldilocks)
|
||||
argv.key = argv.key || '/etc/letsencrypt/live/' + letsencryptHost + '/privkey.pem';
|
||||
argv.cert = argv.cert || '/etc/letsencrypt/live/' + letsencryptHost + '/fullchain.pem';
|
||||
argv.root = argv.root || argv.chain || '';
|
||||
argv.sites = argv.sites || letsencryptHost;
|
||||
argv['serve-root'] = argv['serve-root'] || argv['serve-chain'];
|
||||
// argv[express-app]
|
||||
}
|
||||
|
||||
if (argv['serve-root'] && !argv.root) {
|
||||
console.error("You must specify bath --root to use --serve-root");
|
||||
return;
|
||||
}
|
||||
|
||||
if (argv.key || argv.cert || argv.root) {
|
||||
if (!argv.key || !argv.cert) {
|
||||
console.error("You must specify bath --key and --cert, and optionally --root (required with serve-root)");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Array.isArray(argv.root)) {
|
||||
argv.root = [argv.root];
|
||||
}
|
||||
|
||||
opts.httpsOptions.key = fs.readFileSync(argv.key);
|
||||
opts.httpsOptions.cert = fs.readFileSync(argv.cert);
|
||||
|
||||
// turn multiple-cert pemfile into array of cert strings
|
||||
peerCa = argv.root.reduce(function (roots, fullpath) {
|
||||
if (!fs.existsSync(fullpath)) {
|
||||
return roots;
|
||||
}
|
||||
|
||||
return roots.concat(fs.readFileSync(fullpath, 'ascii')
|
||||
.split('-----END CERTIFICATE-----')
|
||||
.filter(function (ca) {
|
||||
return ca.trim();
|
||||
}).map(function (ca) {
|
||||
return (ca + '-----END CERTIFICATE-----').trim();
|
||||
}));
|
||||
}, []);
|
||||
|
||||
// TODO * `--verify /path/to/root.pem` require peers to present certificates from said authority
|
||||
if (argv.verify) {
|
||||
opts.httpsOptions.ca = peerCa;
|
||||
opts.httpsOptions.requestCert = true;
|
||||
opts.httpsOptions.rejectUnauthorized = true;
|
||||
}
|
||||
|
||||
if (argv['serve-root']) {
|
||||
content = peerCa.join('\r\n');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
opts.cwd = process.cwd();
|
||||
opts.sites = [];
|
||||
opts.sites._map = {};
|
||||
|
||||
if (argv.sites) {
|
||||
opts._externalHost = false;
|
||||
argv.sites.split(',').map(function (name) {
|
||||
var nameparts = name.split('|');
|
||||
var servername = nameparts.shift();
|
||||
var modules;
|
||||
|
||||
opts._externalHost = opts._externalHost || !/(^|\.)localhost\./.test(servername);
|
||||
// TODO allow reverse proxy
|
||||
if (!opts.sites._map[servername]) {
|
||||
opts.sites._map[servername] = { $id: servername, paths: [] };
|
||||
opts.sites._map[servername].paths._map = {};
|
||||
opts.sites.push(opts.sites._map[servername]);
|
||||
}
|
||||
|
||||
if (!nameparts.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!opts.sites._map[servername].paths._map['/']) {
|
||||
opts.sites._map[servername].paths._map['/'] = { $id: '/', modules: [] };
|
||||
opts.sites._map[servername].paths.push(opts.sites._map[servername].paths._map['/']);
|
||||
}
|
||||
|
||||
modules = opts.sites._map[servername].paths._map['/'].modules;
|
||||
modules.push({
|
||||
$id: 'serve'
|
||||
, paths: nameparts
|
||||
});
|
||||
modules.push({
|
||||
$id: 'indexes'
|
||||
, paths: nameparts
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
opts.groups = [];
|
||||
|
||||
// 'packages', 'assets', 'com.daplie.caddy'
|
||||
opts.global = {
|
||||
modules: [ // TODO uh-oh we've got a mixed bag of modules (various types), a true map
|
||||
{ $id: 'greenlock', email: opts.email, tos: opts.tos }
|
||||
, { $id: 'rvpn', email: opts.email, tos: opts.tos }
|
||||
, { $id: 'content', content: content }
|
||||
, { $id: 'livereload', on: opts.livereload }
|
||||
, { $id: 'app', path: opts.expressApp }
|
||||
]
|
||||
, paths: [
|
||||
{ $id: '/assets/', modules: [ { $id: 'serve', paths: [ assetsPath ] } ] }
|
||||
// TODO figure this b out
|
||||
, { $id: '/.well-known/', modules: [
|
||||
{ $id: 'serve', paths: [ path.join(assetsPath, 'well-known') ] }
|
||||
] }
|
||||
]
|
||||
};
|
||||
opts.defaults = {
|
||||
modules: []
|
||||
, paths: [
|
||||
{ $id: '/', modules: [
|
||||
{ $id: 'serve', paths: [ defaultWebRoot ] }
|
||||
, { $id: 'indexes', paths: [ defaultWebRoot ] }
|
||||
] }
|
||||
]
|
||||
};
|
||||
opts.sites.push({
|
||||
// greenlock: {}
|
||||
$id: 'localhost.alpha.daplie.me'
|
||||
, paths: [
|
||||
{ $id: '/', modules: [
|
||||
{ $id: 'serve', paths: [ path.resolve(__dirname, '..', 'admin', 'public') ] }
|
||||
] }
|
||||
, { $id: '/api/', modules: [
|
||||
{ $id: 'app', path: path.join(__dirname, 'admin') }
|
||||
] }
|
||||
]
|
||||
});
|
||||
opts.sites.push({
|
||||
$id: 'localhost.daplie.invalid'
|
||||
, paths: [
|
||||
{ $id: '/', modules: [ { $id: 'serve', paths: [ path.resolve(__dirname, '..', 'admin', 'public') ] } ] }
|
||||
, { $id: '/api/', modules: [ { $id: 'app', path: path.join(__dirname, 'admin') } ] }
|
||||
]
|
||||
});
|
||||
|
||||
// ifaces
|
||||
opts.ifaces = require('../lib/local-ip.js').find();
|
||||
|
||||
// TODO use arrays in all things
|
||||
opts._old_server_name = opts.sites[0].$id;
|
||||
opts.pubdir = defaultWebRoot.replace(/(:hostname|:servername).*/, '');
|
||||
|
||||
if (argv.p || argv.port || argv._[0]) {
|
||||
opts.manualPort = true;
|
||||
}
|
||||
if (argv.t || argv.tunnel) {
|
||||
opts.tunnel = true;
|
||||
}
|
||||
if (argv.i || argv['insecure-port']) {
|
||||
opts.manualInsecurePort = true;
|
||||
}
|
||||
opts.insecurePort = parseInt(argv.i || argv['insecure-port'], 10)
|
||||
|| argv.i || argv['insecure-port']
|
||||
|| httpPort
|
||||
;
|
||||
opts.livereload = livereload;
|
||||
|
||||
if (argv['express-app']) {
|
||||
opts.expressApp = require(argv['express-app']);
|
||||
}
|
||||
|
||||
if (opts.email || opts._externalHost) {
|
||||
if (!opts.agreeTos) {
|
||||
console.warn("You may need to specify --agree-tos to agree to both the Let's Encrypt and Daplie DNS terms of service.");
|
||||
}
|
||||
if (!opts.email) {
|
||||
// TODO store email in .ddnsrc.json
|
||||
console.warn("You may need to specify --email to register with both the Let's Encrypt and Daplie DNS.");
|
||||
}
|
||||
p = DDNS.refreshToken({
|
||||
email: opts.email
|
||||
, providerUrl: opts.provider
|
||||
, silent: true
|
||||
, homedir: opts.homedir
|
||||
}, {
|
||||
debug: false
|
||||
, email: opts.argv.email
|
||||
}).then(function (refreshToken) {
|
||||
opts.refreshToken = refreshToken;
|
||||
});
|
||||
if (args.config) {
|
||||
text = fs.readFileSync(path.join(cwd, args.config), 'utf8');
|
||||
}
|
||||
else {
|
||||
p = PromiseA.resolve();
|
||||
filename = path.join(cwd, 'Goldilocks.yml');
|
||||
|
||||
if (fs.existsSync(filename)) {
|
||||
text = fs.readFileSync(filename, 'utf8');
|
||||
}
|
||||
else {
|
||||
filename = path.join(cwd, 'Goldilocks.json');
|
||||
if (fs.existsSync(filename)) {
|
||||
text = fs.readFileSync(filename, 'utf8');
|
||||
} else {
|
||||
text = '{}';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return p.then(function () {
|
||||
|
||||
// can be changed to tunnel external port
|
||||
opts.redirectOptions = {
|
||||
port: opts.port
|
||||
};
|
||||
opts.redirectApp = require('redirect-https')(opts.redirectOptions);
|
||||
|
||||
return createServer(port, null, content, opts).then(function (servers) {
|
||||
var p;
|
||||
var httpsUrl;
|
||||
var httpUrl;
|
||||
var promise;
|
||||
|
||||
// TODO show all sites
|
||||
console.info('');
|
||||
console.info('Serving ' + opts.pubdir + ' at ');
|
||||
console.info('');
|
||||
|
||||
// Port
|
||||
httpsUrl = 'https://' + opts._old_server_name;
|
||||
p = opts.port;
|
||||
if (httpsPort !== p) {
|
||||
httpsUrl += ':' + p;
|
||||
try {
|
||||
config = JSON.parse(text);
|
||||
} catch(e) {
|
||||
try {
|
||||
config = require('js-yaml').safeLoad(text);
|
||||
} catch(e) {
|
||||
throw new Error(
|
||||
"Could not load '" + filename + "' as JSON nor YAML"
|
||||
);
|
||||
}
|
||||
console.info('\t' + httpsUrl);
|
||||
}
|
||||
|
||||
// Insecure Port
|
||||
httpUrl = 'http://' + opts._old_server_name;
|
||||
p = opts.insecurePort;
|
||||
if (httpPort !== p) {
|
||||
httpUrl += ':' + p;
|
||||
}
|
||||
console.info('\t' + httpUrl + ' (redirecting to https)');
|
||||
console.info('');
|
||||
|
||||
if (!(argv.sites && (defaultServername !== argv.sites) && !(argv.key && argv.cert))) {
|
||||
// TODO what is this condition actually intending to test again?
|
||||
// (I think it can be replaced with if (!opts._externalHost) { ... }
|
||||
|
||||
promise = PromiseA.resolve();
|
||||
} else {
|
||||
console.info("Attempting to resolve external connection for '" + opts._old_server_name + "'");
|
||||
try {
|
||||
promise = require('../lib/match-ips.js').match(opts._old_server_name, opts);
|
||||
} catch(e) {
|
||||
console.warn("Upgrade to version 2.x to use automatic certificate issuance for '" + opts._old_server_name + "'");
|
||||
promise = PromiseA.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
return promise.then(function (matchingIps) {
|
||||
if (matchingIps) {
|
||||
if (!matchingIps.length) {
|
||||
console.info("Neither the attached nor external interfaces match '" + opts._old_server_name + "'");
|
||||
}
|
||||
}
|
||||
opts.matchingIps = matchingIps || [];
|
||||
|
||||
if (opts.matchingIps.length) {
|
||||
console.info('');
|
||||
console.info('External IPs:');
|
||||
console.info('');
|
||||
opts.matchingIps.forEach(function (ip) {
|
||||
if ('IPv4' === ip.family) {
|
||||
httpsUrl = 'https://' + ip.address;
|
||||
if (httpsPort !== opts.port) {
|
||||
httpsUrl += ':' + opts.port;
|
||||
}
|
||||
console.info('\t' + httpsUrl);
|
||||
}
|
||||
else {
|
||||
httpsUrl = 'https://[' + ip.address + ']';
|
||||
if (httpsPort !== opts.port) {
|
||||
httpsUrl += ':' + opts.port;
|
||||
}
|
||||
console.info('\t' + httpsUrl);
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (!opts.tunnel) {
|
||||
console.info("External IP address does not match local IP address.");
|
||||
console.info("Use --tunnel to allow the people of the Internet to access your server.");
|
||||
}
|
||||
|
||||
if (opts.tunnel) {
|
||||
require('../lib/tunnel.js').create(opts, servers);
|
||||
}
|
||||
else if (opts.ddns) {
|
||||
require('../lib/ddns.js').create(opts, servers);
|
||||
}
|
||||
|
||||
Object.keys(opts.ifaces).forEach(function (iname) {
|
||||
var iface = opts.ifaces[iname];
|
||||
|
||||
if (iface.ipv4.length) {
|
||||
console.info('');
|
||||
console.info(iname + ':');
|
||||
|
||||
httpsUrl = 'https://' + iface.ipv4[0].address;
|
||||
if (httpsPort !== opts.port) {
|
||||
httpsUrl += ':' + opts.port;
|
||||
}
|
||||
console.info('\t' + httpsUrl);
|
||||
|
||||
if (iface.ipv6.length) {
|
||||
httpsUrl = 'https://[' + iface.ipv6[0].address + ']';
|
||||
if (httpsPort !== opts.port) {
|
||||
httpsUrl += ':' + opts.port;
|
||||
}
|
||||
console.info('\t' + httpsUrl);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
console.info('');
|
||||
});
|
||||
});
|
||||
});
|
||||
require('../lib/goldilocks.js').create(config);
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
run();
|
||||
if (process.argv.length === 2) {
|
||||
readConfigAndRun({});
|
||||
}
|
||||
else if (process.argv.length === 4) {
|
||||
if ('-c' === process.argv[3] || '--config' === process.argv[3]) {
|
||||
readConfigAndRun({ config: process.argv[4] });
|
||||
}
|
||||
}
|
||||
else if (process.argv.length > 2) {
|
||||
var program = require('commander');
|
||||
|
||||
program
|
||||
.version(require('package.json').version)
|
||||
.option('--config', 'Path to config file (Goldilocks.json or Goldilocks.yml) example: --config /etc/goldilocks/Goldilocks.json')
|
||||
.option('--tunnel [token]', 'Turn tunnel on. This will enter interactive mode for login if no token is specified.')
|
||||
.parse(process.argv);
|
||||
|
||||
readConfigAndRun(program);
|
||||
}
|
||||
else {
|
||||
throw new Error("impossible number of arguments: " + process.argv.length);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,688 @@
|
|||
'use strict';
|
||||
|
||||
module.exports.create = function (config) {
|
||||
//var PromiseA = global.Promise;
|
||||
var PromiseA = require('bluebird');
|
||||
var tls = require('tls');
|
||||
var https = require('httpolyglot');
|
||||
var http = require('http');
|
||||
var path = require('path');
|
||||
var httpPort = 80;
|
||||
var httpsPort = 443;
|
||||
var lrPort = 35729;
|
||||
var portFallback = 8443;
|
||||
var insecurePortFallback = 4080;
|
||||
|
||||
function showError(err, port) {
|
||||
if ('EACCES' === err.code) {
|
||||
console.error(err);
|
||||
console.warn("You do not have permission to use '" + port + "'.");
|
||||
console.warn("You can probably fix that by running as Administrator or root.");
|
||||
}
|
||||
else if ('EADDRINUSE' === err.code) {
|
||||
console.warn("Another server is already running on '" + port + "'.");
|
||||
console.warn("You can probably fix that by rebooting your computer (or stopping it if you know what it is).");
|
||||
}
|
||||
}
|
||||
|
||||
function createInsecureServer(port, _delete_me_, opts) {
|
||||
return new PromiseA(function (realResolve) {
|
||||
var server = http.createServer();
|
||||
|
||||
function resolve() {
|
||||
realResolve(server);
|
||||
}
|
||||
|
||||
server.on('error', function (err) {
|
||||
if (opts.errorInsecurePort || opts.manualInsecurePort) {
|
||||
showError(err, port);
|
||||
process.exit(1);
|
||||
return;
|
||||
}
|
||||
|
||||
opts.errorInsecurePort = err.toString();
|
||||
|
||||
return createInsecureServer(insecurePortFallback, null, opts).then(resolve);
|
||||
});
|
||||
|
||||
server.on('request', opts.redirectApp);
|
||||
|
||||
server.listen(port, function () {
|
||||
opts.insecurePort = port;
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function createServer(port, _delete_me_, content, opts) {
|
||||
function approveDomains(params, certs, cb) {
|
||||
// This is where you check your database and associated
|
||||
// email addresses with domains and agreements and such
|
||||
var domains = params.domains;
|
||||
//var p;
|
||||
console.log('approveDomains');
|
||||
console.log(domains);
|
||||
|
||||
|
||||
// The domains being approved for the first time are listed in opts.domains
|
||||
// Certs being renewed are listed in certs.altnames
|
||||
if (certs) {
|
||||
params.domains = certs.altnames;
|
||||
//p = PromiseA.resolve();
|
||||
}
|
||||
else {
|
||||
//params.email = opts.email;
|
||||
if (!opts.agreeTos) {
|
||||
console.error("You have not previously registered '" + domains + "' so you must specify --agree-tos to agree to both the Let's Encrypt and Daplie DNS terms of service.");
|
||||
process.exit(1);
|
||||
return;
|
||||
}
|
||||
params.agreeTos = opts.agreeTos;
|
||||
}
|
||||
|
||||
// ddns.token(params.email, domains[0])
|
||||
params.email = opts.email;
|
||||
params.refreshToken = opts.refreshToken;
|
||||
params.challengeType = 'dns-01';
|
||||
params.cli = opts.argv;
|
||||
|
||||
cb(null, { options: params, certs: certs });
|
||||
}
|
||||
|
||||
return new PromiseA(function (realResolve) {
|
||||
var app = require('../lib/app.js');
|
||||
var ipaddr = require('ipaddr.js');
|
||||
var addresses = [];
|
||||
|
||||
Object.keys(opts.ifaces).forEach(function (ifacename) {
|
||||
var iface = opts.ifaces[ifacename];
|
||||
iface.ipv4.forEach(function (ip) {
|
||||
addresses.push(ip);
|
||||
});
|
||||
iface.ipv6.forEach(function (ip) {
|
||||
addresses.push(ip);
|
||||
});
|
||||
});
|
||||
|
||||
addresses.sort(function (a, b) {
|
||||
if (a.family !== b.family) {
|
||||
return 'IPv4' === a.family ? 1 : -1;
|
||||
}
|
||||
|
||||
return a.address > b.address ? 1 : -1;
|
||||
});
|
||||
|
||||
addresses.forEach(function (addr) {
|
||||
addr.range = ipaddr.parse(addr.address).range();
|
||||
});
|
||||
|
||||
var Oauth3 = require('oauth3-cli');
|
||||
var oauth3 = Oauth3.create({ device: { hostname: opts.device } });
|
||||
return Oauth3.Devices.one(oauth3).then(function (device) {
|
||||
return Oauth3.Devices.all(oauth3).then(function (devices) {
|
||||
return { devices: devices, device: device.device || device };
|
||||
});
|
||||
}).then(function (devices) {
|
||||
devices.device.secret = undefined;
|
||||
console.log('devices');
|
||||
console.log(devices);
|
||||
var directive = {
|
||||
global: opts.global
|
||||
, sites: opts.sites
|
||||
, defaults: opts.defaults
|
||||
, cwd: process.cwd()
|
||||
, ifaces: opts.ifaces
|
||||
, addresses: addresses
|
||||
, devices: devices.devices
|
||||
, device: devices.device
|
||||
, net: {
|
||||
createConnection: function (opts, cb) {
|
||||
// opts = { host, port, data
|
||||
// , /*proprietary to tunneler*/ servername, remoteAddress, remoteFamily, remotePort
|
||||
// , secure (tls already terminated by a proxy) }
|
||||
// // http://stackoverflow.com/questions/10348906/how-to-know-if-a-request-is-http-or-https-in-node-js
|
||||
// var packerStream = require('tunnel-packer').Stream;
|
||||
// TODO here we will have the tls termination (or re-forward)
|
||||
return require('net').createConnection(opts, cb);
|
||||
}
|
||||
}
|
||||
};
|
||||
var server;
|
||||
var insecureServer;
|
||||
|
||||
function resolve() {
|
||||
realResolve({
|
||||
plainServer: insecureServer
|
||||
, server: server
|
||||
});
|
||||
}
|
||||
|
||||
// returns an instance of node-letsencrypt with additional helper methods
|
||||
var webrootPath = require('os').tmpdir();
|
||||
var leChallengeFs = require('le-challenge-fs').create({ webrootPath: webrootPath });
|
||||
//var leChallengeSni = require('le-challenge-sni').create({ webrootPath: webrootPath });
|
||||
var leChallengeDdns = require('le-challenge-ddns').create({ ttl: 1 });
|
||||
var lex = require('greenlock-express').create({
|
||||
// set to https://acme-v01.api.letsencrypt.org/directory in production
|
||||
server: opts.debug ? 'staging' : 'https://acme-v01.api.letsencrypt.org/directory'
|
||||
|
||||
// If you wish to replace the default plugins, you may do so here
|
||||
//
|
||||
, challenges: {
|
||||
'http-01': leChallengeFs
|
||||
, 'tls-sni-01': leChallengeFs // leChallengeSni
|
||||
, 'dns-01': leChallengeDdns
|
||||
}
|
||||
, challengeType: (opts.tunnel ? 'http-01' : 'dns-01')
|
||||
, store: require('le-store-certbot').create({
|
||||
webrootPath: webrootPath
|
||||
, configDir: path.join((opts.homedir || '~'), 'letsencrypt', 'etc')
|
||||
, homedir: opts.homedir
|
||||
})
|
||||
, webrootPath: webrootPath
|
||||
|
||||
// You probably wouldn't need to replace the default sni handler
|
||||
// See https://git.daplie.com/Daplie/le-sni-auto if you think you do
|
||||
//, sni: require('le-sni-auto').create({})
|
||||
|
||||
, approveDomains: approveDomains
|
||||
});
|
||||
|
||||
var secureContexts = {
|
||||
'localhost.daplie.me': null
|
||||
};
|
||||
opts.httpsOptions.SNICallback = function (sni, cb ) {
|
||||
var tlsOptions;
|
||||
console.log('[https] sni', sni);
|
||||
|
||||
// Static Certs
|
||||
if (/.*localhost.*\.daplie\.me/.test(sni.toLowerCase())) {
|
||||
// TODO implement
|
||||
if (!secureContexts[sni]) {
|
||||
tlsOptions = require('localhost.daplie.me-certificates').mergeTlsOptions(sni, {});
|
||||
}
|
||||
if (tlsOptions) {
|
||||
secureContexts[sni] = tls.createSecureContext(tlsOptions);
|
||||
}
|
||||
cb(null, secureContexts[sni]);
|
||||
return;
|
||||
}
|
||||
|
||||
// Dynamic Certs
|
||||
lex.httpsOptions.SNICallback(sni, cb);
|
||||
};
|
||||
server = https.createServer(opts.httpsOptions);
|
||||
|
||||
server.on('error', function (err) {
|
||||
if (opts.errorPort || opts.manualPort) {
|
||||
showError(err, port);
|
||||
process.exit(1);
|
||||
return;
|
||||
}
|
||||
|
||||
opts.errorPort = err.toString();
|
||||
|
||||
return createServer(portFallback, null, content, opts).then(resolve);
|
||||
});
|
||||
|
||||
server.listen(port, function () {
|
||||
opts.port = port;
|
||||
opts.redirectOptions.port = port;
|
||||
|
||||
if (opts.livereload) {
|
||||
opts.lrPort = opts.lrPort || lrPort;
|
||||
var livereload = require('livereload');
|
||||
var server2 = livereload.createServer({
|
||||
https: opts.httpsOptions
|
||||
, port: opts.lrPort
|
||||
, exclusions: [ 'node_modules' ]
|
||||
});
|
||||
|
||||
console.info("[livereload] watching " + opts.pubdir);
|
||||
console.warn("WARNING: If CPU usage spikes to 100% it's because too many files are being watched");
|
||||
// TODO create map of directories to watch from opts.sites and iterate over it
|
||||
server2.watch(opts.pubdir);
|
||||
}
|
||||
|
||||
// if we haven't disabled insecure port
|
||||
if ('false' !== opts.insecurePort) {
|
||||
// and both ports are the default
|
||||
if ((httpsPort === opts.port && httpPort === opts.insecurePort)
|
||||
// or other case
|
||||
|| (httpPort !== opts.insecurePort && opts.port !== opts.insecurePort)
|
||||
) {
|
||||
return createInsecureServer(opts.insecurePort, null, opts).then(function (_server) {
|
||||
insecureServer = _server;
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
opts.insecurePort = opts.port;
|
||||
resolve();
|
||||
return;
|
||||
});
|
||||
|
||||
if ('function' === typeof app) {
|
||||
app = app(directive);
|
||||
} else if ('function' === typeof app.create) {
|
||||
app = app.create(directive);
|
||||
}
|
||||
|
||||
server.on('request', function (req, res) {
|
||||
console.log('[' + req.method + '] ' + req.url);
|
||||
if (!req.socket.encrypted && !/\/\.well-known\/acme-challenge\//.test(req.url)) {
|
||||
opts.redirectApp(req, res);
|
||||
return;
|
||||
}
|
||||
|
||||
if ('function' === typeof app) {
|
||||
app(req, res);
|
||||
return;
|
||||
}
|
||||
|
||||
res.end('not ready');
|
||||
});
|
||||
|
||||
return PromiseA.resolve(app).then(function (_app) {
|
||||
app = _app;
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports.createServer = createServer;
|
||||
|
||||
function run() {
|
||||
var defaultServername = 'localhost.daplie.me';
|
||||
var minimist = require('minimist');
|
||||
var argv = minimist(process.argv.slice(2));
|
||||
var port = parseInt(argv.p || argv.port || argv._[0], 10) || httpsPort;
|
||||
var livereload = argv.livereload;
|
||||
var defaultWebRoot = path.normalize(argv['default-web-root'] || argv.d || argv._[1] || '.');
|
||||
var assetsPath = path.join(__dirname, '..', 'packages', 'assets');
|
||||
var content = argv.c;
|
||||
var letsencryptHost = argv['letsencrypt-certs'];
|
||||
var yaml = require('js-yaml');
|
||||
var fs = PromiseA.promisifyAll(require('fs'));
|
||||
var configFile = argv.c || argv.conf || argv.config;
|
||||
var config;
|
||||
var DDNS;
|
||||
console.log('defaultWebRoot', defaultWebRoot);
|
||||
|
||||
try {
|
||||
config = fs.readFileSync(configFile || 'Goldilocks.yml');
|
||||
} catch(e) {
|
||||
if (configFile) {
|
||||
console.error('Failed to read config:', e);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (config) {
|
||||
try {
|
||||
config = yaml.safeLoad(config);
|
||||
} catch(e) {
|
||||
console.error('Failed to parse config:', e);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (argv.V || argv.version || argv.v) {
|
||||
if (argv.v) {
|
||||
console.warn("flag -v is reserved for future use. Use -V or --version for version information.");
|
||||
}
|
||||
console.info('v' + require('../package.json').version);
|
||||
return;
|
||||
}
|
||||
|
||||
argv.sites = argv.sites;
|
||||
|
||||
// letsencrypt
|
||||
var httpsOptions = require('localhost.daplie.me-certificates').merge({});
|
||||
var secureContext;
|
||||
|
||||
var opts = {
|
||||
agreeTos: argv.agreeTos || argv['agree-tos']
|
||||
, debug: argv.debug
|
||||
, device: argv.device
|
||||
, provider: (argv.provider && 'false' !== argv.provider) ? argv.provider : 'oauth3.org'
|
||||
, email: argv.email
|
||||
, httpsOptions: {
|
||||
key: httpsOptions.key
|
||||
, cert: httpsOptions.cert
|
||||
//, ca: httpsOptions.ca
|
||||
}
|
||||
, homedir: argv.homedir
|
||||
, argv: argv
|
||||
};
|
||||
var peerCa;
|
||||
var p;
|
||||
|
||||
opts.PromiseA = PromiseA;
|
||||
opts.httpsOptions.SNICallback = function (sni, cb) {
|
||||
if (!secureContext) {
|
||||
secureContext = tls.createSecureContext(opts.httpsOptions);
|
||||
}
|
||||
cb(null, secureContext);
|
||||
return;
|
||||
};
|
||||
|
||||
if (letsencryptHost) {
|
||||
// TODO remove in v3.x (aka goldilocks)
|
||||
argv.key = argv.key || '/etc/letsencrypt/live/' + letsencryptHost + '/privkey.pem';
|
||||
argv.cert = argv.cert || '/etc/letsencrypt/live/' + letsencryptHost + '/fullchain.pem';
|
||||
argv.root = argv.root || argv.chain || '';
|
||||
argv.sites = argv.sites || letsencryptHost;
|
||||
argv['serve-root'] = argv['serve-root'] || argv['serve-chain'];
|
||||
// argv[express-app]
|
||||
}
|
||||
|
||||
if (argv['serve-root'] && !argv.root) {
|
||||
console.error("You must specify bath --root to use --serve-root");
|
||||
return;
|
||||
}
|
||||
|
||||
if (argv.key || argv.cert || argv.root) {
|
||||
if (!argv.key || !argv.cert) {
|
||||
console.error("You must specify bath --key and --cert, and optionally --root (required with serve-root)");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Array.isArray(argv.root)) {
|
||||
argv.root = [argv.root];
|
||||
}
|
||||
|
||||
opts.httpsOptions.key = fs.readFileSync(argv.key);
|
||||
opts.httpsOptions.cert = fs.readFileSync(argv.cert);
|
||||
|
||||
// turn multiple-cert pemfile into array of cert strings
|
||||
peerCa = argv.root.reduce(function (roots, fullpath) {
|
||||
if (!fs.existsSync(fullpath)) {
|
||||
return roots;
|
||||
}
|
||||
|
||||
return roots.concat(fs.readFileSync(fullpath, 'ascii')
|
||||
.split('-----END CERTIFICATE-----')
|
||||
.filter(function (ca) {
|
||||
return ca.trim();
|
||||
}).map(function (ca) {
|
||||
return (ca + '-----END CERTIFICATE-----').trim();
|
||||
}));
|
||||
}, []);
|
||||
|
||||
// TODO * `--verify /path/to/root.pem` require peers to present certificates from said authority
|
||||
if (argv.verify) {
|
||||
opts.httpsOptions.ca = peerCa;
|
||||
opts.httpsOptions.requestCert = true;
|
||||
opts.httpsOptions.rejectUnauthorized = true;
|
||||
}
|
||||
|
||||
if (argv['serve-root']) {
|
||||
content = peerCa.join('\r\n');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
opts.cwd = process.cwd();
|
||||
opts.sites = [];
|
||||
opts.sites._map = {};
|
||||
|
||||
if (argv.sites) {
|
||||
opts._externalHost = false;
|
||||
argv.sites.split(',').map(function (name) {
|
||||
var nameparts = name.split('|');
|
||||
var servername = nameparts.shift();
|
||||
var modules;
|
||||
|
||||
opts._externalHost = opts._externalHost || !/(^|\.)localhost\./.test(servername);
|
||||
// TODO allow reverse proxy
|
||||
if (!opts.sites._map[servername]) {
|
||||
opts.sites._map[servername] = { $id: servername, paths: [] };
|
||||
opts.sites._map[servername].paths._map = {};
|
||||
opts.sites.push(opts.sites._map[servername]);
|
||||
}
|
||||
|
||||
if (!nameparts.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!opts.sites._map[servername].paths._map['/']) {
|
||||
opts.sites._map[servername].paths._map['/'] = { $id: '/', modules: [] };
|
||||
opts.sites._map[servername].paths.push(opts.sites._map[servername].paths._map['/']);
|
||||
}
|
||||
|
||||
modules = opts.sites._map[servername].paths._map['/'].modules;
|
||||
modules.push({
|
||||
$id: 'serve'
|
||||
, paths: nameparts
|
||||
});
|
||||
modules.push({
|
||||
$id: 'indexes'
|
||||
, paths: nameparts
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
opts.groups = [];
|
||||
|
||||
// 'packages', 'assets', 'com.daplie.caddy'
|
||||
opts.global = {
|
||||
modules: [ // TODO uh-oh we've got a mixed bag of modules (various types), a true map
|
||||
{ $id: 'greenlock', email: opts.email, tos: opts.tos }
|
||||
, { $id: 'rvpn', email: opts.email, tos: opts.tos }
|
||||
, { $id: 'content', content: content }
|
||||
, { $id: 'livereload', on: opts.livereload }
|
||||
, { $id: 'app', path: opts.expressApp }
|
||||
]
|
||||
, paths: [
|
||||
{ $id: '/assets/', modules: [ { $id: 'serve', paths: [ assetsPath ] } ] }
|
||||
// TODO figure this b out
|
||||
, { $id: '/.well-known/', modules: [
|
||||
{ $id: 'serve', paths: [ path.join(assetsPath, 'well-known') ] }
|
||||
] }
|
||||
]
|
||||
};
|
||||
opts.defaults = {
|
||||
modules: []
|
||||
, paths: [
|
||||
{ $id: '/', modules: [
|
||||
{ $id: 'serve', paths: [ defaultWebRoot ] }
|
||||
, { $id: 'indexes', paths: [ defaultWebRoot ] }
|
||||
] }
|
||||
]
|
||||
};
|
||||
opts.sites.push({
|
||||
// greenlock: {}
|
||||
$id: 'localhost.alpha.daplie.me'
|
||||
, paths: [
|
||||
{ $id: '/', modules: [
|
||||
{ $id: 'serve', paths: [ path.resolve(__dirname, '..', 'admin', 'public') ] }
|
||||
] }
|
||||
, { $id: '/api/', modules: [
|
||||
{ $id: 'app', path: path.join(__dirname, 'admin') }
|
||||
] }
|
||||
]
|
||||
});
|
||||
opts.sites.push({
|
||||
$id: 'localhost.daplie.invalid'
|
||||
, paths: [
|
||||
{ $id: '/', modules: [ { $id: 'serve', paths: [ path.resolve(__dirname, '..', 'admin', 'public') ] } ] }
|
||||
, { $id: '/api/', modules: [ { $id: 'app', path: path.join(__dirname, 'admin') } ] }
|
||||
]
|
||||
});
|
||||
|
||||
// ifaces
|
||||
opts.ifaces = require('../lib/local-ip.js').find();
|
||||
|
||||
// TODO use arrays in all things
|
||||
opts._old_server_name = opts.sites[0].$id;
|
||||
opts.pubdir = defaultWebRoot.replace(/(:hostname|:servername).*/, '');
|
||||
|
||||
if (argv.p || argv.port || argv._[0]) {
|
||||
opts.manualPort = true;
|
||||
}
|
||||
if (argv.t || argv.tunnel) {
|
||||
opts.tunnel = true;
|
||||
}
|
||||
if (argv.i || argv['insecure-port']) {
|
||||
opts.manualInsecurePort = true;
|
||||
}
|
||||
opts.insecurePort = parseInt(argv.i || argv['insecure-port'], 10)
|
||||
|| argv.i || argv['insecure-port']
|
||||
|| httpPort
|
||||
;
|
||||
opts.livereload = livereload;
|
||||
|
||||
if (argv['express-app']) {
|
||||
opts.expressApp = require(argv['express-app']);
|
||||
}
|
||||
|
||||
if (opts.email || opts._externalHost) {
|
||||
if (!opts.agreeTos) {
|
||||
console.warn("You may need to specify --agree-tos to agree to both the Let's Encrypt and Daplie DNS terms of service.");
|
||||
}
|
||||
if (!opts.email) {
|
||||
// TODO store email in .ddnsrc.json
|
||||
console.warn("You may need to specify --email to register with both the Let's Encrypt and Daplie DNS.");
|
||||
}
|
||||
DDNS = require('ddns-cli');
|
||||
p = DDNS.refreshToken({
|
||||
email: opts.email
|
||||
, providerUrl: opts.provider
|
||||
, silent: true
|
||||
, homedir: opts.homedir
|
||||
}, {
|
||||
debug: false
|
||||
, email: opts.argv.email
|
||||
}).then(function (refreshToken) {
|
||||
opts.refreshToken = refreshToken;
|
||||
});
|
||||
}
|
||||
else {
|
||||
p = PromiseA.resolve();
|
||||
}
|
||||
|
||||
return p.then(function () {
|
||||
|
||||
// can be changed to tunnel external port
|
||||
opts.redirectOptions = {
|
||||
port: opts.port
|
||||
};
|
||||
opts.redirectApp = require('redirect-https')(opts.redirectOptions);
|
||||
|
||||
return createServer(port, null, content, opts).then(function (servers) {
|
||||
var p;
|
||||
var httpsUrl;
|
||||
var httpUrl;
|
||||
var promise;
|
||||
|
||||
// TODO show all sites
|
||||
console.info('');
|
||||
console.info('Serving ' + opts.pubdir + ' at ');
|
||||
console.info('');
|
||||
|
||||
// Port
|
||||
httpsUrl = 'https://' + opts._old_server_name;
|
||||
p = opts.port;
|
||||
if (httpsPort !== p) {
|
||||
httpsUrl += ':' + p;
|
||||
}
|
||||
console.info('\t' + httpsUrl);
|
||||
|
||||
// Insecure Port
|
||||
httpUrl = 'http://' + opts._old_server_name;
|
||||
p = opts.insecurePort;
|
||||
if (httpPort !== p) {
|
||||
httpUrl += ':' + p;
|
||||
}
|
||||
console.info('\t' + httpUrl + ' (redirecting to https)');
|
||||
console.info('');
|
||||
|
||||
if (!(argv.sites && (defaultServername !== argv.sites) && !(argv.key && argv.cert))) {
|
||||
// TODO what is this condition actually intending to test again?
|
||||
// (I think it can be replaced with if (!opts._externalHost) { ... }
|
||||
|
||||
promise = PromiseA.resolve();
|
||||
} else {
|
||||
console.info("Attempting to resolve external connection for '" + opts._old_server_name + "'");
|
||||
try {
|
||||
promise = require('../lib/match-ips.js').match(opts._old_server_name, opts);
|
||||
} catch(e) {
|
||||
console.warn("Upgrade to version 2.x to use automatic certificate issuance for '" + opts._old_server_name + "'");
|
||||
promise = PromiseA.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
return promise.then(function (matchingIps) {
|
||||
if (matchingIps) {
|
||||
if (!matchingIps.length) {
|
||||
console.info("Neither the attached nor external interfaces match '" + opts._old_server_name + "'");
|
||||
}
|
||||
}
|
||||
opts.matchingIps = matchingIps || [];
|
||||
|
||||
if (opts.matchingIps.length) {
|
||||
console.info('');
|
||||
console.info('External IPs:');
|
||||
console.info('');
|
||||
opts.matchingIps.forEach(function (ip) {
|
||||
if ('IPv4' === ip.family) {
|
||||
httpsUrl = 'https://' + ip.address;
|
||||
if (httpsPort !== opts.port) {
|
||||
httpsUrl += ':' + opts.port;
|
||||
}
|
||||
console.info('\t' + httpsUrl);
|
||||
}
|
||||
else {
|
||||
httpsUrl = 'https://[' + ip.address + ']';
|
||||
if (httpsPort !== opts.port) {
|
||||
httpsUrl += ':' + opts.port;
|
||||
}
|
||||
console.info('\t' + httpsUrl);
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (!opts.tunnel) {
|
||||
console.info("External IP address does not match local IP address.");
|
||||
console.info("Use --tunnel to allow the people of the Internet to access your server.");
|
||||
}
|
||||
|
||||
if (opts.tunnel) {
|
||||
require('../lib/tunnel.js').create(opts, servers);
|
||||
}
|
||||
else if (opts.ddns) {
|
||||
require('../lib/ddns.js').create(opts, servers);
|
||||
}
|
||||
|
||||
Object.keys(opts.ifaces).forEach(function (iname) {
|
||||
var iface = opts.ifaces[iname];
|
||||
|
||||
if (iface.ipv4.length) {
|
||||
console.info('');
|
||||
console.info(iname + ':');
|
||||
|
||||
httpsUrl = 'https://' + iface.ipv4[0].address;
|
||||
if (httpsPort !== opts.port) {
|
||||
httpsUrl += ':' + opts.port;
|
||||
}
|
||||
console.info('\t' + httpsUrl);
|
||||
|
||||
if (iface.ipv6.length) {
|
||||
httpsUrl = 'https://[' + iface.ipv6[0].address + ']';
|
||||
if (httpsPort !== opts.port) {
|
||||
httpsUrl += ':' + opts.port;
|
||||
}
|
||||
console.info('\t' + httpsUrl);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
console.info('');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
run();
|
||||
};
|
|
@ -40,6 +40,7 @@
|
|||
"dependencies": {
|
||||
"bluebird": "^3.4.6",
|
||||
"body-parser": "git+https://github.com/expressjs/body-parser.git#1.16.1",
|
||||
"commander": "^2.9.0",
|
||||
"daplie-tunnel": "git+https://git.daplie.com/Daplie/daplie-cli-tunnel.git#master",
|
||||
"ddns-cli": "git+https://git.daplie.com/Daplie/node-ddns-client.git#master",
|
||||
"express": "git+https://github.com/expressjs/express.git#4.x",
|
||||
|
@ -49,7 +50,7 @@
|
|||
"httpolyglot": "^0.1.1",
|
||||
"ipaddr.js": "git+https://github.com/whitequark/ipaddr.js.git#v1.3.0",
|
||||
"ipify": "^1.1.0",
|
||||
"js-yaml": "^3.8.1",
|
||||
"js-yaml": "^3.8.3",
|
||||
"le-challenge-ddns": "git+https://git.daplie.com/Daplie/le-challenge-ddns.git#master",
|
||||
"le-challenge-fs": "git+https://git.daplie.com/Daplie/le-challenge-webroot.git#master",
|
||||
"le-challenge-sni": "^2.0.1",
|
||||
|
|
Loading…
Reference in New Issue