Greenlock Cluster -> Greenlock Express v3
This commit is contained in:
parent
76cbd1e52a
commit
edb882e792
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"bracketSpacing": true,
|
||||
"printWidth": 120,
|
||||
"tabWidth": 4,
|
||||
"trailingComma": "none",
|
||||
"useTabs": false
|
||||
}
|
194
README.md
194
README.md
|
@ -1,3 +1,41 @@
|
|||
# Replaced: Use Greenlock Express v3
|
||||
|
||||
See https://git.rootprojects.org/root/greenlock-express.js
|
||||
|
||||
```js
|
||||
"use strict";
|
||||
|
||||
var pkg = require("./package.json");
|
||||
require("greenlock-express")
|
||||
.init(function getConfig() {
|
||||
// Greenlock Config
|
||||
|
||||
return {
|
||||
package: { name: pkg.name, version: pkg.version },
|
||||
maintainerEmail: pkg.author,
|
||||
|
||||
// put cluster on full throttle!
|
||||
cloudnative: true
|
||||
webscale: true
|
||||
cluster: true
|
||||
};
|
||||
})
|
||||
.serve(httpsWorker);
|
||||
|
||||
function httpsWorker(glx) {
|
||||
// Serves on 80 and 443
|
||||
// Get's SSL certificates magically!
|
||||
|
||||
glx.serveApp(function(req, res) {
|
||||
res.end("Hello, Encrypted World!");
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
# OLD STUFF BELOW
|
||||
|
||||
# (Preserved for historical reference)
|
||||
|
||||
| A [Root](https://therootcompany.com) Project
|
||||
| [greenlock (lib)](https://git.coolaj86.com/coolaj86/greenlock.js)
|
||||
| [greenlock-cli](https://git.coolaj86.com/coolaj86/greenlock-cli.js)
|
||||
|
@ -7,27 +45,24 @@
|
|||
| [greenlock-hapi](https://git.coolaj86.com/coolaj86/greenlock-hapi.js)
|
||||
|
|
||||
|
||||
# greenlock-cluster
|
||||
|
||||
greenlock-cluster
|
||||
===================
|
||||
(previously letsencrypt-cluster)
|
||||
|
||||
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)
|
||||
- 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
|
||||
=======
|
||||
# Install
|
||||
|
||||
```bash
|
||||
npm install --save greenlock-cluster@2.x
|
||||
```
|
||||
|
||||
Usage
|
||||
=====
|
||||
# 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
|
||||
|
@ -37,35 +72,34 @@ 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');
|
||||
```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
|
||||
webrootPath: path.join(os.tmpdir(), "acme-challenge"), // /tmp/acme-challenge
|
||||
// used by le-challenge-fs, the default plugin
|
||||
|
||||
, renewWithin: 14 * 24 * 60 * 60 * 1000 // 10 days before expiration
|
||||
renewWithin: 14 * 24 * 60 * 60 * 1000, // 10 days before expiration
|
||||
|
||||
, debug: true
|
||||
debug: true
|
||||
};
|
||||
|
||||
if (cluster.isMaster) {
|
||||
main = require('./master');
|
||||
}
|
||||
else {
|
||||
main = require('./worker');
|
||||
main = require("./master");
|
||||
} else {
|
||||
main = require("./worker");
|
||||
}
|
||||
|
||||
main.init(sharedOptions);
|
||||
```
|
||||
|
||||
Master
|
||||
------
|
||||
## Master
|
||||
|
||||
We think it makes the most sense to load greenlock in master.
|
||||
This can prevent race conditions (see [node-letsencrypt#45](https://github.com/Daplie/node-letsencrypt/issues/45))
|
||||
|
@ -78,6 +112,7 @@ The master takes **the same arguments** as `node-greenlock` (`challenge`, `store
|
|||
plus a few extra (`approveDomains`... okay, just one extra):
|
||||
|
||||
`master.js`:
|
||||
|
||||
```javascript
|
||||
'use strict';
|
||||
|
||||
|
@ -120,75 +155,75 @@ All options are passed directly to `node-greenlock`
|
|||
(in other works, `leMaster` is a `greenlock` instance),
|
||||
but a few are only actually used by `greenlock-cluster`.
|
||||
|
||||
* `leOptions.approveDomains(options, certs, cb)` is special for `greenlock-cluster`, but will probably be included in `node-greenlock` in the future (no API change).
|
||||
- `leOptions.approveDomains(options, certs, cb)` is special for `greenlock-cluster`, but will probably be included in `node-greenlock` in the future (no API change).
|
||||
|
||||
* `leMaster.addWorker(worker)` is added by `greenlock-cluster` and **must be called** for each new worker.
|
||||
- `leMaster.addWorker(worker)` is added by `greenlock-cluster` and **must be called** for each new worker.
|
||||
|
||||
Worker
|
||||
------
|
||||
## Worker
|
||||
|
||||
The worker takes *similar* arguments to `node-greenlock`,
|
||||
The worker takes _similar_ arguments to `node-greenlock`,
|
||||
but only ones that are useful for determining certificate
|
||||
renewal and for `le.challenge.get`.
|
||||
|
||||
If you want to a non-default `le.challenge`
|
||||
If you want to a non-default `le.challenge`
|
||||
|
||||
`worker.js`:
|
||||
|
||||
```javascript
|
||||
'use strict';
|
||||
"use strict";
|
||||
|
||||
module.exports.init = function (sharedOpts) {
|
||||
var leWorker = require('greenlock-cluster/worker').create({
|
||||
debug: sharedOpts.debug
|
||||
module.exports.init = function(sharedOpts) {
|
||||
var leWorker = require("greenlock-cluster/worker").create({
|
||||
debug: sharedOpts.debug,
|
||||
|
||||
, renewWithin: sharedOpts.renewWithin
|
||||
renewWithin: sharedOpts.renewWithin,
|
||||
|
||||
, webrootPath: sharedOpts.webrootPath
|
||||
webrootPath: sharedOpts.webrootPath,
|
||||
|
||||
// , challenge: require('le-challenge-fs').create({ webrootPath: '...', ... })
|
||||
// , challenge: require('le-challenge-fs').create({ webrootPath: '...', ... })
|
||||
|
||||
, approveDomains: function (workerOptions, certs, cb) {
|
||||
// opts = { domains, email, agreeTos, tosUrl }
|
||||
// certs = { subject, altnames, expiresAt, issuedAt }
|
||||
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
|
||||
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);
|
||||
}
|
||||
, 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!");
|
||||
}
|
||||
});
|
||||
|
||||
function app(req, res) {
|
||||
res.end("Hello, World!");
|
||||
}
|
||||
var redirectHttps = require("redirect-https")();
|
||||
var plainServer = require("http").createServer(leWorker.middleware(redirectHttps));
|
||||
plainServer.listen(80);
|
||||
|
||||
var redirectHttps = require('redirect-https')();
|
||||
var plainServer = require('http').createServer(leWorker.middleware(redirectHttps));
|
||||
plainServer.listen(80);
|
||||
|
||||
var server = require('https').createServer(leWorker.httpsOptions, leWorker.middleware(app));
|
||||
server.listen(443);
|
||||
var server = require("https").createServer(leWorker.httpsOptions, leWorker.middleware(app));
|
||||
server.listen(443);
|
||||
};
|
||||
```
|
||||
|
||||
|
@ -197,16 +232,15 @@ module.exports.init = function (sharedOpts) {
|
|||
`node-greenlock` is **not used** directly by the worker,
|
||||
but certain options are shared because certain logic is duplicated.
|
||||
|
||||
* `leOptions.renewWithin` is shared so that the worker knows how earlier to request a new cert
|
||||
* `leOptions.renewBy` is passed to `le-sni-auto` so that it staggers renewals between `renewWithin` (latest) and `renewBy` (earlier)
|
||||
* `leWorker.middleware(nextApp)` uses `greenlock/middleware` for GET-ing `http-01`, hence `sharedOptions.webrootPath`
|
||||
* `leWorker.httpsOptions` has a default localhost certificate and the `SNICallback`.
|
||||
- `leOptions.renewWithin` is shared so that the worker knows how earlier to request a new cert
|
||||
- `leOptions.renewBy` is passed to `le-sni-auto` so that it staggers renewals between `renewWithin` (latest) and `renewBy` (earlier)
|
||||
- `leWorker.middleware(nextApp)` uses `greenlock/middleware` for GET-ing `http-01`, hence `sharedOptions.webrootPath`
|
||||
- `leWorker.httpsOptions` has a default localhost certificate and the `SNICallback`.
|
||||
|
||||
There are a few options that aren't shown in these examples, so if you need to change something
|
||||
that isn't shown here, look at the code (it's not that much) or open an issue.
|
||||
|
||||
Message Passing
|
||||
---------------
|
||||
## Message Passing
|
||||
|
||||
The master and workers will communicate through `process.on('message', fn)`, `process.send({})`,
|
||||
`worker.on('message', fn)`and `worker.send({})`.
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
greenlock cluster examples
|
||||
-------------------
|
||||
## greenlock cluster examples
|
||||
|
||||
First you need to change the email address in `examples/worker.js`.
|
||||
|
||||
|
|
|
@ -1,35 +1,29 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
var cluster = require('cluster');
|
||||
var cluster = require("cluster");
|
||||
|
||||
module.exports.init = function (sharedOpts) {
|
||||
var numCores = 2; // // Math.max(2, require('os').cpus().length)
|
||||
var i;
|
||||
var master = require('../master').create({
|
||||
debug: true
|
||||
module.exports.init = function(sharedOpts) {
|
||||
var numCores = 2; // // Math.max(2, require('os').cpus().length)
|
||||
var i;
|
||||
var master = require("../master").create({
|
||||
debug: true,
|
||||
|
||||
server: "staging",
|
||||
webrootPath: sharedOpts.webrootPath,
|
||||
|
||||
approveDomains: function(masterOptions, certs, cb) {
|
||||
// Depending on your setup it may be more efficient
|
||||
// for you to implement the approveDomains function
|
||||
// in your master or in your workers.
|
||||
//
|
||||
// Since we implement it in the worker (below) in this example
|
||||
// we'll give it an immediate approval here in the master
|
||||
var results = { domain: masterOptions.domain, options: masterOptions, certs: certs };
|
||||
cb(null, results);
|
||||
}
|
||||
});
|
||||
|
||||
, server: 'staging'
|
||||
, webrootPath: sharedOpts.webrootPath
|
||||
|
||||
|
||||
|
||||
, approveDomains: function (masterOptions, certs, cb) {
|
||||
// Depending on your setup it may be more efficient
|
||||
// for you to implement the approveDomains function
|
||||
// in your master or in your workers.
|
||||
//
|
||||
// Since we implement it in the worker (below) in this example
|
||||
// we'll give it an immediate approval here in the master
|
||||
var results = { domain: masterOptions.domain, options: masterOptions, certs: certs };
|
||||
cb(null, results);
|
||||
for (i = 0; i < numCores; i += 1) {
|
||||
master.addWorker(cluster.fork());
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
for (i = 0; i < numCores; i += 1) {
|
||||
master.addWorker(cluster.fork());
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,33 +1,27 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
var cluster = require('cluster');
|
||||
var cluster = require("cluster");
|
||||
var main;
|
||||
|
||||
|
||||
|
||||
// You'll often see examples where people use cluster
|
||||
// master and worker all in the same file, which is fine,
|
||||
// but in order to conserve memory and especially to be
|
||||
// less confusing, I'm splitting the code into two files
|
||||
if (cluster.isMaster) {
|
||||
main = require('./master');
|
||||
main = require("./master");
|
||||
} else {
|
||||
main = require("./worker");
|
||||
}
|
||||
else {
|
||||
main = require('./worker');
|
||||
}
|
||||
|
||||
|
||||
|
||||
// this is nothing greenlock-cluster specific
|
||||
// I'm just arbitrarily choosing to share some configuration
|
||||
// that I know I'm going to use in both places
|
||||
main.init({
|
||||
// Depending on the strategy, the whole le-challenge-<<strategy>>
|
||||
// could be shared between worker and server, but since I'm just
|
||||
// using using le-challenge-fs (as you'll see), I'm only sharing the webrootPath
|
||||
webrootPath: require("os").tmpdir() + require("path").sep + "acme-challenge",
|
||||
|
||||
// Depending on the strategy, the whole le-challenge-<<strategy>>
|
||||
// could be shared between worker and server, but since I'm just
|
||||
// using using le-challenge-fs (as you'll see), I'm only sharing the webrootPath
|
||||
webrootPath: require('os').tmpdir() + require('path').sep + 'acme-challenge'
|
||||
|
||||
// this is used both by node-greenlock (master) and le-sni-auto (worker)
|
||||
, renewWithin: 15 * 24 * 60 * 60 * 1000
|
||||
// this is used both by node-greenlock (master) and le-sni-auto (worker)
|
||||
renewWithin: 15 * 24 * 60 * 60 * 1000
|
||||
});
|
||||
|
|
|
@ -1,24 +1,18 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
module.exports.init = function (sharedOpts) {
|
||||
var worker = require('../worker').create({
|
||||
debug: true
|
||||
module.exports.init = function(sharedOpts) {
|
||||
var worker = require("../worker").create({
|
||||
debug: true,
|
||||
|
||||
// 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
|
||||
renewWithin: sharedOpts.renewWithin,
|
||||
renewBy: 10 * 24 * 60 * 60 * 1000, // optional
|
||||
|
||||
webrootPath: sharedOpts.webrootPath,
|
||||
|
||||
// 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
|
||||
, renewWithin: sharedOpts.renewWithin
|
||||
, renewBy: 10 * 24 * 60 * 60 * 1000 // optional
|
||||
|
||||
|
||||
|
||||
, webrootPath: sharedOpts.webrootPath
|
||||
|
||||
|
||||
|
||||
/*
|
||||
/*
|
||||
challenge: {
|
||||
get: function (ignored, domain, token, cb) {
|
||||
cb(null, keyAuthorization);
|
||||
|
@ -33,55 +27,48 @@ module.exports.init = function (sharedOpts) {
|
|||
}
|
||||
*/
|
||||
|
||||
// There are two approval processes:
|
||||
// 1. emails are tied to private keys (accounts) which must agree to the tos url
|
||||
// 2. domains are tied to accounts (and should be verifiable via loopback)
|
||||
approveDomains: function(workerOptions, certs, cb) {
|
||||
// opts = { domains, email, agreeTos, tosUrl }
|
||||
// certs = { subject, altnames, expiresAt, issuedAt }
|
||||
var results = {
|
||||
domain: workerOptions.domains[0],
|
||||
options: {
|
||||
domains: (certs && certs.altnames) || workerOptions.domains,
|
||||
email: "john.doe@example.com",
|
||||
agreeTos: true
|
||||
},
|
||||
certs: certs
|
||||
};
|
||||
|
||||
// There are two approval processes:
|
||||
// 1. emails are tied to private keys (accounts) which must agree to the tos url
|
||||
// 2. domains are tied to accounts (and should be verifiable via loopback)
|
||||
, approveDomains: function (workerOptions, certs, cb) {
|
||||
// opts = { domains, email, agreeTos, tosUrl }
|
||||
// certs = { subject, altnames, expiresAt, issuedAt }
|
||||
var results = {
|
||||
domain: workerOptions.domains[0]
|
||||
, options: {
|
||||
domains: certs && certs.altnames || workerOptions.domains
|
||||
, email: 'john.doe@example.com'
|
||||
, agreeTos: true
|
||||
// We might want to do a check to make sure that all of the domains
|
||||
// specified in altnames are still approved to be renewed and have
|
||||
// the correct dns entries, but generally speaking it's probably okay
|
||||
// for renewals to be automatic
|
||||
if (certs) {
|
||||
// modify opts.domains to overwrite certs.altnames in renewal
|
||||
cb(null, results);
|
||||
return;
|
||||
}
|
||||
|
||||
// This is where we would check our database to make sure that
|
||||
// this user (specified by email address) has agreed to the terms
|
||||
// and do some check that they have access to this domain
|
||||
cb(null, results);
|
||||
}
|
||||
, certs: certs
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
|
||||
// We might want to do a check to make sure that all of the domains
|
||||
// specified in altnames are still approved to be renewed and have
|
||||
// the correct dns entries, but generally speaking it's probably okay
|
||||
// for renewals to be automatic
|
||||
if (certs) {
|
||||
// modify opts.domains to overwrite certs.altnames in renewal
|
||||
cb(null, results);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// This is where we would check our database to make sure that
|
||||
// this user (specified by email address) has agreed to the terms
|
||||
// and do some check that they have access to this domain
|
||||
cb(null, results);
|
||||
function app(req, res) {
|
||||
res.end("Hello, World!");
|
||||
}
|
||||
});
|
||||
|
||||
function app(req, res) {
|
||||
res.end("Hello, World!");
|
||||
}
|
||||
|
||||
|
||||
// worker.handleAcmeOrRedirectToHttps()
|
||||
// worker.handleAcmeOrUse(app)
|
||||
var redirectHttps = require('redirect-https')();
|
||||
var plainServer = require('http').createServer(worker.middleware(redirectHttps));
|
||||
var server = require('https').createServer(worker.httpsOptions, worker.middleware(app));
|
||||
plainServer.listen(80);
|
||||
server.listen(443);
|
||||
// worker.handleAcmeOrRedirectToHttps()
|
||||
// worker.handleAcmeOrUse(app)
|
||||
var redirectHttps = require("redirect-https")();
|
||||
var plainServer = require("http").createServer(worker.middleware(redirectHttps));
|
||||
var server = require("https").createServer(worker.httpsOptions, worker.middleware(app));
|
||||
plainServer.listen(80);
|
||||
server.listen(443);
|
||||
};
|
||||
|
|
13
index.js
13
index.js
|
@ -1,12 +1,3 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
console.error("");
|
||||
console.error("One does not simply require('greenlock-cluster');");
|
||||
console.error("");
|
||||
console.error("Usage:");
|
||||
console.error("\trequire('greenlock-cluster/master').create({ ... });");
|
||||
console.error("\trequire('greenlock-cluster/worker').create({ ... });");
|
||||
console.error("");
|
||||
console.error("");
|
||||
|
||||
process.exit(1);
|
||||
module.exports = require("@root/greenlock-express");
|
||||
|
|
136
master.js
136
master.js
|
@ -1,58 +1,60 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
// opts.addWorker(worker)
|
||||
// opts.approveDomains(options, certs, cb)
|
||||
module.exports.create = function (opts) {
|
||||
opts = opts || { };
|
||||
opts._workers = [];
|
||||
opts.webrootPath = opts.webrootPath || require('os').tmpdir() + require('path').sep + 'acme-challenge';
|
||||
if (!opts.greenlock) { opts.greenlock = require('greenlock').create(opts); }
|
||||
if ('function' !== typeof opts.approveDomains) {
|
||||
throw new Error("You must provide opts.approveDomains(domain, certs, callback) to approve certificates");
|
||||
}
|
||||
|
||||
function log(debug) {
|
||||
if (!debug) {
|
||||
return;
|
||||
module.exports.create = function(opts) {
|
||||
opts = opts || {};
|
||||
opts._workers = [];
|
||||
opts.webrootPath = opts.webrootPath || require("os").tmpdir() + require("path").sep + "acme-challenge";
|
||||
if (!opts.greenlock) {
|
||||
opts.greenlock = require("greenlock").create(opts);
|
||||
}
|
||||
if ("function" !== typeof opts.approveDomains) {
|
||||
throw new Error("You must provide opts.approveDomains(domain, certs, callback) to approve certificates");
|
||||
}
|
||||
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
args.shift();
|
||||
args.unshift("[le/lib/core.js]");
|
||||
console.log.apply(console, args);
|
||||
}
|
||||
|
||||
opts.addWorker = function (worker) {
|
||||
opts._workers.push(worker);
|
||||
|
||||
worker.on('online', function () {
|
||||
log(opts.debug, 'worker is up');
|
||||
});
|
||||
|
||||
worker.on('message', function (msg) {
|
||||
log(opts.debug, 'Message from worker ' + worker.id);
|
||||
if ('LE_REQUEST' !== (msg && msg.type)) {
|
||||
log(opts.debug, 'Ignoring irrelevant message');
|
||||
log(opts.debug, msg);
|
||||
return;
|
||||
}
|
||||
|
||||
log(opts.debug, 'about to approveDomains');
|
||||
opts.approveDomains(msg.options, msg.certs, function (err, results) {
|
||||
if (err) {
|
||||
log(opts.debug, 'Approval got ERROR', err.stack || err);
|
||||
worker.send({
|
||||
type: 'LE_RESPONSE'
|
||||
, domain: msg.domain
|
||||
, error: { message: err.message, code: err.code, stack: err.stack }
|
||||
});
|
||||
return;
|
||||
function log(debug) {
|
||||
if (!debug) {
|
||||
return;
|
||||
}
|
||||
|
||||
var promise;
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
args.shift();
|
||||
args.unshift("[le/lib/core.js]");
|
||||
console.log.apply(console, args);
|
||||
}
|
||||
|
||||
//
|
||||
/*
|
||||
opts.addWorker = function(worker) {
|
||||
opts._workers.push(worker);
|
||||
|
||||
worker.on("online", function() {
|
||||
log(opts.debug, "worker is up");
|
||||
});
|
||||
|
||||
worker.on("message", function(msg) {
|
||||
log(opts.debug, "Message from worker " + worker.id);
|
||||
if ("LE_REQUEST" !== (msg && msg.type)) {
|
||||
log(opts.debug, "Ignoring irrelevant message");
|
||||
log(opts.debug, msg);
|
||||
return;
|
||||
}
|
||||
|
||||
log(opts.debug, "about to approveDomains");
|
||||
opts.approveDomains(msg.options, msg.certs, function(err, results) {
|
||||
if (err) {
|
||||
log(opts.debug, "Approval got ERROR", err.stack || err);
|
||||
worker.send({
|
||||
type: "LE_RESPONSE",
|
||||
domain: msg.domain,
|
||||
error: { message: err.message, code: err.code, stack: err.stack }
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
var promise;
|
||||
|
||||
//
|
||||
/*
|
||||
var certs = require('localhost.example.com-certificates').merge({
|
||||
subject: msg.domain
|
||||
, altnames: [ msg.domain ]
|
||||
|
@ -66,26 +68,28 @@ module.exports.create = function (opts) {
|
|||
return;
|
||||
// */
|
||||
|
||||
if (results.certs) {
|
||||
promise = opts.greenlock.renew(results.options, results.certs);
|
||||
}
|
||||
else {
|
||||
promise = opts.greenlock.register(results.options);
|
||||
}
|
||||
if (results.certs) {
|
||||
promise = opts.greenlock.renew(results.options, results.certs);
|
||||
} else {
|
||||
promise = opts.greenlock.register(results.options);
|
||||
}
|
||||
|
||||
promise.then(function (certs) {
|
||||
log(opts.debug, 'Approval got certs', certs);
|
||||
// certs = { subject, domains, issuedAt, expiresAt, privkey, cert, chain };
|
||||
opts._workers.forEach(function (w) {
|
||||
w.send({ type: 'LE_RESPONSE', domain: msg.domain, certs: certs });
|
||||
});
|
||||
}, function (err) {
|
||||
log(opts.debug, 'Approval got ERROR', err.stack || err);
|
||||
worker.send({ type: 'LE_RESPONSE', domain: msg.domain, error: err });
|
||||
promise.then(
|
||||
function(certs) {
|
||||
log(opts.debug, "Approval got certs", certs);
|
||||
// certs = { subject, domains, issuedAt, expiresAt, privkey, cert, chain };
|
||||
opts._workers.forEach(function(w) {
|
||||
w.send({ type: "LE_RESPONSE", domain: msg.domain, certs: certs });
|
||||
});
|
||||
},
|
||||
function(err) {
|
||||
log(opts.debug, "Approval got ERROR", err.stack || err);
|
||||
worker.send({ type: "LE_RESPONSE", domain: msg.domain, error: err });
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
return opts;
|
||||
return opts;
|
||||
};
|
||||
|
|
|
@ -0,0 +1,149 @@
|
|||
{
|
||||
"name": "greenlock-cluster",
|
||||
"version": "3.0.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"@root/acme": {
|
||||
"version": "3.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@root/acme/-/acme-3.0.8.tgz",
|
||||
"integrity": "sha512-VmBvLvWdCDkolkanI9Dzm1ouSWPaAa2eCCwcDZcVQbWoNiUIOqbbd57fcMA/gZxLyuJPStD2WXFuEuSMPDxcww==",
|
||||
"requires": {
|
||||
"@root/encoding": "^1.0.1",
|
||||
"@root/keypairs": "^0.9.0",
|
||||
"@root/pem": "^1.0.4",
|
||||
"@root/request": "^1.3.11",
|
||||
"@root/x509": "^0.7.2"
|
||||
}
|
||||
},
|
||||
"@root/asn1": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@root/asn1/-/asn1-1.0.0.tgz",
|
||||
"integrity": "sha512-0lfZNuOULKJDJmdIkP8V9RnbV3XaK6PAHD3swnFy4tZwtlMDzLKoM/dfNad7ut8Hu3r91wy9uK0WA/9zym5mig==",
|
||||
"requires": {
|
||||
"@root/encoding": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"@root/csr": {
|
||||
"version": "0.8.1",
|
||||
"resolved": "https://registry.npmjs.org/@root/csr/-/csr-0.8.1.tgz",
|
||||
"integrity": "sha512-hKl0VuE549TK6SnS2Yn9nRvKbFZXn/oAg+dZJU/tlKl/f/0yRXeuUzf8akg3JjtJq+9E592zDqeXZ7yyrg8fSQ==",
|
||||
"requires": {
|
||||
"@root/asn1": "^1.0.0",
|
||||
"@root/pem": "^1.0.4",
|
||||
"@root/x509": "^0.7.2"
|
||||
}
|
||||
},
|
||||
"@root/encoding": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@root/encoding/-/encoding-1.0.1.tgz",
|
||||
"integrity": "sha512-OaEub02ufoU038gy6bsNHQOjIn8nUjGiLcaRmJ40IUykneJkIW5fxDqKxQx48cszuNflYldsJLPPXCrGfHs8yQ=="
|
||||
},
|
||||
"@root/greenlock": {
|
||||
"version": "3.0.25",
|
||||
"resolved": "https://registry.npmjs.org/@root/greenlock/-/greenlock-3.0.25.tgz",
|
||||
"integrity": "sha512-VC8H9MTkbqxlB2LGntmcq5cstkE0TdZLvxm25SO5i7c6abJBVMQafhTD415OXwoGimnmWTn6SZ93Fj73d9QX/w==",
|
||||
"requires": {
|
||||
"@root/acme": "^3.0.8",
|
||||
"@root/csr": "^0.8.1",
|
||||
"@root/keypairs": "^0.9.0",
|
||||
"@root/mkdirp": "^1.0.0",
|
||||
"@root/request": "^1.3.10",
|
||||
"acme-http-01-standalone": "^3.0.5",
|
||||
"cert-info": "^1.5.1",
|
||||
"greenlock-manager-fs": "^3.0.1",
|
||||
"greenlock-store-fs": "^3.2.0",
|
||||
"safe-replace": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"@root/greenlock-express": {
|
||||
"version": "3.0.13",
|
||||
"resolved": "https://registry.npmjs.org/@root/greenlock-express/-/greenlock-express-3.0.13.tgz",
|
||||
"integrity": "sha512-SgFsP4rBDPRBp52yqb8kONw7ZCkgyYrBFJLg4xhfIMbsMct4dfqB+N5eJbeF/exJh4+BHM7tppvf31Xuz6EO2Q==",
|
||||
"requires": {
|
||||
"@root/greenlock": "^3.0.25",
|
||||
"redirect-https": "^1.1.5"
|
||||
}
|
||||
},
|
||||
"@root/keypairs": {
|
||||
"version": "0.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@root/keypairs/-/keypairs-0.9.0.tgz",
|
||||
"integrity": "sha512-NXE2L9Gv7r3iC4kB/gTPZE1vO9Ox/p14zDzAJ5cGpTpytbWOlWF7QoHSJbtVX4H7mRG/Hp7HR3jWdWdb2xaaXg==",
|
||||
"requires": {
|
||||
"@root/encoding": "^1.0.1",
|
||||
"@root/pem": "^1.0.4",
|
||||
"@root/x509": "^0.7.2"
|
||||
}
|
||||
},
|
||||
"@root/mkdirp": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@root/mkdirp/-/mkdirp-1.0.0.tgz",
|
||||
"integrity": "sha512-hxGAYUx5029VggfG+U9naAhQkoMSXtOeXtbql97m3Hi6/sQSRL/4khKZPyOF6w11glyCOU38WCNLu9nUcSjOfA=="
|
||||
},
|
||||
"@root/pem": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@root/pem/-/pem-1.0.4.tgz",
|
||||
"integrity": "sha512-rEUDiUsHtild8GfIjFE9wXtcVxeS+ehCJQBwbQQ3IVfORKHK93CFnRtkr69R75lZFjcmKYVc+AXDB+AeRFOULA=="
|
||||
},
|
||||
"@root/request": {
|
||||
"version": "1.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@root/request/-/request-1.4.2.tgz",
|
||||
"integrity": "sha512-J8FM4+SJuc7WRC+Jz17m+VT2lgI7HtatHhxN1F2ck5aIKUAxJEaR4u/gLBsgT60mVHevKCjKN0O8115UtJjwLw=="
|
||||
},
|
||||
"@root/x509": {
|
||||
"version": "0.7.2",
|
||||
"resolved": "https://registry.npmjs.org/@root/x509/-/x509-0.7.2.tgz",
|
||||
"integrity": "sha512-ENq3LGYORK5NiMFHEVeNMt+fTXaC7DTS6sQXoqV+dFdfT0vmiL5cDLjaXQhaklJQq0NiwicZegzJRl1ZOTp3WQ==",
|
||||
"requires": {
|
||||
"@root/asn1": "^1.0.0",
|
||||
"@root/encoding": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"acme-http-01-standalone": {
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/acme-http-01-standalone/-/acme-http-01-standalone-3.0.5.tgz",
|
||||
"integrity": "sha512-W4GfK+39GZ+u0mvxRVUcVFCG6gposfzEnSBF20T/NUwWAKG59wQT1dUbS1NixRIAsRuhpGc4Jx659cErFQH0Pg=="
|
||||
},
|
||||
"cert-info": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/cert-info/-/cert-info-1.5.1.tgz",
|
||||
"integrity": "sha512-eoQC/yAgW3gKTKxjzyClvi+UzuY97YCjcl+lSqbsGIy7HeGaWxCPOQFivhUYm27hgsBMhsJJFya3kGvK6PMIcQ=="
|
||||
},
|
||||
"escape-html": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
||||
"integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
|
||||
},
|
||||
"greenlock-manager-fs": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/greenlock-manager-fs/-/greenlock-manager-fs-3.0.1.tgz",
|
||||
"integrity": "sha512-vZfGFq1TTKxaAqdGDUwNservrNzXx0xCwT/ovG/N378GrhS+U5S8B8LUlNtQU7Fdw6RToMiBcm22OOxSrvZ2zw==",
|
||||
"requires": {
|
||||
"@root/mkdirp": "^1.0.0",
|
||||
"safe-replace": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"greenlock-store-fs": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/greenlock-store-fs/-/greenlock-store-fs-3.2.0.tgz",
|
||||
"integrity": "sha512-zqcPnF+173oYq5qU7FoGtuqeG8dmmvAiSnz98kEHAHyvgRF9pE1T0MM0AuqDdj45I3kXlCj2gZBwutnRi37J3g==",
|
||||
"requires": {
|
||||
"@root/mkdirp": "^1.0.0",
|
||||
"safe-replace": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"redirect-https": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/redirect-https/-/redirect-https-1.3.0.tgz",
|
||||
"integrity": "sha512-9GzwI/+Cqw3jlSg0CW6TgBQbhiVhkHSDvW8wjgRQ9IK34wtxS71YJiQeazSCSEqbvowHCJuQZgmQFl1xUHKEgg==",
|
||||
"requires": {
|
||||
"escape-html": "^1.0.3"
|
||||
}
|
||||
},
|
||||
"safe-replace": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/safe-replace/-/safe-replace-1.1.0.tgz",
|
||||
"integrity": "sha512-9/V2E0CDsKs9DWOOwJH7jYpSl9S3N05uyevNjvsnDauBqRowBPOyot1fIvV5N2IuZAbYyvrTXrYFVG0RZInfFw=="
|
||||
}
|
||||
}
|
||||
}
|
80
package.json
80
package.json
|
@ -1,44 +1,40 @@
|
|||
{
|
||||
"name": "greenlock-cluster",
|
||||
"version": "2.1.0",
|
||||
"description": "Use automatic letsencrypt (free ssl certs) on multiple cores or even multiple machines",
|
||||
"main": "index.js",
|
||||
"directories": {
|
||||
"example": "examples"
|
||||
},
|
||||
"dependencies": {
|
||||
"le-sni-auto": "^2.0.1",
|
||||
"greenlock": "^2.0.4",
|
||||
"redirect-https": "^1.1.0"
|
||||
},
|
||||
"devDependencies": {},
|
||||
"scripts": {
|
||||
"test": "node examples/serve.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://git.coolaj86.com/coolaj86/greenlock-cluster.js.git"
|
||||
},
|
||||
"keywords": [
|
||||
"cluster",
|
||||
"acme",
|
||||
"le",
|
||||
"multi-core",
|
||||
"cloud",
|
||||
"scale",
|
||||
"free",
|
||||
"ssl",
|
||||
"https",
|
||||
"tls",
|
||||
"letsencrypt",
|
||||
"node",
|
||||
"greenlock",
|
||||
"node.js"
|
||||
],
|
||||
"author": "AJ ONeal <coolaj86@gmail.com> (https://coolaj86.com/)",
|
||||
"license": "(MIT OR Apache-2.0)",
|
||||
"bugs": {
|
||||
"url": "https://git.coolaj86.com/coolaj86/greenlock-cluster.js/issues"
|
||||
},
|
||||
"homepage": "https://git.coolaj86.com/coolaj86/greenlock-cluster.js"
|
||||
"name": "greenlock-cluster",
|
||||
"version": "3.0.0",
|
||||
"description": "Use automatic letsencrypt (free ssl certs) on multiple cores or even multiple machines",
|
||||
"main": "index.js",
|
||||
"directories": {
|
||||
"example": "examples"
|
||||
},
|
||||
"dependencies": {
|
||||
"@root/greenlock-express": "^3.0.13"
|
||||
},
|
||||
"devDependencies": {},
|
||||
"scripts": {},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://git.coolaj86.com/coolaj86/greenlock-cluster.js.git"
|
||||
},
|
||||
"keywords": [
|
||||
"cluster",
|
||||
"acme",
|
||||
"le",
|
||||
"multi-core",
|
||||
"cloud",
|
||||
"scale",
|
||||
"free",
|
||||
"ssl",
|
||||
"https",
|
||||
"tls",
|
||||
"letsencrypt",
|
||||
"node",
|
||||
"greenlock",
|
||||
"node.js"
|
||||
],
|
||||
"author": "AJ ONeal <coolaj86@gmail.com> (https://coolaj86.com/)",
|
||||
"license": "MPL-2.0",
|
||||
"bugs": {
|
||||
"url": "https://git.coolaj86.com/coolaj86/greenlock-cluster.js/issues"
|
||||
},
|
||||
"homepage": "https://git.coolaj86.com/coolaj86/greenlock-cluster.js"
|
||||
}
|
||||
|
|
130
worker.js
130
worker.js
|
@ -1,88 +1,78 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
function log(debug) {
|
||||
if (!debug) {
|
||||
return;
|
||||
}
|
||||
if (!debug) {
|
||||
return;
|
||||
}
|
||||
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
args.shift();
|
||||
args.unshift("[le/lib/core.js]");
|
||||
console.log.apply(console, args);
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
args.shift();
|
||||
args.unshift("[le/lib/core.js]");
|
||||
console.log.apply(console, args);
|
||||
}
|
||||
|
||||
|
||||
|
||||
module.exports.create = function (opts) {
|
||||
|
||||
// if another worker updates the certs,
|
||||
// receive a copy from master here as well
|
||||
// and update the sni cache manually
|
||||
process.on('message', function (msg) {
|
||||
if ('LE_RESPONSE' === msg.type && msg.certs) {
|
||||
opts.sni.cacheCerts(msg.certs);
|
||||
}
|
||||
});
|
||||
|
||||
opts.sni = require('le-sni-auto').create({
|
||||
renewWithin: opts.renewWithin || (10 * 24 * 60 * 60 * 1000)
|
||||
, renewBy: opts.renewBy || (5 * 24 * 60 * 60 * 1000)
|
||||
, getCertificates: function (domain, certs, cb) {
|
||||
var workerOptions = { domains: [ domain ] };
|
||||
opts.approveDomains(workerOptions, certs, function (_err, results) {
|
||||
if (_err) {
|
||||
cb(_err);
|
||||
return;
|
||||
module.exports.create = function(opts) {
|
||||
// if another worker updates the certs,
|
||||
// receive a copy from master here as well
|
||||
// and update the sni cache manually
|
||||
process.on("message", function(msg) {
|
||||
if ("LE_RESPONSE" === msg.type && msg.certs) {
|
||||
opts.sni.cacheCerts(msg.certs);
|
||||
}
|
||||
});
|
||||
|
||||
process.send({ type: 'LE_REQUEST', domain: domain, options: results.options, certs: results.certs });
|
||||
opts.sni = require("le-sni-auto").create({
|
||||
renewWithin: opts.renewWithin || 10 * 24 * 60 * 60 * 1000,
|
||||
renewBy: opts.renewBy || 5 * 24 * 60 * 60 * 1000,
|
||||
getCertificates: function(domain, certs, cb) {
|
||||
var workerOptions = { domains: [domain] };
|
||||
opts.approveDomains(workerOptions, certs, function(_err, results) {
|
||||
if (_err) {
|
||||
cb(_err);
|
||||
return;
|
||||
}
|
||||
|
||||
process.on('message', function (msg) {
|
||||
var err = new Error("___MESSAGE___");
|
||||
process.send({ type: "LE_REQUEST", domain: domain, options: results.options, certs: results.certs });
|
||||
|
||||
log(opts.debug, 'Message from master');
|
||||
log(opts.debug, msg);
|
||||
process.on("message", function(msg) {
|
||||
var err = new Error("___MESSAGE___");
|
||||
|
||||
if (msg.domain !== domain) {
|
||||
return;
|
||||
}
|
||||
log(opts.debug, "Message from master");
|
||||
log(opts.debug, msg);
|
||||
|
||||
if (msg.error) {
|
||||
err.message = msg.error.message || "unknown error sent from cluster master to worker";
|
||||
err.stack.replace("___MESSAGE___", err.message);
|
||||
err = {
|
||||
message: err.message
|
||||
, stack: err.stack
|
||||
, data: { options: workerOptions, certs: certs }
|
||||
};
|
||||
} else {
|
||||
err = null;
|
||||
}
|
||||
if (msg.domain !== domain) {
|
||||
return;
|
||||
}
|
||||
|
||||
cb(err, msg.certs);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
if (msg.error) {
|
||||
err.message = msg.error.message || "unknown error sent from cluster master to worker";
|
||||
err.stack.replace("___MESSAGE___", err.message);
|
||||
err = {
|
||||
message: err.message,
|
||||
stack: err.stack,
|
||||
data: { options: workerOptions, certs: certs }
|
||||
};
|
||||
} else {
|
||||
err = null;
|
||||
}
|
||||
|
||||
cb(err, msg.certs);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
opts.httpsOptions = { SNICallback: opts.sni.sniCallback };
|
||||
|
||||
opts.httpsOptions = { SNICallback: opts.sni.sniCallback };
|
||||
opts.challenge = {
|
||||
get:
|
||||
opts.getChallenge ||
|
||||
(opts.challenge && opts.challenge.get) ||
|
||||
require("le-challenge-fs").create({ webrootPath: opts.webrootPath }).get
|
||||
};
|
||||
|
||||
// opts.challenge.get, opts.acmeChallengePrefix
|
||||
opts.middleware = require("greenlock/lib/middleware").create(opts);
|
||||
|
||||
|
||||
opts.challenge = {
|
||||
get: opts.getChallenge
|
||||
|| (opts.challenge && opts.challenge.get)
|
||||
|| require('le-challenge-fs').create({ webrootPath: opts.webrootPath }).get
|
||||
};
|
||||
|
||||
|
||||
|
||||
// opts.challenge.get, opts.acmeChallengePrefix
|
||||
opts.middleware = require('greenlock/lib/middleware').create(opts);
|
||||
|
||||
|
||||
|
||||
return opts;
|
||||
return opts;
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue