#!/usr/bin/env node 'use strict'; var cluster = require('cluster'); if (!cluster.isMaster) { require('../lib/worker.js'); return; } function run(config) { // TODO spin up multiple workers // TODO use greenlock-cluster function work() { var worker = cluster.fork(); worker.on('exit', work).on('online', function () { console.log('[worker]', worker.id, 'online'); // Worker is listening worker.send(config); }); } console.log('config.tcp.ports', config.tcp.ports); work(); } function readConfigAndRun(args) { var fs = require('fs'); var path = require('path'); var cwd = args.cwd || process.cwd(); var text; var filename; var config; if (args.config) { filename = path.join(cwd, args.config); text = fs.readFileSync(filename, 'utf8'); } else { 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 = '{}'; } } } 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" ); } } if (!config.dns) { config.dns = { proxy: { port: 3053 } }; } if (!config.tcp) { config.tcp = {}; } if (!config.http) { config.http = { proxy: { port: 3000 } }; } if (!config.tls) { config.tls = { agreeTos: args.agreeTos || args.agree || args['agree-tos'] , servernames: (args.servernames||'').split(',').filter(Boolean).map(function (str) { return str.toLowerCase(); }) }; } if (args.email) { config.email = args.email; config.tls.email = args.email; } // maybe this should not go in config... but be ephemeral in some way? if (args.cwd) { config.cwd = args.cwd; } if (!config.cwd) { config.cwd = process.cwd(); } var ipaddr = require('ipaddr.js'); var addresses = []; var ifaces = require('../lib/local-ip.js').find(); Object.keys(ifaces).forEach(function (ifacename) { var iface = 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(); }); // TODO maybe move to config.state.addresses (?) config.addresses = addresses; config.device = { hostname: 'daplien-pod' }; var PromiseA = require('bluebird'); var tcpProm, dnsProm; if (config.tcp.ports) { tcpProm = PromiseA.resolve(); } else { tcpProm = new PromiseA(function (resolve, reject) { require('../lib/check-ports').checkTcpPorts(function (failed, bound) { config.tcp.ports = Object.keys(bound); if (config.tcp.ports.length) { resolve(); } else { reject(failed); } }); }); } if (config.dns.bind) { dnsProm = PromiseA.resolve(); } else { dnsProm = new PromiseA(function (resolve) { require('../lib/check-ports').checkUdpPorts(function (failed, bound) { var ports = Object.keys(bound); if (ports.length === 0) { // I don't think we want to prevent the rest of the app from running in // this case like we do for TCP, do don't call reject. console.warn('could not bind to the desired ports for DNS'); Object.keys(failed).forEach(function (key) { console.log('[error bind]', key, failed[key].code); }); } else if (ports.length === 1) { config.dns.bind = parseInt(ports[0], 10); } else { config.dns.bind = ports.map(function (numStr) { return parseInt(numStr, 10); }); } resolve(); }); }); } PromiseA.all([tcpProm, dnsProm]) .then(function () { run(config); }) .catch(function (failed) { console.warn("could not bind to the desired ports"); Object.keys(failed).forEach(function (key) { console.log('[error bind]', key, failed[key].code); }); }); } function readEnv(args) { // TODO var env = { tunnel: process.env.GOLDILOCKS_TUNNEL_TOKEN || process.env.GOLDILOCKS_TUNNEL && true , email: process.env.GOLDILOCKS_EMAIL , cwd: process.env.GOLDILOCKS_HOME , debug: process.env.GOLDILOCKS_DEBUG && true }; args.cwd = args.cwd || env.cwd; Object.keys(env).forEach(function (key) { if ('undefined' === typeof args[key]) { args[key] = env[key]; } }); readConfigAndRun(args); } var program = require('commander'); program .version(require('../package.json').version) .option('--agree-tos [url1,url2]', "agree to all Terms of Service for Daplie, Let's Encrypt, etc (or specific URLs only)") .option('-c --config [file]', '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.') .option('--email ', "(Re)set default email to use for Daplie, Let's Encrypt, ACME, etc.") .option('--debug', "Enable debug output") .parse(process.argv); program.cwd = process.cwd(); readEnv(program);