Bladeren bron

Merge pull request #1248 from jc21/develop

v2.9.5
jc21 4 jaren geleden
bovenliggende
commit
a56d976947

+ 1 - 1
.version

@@ -1 +1 @@
-2.9.4
+2.9.5

+ 46 - 2
README.md

@@ -1,7 +1,7 @@
 <p align="center">
 	<img src="https://nginxproxymanager.com/github.png">
 	<br><br>
-	<img src="https://img.shields.io/badge/version-2.9.4-green.svg?style=for-the-badge">
+	<img src="https://img.shields.io/badge/version-2.9.5-green.svg?style=for-the-badge">
 	<a href="https://hub.docker.com/repository/docker/jc21/nginx-proxy-manager">
 		<img src="https://img.shields.io/docker/stars/jc21/nginx-proxy-manager.svg?style=for-the-badge">
 	</a>
@@ -17,7 +17,7 @@
 	<a href="https://reddit.com/r/nginxproxymanager">
 		<img alt="Reddit" src="https://img.shields.io/reddit/subreddit-subscribers/nginxproxymanager?label=Reddit%20Community&style=for-the-badge">
 	</a>
-	
+
 </p>
 
 This project comes as a pre-built docker image that enables you to easily forward to your websites
@@ -414,6 +414,50 @@ Special thanks to the following contributors:
 				<br /><sub><b>RBXII3</b></sub>
 			</a>
 		</td>
+		<td align="center">
+			<a href="https://github.com/demize">
+				<img src="https://avatars.githubusercontent.com/u/264914?v=4" width="80" alt=""/>
+				<br /><sub><b>demize</b></sub>
+			</a>
+		</td>
+		<td align="center">
+			<a href="https://github.com/PUP-Loki">
+				<img src="https://avatars.githubusercontent.com/u/75944209?v=4" width="80" alt=""/>
+				<br /><sub><b>PUP-Loki</b></sub>
+			</a>
+		</td>
+		<td align="center">
+			<a href="https://github.com/DSorlov">
+				<img src="https://avatars.githubusercontent.com/u/8133650?v=4" width="80" alt=""/>
+				<br /><sub><b>Daniel Sörlöv</b></sub>
+			</a>
+		</td>
+	</tr>
+	<tr>
+		<td align="center">
+			<a href="https://github.com/Theyooo">
+				<img src="https://avatars.githubusercontent.com/u/58510131?v=4" width="80" alt=""/>
+				<br /><sub><b>Theyooo</b></sub>
+			</a>
+		</td>
+		<td align="center">
+			<a href="https://github.com/mrdink">
+				<img src="https://avatars.githubusercontent.com/u/514751?v=4" width="80" alt=""/>
+				<br /><sub><b>Justin Peacock</b></sub>
+			</a>
+		</td>
+		<td align="center">
+			<a href="https://github.com/ChrisTracy">
+				<img src="https://avatars.githubusercontent.com/u/58871574?v=4" width="80" alt=""/>
+				<br /><sub><b>Chris Tracy</b></sub>
+			</a>
+		</td>
+		<td align="center">
+			<a href="https://github.com/Fuechslein">
+				<img src="https://avatars.githubusercontent.com/u/15112818?v=4" width="80" alt=""/>
+				<br /><sub><b>Fuechslein</b></sub>
+			</a>
+		</td>
 	</tr>
 </table>
 <!-- markdownlint-enable -->

+ 108 - 125
backend/internal/certificate.js

