From 7313167ca02aea376a303621d5d4d2fe526cc170 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Tue, 5 Nov 2019 00:01:31 -0700 Subject: [PATCH] allow for partial manager --- .gitignore | 1 + bin/init.js | 75 +++++--- bin/lib/greenlockrc.js | 9 +- bin/tmpl/greenlock.tmpl.js | 29 +++ bin/tmpl/server.tmpl.js | 3 +- greenlock.js | 139 ++------------ manager-underlay.js | 377 ++++++++++++++++++++++++++++++++----- package-lock.json | 25 ++- package.json | 3 +- 9 files changed, 455 insertions(+), 206 deletions(-) create mode 100644 bin/tmpl/greenlock.tmpl.js diff --git a/.gitignore b/.gitignore index bbff6d2..5ce70f2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +greenlock.json* TODO.txt link.sh .env diff --git a/bin/init.js b/bin/init.js index 3cae478..a63d364 100644 --- a/bin/init.js +++ b/bin/init.js @@ -21,20 +21,15 @@ cli.main(async function(argList, flags) { var pkgpath = path.join(process.cwd(), 'package.json'); var pkgdir = path.dirname(pkgpath); //var rcpath = path.join(pkgpath, '.greenlockrc'); - var configFile = path.join(pkgdir, 'greenlock.d/manager.json'); var manager = flags.manager; - // TODO move to bin/lib/greenlockrc.js - if (!manager) { - manager = 'greenlock-cloud-fs'; - if (!flags.managerOpts.configFile) { - flags.managerOpts.configFile = configFile; - } - } if (['fs', 'cloud'].includes(manager)) { - // TODO publish the 1st party modules under a secure namespace - flags.manager = '@greenlock/manager-' + flags.manager; + manager = '@greenlock/manager'; } + if (['cloud'].includes(manager)) { + flags.managerOpts.cloud = true; + } + flags.manager = flags.managerOpts; delete flags.managerOpts; flags.manager.manager = manager; @@ -57,6 +52,7 @@ cli.main(async function(argList, flags) { var GreenlockRc = require('./lib/greenlockrc.js'); //var rc = await GreenlockRc(pkgpath, manager, flags.manager); await GreenlockRc(pkgpath, manager, flags.manager); + writeGreenlockJs(pkgdir, flags); writeServerJs(pkgdir, flags); writeAppJs(pkgdir); @@ -70,9 +66,41 @@ cli.main(async function(argList, flags) { */ }, args); +function writeGreenlockJs(pkgdir, flags) { + var greenlockJs = 'greenlock.js'; + var fs = require('fs'); + var path = require('path'); + var tmpl = fs.readFileSync( + path.join(__dirname, 'tmpl/greenlock.tmpl.js'), + 'utf8' + ); + + try { + fs.accessSync(path.join(pkgdir, greenlockJs)); + console.warn("[skip] '%s' exists", greenlockJs); + return; + } catch (e) { + // continue + } + + if (flags.cluster) { + tmpl = tmpl.replace( + /options.cluster = false/g, + 'options.cluster = true' + ); + } + if (flags.maintainerEmail) { + tmpl = tmpl.replace( + /pkg.author/g, + JSON.stringify(flags.maintainerEmail) + ); + } + fs.writeFileSync(path.join(pkgdir, greenlockJs), tmpl); + console.info("created '%s'", greenlockJs); +} + function writeServerJs(pkgdir, flags) { var serverJs = 'server.js'; - var bakTmpl = 'server-greenlock-tmpl.js'; var fs = require('fs'); var path = require('path'); var tmpl = fs.readFileSync( @@ -82,13 +110,8 @@ function writeServerJs(pkgdir, flags) { try { fs.accessSync(path.join(pkgdir, serverJs)); - console.warn( - JSON.stringify(serverJs), - ' exists, writing to ', - JSON.stringify(bakTmpl), - 'instead' - ); - serverJs = bakTmpl; + console.warn("[skip] '%s' exists", serverJs); + return; } catch (e) { // continue } @@ -106,10 +129,10 @@ function writeServerJs(pkgdir, flags) { ); } fs.writeFileSync(path.join(pkgdir, serverJs), tmpl); + console.info("created '%s'", serverJs); } function writeAppJs(pkgdir) { - var bakTmpl = 'app-greenlock-tmpl.js'; var appJs = 'app.js'; var fs = require('fs'); var path = require('path'); @@ -120,16 +143,10 @@ function writeAppJs(pkgdir) { try { fs.accessSync(path.join(pkgdir, appJs)); - console.warn( - JSON.stringify(appJs), - ' exists, writing to ', - JSON.stringify(bakTmpl), - 'instead' - ); - appJs = bakTmpl; + console.warn("[skip] '%s' exists", appJs); + return; } catch (e) { - // continue + fs.writeFileSync(path.join(pkgdir, appJs), tmpl); + console.info("created '%s'", appJs); } - - fs.writeFileSync(path.join(pkgdir, appJs), tmpl); } diff --git a/bin/lib/greenlockrc.js b/bin/lib/greenlockrc.js index f8e22fe..2dd6242 100644 --- a/bin/lib/greenlockrc.js +++ b/bin/lib/greenlockrc.js @@ -93,10 +93,11 @@ module.exports = async function(pkgpath, manager, rc) { }); } - if (!_rc.manager) { - changed = true; - _rc.manager = 'greenlock-manager-fs'; - console.info('Using default manager ' + _rc.manager); + if (['@greenlock/manager', 'greenlock-manager-fs'].includes(_rc.manager)) { + if (!_rc.configFile) { + changed = true; + _rc.configFile = path.join(pkgdir, 'greenlock.json'); + } } if (!changed) { diff --git a/bin/tmpl/greenlock.tmpl.js b/bin/tmpl/greenlock.tmpl.js new file mode 100644 index 0000000..5994e4f --- /dev/null +++ b/bin/tmpl/greenlock.tmpl.js @@ -0,0 +1,29 @@ +'use strict'; + +module.exports = require('greenlock').create(init()); + +function init() { + // .greenlockrc defines which manager to use + // (i.e. greenlock-manager-fs or greenlock-manager-cloud) + var options = getGreenlockRc() || {}; + + // name & version for ACME client user agent + var pkg = require('./package.json'); + options.packageAgent = pkg.name + '/' + pkg.version; + + // contact for security and critical bug notices + options.maintainerEmail = pkg.author; + + return options; +} + +function getGreenlockRc() { + // The RC file is also used by the (optional) CLI and (optional) Web GUI. + // You are free to forego CLI and GUI support. + var fs = require('fs'); + var path = require('path'); + var rcPath = path.join(__dirname, '.greenlockrc'); + var rc = fs.readFileSync(rcPath, 'utf8'); + rc = JSON.parse(rc); + rc.packageRoot = __dirname; +} diff --git a/bin/tmpl/server.tmpl.js b/bin/tmpl/server.tmpl.js index 62c8e31..8663943 100644 --- a/bin/tmpl/server.tmpl.js +++ b/bin/tmpl/server.tmpl.js @@ -33,5 +33,6 @@ function getGreenlockRc() { var path = require('path'); var rcPath = path.join(__dirname, '.greenlockrc'); var rc = fs.readFileSync(rcPath, 'utf8'); - return JSON.parse(rc); + rc = JSON.parse(rc); + rc.packageRoot = __dirname; } diff --git a/greenlock.js b/greenlock.js index 22122be..0866f96 100644 --- a/greenlock.js +++ b/greenlock.js @@ -23,7 +23,6 @@ G.create = function(gconf) { if (!gconf) { gconf = {}; } - var manager; greenlock._create = function() { if (!gconf._bin_mode) { @@ -65,15 +64,14 @@ G.create = function(gconf) { } console.info('ACME Directory URL:', gdefaults.directoryUrl); - manager = normalizeManager(gconf); - // Wraps each of the following with appropriate error checking // greenlock.manager.defaults - // greenlock.manager.add - // greenlock.manager.update - // greenlock.manager.remove - // greenlock.manager.find - require('./manager-underlay.js').wrap(greenlock, manager, gconf); + // greenlock.sites.add + // greenlock.sites.update + // greenlock.sites.remove + // greenlock.sites.find + // greenlock.sites.get + require('./manager-underlay.js').wrap(greenlock, gconf); // The goal here is to reduce boilerplate, such as error checking // and duration parsing, that a manager must implement greenlock.sites.add = greenlock.add = greenlock.manager.add; @@ -83,7 +81,7 @@ G.create = function(gconf) { // Exports challenges.get for Greenlock Express HTTP-01, // and whatever odd use case pops up, I suppose // greenlock.challenges.get - require('./challenges-underlay.js').wrap(greenlock, manager, gconf); + require('./challenges-underlay.js').wrap(greenlock); greenlock._defaults = gdefaults; greenlock._defaults.debug = gconf.debug; @@ -108,25 +106,20 @@ G.create = function(gconf) { return p; }; - if (manager.init) { - // TODO punycode? - p = manager.init({ + p = greenlock.manager + .init({ request: request //punycode: require('punycode') - }); - } else { - p = Promise.resolve(); - } - p = p + }) .then(function() { - return manager.defaults().then(function(MCONF) { + return greenlock.manager._defaults().then(function(MCONF) { mergeDefaults(MCONF, gconf); if (true === MCONF.agreeToTerms) { gdefaults.agreeToTerms = function(tos) { return Promise.resolve(tos); }; } - return manager.defaults(MCONF); + return greenlock.manager._defaults(MCONF); }); }) .catch(function(err) { @@ -278,7 +271,7 @@ G.create = function(gconf) { greenlock._config = function(args) { return greenlock._single(args).then(function() { - return greenlock._configAll(args).then(function (sites) { + return greenlock._configAll(args).then(function(sites) { return sites[0]; }); }); @@ -289,7 +282,7 @@ G.create = function(gconf) { return null; } sites = JSON.parse(JSON.stringify(sites)); - return manager.defaults().then(function(mconf) { + return greenlock.manager._defaults().then(function(mconf) { return sites.map(function(site) { if (site.store && site.challenges) { return site; @@ -314,7 +307,7 @@ G.create = function(gconf) { // needs to get info about the renewal, such as which store and challenge(s) to use greenlock.renew = function(args) { return greenlock._init().then(function() { - return manager.defaults().then(function(mconf) { + return greenlock.manager._defaults().then(function(mconf) { return greenlock._renew(mconf, args); }); }); @@ -418,7 +411,7 @@ G.create = function(gconf) { greenlock.order = function(args) { return greenlock._init().then(function() { - return manager.defaults().then(function(mconf) { + return greenlock.manager._defaults().then(function(mconf) { return greenlock._order(mconf, args); }); }); @@ -485,106 +478,6 @@ function errorToJSON(e) { return error; } -function normalizeManager(gconf) { - var m; - // 1. Get the manager - // 2. Figure out if we need to wrap it - - if (!gconf.manager) { - gconf.manager = 'greenlock-manager-fs'; - if (gconf.find) { - // { manager: 'greenlock-manager-fs', find: function () { } } - warpFind(gconf); - } - } - - if ('string' === typeof gconf.manager) { - try { - // wrap this to be safe for greenlock-manager-fs - m = require(gconf.manager).create(gconf); - } catch (e) { - console.error('Error loading manager:'); - console.error(e.code); - console.error(e.message); - } - } else { - m = gconf.manager; - } - - if (!m) { - console.error(); - console.error( - 'Failed to load manager plugin ', - JSON.stringify(gconf.manager) - ); - console.error(); - process.exit(1); - } - - if ( - ['set', 'remove', 'find', 'defaults'].every(function(k) { - return 'function' === typeof m[k]; - }) - ) { - return m; - } - - // { manager: { find: function () { } } } - if (m.find) { - warpFind(m); - } - // m.configFile could also be set - m = require('greenlock-manager-fs').create(m); - - if ('function' !== typeof m.find) { - console.error(); - console.error( - JSON.stringify(gconf.manager), - 'must implement `find()` and should implement `set()`, `remove()`, `defaults()`, and `init()`' - ); - console.error(); - process.exit(1); - } - - return m; -} - -function warpFind(gconf) { - gconf.__gl_find = gconf.find; - gconf.find = function(args) { - // the incoming args will be normalized by greenlock - return gconf.__gl_find(args).then(function(sites) { - // we also need to error check the incoming sites, - // as if they were being passed through `add()` or `set()` - // (effectively they are) because the manager assumes that - // they're not bad - sites.forEach(function(s) { - if (!s || 'string' !== typeof s.subject) { - throw new Error('missing subject'); - } - if ( - !Array.isArray(s.altnames) || - !s.altnames.length || - !s.altnames[0] || - s.altnames[0] !== s.subject - ) { - throw new Error('missing or malformed altnames'); - } - ['renewAt', 'issuedAt', 'expiresAt'].forEach(function(k) { - if (s[k]) { - throw new Error( - '`' + - k + - '` should be updated by `set()`, not by `find()`' - ); - } - }); - }); - return sites; - }); - }; -} - function mergeDefaults(MCONF, gconf) { if ( gconf.agreeToTerms === true || diff --git a/manager-underlay.js b/manager-underlay.js index 07270ec..9b1557f 100644 --- a/manager-underlay.js +++ b/manager-underlay.js @@ -5,7 +5,15 @@ var E = require('./errors.js'); var warned = {}; -module.exports.wrap = function(greenlock, manager, gconf) { +// The purpose of this file is to try to auto-build +// partial managers so that the external API can be smaller. + +module.exports.wrap = function(greenlock, gconf) { + var myFind = gconf.find; + delete gconf.find; + + var mega = mergeManager(gconf); + greenlock.manager = {}; greenlock.sites = {}; //greenlock.accounts = {}; @@ -31,7 +39,7 @@ module.exports.wrap = function(greenlock, manager, gconf) { greenlock.manager.defaults = function(conf) { return greenlock._init().then(function() { if (!conf) { - return manager.defaults(); + return mega.defaults(); } if (conf.sites) { @@ -83,9 +91,10 @@ module.exports.wrap = function(greenlock, manager, gconf) { } }); - return manager.defaults(conf); + return mega.defaults(conf); }); }; + greenlock.manager._defaults = mega.defaults; greenlock.manager.add = function(args) { if (!args || !Array.isArray(args.altnames) || !args.altnames.length) { @@ -142,7 +151,7 @@ module.exports.wrap = function(greenlock, manager, gconf) { args.renewStagger = U._parseDuration(args.renewStagger); } - return manager.set(args).then(function(result) { + return mega.set(args).then(function(result) { if (!gconf._bin_mode) { greenlock.renew({}).catch(function(err) { if (!err.context) { @@ -157,6 +166,22 @@ module.exports.wrap = function(greenlock, manager, gconf) { }); }; + greenlock.manager.get = greenlock.sites.get = function(args) { + return Promise.resolve().then(function() { + if (args.subject) { + throw new Error( + 'get({ servername }) searches certificates by altnames, not by subject specifically' + ); + } + if (args.servernames || args.altnames || args.renewBefore) { + throw new Error( + 'get({ servername }) does not take arguments that could lead to multiple results' + ); + } + return mega.get(args); + }); + }; + greenlock.manager.remove = function(args) { return Promise.resolve().then(function() { args.subject = checkSubject(args); @@ -171,57 +196,137 @@ module.exports.wrap = function(greenlock, manager, gconf) { ); } // TODO check no altnames - return manager.remove(args); + return mega.remove(args); }); }; /* - { - subject: site.subject, - altnames: site.altnames, - //issuedAt: site.issuedAt, - //expiresAt: site.expiresAt, - renewOffset: site.renewOffset, - renewStagger: site.renewStagger, - renewAt: site.renewAt, - subscriberEmail: site.subscriberEmail, - customerEmail: site.customerEmail, - challenges: site.challenges, - store: site.store - }; - */ + { + subject: site.subject, + altnames: site.altnames, + //issuedAt: site.issuedAt, + //expiresAt: site.expiresAt, + renewOffset: site.renewOffset, + renewStagger: site.renewStagger, + renewAt: site.renewAt, + subscriberEmail: site.subscriberEmail, + customerEmail: site.customerEmail, + challenges: site.challenges, + store: site.store + }; + */ - greenlock._find = function(args) { - var servernames = (args.servernames || []) - .concat(args.altnames || []) - .filter(Boolean) - .slice(0); - var modified = servernames.slice(0); + // no transaction promise here because it calls set + greenlock._find = async function(args) { + args = _mangleFindArgs(args); + var ours = await mega.find(args); + if (!myFind) { + return ours; + } - // servername, wildname, and altnames are all the same - ['wildname', 'servername'].forEach(function(k) { - var altname = args[k] || ''; - if (altname && !modified.includes(altname)) { - modified.push(altname); + // if the user has an overlay find function we'll do a diff + // between the managed state and the overlay, and choose + // what was found. + var theirs = await myFind(args); + theirs = theirs.filter(function(site) { + if (!site || 'string' !== typeof site.subject) { + throw new Error('found site is missing subject'); + } + if ( + !Array.isArray(site.altnames) || + !site.altnames.length || + !site.altnames[0] || + site.altnames[0] !== site.subject + ) { + throw new Error('missing or malformed altnames'); + } + ['renewAt', 'issuedAt', 'expiresAt'].forEach(function(k) { + if (site[k]) { + throw new Error( + '`' + + k + + '` should be updated by `set()`, not by `find()`' + ); + } + }); + if (!site) { + return; + } + if (args.subject && site.subject !== args.subject) { + return false; + } + + var servernames = args.servernames || args.altnames; + if ( + servernames && + !site.altnames.some(function(altname) { + return servernames.includes(altname); + }) + ) { + return false; + } + + return site.renewAt < (args.renewBefore || Infinity); + }); + return _mergeFind(ours, theirs); + }; + + function _mergeFind(ours, theirs) { + var toUpdate = []; + theirs.forEach(function(_newer) { + var hasCurrent = ours.some(function(_older) { + var changed = false; + if (_newer.subject !== _older.subject) { + return false; + } + + // BE SURE TO SET THIS UNDEFINED AFTERWARDS + _older._exists = true; + + _newer.deletedAt = _newer.deletedAt || 0; + Object.keys(_newer).forEach(function(k) { + if (_older[k] !== _newer[k]) { + changed = true; + _older[k] = _newer[k]; + } + }); + if (changed) { + toUpdate.push(_older); + } + + // handled the (only) match + return true; + }); + if (!hasCurrent) { + toUpdate.push(_newer); } }); - if (modified.length) { - servernames = modified; - servernames = servernames.altnames.map(U._encodeName); - args.altnames = servernames; - args.servernames = args.altnames = checkAltnames(false, args); - } + // delete the things that are gone + ours.forEach(function(_older) { + if (!_older._exists) { + _older.deletedAt = Date.now(); + toUpdate.push(_older); + } + _older._exists = undefined; + }); - // documented as args.servernames - // preserved as args.altnames for v3 beta backwards compat - // my only hesitancy in this choice is that a "servername" - // may NOT contain '*.', in which case `altnames` is a better choice. - // However, `altnames` is ambiguous - as if it means to find a - // certificate by that specific collection of altnames. - // ... perhaps `domains` could work? - return manager.find(args); - }; + Promise.all( + toUpdate.map(function(site) { + return greenlock.sites.update(site).catch(function(err) { + console.error( + 'Developer Error: cannot update sites from user-supplied `find()`:' + ); + console.error(err); + }); + }) + ); + + // ours is updated from theirs + return ours; + } + + greenlock.manager.init = mega.init; }; function checkSubject(args) { @@ -284,3 +389,185 @@ function checkAltnames(subject, args) { return altnames; } + +function loadManager(gconf) { + var m; + // 1. Get the manager + // 2. Figure out if we need to wrap it + + if (!gconf.manager) { + gconf.manager = '@greenlock/manager'; + } + + if ('string' !== typeof gconf.manager) { + throw new Error( + '`manager` should be a string representing the npm name or file path of the module' + ); + } + try { + // wrap this to be safe for @greenlock/manager + m = require(gconf.manager).create(gconf); + } catch (e) { + console.error('Error loading manager:'); + console.error(e.code); + console.error(e.message); + } + + if (!m) { + console.error(); + console.error( + 'Failed to load manager plugin ', + JSON.stringify(gconf.manager) + ); + console.error(); + process.exit(1); + } + + return m; +} + +function mergeManager(gconf) { + var mng; + function m() { + if (mng) { + return mng; + } + mng = require('@greenlock/manager').create(gconf); + return mng; + } + + var mini = loadManager(gconf); + var mega = {}; + // optional + if (mini.defaults) { + mega.defaults = function(opts) { + return mini.defaults(opts); + }; + } else { + mega.defaults = m().defaults; + } + + // optional + if (mini.remove) { + mega.remove = function(opts) { + return mini.remove(opts); + }; + } else { + mega.remove = function(opts) { + mega.get(opts).then(function(site) { + if (!site) { + return null; + } + site.deletedAt = Date.now(); + return mega.set(site).then(function() { + return site; + }); + }); + }; + } + + if (mini.find) { + // without this there cannot be fully automatic renewal + mega.find = function(opts) { + return mini.find(opts); + }; + } + + // set and (find and/or get) should be from the same set + if (mini.set) { + mega.set = function(opts) { + if (!mini.find) { + // TODO create the list so that find can be implemented + } + return mini.set(opts); + }; + } else { + mega.set = m().set; + mega.get = m().get; + } + + if (mini.get) { + mega.get = function(opts) { + return mini.get(opts); + }; + } else if (mini.find) { + mega.get = function(opts) { + var servername = opts.servername; + delete opts.servername; + opts.servernames = (servername && [servername]) || undefined; + return mini.find(opts).then(function(sites) { + return sites.filter(function(site) { + return site.altnames.include(servername); + })[0]; + }); + }; + } else if (mini.set) { + throw new Error( + gconf.manager + ' implements `set()`, but not `get()` or `find()`' + ); + } else { + mega.find = m().find; + mega.get = m().get; + } + + if (!mega.get) { + mega.get = function(opts) { + var servername = opts.servername; + delete opts.servername; + opts.servernames = (servername && [servername]) || undefined; + return mega.find(opts).then(function(sites) { + return sites.filter(function(site) { + return site.altnames.include(servername); + })[0]; + }); + }; + } + + mega.init = function(deps) { + if (mini.init) { + return mini.init(deps).then(function() { + if (mng) { + return mng.init(deps); + } + }); + } else if (mng) { + return mng.init(deps); + } else { + return Promise.resolve(null); + } + }; + + return mega; +} + +function _mangleFindArgs(args) { + var servernames = (args.servernames || []) + .concat(args.altnames || []) + .filter(Boolean) + .slice(0); + var modified = servernames.slice(0); + + // servername, wildname, and altnames are all the same + ['wildname', 'servername'].forEach(function(k) { + var altname = args[k] || ''; + if (altname && !modified.includes(altname)) { + modified.push(altname); + } + }); + + if (modified.length) { + servernames = modified; + servernames = servernames.map(U._encodeName); + args.altnames = servernames; + args.servernames = args.altnames = checkAltnames(false, args); + } + + // documented as args.servernames + // preserved as args.altnames for v3 beta backwards compat + // my only hesitancy in this choice is that a "servername" + // may NOT contain '*.', in which case `altnames` is a better choice. + // However, `altnames` is ambiguous - as if it means to find a + // certificate by that specific collection of altnames. + // ... perhaps `domains` could work? + return args; +} diff --git a/package-lock.json b/package-lock.json index 1e75e62..0e03160 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,6 +4,25 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@greenlock/manager": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@greenlock/manager/-/manager-3.0.0.tgz", + "integrity": "sha512-ijgJrFdzJPmzrDk8aKXYoYR8LNfG3hXd9/s54ZY7IgxTulyPQ/qOPgl7sWgCxxLhZBzSY1xI6eC/6Y5TQ01agg==", + "requires": { + "greenlock-manager-fs": "^3.0.5" + }, + "dependencies": { + "greenlock-manager-fs": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/greenlock-manager-fs/-/greenlock-manager-fs-3.0.5.tgz", + "integrity": "sha512-r/q+tEFuDwklfzPfiGhcIrHuJxMrppC+EseESpu5f0DMokh+1iZVm9nGC/VE7/7GETdOYfEYhhQkmspsi8Gr/A==", + "requires": { + "@root/mkdirp": "^1.0.0", + "safe-replace": "^1.1.0" + } + } + } + }, "@root/acme": { "version": "3.0.8", "resolved": "https://registry.npmjs.org/@root/acme/-/acme-3.0.8.tgz", @@ -90,9 +109,9 @@ "dev": true }, "greenlock-manager-fs": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/greenlock-manager-fs/-/greenlock-manager-fs-3.0.3.tgz", - "integrity": "sha512-Jwo60nHd10PNUA9M6cylD9YB4x4hzlfO2LRIGI0X+V+zA0x3KVbNW14yj8frdfHrtsWC1JQe7oFnHVdoRbAU2A==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/greenlock-manager-fs/-/greenlock-manager-fs-3.0.5.tgz", + "integrity": "sha512-r/q+tEFuDwklfzPfiGhcIrHuJxMrppC+EseESpu5f0DMokh+1iZVm9nGC/VE7/7GETdOYfEYhhQkmspsi8Gr/A==", "requires": { "@root/mkdirp": "^1.0.0", "safe-replace": "^1.1.0" diff --git a/package.json b/package.json index 7920aef..db58d06 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "author": "AJ ONeal (https://coolaj86.com/)", "license": "MPL-2.0", "dependencies": { + "@greenlock/manager": "^3.0.0", "@root/acme": "^3.0.8", "@root/csr": "^0.8.1", "@root/keypairs": "^0.9.0", @@ -45,7 +46,7 @@ "@root/request": "^1.3.10", "acme-http-01-standalone": "^3.0.5", "cert-info": "^1.5.1", - "greenlock-manager-fs": "^3.0.3", + "greenlock-manager-fs": "^3.0.5", "greenlock-store-fs": "^3.2.0", "safe-replace": "^1.1.0" },