Compare commits

..

2 Commits

Author SHA1 Message Date
AJ ONeal 5b38fe7fcd CLI: add, update, config, defaults, remove 2019-11-03 01:58:01 -06:00
AJ ONeal 2dbd61158f cli: add, update, config 2019-11-02 19:33:12 -06:00
14 changed files with 787 additions and 138 deletions

View File

@ -1,134 +1,91 @@
'use strict'; 'use strict';
var args = process.argv.slice(3); var args = process.argv.slice(3);
var cli = require('./cli.js'); var cli = require('./lib/cli.js');
var path = require('path'); //var path = require('path');
//var pkgpath = path.join(__dirname, '..', 'package.json'); //var pkgpath = path.join(__dirname, '..', 'package.json');
var pkgpath = path.join(process.cwd(), 'package.json'); //var pkgpath = path.join(process.cwd(), 'package.json');
require('./greenlockrc')(pkgpath).then(async function(rc) { var Flags = require('./lib/flags.js');
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();
cli.parse({ Flags.init().then(function({ flagOptions, rc, greenlock, mconf }) {
subject: [ var myFlags = {};
false, [
'the "subject" (primary domain) of the certificate', 'subject',
'string' 'altnames',
], 'renew-offset',
altnames: [ 'subscriber-email',
false, 'customer-email',
'the "subject alternative names" (additional domains) on the certificate, the first of which MUST be the subject', 'server-key-type',
'string' 'challenge-http-01',
], 'challenge-http-01-xxxx',
'renew-offset': [ 'challenge-dns-01',
false, 'challenge-dns-01-xxxx',
"time to wait until renewing the cert such as '45d' (45 days after being issued) or '-3w' (3 weeks before expiration date)", 'challenge-tls-alpn-01',
'string', 'challenge-tls-alpn-01-xxxx',
mconf.renewOffset 'challenge',
], 'challenge-xxxx',
'server-key-type': [ 'challenge-json',
false, 'force-save'
"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)", ].forEach(function(k) {
'string', myFlags[k] = flagOptions[k];
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
]
}); });
// ignore certonly and extraneous arguments cli.parse(myFlags);
async function main(_, options) { cli.main(function(argList, flags) {
if (!options.subject || !options.altnames) { Flags.mangleFlags(flags, mconf);
console.error( main(argList, flags, rc, greenlock);
'--subject and --altnames must be provided and should be valid domains' }, args);
); });
process.exit(1);
return;
}
options.altnames = options.altnames.split(/[,\s]+/);
Object.keys(options).forEach(function(k) { async function main(_, flags, rc, greenlock) {
if (options[k] === mconf[k] && !options.forceSave) { if (!flags.subject || !flags.altnames) {
delete options[k]; console.error(
} '--subject and --altnames must be provided and should be valid domains'
}); );
process.exit(1);
return;
}
var typ; greenlock
var challenge; .add(flags)
if (options.challenge) { .catch(function(err) {
if (/http-01/.test(options.challenge)) {
typ = 'http-01';
} else if (/dns-01/.test(options.challenge)) {
typ = 'dns-01';
} else if (/tls-alpn-01/.test(options.challenge)) {
typ = 'tls-alpn-01';
}
challenge = options.challengeOpts;
challenge.module = options.challenge;
options.challenges = {};
options.challenges[typ] = challenge;
delete options.challengeOpts;
delete options.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 && !options.forceSave) {
delete options.challenges;
}
}
}
delete options.forceSave;
/*
console.log('manager conf:');
console.log(mconf);
console.log('cli options:');
console.log(options);
*/
greenlock.add(options).catch(function(err) {
console.error(); console.error();
console.error('error:', err.message); console.error('error:', err.message);
console.error(); console.error();
}); process.exit(1);
} })
.then(function() {
return greenlock
._config({
servername:
flags.altnames[
Math.floor(Math.random() * flags.altnames.length)
]
})
.then(function(site) {
if (!site) {
console.info();
console.info(
'Internal bug or configuration mismatch: No config found.'
);
console.info();
process.exit(1);
return;
}
cli.main(main, process.argv.slice(3)); console.info();
}); Object.keys(site).forEach(function(k) {
if ('defaults' === k) {
console.info(k + ':');
Object.keys(site.defaults).forEach(function(key) {
var value = JSON.stringify(site.defaults[key]);
console.info('\t' + key + ':' + value);
});
} else {
console.info(k + ': ' + JSON.stringify(site[k]));
}
});
});
});
}

