404 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
			
		
		
	
	
			404 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
# Migrating Guide
 | 
						|
 | 
						|
Greenlock v4 is the current version.
 | 
						|
 | 
						|
# v3 to v4
 | 
						|
 | 
						|
v4 is a very minor, but breaking, change from v3
 | 
						|
 | 
						|
### `configFile` is replaced with `configDir`
 | 
						|
 | 
						|
The default config file `./greenlock.json` is now `./greenlock.d/config.json`.
 | 
						|
 | 
						|
This was change was mode to eliminate unnecessary configuration that was inadvertantly introduced in v3.
 | 
						|
 | 
						|
### `.greenlockrc` is auto-generated
 | 
						|
 | 
						|
`.greenlockrc` exists for the sake of tooling - so that the CLI, Web API, and your code naturally stay in sync.
 | 
						|
 | 
						|
It looks like this:
 | 
						|
 | 
						|
```json
 | 
						|
{
 | 
						|
    "manager": {
 | 
						|
        "module": "@greenlock/manager"
 | 
						|
    },
 | 
						|
    "configDir": "./greenlock.d"
 | 
						|
}
 | 
						|
```
 | 
						|
 | 
						|
If you deploy to a read-only filesystem, it is best that you create the `.greenlockrc` file as part
 | 
						|
of your image and use that rather than including any configuration in your code.
 | 
						|
 | 
						|
# v2 to v4
 | 
						|
 | 
						|
**Greenlock Express** uses Greenlock directly, the same as before.
 | 
						|
 | 
						|
All options described for `Greenlock.create({...})` also apply to the Greenlock Express `init()` callback.
 | 
						|
 | 
						|
# Overview of Major Differences
 | 
						|
 | 
						|
-   Reduced API
 | 
						|
-   No code in the config
 | 
						|
    -   (config is completely serializable)
 | 
						|
-   Manager callbacks replace `approveDomains`
 | 
						|
-   Greenlock Express does more, with less config
 | 
						|
    -   cluster is supported out-of-the-box
 | 
						|
    -   high-performance
 | 
						|
    -   scalable
 | 
						|
-   ACME challenges are simplified
 | 
						|
    -   init
 | 
						|
    -   zones (dns-01)
 | 
						|
    -   set
 | 
						|
    -   get
 | 
						|
    -   remove
 | 
						|
-   Store callbacks are simplified
 | 
						|
    -   accounts
 | 
						|
        -   checkKeypairs
 | 
						|
    -   certificates
 | 
						|
        -   checkKeypairs
 | 
						|
        -   check
 | 
						|
        -   set
 | 
						|
 | 
						|
# Greenlock JavaScript API greatly reduced
 | 
						|
 | 
						|
Whereas before there were many different methods with nuance differences,
 | 
						|
now there's just `create`, `get`, `renew`, and sometimes `add` ().
 | 
						|
 | 
						|
-   Greenlock.create({ maintainerEmail, packageAgent, notify })
 | 
						|
-   Greenlock.get({ servername, wildname, duplicate, force })
 | 
						|
    -   (just a convenience wrapper around renew)
 | 
						|
-   Greenlock.renew({ subject, altnames, issuedBefore, expiresAfter })
 | 
						|
    -   (retrieves, issues, renews, all-in-one)
 | 
						|
-   _optional_ Greenlock.add({ subject, altnames, subscriberEmail })
 | 
						|
    -   (partially replaces `approveDomains`)
 | 
						|
 | 
						|
Also, some disambiguation on terms:
 | 
						|
 | 
						|
-   `domains` was often ambiguous and confusing, it has been replaced by:
 | 
						|
    -   `subject` refers to the subject of a certificate - the primary domain
 | 
						|
    -   `altnames` refers to the domains in the SAN (Subject Alternative Names) section of the certificate
 | 
						|
    -   `servername` refers to the TLS (SSL) SNI (Server Name Indication) request for a cetificate
 | 
						|
    -   `wildname` refers to the wildcard version of the servername (ex: `www.example.com => *.example.com`)
 | 
						|
 | 
						|
When you create an instance of Greenlock, you only supply package and maintainer info.
 | 
						|
 | 
						|
All other configuration is A) optional and B) handled by the _Manager_.
 | 
						|
 | 
						|