@@ -1,19 +1,18 @@
-const fs               = require('fs');
-const _                = require('lodash');
-const logger           = require('../logger').ssl;
-const error            = require('../lib/error');
-const certificateModel = require('../models/certificate');
-const internalAuditLog = require('./audit-log');
-const tempWrite        = require('temp-write');
-const utils            = require('../lib/utils');
-const moment           = require('moment');
-const debug_mode       = process.env.NODE_ENV !== 'production' || !!process.env.DEBUG;
-const le_staging       = process.env.NODE_ENV !== 'production';
-const internalNginx    = require('./nginx');
-const internalHost     = require('./host');
-const certbot_command  = 'certbot';
-const le_config        = '/etc/letsencrypt.ini';
-const dns_plugins      = require('../global/certbot-dns-plugins');
+const _                  = require('lodash');
+const fs                 = require('fs');
+const tempWrite          = require('temp-write');
+const moment             = require('moment');
+const logger             = require('../logger').ssl;
+const error              = require('../lib/error');
+const utils              = require('../lib/utils');
+const certificateModel   = require('../models/certificate');
+const dnsPlugins         = require('../global/certbot-dns-plugins');
+const internalAuditLog   = require('./audit-log');
+const internalNginx      = require('./nginx');
+const internalHost       = require('./host');
+const letsencryptStaging = process.env.NODE_ENV !== 'production';
+const letsencryptConfig  = '/etc/letsencrypt.ini';
+const certbotCommand     = 'certbot';
 
 function omissions() {
 	return ['is_deleted'];
@@ -21,14 +20,14 @@ function omissions() {
 
 const internalCertificate = {
 
-	allowed_ssl_files:   ['certificate', 'certificate_key', 'intermediate_certificate'],
-	interval_timeout:    1000 * 60 * 60, // 1 hour
-	interval:            null,
-	interval_processing: false,
+	allowedSslFiles:    ['certificate', 'certificate_key', 'intermediate_certificate'],
+	intervalTimeout:    1000 * 60 * 60, // 1 hour
+	interval:           null,
+	intervalProcessing: false,
 
 	initTimer: () => {
 		logger.info('Let\'s Encrypt Renewal Timer initialized');
-		internalCertificate.interval = setInterval(internalCertificate.processExpiringHosts, internalCertificate.interval_timeout);
+		internalCertificate.interval = setInterval(internalCertificate.processExpiringHosts, internalCertificate.intervalTimeout);
 		// And do this now as well
 		internalCertificate.processExpiringHosts();
 	},
@@ -37,15 +36,15 @@ const internalCertificate = {
 	 * Triggered by a timer, this will check for expiring hosts and renew their ssl certs if required
 	 */
 	processExpiringHosts: () => {
-		if (!internalCertificate.interval_processing) {
-			internalCertificate.interval_processing = true;
+		if (!internalCertificate.intervalProcessing) {
+			internalCertificate.intervalProcessing = true;
 			logger.info('Renewing SSL certs close to expiry...');
 
-			let cmd = certbot_command + ' renew --non-interactive --quiet ' +
-				'--config "' + le_config + '" ' +
+			const cmd = certbotCommand + ' renew --non-interactive --quiet ' +
+				'--config "' + letsencryptConfig + '" ' +
 				'--preferred-challenges "dns,http" ' +
 				'--disable-hook-validation ' +
-				(le_staging ? '--staging' : '');
+				(letsencryptStaging ? '--staging' : '');
 
 			return utils.exec(cmd)
 				.then((result) => {
@@ -93,11 +92,11 @@ const internalCertificate = {
 						});
 				})
 				.then(() => {
-					internalCertificate.interval_processing = false;
+					internalCertificate.intervalProcessing = false;
 				})
 				.catch((err) => {
 					logger.error(err);
-					internalCertificate.interval_processing = false;
+					internalCertificate.intervalProcessing = false;
 				});
 		}
 	},
@@ -221,7 +220,7 @@ const internalCertificate = {
 							await certificateModel
 								.query()
 								.deleteById(certificate.id);
-							
+
 							throw error;
 						});
 				} else {
@@ -448,11 +447,9 @@ const internalCertificate = {
 	 * @returns {Promise}
 	 */
 	writeCustomCert: (certificate) => {
-		if (debug_mode) {
-			logger.info('Writing Custom Certificate:', certificate);
-		}
+		logger.info('Writing Custom Certificate:', certificate);
 
-		let dir = '/data/custom_ssl/npm-' + certificate.id;
+		const dir = '/data/custom_ssl/npm-' + certificate.id;
 
 		return new Promise((resolve, reject) => {
 			if (certificate.provider === 'letsencrypt') {
@@ -460,9 +457,9 @@ const internalCertificate = {
 				return;
 			}
 
-			let cert_data = certificate.meta.certificate;
+			let certData = certificate.meta.certificate;
 			if (typeof certificate.meta.intermediate_certificate !== 'undefined') {
-				cert_data = cert_data + '\n' + certificate.meta.intermediate_certificate;
+				certData = certData + '\n' + certificate.meta.intermediate_certificate;
 			}
 
 			try {
@@ -474,7 +471,7 @@ const internalCertificate = {
 				return;
 			}
 
-			fs.writeFile(dir + '/fullchain.pem', cert_data, function (err) {
+			fs.writeFile(dir + '/fullchain.pem', certData, function (err) {
 				if (err) {
 					reject(err);
 				} else {
@@ -524,7 +521,7 @@ const internalCertificate = {
 			// Put file contents into an object
 			let files = {};
 			_.map(data.files, (file, name) => {
-				if (internalCertificate.allowed_ssl_files.indexOf(name) !== -1) {
+				if (internalCertificate.allowedSslFiles.indexOf(name) !== -1) {
 					files[name] = file.data.toString();
 				}
 			});
@@ -582,7 +579,7 @@ const internalCertificate = {
 						}
 
 						_.map(data.files, (file, name) => {
-							if (internalCertificate.allowed_ssl_files.indexOf(name) !== -1) {
+							if (internalCertificate.allowedSslFiles.indexOf(name) !== -1) {
 								row.meta[name] = file.data.toString();
 							}
 						});
@@ -601,7 +598,7 @@ const internalCertificate = {
 							});
 					})
 					.then(() => {
-						return _.pick(row.meta, internalCertificate.allowed_ssl_files);
+						return _.pick(row.meta, internalCertificate.allowedSslFiles);
 					});
 			});
 	},
@@ -649,9 +646,9 @@ const internalCertificate = {
 		return tempWrite(certificate, '/tmp')
 			.then((filepath) => {
 				return internalCertificate.getCertificateInfoFromFile(filepath, throw_expired)
-					.then((cert_data) => {
+					.then((certData) => {
 						fs.unlinkSync(filepath);
-						return cert_data;
+						return certData;
 					}).catch((err) => {
 						fs.unlinkSync(filepath);
 						throw err;
@@ -667,33 +664,33 @@ const internalCertificate = {
 	 * @param {Boolean} [throw_expired]  Throw when the certificate is out of date
 	 */
 	getCertificateInfoFromFile: (certificate_file, throw_expired) => {
-		let cert_data = {};
+		let certData = {};
 
 		return utils.exec('openssl x509 -in ' + certificate_file + ' -subject -noout')
 			.then((result) => {
 				// subject=CN = something.example.com
-				let regex = /(?:subject=)?[^=]+=\s+(\S+)/gim;
-				let match = regex.exec(result);
+				const regex = /(?:subject=)?[^=]+=\s+(\S+)/gim;
+				const match = regex.exec(result);
 
 				if (typeof match[1] === 'undefined') {
 					throw new error.ValidationError('Could not determine subject from certificate: ' + result);
 				}
 
-				cert_data['cn'] = match[1];
+				certData['cn'] = match[1];
 			})
 			.then(() => {
 				return utils.exec('openssl x509 -in ' + certificate_file + ' -issuer -noout');
 			})
 			.then((result) => {
 				// issuer=C = US, O = Let's Encrypt, CN = Let's Encrypt Authority X3
-				let regex = /^(?:issuer=)?(.*)$/gim;
-				let match = regex.exec(result);
+				const regex = /^(?:issuer=)?(.*)$/gim;
+				const match = regex.exec(result);
 
 				if (typeof match[1] === 'undefined') {
 					throw new error.ValidationError('Could not determine issuer from certificate: ' + result);
 				}
 
-				cert_data['issuer'] = match[1];
+				certData['issuer'] = match[1];
 			})
 			.then(() => {
 				return utils.exec('openssl x509 -in ' + certificate_file + ' -dates -noout');
@@ -701,39 +698,39 @@ const internalCertificate = {
 			.then((result) => {
 				// notBefore=Jul 14 04:04:29 2018 GMT
 				// notAfter=Oct 12 04:04:29 2018 GMT
-				let valid_from = null;
-				let valid_to   = null;
+				let validFrom = null;
+				let validTo   = null;
 
-				let lines = result.split('\n');
+				const lines = result.split('\n');
 				lines.map(function (str) {
-					let regex = /^(\S+)=(.*)$/gim;
-					let match = regex.exec(str.trim());
+					const regex = /^(\S+)=(.*)$/gim;
+					const match = regex.exec(str.trim());
 
 					if (match && typeof match[2] !== 'undefined') {
-						let date = parseInt(moment(match[2], 'MMM DD HH:mm:ss YYYY z').format('X'), 10);
+						const date = parseInt(moment(match[2], 'MMM DD HH:mm:ss YYYY z').format('X'), 10);
 
 						if (match[1].toLowerCase() === 'notbefore') {
-							valid_from = date;
+							validFrom = date;
 						} else if (match[1].toLowerCase() === 'notafter') {
-							valid_to = date;
+							validTo = date;
 						}
 					}
 				});
 
-				if (!valid_from || !valid_to) {
+				if (!validFrom || !validTo) {
 					throw new error.ValidationError('Could not determine dates from certificate: ' + result);
 				}
 
-				if (throw_expired && valid_to < parseInt(moment().format('X'), 10)) {
+				if (throw_expired && validTo < parseInt(moment().format('X'), 10)) {
 					throw new error.ValidationError('Certificate has expired');
 				}
 
-				cert_data['dates'] = {
-					from: valid_from,
-					to:   valid_to
+				certData['dates'] = {
+					from: validFrom,
+					to:   validTo
 				};
 
-				return cert_data;
+				return certData;
 			}).catch((err) => {
 				throw new error.ValidationError('Certificate is not valid (' + err.message + ')', err);
 			});
@@ -747,7 +744,7 @@ const internalCertificate = {
 	 * @returns {Object}
 	 */
 	cleanMeta: function (meta, remove) {
-		internalCertificate.allowed_ssl_files.map((key) => {
+		internalCertificate.allowedSslFiles.map((key) => {
 			if (typeof meta[key] !== 'undefined' && meta[key]) {
 				if (remove) {
 					delete meta[key];
@@ -767,18 +764,16 @@ const internalCertificate = {
 	requestLetsEncryptSsl: (certificate) => {
 		logger.info('Requesting Let\'sEncrypt certificates for Cert #' + certificate.id + ': ' + certificate.domain_names.join(', '));
 
-		let cmd = certbot_command + ' certonly --non-interactive ' +
-			'--config "' + le_config + '" ' +
+		const cmd = certbotCommand + ' certonly --non-interactive ' +
+			'--config "' + letsencryptConfig + '" ' +
 			'--cert-name "npm-' + certificate.id + '" ' +
 			'--agree-tos ' +
 			'--email "' + certificate.meta.letsencrypt_email + '" ' +
 			'--preferred-challenges "dns,http" ' +
 			'--domains "' + certificate.domain_names.join(',') + '" ' +
-			(le_staging ? '--staging' : '');
+			(letsencryptStaging ? '--staging' : '');
 
-		if (debug_mode) {
-			logger.info('Command:', cmd);
-		}
+		logger.info('Command:', cmd);
 
 		return utils.exec(cmd)
 			.then((result) => {
@@ -788,14 +783,14 @@ const internalCertificate = {
 	},
 
 	/**
-	 * @param   {Object}  certificate   			the certificate row
-	 * @param		{String} 	dns_provider				the dns provider name (key used in `certbot-dns-plugins.js`)
-	 * @param		{String | null} 	credentials	the content of this providers credentials file
-	 * @param		{String} 	propagation_seconds	the cloudflare api token
+	 * @param   {Object}         certificate          the certificate row
+	 * @param   {String}         dns_provider         the dns provider name (key used in `certbot-dns-plugins.js`)
+	 * @param   {String | null}  credentials          the content of this providers credentials file
+	 * @param   {String}         propagation_seconds  the cloudflare api token
 	 * @returns {Promise}
 	 */
 	requestLetsEncryptSslWithDnsChallenge: (certificate) => {
-		const dns_plugin = dns_plugins[certificate.meta.dns_provider];
+		const dns_plugin = dnsPlugins[certificate.meta.dns_provider];
 
 		if (!dns_plugin) {
 			throw Error(`Unknown DNS provider '${certificate.meta.dns_provider}'`);
@@ -803,46 +798,43 @@ const internalCertificate = {
 
 		logger.info(`Requesting Let'sEncrypt certificates via ${dns_plugin.display_name} for Cert #${certificate.id}: ${certificate.domain_names.join(', ')}`);
 
-		const credentials_loc = '/etc/letsencrypt/credentials/credentials-' + certificate.id;
-		const credentials_cmd = 'mkdir -p /etc/letsencrypt/credentials 2> /dev/null; echo \'' + certificate.meta.dns_provider_credentials.replace('\'', '\\\'') + '\' > \'' + credentials_loc + '\' && chmod 600 \'' + credentials_loc + '\'';
-		const prepare_cmd     = 'pip install ' + dns_plugin.package_name + '==' + dns_plugin.package_version + ' ' + dns_plugin.dependencies;
+		const credentialsLocation = '/etc/letsencrypt/credentials/credentials-' + certificate.id;
+		const credentialsCmd      = 'mkdir -p /etc/letsencrypt/credentials 2> /dev/null; echo \'' + certificate.meta.dns_provider_credentials.replace('\'', '\\\'') + '\' > \'' + credentialsLocation + '\' && chmod 600 \'' + credentialsLocation + '\'';
+		const prepareCmd          = 'pip install ' + dns_plugin.package_name + '==' + dns_plugin.package_version + ' ' + dns_plugin.dependencies;
 
 		// Whether the plugin has a --<name>-credentials argument
-		const has_config_arg = certificate.meta.dns_provider !== 'route53';
+		const hasConfigArg = certificate.meta.dns_provider !== 'route53';
 
-		let main_cmd = 
-			certbot_command + ' certonly --non-interactive ' +
+		let mainCmd = certbotCommand + ' certonly --non-interactive ' +
 			'--cert-name "npm-' + certificate.id + '" ' +
 			'--agree-tos ' +
-			'--email "' + certificate.meta.letsencrypt_email + '" ' +			
+			'--email "' + certificate.meta.letsencrypt_email + '" ' +
 			'--domains "' + certificate.domain_names.join(',') + '" ' +
 			'--authenticator ' + dns_plugin.full_plugin_name + ' ' +
 			(
-				has_config_arg 
-					? '--' + dns_plugin.full_plugin_name + '-credentials "' + credentials_loc + '"' 
+				hasConfigArg
+					? '--' + dns_plugin.full_plugin_name + '-credentials "' + credentialsLocation + '"'
 					: ''
 			) +
 			(
-				certificate.meta.propagation_seconds !== undefined 
-					? ' --' + dns_plugin.full_plugin_name + '-propagation-seconds ' + certificate.meta.propagation_seconds 
+				certificate.meta.propagation_seconds !== undefined
+					? ' --' + dns_plugin.full_plugin_name + '-propagation-seconds ' + certificate.meta.propagation_seconds
 					: ''
 			) +
-			(le_staging ? ' --staging' : '');
+			(letsencryptStaging ? ' --staging' : '');
 
 		// Prepend the path to the credentials file as an environment variable
 		if (certificate.meta.dns_provider === 'route53') {
-			main_cmd = 'AWS_CONFIG_FILE=\'' + credentials_loc + '\' ' + main_cmd;
+			mainCmd = 'AWS_CONFIG_FILE=\'' + credentialsLocation + '\' ' + mainCmd;
 		}
 
-		if (debug_mode) {
-			logger.info('Command:', `${credentials_cmd} && ${prepare_cmd} && ${main_cmd}`);
-		}
+		logger.info('Command:', `${credentialsCmd} && ${prepareCmd} && ${mainCmd}`);
 
-		return utils.exec(credentials_cmd)
+		return utils.exec(credentialsCmd)
 			.then(() => {
-				return utils.exec(prepare_cmd)
+				return utils.exec(prepareCmd)
 					.then(() => {
-						return utils.exec(main_cmd)
+						return utils.exec(mainCmd)
 							.then(async (result) => {
 								logger.info(result);
 								return result;
@@ -850,8 +842,8 @@ const internalCertificate = {
 					});
 			}).catch(async (err) => {
 				// Don't fail if file does not exist
-				const delete_credentials_cmd = `rm -f '${credentials_loc}' || true`;
-				await utils.exec(delete_credentials_cmd);
+				const delete_credentialsCmd = `rm -f '${credentialsLocation}' || true`;
+				await utils.exec(delete_credentialsCmd);
 				throw err;
 			});
 	},
@@ -870,7 +862,7 @@ const internalCertificate = {
 			})
 			.then((certificate) => {
 				if (certificate.provider === 'letsencrypt') {
-					let renewMethod = certificate.meta.dns_challenge ? internalCertificate.renewLetsEncryptSslWithDnsChallenge : internalCertificate.renewLetsEncryptSsl;		
+					const renewMethod = certificate.meta.dns_challenge ? internalCertificate.renewLetsEncryptSslWithDnsChallenge : internalCertificate.renewLetsEncryptSsl;
 
 					return renewMethod(certificate)
 						.then(() => {
@@ -908,16 +900,14 @@ const internalCertificate = {
 	renewLetsEncryptSsl: (certificate) => {
 		logger.info('Renewing Let\'sEncrypt certificates for Cert #' + certificate.id + ': ' + certificate.domain_names.join(', '));
 
-		let cmd = certbot_command + ' renew --non-interactive ' +
-			'--config "' + le_config + '" ' +
+		const cmd = certbotCommand + ' renew --force-renewal --non-interactive ' +
+			'--config "' + letsencryptConfig + '" ' +
 			'--cert-name "npm-' + certificate.id + '" ' +
 			'--preferred-challenges "dns,http" ' +
 			'--disable-hook-validation ' +
-			(le_staging ? '--staging' : '');
+			(letsencryptStaging ? '--staging' : '');
 
-		if (debug_mode) {
-			logger.info('Command:', cmd);
-		}
+		logger.info('Command:', cmd);
 
 		return utils.exec(cmd)
 			.then((result) => {
@@ -931,7 +921,7 @@ const internalCertificate = {
 	 * @returns {Promise}
 	 */
 	renewLetsEncryptSslWithDnsChallenge: (certificate) => {
-		const dns_plugin = dns_plugins[certificate.meta.dns_provider];
+		const dns_plugin = dnsPlugins[certificate.meta.dns_provider];
 
 		if (!dns_plugin) {
 			throw Error(`Unknown DNS provider '${certificate.meta.dns_provider}'`);
@@ -939,23 +929,20 @@ const internalCertificate = {
 
 		logger.info(`Renewing Let'sEncrypt certificates via ${dns_plugin.display_name} for Cert #${certificate.id}: ${certificate.domain_names.join(', ')}`);
 
-		let main_cmd = 
-			certbot_command + ' renew --non-interactive ' +
+		let mainCmd = certbotCommand + ' renew --non-interactive ' +
 			'--cert-name "npm-' + certificate.id + '" ' +
 			'--disable-hook-validation' +
-			(le_staging ? ' --staging' : '');
+			(letsencryptStaging ? ' --staging' : '');
 
 		// Prepend the path to the credentials file as an environment variable
 		if (certificate.meta.dns_provider === 'route53') {
-			const credentials_loc = '/etc/letsencrypt/credentials/credentials-' + certificate.id;
-			main_cmd              = 'AWS_CONFIG_FILE=\'' + credentials_loc + '\' ' + main_cmd;
+			const credentialsLocation = '/etc/letsencrypt/credentials/credentials-' + certificate.id;
+			mainCmd                   = 'AWS_CONFIG_FILE=\'' + credentialsLocation + '\' ' + mainCmd;
 		}
 
-		if (debug_mode) {
-			logger.info('Command:', main_cmd);
-		}
+		logger.info('Command:', mainCmd);
 
-		return utils.exec(main_cmd)
+		return utils.exec(mainCmd)
 			.then(async (result) => {
 				logger.info(result);
 				return result;
@@ -970,28 +957,24 @@ const internalCertificate = {
 	revokeLetsEncryptSsl: (certificate, throw_errors) => {
 		logger.info('Revoking Let\'sEncrypt certificates for Cert #' + certificate.id + ': ' + certificate.domain_names.join(', '));
 
-		const main_cmd = certbot_command + ' revoke --non-interactive ' +
+		const mainCmd = certbotCommand + ' revoke --non-interactive ' +
 			'--cert-path "/etc/letsencrypt/live/npm-' + certificate.id + '/fullchain.pem" ' +
 			'--delete-after-revoke ' +
-			(le_staging ? '--staging' : '');
+			(letsencryptStaging ? '--staging' : '');
 
 		// Don't fail command if file does not exist
-		const delete_credentials_cmd = `rm -f '/etc/letsencrypt/credentials/credentials-${certificate.id}' || true`;
+		const delete_credentialsCmd = `rm -f '/etc/letsencrypt/credentials/credentials-${certificate.id}' || true`;
 
-		if (debug_mode) {
-			logger.info('Command:', main_cmd + '; ' + delete_credentials_cmd);
-		}
+		logger.info('Command:', mainCmd + '; ' + delete_credentialsCmd);
 
-		return utils.exec(main_cmd)
+		return utils.exec(mainCmd)
 			.then(async (result) => {
-				await utils.exec(delete_credentials_cmd);
+				await utils.exec(delete_credentialsCmd);
 				logger.info(result);
 				return result;
 			})
 			.catch((err) => {
-				if (debug_mode) {
-					logger.error(err.message);
-				}
+				logger.error(err.message);
 
 				if (throw_errors) {
 					throw err;
@@ -1004,9 +987,9 @@ const internalCertificate = {
 	 * @returns {Boolean}
 	 */
 	hasLetsEncryptSslCerts: (certificate) => {
-		let le_path = '/etc/letsencrypt/live/npm-' + certificate.id;
+		const letsencryptPath = '/etc/letsencrypt/live/npm-' + certificate.id;
 
-		return fs.existsSync(le_path + '/fullchain.pem') && fs.existsSync(le_path + '/privkey.pem');
+		return fs.existsSync(letsencryptPath + '/fullchain.pem') && fs.existsSync(letsencryptPath + '/privkey.pem');
 	},
 
 	/**

+ 21 - 1
backend/setup.js

@@ -201,9 +201,29 @@ const setupCertbotPlugins = () => {
 		});
 };
 
+
+/**
+ * Starts a timer to call run the logrotation binary every two days
+ * @returns {Promise}
+ */
+const setupLogrotation = () => {
+	const intervalTimeout = 1000 * 60 * 60 * 24 * 2; // 2 days
+
+	const runLogrotate = async () => {
+		await utils.exec('logrotate /etc/logrotate.d/nginx-proxy-manager');
+		logger.info('Logrotate completed.');
+	};
+
+	logger.info('Logrotate Timer initialized');
+	setInterval(runLogrotate, intervalTimeout);
+	// And do this now as well
+	return runLogrotate();
+};
+
 module.exports = function () {
 	return setupJwt()
 		.then(setupDefaultUser)
 		.then(setupDefaultSettings)
-		.then(setupCertbotPlugins);
+		.then(setupCertbotPlugins)
+		.then(setupLogrotation);
 };

+ 2 - 1
backend/templates/dead_host.conf

@@ -7,7 +7,8 @@ server {
 {% include "_hsts.conf" %}
 {% include "_forced_ssl.conf" %}
 
-  access_log /data/logs/dead_host-{{ id }}.log standard;
+  access_log /data/logs/dead-host-{{ id }}_access.log standard;
+  error_log /data/logs/dead-host-{{ id }}_error.log warn;
 
 {{ advanced_config }}
 

+ 2 - 1
backend/templates/default.conf

@@ -12,7 +12,8 @@ server {
   #listen [::]:80;
 {% endif %}
   server_name default-host.localhost;
-  access_log /data/logs/default_host.log combined;
+  access_log /data/logs/default-host_access.log combined;
+  error_log /data/logs/default-host_error.log warn;
 {% include "_exploits.conf" %}
 
 {%- if value == "404" %}

+ 2 - 1
backend/templates/letsencrypt-request.conf

@@ -8,7 +8,8 @@ server {
 
   server_name {{ domain_names | join: " " }};
 
-  access_log /data/logs/letsencrypt-requests.log standard;
+  access_log /data/logs/letsencrypt-requests_access.log standard;
+  error_log /data/logs/letsencrypt-requests_error.log warn;
 
   include conf.d/include/letsencrypt-acme-challenge.conf;
 

+ 2 - 2
backend/templates/proxy_host.conf

@@ -19,8 +19,8 @@ proxy_set_header Connection $http_connection;
 proxy_http_version 1.1;
 {% endif %}
 
-
-  access_log /data/logs/proxy_host-{{ id }}.log proxy;
+  access_log /data/logs/proxy-host-{{ id }}_access.log proxy;
+  error_log /data/logs/proxy-host-{{ id }}_error.log warn;
 
 {{ advanced_config }}
 

+ 2 - 1
backend/templates/redirection_host.conf

@@ -9,7 +9,8 @@ server {
 {% include "_hsts.conf" %}
 {% include "_forced_ssl.conf" %}
 
-  access_log /data/logs/redirection_host-{{ id }}.log standard;
+  access_log /data/logs/redirection-host-{{ id }}_access.log standard;
+  error_log /data/logs/redirection-host-{{ id }}_error.log warn;
 
 {{ advanced_config }}
 

+ 4 - 1
docker/Dockerfile

@@ -20,7 +20,7 @@ ENV SUPPRESS_NO_CONFIG_WARNING=1 \
 
 RUN echo "fs.file-max = 65535" > /etc/sysctl.conf \
 	&& apt-get update \
-	&& apt-get install -y --no-install-recommends jq \
+	&& apt-get install -y --no-install-recommends jq logrotate \
 	&& apt-get clean \
 	&& rm -rf /var/lib/apt/lists/*
 
@@ -43,6 +43,9 @@ COPY docker/rootfs /
 # Remove frontend service not required for prod, dev nginx config as well
 RUN rm -rf /etc/services.d/frontend /etc/nginx/conf.d/dev.conf
 
+# Change permission of logrotate config file
+RUN chmod 644 /etc/logrotate.d/nginx-proxy-manager
+
 VOLUME [ "/data", "/etc/letsencrypt" ]
 ENTRYPOINT [ "/init" ]
 HEALTHCHECK --interval=5s --timeout=3s CMD /bin/check-health

+ 2 - 1
docker/dev/Dockerfile

@@ -7,7 +7,7 @@ ENV S6_LOGGING=0 \
 
 RUN echo "fs.file-max = 65535" > /etc/sysctl.conf \
 	&& apt-get update \
-	&& apt-get install -y certbot jq python3-pip \
+	&& apt-get install -y certbot jq python3-pip logrotate \
 	&& apt-get clean \
 	&& rm -rf /var/lib/apt/lists/*
 
@@ -18,6 +18,7 @@ RUN cd /usr \
 
 COPY rootfs /
 RUN rm -f /etc/nginx/conf.d/production.conf
+RUN chmod 644 /etc/logrotate.d/nginx-proxy-manager
 
 # s6 overlay
 RUN curl -L -o /tmp/s6-overlay-amd64.tar.gz "https://github.com/just-containers/s6-overlay/releases/download/v1.22.1.0/s6-overlay-amd64.tar.gz" \

+ 10 - 4
docker/docker-compose.dev.yml

@@ -1,9 +1,9 @@
 # WARNING: This is a DEVELOPMENT docker-compose file, it should not be used for production.
-version: "3"
+version: "3.5"
 services:
-
   npm:
     image: nginxproxymanager:dev
+    container_name: npm_core
     build:
       context: ./
       dockerfile: ./dev/Dockerfile
@@ -36,6 +36,7 @@ services:
 
   db:
     image: jc21/mariadb-aria
+    container_name: npm_db
     networks:
       - nginx_proxy_manager
     environment:
@@ -47,21 +48,26 @@ services:
       - db_data:/var/lib/mysql
 
   swagger:
-    image: 'swaggerapi/swagger-ui:latest'
+    image: "swaggerapi/swagger-ui:latest"
+    container_name: npm_swagger
     ports:
       - 3001:80
     networks:
       - nginx_proxy_manager
     environment:
       URL: "http://127.0.0.1:3081/api/schema"
-      PORT: '80'
+      PORT: "80"
     depends_on:
       - npm
 
 volumes:
   npm_data:
+    name: npm_core_data
   le_data:
+    name: npm_le_data
   db_data:
+    name: npm_db_data
 
 networks:
   nginx_proxy_manager:
+    name: npm_network

+ 25 - 0
docker/rootfs/etc/logrotate.d/nginx-proxy-manager

@@ -0,0 +1,25 @@
+/data/logs/*_access.log /data/logs/*/access.log {
+    create 0644 root root
+    weekly
+    rotate 4
+    missingok
+    notifempty
+    compress
+    sharedscripts
+    postrotate
+    /bin/kill -USR1 `cat /run/nginx.pid 2>/dev/null` 2>/dev/null || true
+    endscript
+}
+
+/data/logs/*_error.log /data/logs/*/error.log {
+    create 0644 root root
+    weekly
+    rotate 10
+    missingok
+    notifempty
+    compress
+    sharedscripts
+    postrotate
+    /bin/kill -USR1 `cat /run/nginx.pid 2>/dev/null` 2>/dev/null || true
+    endscript
+}

+ 2 - 2
docker/rootfs/etc/nginx/conf.d/default.conf

@@ -8,7 +8,7 @@ server {
 	set $port "80";
 
 	server_name localhost-nginx-proxy-manager;
-	access_log /data/logs/default.log standard;
+	access_log /data/logs/fallback_access.log standard;
 	error_log /dev/null crit;
 	include conf.d/include/assets.conf;
 	include conf.d/include/block-exploits.conf;
@@ -29,7 +29,7 @@ server {
 	set $port "443";
 
 	server_name localhost;
-	access_log /data/logs/default.log standard;
+	access_log /data/logs/fallback-access.log standard;
 	error_log /dev/null crit;
 	ssl_certificate /data/nginx/dummycert.pem;
 	ssl_certificate_key /data/nginx/dummykey.pem;

+ 1 - 0
docker/rootfs/etc/nginx/conf.d/include/letsencrypt-acme-challenge.conf

@@ -5,6 +5,7 @@ location ^~ /.well-known/acme-challenge/ {
 	# Since this is for letsencrypt authentication of a domain and they do not give IP ranges of their infrastructure
 	# we need to open up access by turning off auth and IP ACL for this location.
 	auth_basic off;
+	auth_request off;
 	allow all;
 
 	# Set correct content type. According to this:

+ 2 - 3
docker/rootfs/etc/nginx/nginx.conf

@@ -9,7 +9,7 @@ worker_processes auto;
 # Enables the use of JIT for regular expressions to speed-up their processing.
 pcre_jit on;
 
-error_log /data/logs/error.log warn;
+error_log /data/logs/fallback_error.log warn;
 
 # Includes files with directives to load dynamic modules.
 include /etc/nginx/modules/*.conf;
@@ -46,8 +46,7 @@ http {
 	log_format proxy '[$time_local] $upstream_cache_status $upstream_status $status - $request_method $scheme $host "$request_uri" [Client $remote_addr] [Length $body_bytes_sent] [Gzip $gzip_ratio] [Sent-to $server] "$http_user_agent" "$http_referer"';
 	log_format standard '[$time_local] $status - $request_method $scheme $host "$request_uri" [Client $remote_addr] [Length $body_bytes_sent] [Gzip $gzip_ratio] "$http_user_agent" "$http_referer"';
 
-
-	access_log /data/logs/default.log proxy;
+	access_log /data/logs/fallback_access.log proxy;
 
 	# Dynamically generated resolvers file
 	include /etc/nginx/conf.d/include/resolvers.conf;

+ 3 - 3
docs/yarn.lock

@@ -2650,9 +2650,9 @@ color-name@^1.0.0, color-name@^1.1.4, color-name@~1.1.4:
   integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
 
 color-string@^1.5.2, color-string@^1.5.3:
-  version "1.5.3"
-  resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.3.tgz#c9bbc5f01b58b5492f3d6857459cb6590ce204cc"
-  integrity sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw==
+  version "1.5.5"
+  resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.5.tgz#65474a8f0e7439625f3d27a6a19d89fc45223014"
+  integrity sha512-jgIoum0OfQfq9Whcfc2z/VhCNcmQjWbey6qBX0vqt7YICflUmBCh9E9CiQD5GSJ+Uehixm3NUwHVhqUAWRivZg==
   dependencies:
     color-name "^1.0.0"
     simple-swizzle "^0.2.2"

+ 1 - 1
frontend/js/app/nginx/certificates/form.js

@@ -251,7 +251,7 @@ module.exports = Mn.View.extend({
                     text:  input
                 };
             },
-            createFilter: /^(?:[^.]+\.?)+[^.]$/
+            createFilter: /^(?:\*\.)?(?:[^.*]+\.?)+[^.]$/
         });
         this.ui.dns_challenge_content.hide();
         this.ui.credentials_file_content.hide(); 

+ 1 - 1
frontend/js/app/nginx/proxy/form.js

@@ -278,7 +278,7 @@ module.exports = Mn.View.extend({
                     text:  input
                 };
             },
-            createFilter: /^(?:\.)?(?:[^.*]+\.?)+[^.]$/
+            createFilter: /^(?:\*\.)?(?:[^.*]+\.?)+[^.]$/
         });
 
         // Access Lists

+ 1 - 1
frontend/js/i18n/messages.json

@@ -60,7 +60,7 @@
     },
     "footer": {
       "fork-me": "Fork me on Github",
-      "copy": "&copy; 2019 <a href=\"{url}\" target=\"_blank\">jc21.com</a>.",
+      "copy": "&copy; 2021 <a href=\"{url}\" target=\"_blank\">jc21.com</a>.",
       "theme": "Theme by <a href=\"{url}\" target=\"_blank\">Tabler</a>"
     },
     "dashboard": {

+ 10 - 0
global/certbot-dns-plugins.js

@@ -293,6 +293,16 @@ certbot_dns_ispconfig:dns_ispconfig_endpoint = https://localhost:8080`,
 certbot_dns_isset:dns_isset_token="<token>"`,
 		full_plugin_name: 'certbot-dns-isset:dns-isset',
 	},
+	joker: {
+		display_name:    'Joker',
+		package_name:    'certbot-dns-joker',
+		package_version: '1.1.0',
+		dependencies:    '',
+		credentials:     `certbot_dns_joker:dns_joker_username = <Dynamic DNS Authentication Username>
+certbot_dns_joker:dns_joker_password = <Dynamic DNS Authentication Password>
+certbot_dns_joker:dns_joker_domain = <Dynamic DNS Domain>`,
+		full_plugin_name: 'certbot-dns-joker:dns-joker',
+	},
 	//####################################################//
 	linode: {
 		display_name:    'Linode',