From 5b38fe7fcd50b1904da2a8bc68bb7bc04f024faf Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Sun, 3 Nov 2019 01:58:01 -0600 Subject: [PATCH] CLI: add, update, config, defaults, remove --- bin/add.js | 29 +-- bin/config.js | 15 +- bin/defaults.js | 79 ++++++-- bin/flags.js | 138 -------------- bin/greenlock.js | 14 +- bin/{ => lib}/cli.js | 8 +- bin/lib/flags.js | 356 +++++++++++++++++++++++++++++++++++ bin/{ => lib}/greenlockrc.js | 0 bin/remove.js | 55 ++++++ bin/update.js | 49 ++--- greenlock.js | 2 + manager-underlay.js | 20 +- package.json | 2 +- tests/cli.sh | 31 +++ 14 files changed, 590 insertions(+), 208 deletions(-) delete mode 100644 bin/flags.js rename bin/{ => lib}/cli.js (96%) create mode 100644 bin/lib/flags.js rename bin/{ => lib}/greenlockrc.js (100%) create mode 100644 bin/remove.js create mode 100644 tests/cli.sh diff --git a/bin/add.js b/bin/add.js index 4a06dfc..f289abc 100644 --- a/bin/add.js +++ b/bin/add.js @@ -1,12 +1,12 @@ 'use strict'; var args = process.argv.slice(3); -var cli = require('./cli.js'); +var cli = require('./lib/cli.js'); //var path = require('path'); //var pkgpath = path.join(__dirname, '..', 'package.json'); //var pkgpath = path.join(process.cwd(), 'package.json'); -var Flags = require('./flags.js'); +var Flags = require('./lib/flags.js'); Flags.init().then(function({ flagOptions, rc, greenlock, mconf }) { var myFlags = {}; @@ -14,7 +14,15 @@ Flags.init().then(function({ flagOptions, rc, greenlock, mconf }) { 'subject', 'altnames', 'renew-offset', + 'subscriber-email', + 'customer-email', 'server-key-type', + 'challenge-http-01', + 'challenge-http-01-xxxx', + 'challenge-dns-01', + 'challenge-dns-01-xxxx', + 'challenge-tls-alpn-01', + 'challenge-tls-alpn-01-xxxx', 'challenge', 'challenge-xxxx', 'challenge-json', @@ -25,27 +33,27 @@ Flags.init().then(function({ flagOptions, rc, greenlock, mconf }) { cli.parse(myFlags); cli.main(function(argList, flags) { - main(argList, flags, rc, greenlock, mconf); + Flags.mangleFlags(flags, mconf); + main(argList, flags, rc, greenlock); }, args); }); -async function main(_, flags, rc, greenlock, mconf) { - if (!flags.subject) { +async function main(_, flags, rc, greenlock) { + if (!flags.subject || !flags.altnames) { console.error( - '--subject must be provided as the id of the site/certificate' + '--subject and --altnames must be provided and should be valid domains' ); process.exit(1); return; } - Flags.mangleFlags(flags, mconf); - greenlock .add(flags) .catch(function(err) { console.error(); console.error('error:', err.message); console.error(); + process.exit(1); }) .then(function() { return greenlock @@ -65,10 +73,8 @@ async function main(_, flags, rc, greenlock, mconf) { process.exit(1); return; } - console.info(); - console.info('Created config!'); - console.info(); + console.info(); Object.keys(site).forEach(function(k) { if ('defaults' === k) { console.info(k + ':'); @@ -80,7 +86,6 @@ async function main(_, flags, rc, greenlock, mconf) { console.info(k + ': ' + JSON.stringify(site[k])); } }); - console.info(); }); }); } diff --git a/bin/config.js b/bin/config.js index aaf2147..b54888f 100644 --- a/bin/config.js +++ b/bin/config.js @@ -1,12 +1,12 @@ 'use strict'; var args = process.argv.slice(3); -var cli = require('./cli.js'); +var cli = require('./lib/cli.js'); //var path = require('path'); //var pkgpath = path.join(__dirname, '..', 'package.json'); //var pkgpath = path.join(process.cwd(), 'package.json'); -var Flags = require('./flags.js'); +var Flags = require('./lib/flags.js'); Flags.init().then(function({ flagOptions, rc, greenlock, mconf }) { var myFlags = {}; @@ -19,11 +19,11 @@ Flags.init().then(function({ flagOptions, rc, greenlock, mconf }) { cli.parse(myFlags); cli.main(function(argList, flags) { Flags.mangleFlags(flags, mconf); - main(argList, flags, rc, greenlock, mconf); + main(argList, flags, rc, greenlock); }, args); }); -async function main(_, flags, rc, greenlock /*, mconf */) { +async function main(_, flags, rc, greenlock) { var servernames = [flags.subject] .concat([flags.servername]) //.concat(flags.servernames) @@ -49,22 +49,23 @@ async function main(_, flags, rc, greenlock /*, mconf */) { .catch(function(err) { console.error(); console.error('error:', err.message); - console.log(err.stack); + //console.log(err.stack); console.error(); + process.exit(1); }) .then(function(site) { if (!site) { console.info(); - console.info('No config found for '); + console.info('No config found for', flags.servername); console.info(); process.exit(1); return; } + console.info(); console.info( 'Config for ' + JSON.stringify(flags.servername) + ':' ); console.info(JSON.stringify(site, null, 2)); - console.info(); }); } diff --git a/bin/defaults.js b/bin/defaults.js index d61f6fa..04512ff 100644 --- a/bin/defaults.js +++ b/bin/defaults.js @@ -1,17 +1,62 @@ - store: [ - false, - 'the npm name or path of the storage module or callbacks file', - 'string', - 'greenlock-store-fs' - ], - 'store-xxxx': [ - false, - 'an option for the chosen storage module, such as --store-apikey or --store-bucket', - 'bag' - ], - 'store-json': [ - false, - 'a JSON string containing all option for the chosen store module (instead of --store-xxxx)', - 'json', - '{}' - ], +'use strict'; + +var args = process.argv.slice(3); +var cli = require('./lib/cli.js'); +//var path = require('path'); +//var pkgpath = path.join(__dirname, '..', 'package.json'); +//var pkgpath = path.join(process.cwd(), 'package.json'); + +var Flags = require('./lib/flags.js'); + +Flags.init({ forceSave: true }).then(function({ + flagOptions, + rc, + greenlock, + mconf +}) { + var myFlags = {}; + [ + 'account-key-type', + 'server-key-type', + 'subscriber-email', + 'renew-offset', + 'store', + 'store-xxxx', + 'challenge-http-01-xxxx', + 'challenge-dns-01', + 'challenge-dns-01-xxxx', + 'challenge-tls-alpn-01', + 'challenge-tls-alpn-01-xxxx', + 'challenge', + 'challenge-xxxx', + 'challenge-http-01', + ].forEach(function(k) { + myFlags[k] = flagOptions[k]; + }); + + cli.parse(myFlags); + cli.main(function(argList, flags) { + Flags.mangleFlags(flags, mconf, null, { forceSave: true }); + main(argList, flags, rc, greenlock); + }, args); +}); + +async function main(_, flags, rc, greenlock) { + greenlock.manager + .defaults(flags) + .catch(function(err) { + console.error(); + console.error('error:', err.message); + //console.log(err.stack); + console.error(); + process.exit(1); + }) + .then(function() { + return greenlock.manager.defaults(); + }) + .then(function(dconf) { + console.info(); + console.info('Global config'); + console.info(JSON.stringify(dconf, null, 2)); + }); +} diff --git a/bin/flags.js b/bin/flags.js deleted file mode 100644 index ea82afe..0000000 --- a/bin/flags.js +++ /dev/null @@ -1,138 +0,0 @@ -'use strict'; - -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'); - -Flags.init = function() { - return GreenlockRc(pkgpath).then(async function(rc) { - var Greenlock = require('../'); - // this is a copy, so it's safe to modify - rc._bin_mode = true; - var greenlock = Greenlock.create(rc); - var mconf = await greenlock.manager.defaults(); - - var flagOptions = { - subject: [ - false, - 'the "subject" (primary domain) of the certificate', - 'string' - ], - altnames: [ - false, - 'the "subject alternative names" (additional domains) on the certificate, the first of which MUST be the subject', - 'string' - ], - servername: [ - false, - 'a name that matches a subject or altname', - 'string' - ], - servernames: [ - false, - 'a list of names that matches a subject or altname', - 'string' - ], - 'renew-offset': [ - false, - "time to wait until renewing the cert such as '45d' (45 days after being issued) or '-3w' (3 weeks before expiration date)", - 'string', - mconf.renewOffset - ], - 'server-key-type': [ - false, - "either 'RSA-2048' or 'P-256' (ECDSA) - although other values are technically supported, they don't make sense and won't work with many services (More bits != More security)", - 'string', - mconf.serverKeyType - ], - challenge: [ - false, - 'the name name of file path of the HTTP-01, DNS-01, or TLS-ALPN-01 challenge module to use', - 'string', - Object.keys(mconf.challenges) - .map(function(typ) { - return mconf.challenges[typ].module; - }) - .join(',') - ], - 'challenge-xxxx': [ - false, - 'an option for the chosen challenge module, such as --challenge-apikey or --challenge-bucket', - 'bag' - ], - 'challenge-json': [ - false, - 'a JSON string containing all option for the chosen challenge module (instead of --challenge-xxxx)', - 'json', - '{}' - ], - 'force-save': [ - false, - "save all options for this site, even if it's the same as the defaults", - 'boolean', - false - ] - }; - - return { - flagOptions, - rc, - greenlock, - mconf - }; - }); -}; - -Flags.mangleFlags = function(flags, mconf) { - if ('altnames' in flags) { - flags.altnames = (flags.altnames || '').split(/[,\s]+/).filter(Boolean); - } - if ('servernames' in flags) { - flags.servernames = (flags.servernames || '') - .split(/[,\s]+/) - .filter(Boolean); - } - - Object.keys(flags).forEach(function(k) { - if (flags[k] === mconf[k] && !flags.forceSave) { - delete flags[k]; - } - }); - - var typ; - var challenge; - if (flags.challenge) { - if (/http-01/.test(flags.challenge)) { - typ = 'http-01'; - } else if (/dns-01/.test(flags.challenge)) { - typ = 'dns-01'; - } else if (/tls-alpn-01/.test(flags.challenge)) { - typ = 'tls-alpn-01'; - } - - challenge = flags.challengeOpts; - challenge.module = flags.challenge; - flags.challenges = {}; - flags.challenges[typ] = challenge; - delete flags.challengeOpts; - delete flags.challenge; - - var chall = mconf.challenges[typ]; - if (challenge.module === chall.module) { - var keys = Object.keys(challenge); - var same = - !keys.length || - keys.every(function(k) { - return chall[k] === challenge[k]; - }); - if (same && !flags.forceSave) { - delete flags.challenges; - } - } - } - - delete flags.forceSave; -}; diff --git a/bin/greenlock.js b/bin/greenlock.js index 282f5eb..c947217 100755 --- a/bin/greenlock.js +++ b/bin/greenlock.js @@ -5,14 +5,14 @@ var args = process.argv.slice(2); var arg0 = args[0]; //console.log(args); -var found = ['certonly', 'add', 'update', 'config', 'defaults', 'remove'].some(function( - k -) { - if (k === arg0) { - require('./' + k); - return true; +var found = ['certonly', 'add', 'update', 'config', 'defaults', 'remove'].some( + function(k) { + if (k === arg0) { + require('./' + k); + return true; + } } -}); +); if (!found) { console.error(arg0 + ': command not yet implemented'); diff --git a/bin/cli.js b/bin/lib/cli.js similarity index 96% rename from bin/cli.js rename to bin/lib/cli.js index 49d92f6..e544b31 100644 --- a/bin/cli.js +++ b/bin/lib/cli.js @@ -12,6 +12,13 @@ CLI.parse = function(conf) { Object.keys(conf).forEach(function(k) { var v = conf[k]; + if (!v) { + console.error( + 'Developer Error: missing config value for', + JSON.stringify(k) + ); + process.exit(1); + } var aliases = v[5]; var bag; var bagName; @@ -85,7 +92,6 @@ CLI.main = function(cb, args) { if (bag !== flag.slice(0, bag.length)) { return false; } - console.log(bagName, toCamel(flag.slice(bag.length))); opts[bagName][toCamel(flag.slice(bag.length))] = args.shift(); return true; } diff --git a/bin/lib/flags.js b/bin/lib/flags.js new file mode 100644 index 0000000..cc1afff --- /dev/null +++ b/bin/lib/flags.js @@ -0,0 +1,356 @@ +'use strict'; + +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'); + +Flags.init = function(myOpts) { + if (!myOpts) { + myOpts = {}; + } + return GreenlockRc(pkgpath).then(async function(rc) { + var Greenlock = require('../../'); + // this is a copy, so it's safe to modify + rc._bin_mode = true; + var greenlock = Greenlock.create(rc); + var mconf = await greenlock.manager.defaults(); + + var flagOptions = { + subject: [ + false, + 'the "subject" (primary domain) of the certificate', + 'string' + ], + altnames: [ + false, + 'the "subject alternative names" (additional domains) on the certificate, the first of which MUST be the subject', + 'string' + ], + servername: [ + false, + 'a name that matches a subject or altname', + 'string' + ], + servernames: [ + false, + 'a list of names that matches a subject or altname', + 'string' + ], + 'renew-offset': [ + false, + "time to wait until renewing the cert such as '45d' (45 days after being issued) or '-3w' (3 weeks before expiration date)", + 'string', + mconf.renewOffset + ], + 'customer-email': [ + false, + "the email address of the owner of the domain or site (not necessarily the Let's Encrypt or ACME subscriber)", + 'string' + ], + 'subscriber-email': [ + false, + "the email address of the Let's Encrypt or ACME Account subscriber (not necessarily the domain owner)", + 'string' + ], + 'account-key-type': [ + false, + "either 'P-256' (ECDSA) or 'RSA-2048' - although other values are technically supported, they don't make sense and won't work with many services (More bits != More security)", + 'string', + mconf.accountKeyType + ], + 'server-key-type': [ + false, + "either 'RSA-2048' or 'P-256' (ECDSA) - although other values are technically supported, they don't make sense and won't work with many services (More bits != More security)", + 'string', + mconf.serverKeyType + ], + store: [ + false, + 'the module name or file path of the store module to use', + 'string' + //mconf.store.module + ], + 'store-xxxx': [ + false, + 'an option for the chosen store module, such as --store-apikey or --store-bucket', + 'bag' + ], + challenge: [ + false, + 'the module name or file path of the HTTP-01, DNS-01, or TLS-ALPN-01 challenge module to use', + 'string', + '' + /* + Object.keys(mconf.challenges) + .map(function(typ) { + return mconf.challenges[typ].module; + }) + .join(',') + */ + ], + 'challenge-xxxx': [ + false, + 'an option for the chosen challenge module, such as --challenge-apikey or --challenge-bucket', + 'bag' + ], + 'challenge-json': [ + false, + 'a JSON string containing all option for the chosen challenge module (instead of --challenge-xxxx)', + 'json', + '{}' + ], + 'challenge-http-01': [ + false, + 'the module name or file path of the HTTP-01 to add', + 'string' + //(mconf.challenges['http-01'] || {}).module + ], + 'challenge-http-01-xxxx': [ + false, + 'an option for the chosen challenge module, such as --challenge-http-01-apikey or --challenge-http-01-bucket', + 'bag' + ], + 'challenge-dns-01': [ + false, + 'the module name or file path of the DNS-01 to add', + 'string' + //(mconf.challenges['dns-01'] || {}).module + ], + 'challenge-dns-01-xxxx': [ + false, + 'an option for the chosen challenge module, such as --challenge-dns-01-apikey or --challenge-dns-01-bucket', + 'bag' + ], + 'challenge-tls-alpn-01': [ + false, + 'the module name or file path of the DNS-01 to add', + 'string' + //(mconf.challenges['tls-alpn-01'] || {}).module + ], + 'challenge-tls-alpn-01-xxxx': [ + false, + 'an option for the chosen challenge module, such as --challenge-tls-alpn-01-apikey or --challenge-tls-alpn-01-bucket', + 'bag' + ], + 'force-save': [ + false, + "save all options for this site, even if it's the same as the defaults", + 'boolean', + myOpts.forceSave || false + ] + }; + + return { + flagOptions, + rc, + greenlock, + mconf + }; + }); +}; + +Flags.mangleFlags = function(flags, mconf, sconf, extras) { + if (extras) { + if (extras.forceSave) { + flags.forceSave = true; + } + } + //console.log('debug a:', flags); + + if ('altnames' in flags) { + flags.altnames = (flags.altnames || '').split(/[,\s]+/).filter(Boolean); + } + if ('servernames' in flags) { + flags.servernames = (flags.servernames || '') + .split(/[,\s]+/) + .filter(Boolean); + } + + var store; + if (flags.store) { + store = flags.storeOpts; + store.module = flags.store; + flags.store = store; + } else { + delete flags.store; + } + delete flags.storeOpts; + + // If this is additive, make an object to hold all values + var isAdditive = [ + ['http-01', 'Http01'], + ['dns-01', 'Dns01'], + ['tls-alpn-01', 'TlsAlpn01'] + ].some(function(types) { + var typCamel = types[1]; + var modname = 'challenge' + typCamel; + if (flags[modname]) { + if (!flags.challenges) { + flags.challenges = {}; + } + return true; + } + }); + if (isAdditive && sconf) { + // copy over the old + var schallenges = sconf.challenges || {}; + Object.keys(schallenges).forEach(function(k) { + if (!flags.challenges[k]) { + flags.challenges[k] = schallenges[k]; + } + }); + } + + var typ; + var challenge; + if (flags.challenge) { + // this varient of the flag is exclusive + flags.challenges = {}; + isAdditive = false; + + if (/http-01/.test(flags.challenge)) { + typ = 'http-01'; + } else if (/dns-01/.test(flags.challenge)) { + typ = 'dns-01'; + } else if (/tls-alpn-01/.test(flags.challenge)) { + typ = 'tls-alpn-01'; + } + + var modname = 'challenge'; + var optsname = 'challengeOpts'; + challenge = flags[optsname]; + // JSON may already have module name + if (challenge.module) { + if (flags[modname] && challenge.module !== flags[modname]) { + console.error( + 'module names do not match:', + JSON.stringify(challenge.module), + JSON.stringify(flags[modname]) + ); + process.exit(1); + } + } else { + challenge.module = flags[modname]; + } + flags.challenges[typ] = challenge; + + var chall = mconf.challenges[typ]; + if (chall && challenge.module === chall.module) { + var keys = Object.keys(challenge); + var same = + !keys.length || + keys.every(function(k) { + return chall[k] === challenge[k]; + }); + if (same && !flags.forceSave) { + delete flags.challenges; + } + } + } + delete flags.challenge; + delete flags.challengeOpts; + + // Add each of the values, including the existing + [ + ['http-01', 'Http01'], + ['dns-01', 'Dns01'], + ['tls-alpn-01', 'TlsAlpn01'] + ].forEach(function(types) { + var typ = types[0]; + var typCamel = types[1]; + var modname = 'challenge' + typCamel; + var optsname = 'challenge' + typCamel + 'Opts'; + var chall = mconf.challenges[typ]; + var challenge = flags[optsname]; + + // this variant of the flag is additive + if (isAdditive && chall && flags.forceSave) { + if (flags.challenges && !flags.challenges[typ]) { + flags.challenges[typ] = chall; + } + } + + if (!flags[modname]) { + delete flags[modname]; + delete flags[optsname]; + return; + } + + // JSON may already have module name + if (challenge.module) { + if (flags[modname] && challenge.module !== flags[modname]) { + console.error( + 'module names do not match:', + JSON.stringify(challenge.module), + JSON.stringify(flags[modname]) + ); + process.exit(1); + } + } else { + challenge.module = flags[modname]; + } + if (flags[modname]) { + if (!flags.challenges) { + flags.challenges = {}; + } + flags.challenges[typ] = challenge; + } + + // Check to see if this is already what's set in the defaults + if (chall && challenge.module === chall.module) { + var keys = Object.keys(challenge); + // Check if all of the options are also the same + var same = + !keys.length || + keys.every(function(k) { + return chall[k] === challenge[k]; + }); + if (same && !flags.forceSave) { + // If it's already the global, don't make it the per-site + delete flags[modname]; + delete flags[optsname]; + } + } + + delete flags[modname]; + delete flags[optsname]; + }); + + [ + ['accountKeyType', [/256/, /384/, /EC/], 'EC-P256'], + ['serverKeyType', [/RSA/], 'RSA-2048'] + ].forEach(function(k) { + var key = k[0]; + var vals = k[1]; + var val = flags[key]; + if (val) { + if ( + !vals.some(function(v) { + return v.test(val); + }) + ) { + flags[key] = k[2]; + console.warn( + key, + "does not allow the value '", + val, + "' using the default '", + k[2], + "' instead." + ); + } + } + }); + + Object.keys(flags).forEach(function(k) { + if (flags[k] === mconf[k] && !flags.forceSave) { + delete flags[k]; + } + }); + + //console.log('debug z:', flags); + delete flags.forceSave; +}; diff --git a/bin/greenlockrc.js b/bin/lib/greenlockrc.js similarity index 100% rename from bin/greenlockrc.js rename to bin/lib/greenlockrc.js diff --git a/bin/remove.js b/bin/remove.js new file mode 100644 index 0000000..b1f23cb --- /dev/null +++ b/bin/remove.js @@ -0,0 +1,55 @@ +'use strict'; + +var args = process.argv.slice(3); +var cli = require('./lib/cli.js'); +//var path = require('path'); +//var pkgpath = path.join(__dirname, '..', 'package.json'); +//var pkgpath = path.join(process.cwd(), 'package.json'); + +var Flags = require('./lib/flags.js'); + +Flags.init().then(function({ flagOptions, rc, greenlock, mconf }) { + var myFlags = {}; + ['subject'].forEach(function(k) { + myFlags[k] = flagOptions[k]; + }); + + cli.parse(myFlags); + cli.main(function(argList, flags) { + Flags.mangleFlags(flags, mconf); + main(argList, flags, rc, greenlock); + }, args); +}); + +async function main(_, flags, rc, greenlock) { + if (!flags.subject) { + console.error('--subject must be provided as a valid domain'); + process.exit(1); + return; + } + + greenlock + .remove(flags) + .catch(function(err) { + console.error(); + console.error('error:', err.message); + //console.log(err.stack); + console.error(); + process.exit(1); + }) + .then(function(site) { + if (!site) { + console.info(); + console.info('No config found for', flags.subject); + console.info(); + process.exit(1); + return; + } + console.info(); + console.info( + 'Deleted config for ' + JSON.stringify(flags.subject) + ':' + ); + console.info(JSON.stringify(site, null, 2)); + console.info(); + }); +} diff --git a/bin/update.js b/bin/update.js index fcbffc4..b8fdd7c 100644 --- a/bin/update.js +++ b/bin/update.js @@ -1,8 +1,8 @@ 'use strict'; var args = process.argv.slice(3); -var cli = require('./cli.js'); -var Flags = require('./flags.js'); +var cli = require('./lib/cli.js'); +var Flags = require('./lib/flags.js'); Flags.init().then(function({ flagOptions, rc, greenlock, mconf }) { var myFlags = {}; @@ -10,7 +10,15 @@ Flags.init().then(function({ flagOptions, rc, greenlock, mconf }) { 'subject', 'altnames', 'renew-offset', + 'subscriber-email', + 'customer-email', 'server-key-type', + 'challenge-http-01', + 'challenge-http-01-xxxx', + 'challenge-dns-01', + 'challenge-dns-01-xxxx', + 'challenge-tls-alpn-01', + 'challenge-tls-alpn-01-xxxx', 'challenge', 'challenge-xxxx', 'challenge-json', @@ -20,41 +28,41 @@ Flags.init().then(function({ flagOptions, rc, greenlock, mconf }) { }); cli.parse(myFlags); - cli.main(function(argList, flags) { - main(argList, flags, rc, greenlock, mconf); + cli.main(async function(argList, flags) { + var sconf = await greenlock._config({ servername: flags.subject }); + Flags.mangleFlags(flags, mconf, sconf); + main(argList, flags, rc, greenlock); }, args); }); -async function main(_, flags, rc, greenlock, mconf) { - if (!flags.subject || !flags.altnames) { - console.error( - '--subject and --altnames must be provided and should be valid domains' - ); +async function main(_, flags, rc, greenlock) { + if (!flags.subject) { + console.error('--subject must be provided as a valid domain'); process.exit(1); return; } - Flags.mangleFlags(flags, mconf); - - greenlock.update(flags).catch(function(err) { - console.error(); - console.error('error:', err.message); - console.error(); - }) .then(function() { + greenlock + .update(flags) + .catch(function(err) { + console.error(); + console.error('error:', err.message); + console.error(); + process.exit(1); + }) + .then(function() { return greenlock ._config({ servername: flags.subject }) .then(function(site) { if (!site) { console.info(); - console.info('No config found for '); + console.info('No config found for', flags.subject); console.info(); process.exit(1); return; } - console.info(); - console.info("Updated config!"); - console.info(); + console.info(); Object.keys(site).forEach(function(k) { if ('defaults' === k) { console.info(k + ':'); @@ -66,7 +74,6 @@ async function main(_, flags, rc, greenlock, mconf) { console.info(k + ': ' + JSON.stringify(site[k])); } }); - console.info(); }); }); } diff --git a/greenlock.js b/greenlock.js index 18bff3f..b07682e 100644 --- a/greenlock.js +++ b/greenlock.js @@ -77,6 +77,8 @@ G.create = function(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; + greenlock.sites.update = greenlock.update = greenlock.manager.update; + greenlock.sites.remove = greenlock.remove = greenlock.manager.remove; // Exports challenges.get for Greenlock Express HTTP-01, // and whatever odd use case pops up, I suppose diff --git a/manager-underlay.js b/manager-underlay.js index f1db0f7..d7e4907 100644 --- a/manager-underlay.js +++ b/manager-underlay.js @@ -87,7 +87,7 @@ module.exports.wrap = function(greenlock, manager, gconf) { }); }; - greenlock.add = greenlock.manager.add = function(args) { + greenlock.manager.add = function(args) { if (!args || !Array.isArray(args.altnames) || !args.altnames.length) { throw new Error( 'you must specify `altnames` when adding a new site' @@ -158,9 +158,21 @@ module.exports.wrap = function(greenlock, manager, gconf) { }; greenlock.manager.remove = function(args) { - args.subject = checkSubject(args); - // TODO check no altnames - return manager.remove(args); + return Promise.resolve().then(function() { + args.subject = checkSubject(args); + if (args.servername) { + throw new Error( + 'remove() should be called with `subject` only, if you wish to remove altnames use `update()`' + ); + } + if (args.altnames) { + throw new Error( + 'remove() should be called with `subject` only, not `altnames`' + ); + } + // TODO check no altnames + return manager.remove(args); + }); }; /* diff --git a/package.json b/package.json index 7920aef..7b19d2d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@root/greenlock", - "version": "3.1.0-wip", + "version": "3.1.0", "description": "The easiest Let's Encrypt client for Node.js and Browsers", "homepage": "https://rootprojects.org/greenlock/", "main": "greenlock.js", diff --git a/tests/cli.sh b/tests/cli.sh new file mode 100644 index 0000000..8206f83 --- /dev/null +++ b/tests/cli.sh @@ -0,0 +1,31 @@ +#!/bin/bash + +set -e + +# TODO notify if wildcard is selected and no dns challenge is present +node bin/greenlock.js add --subject example.com --altnames 'example.com,*.example.com' +node bin/greenlock.js update --subject example.com +node bin/greenlock.js config --subject example.com +node bin/greenlock.js config --subject *.example.com +node bin/greenlock.js defaults +node bin/greenlock.js defaults --account-key-type +node bin/greenlock.js defaults +# using --challenge-xx-xx-xxx is additive +node bin/greenlock.js defaults --challenge-dns-01 foo-http-01-bar --challenge-dns-01-token BIG_TOKEN +# using --challenge is exclusive (will delete things not mentioned) +node bin/greenlock.js defaults --challenge acme-http-01-standalone +node bin/greenlock.js remove --subject example.com +# should delete all and add just this one anew +node bin/greenlock.js update --subject example.com --challenge bar-http-01-baz +# should add, leaving the existing +node bin/greenlock.js update --subject example.com --challenge-dns-01 baz-dns-01-qux --challenge-dns-01-token BIG_TOKEN +# should delete all and add just this one anew +node bin/greenlock.js update --subject example.com --challenge bar-http-01-baz + +# TODO test for failure +# node bin/greenlock.js add --subject example.com +# node bin/greenlock.js add --subject example --altnames example +# node bin/greenlock.js add --subject example.com --altnames '*.example.com' +# node bin/greenlock.js add --subject example.com --altnames '*.example.com,example.com' +# node bin/greenlock.js update --altnames example.com +# node bin/greenlock.js config foo.example.com