```js
 | 
						|
'use strict';
 | 
						|
 | 
						|
var pkg = require('./package.json');
 | 
						|
 | 
						|
var Greenlock = require('greenlock');
 | 
						|
var greenlock = Greenlock.create({
 | 
						|
    // used for the ACME client User-Agent string as per RFC 8555 and RFC 7231
 | 
						|
    packageAgent: pkg.name + '/' + pkg.version,
 | 
						|
 | 
						|
    // used as the contact for critical bug and security notices
 | 
						|
    // should be the same as pkg.author.email
 | 
						|
    maintainerEmail: 'jon@example.com',
 | 
						|
 | 
						|
    // used for logging background events and errors
 | 
						|
    notify: function(ev, args) {
 | 
						|
        if ('error' === ev || 'warning' === ev) {
 | 
						|
            console.error(ev, args);
 | 
						|
            return;
 | 
						|
        }
 | 
						|
        console.info(ev, args);
 | 
						|
    }
 | 
						|
});
 | 
						|
```
 | 
						|
 | 
						|
By default **no certificates will be issued**. See the _manager_ section.
 | 
						|
 | 
						|
When you want to get a single certificate, you use `get`, which will:
 | 
						|
 | 
						|
-   will return null if neither the `servername` or its `wildname` (wildcard) variant can be found
 | 
						|
-   retrieve a non-expired certificate, if possible
 | 
						|
-   will renew the certificate in the background, if stale
 | 
						|
-   will wait for the certificate to be issued if new
 | 
						|
 | 
						|
```js
 | 
						|
greenlock
 | 
						|
    .get({ servername: 'www.example.com' })
 | 
						|
    .then(function(result) {
 | 
						|
        if (!result) {
 | 
						|
            // certificate is not on the approved list
 | 
						|
            return null;
 | 
						|
        }
 | 
						|
 | 
						|
        var fullchain = result.pems.cert + '\n' + result.pems.chain + '\n';
 | 
						|
        var privkey = result.pems.privkey;
 | 
						|
 | 
						|
        return {
 | 
						|
            fullchain: fullchain,
 | 
						|
            privkey: privkey
 | 
						|
        };
 | 
						|
    })
 | 
						|
    .catch(function(e) {
 | 
						|
        // something went wrong in the renew process
 | 
						|
        console.error(e);
 | 
						|
    });
 | 
						|
```
 | 
						|
 | 
						|
By default **no certificates will be issued**. See the _manager_ section.
 | 
						|
 | 
						|
When you want to renew certificates, _en masse_, you use `renew`, which will:
 | 
						|
 | 
						|
-   check all certificates matching the given criteria
 | 
						|
-   only renew stale certificates by default
 | 
						|
-   return error objects (will NOT throw exception for failed renewals)
 | 
						|
 | 
						|
```js
 | 
						|
greenlock
 | 
						|
    .renew({})
 | 
						|
    .then(function(results) {
 | 
						|
        if (!result.length) {
 | 
						|
            // no certificates found
 | 
						|
            return null;
 | 
						|
        }
 | 
						|
 | 
						|
        // [{ site, error }]
 | 
						|
        return results;
 | 
						|
    })
 | 
						|
    .catch(function(e) {
 | 
						|
        // an unexpected error, not related to renewal
 | 
						|
        console.error(e);
 | 
						|
    });
 | 
						|
```
 | 
						|
 | 
						|
Options:
 | 
						|
 | 
						|
| Option        | Description                                                                |
 | 
						|
| ------------- | -------------------------------------------------------------------------- |
 | 
						|
| `altnames`    | only check and renew certs matching these altnames (including wildcards)   |
 | 
						|
| `renewBefore` | only check and renew certs marked for renewal before the given date, in ms |
 | 
						|
| `duplicate`   | renew certificates regardless of timing                                    |
 | 
						|
| `force`       | allow silly things, like tiny `renewOffset`s                               |
 | 
						|
 | 
						|
By default **no certificates will be issued**. See the _manager_ section.
 | 
						|
 | 
						|
# Greenlock Express Example
 | 
						|
 | 
						|
The options that must be returned from `init()` are the same that are used in `Greenlock.create()`,
 | 
						|
