From 91981120b46a03c1e2db5907454fd72fc9c1c229 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Sun, 7 Aug 2016 02:05:31 -0400 Subject: [PATCH] getting closer... --- README.md | 4 +- index.js | 695 ++++++++++++++++++++++++----------------------- renewal.conf.tpl | 68 +++++ 3 files changed, 432 insertions(+), 335 deletions(-) create mode 100644 renewal.conf.tpl diff --git a/README.md b/README.md index e33b3ea..79581e4 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Usage ----- ```bash -npm install --save le-store-certbot +npm install --save le-store-certbot@2.x ``` ```bash @@ -26,6 +26,8 @@ var leStore = require('le-store-certbot').create({ , workDir: require('homedir')() + '/letsencrypt/var/lib' , logsDir: require('homedir')() + '/letsencrypt/var/log' +, webrootPath: '~/letsencrypt/srv/www/:hostname/.well-known/acme-challenge' + , debug: false }); diff --git a/index.js b/index.js index 427253c..3a681a2 100644 --- a/index.js +++ b/index.js @@ -5,98 +5,6 @@ var mkdirpAsync = PromiseA.promisify(require('mkdirp')); var path = require('path'); var fs = PromiseA.promisifyAll(require('fs')); var sfs = require('safe-replace'); -var homeRe = new RegExp("^~(\\/|\\\|\\" + path.sep + ")"); - -var fetchFromConfigLiveDir = function (args) { - // TODO NO HARD-CODED DEFAULTS - if (!args.fullchainPath || !args.privkeyPath || !args.certPath || !args.chainPath) { - console.warn("missing one or more of args.privkeyPath, args.fullchainPath, args.certPath, args.chainPath"); - console.warn("hard-coded conventional pathnames were for debugging and are not a stable part of the API"); - } - - //, fs.readFileAsync(fullchainPath, 'ascii') - // note: if this ^^ gets added back in, the arrays below must change - return PromiseA.all([ - fs.readFileAsync(args.privkeyPath, 'ascii') // 0 - , fs.readFileAsync(args.certPath, 'ascii') // 1 - , fs.readFileAsync(args.chainPath, 'ascii') // 2 - - // stat the file, not the link - , fs.statAsync(args.certPath) // 3 - ]).then(function (arr) { - return { - key: arr[0] // privkey.pem - , privkey: arr[0] // privkey.pem - - , fullchain: arr[1] + '\n' + arr[2] // fullchain.pem - , cert: arr[1] // cert.pem - /* - , issuedAt: arr[3].mtime.valueOf() - , expiresAt: arr[3].mtime.valueOf() + (90 * 24 * 60 * 60 * 100) - */ - - , chain: arr[2] // chain.pem - , ca: arr[2] // chain.pem - - , privkeyPath: args.privkeyPath - , fullchainPath: args.fullchainPath - , certPath: args.certPath - , chainPath: args.chainPath - }; - }, function (err) { - if (args.debug) { - console.error("[letsencrypt/lib/common.js] fetchFromDisk"); - console.error(err.stack); - } - return null; - }); -}; - -function getAccount(args) { - var accountId = args.accountId; - var accountDir = path.join(args.accountsDir, accountId); - var files = {}; - var configs = [ 'meta.json', 'private_key.json', 'regr.json' ]; - - return PromiseA.all(configs.map(function (filename) { - var keyname = filename.slice(0, -5); - - return fs.readFileAsync(path.join(accountDir, filename), 'utf8').then(function (text) { - var data; - - try { - data = JSON.parse(text); - } catch(e) { - files[keyname] = { error: e }; - return; - } - - files[keyname] = data; - }, function (err) { - files[keyname] = { error: err }; - }); - })).then(function () { - var err; - - if (!Object.keys(files).every(function (key) { - return !files[key].error; - }) || !files.private_key || !files.private_key.n) { - err = new Error("Account '" + accountId + "' was corrupt. No big deal (I think?). Creating a new one..."); - err.code = 'E_ACCOUNT_CORRUPT'; - err.data = files; - return PromiseA.reject(err); - } - - //files.private_key; - //files.regr; - //files.meta; - files.accountId = accountId; // preserve current account id - files.id = accountId; - files.keypair = { privateKeyJwk: files.private_key }; - - return files; - }); -} function getAccountIdByEmail(args) { // If we read 10,000 account directories looking for @@ -161,18 +69,6 @@ function getAccountIdByEmail(args) { }); } -function readRenewalConfig(args) { - var pyconf = PromiseA.promisifyAll(require('pyconf')); - - return pyconf.readFileAsync(args.renewalPath).then(function (pyobj) { - return pyobj; - }, function () { - return pyconf.readFileAsync(path.join(__dirname, 'renewal.conf.tpl')).then(function (pyobj) { - return pyobj; - }); - }); -} - function writeRenewalConfig(args) { function log() { if (args.debug) { @@ -248,137 +144,6 @@ function writeRenewalConfig(args) { }); } -function getOrCreateRenewal(args) { - return readRenewalConfig(args).then(function (pyobj) { - var minver = pyobj.checkpoints >= 0; - - args.pyobj = pyobj; - - if (!minver) { - args.checkpoints = 0; - pyobj.checkpoints = 0; - return writeRenewalConfig(args); - } - - // args.account.id = pyobj.account - // args.configDir = args.configDir || pyobj.configDir; - - args.checkpoints = pyobj.checkpoints; - - args.agreeTos = (args.agreeTos || pyobj.tos) && true; - args.email = args.email || pyobj.email; - args.domains = args.domains || pyobj.domains; - - // yes, it's an array. weird, right? - args.webrootPath = args.webrootPath || pyobj.webrootPath[0]; - args.server = args.server || args.acmeDiscoveryUrl || pyobj.server; - - args.certPath = args.certPath || pyobj.cert; - args.privkeyPath = args.privkeyPath || pyobj.privkey; - args.chainPath = args.chainPath || pyobj.chain; - args.fullchainPath = args.fullchainPath || pyobj.fullchain; - - //, workDir: args.workDir - //, logsDir: args.logsDir - args.rsaKeySize = args.rsaKeySize || pyobj.rsaKeySize; - args.http01Port = args.http01Port || pyobj.http01Port; - args.domainKeyPath = args.domainPrivateKeyPath || args.domainKeyPath || args.keyPath || pyobj.keyPath; - - return writeRenewalConfig(args); - }); -} - -function writeCertificateAsync(args) { - function log() { - if (args.debug) { - console.log.apply(console, arguments); - } - } - - log("[le/core.js] got certificate!"); - - var obj = args.pyobj; - var pems = args.pems; - - pems.fullchain = pems.cert + '\n' + (pems.chain || pems.ca); - obj.checkpoints = parseInt(obj.checkpoints, 10) || 0; - - var liveDir = args.liveDir || path.join(args.configDir, 'live', args.domains[0]); - - var certPath = args.certPath || obj.cert || path.join(liveDir, 'cert.pem'); - var fullchainPath = args.fullchainPath || obj.fullchain || path.join(liveDir, 'fullchain.pem'); - var chainPath = args.chainPath || obj.chain || path.join(liveDir, 'chain.pem'); - var privkeyPath = args.privkeyPath || obj.privkey - //|| args.domainPrivateKeyPath || args.domainKeyPath || obj.keyPath - || path.join(liveDir, 'privkey.pem'); - - log('[le/core.js] privkeyPath', privkeyPath); - - var archiveDir = args.archiveDir || path.join(args.configDir, 'archive', args.domains[0]); - - var checkpoints = obj.checkpoints.toString(); - var certArchive = path.join(archiveDir, 'cert' + checkpoints + '.pem'); - var fullchainArchive = path.join(archiveDir, 'fullchain' + checkpoints + '.pem'); - var chainArchive = path.join(archiveDir, 'chain'+ checkpoints + '.pem'); - var privkeyArchive = path.join(archiveDir, 'privkey' + checkpoints + '.pem'); - - return mkdirpAsync(archiveDir).then(function () { - return PromiseA.all([ - sfs.writeFileAsync(certArchive, pems.cert, 'ascii') - , sfs.writeFileAsync(chainArchive, (pems.chain || pems.ca), 'ascii') - , sfs.writeFileAsync(fullchainArchive, pems.fullchain, 'ascii') - , sfs.writeFileAsync( - privkeyArchive - // TODO nix args.key, args.domainPrivateKeyPem ?? - , (pems.privkey || pems.key) // || RSA.exportPrivatePem(args.domainKeypair) - , 'ascii' - ) - ]); - }).then(function () { - return mkdirpAsync(liveDir); - }).then(function () { - return PromiseA.all([ - sfs.writeFileAsync(certPath, pems.cert, 'ascii') - , sfs.writeFileAsync(chainPath, (pems.chain || pems.ca), 'ascii') - , sfs.writeFileAsync(fullchainPath, pems.fullchain, 'ascii') - , sfs.writeFileAsync( - privkeyPath - // TODO nix args.key, args.domainPrivateKeyPem ?? - , (pems.privkey || pems.key) // || RSA.exportPrivatePem(args.domainKeypair) - , 'ascii' - ) - ]); - }).then(function () { - obj.checkpoints += 1; - args.checkpoints += 1; - - return writeRenewalConfig(args); - }).then(function () { - return { - certPath: certPath - , chainPath: chainPath - , fullchainPath: fullchainPath - , privkeyPath: privkeyPath - - // TODO nix keypair - , keypair: args.domainKeypair - - // TODO nix args.key, args.domainPrivateKeyPem ?? - // some ambiguity here... - , privkey: (pems.privkey || pems.key) //|| RSA.exportPrivatePem(args.domainKeypair) - , fullchain: pems.fullchain || (pems.cert + '\n' + pems.chain) - , chain: (pems.chain || pems.ca) - // especially this one... might be cert only, might be fullchain - , cert: pems.cert - - /* - , issuedAt: Date.now() - , expiresAt: Date.now() + (90 * 24 * 60 * 60 * 100) - */ - }; - }); -} - function pyToJson(pyobj) { if (!pyobj) { return null; @@ -415,20 +180,7 @@ var defaults = { module.exports.create = function (configs) { var mergedConfigs; - function getConfigAsync(copy) { - copy.domains = []; - - return readRenewalConfig(copy).then(function (pyobj) { - var exists = pyobj.checkpoints >= 0; - if (!exists) { - return null; - } - - return pyToJson(pyobj); - }); - } - - return { + var store = { getDefaults: function () { if (mergedConfigs) { return configs; @@ -443,94 +195,369 @@ module.exports.create = function (configs) { mergedConfigs = configs; return configs; } - , getPrivatePemAsync: function (args) { - return fs.readFileAsync(args.domainKeyPath, 'ascii'); - } - , setPrivatePemAsync: function (args, keypair) { - return mkdirpAsync(path.dirname(args.domainKeyPath)).then(function () { - return fs.writeFileAsync(args.domainKeyPath, keypair.privateKeyPem, 'ascii').then(function () { - return keypair; - }); - }); - } - , setRegistrationAsync: function (args) { - return writeCertificateAsync(args); - } - , getRegistrationAsync: function (args) { - return fetchFromConfigLiveDir(args); - } - , getOrCreateRenewalAsync: function (args) { - return getOrCreateRenewal(args); - } - , getConfigAsync: getConfigAsync - , getConfigsAsync: function (copy) { - copy.domains = []; - - return fs.readdirAsync(copy.renewalDir).then(function (nodes) { - nodes = nodes.filter(function (node) { - return /^[a-z0-9]+.*\.conf$/.test(node); - }); - - return PromiseA.all(nodes.map(function (node) { - copy.domains = [node.replace(/\.conf$/, '')]; - return getConfigAsync(copy); - })); - }); - } - , fetchAsync: function (args) { - return fetchFromConfigLiveDir(args); - } - , getAccountIdByEmailAsync: getAccountIdByEmail - , getAccountAsync: getAccount - , setAccountAsync: function (args, account) { - var isoDate = new Date().toISOString(); - var os = require("os"); - var localname = os.hostname(); - var accountDir = path.join(args.accountsDir, account.accountId); - - account.meta = account.meta || { - creation_host: localname - , creation_dt: isoDate - }; - - return mkdirpAsync(accountDir).then(function () { - var RSA = require('rsa-compat').RSA; - - // TODO abstract file writing - return PromiseA.all([ - // meta.json {"creation_host": "ns1.redirect-www.org", "creation_dt": "2015-12-11T04:14:38Z"} - fs.writeFileAsync(path.join(accountDir, 'meta.json'), JSON.stringify(account.meta), 'utf8') - // private_key.json { "e", "d", "n", "q", "p", "kty", "qi", "dp", "dq" } - , fs.writeFileAsync(path.join(accountDir, 'private_key.json'), JSON.stringify(RSA.exportPrivateJwk(account.keypair)), 'utf8') - // regr.json: - /* - { body: { contact: [ 'mailto:coolaj86@gmail.com' ], - agreement: 'https://letsencrypt.org/documents/LE-SA-v1.0.1-July-27-2015.pdf', - key: { e: 'AQAB', kty: 'RSA', n: '...' } }, - uri: 'https://acme-v01.api.letsencrypt.org/acme/reg/71272', - new_authzr_uri: 'https://acme-v01.api.letsencrypt.org/acme/new-authz', - terms_of_service: 'https://letsencrypt.org/documents/LE-SA-v1.0.1-July-27-2015.pdf' } - */ - , fs.writeFileAsync(path.join(accountDir, 'regr.json'), JSON.stringify(account.regr), 'utf8') - ]); - }); - } - , getAccountIdAsync: function (args) { - var pyconf = PromiseA.promisifyAll(require('pyconf')); - - return pyconf.readFileAsync(args.renewalPath).then(function (renewal) { - var accountId = renewal.account; - renewal = renewal.account; - - return accountId; - }, function (err) { - if ("ENOENT" === err.code) { - return getAccountIdByEmail(args); + // + // Certificates + // + , certificates: { + checkKeypairAsync: function (args) { + if (!args.domainKeyPath) { + return PromiseA.reject(new Error("missing options.domainKeyPath")); } - return PromiseA.reject(err); - }); + return fs.readFileAsync(args.domainKeyPath, 'ascii'); + } + , setKeypairAsync: function (args, keypair) { + return mkdirpAsync(path.dirname(args.domainKeyPath)).then(function () { + return fs.writeFileAsync(args.domainKeyPath, keypair.privateKeyPem, 'ascii').then(function () { + return keypair; + }); + }); + } + , checkAsync: function (args) { + if (!args.fullchainPath || !args.privkeyPath || !args.certPath || !args.chainPath) { + return PromiseA.reject(new Error("missing one or more of privkeyPath, fullchainPath, certPath, chainPath from options")); + } + + //, fs.readFileAsync(fullchainPath, 'ascii') + // note: if this ^^ gets added back in, the arrays below must change + return PromiseA.all([ + fs.readFileAsync(args.privkeyPath, 'ascii') // 0 + , fs.readFileAsync(args.certPath, 'ascii') // 1 + , fs.readFileAsync(args.chainPath, 'ascii') // 2 + + // stat the file, not the link + , fs.statAsync(args.certPath) // 3 + ]).then(function (arr) { + return { + key: arr[0] // privkey.pem + , privkey: arr[0] // privkey.pem + + // TODO for haproxy + , fullchain: arr[1] + '\r\n' + arr[2] // fullchain.pem + , cert: arr[1] // cert.pem + /* + , issuedAt: arr[3].mtime.valueOf() + , expiresAt: arr[3].mtime.valueOf() + (90 * 24 * 60 * 60 * 100) + */ + + , chain: arr[2] // chain.pem + , ca: arr[2] // chain.pem + + , privkeyPath: args.privkeyPath + , fullchainPath: args.fullchainPath + , certPath: args.certPath + , chainPath: args.chainPath + }; + }, function (err) { + if (args.debug) { + console.error("[le-store-certbot] certificates.check"); + console.error(err.stack); + } + return null; + }); + } + , setAsync: function (args) { + // TODO get config + var pyobj = args.pyobj; + var pems = args.pems; + + // TODO for haproxy + pems.fullchain = pems.cert + '\r\n' + (pems.chain || pems.ca); + pyobj.checkpoints = parseInt(pyobj.checkpoints, 10) || 0; + + var liveDir = args.liveDir || path.join(args.configDir, 'live', args.domains[0]); + + var certPath = args.certPath || pyobj.cert || path.join(liveDir, 'cert.pem'); + var fullchainPath = args.fullchainPath || pyobj.fullchain || path.join(liveDir, 'fullchain.pem'); + var chainPath = args.chainPath || pyobj.chain || path.join(liveDir, 'chain.pem'); + var privkeyPath = args.privkeyPath || pyobj.privkey + //|| args.domainPrivateKeyPath || args.domainKeyPath || pyobj.keyPath + || path.join(liveDir, 'privkey.pem'); + + var archiveDir = args.archiveDir || path.join(args.configDir, 'archive', args.domains[0]); + + var checkpoints = pyobj.checkpoints.toString(); + var certArchive = path.join(archiveDir, 'cert' + checkpoints + '.pem'); + var fullchainArchive = path.join(archiveDir, 'fullchain' + checkpoints + '.pem'); + var chainArchive = path.join(archiveDir, 'chain'+ checkpoints + '.pem'); + var privkeyArchive = path.join(archiveDir, 'privkey' + checkpoints + '.pem'); + + return mkdirpAsync(archiveDir).then(function () { + return PromiseA.all([ + sfs.writeFileAsync(certArchive, pems.cert, 'ascii') + , sfs.writeFileAsync(chainArchive, (pems.chain || pems.ca), 'ascii') + , sfs.writeFileAsync(fullchainArchive, pems.fullchain, 'ascii') + , sfs.writeFileAsync( + privkeyArchive + // TODO nix args.key, args.domainPrivateKeyPem ?? + , (pems.privkey || pems.key) // || RSA.exportPrivatePem(args.domainKeypair) + , 'ascii' + ) + ]); + }).then(function () { + return mkdirpAsync(liveDir); + }).then(function () { + return PromiseA.all([ + sfs.writeFileAsync(certPath, pems.cert, 'ascii') + , sfs.writeFileAsync(chainPath, (pems.chain || pems.ca), 'ascii') + , sfs.writeFileAsync(fullchainPath, pems.fullchain, 'ascii') + , sfs.writeFileAsync( + privkeyPath + // TODO nix args.key, args.domainPrivateKeyPem ?? + , (pems.privkey || pems.key) // || RSA.exportPrivatePem(args.domainKeypair) + , 'ascii' + ) + ]); + }).then(function () { + pyobj.checkpoints += 1; + args.checkpoints += 1; + + return writeRenewalConfig(args); + }).then(function () { + return { + certPath: certPath + , chainPath: chainPath + , fullchainPath: fullchainPath + , privkeyPath: privkeyPath + + // TODO nix keypair + , keypair: args.domainKeypair + + // TODO nix args.key, args.domainPrivateKeyPem ?? + // some ambiguity here... + , privkey: (pems.privkey || pems.key) //|| RSA.exportPrivatePem(args.domainKeypair) + , fullchain: pems.fullchain || (pems.cert + pems.chain) + , chain: (pems.chain || pems.ca) + // especially this one... might be cert only, might be fullchain + , cert: pems.cert + + /* + , issuedAt: Date.now() + , expiresAt: Date.now() + (90 * 24 * 60 * 60 * 100) + */ + }; + }); + } } + + // + // Accounts + // + , accounts: { + checkAsync: function (args) { + var promise; + var files = {}; + var accountId; + + if (args.accountId) { + promise = PromiseA.resolve(args.accountId); + } + else if (args.email) { + promise = getAccountIdByEmail(args); + } + else { + promise = PromiseA.reject(new Error("must provide accountId or email")); + } + + promise.then(function (_accountId) { + accountId = _accountId; + var accountDir = path.join(args.accountsDir, accountId); + var configs = [ 'meta.json', 'private_key.json', 'regr.json' ]; + + return PromiseA.all(configs.map(function (filename) { + var keyname = filename.slice(0, -5); + + return fs.readFileAsync(path.join(accountDir, filename), 'utf8').then(function (text) { + var data; + + try { + data = JSON.parse(text); + } catch(e) { + files[keyname] = { error: e }; + return; + } + + files[keyname] = data; + }, function (err) { + files[keyname] = { error: err }; + }); + })); + }).then(function () { + var err; + + if (!Object.keys(files).every(function (key) { + return !files[key].error; + }) || !files.private_key || !files.private_key.n) { + err = new Error("Account '" + accountId + "' was corrupt. No big deal (I think?). Creating a new one..."); + err.code = 'E_ACCOUNT_CORRUPT'; + err.data = files; + return PromiseA.reject(err); + } + + //files.private_key; + //files.regr; + //files.meta; + files.accountId = accountId; // preserve current account id + files.id = accountId; + files.keypair = { privateKeyJwk: files.private_key }; + + return files; + }); + } + , registerAsync: function (args) { + // save to renewal / account dir ??? + return getOrCreateRenewal(copy).then(function (pyobj) { + + copy.pyobj = pyobj; + return files; + }); + + } + , getAsync: function (args) { + return store.accounts.checkAsync(args).then(function (account) { + if (!args.account) { + return store.accounts.registerAsync(args); + } + + //args.account = account; + return account; + }); + } + , setAsync: function (args, account) { + var isoDate = new Date().toISOString(); + var os = require("os"); + var localname = os.hostname(); + var accountDir = path.join(args.accountsDir, account.accountId); + + account.meta = account.meta || { + creation_host: localname + , creation_dt: isoDate + }; + + return mkdirpAsync(accountDir).then(function () { + var RSA = require('rsa-compat').RSA; + + // TODO abstract file writing + return PromiseA.all([ + // meta.json {"creation_host": "ns1.redirect-www.org", "creation_dt": "2015-12-11T04:14:38Z"} + fs.writeFileAsync(path.join(accountDir, 'meta.json'), JSON.stringify(account.meta), 'utf8') + // private_key.json { "e", "d", "n", "q", "p", "kty", "qi", "dp", "dq" } + , fs.writeFileAsync(path.join(accountDir, 'private_key.json'), JSON.stringify(RSA.exportPrivateJwk(account.keypair)), 'utf8') + // regr.json: + /* + { body: { contact: [ 'mailto:coolaj86@gmail.com' ], + agreement: 'https://letsencrypt.org/documents/LE-SA-v1.0.1-July-27-2015.pdf', + key: { e: 'AQAB', kty: 'RSA', n: '...' } }, + uri: 'https://acme-v01.api.letsencrypt.org/acme/reg/71272', + new_authzr_uri: 'https://acme-v01.api.letsencrypt.org/acme/new-authz', + terms_of_service: 'https://letsencrypt.org/documents/LE-SA-v1.0.1-July-27-2015.pdf' } + */ + , fs.writeFileAsync(path.join(accountDir, 'regr.json'), JSON.stringify(account.regr), 'utf8') + ]); + }); + } + , getAccountIdAsync: function (args) { + var pyconf = PromiseA.promisifyAll(require('pyconf')); + + return pyconf.readFileAsync(args.renewalPath).then(function (renewal) { + var accountId = renewal.account; + renewal = renewal.account; + + return accountId; + }, function (err) { + if ("ENOENT" === err.code) { + return getAccountIdByEmail(args); + } + + return PromiseA.reject(err); + }); + } + } + + // + // Configs + // + , configs: { + checkAsync: function (copy) { + copy.domains = []; + + return store.configs._checkHelperAsync(copy).then(function (pyobj) { + var exists = pyobj.checkpoints >= 0; + if (!exists) { + return null; + } + + return pyToJson(pyobj); + }); + } + , _checkHelperAsync: function (args) { + var pyconf = PromiseA.promisifyAll(require('pyconf')); + + return pyconf.readFileAsync(args.renewalPath).then(function (pyobj) { + return pyobj; + }, function () { + return pyconf.readFileAsync(path.join(__dirname, 'renewal.conf.tpl')).then(function (pyobj) { + return pyobj; + }); + }); + } + , getAsync: function (args) { + return store.configs._checkHelperAsync(args).then(function (pyobj) { + var minver = pyobj.checkpoints >= 0; + + args.pyobj = pyobj; + + if (!minver) { + args.checkpoints = 0; + pyobj.checkpoints = 0; + return writeRenewalConfig(args); + } + + // args.account.id = pyobj.account + // args.configDir = args.configDir || pyobj.configDir; + + args.checkpoints = pyobj.checkpoints; + + args.agreeTos = (args.agreeTos || pyobj.tos) && true; + args.email = args.email || pyobj.email; + args.domains = args.domains || pyobj.domains; + + // yes, it's an array. weird, right? + args.webrootPath = args.webrootPath || pyobj.webrootPath[0]; + args.server = args.server || args.acmeDiscoveryUrl || pyobj.server; + + args.certPath = args.certPath || pyobj.cert; + args.privkeyPath = args.privkeyPath || pyobj.privkey; + args.chainPath = args.chainPath || pyobj.chain; + args.fullchainPath = args.fullchainPath || pyobj.fullchain; + + //, workDir: args.workDir + //, logsDir: args.logsDir + args.rsaKeySize = args.rsaKeySize || pyobj.rsaKeySize; + args.http01Port = args.http01Port || pyobj.http01Port; + args.domainKeyPath = args.domainPrivateKeyPath || args.domainKeyPath || args.keyPath || pyobj.keyPath; + + return writeRenewalConfig(args); + }); + } + , allAsync: function (copy) { + copy.domains = []; + + return fs.readdirAsync(copy.renewalDir).then(function (nodes) { + nodes = nodes.filter(function (node) { + return /^[a-z0-9]+.*\.conf$/.test(node); + }); + + return PromiseA.all(nodes.map(function (node) { + copy.domains = [node.replace(/\.conf$/, '')]; + return store.configs.getAsync(copy); + })); + }); + } + } + }; + + return store; }; diff --git a/renewal.conf.tpl b/renewal.conf.tpl new file mode 100644 index 0000000..ad7ae0a --- /dev/null +++ b/renewal.conf.tpl @@ -0,0 +1,68 @@ +#cert = :config/live/:hostname/cert.pem +cert = :cert_path +privkey = :privkey_path +chain = :chain_path +fullchain = :fullchain_path + +# Options and defaults used in the renewal process +[renewalparams] +apache_enmod = a2enmod +no_verify_ssl = False +ifaces = None +apache_dismod = a2dismod +register_unsafely_without_email = False +uir = None +installer = none +config_dir = :config +text_mode = True +# junk? +# https://github.com/letsencrypt/letsencrypt/issues/1955 +func = +prepare = False +work_dir = :work_dir +tos = :agree_tos +init = False +http01_port = :http_01_port +duplicate = False +# this is for the domain +key_path = :privkey_path +nginx = False +fullchain_path = :fullchain_path +email = :email +csr = None +agree_dev_preview = None +redirect = None +verbose_count = -3 +config_file = None +renew_by_default = True +hsts = False +authenticator = webroot +domains = :hostnames #comma,delimited,list +rsa_key_size = :rsa_key_size +# starts at 0 and increments at every renewal +checkpoints = -1 +manual_test_mode = False +apache = False +cert_path = :cert_path +webroot_path = :webroot_paths # comma,delimited,list +strict_permissions = False +apache_server_root = /etc/apache2 +# https://github.com/letsencrypt/letsencrypt/issues/1948 +account = :account_id +manual_public_ip_logging_ok = False +chain_path = :chain_path +standalone = False +manual = False +server = :acme_discovery_url +standalone_supported_challenges = "http-01,tls-sni-01" +webroot = True +apache_init_script = None +user_agent = None +apache_ctl = apache2ctl +apache_le_vhost_ext = -le-ssl.conf +debug = False +tls_sni_01_port = 443 +logs_dir = :logs_dir +configurator = None +[[webroot_map]] +# :hostname = :webroot_path