CLI: implement init and bugfix .greenlockrc

This commit is contained in:
AJ ONeal 2019-11-03 02:51:32 -07:00
parent 5b38fe7fcd
commit 341347ba3e
9 changed files with 342 additions and 129 deletions

View File

@ -5,7 +5,7 @@ var args = process.argv.slice(2);
var arg0 = args[0]; var arg0 = args[0];
//console.log(args); //console.log(args);
var found = ['certonly', 'add', 'update', 'config', 'defaults', 'remove'].some( var found = ['certonly', 'add', 'update', 'config', 'defaults', 'remove', 'init'].some(
function(k) { function(k) {
if (k === arg0) { if (k === arg0) {
require('./' + k); require('./' + k);

135
bin/init.js Normal file
View File

@ -0,0 +1,135 @@
'use strict';
var P = require('../plugins.js');
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');
var flagOptions = Flags.flags();
var myFlags = {};
['maintainer-email', 'cluster', 'manager', 'manager-xxxx'].forEach(function(k) {
myFlags[k] = flagOptions[k];
});
cli.parse(myFlags);
cli.main(async function(argList, flags) {
var path = require('path');
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;
}
flags.manager = flags.managerOpts;
delete flags.managerOpts;
flags.manager.manager = manager;
try {
P._loadSync(manager);
} catch (e) {
try {
P._installSync(manager);
} catch (e) {
console.error(
'error:',
JSON.stringify(manager),
'could not be loaded, and could not be installed.'
);
process.exit(1);
}
}
var GreenlockRc = require('./lib/greenlockrc.js');
//var rc = await GreenlockRc(pkgpath, manager, flags.manager);
await GreenlockRc(pkgpath, manager, flags.manager);
writeServerJs(pkgdir, flags);
writeAppJs(pkgdir);
/*
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);
*/
}, args);
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(
path.join(__dirname, 'tmpl/server.tmpl.js'),
'utf8'
);
try {
fs.accessSync(path.join(pkgdir, serverJs));
console.warn(
JSON.stringify(serverJs),
' exists, writing to ',
JSON.stringify(bakTmpl),
'instead'
);
serverJs = bakTmpl;
} 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, serverJs), tmpl);
}
function writeAppJs(pkgdir) {
var bakTmpl = 'app-greenlock-tmpl.js';
var appJs = 'app.js';
var fs = require('fs');
var path = require('path');
var tmpl = fs.readFileSync(
path.join(__dirname, 'tmpl/app.tmpl.js'),
'utf8'
);
try {
fs.accessSync(path.join(pkgdir, appJs));
console.warn(
JSON.stringify(appJs),
' exists, writing to ',
JSON.stringify(bakTmpl),
'instead'
);
appJs = bakTmpl;
} catch (e) {
// continue
}
fs.writeFileSync(path.join(pkgdir, appJs), tmpl);
}

View File

@ -14,7 +14,7 @@ CLI.parse = function(conf) {
var v = conf[k]; var v = conf[k];
if (!v) { if (!v) {
console.error( console.error(
'Developer Error: missing config value for', 'Developer Error: missing cli flag definition for',
JSON.stringify(k) JSON.stringify(k)
); );
process.exit(1); process.exit(1);

View File

@ -7,142 +7,170 @@ var path = require('path');
var pkgpath = path.join(process.cwd(), 'package.json'); var pkgpath = path.join(process.cwd(), 'package.json');
var GreenlockRc = require('./greenlockrc.js'); var GreenlockRc = require('./greenlockrc.js');
Flags.init = function(myOpts) { // These are ALL options
// The individual CLI files each select a subset of them
Flags.flags = function(mconf, myOpts) {
// Current Manager Config
if (!mconf) {
mconf = {};
}
// Extra Override Options
if (!myOpts) { if (!myOpts) {
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 = { return {
subject: [ subject: [
false, false,
'the "subject" (primary domain) of the certificate', 'the "subject" (primary domain) of the certificate',
'string' 'string'
], ],
altnames: [ altnames: [
false, false,
'the "subject alternative names" (additional domains) on the certificate, the first of which MUST be the subject', 'the "subject alternative names" (additional domains) on the certificate, the first of which MUST be the subject',
'string' 'string'
], ],
servername: [ servername: [
false, false,
'a name that matches a subject or altname', 'a name that matches a subject or altname',
'string' 'string'
], ],
servernames: [ servernames: [
false, false,
'a list of names that matches a subject or altname', 'a list of names that matches a subject or altname',
'string' 'string'
], ],
'renew-offset': [ cluster: [false, 'initialize with cluster mode on', 'boolean', false],
false, 'renew-offset': [
"time to wait until renewing the cert such as '45d' (45 days after being issued) or '-3w' (3 weeks before expiration date)", false,
'string', "time to wait until renewing the cert such as '45d' (45 days after being issued) or '-3w' (3 weeks before expiration date)",
mconf.renewOffset 'string',
], mconf.renewOffset
'customer-email': [ ],
false, 'customer-email': [
"the email address of the owner of the domain or site (not necessarily the Let's Encrypt or ACME subscriber)", false,
'string' "the email address of the owner of the domain or site (not necessarily the Let's Encrypt or ACME subscriber)",
], 'string'
'subscriber-email': [ ],
false, 'subscriber-email': [
"the email address of the Let's Encrypt or ACME Account subscriber (not necessarily the domain owner)", false,
'string' "the email address of the Let's Encrypt or ACME Account subscriber (not necessarily the domain owner)",
], 'string'
'account-key-type': [ ],
false, 'maintainer-email': [
"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)", false,
'string', 'the maintainance contact for security and critical bug notices',
mconf.accountKeyType 'string'
], ],
'server-key-type': [ 'account-key-type': [
false, 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)", "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', 'string',
mconf.serverKeyType mconf.accountKeyType
], ],
store: [ 'server-key-type': [
false, false,
'the module name or file path of the store module to use', "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' 'string',
//mconf.store.module mconf.serverKeyType
], ],
'store-xxxx': [ store: [
false, false,
'an option for the chosen store module, such as --store-apikey or --store-bucket', 'the module name or file path of the store module to use',
'bag' 'string'
], //mconf.store.module
challenge: [ ],
false, 'store-xxxx': [
'the module name or file path of the HTTP-01, DNS-01, or TLS-ALPN-01 challenge module to use', false,
'string', 'an option for the chosen store module, such as --store-apikey or --store-bucket',
'' 'bag'
/* ],
manager: [
false,
'the module name or file path of the manager module to use',
'string',
'greenlock-manager-fs'
],
'manager-xxxx': [
false,
'an option for the chosen manager module, such as --manager-apikey or --manager-dburl',
'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) Object.keys(mconf.challenges)
.map(function(typ) { .map(function(typ) {
return mconf.challenges[typ].module; return mconf.challenges[typ].module;
}) })
.join(',') .join(',')
*/ */
], ],
'challenge-xxxx': [ 'challenge-xxxx': [
false, false,
'an option for the chosen challenge module, such as --challenge-apikey or --challenge-bucket', 'an option for the chosen challenge module, such as --challenge-apikey or --challenge-bucket',
'bag' 'bag'
], ],
'challenge-json': [ 'challenge-json': [
false, false,
'a JSON string containing all option for the chosen challenge module (instead of --challenge-xxxx)', 'a JSON string containing all option for the chosen challenge module (instead of --challenge-xxxx)',
'json', 'json',
'{}' '{}'
], ],
'challenge-http-01': [ 'challenge-http-01': [
false, false,
'the module name or file path of the HTTP-01 to add', 'the module name or file path of the HTTP-01 to add',
'string' 'string'
//(mconf.challenges['http-01'] || {}).module //(mconf.challenges['http-01'] || {}).module
], ],
'challenge-http-01-xxxx': [ 'challenge-http-01-xxxx': [
false, false,
'an option for the chosen challenge module, such as --challenge-http-01-apikey or --challenge-http-01-bucket', 'an option for the chosen challenge module, such as --challenge-http-01-apikey or --challenge-http-01-bucket',
'bag' 'bag'
], ],
'challenge-dns-01': [ 'challenge-dns-01': [
false, false,
'the module name or file path of the DNS-01 to add', 'the module name or file path of the DNS-01 to add',
'string' 'string'
//(mconf.challenges['dns-01'] || {}).module //(mconf.challenges['dns-01'] || {}).module
], ],
'challenge-dns-01-xxxx': [ 'challenge-dns-01-xxxx': [
false, false,
'an option for the chosen challenge module, such as --challenge-dns-01-apikey or --challenge-dns-01-bucket', 'an option for the chosen challenge module, such as --challenge-dns-01-apikey or --challenge-dns-01-bucket',
'bag' 'bag'
], ],
'challenge-tls-alpn-01': [ 'challenge-tls-alpn-01': [
false, false,
'the module name or file path of the DNS-01 to add', 'the module name or file path of the DNS-01 to add',
'string' 'string'
//(mconf.challenges['tls-alpn-01'] || {}).module //(mconf.challenges['tls-alpn-01'] || {}).module
], ],
'challenge-tls-alpn-01-xxxx': [ 'challenge-tls-alpn-01-xxxx': [
false, false,
'an option for the chosen challenge module, such as --challenge-tls-alpn-01-apikey or --challenge-tls-alpn-01-bucket', 'an option for the chosen challenge module, such as --challenge-tls-alpn-01-apikey or --challenge-tls-alpn-01-bucket',
'bag' 'bag'
], ],
'force-save': [ 'force-save': [
false, false,
"save all options for this site, even if it's the same as the defaults", "save all options for this site, even if it's the same as the defaults",
'boolean', 'boolean',
myOpts.forceSave || false myOpts.forceSave || false
] ]
}; };
};
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 { return {
flagOptions, flagOptions,
rc, rc,

View File

@ -89,7 +89,7 @@ module.exports = async function(pkgpath, manager, rc) {
if (rc) { if (rc) {
changed = true; changed = true;
Object.keys(rc).forEach(function(k) { Object.keys(rc).forEach(function(k) {
_rc[k] = rc; _rc[k] = rc[k];
}); });
} }

9
bin/tmpl/app.tmpl.js Normal file
View File

@ -0,0 +1,9 @@
'use strict';
// Here's a vanilla HTTP app to start,
// but feel free to replace it with Express, Koa, etc
var app = function(req, res) {
res.end('Hello, Encrypted World!');
};
module.exports = app;

36
bin/tmpl/server.tmpl.js Normal file
View File

@ -0,0 +1,36 @@
'use strict';
require('greenlock-express')
.init(function() {
// .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;
// whether or not to run at cloudscale
options.cluster = false;
return options;
})
.ready(function(glx) {
var app = require('./app.js');
// Serves on 80 and 443
// 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 rcPath = '.greenlockrc';
var rc = fs.readFileSync(rcPath, 'utf8');
return JSON.parse(rc);
}

View File

@ -208,6 +208,11 @@ P._loadSync = function(modname) {
}; };
P._installSync = function(moduleName) { P._installSync = function(moduleName) {
try {
return require(moduleName);
} catch (e) {
// continue
}
var npm = 'npm'; var npm = 'npm';
var args = ['install', '--save', moduleName]; var args = ['install', '--save', moduleName];
var out = ''; var out = '';

View File

@ -14,13 +14,13 @@ node bin/greenlock.js defaults
node bin/greenlock.js defaults --challenge-dns-01 foo-http-01-bar --challenge-dns-01-token BIG_TOKEN 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) # using --challenge is exclusive (will delete things not mentioned)
node bin/greenlock.js defaults --challenge acme-http-01-standalone 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 # should delete all and add just this one anew
node bin/greenlock.js update --subject example.com --challenge bar-http-01-baz node bin/greenlock.js update --subject example.com --challenge bar-http-01-baz
# should add, leaving the existing # 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 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 # should delete all and add just this one anew
node bin/greenlock.js update --subject example.com --challenge bar-http-01-baz node bin/greenlock.js update --subject example.com --challenge bar-http-01-baz
node bin/greenlock.js remove --subject example.com
# TODO test for failure # TODO test for failure
# node bin/greenlock.js add --subject example.com # node bin/greenlock.js add --subject example.com