with a few extra that are specific to Greenlock Express:
 | 
						|
 | 
						|
```js
 | 
						|
require('@root/greenlock-express')
 | 
						|
    .init(function() {
 | 
						|
        // This object will be passed to Greenlock.create()
 | 
						|
 | 
						|
        var options = {
 | 
						|
            // some options, like cluster, are special to Greenlock Express
 | 
						|
 | 
						|
            cluster: false,
 | 
						|
 | 
						|
            // The rest are the same as for Greenlock
 | 
						|
 | 
						|
            packageAgent: pkg.name + '/' + pkg.version,
 | 
						|
            maintainerEmail: 'jon@example.com',
 | 
						|
            notify: function(ev, args) {
 | 
						|
                console.info(ev, args);
 | 
						|
            }
 | 
						|
        };
 | 
						|
 | 
						|
        return options;
 | 
						|
    })
 | 
						|
    .serve(function(glx) {
 | 
						|
        // will start servers on port 80 and 443
 | 
						|
 | 
						|
        glx.serveApp(function(req, res) {
 | 
						|
            res.end('Hello, Encrypted World!');
 | 
						|
        });
 | 
						|
 | 
						|
        // you can get access to the raw server (i.e. for websockets)
 | 
						|
 | 
						|
        glx.httpsServer(); // returns raw server object
 | 
						|
    });
 | 
						|
```
 | 
						|
 | 
						|
# _Manager_ replaces `approveDomains`
 | 
						|
 | 
						|
`approveDomains` was always a little confusing. Most people didn't need it.
 | 
						|
 | 
						|
Instead, now there is a simple config file that will work for most people,
 | 
						|
as well as a set of callbacks for easy configurability.
 | 
						|
 | 
						|
### Default Manager
 | 
						|
 | 
						|
The default manager is `@greenlock/manager` and the default `configDir` is `./.greenlock.d`.
 | 
						|
 | 
						|
The config file should look something like this:
 | 
						|
 | 
						|
`./greenlock.d/config.json`:
 | 
						|
 | 
						|
```json
 | 
						|
{
 | 
						|
    "subscriberEmail": "jon@example.com",
 | 
						|
    "agreeToTerms": true,
 | 
						|
    "sites": {
 | 
						|
        "example.com": {
 | 
						|
            "subject": "example.com",
 | 
						|
            "altnames": ["example.com", "www.example.com"]
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
```
 | 
						|
 | 
						|
You can specify a `acme-dns-01-*` or `acme-http-01-*` challenge plugin globally, or per-site.
 | 
						|
 | 
						|
```json
 | 
						|
{
 | 
						|
    "subscriberEmail": "jon@example.com",
 | 
						|
    "agreeToTerms": true,
 | 
						|
    "sites": {
 | 
						|
        "example.com": {
 | 
						|
            "subject": "example.com",
 | 
						|
            "altnames": ["example.com", "www.example.com"],
 | 
						|
            "challenges": {
 | 
						|
                "dns-01": {
 | 
						|
                    "module": "acme-dns-01-digitalocean",
 | 
						|
                    "token": "apikey-xxxxx"
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
```
 | 
						|
 | 
						|
The same is true with `greenlock-store-*` plugins:
 | 
						|
 | 
						|
```json
 | 
						|
{
 | 
						|
    "subscriberEmail": "jon@example.com",
 | 
						|
    "agreeToTerms": true,
 | 
						|
    "sites": {
 | 
						|
        "example.com": {
 | 
						|
            "subject": "example.com",
 | 
						|
            "altnames": ["example.com", "www.example.com"]
 | 
						|
        }
 | 
						|
    },
 | 
						|
    "store": {
 | 
						|
        "module": "greenlock-store-fs",
 | 
						|
        "basePath": "~/.config/greenlock"
 | 
						|
    }
 | 
						|
}
 | 
						|
```
 | 
						|
 | 
						|
### Customer Manager, the lazy way
 | 
						|
 | 
						|
At the very least you have to implement `get({ servername, wildname })`.
 | 
						|
 | 
						|