71
bin/config.js Normal file
View File

@ -0,0 +1,71 @@
'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', 'servername' /*, 'servernames', 'altnames'*/].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) {
var servernames = [flags.subject]
.concat([flags.servername])
//.concat(flags.servernames)
//.concat(flags.altnames)
.filter(Boolean);
delete flags.subject;
delete flags.altnames;
flags.servernames = servernames;
if (flags.servernames.length > 1) {
console.error('Error: should only have one servername');
process.exit(1);
return;
} else if (flags.servernames.length !== 1) {
console.error('Error: need a servername to check');
process.exit(1);
return;
}
flags.servername = flags.servernames[0];
delete flags.servernames;
greenlock
._config(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.servername);
console.info();
process.exit(1);
return;
}
console.info();
console.info(
'Config for ' + JSON.stringify(flags.servername) + ':'
);
console.info(JSON.stringify(site, null, 2));
});
}

62
bin/defaults.js Normal file
View File

@ -0,0 +1,62 @@
'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));
});
}

View File

@ -5,14 +5,14 @@ var args = process.argv.slice(2);
var arg0 = args[0]; var arg0 = args[0];
//console.log(args); //console.log(args);
var found = ['certonly', 'add', 'config', 'defaults', 'remove'].some(function( var found = ['certonly', 'add', 'update', 'config', 'defaults', 'remove'].some(
k function(k) {
) { if (k === arg0) {
if (k === arg0) { require('./' + k);
require('./' + k); return true;
return true; }
} }
}); );
if (!found) { if (!found) {
console.error(arg0 + ': command not yet implemented'); console.error(arg0 + ': command not yet implemented');

View File

@ -12,6 +12,13 @@ CLI.parse = function(conf) {
Object.keys(conf).forEach(function(k) { Object.keys(conf).forEach(function(k) {
var v = conf[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 aliases = v[5];
var bag; var bag;
var bagName; var bagName;
@ -85,7 +92,6 @@ CLI.main = function(cb, args) {
if (bag !== flag.slice(0, bag.length)) { if (bag !== flag.slice(0, bag.length)) {
return false; return false;
} }
console.log(bagName, toCamel(flag.slice(bag.length)));
opts[bagName][toCamel(flag.slice(bag.length))] = args.shift(); opts[bagName][toCamel(flag.slice(bag.length))] = args.shift();
return true; return true;
} }

356
bin/lib/flags.js Normal file
View File

@ -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;
};

55
bin/remove.js Normal file
View File

@ -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();
});
}

79
bin/update.js Normal file
View File

@ -0,0 +1,79 @@
'use strict';
var args = process.argv.slice(3);
var cli = require('./lib/cli.js');
var Flags = require('./lib/flags.js');
Flags.init().then(function({ flagOptions, rc, greenlock, mconf }) {
var myFlags = {};
[
'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',
'force-save'
].forEach(function(k) {
myFlags[k] = flagOptions[k];
});
cli.parse(myFlags);
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) {
if (!flags.subject) {
console.error('--subject must be provided as a valid domain');
process.exit(1);
return;
}
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', flags.subject);
console.info();
process.exit(1);
return;
}
console.info();
Object.keys(site).forEach(function(k) {
if ('defaults' === k) {
console.info(k + ':');
Object.keys(site.defaults).forEach(function(key) {
var value = JSON.stringify(site.defaults[key]);
console.info('\t' + key + ':' + value);
});
} else {
console.info(k + ': ' + JSON.stringify(site[k]));
}
});
});
});
}

View File

