forked from coolaj86/goldilocks.js
		
	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);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										688
									
								
								lib/goldilocks.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										688
									
								
								lib/goldilocks.js
									
									
									
									
									
										Normal file
									
								
							@ -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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user