mirror of
				https://github.com/therootcompany/greenlock-express.js.git
				synced 2024-11-16 17:28:59 +00:00 
			
		
		
		
	v2.0.0
This commit is contained in:
		
							parent
							
								
									913cf7a3fd
								
							
						
					
					
						commit
						ed49e5556c
					
				
							
								
								
									
										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/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
 |     // 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