@ -77,6 +77,8 @@ G.create = function(gconf) {
// The goal here is to reduce boilerplate, such as error checking // The goal here is to reduce boilerplate, such as error checking
// and duration parsing, that a manager must implement // and duration parsing, that a manager must implement
greenlock.sites.add = greenlock.add = greenlock.manager.add; 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, // Exports challenges.get for Greenlock Express HTTP-01,
// and whatever odd use case pops up, I suppose // and whatever odd use case pops up, I suppose
@ -128,9 +130,13 @@ G.create = function(gconf) {
}); });
}) })
.catch(function(err) { .catch(function(err) {
console.error('Fatal error during greenlock init:'); if ('load_plugin' !== err.context) {
console.error(err); console.error('Fatal error during greenlock init:');
process.exit(1); console.error(err.message);
}
if (!gconf._bin_mode) {
process.exit(1);
}
}); });
return p; return p;
}; };
@ -247,8 +253,14 @@ G.create = function(gconf) {
.split('.') .split('.')
.slice(1) .slice(1)
.join('.'); .join('.');
if (args.wildname.split('.').length < 3) {
// No '*.com'
args.wildname = '';
}
if ( if (
args.servernames || args.servernames ||
//TODO I think we need to block altnames as well, but I don't want to break anything
//args.altnames ||
args.subject || args.subject ||
args.renewBefore || args.renewBefore ||
args.issueBefore || args.issueBefore ||
@ -279,12 +291,16 @@ G.create = function(gconf) {
if (site.store && site.challenges) { if (site.store && site.challenges) {
return site; return site;
} }
var dconf = site;
if (gconf._bin_mode) {
dconf = site.defaults = {};
}
return manager.defaults().then(function(mconf) { return manager.defaults().then(function(mconf) {
if (!site.store) { if (!site.store) {
site.store = mconf.store; dconf.store = mconf.store;
} }
if (!site.challenges) { if (!site.challenges) {
site.challenges = mconf.challenges; dconf.challenges = mconf.challenges;
} }
return site; return site;
}); });
@ -483,6 +499,7 @@ function normalizeManager(gconf) {
// wrap this to be safe for greenlock-manager-fs // wrap this to be safe for greenlock-manager-fs
m = require(gconf.manager).create(gconf); m = require(gconf.manager).create(gconf);
} catch (e) { } catch (e) {
console.error('Error loading manager:');
console.error(e.code); console.error(e.code);
console.error(e.message); console.error(e.message);
} }
@ -592,6 +609,7 @@ function mergeDefaults(MCONF, gconf) {
}; };
} }
} }
// just to test that it loads // just to test that it loads
P._loadSync(MCONF.store.module); P._loadSync(MCONF.store.module);

View File

@ -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) { if (!args || !Array.isArray(args.altnames) || !args.altnames.length) {
throw new Error( throw new Error(
'you must specify `altnames` when adding a new site' '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) { greenlock.manager.remove = function(args) {
args.subject = checkSubject(args); return Promise.resolve().then(function() {
// TODO check no altnames args.subject = checkSubject(args);
return manager.remove(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);
});
}; };
/* /*

View File

@ -1,6 +1,6 @@
{ {
"name": "@root/greenlock", "name": "@root/greenlock",
"version": "3.1.0-wip", "version": "3.1.0",
"description": "The easiest Let's Encrypt client for Node.js and Browsers", "description": "The easiest Let's Encrypt client for Node.js and Browsers",
"homepage": "https://rootprojects.org/greenlock/", "homepage": "https://rootprojects.org/greenlock/",
"main": "greenlock.js", "main": "greenlock.js",

View File

@ -31,6 +31,7 @@ P._loadHelper = function(modname) {
console.error("Could not load '%s'", modname); console.error("Could not load '%s'", modname);
console.error('Did you install it?'); console.error('Did you install it?');
console.error('\tnpm install --save %s', modname); console.error('\tnpm install --save %s', modname);
e.context = 'load_plugin';
throw e; throw e;
// Fun experiment, bad idea // Fun experiment, bad idea
@ -193,6 +194,7 @@ P._loadSync = function(modname) {
console.error("Could not load '%s'", modname); console.error("Could not load '%s'", modname);
console.error('Did you install it?'); console.error('Did you install it?');
console.error('\tnpm install --save %s', modname); console.error('\tnpm install --save %s', modname);
e.context = 'load_plugin';
throw e; throw e;
} }
/* /*

31
tests/cli.sh Normal file
View File

@ -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