mirror of
				https://github.com/therootcompany/greenlock.js.git
				synced 2024-11-16 17:29:00 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			379 lines
		
	
	
		
			8.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			379 lines
		
	
	
		
			8.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
		
			Executable File
		
	
	
	
	
'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-offset': [
 | 
						|
		false,
 | 
						|
		' Positive (time after issue) or negative (time before expiry) offset, such as 30d or -45d',
 | 
						|
		'string',
 | 
						|
		'45d'
 | 
						|
	],
 | 
						|
	'renew-within': [
 | 
						|
		false,
 | 
						|
		' (ignored) use renew-offset instead',
 | 
						|
		'ignore',
 | 
						|
		undefined
 | 
						|
	],
 | 
						|
	'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 server-key-type or account-key-type instead',
 | 
						|
		'ignore',
 | 
						|
		2048
 | 
						|
	],
 | 
						|
	'server-key-path': [
 | 
						|
		false,
 | 
						|
		' Path to privkey.pem to use for certificate (default: generate new)',
 | 
						|
		'string',
 | 
						|
		undefined,
 | 
						|
		'domain-key-path'
 | 
						|
	],
 | 
						|
	'server-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-alpn-01)", 'string', 'http-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() {
 | 
						|
		var challenges = {};
 | 
						|
		if (/http.?01/i.test(args.challenge)) {
 | 
						|
			challenges['http-01'] = args.challengeOpts;
 | 
						|
		}
 | 
						|
		if (/dns.?01/i.test(args.challenge)) {
 | 
						|
			challenges['dns-01'] = args.challengeOpts;
 | 
						|
		}
 | 
						|
		if (/alpn.?01/i.test(args.challenge)) {
 | 
						|
			challenges['tls-alpn-01'] = args.challengeOpts;
 | 
						|
		}
 | 
						|
		if (!Object.keys(challenges).length) {
 | 
						|
			throw new Error(
 | 
						|
				"Could not determine the challenge type for '" +
 | 
						|
					args.challengeOpts.module +
 | 
						|
					"'. Expected a name like @you/acme-xxxx-01-foo. Please name the module with http-01, dns-01, or tls-alpn-01."
 | 
						|
			);
 | 
						|
		}
 | 
						|
		args.challengeOpts.module = args.challenge;
 | 
						|
		args.storeOpts.module = args.store;
 | 
						|
 | 
						|
		console.log('\ngot to the run step');
 | 
						|
		require(args.challenge);
 | 
						|
		require(args.store);
 | 
						|
 | 
						|
		var greenlock = require('../').create({
 | 
						|
			maintainerEmail: args.maintainerEmail || 'coolaj86@gmail.com',
 | 
						|
			manager: './manager.js',
 | 
						|
			configFile: '~/.config/greenlock/certs.json',
 | 
						|
			challenges: challenges,
 | 
						|
			store: args.storeOpts,
 | 
						|
			renewOffset: args.renewOffset || '30d',
 | 
						|
			renewStagger: '1d'
 | 
						|
		});
 | 
						|
 | 
						|
		// for long-running processes
 | 
						|
		if (args.renewEvery) {
 | 
						|
			setInterval(function() {
 | 
						|
				greenlock.renew({
 | 
						|
					period: args.renewEvery
 | 
						|
				});
 | 
						|
			}, args.renewEvery);
 | 
						|
		}
 | 
						|
 | 
						|
		// TODO should greenlock.add simply always include greenlock.renew?
 | 
						|
		// the concern is conflating error events
 | 
						|
		return greenlock
 | 
						|
			.add({
 | 
						|
				subject: args.subject,
 | 
						|
				altnames: args.altnames,
 | 
						|
				subscriberEmail: args.subscriberEmail || args.email
 | 
						|
			})
 | 
						|
			.then(function(changes) {
 | 
						|
				console.info(changes);
 | 
						|
				// renew should always
 | 
						|
				return greenlock
 | 
						|
					.renew({
 | 
						|
						subject: args.subject,
 | 
						|
						force: false
 | 
						|
					})
 | 
						|
					.then(function() {});
 | 
						|
			});
 | 
						|
	}
 | 
						|
 | 
						|
	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));
 |