Browse Source

CLI: implement init and bugfix .greenlockrc

v4
AJ ONeal 3 years ago
parent
commit
341347ba3e
  1. 2
      bin/greenlock.js
  2. 135
      bin/init.js
  3. 2
      bin/lib/cli.js
  4. 278
      bin/lib/flags.js
  5. 2
      bin/lib/greenlockrc.js
  6. 9
      bin/tmpl/app.tmpl.js
  7. 36
      bin/tmpl/server.tmpl.js
  8. 5
      plugins.js
  9. 2
      tests/cli.sh

2
bin/greenlock.js

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

135
bin/init.js

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

2
bin/lib/cli.js

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

278
bin/lib/flags.js

@ -7,142 +7,170 @@ var path = require('path');
var pkgpath = path.join(process.cwd(), 'package.json');
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) {
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',
''
/*
return {
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'
],
cluster: [false, 'initialize with cluster mode on', 'boolean', false],
'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'
],
'maintainer-email': [
false,
'the maintainance contact for security and critical bug notices',
'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'
],
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)
.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
]
};
],
'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
]
};
};
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,

2
bin/lib/greenlockrc.js

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

9
bin/tmpl/app.tmpl.js

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

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

5
plugins.js

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

2
tests/cli.sh

@ -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
# 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
node bin/greenlock.js remove --subject example.com
# TODO test for failure
# node bin/greenlock.js add --subject example.com

Loading…
Cancel
Save