AJ ONeal
5 years ago
4 changed files with 634 additions and 0 deletions
@ -0,0 +1,316 @@ |
|||
'use strict'; |
|||
|
|||
var mkdirp = require('@root/mkdirp'); |
|||
var cli = require('./cli.js'); |
|||
|
|||
cli.parse({ |
|||
'directory-url': [ |
|||
false, |
|||
' ACME Directory Resource URL', |
|||
'string', |
|||
'https://acme-v02.api.letsencrypt.org/directory', |
|||
'server,acme-url' |
|||
], |
|||
email: [ |
|||
false, |
|||
' Email used for registration and recovery contact. (default: null)', |
|||
'email' |
|||
], |
|||
'agree-tos': [ |
|||
false, |
|||
" Agree to the Greenlock and Let's Encrypt Subscriber Agreements", |
|||
'boolean', |
|||
false |
|||
], |
|||
'community-member': [ |
|||
false, |
|||
' Submit stats to and get updates from Greenlock', |
|||
'boolean', |
|||
false |
|||
], |
|||
domains: [ |
|||
false, |
|||
' Domain names to apply. For multiple domains you can enter a comma separated list of domains as a parameter. (default: [])', |
|||
'string' |
|||
], |
|||
'renew-within': [ |
|||
false, |
|||
' Renew certificates this many days before expiry', |
|||
'int', |
|||
7 |
|||
], |
|||
'cert-path': [ |
|||
false, |
|||
' Path to where new cert.pem is saved', |
|||
'string', |
|||
':configDir/live/:hostname/cert.pem' |
|||
], |
|||
'fullchain-path': [ |
|||
false, |
|||
' Path to where new fullchain.pem (cert + chain) is saved', |
|||
'string', |
|||
':configDir/live/:hostname/fullchain.pem' |
|||
], |
|||
'bundle-path': [ |
|||
false, |
|||
' Path to where new bundle.pem (fullchain + privkey) is saved', |
|||
'string', |
|||
':configDir/live/:hostname/bundle.pem' |
|||
], |
|||
'chain-path': [ |
|||
false, |
|||
' Path to where new chain.pem is saved', |
|||
'string', |
|||
':configDir/live/:hostname/chain.pem' |
|||
], |
|||
'privkey-path': [ |
|||
false, |
|||
' Path to where privkey.pem is saved', |
|||
'string', |
|||
':configDir/live/:hostname/privkey.pem' |
|||
], |
|||
'config-dir': [ |
|||
false, |
|||
' Configuration directory.', |
|||
'string', |
|||
'~/letsencrypt/etc/' |
|||
], |
|||
store: [ |
|||
false, |
|||
' The name of the storage module to use', |
|||
'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', |
|||
'{}' |
|||
], |
|||
challenge: [ |
|||
false, |
|||
' The name of the HTTP-01, DNS-01, or TLS-ALPN-01 challenge module to use', |
|||
'string', |
|||
'@greenlock/acme-http-01-fs' |
|||
], |
|||
'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', |
|||
'{}' |
|||
], |
|||
'skip-dry-run': [ |
|||
false, |
|||
' Use with caution (and test with the staging url first). Creates an Order on the ACME server without a self-test.', |
|||
'boolean' |
|||
], |
|||
'skip-challenge-tests': [ |
|||
false, |
|||
' Use with caution (and with the staging url first). Presents challenges to the ACME server without first testing locally.', |
|||
'boolean' |
|||
], |
|||
'http-01-port': [ |
|||
false, |
|||
' Required to be 80 for live servers. Do not use. For special test environments only.', |
|||
'int' |
|||
], |
|||
'dns-01': [false, ' Use DNS-01 challange type', 'boolean', false], |
|||
standalone: [ |
|||
false, |
|||
' Obtain certs using a "standalone" webserver.', |
|||
'boolean', |
|||
false |
|||
], |
|||
manual: [ |
|||
false, |
|||
' Print the token and key to the screen and wait for you to hit enter, giving you time to copy it somewhere before continuing (uses acme-http-01-cli or acme-dns-01-cli)', |
|||
'boolean', |
|||
false |
|||
], |
|||
debug: [false, ' show traces and logs', 'boolean', false], |
|||
root: [ |
|||
false, |
|||
' public_html / webroot path (may use the :hostname template such as /srv/www/:hostname)', |
|||
'string', |
|||
undefined, |
|||
'webroot-path' |
|||
], |
|||
|
|||
//
|
|||
// backwards compat
|
|||
//
|
|||
duplicate: [ |
|||
false, |
|||
' Allow getting a certificate that duplicates an existing one/is an early renewal', |
|||
'boolean', |
|||
false |
|||
], |
|||
'rsa-key-size': [ |
|||
false, |
|||
' (ignored) use domain-key-type or account-key-type instead', |
|||
'ignore', |
|||
2048 |
|||
], |
|||
'domain-key-path': [ |
|||
false, |
|||
' Path to privkey.pem to use for domain (default: generate new)', |
|||
'string' |
|||
], |
|||
'domain-key-type': [ |
|||
false, |
|||
" One of 'RSA' (2048), 'RSA-3084', 'RSA-4096', 'ECDSA' (P-256), or 'P-384'. For best compatibility, security, and efficiency use the default (More bits != More security)", |
|||
'string', |
|||
'RSA' |
|||
], |
|||
'account-key-path': [ |
|||
false, |
|||
' Path to privkey.pem to use for account (default: generate new)', |
|||
'string' |
|||
], |
|||
'account-key-type': [ |
|||
false, |
|||
" One of 'ECDSA' (P-256), 'P-384', 'RSA', 'RSA-3084', or 'RSA-4096'. Stick with 'ECDSA' (P-256) unless you need 'RSA' (2048) for legacy compatibility. (More bits != More security)", |
|||
'string', |
|||
'P-256' |
|||
], |
|||
webroot: [false, ' (ignored) for certbot compatibility', 'ignore', false], |
|||
//, 'standalone-supported-challenges': [ false, " Supported challenges, order preferences are randomly chosen. (default: http-01,tls-sni-01)", 'string', 'http-01,tls-sni-01']
|
|||
'work-dir': [ |
|||
false, |
|||
' for certbot compatibility (ignored)', |
|||
'string', |
|||
'~/letsencrypt/var/lib/' |
|||
], |
|||
'logs-dir': [ |
|||
false, |
|||
' for certbot compatibility (ignored)', |
|||
'string', |
|||
'~/letsencrypt/var/log/' |
|||
], |
|||
'acme-version': [ |
|||
false, |
|||
' (ignored) ACME is now RFC 8555 and prior drafts are no longer supported', |
|||
'ignore', |
|||
'rfc8555' |
|||
] |
|||
}); |
|||
|
|||
// ignore certonly and extraneous arguments
|
|||
cli.main(function(_, options) { |
|||
console.info(''); |
|||
|
|||
[ |
|||
'configDir', |
|||
'privkeyPath', |
|||
'certPath', |
|||
'chainPath', |
|||
'fullchainPath', |
|||
'bundlePath' |
|||
].forEach(function(k) { |
|||
if (options[k]) { |
|||
options.storeOpts[k] = options[k]; |
|||
} |
|||
delete options[k]; |
|||
}); |
|||
|
|||
if (options.workDir) { |
|||
options.challengeOpts.workDir = options.workDir; |
|||
delete options.workDir; |
|||
} |
|||
|
|||
if (options.debug) { |
|||
console.debug(options); |
|||
} |
|||
|
|||
var args = {}; |
|||
var homedir = require('os').homedir(); |
|||
|
|||
Object.keys(options).forEach(function(key) { |
|||
var val = options[key]; |
|||
|
|||
if ('string' === typeof val) { |
|||
val = val.replace(/^~/, homedir); |
|||
} |
|||
|
|||
key = key.replace(/\-([a-z0-9A-Z])/g, function(c) { |
|||
return c[1].toUpperCase(); |
|||
}); |
|||
args[key] = val; |
|||
}); |
|||
|
|||
Object.keys(args).forEach(function(key) { |
|||
var val = args[key]; |
|||
|
|||
if ('string' === typeof val) { |
|||
val = val.replace(/(\:configDir)|(\:config)/, args.configDir); |
|||
} |
|||
|
|||
args[key] = val; |
|||
}); |
|||
|
|||
if (args.domains) { |
|||
args.domains = args.domains.split(','); |
|||
} |
|||
|
|||
if ( |
|||
!(Array.isArray(args.domains) && args.domains.length) || |
|||
!args.email || |
|||
!args.agreeTos || |
|||
(!args.server && !args.directoryUrl) |
|||
) { |
|||
console.error('\nUsage:\n\ngreenlock certonly --standalone \\'); |
|||
console.error( |
|||
'\t--agree-tos --email user@example.com --domains example.com \\' |
|||
); |
|||
console.error('\t--config-dir ~/acme/etc \\'); |
|||
console.error('\nSee greenlock --help for more details\n'); |
|||
return; |
|||
} |
|||
|
|||
if (args.http01Port) { |
|||
// [@agnat]: Coerce to string. cli returns a number although we request a string.
|
|||
args.http01Port = '' + args.http01Port; |
|||
args.http01Port = args.http01Port.split(',').map(function(port) { |
|||
return parseInt(port, 10); |
|||
}); |
|||
} |
|||
|
|||
function run() { |
|||
console.log('\ngot to the run step'); |
|||
process.exit(1); |
|||
require('../') |
|||
.run(args) |
|||
.then(function(status) { |
|||
process.exit(status); |
|||
}); |
|||
} |
|||
|
|||
if ('greenlock-store-fs' !== args.store) { |
|||
run(); |
|||
return; |
|||
} |
|||
|
|||
// TODO remove mkdirp and let greenlock-store-fs do this?
|
|||
mkdirp(args.storeOpts.configDir, function(err) { |
|||
if (!err) { |
|||
run(); |
|||
} |
|||
|
|||
console.error( |
|||
"Could not create --config-dir '" + args.configDir + "':", |
|||
err.code |
|||
); |
|||
console.error("Try setting --config-dir '/tmp'"); |
|||
return; |
|||
}); |
|||
}, process.argv.slice(3)); |
@ -0,0 +1,234 @@ |
|||
'use strict'; |
|||
|
|||
var CLI = module.exports; |
|||
|
|||
var defaultConf; |
|||
var defaultOpts; |
|||
var bags = []; |
|||
|
|||
CLI.parse = function(conf) { |
|||
var opts = (defaultOpts = {}); |
|||
defaultConf = conf; |
|||
|
|||
Object.keys(conf).forEach(function(k) { |
|||
var v = conf[k]; |
|||
var aliases = v[5]; |
|||
var bag; |
|||
var bagName; |
|||
|
|||
// the name of the argument set is now the 0th argument
|
|||
v.unshift(k); |
|||
// v[0] flagname
|
|||
// v[1] short flagname
|
|||
// v[2] description
|
|||
// v[3] type
|
|||
// v[4] default value
|
|||
// v[5] aliases
|
|||
|
|||
if ('bag' === v[3]) { |
|||
bag = v[0]; // 'bag-option-xxxx' => '--bag-option-'
|
|||
bag = '--' + bag.replace(/xxx.*/, ''); |
|||
bags.push(bag); |
|||
|
|||
bagName = toBagName(bag.replace(/^--/, '')); |
|||
opts[bagName] = {}; |
|||
} |
|||
|
|||
if ('json' === v[3]) { |
|||
bagName = toBagName(v[0].replace(/-json$/, '')); // 'bag-option-json' => 'bagOptionOpts'
|
|||
opts[bagName] = {}; |
|||
} else if ('ignore' !== v[3] && 'undefined' !== typeof v[4]) { |
|||
// set the default values (where 'undefined' is not an allowed value)
|
|||
opts[toCamel(k)] = v[4]; |
|||
} |
|||
|
|||
if (!aliases) { |
|||
aliases = []; |
|||
} else if ('string' === typeof aliases) { |
|||
aliases = aliases.split(','); |
|||
} |
|||
aliases.forEach(function(alias) { |
|||
if (alias in conf) { |
|||
throw new Error( |
|||
"Cannot alias '" + |
|||
alias + |
|||
"' from '" + |
|||
k + |
|||
"': option already exists" |
|||
); |
|||
} |
|||
conf[alias] = v; |
|||
}); |
|||
}); |
|||
}; |
|||
|
|||
CLI.main = function(cb, args) { |
|||
var leftovers = []; |
|||
var conf = defaultConf; |
|||
var opts = defaultOpts; |
|||
|
|||
if (!opts) { |
|||
throw new Error("you didn't call `CLI.parse(configuration)`"); |
|||
} |
|||
|
|||
// TODO what's the existing API for this?
|
|||
if (!args) { |
|||
args = process.argv.slice(2); |
|||
} |
|||
|
|||
var flag; |
|||
var cnf; |
|||
var typ; |
|||
|
|||
function grab(bag) { |
|||
var bagName = toBagName(bag); |
|||
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; |
|||
} |
|||
|
|||
while (args.length) { |
|||
// take one off the top
|
|||
flag = args.shift(); |
|||
|
|||
// mind the gap
|
|||
if ('--' === flag) { |
|||
leftovers = leftovers.concat(args); |
|||
break; |
|||
} |
|||
|
|||
// help!
|
|||
if ( |
|||
'--help' === flag || |
|||
'-h' === flag || |
|||
'/?' === flag || |
|||
'help' === flag |
|||
) { |
|||
printHelp(conf); |
|||
process.exit(1); |
|||
} |
|||
|
|||
// only long names are actually used
|
|||
if ('--' !== flag.slice(0, 2)) { |
|||
console.error("Unrecognized argument '" + flag + "'"); |
|||
process.exit(1); |
|||
} |
|||
|
|||
cnf = conf[flag.slice(2)]; |
|||
if (!cnf) { |
|||
// look for arbitrary flags
|
|||
if (bags.some(grab)) { |
|||
continue; |
|||
} |
|||
|
|||
// other arbitrary args are not used
|
|||
console.error("Unrecognized flag '" + flag + "'"); |
|||
process.exit(1); |
|||
} |
|||
|
|||
// encourage switching to non-aliased version
|
|||
if (flag !== '--' + cnf[0]) { |
|||
console.warn( |
|||
"use of '" + |
|||
flag + |
|||
"' is deprecated, use '--" + |
|||
cnf[0] + |
|||
"' instead" |
|||
); |
|||
} |
|||
|
|||
// look for xxx-json flags
|
|||
if ('json' === cnf[3]) { |
|||
try { |
|||
var json = JSON.parse(args.shift()); |
|||
var bagName = toBagName(cnf[0].replace(/-json$/, '')); |
|||
Object.keys(json).forEach(function(k) { |
|||
opts[bagName][k] = json[k]; |
|||
}); |
|||
} catch (e) { |
|||
console.error("Could not parse option '" + flag + "' as JSON:"); |
|||
console.error(e.message); |
|||
process.exit(1); |
|||
} |
|||
continue; |
|||
} |
|||
|
|||
// set booleans, otherwise grab the next arg in line
|
|||
typ = cnf[3]; |
|||
// TODO --no-<whatever> to negate
|
|||
if (Boolean === typ || 'boolean' === typ) { |
|||
opts[toCamel(cnf[0])] = true; |
|||
continue; |
|||
} |
|||
opts[toCamel(cnf[0])] = args.shift(); |
|||
continue; |
|||
} |
|||
|
|||
cb(leftovers, opts); |
|||
}; |
|||
|
|||
function toCamel(str) { |
|||
return str.replace(/-([a-z0-9])/g, function(m) { |
|||
return m[1].toUpperCase(); |
|||
}); |
|||
} |
|||
|
|||
function toBagName(bag) { |
|||
// trim leading and trailing '-'
|
|||
bag = bag.replace(/^-+/g, '').replace(/-+$/g, '') |
|||
return toCamel(bag) + 'Opts'; // '--bag-option-' => bagOptionOpts
|
|||
} |
|||
|
|||
function printHelp(conf) { |
|||
var flagLen = 0; |
|||
var typeLen = 0; |
|||
var defLen = 0; |
|||
|
|||
Object.keys(conf).forEach(function(k) { |
|||
flagLen = Math.max(flagLen, conf[k][0].length); |
|||
typeLen = Math.max(typeLen, conf[k][3].length); |
|||
if ('undefined' !== typeof conf[k][4]) { |
|||
defLen = Math.max( |
|||
defLen, |
|||
'(Default: )'.length + String(conf[k][4]).length |
|||
); |
|||
} |
|||
}); |
|||
|
|||
Object.keys(conf).forEach(function(k) { |
|||
var v = conf[k]; |
|||
|
|||
// skip aliases
|
|||
if (v[0] !== k) { |
|||
return; |
|||
} |
|||
|
|||
var def = v[4]; |
|||
if ('undefined' === typeof def) { |
|||
def = ''; |
|||
} else { |
|||
def = '(default: ' + JSON.stringify(def) + ')'; |
|||
} |
|||
|
|||
var msg = |
|||
' --' + |
|||
v[0].padEnd(flagLen) + |
|||
' ' + |
|||
v[3].padStart(typeLen + 1) + |
|||
' ' + |
|||
(v[2] || '') + |
|||
' ' + |
|||
def; /*.padStart(defLen)*/ |
|||
// v[0] flagname
|
|||
// v[1] short flagname
|
|||
// v[2] description
|
|||
// v[3] type
|
|||
// v[4] default value
|
|||
// v[5] aliases
|
|||
|
|||
console.info(msg); |
|||
}); |
|||
} |
@ -0,0 +1,9 @@ |
|||
#!/usr/bin/env node
|
|||
'use strict'; |
|||
|
|||
var args = process.argv.slice(2); |
|||
console.log(args); |
|||
if ('certonly' === args[0]) { |
|||
require('./certonly.js'); |
|||
return; |
|||
} |
@ -0,0 +1,75 @@ |
|||
'use strict'; |
|||
|
|||
var spawn = require('child_process').spawn; |
|||
var path = require('path'); |
|||
var PKG_DIR = path.join(__dirname, '..'); |
|||
|
|||
module.exports.install = function(moduleName) { |
|||
return new Promise(function(resolve) { |
|||
if (!moduleName) { |
|||
throw new Error('no module name given'); |
|||
} |
|||
|
|||
var npm = 'npm'; |
|||
var args = ['install', '--save', moduleName]; |
|||
var out = ''; |
|||
var cmd = spawn(npm, args, { |
|||
cwd: PKG_DIR, |
|||
windowsHide: true |
|||
}); |
|||
|
|||
cmd.stdout.on('data', function(chunk) { |
|||
out += chunk.toString('utf8'); |
|||
}); |
|||
cmd.stdout.on('data', function(chunk) { |
|||
out += chunk.toString('utf8'); |
|||
}); |
|||
|
|||
cmd.on('error', function(e) { |
|||
console.error( |
|||
"Failed to start: '" + |
|||
npm + |
|||
' ' + |
|||
args.join(' ') + |
|||
"' in '" + |
|||
PKG_DIR + |
|||
"'" |
|||
); |
|||
console.error(e.message); |
|||
process.exit(1); |
|||
}); |
|||
|
|||
cmd.on('exit', function(code) { |
|||
if (!code) { |
|||
resolve(); |
|||
return; |
|||
} |
|||
|
|||
if (out) { |
|||
console.error(out); |
|||
console.error(); |
|||
console.error(); |
|||
} |
|||
console.error( |
|||
"Failed to run: '" + |
|||
npm + |
|||
' ' + |
|||
args.join(' ') + |
|||
"' in '" + |
|||
PKG_DIR + |
|||
"'" |
|||
); |
|||
console.error( |
|||
'Try for yourself:\n\tcd ' + |
|||
PKG_DIR + |
|||
'\n\tnpm ' + |
|||
args.join(' ') |
|||
); |
|||
process.exit(1); |
|||
}); |
|||
}); |
|||
}; |
|||
|
|||
if (require.main === module) { |
|||
module.exports.install(process.argv[2]); |
|||
} |
Loading…
Reference in new issue