CLI: implement init and bugfix .greenlockrc
This commit is contained in:
parent
5b38fe7fcd
commit
341347ba3e
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
|
@ -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);
|
||||||
|
|
278
bin/lib/flags.js
278
bin/lib/flags.js
|
@ -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,
|
||||||
|
|
|
@ -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];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
|
@ -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);
|
||||||
|
}
|
|
@ -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 = '';
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue