# [greenlock-store-fs](https://git.rootprojects.org/root/greenlock-store-fs.js) | A [Root](https://rootprojects.org) project A keypair and certificate storage strategy for Greenlock v2.7+ (and v3). The (much simpler) successor to le-store-certbot. Works with all ACME (Let's Encrypt) SSL certificate sytles: * [x] single domains * [x] multiple domains (SANs, AltNames) * [x] wildcards * [x] private / localhost domains # Usage ```js var greenlock = require('greenlock'); var gl = greenlock.create({ configDir: '~/.config/acme' , store: require('greenlock-store-fs') , approveDomains: approveDomains , ... }); ``` # File System The default file system layout mirrors that of le-store-certbot in order to make transitioning effortless, in most situations: ``` acme ├── accounts │   └── acme-staging-v02.api.letsencrypt.org │   └── directory │   └── sites@example.com.json └── live ├── example.com │   ├── bundle.pem │   ├── cert.pem │   ├── chain.pem │   ├── fullchain.pem │   └── privkey.pem └── www.example.com ├── bundle.pem ├── cert.pem ├── chain.pem ├── fullchain.pem └── privkey.pem ``` # Wildcards & AltNames Working with wildcards and multiple altnames requires greenlock >= v2.7 (or v3). To do so you must return `{ subject: '...', altnames: ['...', ...] }` within the `approveDomains()` callback. `subject` refers to "the subject of the ssl certificate" as opposed to `domain` which indicates "the domain servername used in the current request". For single-domain certificates they're always the same, but for multiple-domain certificates `subject` must be the name no matter what `domain` is receiving a request. `subject` is used as part of the name of the file storage path where the certificate will be saved (or retrieved). `altnames` should be the list of SubjectAlternativeNames (SANs) on the certificate. The subject and the first altname must be an exact match: `subject === altnames[0]`. ## Simple Example ```js function approveDomains(opts) { // Allow only example.com and *.example.com (such as foo.example.com) // foo.example.com => *.example.com var wild = '*.' + opts.domain.split('.').slice(1).join('.'); if ('example.com' !== opts.domain && '*.example.com' !== wild) { cb(new Error(opts.domain + " is not allowed")); } var result = { subject: 'example.com', altnames: [ 'example.com', '*.example.com' ] }; return Promise.resolve(result); } ``` ## Realistic Example ```js function approveDomains(opts, certs, cb) { var related = getRelated(opts.domain); if (!related) { cb(new Error(opts.domain + " is not allowed")); }; opts.subject = related.subject; opts.domains = related.domains; cb({ options: opts, certs: certs }); } ``` ```js function getRelated(domain) { var related; var wild = '*.' + domain.split('.').slice(1).join('.'); if (Object.keys(allAllowedDomains).some(function (k) { return allAllowedDomains[k].some(function (name) { if (domain === name || wild === name) { related = { subject: k, altnames: allAllowedDomains[k] }; return true; } }); })) { return related; } } ``` ```js var allAllowedDomains = { 'example.com': ['example.com', '*.example.com'] , 'example.net': ['example.net', '*.example.net'] } ```