229 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			229 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
		
			Executable File
		
	
	
	
	
| #!/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.bind', config.tcp.bind);
 | |
|   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.resolve(cwd, args.config);
 | |
|     text = fs.readFileSync(filename, 'utf8');
 | |
|   }
 | |
|   else {
 | |
|     filename = path.resolve(cwd, 'goldilocks.yml');
 | |
| 
 | |
|     if (fs.existsSync(filename)) {
 | |
|       text = fs.readFileSync(filename, 'utf8');
 | |
|     }
 | |
|     else {
 | |
|       filename = path.resolve(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);
 | |
|       // blank config file
 | |
|       if ('undefined' === typeof config) {
 | |
|         config = {};
 | |
|       }
 | |
|     } catch(e) {
 | |
|       throw new Error(
 | |
|         "Could not load '" + filename + "' as JSON nor YAML"
 | |
|       );
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   var recase = require('recase').create({});
 | |
|   config = recase.camelCopy(config);
 | |
|   config.debug = config.debug || args.debug;
 | |
| 
 | |
|   if (!config.dns) {
 | |
|     config.dns = { modules: [{ name: 'proxy', port: 3053 }] };
 | |
|   }
 | |
|   // Use Object.assign to add any properties needed but not defined in the mdns config.
 | |
|   // It will first copy the defaults into an empty object, then copy any real config over that.
 | |
|   var mdnsDefaults = { port: 5353, broadcast: '224.0.0.251', ttl: 300 };
 | |
|   config.mdns = Object.assign({}, mdnsDefaults, config.mdns || {});
 | |
| 
 | |
|   if (!config.tcp) {
 | |
|     config.tcp = {};
 | |
|   }
 | |
|   if (!config.http) {
 | |
|     config.http = { modules: [{ name: 'proxy', domains: ['*'], port: 3000 }] };
 | |
|   }
 | |
|   if (!config.tls) {
 | |
|     config.tls = {};
 | |
|   }
 | |
|   if (!config.tls.acme && (args.email || args.agreeTos)) {
 | |
|     config.tls.acme = {};
 | |
|   }
 | |
|   if (typeof args.agreeTos === 'string') {
 | |
|     config.tls.acme.approvedDomains = args.agreeTos.split(',');
 | |
|   }
 | |
|   if (args.email) {
 | |
|     config.email = args.email;
 | |
|     config.tls.acme.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.bind) {
 | |
|     tcpProm = PromiseA.resolve();
 | |
|   } else {
 | |
|     tcpProm = new PromiseA(function (resolve, reject) {
 | |
|       require('../lib/check-ports').checkTcpPorts(function (failed, bound) {
 | |
|         config.tcp.bind = Object.keys(bound);
 | |
|         if (config.tcp.bind.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, so 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
 | |
|   try {
 | |
|     if (process.env.GOLDILOCKS_HOME) {
 | |
|       process.chdir(process.env.GOLDILOCKS_HOME);
 | |
|     }
 | |
|   } catch (err) {}
 | |
| 
 | |
|   var env = {
 | |
|     tunnel: process.env.GOLDILOCKS_TUNNEL_TOKEN || process.env.GOLDILOCKS_TUNNEL && true
 | |
|   , email: process.env.GOLDILOCKS_EMAIL
 | |
|   , cwd: process.env.GOLDILOCKS_HOME || process.cwd()
 | |
|   , debug: process.env.GOLDILOCKS_DEBUG && true
 | |
|   };
 | |
| 
 | |
|   readConfigAndRun(Object.assign({}, env, 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 <email>', "(Re)set default email to use for Daplie, Let's Encrypt, ACME, etc.")
 | |
|   .option('--debug', "Enable debug output")
 | |
|   .parse(process.argv);
 | |
| 
 | |
| readEnv(program);
 |