v2.0.0
This commit is contained in:
		
							parent
							
								
									00ec1cba84
								
							
						
					
					
						commit
						5717445762
					
				
							
								
								
									
										180
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										180
									
								
								README.md
									
									
									
									
									
								
							@ -1,4 +1,178 @@
 | 
				
			|||||||
# letsencrypt-cluster
 | 
					letsencrypt-cluster
 | 
				
			||||||
Use automatic letsencrypt with node cluster.
 | 
					===================
 | 
				
			||||||
 | 
					
 | 
				
			||||||
(working on letsencrypt-express first)
 | 
					Use automatic letsencrypt with node on multiple cores or even multiple machines.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* Take advantage of multi-core computing
 | 
				
			||||||
 | 
					* Process certificates in master
 | 
				
			||||||
 | 
					* Serve https from multiple workers
 | 
				
			||||||
 | 
					* Can work with any clustering strategy [#1](https://github.com/coolaj86/letsencrypt-cluster/issues/1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Install
 | 
				
			||||||
 | 
					=======
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```bash
 | 
				
			||||||
 | 
					npm install --save letsencrypt-cluster@2.x
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Usage
 | 
				
			||||||
 | 
					=====
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					In a cluster environment you have some main file that boots your app
 | 
				
			||||||
 | 
					and then conditionally loads certain code based on whether that fork
 | 
				
			||||||
 | 
					is the master or just a worker.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					In such a file you might want to define some of the options that need
 | 
				
			||||||
 | 
					to be shared between both the master and the worker, like this:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					`boot.js`:
 | 
				
			||||||
 | 
					```javascript
 | 
				
			||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var cluster = require('cluster');
 | 
				
			||||||
 | 
					var path = require('path');
 | 
				
			||||||
 | 
					var os = require('os');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var main;
 | 
				
			||||||
 | 
					var sharedOptions = {
 | 
				
			||||||
 | 
					  webrootPath: path.join(os.tmpdir(), 'acme-challenge')			// /tmp/acme-challenge
 | 
				
			||||||
 | 
					                                                            // used by le-challenge-fs, the default plugin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					, renewWithin: 10 * 24 * 60 * 60 * 1000 										// 10 days before expiration
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					, debug: true
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if (cluster.isMaster) {
 | 
				
			||||||
 | 
					  main = require('./master');
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					else {
 | 
				
			||||||
 | 
					  main = require('./worker');
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					main.init(sharedOptions);
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Master
 | 
				
			||||||
 | 
					------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					We think it makes the most sense to load letsencrypt in master.
 | 
				
			||||||
 | 
					This can prevent race conditions (see [node-letsencrypt#45](https://github.com/coolaj86/node-letsencrypt/issues/45))
 | 
				
			||||||
 | 
					as only one process is writing the to file system or database at a time.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The main implementation detail here is `approveDomains(options, certs, cb)` for new domain certificates
 | 
				
			||||||
 | 
					and potentially `agreeToTerms(opts, cb)` for new accounts.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The master takes **the same arguments** as `node-letsencrypt`, plus a few extra:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					`master.js`:
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var cluster = require('cluster');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module.exports.init = function (sharedOpts) {
 | 
				
			||||||
 | 
					  var cores = require('os').cpus();
 | 
				
			||||||
 | 
					  var master = require('letsencrypt-cluster/master').create({
 | 
				
			||||||
 | 
					    debug: sharedOpts.debug
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  , server: 'staging'                                                       // CHANGE TO PRODUCTION
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  , renewWithin: sharedOpts.renewWithin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  , webrootPath: sharedOpts.webrootPath
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  , approveDomains: function (masterOptions, certs, cb) {
 | 
				
			||||||
 | 
					      // Do any work that must be done by master to approve this domain
 | 
				
			||||||
 | 
					      // (in this example, it's assumed to be done by the worker)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      var results = { domain: masterOptions.domain                          // required
 | 
				
			||||||
 | 
					                    , options: masterOptions                                // domains, email, agreeTos
 | 
				
			||||||
 | 
					                    , certs: certs };                                       // altnames, privkey, cert
 | 
				
			||||||
 | 
					      cb(null, results);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  cores.forEach(function () {
 | 
				
			||||||
 | 
					    var worker = cluster.fork();
 | 
				
			||||||
 | 
					    master.addWorker(worker);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Worker
 | 
				
			||||||
 | 
					------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The worker takes *similar* arguments to `node-letsencrypt`,
 | 
				
			||||||
 | 
					but only ones that are useful for determining certificate
 | 
				
			||||||
 | 
					renewal and for `le.challenge.get`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					If you want to  a non-default `le.challenge`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					`worker.js`:
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module.exports.init = function (sharedOpts) {
 | 
				
			||||||
 | 
					  var worker = require('../worker').create({
 | 
				
			||||||
 | 
					    debug: sharedOpts.debug
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  , renewWithin: sharedOpts.renewWithin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  , webrootPath: sharedOpts.webrootPath
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // , challenge: require('le-challenge-fs').create({ webrootPath: '...', ... })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  , approveDomains: function (workerOptions, certs, cb) {
 | 
				
			||||||
 | 
					      // opts = { domains, email, agreeTos, tosUrl }
 | 
				
			||||||
 | 
					      // certs = { subject, altnames, expiresAt, issuedAt }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      var results = {
 | 
				
			||||||
 | 
					        domain: workerOptions.domains[0]
 | 
				
			||||||
 | 
					      , options: {
 | 
				
			||||||
 | 
					          domains: workerOptions.domains
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      , certs: certs
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (certs) {
 | 
				
			||||||
 | 
					        // modify opts.domains to match the original request
 | 
				
			||||||
 | 
					        // email is not necessary, because the account already exists
 | 
				
			||||||
 | 
					        // this will only fail if the account has become corrupt
 | 
				
			||||||
 | 
					        results.options.domains = certs.altnames;
 | 
				
			||||||
 | 
					        cb(null, results);
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // This is where one would check one's application-specific database:
 | 
				
			||||||
 | 
					      //   1. Lookup the domain to see which email it belongs to
 | 
				
			||||||
 | 
					      //   2. Assign a default email if it isn't in the system
 | 
				
			||||||
 | 
					      //   3. If the email has no le account, `agreeToTerms` will fire unless `agreeTos` is preset
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      results.options.email = 'john.doe@example.com'
 | 
				
			||||||
 | 
					      results.options.agreeTos = true                                 // causes agreeToTerms to be skipped
 | 
				
			||||||
 | 
					      cb(null, results);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  function app(req, res) {
 | 
				
			||||||
 | 
					    res.end("Hello, World!");
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  var redirectHttps = require('redirect-https')();
 | 
				
			||||||
 | 
					  var plainServer = require('http').createServer(worker.middleware(redirectHttps));
 | 
				
			||||||
 | 
					  plainServer.listen(80);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  var server = require('https').createServer(worker.httpsOptions, worker.middleware(app));
 | 
				
			||||||
 | 
					  server.listen(443);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Message Passing
 | 
				
			||||||
 | 
					---------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The master and workers will communicate through `process.on('message', fn)`, `process.send({})`,
 | 
				
			||||||
 | 
					`worker.on('message', fn)`and `worker.send({})`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					All messages have a `type` property which is a string and begins with `LE_`.
 | 
				
			||||||
 | 
					All other messages are ignored.
 | 
				
			||||||
 | 
				
			|||||||
@ -9,8 +9,8 @@ module.exports.init = function (sharedOpts) {
 | 
				
			|||||||
    // We want both to renew well before the expiration date
 | 
					    // We want both to renew well before the expiration date
 | 
				
			||||||
    // and also to stagger the renewals, just a touch
 | 
					    // and also to stagger the renewals, just a touch
 | 
				
			||||||
    // here we specify to renew between 10 and 15 days
 | 
					    // here we specify to renew between 10 and 15 days
 | 
				
			||||||
  , notBefore: 15 * 24 * 60 * 60 * 1000
 | 
					  , renewWithin: sharedOpts.renewWithin
 | 
				
			||||||
  , notAfter: 10 * 24 * 60 * 60 * 1000 // optional
 | 
					  , renewBy: 10 * 24 * 60 * 60 * 1000 // optional
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										12
									
								
								index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								index.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,12 @@
 | 
				
			|||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					console.error("");
 | 
				
			||||||
 | 
					console.error("One does not simply require('letsencrypt-cluster');");
 | 
				
			||||||
 | 
					console.error("");
 | 
				
			||||||
 | 
					console.error("Usage:");
 | 
				
			||||||
 | 
					console.error("\trequire('letsencrypt-cluster/master').create({ ... });");
 | 
				
			||||||
 | 
					console.error("\trequire('letsencrypt-cluster/worker').create({ ... });");
 | 
				
			||||||
 | 
					console.error("");
 | 
				
			||||||
 | 
					console.error("");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					process.exit(1);
 | 
				
			||||||
@ -1,10 +1,11 @@
 | 
				
			|||||||
'use strict';
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// opts.addWorker(worker)
 | 
				
			||||||
 | 
					// opts.approveDomains(options, certs, cb)
 | 
				
			||||||
module.exports.create = function (opts) {
 | 
					module.exports.create = function (opts) {
 | 
				
			||||||
  if (!opts.letsencrypt) { opts.letsencrypt = require('letsencrypt').create({
 | 
					  opts = opts || { };
 | 
				
			||||||
    server: opts.server
 | 
					  opts.webrootPath = opts.webrootPath || require('os').tmpdir() + require('path').sep + 'acme-challenge';
 | 
				
			||||||
  , webrootPath: require('os').tmpdir() + require('path').sep + 'acme-challenge'
 | 
					  if (!opts.letsencrypt) { opts.letsencrypt = require('letsencrypt').create(opts); }
 | 
				
			||||||
  }); }
 | 
					 | 
				
			||||||
  if ('function' !== typeof opts.approveDomains) {
 | 
					  if ('function' !== typeof opts.approveDomains) {
 | 
				
			||||||
    throw new Error("You must provide opts.approveDomains(domain, certs, callback) to approve certificates");
 | 
					    throw new Error("You must provide opts.approveDomains(domain, certs, callback) to approve certificates");
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user