diff --git a/README.md b/README.md index 039b7db..573d2cb 100644 --- a/README.md +++ b/README.md @@ -464,7 +464,7 @@ This is what keeps the mapping of domains <-> certificates. In many cases it will interact with the same database as the Key & Cert Store, and probably the code as well. - set({ subject, altnames, renewAt }) -- find({ altnames, renewBefore }) +- find({ servernames, renewBefore }) ```js // should return a list of site configs: [ diff --git a/accounts.js b/accounts.js index 2166ab4..c24f512 100644 --- a/accounts.js +++ b/accounts.js @@ -117,7 +117,7 @@ A._newAccount = function(gnlck, mconf, db, acme, args, email, fullAccount) { /^https?:\/\//i, '' ); - */ + */ keyP = db.setKeypair(query, keypair); } @@ -145,7 +145,7 @@ A._newAccount = function(gnlck, mconf, db, acme, args, email, fullAccount) { /^https?:\/\//i, '' ) - */ + */ }, reg ); diff --git a/bin/init.js b/bin/init.js index a63d364..9cdd2ba 100644 --- a/bin/init.js +++ b/bin/init.js @@ -49,7 +49,7 @@ cli.main(async function(argList, flags) { } } - var GreenlockRc = require('./lib/greenlockrc.js'); + var GreenlockRc = require('../greenlockrc.js'); //var rc = await GreenlockRc(pkgpath, manager, flags.manager); await GreenlockRc(pkgpath, manager, flags.manager); writeGreenlockJs(pkgdir, flags); diff --git a/bin/lib/flags.js b/bin/lib/flags.js index dd482e1..564bb10 100644 --- a/bin/lib/flags.js +++ b/bin/lib/flags.js @@ -5,7 +5,7 @@ var Flags = module.exports; var path = require('path'); //var pkgpath = path.join(__dirname, '..', 'package.json'); var pkgpath = path.join(process.cwd(), 'package.json'); -var GreenlockRc = require('./greenlockrc.js'); +var GreenlockRc = require('../../greenlockrc.js'); // These are ALL options // The individual CLI files each select a subset of them @@ -168,21 +168,21 @@ Flags.flags = function(mconf, myOpts) { }; }; -Flags.init = function(myOpts) { - return GreenlockRc(pkgpath).then(async function(rc) { - rc._bin_mode = true; - var Greenlock = require('../../'); - // this is a copy, so it's safe to modify - var greenlock = Greenlock.create(rc); - var mconf = await greenlock.manager.defaults(); - var flagOptions = Flags.flags(mconf, myOpts); - return { - flagOptions, - rc, - greenlock, - mconf - }; - }); +Flags.init = async function(myOpts) { + var rc = await GreenlockRc(pkgpath); + rc._bin_mode = true; + var Greenlock = require('../../'); + // this is a copy, so it's safe to modify + rc.packageRoot = path.dirname(pkgpath); + var greenlock = Greenlock.create(rc); + var mconf = await greenlock.manager.defaults(); + var flagOptions = Flags.flags(mconf, myOpts); + return { + flagOptions, + rc, + greenlock, + mconf + }; }; Flags.mangleFlags = function(flags, mconf, sconf, extras) { diff --git a/bin/lib/greenlockrc.js b/bin/lib/greenlockrc.js deleted file mode 100644 index 2dd6242..0000000 --- a/bin/lib/greenlockrc.js +++ /dev/null @@ -1,114 +0,0 @@ -'use strict'; - -// TODO how to handle path differences when run from npx vs when required by greenlock? - -var promisify = require('util').promisify; -var fs = require('fs'); -var readFile = promisify(fs.readFile); -var writeFile = promisify(fs.writeFile); -var chmodFile = promisify(fs.chmod); -var path = require('path'); - -function saveFile(rcpath, data, enc) { - // because this may have a database url or some such - return writeFile(rcpath, data, enc).then(function() { - return chmodFile(rcpath, parseInt('0600', 8)); - }); -} - -module.exports = async function(pkgpath, manager, rc) { - // TODO when run from package - // Run from the package root (assumed) or exit - var pkgdir = path.dirname(pkgpath); - var rcpath = path.join(pkgdir, '.greenlockrc'); - var created = false; - - try { - require(pkgpath); - } catch (e) { - console.error( - 'npx greenlock must be run from the package root (where package.json is)' - ); - process.exit(1); - } - - if (manager) { - if ('.' === manager[0]) { - manager = path.resolve(pkgdir, manager); - } - try { - require(manager); - } catch (e) { - console.error('npx greenlock must be run from the package root'); - process.exit(1); - } - } - - var _data = await readFile(rcpath, 'utf8').catch(function(err) { - if ('ENOENT' !== err.code) { - throw err; - } - console.info('Creating ' + rcpath); - created = true; - var data = '{}'; - return saveFile(rcpath, data, 'utf8').then(function() { - return data; - }); - }); - - var changed; - var _rc; - try { - _rc = JSON.parse(_data); - } catch (e) { - console.error("couldn't parse " + rcpath, _data); - console.error('(perhaps you should just delete it and try again?)'); - process.exit(1); - } - - if (manager) { - if (!_rc.manager) { - _rc.manager = manager; - } - if (_rc.manager !== manager) { - console.info('Switching manager:'); - var older = _rc.manager; - var newer = manager; - if ('/' === older[0]) { - older = path.relative(pkgdir, older); - } - if ('/' === newer[0]) { - newer = path.relative(pkgdir, newer); - } - console.info('\told: ' + older); - console.info('\tnew: ' + newer); - changed = true; - } - } - - if (rc) { - changed = true; - Object.keys(rc).forEach(function(k) { - _rc[k] = rc[k]; - }); - } - - if (['@greenlock/manager', 'greenlock-manager-fs'].includes(_rc.manager)) { - if (!_rc.configFile) { - changed = true; - _rc.configFile = path.join(pkgdir, 'greenlock.json'); - } - } - - if (!changed) { - return _rc; - } - - var data = JSON.stringify(_rc, null, 2); - if (created) { - console.info('Wrote ' + rcpath); - } - return saveFile(rcpath, data, 'utf8').then(function() { - return _rc; - }); -}; diff --git a/bin/tmpl/greenlock.tmpl.js b/bin/tmpl/greenlock.tmpl.js index 5994e4f..80d3937 100644 --- a/bin/tmpl/greenlock.tmpl.js +++ b/bin/tmpl/greenlock.tmpl.js @@ -1,29 +1,13 @@ '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() || {}; - +var pkg = require('./package.json'); +module.exports = require('@root/greenlock').create({ // name & version for ACME client user agent - var pkg = require('./package.json'); - options.packageAgent = pkg.name + '/' + pkg.version; + packageAgent: pkg.name + '/' + pkg.version, // contact for security and critical bug notices - options.maintainerEmail = pkg.author; - - return options; -} + maintainerEmail: pkg.author, -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; -} + // where to find .greenlockrc and set default paths + packageRoot: __dirname +}); diff --git a/bin/tmpl/server.tmpl.js b/bin/tmpl/server.tmpl.js index 8663943..3dcc225 100644 --- a/bin/tmpl/server.tmpl.js +++ b/bin/tmpl/server.tmpl.js @@ -2,21 +2,12 @@ require('greenlock-express') .init(function() { - // .greenlockrc defines which manager to use - // (i.e. greenlock-manager-fs or greenlock-manager-cloud) - var options = getGreenlockRc() || {}; + return { + greenlock: require('./greenlock.js'), - // 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; - - // whether or not to run at cloudscale - options.cluster = false; - - return options; + // whether or not to run at cloudscale + cluster: false + }; }) .ready(function(glx) { var app = require('./app.js'); @@ -25,14 +16,3 @@ require('greenlock-express') // Get's SSL certificates magically! glx.serveApp(app); }); - -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/certificates.js b/certificates.js index 8bd44a7..9a46a0e 100644 --- a/certificates.js +++ b/certificates.js @@ -127,7 +127,10 @@ C._rawOrder = function(gnlck, mconf, db, acme, chs, acc, email, args) { var query = { subject: args.subject, certificate: args.certificate || {}, - directoryUrl: args.directoryUrl || gnlck._defaults.directoryUrl + directoryUrl: + args.directoryUrl || + mconf.directoryUrl || + gnlck._defaults.directoryUrl }; rawPending[id] = U._getOrCreateKeypair(db, args.subject, query, keyType) .then(function(kresult) { @@ -208,7 +211,10 @@ C._check = function(gnlck, mconf, db, args) { subject: args.subject, // may contain certificate.id certificate: args.certificate, - directoryUrl: args.directoryUrl || gnlck._defaults.directoryUrl + directoryUrl: + args.directoryUrl || + mconf.directoryUrl || + gnlck._defaults.directoryUrl }; return db.check(query).then(function(pems) { if (!pems) { diff --git a/greenlock.js b/greenlock.js index c3b573f..c3b83f0 100644 --- a/greenlock.js +++ b/greenlock.js @@ -13,6 +13,7 @@ var P = require('./plugins.js'); var A = require('./accounts.js'); var C = require('./certificates.js'); var UserEvents = require('./user-events.js'); +var GreenlockRc = require('./greenlockrc.js'); var caches = {}; @@ -42,27 +43,21 @@ G.create = function(gconf) { }); } + if (!gconf.packageRoot) { + gconf.packageRoot = process.cwd(); + console.warn( + '`packageRoot` not defined, trying ' + gconf.packageRoot + ); + } + if ('function' === typeof gconf.notify) { gdefaults.notify = gconf.notify; } else { gdefaults.notify = _notify; } - if (gconf.directoryUrl) { - gdefaults = gconf.directoryUrl; - if (gconf.staging) { - throw new Error( - 'supply `directoryUrl` or `staging`, but not both' - ); - } - } else if (gconf.staging) { - gdefaults.directoryUrl = - 'https://acme-staging-v02.api.letsencrypt.org/directory'; - } else { - gdefaults.directoryUrl = - 'https://acme-v02.api.letsencrypt.org/directory'; - } - console.info('ACME Directory URL:', gdefaults.directoryUrl); + var rc = GreenlockRc.resolve(gconf); + gconf = Object.assign(rc, gconf); // Wraps each of the following with appropriate error checking // greenlock.manager.defaults @@ -83,10 +78,31 @@ G.create = function(gconf) { // greenlock.challenges.get require('./challenges-underlay.js').wrap(greenlock); + if (gconf.directoryUrl) { + gdefaults.directoryUrl = gconf.directoryUrl; + if (gconf.staging) { + throw new Error( + 'supply `directoryUrl` or `staging`, but not both' + ); + } + } else if ( + gconf.staging || + process.argv.includes('--staging') || + /DEV|STAG/i.test(process.env.ENV) + ) { + greenlock.staging = true; + gdefaults.directoryUrl = + 'https://acme-staging-v02.api.letsencrypt.org/directory'; + } else { + greenlock.live = true; + gdefaults.directoryUrl = + 'https://acme-v02.api.letsencrypt.org/directory'; + } + greenlock._defaults = gdefaults; greenlock._defaults.debug = gconf.debug; - if (!gconf._bin_mode) { + if (!gconf._bin_mode && false !== gconf.renew) { // renew every 90-ish minutes (random for staggering) // the weak setTimeout (unref) means that when run as a CLI process this // will still finish as expected, and not wait on the timeout @@ -371,7 +387,7 @@ G.create = function(gconf) { }); }; - greenlock._acme = function(args) { + greenlock._acme = function(mconf, args) { var packageAgent = gconf.packageAgent || ''; // because Greenlock_Express/v3.x Greenlock/v3 is redundant if (!/greenlock/i.test(packageAgent)) { @@ -383,7 +399,15 @@ G.create = function(gconf) { notify: greenlock._notify, debug: greenlock._defaults.debug || args.debug }); - var dirUrl = args.directoryUrl || greenlock._defaults.directoryUrl; + var dirUrl = args.directoryUrl || mconf.directoryUrl; + var showDir = false; + if (!dirUrl) { + showDir = true; + dirUrl = greenlock._defaults.directoryUrl; + } + if (showDir || /staging/.test(dirUrl)) { + console.info('ACME Directory URL:', gdefaults.directoryUrl); + } var dir = caches[dirUrl]; @@ -409,17 +433,17 @@ G.create = function(gconf) { }); }; - greenlock.order = function(args) { + greenlock.order = function(siteConf) { return greenlock._init().then(function() { return greenlock.manager._defaults().then(function(mconf) { - return greenlock._order(mconf, args); + return greenlock._order(mconf, siteConf); }); }); }; - greenlock._order = function(mconf, args) { + greenlock._order = function(mconf, siteConf) { // packageAgent, maintainerEmail - return greenlock._acme(args).then(function(acme) { - var storeConf = args.store || mconf.store; + return greenlock._acme(mconf, siteConf).then(function(acme) { + var storeConf = siteConf.store || mconf.store; storeConf = JSON.parse(JSON.stringify(storeConf)); storeConf.packageRoot = gconf.packageRoot; if (storeConf.basePath) { @@ -435,9 +459,10 @@ G.create = function(gconf) { mconf, store.accounts, acme, - args + siteConf ).then(function(account) { - var challengeConfs = args.challenges || mconf.challenges; + var challengeConfs = + siteConf.challenges || mconf.challenges; return Promise.all( Object.keys(challengeConfs).map(function(typ01) { return P._loadChallenge(challengeConfs, typ01); @@ -454,7 +479,7 @@ G.create = function(gconf) { acme, challenges, account, - args + siteConf ).then(function(pems) { if (!pems) { throw new Error('no order result'); diff --git a/greenlockrc.js b/greenlockrc.js new file mode 100644 index 0000000..54d602c --- /dev/null +++ b/greenlockrc.js @@ -0,0 +1,191 @@ +'use strict'; + +// TODO how to handle path differences when run from npx vs when required by greenlock? + +var fs = require('fs'); +var path = require('path'); + +function saveFile(rcpath, data, enc) { + // because this may have a database url or some such + fs.writeFileSync(rcpath, data, enc); + return fs.chmodSync(rcpath, parseInt('0600', 8)); +} + +var GRC = (module.exports = function(pkgpath, manager, rc) { + // TODO when run from package + // Run from the package root (assumed) or exit + var pkgdir = path.dirname(pkgpath); + + try { + require(pkgpath); + } catch (e) { + console.error( + 'npx greenlock must be run from the package root (where package.json is)' + ); + process.exit(1); + } + + try { + return module.exports._defaults(pkgdir, manager, rc); + } catch (e) { + if ('package.json' === e.context) { + console.error(e.desc); + process.exit(1); + } + console.error(e.message); + process.exit(1); + } +}); + +// Figure out what to do between what's hard-coded, +// what's in the config file, and what's left unset +module.exports.resolve = function(gconf) { + var rc = GRC.read(gconf.packageRoot); + if (gconf.configFile) { + rc = { configFile: gconf.configFile }; + } + + var manager; + var updates; + + if (rc.manager) { + if (gconf.manager && rc.manager !== gconf.manager) { + console.warn( + 'warn: ignoring hard-coded ' + + gconf.manager + + ' in favor of ' + + rc.manager + ); + } + gconf.manager = rc.manager; + } else if (gconf.manager) { + manager = gconf.manager; + } + + if (rc.configFile) { + if (gconf.configFile && rc.configFile !== gconf.configFile) { + console.warn( + 'warn: ignoring hard-coded ' + + gconf.configFile + + ' in favor of ' + + rc.configFile + ); + } + gconf.configFile = rc.configFile; + } else if (gconf.manager) { + updates = { configFile: gconf.configFile }; + } + + return GRC._defaults(gconf.packageRoot, manager, rc); +}; + +module.exports._defaults = function(pkgdir, manager, rc) { + var rcpath = path.join(pkgdir, '.greenlockrc'); + var _rc; + var created = false; + + if (manager) { + if ('.' === manager[0]) { + manager = path.resolve(pkgdir, manager); + } + try { + require(manager); + } catch (e) { + console.error('could not load ' + manager + ' from ' + pkgdir); + throw e; + } + } + + var stuff = module.exports._read(pkgdir); + _rc = stuff.rc; + created = stuff.created; + + var changed; + if (manager) { + if (!_rc.manager) { + _rc.manager = manager; + } + if (_rc.manager !== manager) { + console.info('Switching manager:'); + var older = _rc.manager; + var newer = manager; + if ('/' === older[0]) { + older = path.relative(pkgdir, older); + } + if ('/' === newer[0]) { + newer = path.relative(pkgdir, newer); + } + console.info('\told: ' + older); + console.info('\tnew: ' + newer); + changed = true; + } + } + + if (rc) { + changed = true; + Object.keys(rc).forEach(function(k) { + _rc[k] = rc[k]; + }); + } + + if (['@greenlock/manager', 'greenlock-manager-fs'].includes(_rc.manager)) { + if (!_rc.configFile) { + changed = true; + _rc.configFile = path.join(pkgdir, 'greenlock.json'); + } + } + + if (!changed) { + return _rc; + } + + var data = JSON.stringify(_rc, null, 2); + if (created) { + console.info('Wrote ' + rcpath); + } + saveFile(rcpath, data, 'utf8'); + return _rc; +}; + +module.exports.read = function(pkgdir) { + return module.exports._read(pkgdir).rc; +}; + +module.exports._read = function(pkgdir) { + var created; + var rcpath = path.join(pkgdir, '.greenlockrc'); + var _data; + try { + _data = fs.readFileSync(rcpath, 'utf8'); + } catch (err) { + if ('ENOENT' !== err.code) { + throw err; + } + try { + require(path.join(pkgdir, 'package.json')); + } catch (e) { + e.context = 'package.json'; + e.desc = + 'run `greenlock` from the same directory as `package.json`, or specify `packageRoot` of `.greenlockrc`'; + throw e; + } + console.info('Creating ' + rcpath); + created = true; + _data = '{}'; + saveFile(rcpath, _data, 'utf8'); + } + + var rc; + try { + rc = JSON.parse(_data); + } catch (e) { + console.error("couldn't parse " + rcpath, _data); + console.error('(perhaps you should just delete it and try again?)'); + process.exit(1); + } + + return { + created: created, + rc: rc + }; +}; diff --git a/manager-underlay.js b/manager-underlay.js index 9b1557f..0325622 100644 --- a/manager-underlay.js +++ b/manager-underlay.js @@ -94,7 +94,10 @@ module.exports.wrap = function(greenlock, gconf) { return mega.defaults(conf); }); }; - greenlock.manager._defaults = mega.defaults; + + greenlock.manager._defaults = function(opts) { + return mega.defaults(opts); + }; greenlock.manager.add = function(args) { if (!args || !Array.isArray(args.altnames) || !args.altnames.length) {