```js
 | 
						|
var greenlock = Greenlock.create({
 | 
						|
    packageAgent: pkg.name + '/' + pkg.version,
 | 
						|
    maintainerEmail: 'jon@example.com',
 | 
						|
    notify: notify,
 | 
						|
 | 
						|
    packageRoot: __dirname,
 | 
						|
    manager: {
 | 
						|
        module: './manager.js'
 | 
						|
    }
 | 
						|
});
 | 
						|
 | 
						|
function notify(ev, args) {
 | 
						|
    if ('error' === ev || 'warning' === ev) {
 | 
						|
        console.error(ev, args);
 | 
						|
        return;
 | 
						|
    }
 | 
						|
    console.info(ev, args);
 | 
						|
}
 | 
						|
```
 | 
						|
 | 
						|
In the simplest case you can ignore all incoming options
 | 
						|
and return a single site config in the same format as the config file
 | 
						|
 | 
						|
`./manager.js`:
 | 
						|
 | 
						|
```js
 | 
						|
'use strict';
 | 
						|
 | 
						|
module.exports.create = function() {
 | 
						|
    return {
 | 
						|
        get: async function({ servername }) {
 | 
						|
            // do something to fetch the site
 | 
						|
            var site = {
 | 
						|
                subject: 'example.com',
 | 
						|
                altnames: ['example.com', 'www.example.com']
 | 
						|
            };
 | 
						|
 | 
						|
            return site;
 | 
						|
        }
 | 
						|
    };
 | 
						|
};
 | 
						|
```
 | 
						|
 | 
						|
If you want to use wildcards or local domains for a specific domain, you must specify the `dns-01` challenge plugin to use:
 | 
						|
 | 
						|
```js
 | 
						|
'use strict';
 | 
						|
 | 
						|
module.exports.create = function() {
 | 
						|
    return {
 | 
						|
        get: async function({ servername }) {
 | 
						|
            // do something to fetch the site
 | 
						|
            var site = {
 | 
						|
                subject: 'example.com',
 | 
						|
                altnames: ['example.com', 'www.example.com'],
 | 
						|
 | 
						|
                // dns-01 challenge
 | 
						|
                challenges: {
 | 
						|
                    'dns-01': {
 | 
						|
                        module: 'acme-dns-01-namedotcom',
 | 
						|
                        apikey: 'xxxx'
 | 
						|
                    }
 | 
						|
                }
 | 
						|
            };
 | 
						|
 | 
						|
            return site;
 | 
						|
        }
 | 
						|
    };
 | 
						|
};
 | 
						|
```
 | 
						|
 | 
						|
### Customer Manager, Complete
 | 
						|
 | 
						|
See <https://git.rootprojects.org/root/greenlock-manager-test.js#quick-start>
 | 
						|
 | 
						|
# ACME Challenge Plugins
 | 
						|
 | 
						|
The ACME challenge plugins are just a few simple callbacks:
 | 
						|
 | 
						|
-   `init`
 | 
						|
-   `zones` (dns-01 only)
 | 
						|
-   `set`
 | 
						|
-   `get`
 | 
						|
-   `remove`
 | 
						|
 | 
						|
They are described here:
 | 
						|
 | 
						|
-   [dns-01 documentation](https://git.rootprojects.org/root/acme-dns-01-test.js)
 | 
						|
-   [http-01 documentation](https://git.rootprojects.org/root/acme-http-01-test.js)
 | 
						|
 | 
						|
# Key and Cert Store Plugins
 | 
						|
 | 
						|
Again, these are just a few simple callbacks:
 | 
						|
 | 
						|
-   `certificates.checkKeypair`
 | 
						|
-   `certificates.check`
 | 
						|
-   `certificates.setKeypair`
 | 
						|
-   `certificates.set`
 | 
						|
-   `accounts.checkKeypair`
 | 
						|
-   `accounts.check` (optional)
 | 
						|
-   `accounts.setKeypair`
 | 
						|
-   `accounts.set` (optional)
 | 
						|
 | 
						|
The name `check` is used instead of `get` because they only need to return something if it exists. They do not need to fail, nor do they need to generate anything.
 | 
						|
 | 
						|
They are described here:
 | 
						|
 | 
						|
-   [greenlock store documentation](https://git.rootprojects.org/root/greenlock-store-test.js)
 | 
						|
 | 
						|
If you are just implenting in-house and are not going to publish a module, you can also do some hack things like this:
 |