v2.0.0
This commit is contained in:
		
							父節點
							
								
									bcc63267e7
								
							
						
					
					
						當前提交
						8add5409c6
					
				
							
								
								
									
										180
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										180
									
								
								README.md
									
									
									
									
									
								
							@ -1,4 +1,178 @@
 | 
			
		||||
# letsencrypt-cluster
 | 
			
		||||
Use automatic letsencrypt with node cluster.
 | 
			
		||||
letsencrypt-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/Daplie/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/Daplie/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
 | 
			
		||||
    // and also to stagger the renewals, just a touch
 | 
			
		||||
    // here we specify to renew between 10 and 15 days
 | 
			
		||||
  , notBefore: 15 * 24 * 60 * 60 * 1000
 | 
			
		||||
  , notAfter: 10 * 24 * 60 * 60 * 1000 // optional
 | 
			
		||||
  , renewWithin: sharedOpts.renewWithin
 | 
			
		||||
  , 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';
 | 
			
		||||
 | 
			
		||||
// opts.addWorker(worker)
 | 
			
		||||
// opts.approveDomains(options, certs, cb)
 | 
			
		||||
module.exports.create = function (opts) {
 | 
			
		||||
  if (!opts.letsencrypt) { opts.letsencrypt = require('letsencrypt').create({
 | 
			
		||||
    server: opts.server
 | 
			
		||||
  , webrootPath: require('os').tmpdir() + require('path').sep + 'acme-challenge'
 | 
			
		||||
  }); }
 | 
			
		||||
  opts = opts || { };
 | 
			
		||||
  opts.webrootPath = opts.webrootPath || require('os').tmpdir() + require('path').sep + 'acme-challenge';
 | 
			
		||||
  if (!opts.letsencrypt) { opts.letsencrypt = require('letsencrypt').create(opts); }
 | 
			
		||||
  if ('function' !== typeof opts.approveDomains) {
 | 
			
		||||
    throw new Error("You must provide opts.approveDomains(domain, certs, callback) to approve certificates");
 | 
			
		||||
  }
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user