250 lines
7.8 KiB
Markdown
250 lines
7.8 KiB
Markdown
# 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)
|
|
| [greenlock-express](https://git.coolaj86.com/coolaj86/greenlock-express.js)
|
|
| **greenlock-cluster**
|
|
| [greenlock-koa](https://git.coolaj86.com/coolaj86/greenlock-koa.js)
|
|
| [greenlock-hapi](https://git.coolaj86.com/coolaj86/greenlock-hapi.js)
|
|
|
|
|
|
|
# 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)
|
|
|
|
# Install
|
|
|
|
```bash
|
|
npm install --save greenlock-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: 14 * 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 greenlock 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-greenlock` (`challenge`, `store`, etc),
|
|
plus a few extra (`approveDomains`... okay, just one extra):
|
|
|
|
`master.js`:
|
|
|
|
```javascript
|
|
'use strict';
|
|
|
|
var cluster = require('cluster');
|
|
|
|
module.exports.init = function (sharedOpts) {
|
|
var cores = require('os').cpus();
|
|
var leMaster = require('greenlock-cluster/master').create({
|
|
debug: sharedOpts.debug
|
|
|
|
// You MUST change this to 'https://acme-v02.api.letsencrypt.org/directory' in production
|
|
server: 'https://acme-staging-v02.api.letsencrypt.org/directory'
|
|
, version: 'draft-11' // Let's Encrypt v2
|
|
|
|
, 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();
|
|
leMaster.addWorker(worker);
|
|
});
|
|
};
|
|
```
|
|
|
|
### API
|
|
|
|
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).
|
|
|
|
- `leMaster.addWorker(worker)` is added by `greenlock-cluster` and **must be called** for each new worker.
|
|
|
|
## Worker
|
|
|
|
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`
|
|
|
|
`worker.js`:
|
|
|
|
```javascript
|
|
"use strict";
|
|
|
|
module.exports.init = function(sharedOpts) {
|
|
var leWorker = require("greenlock-cluster/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(leWorker.middleware(redirectHttps));
|
|
plainServer.listen(80);
|
|
|
|
var server = require("https").createServer(leWorker.httpsOptions, leWorker.middleware(app));
|
|
server.listen(443);
|
|
};
|
|
```
|
|
|
|
### API
|
|
|
|
`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`.
|
|
|
|
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
|
|
|
|
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.
|