You've already forked nginx-proxy-manager
							
							
				mirror of
				https://github.com/NginxProxyManager/nginx-proxy-manager.git
				synced 2025-11-04 04:11:42 +03:00 
			
		
		
		
	Merge pull request #635 from chaptergy/allow-more-dns-challenges
Allow DNS challenges not just for cloudflare
This commit is contained in:
		
							
								
								
									
										1
									
								
								Jenkinsfile
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								Jenkinsfile
									
									
									
									
										vendored
									
									
								
							@@ -65,6 +65,7 @@ pipeline {
 | 
			
		||||
				// See: https://github.com/yarnpkg/yarn/issues/3254
 | 
			
		||||
				sh '''docker run --rm \\
 | 
			
		||||
					-v "$(pwd)/backend:/app" \\
 | 
			
		||||
					-v "$(pwd)/global:/app/global" \\
 | 
			
		||||
					-w /app \\
 | 
			
		||||
					node:latest \\
 | 
			
		||||
					sh -c "yarn install && yarn eslint . && rm -rf node_modules"
 | 
			
		||||
 
 | 
			
		||||
@@ -66,7 +66,7 @@ app.use(function (err, req, res, next) {
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	if (process.env.NODE_ENV === 'development') {
 | 
			
		||||
	if (process.env.NODE_ENV === 'development' || (req.baseUrl + req.path).includes('nginx/certificates')) {
 | 
			
		||||
		payload.debug = {
 | 
			
		||||
			stack:    typeof err.stack !== 'undefined' && err.stack ? err.stack.split('\n') : null,
 | 
			
		||||
			previous: err.previous
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@
 | 
			
		||||
      "knex": {
 | 
			
		||||
        "client": "sqlite3",
 | 
			
		||||
        "connection": {
 | 
			
		||||
          "filename": "/app/backend/config/mydb.sqlite"
 | 
			
		||||
          "filename": "/app/config/mydb.sqlite"
 | 
			
		||||
        },
 | 
			
		||||
        "pool": {
 | 
			
		||||
          "min": 0,
 | 
			
		||||
 
 | 
			
		||||
@@ -13,6 +13,7 @@ const internalNginx    = require('./nginx');
 | 
			
		||||
const internalHost     = require('./host');
 | 
			
		||||
const certbot_command  = '/usr/bin/certbot';
 | 
			
		||||
const le_config        = '/etc/letsencrypt.ini';
 | 
			
		||||
const dns_plugins      = require('../global/certbot-dns-plugins');
 | 
			
		||||
 | 
			
		||||
function omissions() {
 | 
			
		||||
	return ['is_deleted'];
 | 
			
		||||
@@ -141,11 +142,11 @@ const internalCertificate = {
 | 
			
		||||
								});
 | 
			
		||||
						})
 | 
			
		||||
						.then((in_use_result) => {
 | 
			
		||||
							// Is CloudFlare, no config needed, so skip 3 and 5.
 | 
			
		||||
							if (data.meta.cloudflare_use) {
 | 
			
		||||
							// With DNS challenge no config is needed, so skip 3 and 5.
 | 
			
		||||
							if (certificate.meta.dns_challenge) {
 | 
			
		||||
								return internalNginx.reload().then(() => {
 | 
			
		||||
									// 4. Request cert
 | 
			
		||||
									return internalCertificate.requestLetsEncryptCloudFlareDnsSsl(certificate, data.meta.cloudflare_token);
 | 
			
		||||
									return internalCertificate.requestLetsEncryptSslWithDnsChallenge(certificate);
 | 
			
		||||
								})
 | 
			
		||||
									.then(internalNginx.reload)
 | 
			
		||||
									.then(() => {
 | 
			
		||||
@@ -773,34 +774,69 @@ const internalCertificate = {
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * @param   {Object}  certificate   			the certificate row
 | 
			
		||||
	 * @param	{String} apiToken		the cloudflare api token
 | 
			
		||||
	 * @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}
 | 
			
		||||
	 */
 | 
			
		||||
	requestLetsEncryptCloudFlareDnsSsl: (certificate, apiToken) => {
 | 
			
		||||
		logger.info('Requesting Let\'sEncrypt certificates via Cloudflare DNS for Cert #' + certificate.id + ': ' + certificate.domain_names.join(', '));
 | 
			
		||||
	requestLetsEncryptSslWithDnsChallenge: (certificate) => {
 | 
			
		||||
		const dns_plugin = dns_plugins[certificate.meta.dns_provider];
 | 
			
		||||
 | 
			
		||||
		let tokenLoc = '~/cloudflare-token';
 | 
			
		||||
		let storeKey = 'echo "dns_cloudflare_api_token = ' + apiToken + '" > ' + tokenLoc;	
 | 
			
		||||
		if (!dns_plugin) {
 | 
			
		||||
			throw Error(`Unknown DNS provider '${certificate.meta.dns_provider}'`);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		let cmd = 
 | 
			
		||||
			storeKey + ' && ' +
 | 
			
		||||
		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-' + certificate.id;
 | 
			
		||||
		const credentials_cmd = 'echo \'' + certificate.meta.dns_provider_credentials.replace('\'', '\\\'') + '\' > \'' + credentials_loc + '\' && chmod 600 \'' + credentials_loc + '\'';
 | 
			
		||||
		const prepare_cmd     = 'pip3 install ' + dns_plugin.package_name + '==' + dns_plugin.package_version;
 | 
			
		||||
 | 
			
		||||
		// Whether the plugin has a --<name>-credentials argument
 | 
			
		||||
		const has_config_arg = certificate.meta.dns_provider !== 'route53';
 | 
			
		||||
 | 
			
		||||
		let main_cmd = 
 | 
			
		||||
			certbot_command + ' certonly --non-interactive ' +
 | 
			
		||||
			'--cert-name "npm-' + certificate.id + '" ' +
 | 
			
		||||
			'--agree-tos ' +
 | 
			
		||||
			'--email "' + certificate.meta.letsencrypt_email + '" ' +			
 | 
			
		||||
			'--domains "' + certificate.domain_names.join(',') + '" ' +
 | 
			
		||||
			'--dns-cloudflare --dns-cloudflare-credentials ' + tokenLoc +
 | 
			
		||||
			(le_staging ? ' --staging' : '')
 | 
			
		||||
			+ ' && rm ' + tokenLoc;
 | 
			
		||||
			'--authenticator ' + dns_plugin.full_plugin_name + ' ' +
 | 
			
		||||
			(
 | 
			
		||||
				has_config_arg 
 | 
			
		||||
					? '--' + dns_plugin.full_plugin_name + '-credentials "' + credentials_loc + '"' 
 | 
			
		||||
					: ''
 | 
			
		||||
			) +
 | 
			
		||||
			(
 | 
			
		||||
				certificate.meta.propagation_seconds !== undefined 
 | 
			
		||||
					? ' --' + dns_plugin.full_plugin_name + '-propagation-seconds ' + certificate.meta.propagation_seconds 
 | 
			
		||||
					: ''
 | 
			
		||||
			) +
 | 
			
		||||
			(le_staging ? ' --staging' : '');
 | 
			
		||||
 | 
			
		||||
		if (debug_mode) {
 | 
			
		||||
			logger.info('Command:', cmd);
 | 
			
		||||
		// 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;
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		return utils.exec(cmd).then((result) => {
 | 
			
		||||
		const teardown_cmd = `rm '${credentials_loc}'`;
 | 
			
		||||
 | 
			
		||||
		if (debug_mode) {
 | 
			
		||||
			logger.info('Command:', `${credentials_cmd} && ${prepare_cmd} && ${main_cmd} && ${teardown_cmd}`);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return utils.exec(credentials_cmd)
 | 
			
		||||
			.then(() => {
 | 
			
		||||
				return utils.exec(prepare_cmd)
 | 
			
		||||
					.then(() => {
 | 
			
		||||
						return utils.exec(main_cmd)
 | 
			
		||||
							.then(async (result) => {
 | 
			
		||||
								await utils.exec(teardown_cmd);
 | 
			
		||||
								logger.info(result);
 | 
			
		||||
								return result;
 | 
			
		||||
							});
 | 
			
		||||
					});
 | 
			
		||||
			});
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -817,7 +853,7 @@ const internalCertificate = {
 | 
			
		||||
			})
 | 
			
		||||
			.then((certificate) => {
 | 
			
		||||
				if (certificate.provider === 'letsencrypt') {
 | 
			
		||||
					let renewMethod = certificate.meta.cloudflare_use ? internalCertificate.renewLetsEncryptCloudFlareSsl : internalCertificate.renewLetsEncryptSsl;		
 | 
			
		||||
					let renewMethod = certificate.meta.dns_challenge ? internalCertificate.renewLetsEncryptSslWithDnsChallenge : internalCertificate.renewLetsEncryptSsl;		
 | 
			
		||||
 | 
			
		||||
					return renewMethod(certificate)
 | 
			
		||||
						.then(() => {
 | 
			
		||||
@@ -877,23 +913,48 @@ const internalCertificate = {
 | 
			
		||||
	 * @param   {Object}  certificate   the certificate row
 | 
			
		||||
	 * @returns {Promise}
 | 
			
		||||
	 */
 | 
			
		||||
	renewLetsEncryptCloudFlareSsl: (certificate) => {
 | 
			
		||||
		logger.info('Renewing Let\'sEncrypt certificates for Cert #' + certificate.id + ': ' + certificate.domain_names.join(', '));
 | 
			
		||||
	renewLetsEncryptSslWithDnsChallenge: (certificate) => {
 | 
			
		||||
		const dns_plugin = dns_plugins[certificate.meta.dns_provider];
 | 
			
		||||
 | 
			
		||||
		let cmd = certbot_command + ' renew --non-interactive ' +
 | 
			
		||||
			'--cert-name "npm-' + certificate.id + '" ' +
 | 
			
		||||
			'--disable-hook-validation ' +
 | 
			
		||||
			(le_staging ? '--staging' : '');
 | 
			
		||||
 | 
			
		||||
		if (debug_mode) {
 | 
			
		||||
			logger.info('Command:', cmd);
 | 
			
		||||
		if (!dns_plugin) {
 | 
			
		||||
			throw Error(`Unknown DNS provider '${certificate.meta.dns_provider}'`);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return utils.exec(cmd)
 | 
			
		||||
			.then((result) => {
 | 
			
		||||
		logger.info(`Renewing Let'sEncrypt certificates via ${dns_plugin.display_name} for Cert #${certificate.id}: ${certificate.domain_names.join(', ')}`);
 | 
			
		||||
 | 
			
		||||
		const credentials_loc = '/etc/letsencrypt/credentials-' + certificate.id;
 | 
			
		||||
		const credentials_cmd = 'echo \'' + certificate.meta.dns_provider_credentials.replace('\'', '\\\'') + '\' > \'' + credentials_loc + '\' && chmod 600 \'' + credentials_loc + '\'';
 | 
			
		||||
		const prepare_cmd     = 'pip3 install ' + dns_plugin.package_name + '==' + dns_plugin.package_version;
 | 
			
		||||
 | 
			
		||||
		let main_cmd = 
 | 
			
		||||
			certbot_command + ' renew --non-interactive ' +
 | 
			
		||||
			'--cert-name "npm-' + certificate.id + '" ' +
 | 
			
		||||
			'--disable-hook-validation' +
 | 
			
		||||
			(le_staging ? ' --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;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		const teardown_cmd = `rm '${credentials_loc}'`;
 | 
			
		||||
 | 
			
		||||
		if (debug_mode) {
 | 
			
		||||
			logger.info('Command:', `${credentials_cmd} && ${prepare_cmd} && ${main_cmd} && ${teardown_cmd}`);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return utils.exec(credentials_cmd)
 | 
			
		||||
			.then(() => {
 | 
			
		||||
				return utils.exec(prepare_cmd)
 | 
			
		||||
					.then(() => {
 | 
			
		||||
						return utils.exec(main_cmd)
 | 
			
		||||
							.then(async (result) => {
 | 
			
		||||
								await utils.exec(teardown_cmd);
 | 
			
		||||
								logger.info(result);
 | 
			
		||||
								return result;
 | 
			
		||||
							});
 | 
			
		||||
					});
 | 
			
		||||
			});
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
 
 | 
			
		||||
@@ -58,6 +58,7 @@ router
 | 
			
		||||
	.post((req, res, next) => {
 | 
			
		||||
		apiValidator({$ref: 'endpoints/certificates#/links/1/schema'}, req.body)
 | 
			
		||||
			.then((payload) => {
 | 
			
		||||
				req.setTimeout(900000); // 15 minutes timeout
 | 
			
		||||
				return internalCertificate.create(res.locals.access, payload);
 | 
			
		||||
			})
 | 
			
		||||
			.then((result) => {
 | 
			
		||||
@@ -197,6 +198,7 @@ router
 | 
			
		||||
	 * Renew certificate
 | 
			
		||||
	 */
 | 
			
		||||
	.post((req, res, next) => {
 | 
			
		||||
		req.setTimeout(900000); // 15 minutes timeout
 | 
			
		||||
		internalCertificate.renew(res.locals.access, {
 | 
			
		||||
			id: parseInt(req.params.certificate_id, 10)
 | 
			
		||||
		})
 | 
			
		||||
 
 | 
			
		||||
@@ -42,11 +42,23 @@
 | 
			
		||||
        "letsencrypt_agree": {
 | 
			
		||||
          "type": "boolean"
 | 
			
		||||
        },
 | 
			
		||||
        "cloudflare_use": {
 | 
			
		||||
        "dns_challenge": {
 | 
			
		||||
          "type": "boolean"
 | 
			
		||||
        },
 | 
			
		||||
        "cloudflare_token": {
 | 
			
		||||
        "dns_provider": {
 | 
			
		||||
          "type": "string"
 | 
			
		||||
        },
 | 
			
		||||
        "dns_provider_credentials": {
 | 
			
		||||
          "type": "string"
 | 
			
		||||
        },
 | 
			
		||||
        "propagation_seconds": {
 | 
			
		||||
          "anyOf": [
 | 
			
		||||
            { 
 | 
			
		||||
              "type": "integer",
 | 
			
		||||
              "minimum": 0 
 | 
			
		||||
            }
 | 
			
		||||
          ]
 | 
			
		||||
          
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -17,8 +17,8 @@ ENV NODE_ENV=production
 | 
			
		||||
 | 
			
		||||
RUN echo "fs.file-max = 65535" > /etc/sysctl.conf \
 | 
			
		||||
	&& apk update \
 | 
			
		||||
	&& apk add python2 py-pip certbot jq \
 | 
			
		||||
	&& pip install certbot-dns-cloudflare \
 | 
			
		||||
	&& apk add python3 certbot jq \
 | 
			
		||||
	&& python3 -m ensurepip \
 | 
			
		||||
	&& rm -rf /var/cache/apk/*
 | 
			
		||||
 | 
			
		||||
ENV NPM_BUILD_VERSION="${BUILD_VERSION}" NPM_BUILD_COMMIT="${BUILD_COMMIT}" NPM_BUILD_DATE="${BUILD_DATE}"
 | 
			
		||||
@@ -34,6 +34,7 @@ EXPOSE 443
 | 
			
		||||
COPY docker/rootfs      /
 | 
			
		||||
ADD backend             /app
 | 
			
		||||
ADD frontend/dist       /app/frontend
 | 
			
		||||
COPY global							/app/global
 | 
			
		||||
 | 
			
		||||
WORKDIR /app
 | 
			
		||||
RUN yarn install
 | 
			
		||||
 
 | 
			
		||||
@@ -7,8 +7,8 @@ ENV S6_FIX_ATTRS_HIDDEN=1
 | 
			
		||||
 | 
			
		||||
RUN echo "fs.file-max = 65535" > /etc/sysctl.conf \
 | 
			
		||||
	&& apk update \
 | 
			
		||||
	&& apk add python2 py-pip certbot jq \
 | 
			
		||||
	&& pip install certbot-dns-cloudflare \
 | 
			
		||||
	&& apk add python3 certbot jq \
 | 
			
		||||
	&& python3 -m ensurepip \
 | 
			
		||||
	&& rm -rf /var/cache/apk/*
 | 
			
		||||
 | 
			
		||||
# Task
 | 
			
		||||
 
 | 
			
		||||
@@ -11,6 +11,8 @@ services:
 | 
			
		||||
      - 3080:80
 | 
			
		||||
      - 3081:81
 | 
			
		||||
      - 3443:443
 | 
			
		||||
    networks:
 | 
			
		||||
      - nginx_proxy_manager
 | 
			
		||||
    environment:
 | 
			
		||||
      - NODE_ENV=development
 | 
			
		||||
      - FORCE_COLOR=1
 | 
			
		||||
@@ -19,13 +21,17 @@ services:
 | 
			
		||||
    volumes:
 | 
			
		||||
      - npm_data:/data
 | 
			
		||||
      - le_data:/etc/letsencrypt
 | 
			
		||||
      - ..:/app
 | 
			
		||||
      - ../backend:/app
 | 
			
		||||
      - ../frontend:/app/frontend
 | 
			
		||||
      - ../global:/app/global
 | 
			
		||||
    depends_on:
 | 
			
		||||
      - db
 | 
			
		||||
    working_dir: /app
 | 
			
		||||
 | 
			
		||||
  db:
 | 
			
		||||
    image: jc21/mariadb-aria
 | 
			
		||||
    networks:
 | 
			
		||||
      - nginx_proxy_manager
 | 
			
		||||
    environment:
 | 
			
		||||
      MYSQL_ROOT_PASSWORD: "npm"
 | 
			
		||||
      MYSQL_DATABASE: "npm"
 | 
			
		||||
@@ -38,6 +44,8 @@ services:
 | 
			
		||||
    image: 'swaggerapi/swagger-ui:latest'
 | 
			
		||||
    ports:
 | 
			
		||||
      - 3001:80
 | 
			
		||||
    networks:
 | 
			
		||||
      - nginx_proxy_manager
 | 
			
		||||
    environment:
 | 
			
		||||
      URL: "http://127.0.0.1:3081/api/schema"
 | 
			
		||||
      PORT: '80'
 | 
			
		||||
@@ -48,3 +56,6 @@ volumes:
 | 
			
		||||
  npm_data:
 | 
			
		||||
  le_data:
 | 
			
		||||
  db_data:
 | 
			
		||||
 | 
			
		||||
networks:
 | 
			
		||||
  nginx_proxy_manager:
 | 
			
		||||
 
 | 
			
		||||
@@ -17,6 +17,9 @@ server {
 | 
			
		||||
		proxy_set_header      X-Forwarded-Proto  $scheme;
 | 
			
		||||
		proxy_set_header      X-Forwarded-For    $remote_addr;
 | 
			
		||||
		proxy_pass            http://127.0.0.1:3000/;
 | 
			
		||||
 | 
			
		||||
		proxy_read_timeout 15m;
 | 
			
		||||
		proxy_send_timeout 15m;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	location / {
 | 
			
		||||
 
 | 
			
		||||
@@ -18,6 +18,9 @@ server {
 | 
			
		||||
		proxy_set_header      X-Forwarded-Proto  $scheme;
 | 
			
		||||
		proxy_set_header      X-Forwarded-For    $remote_addr;
 | 
			
		||||
		proxy_pass            http://127.0.0.1:3000/;
 | 
			
		||||
 | 
			
		||||
		proxy_read_timeout 15m;
 | 
			
		||||
		proxy_send_timeout 15m;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	location / {
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@ mkdir -p /data/letsencrypt-acme-challenge
 | 
			
		||||
cd /app || echo
 | 
			
		||||
 | 
			
		||||
if [ "$DEVELOPMENT" == "true" ]; then
 | 
			
		||||
	cd /app/backend || exit 1
 | 
			
		||||
	cd /app || exit 1
 | 
			
		||||
	yarn install
 | 
			
		||||
	node --max_old_space_size=250 --abort_on_uncaught_exception node_modules/nodemon/bin/nodemon.js
 | 
			
		||||
else
 | 
			
		||||
 
 | 
			
		||||
@@ -53,7 +53,7 @@ function fetch(verb, path, data, options) {
 | 
			
		||||
            contentType: options.contentType || 'application/json; charset=UTF-8',
 | 
			
		||||
            processData: options.processData || true,
 | 
			
		||||
            crossDomain: true,
 | 
			
		||||
            timeout:     options.timeout ? options.timeout : 30000,
 | 
			
		||||
            timeout:     options.timeout ? options.timeout : 180000,
 | 
			
		||||
            xhrFields:   {
 | 
			
		||||
                withCredentials: true
 | 
			
		||||
            },
 | 
			
		||||
@@ -587,7 +587,8 @@ module.exports = {
 | 
			
		||||
             * @param {Object}  data
 | 
			
		||||
             */
 | 
			
		||||
            create: function (data) {
 | 
			
		||||
                return fetch('post', 'nginx/certificates', data);
 | 
			
		||||
                const timeout = 180000 + (data.meta.propagation_seconds ? Number(data.meta.propagation_seconds) * 1000 : 0);
 | 
			
		||||
                return fetch('post', 'nginx/certificates', data, {timeout});
 | 
			
		||||
            },
 | 
			
		||||
 | 
			
		||||
            /**
 | 
			
		||||
@@ -630,8 +631,8 @@ module.exports = {
 | 
			
		||||
             * @param   {Number}  id
 | 
			
		||||
             * @returns {Promise}
 | 
			
		||||
             */
 | 
			
		||||
            renew: function (id) {
 | 
			
		||||
                return fetch('post', 'nginx/certificates/' + id + '/renew');
 | 
			
		||||
            renew: function (id, timeout = 180000) {
 | 
			
		||||
                return fetch('post', 'nginx/certificates/' + id + '/renew', undefined, {timeout});
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +1,20 @@
 | 
			
		||||
<div class="modal-content">
 | 
			
		||||
    <div class="modal-header">
 | 
			
		||||
        <h5 class="modal-title"><%- i18n('certificates', 'form-title', {provider: provider}) %></h5>
 | 
			
		||||
        <button type="button" class="close cancel" aria-label="Close" data-dismiss="modal"> </button>
 | 
			
		||||
        <button type="button" class="close cancel non-loader-content" aria-label="Close" data-dismiss="modal"> </button>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="modal-body">
 | 
			
		||||
        <form>
 | 
			
		||||
        <div class="text-center loader-content">
 | 
			
		||||
            <div class="loader mx-auto my-6"></div>
 | 
			
		||||
            <p><%- i18n('ssl', 'obtaining-certificate-info') %></p>
 | 
			
		||||
        </div>
 | 
			
		||||
        <form class="non-loader-content">
 | 
			
		||||
            <div class="row">
 | 
			
		||||
                <% if (provider === 'letsencrypt') { %>
 | 
			
		||||
                    <div class="col-sm-12 col-md-12">
 | 
			
		||||
                        <div class="alert alert-danger" id="le-error-info" role="alert"></div>
 | 
			
		||||
                    </div>
 | 
			
		||||
 | 
			
		||||
                    <div class="col-sm-12 col-md-12">
 | 
			
		||||
                        <div class="form-group">
 | 
			
		||||
                            <label class="form-label"><%- i18n('all-hosts', 'domain-names') %> <span class="form-required">*</span></label>
 | 
			
		||||
@@ -21,22 +29,97 @@
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
 | 
			
		||||
                    <!-- CloudFlare -->
 | 
			
		||||
                    <!-- DNS challenge -->
 | 
			
		||||
                    <div class="col-sm-12 col-md-12">
 | 
			
		||||
                        <div class="form-group">
 | 
			
		||||
                            <label class="custom-switch">
 | 
			
		||||
                                <input type="checkbox" class="custom-switch-input" name="meta[cloudflare_use]" value="1">
 | 
			
		||||
                                <input 
 | 
			
		||||
                                    type="checkbox" 
 | 
			
		||||
                                    class="custom-switch-input" 
 | 
			
		||||
                                    name="meta[dns_challenge]" 
 | 
			
		||||
                                    value="1" 
 | 
			
		||||
                                    <%- getUseDnsChallenge() ? 'checked' : '' %>
 | 
			
		||||
                                >
 | 
			
		||||
                                <span class="custom-switch-indicator"></span>
 | 
			
		||||
                                <span class="custom-switch-description"><%= i18n('ssl', 'use-cloudflare') %></span>
 | 
			
		||||
                                <span class="custom-switch-description"><%= i18n('ssl', 'dns-challenge') %></span>
 | 
			
		||||
                            </label>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="col-sm-12 col-md-12 cloudflare">
 | 
			
		||||
                    <div class="col-sm-12 col-md-12">
 | 
			
		||||
                        <fieldset class="form-fieldset dns-challenge">
 | 
			
		||||
                            <div class="text-red mb-4"><i class="fe fe-alert-triangle"></i> <%= i18n('ssl', 'certbot-warning') %></div>
 | 
			
		||||
 | 
			
		||||
                            <!-- Certbot DNS plugin selection -->
 | 
			
		||||
                            <div class="row">
 | 
			
		||||
                                <div class="col-sm-12 col-md-12">
 | 
			
		||||
                                    <div class="form-group">
 | 
			
		||||
                            <label class="form-label">CloudFlare DNS API Token  <span class="form-required">*</span></label>
 | 
			
		||||
                            <input type="text" name="meta[cloudflare_token]" class="form-control" id="cloudflare_token">
 | 
			
		||||
                                        <label class="form-label"><%- i18n('ssl', 'dns-provider') %> <span class="form-required">*</span></label>
 | 
			
		||||
                                        <select 
 | 
			
		||||
                                            name="meta[dns_provider]" 
 | 
			
		||||
                                            id="dns_provider"
 | 
			
		||||
                                            class="form-control custom-select"
 | 
			
		||||
                                        >
 | 
			
		||||
                                            <option 
 | 
			
		||||
                                                value="" 
 | 
			
		||||
                                                disabled 
 | 
			
		||||
                                                hidden
 | 
			
		||||
                                                <%- getDnsProvider() === null ? 'selected' : '' %>
 | 
			
		||||
                                            >Please Choose...</option>
 | 
			
		||||
                                            <% _.each(dns_plugins, function(plugin_info, plugin_name){ %>
 | 
			
		||||
                                            <option 
 | 
			
		||||
                                                value="<%- plugin_name %>"
 | 
			
		||||
                                                <%- getDnsProvider() === plugin_name ? 'selected' : '' %>
 | 
			
		||||
                                            ><%- plugin_info.display_name %></option>
 | 
			
		||||
                                            <% }); %>
 | 
			
		||||
                                        </select>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                </div>
 | 
			
		||||
                            </div>
 | 
			
		||||
 | 
			
		||||
                            <!-- Certbot credentials file content -->
 | 
			
		||||
                            <div class="row credentials-file-content">
 | 
			
		||||
                                <div class="col-sm-12 col-md-12">
 | 
			
		||||
                                    <div class="form-group">
 | 
			
		||||
                                        <label class="form-label"><%- i18n('ssl', 'credentials-file-content') %> <span class="form-required">*</span></label>
 | 
			
		||||
                                        <textarea 
 | 
			
		||||
                                            name="meta[dns_provider_credentials]" 
 | 
			
		||||
                                            class="form-control text-monospace" 
 | 
			
		||||
                                            id="dns_provider_credentials" 
 | 
			
		||||
                                        ><%- getDnsProviderCredentials() %></textarea>
 | 
			
		||||
                                        <div class="text-secondary small">
 | 
			
		||||
                                            <i class="fe fe-info"></i> 
 | 
			
		||||
                                            <%= i18n('ssl', 'credentials-file-content-info') %>
 | 
			
		||||
                                        </div>
 | 
			
		||||
                                        <div class="text-red small">
 | 
			
		||||
                                            <i class="fe fe-alert-triangle"></i> 
 | 
			
		||||
                                            <%= i18n('ssl', 'stored-as-plaintext-info') %>
 | 
			
		||||
                                        </div>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                </div>
 | 
			
		||||
                            </div>
 | 
			
		||||
 | 
			
		||||
                            <!-- DNS propagation delay -->
 | 
			
		||||
                            <div class="row">
 | 
			
		||||
                                <div class="col-sm-12 col-md-12">
 | 
			
		||||
                                    <div class="form-group mb-0">
 | 
			
		||||
                                        <label class="form-label"><%- i18n('ssl', 'propagation-seconds') %></label>
 | 
			
		||||
                                        <input 
 | 
			
		||||
                                            type="number"
 | 
			
		||||
                                            min="0"
 | 
			
		||||
                                            name="meta[propagation_seconds]" 
 | 
			
		||||
                                            class="form-control" 
 | 
			
		||||
                                            id="propagation_seconds" 
 | 
			
		||||
                                            value="<%- getPropagationSeconds() %>"
 | 
			
		||||
                                        >
 | 
			
		||||
                                        <div class="text-secondary small">
 | 
			
		||||
                                            <i class="fe fe-info"></i> 
 | 
			
		||||
                                            <%= i18n('ssl', 'propagation-seconds-info') %>
 | 
			
		||||
                                        </div>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                </div>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </fieldset>
 | 
			
		||||
                    </div>
 | 
			
		||||
 | 
			
		||||
                    <div class="col-sm-12 col-md-12">
 | 
			
		||||
                        <div class="form-group">
 | 
			
		||||
@@ -87,7 +170,7 @@
 | 
			
		||||
            </div>
 | 
			
		||||
        </form>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="modal-footer">
 | 
			
		||||
    <div class="modal-footer non-loader-content">
 | 
			
		||||
        <button type="button" class="btn btn-secondary cancel" data-dismiss="modal"><%- i18n('str', 'cancel') %></button>
 | 
			
		||||
        <button type="button" class="btn btn-teal save"><%- i18n('str', 'save') %></button>
 | 
			
		||||
    </div>
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,8 @@ const Mn               = require('backbone.marionette');
 | 
			
		||||
const App              = require('../../main');
 | 
			
		||||
const CertificateModel = require('../../../models/certificate');
 | 
			
		||||
const template         = require('./form.ejs');
 | 
			
		||||
const i18n             = require('../../i18n');
 | 
			
		||||
const dns_providers    = require('../../../../../global/certbot-dns-plugins');
 | 
			
		||||
 | 
			
		||||
require('jquery-serializejson');
 | 
			
		||||
require('selectize');
 | 
			
		||||
@@ -14,6 +16,9 @@ module.exports = Mn.View.extend({
 | 
			
		||||
 | 
			
		||||
    ui: {
 | 
			
		||||
        form:                                 'form',
 | 
			
		||||
        loader_content:                       '.loader-content',
 | 
			
		||||
        non_loader_content:                   '.non-loader-content',
 | 
			
		||||
        le_error_info:                        '#le-error-info',
 | 
			
		||||
        domain_names:                         'input[name="domain_names"]',
 | 
			
		||||
        buttons:                              '.modal-footer button',
 | 
			
		||||
        cancel:                               'button.cancel',
 | 
			
		||||
@@ -21,27 +26,49 @@ module.exports = Mn.View.extend({
 | 
			
		||||
        other_certificate:                    '#other_certificate',
 | 
			
		||||
        other_certificate_label:              '#other_certificate_label',
 | 
			
		||||
        other_certificate_key:                '#other_certificate_key',
 | 
			
		||||
        cloudflare_switch:                    'input[name="meta[cloudflare_use]"]',
 | 
			
		||||
        cloudflare_token:                     'input[name="meta[cloudflare_token]"',
 | 
			
		||||
        cloudflare:                           '.cloudflare',
 | 
			
		||||
        dns_challenge_switch:                 'input[name="meta[dns_challenge]"]',
 | 
			
		||||
        dns_challenge_content:                '.dns-challenge',
 | 
			
		||||
        dns_provider:                         'select[name="meta[dns_provider]"]',
 | 
			
		||||
        credentials_file_content:             '.credentials-file-content',
 | 
			
		||||
        dns_provider_credentials:             'textarea[name="meta[dns_provider_credentials]"]',
 | 
			
		||||
        propagation_seconds:                  'input[name="meta[propagation_seconds]"]',
 | 
			
		||||
        other_certificate_key_label:          '#other_certificate_key_label',
 | 
			
		||||
        other_intermediate_certificate:       '#other_intermediate_certificate',
 | 
			
		||||
        other_intermediate_certificate_label: '#other_intermediate_certificate_label'
 | 
			
		||||
    },
 | 
			
		||||
    
 | 
			
		||||
    events: {
 | 
			
		||||
        'change @ui.cloudflare_switch': function() {
 | 
			
		||||
            let checked = this.ui.cloudflare_switch.prop('checked');
 | 
			
		||||
        'change @ui.dns_challenge_switch': function () {
 | 
			
		||||
            const checked = this.ui.dns_challenge_switch.prop('checked');
 | 
			
		||||
            if (checked) {
 | 
			
		||||
                this.ui.cloudflare_token.prop('required', 'required');
 | 
			
		||||
                this.ui.cloudflare.show();
 | 
			
		||||
                this.ui.dns_provider.prop('required', 'required');
 | 
			
		||||
                const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value;
 | 
			
		||||
                if(selected_provider != '' && dns_providers[selected_provider].credentials !== false){
 | 
			
		||||
                    this.ui.dns_provider_credentials.prop('required', 'required');
 | 
			
		||||
                }
 | 
			
		||||
                this.ui.dns_challenge_content.show();
 | 
			
		||||
            } else {
 | 
			
		||||
                this.ui.cloudflare_token.prop('required', false);
 | 
			
		||||
                this.ui.cloudflare.hide();                
 | 
			
		||||
                this.ui.dns_provider.prop('required', false);
 | 
			
		||||
                this.ui.dns_provider_credentials.prop('required', false);
 | 
			
		||||
                this.ui.dns_challenge_content.hide();                
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        'change @ui.dns_provider': function () {
 | 
			
		||||
            const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value;
 | 
			
		||||
            if (selected_provider != '' && dns_providers[selected_provider].credentials !== false) {
 | 
			
		||||
                this.ui.dns_provider_credentials.prop('required', 'required');
 | 
			
		||||
                this.ui.dns_provider_credentials[0].value = dns_providers[selected_provider].credentials;
 | 
			
		||||
                this.ui.credentials_file_content.show();
 | 
			
		||||
            } else {
 | 
			
		||||
                this.ui.dns_provider_credentials.prop('required', false);
 | 
			
		||||
                this.ui.credentials_file_content.hide();                
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        
 | 
			
		||||
        'click @ui.save': function (e) {
 | 
			
		||||
            e.preventDefault();
 | 
			
		||||
            this.ui.le_error_info.hide();
 | 
			
		||||
 | 
			
		||||
            if (!this.ui.form[0].checkValidity()) {
 | 
			
		||||
                $('<input type="submit">').hide().appendTo(this.ui.form).click().remove();
 | 
			
		||||
@@ -56,7 +83,7 @@ module.exports = Mn.View.extend({
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            let domain_err = false;
 | 
			
		||||
            if (!data.meta.cloudflare_use) {                
 | 
			
		||||
            if (!data.meta.dns_challenge) {                
 | 
			
		||||
                data.domain_names.split(',').map(function (name) {
 | 
			
		||||
                    if (name.match(/\*/im)) {
 | 
			
		||||
                        domain_err = true;
 | 
			
		||||
@@ -65,16 +92,21 @@ module.exports = Mn.View.extend({
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (domain_err) {
 | 
			
		||||
                alert('Cannot request Let\'s Encrypt Certificate for wildcard domains when not using CloudFlare DNS');
 | 
			
		||||
                alert(i18n('ssl', 'no-wildcard-without-dns'));
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Manipulate
 | 
			
		||||
            if (typeof data.meta !== 'undefined' && typeof data.meta.letsencrypt_agree !== 'undefined') {
 | 
			
		||||
                data.meta.letsencrypt_agree = !!data.meta.letsencrypt_agree;
 | 
			
		||||
            }
 | 
			
		||||
            if (typeof data.meta !== 'undefined' && typeof data.meta.cloudflare_use !== 'undefined') {
 | 
			
		||||
                data.meta.cloudflare_use = !!data.meta.cloudflare_use;
 | 
			
		||||
            if (typeof data.meta === 'undefined') data.meta = {};
 | 
			
		||||
            data.meta.letsencrypt_agree = data.meta.letsencrypt_agree == 1;
 | 
			
		||||
            data.meta.dns_challenge = data.meta.dns_challenge == 1;
 | 
			
		||||
 | 
			
		||||
            if(!data.meta.dns_challenge){
 | 
			
		||||
                data.meta.dns_provider = undefined;
 | 
			
		||||
                data.meta.dns_provider_credentials = undefined;
 | 
			
		||||
                data.meta.propagation_seconds = undefined;
 | 
			
		||||
            } else {
 | 
			
		||||
                if(data.meta.propagation_seconds === '') data.meta.propagation_seconds = undefined; 
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (typeof data.domain_names === 'string' && data.domain_names) {
 | 
			
		||||
@@ -116,8 +148,8 @@ module.exports = Mn.View.extend({
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            this.ui.buttons.prop('disabled', true).addClass('btn-disabled');
 | 
			
		||||
            this.ui.save.addClass('btn-loading');
 | 
			
		||||
            this.ui.loader_content.show();
 | 
			
		||||
            this.ui.non_loader_content.hide();
 | 
			
		||||
 | 
			
		||||
            // compile file data
 | 
			
		||||
            let form_data = new FormData();
 | 
			
		||||
@@ -154,9 +186,17 @@ module.exports = Mn.View.extend({
 | 
			
		||||
                    });
 | 
			
		||||
                })
 | 
			
		||||
                .catch(err => {
 | 
			
		||||
                    alert(err.message);
 | 
			
		||||
                    this.ui.buttons.prop('disabled', false).removeClass('btn-disabled');
 | 
			
		||||
                    this.ui.save.removeClass('btn-loading');
 | 
			
		||||
                    let more_info = '';
 | 
			
		||||
                    if(err.code === 500 && err.debug){
 | 
			
		||||
                        try{
 | 
			
		||||
                            more_info = JSON.parse(err.debug).debug.stack.join("\n");
 | 
			
		||||
                        } catch(e) {}
 | 
			
		||||
                    }
 | 
			
		||||
                    this.ui.le_error_info[0].innerHTML = `${err.message}${more_info !== '' ? `<pre class="mt-3">${more_info}</pre>`:''}`;
 | 
			
		||||
                    this.ui.le_error_info.show();
 | 
			
		||||
                    this.ui.le_error_info[0].scrollIntoView();
 | 
			
		||||
                    this.ui.loader_content.hide();
 | 
			
		||||
                    this.ui.non_loader_content.show();
 | 
			
		||||
                });
 | 
			
		||||
        },
 | 
			
		||||
        'change @ui.other_certificate_key': function(e){
 | 
			
		||||
@@ -176,14 +216,22 @@ module.exports = Mn.View.extend({
 | 
			
		||||
        getLetsencryptEmail: function () {
 | 
			
		||||
            return typeof this.meta.letsencrypt_email !== 'undefined' ? this.meta.letsencrypt_email : App.Cache.User.get('email');
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        getLetsencryptAgree: function () {
 | 
			
		||||
            return typeof this.meta.letsencrypt_agree !== 'undefined' ? this.meta.letsencrypt_agree : false;
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        getCloudflareUse: function () {
 | 
			
		||||
            return typeof this.meta.cloudflare_use !== 'undefined' ? this.meta.cloudflare_use : false;
 | 
			
		||||
        }
 | 
			
		||||
        getUseDnsChallenge: function () {
 | 
			
		||||
            return typeof this.meta.dns_challenge !== 'undefined' ? this.meta.dns_challenge : false;
 | 
			
		||||
        },
 | 
			
		||||
        getDnsProvider: function () {
 | 
			
		||||
            return typeof this.meta.dns_provider !== 'undefined' && this.meta.dns_provider != '' ? this.meta.dns_provider : null;
 | 
			
		||||
        },
 | 
			
		||||
        getDnsProviderCredentials: function () {
 | 
			
		||||
            return typeof this.meta.dns_provider_credentials !== 'undefined' ? this.meta.dns_provider_credentials : '';
 | 
			
		||||
        },
 | 
			
		||||
        getPropagationSeconds: function () {
 | 
			
		||||
            return typeof this.meta.propagation_seconds !== 'undefined' ? this.meta.propagation_seconds : '';
 | 
			
		||||
        },
 | 
			
		||||
        dns_plugins: dns_providers,
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    onRender: function () {
 | 
			
		||||
@@ -199,7 +247,10 @@ module.exports = Mn.View.extend({
 | 
			
		||||
            },
 | 
			
		||||
            createFilter: /^(?:[^.]+\.?)+[^.]$/
 | 
			
		||||
        });
 | 
			
		||||
        this.ui.cloudflare.hide();
 | 
			
		||||
        this.ui.dns_challenge_content.hide();
 | 
			
		||||
        this.ui.credentials_file_content.hide(); 
 | 
			
		||||
        this.ui.loader_content.hide();
 | 
			
		||||
        this.ui.le_error_info.hide();
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    initialize: function (options) {
 | 
			
		||||
 
 | 
			
		||||
@@ -28,7 +28,7 @@
 | 
			
		||||
    </div>
 | 
			
		||||
</td>
 | 
			
		||||
<td>
 | 
			
		||||
    <%- i18n('ssl', provider) %><% if (meta.cloudflare_use) { %> - CloudFlare DNS<% } %>
 | 
			
		||||
    <%- i18n('ssl', provider) %><% if (meta.dns_provider) { %> - <%- dns_providers[meta.dns_provider].display_name %><% } %>
 | 
			
		||||
</td>
 | 
			
		||||
<td class="<%- isExpired() ? 'text-danger' : '' %>">
 | 
			
		||||
    <%- formatDbDate(expires_on, 'Do MMMM YYYY, h:mm a') %>
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,7 @@ const Mn       = require('backbone.marionette');
 | 
			
		||||
const moment        = require('moment');
 | 
			
		||||
const App           = require('../../../main');
 | 
			
		||||
const template      = require('./item.ejs');
 | 
			
		||||
const dns_providers = require('../../../../../../global/certbot-dns-plugins')
 | 
			
		||||
 | 
			
		||||
module.exports = Mn.View.extend({
 | 
			
		||||
    template: template,
 | 
			
		||||
@@ -35,7 +36,8 @@ module.exports = Mn.View.extend({
 | 
			
		||||
        canManage: App.Cache.User.canManage('certificates'),
 | 
			
		||||
        isExpired: function () {
 | 
			
		||||
            return moment(this.expires_on).isBefore(moment());
 | 
			
		||||
        }
 | 
			
		||||
        },
 | 
			
		||||
        dns_providers: dns_providers
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    initialize: function () {
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,7 @@
 | 
			
		||||
        <button type="button" class="close cancel" aria-label="Close" data-dismiss="modal"> </button>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="modal-body has-tabs">
 | 
			
		||||
        <div class="alert alert-danger mb-0 rounded-0" id="le-error-info" role="alert"></div>
 | 
			
		||||
        <form>
 | 
			
		||||
            <ul class="nav nav-tabs" role="tablist">
 | 
			
		||||
                <li role="presentation" class="nav-item"><a href="#details" aria-controls="tab1" role="tab" data-toggle="tab" class="nav-link active"><i class="fe fe-zap"></i> <%- i18n('all-hosts', 'details') %></a></li>
 | 
			
		||||
@@ -73,22 +74,97 @@
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
 | 
			
		||||
                        <!-- CloudFlare -->
 | 
			
		||||
                        <!-- DNS challenge -->
 | 
			
		||||
                        <div class="col-sm-12 col-md-12 letsencrypt">
 | 
			
		||||
                            <div class="form-group">
 | 
			
		||||
                                <label class="custom-switch">
 | 
			
		||||
                                    <input type="checkbox" class="custom-switch-input" name="meta[cloudflare_use]" value="1">
 | 
			
		||||
                                    <input 
 | 
			
		||||
                                        type="checkbox" 
 | 
			
		||||
                                        class="custom-switch-input" 
 | 
			
		||||
                                        name="meta[dns_challenge]" 
 | 
			
		||||
                                        value="1" 
 | 
			
		||||
                                        <%- getUseDnsChallenge() ? 'checked' : '' %>
 | 
			
		||||
                                    >
 | 
			
		||||
                                    <span class="custom-switch-indicator"></span>
 | 
			
		||||
                                    <span class="custom-switch-description"><%= i18n('ssl', 'use-cloudflare') %></span>
 | 
			
		||||
                                    <span class="custom-switch-description"><%= i18n('ssl', 'dns-challenge') %></span>
 | 
			
		||||
                                </label>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <div class="col-sm-12 col-md-12 cloudflare letsencrypt">
 | 
			
		||||
                        <div class="col-sm-12 col-md-12 letsencrypt">
 | 
			
		||||
                            <fieldset class="form-fieldset dns-challenge">
 | 
			
		||||
                                <div class="text-red mb-4"><i class="fe fe-alert-triangle"></i> <%= i18n('ssl', 'certbot-warning') %></div>
 | 
			
		||||
 | 
			
		||||
                                <!-- Certbot DNS plugin selection -->
 | 
			
		||||
                                <div class="row">
 | 
			
		||||
                                    <div class="col-sm-12 col-md-12">
 | 
			
		||||
                                        <div class="form-group">
 | 
			
		||||
                                <label class="form-label">CloudFlare DNS API Token  <span class="form-required">*</span></label>
 | 
			
		||||
                                <input type="text" name="meta[cloudflare_token]" class="form-control" id="cloudflare_token">
 | 
			
		||||
                                            <label class="form-label"><%- i18n('ssl', 'dns-provider') %> <span class="form-required">*</span></label>
 | 
			
		||||
                                            <select 
 | 
			
		||||
                                                name="meta[dns_provider]" 
 | 
			
		||||
                                                id="dns_provider"
 | 
			
		||||
                                                class="form-control custom-select"
 | 
			
		||||
                                            >
 | 
			
		||||
                                                <option 
 | 
			
		||||
                                                    value="" 
 | 
			
		||||
                                                    disabled 
 | 
			
		||||
                                                    hidden
 | 
			
		||||
                                                    <%- getDnsProvider() === null ? 'selected' : '' %>
 | 
			
		||||
                                                >Please Choose...</option>
 | 
			
		||||
                                                <% _.each(dns_plugins, function(plugin_info, plugin_name){ %>
 | 
			
		||||
                                                <option 
 | 
			
		||||
                                                    value="<%- plugin_name %>"
 | 
			
		||||
                                                    <%- getDnsProvider() === plugin_name ? 'selected' : '' %>
 | 
			
		||||
                                                ><%- plugin_info.display_name %></option>
 | 
			
		||||
                                                <% }); %>
 | 
			
		||||
                                            </select>
 | 
			
		||||
                                        </div>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                </div>
 | 
			
		||||
 | 
			
		||||
                                <!-- Certbot credentials file content -->
 | 
			
		||||
                                <div class="row credentials-file-content">
 | 
			
		||||
                                    <div class="col-sm-12 col-md-12">
 | 
			
		||||
                                        <div class="form-group">
 | 
			
		||||
                                            <label class="form-label"><%- i18n('ssl', 'credentials-file-content') %> <span class="form-required">*</span></label>
 | 
			
		||||
                                            <textarea 
 | 
			
		||||
                                                name="meta[dns_provider_credentials]" 
 | 
			
		||||
                                                class="form-control text-monospace" 
 | 
			
		||||
                                                id="dns_provider_credentials" 
 | 
			
		||||
                                            ><%- getDnsProviderCredentials() %></textarea>
 | 
			
		||||
                                            <div class="text-secondary small">
 | 
			
		||||
                                                <i class="fe fe-info"></i> 
 | 
			
		||||
                                                <%= i18n('ssl', 'credentials-file-content-info') %>
 | 
			
		||||
                                            </div>
 | 
			
		||||
                                            <div class="text-red small">
 | 
			
		||||
                                                <i class="fe fe-alert-triangle"></i> 
 | 
			
		||||
                                                <%= i18n('ssl', 'stored-as-plaintext-info') %>
 | 
			
		||||
                                            </div>
 | 
			
		||||
                                        </div>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                </div>
 | 
			
		||||
 | 
			
		||||
                                <!-- DNS propagation delay -->
 | 
			
		||||
                                <div class="row">
 | 
			
		||||
                                    <div class="col-sm-12 col-md-12">
 | 
			
		||||
                                        <div class="form-group mb-0">
 | 
			
		||||
                                            <label class="form-label"><%- i18n('ssl', 'propagation-seconds') %></label>
 | 
			
		||||
                                            <input 
 | 
			
		||||
                                                type="number"
 | 
			
		||||
                                                min="0"
 | 
			
		||||
                                                name="meta[propagation_seconds]" 
 | 
			
		||||
                                                class="form-control" 
 | 
			
		||||
                                                id="propagation_seconds" 
 | 
			
		||||
                                                value="<%- getPropagationSeconds() %>"
 | 
			
		||||
                                            >
 | 
			
		||||
                                            <div class="text-secondary small">
 | 
			
		||||
                                                <i class="fe fe-info"></i> 
 | 
			
		||||
                                                <%= i18n('ssl', 'propagation-seconds-info') %>
 | 
			
		||||
                                            </div>
 | 
			
		||||
                                        </div>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                </div>
 | 
			
		||||
                            </fieldset>
 | 
			
		||||
                        </div>
 | 
			
		||||
 | 
			
		||||
                        <!-- Lets encrypt -->
 | 
			
		||||
                        <div class="col-sm-12 col-md-12 letsencrypt">
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,8 @@ const DeadHostModel        = require('../../../models/dead-host');
 | 
			
		||||
const template             = require('./form.ejs');
 | 
			
		||||
const certListItemTemplate = require('../certificates-list-item.ejs');
 | 
			
		||||
const Helpers              = require('../../../lib/helpers');
 | 
			
		||||
const i18n                 = require('../../i18n');
 | 
			
		||||
const dns_providers        = require('../../../../../global/certbot-dns-plugins');
 | 
			
		||||
 | 
			
		||||
require('jquery-serializejson');
 | 
			
		||||
require('selectize');
 | 
			
		||||
@@ -18,14 +20,18 @@ module.exports = Mn.View.extend({
 | 
			
		||||
        buttons:                  '.modal-footer button',
 | 
			
		||||
        cancel:                   'button.cancel',
 | 
			
		||||
        save:                     'button.save',
 | 
			
		||||
        le_error_info:            '#le-error-info',
 | 
			
		||||
        certificate_select:       'select[name="certificate_id"]',
 | 
			
		||||
        ssl_forced:               'input[name="ssl_forced"]',
 | 
			
		||||
        hsts_enabled:             'input[name="hsts_enabled"]',
 | 
			
		||||
        hsts_subdomains:          'input[name="hsts_subdomains"]',
 | 
			
		||||
        http2_support:            'input[name="http2_support"]',
 | 
			
		||||
        cloudflare_switch:  'input[name="meta[cloudflare_use]"]',
 | 
			
		||||
        cloudflare_token:   'input[name="meta[cloudflare_token]"',
 | 
			
		||||
        cloudflare:         '.cloudflare',
 | 
			
		||||
        dns_challenge_switch:     'input[name="meta[dns_challenge]"]',
 | 
			
		||||
        dns_challenge_content:    '.dns-challenge',
 | 
			
		||||
        dns_provider:             'select[name="meta[dns_provider]"]',
 | 
			
		||||
        credentials_file_content: '.credentials-file-content',
 | 
			
		||||
        dns_provider_credentials: 'textarea[name="meta[dns_provider_credentials]"]',
 | 
			
		||||
        propagation_seconds:      'input[name="meta[propagation_seconds]"]',
 | 
			
		||||
        letsencrypt:              '.letsencrypt'
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
@@ -34,7 +40,7 @@ module.exports = Mn.View.extend({
 | 
			
		||||
            let id = this.ui.certificate_select.val();
 | 
			
		||||
            if (id === 'new') {
 | 
			
		||||
                this.ui.letsencrypt.show().find('input').prop('disabled', false);
 | 
			
		||||
                this.ui.cloudflare.hide();
 | 
			
		||||
                this.ui.dns_challenge_content.hide();
 | 
			
		||||
            } else {
 | 
			
		||||
                this.ui.letsencrypt.hide().find('input').prop('disabled', true);
 | 
			
		||||
            }
 | 
			
		||||
@@ -81,19 +87,37 @@ module.exports = Mn.View.extend({
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        'change @ui.cloudflare_switch': function() {
 | 
			
		||||
            let checked = this.ui.cloudflare_switch.prop('checked');
 | 
			
		||||
        'change @ui.dns_challenge_switch': function () {
 | 
			
		||||
            const checked = this.ui.dns_challenge_switch.prop('checked');
 | 
			
		||||
            if (checked) {
 | 
			
		||||
                this.ui.cloudflare_token.prop('required', 'required');
 | 
			
		||||
                this.ui.cloudflare.show();
 | 
			
		||||
                this.ui.dns_provider.prop('required', 'required');
 | 
			
		||||
                const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value;
 | 
			
		||||
                if(selected_provider != '' && dns_providers[selected_provider].credentials !== false){
 | 
			
		||||
                    this.ui.dns_provider_credentials.prop('required', 'required');
 | 
			
		||||
                }
 | 
			
		||||
                this.ui.dns_challenge_content.show();
 | 
			
		||||
            } else {
 | 
			
		||||
                this.ui.cloudflare_token.prop('required', false);
 | 
			
		||||
                this.ui.cloudflare.hide();                
 | 
			
		||||
                this.ui.dns_provider.prop('required', false);
 | 
			
		||||
                this.ui.dns_provider_credentials.prop('required', false);
 | 
			
		||||
                this.ui.dns_challenge_content.hide();                
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        'change @ui.dns_provider': function () {
 | 
			
		||||
            const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value;
 | 
			
		||||
            if (selected_provider != '' && dns_providers[selected_provider].credentials !== false) {
 | 
			
		||||
                this.ui.dns_provider_credentials.prop('required', 'required');
 | 
			
		||||
                this.ui.dns_provider_credentials[0].value = dns_providers[selected_provider].credentials;
 | 
			
		||||
                this.ui.credentials_file_content.show();
 | 
			
		||||
            } else {
 | 
			
		||||
                this.ui.dns_provider_credentials.prop('required', false);
 | 
			
		||||
                this.ui.credentials_file_content.hide();                
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        'click @ui.save': function (e) {
 | 
			
		||||
            e.preventDefault();
 | 
			
		||||
            this.ui.le_error_info.hide();
 | 
			
		||||
 | 
			
		||||
            if (!this.ui.form[0].checkValidity()) {
 | 
			
		||||
                $('<input type="submit">').hide().appendTo(this.ui.form).click().remove();
 | 
			
		||||
@@ -109,6 +133,18 @@ module.exports = Mn.View.extend({
 | 
			
		||||
            data.http2_support      = !!data.http2_support;
 | 
			
		||||
            data.ssl_forced         = !!data.ssl_forced;
 | 
			
		||||
 | 
			
		||||
            if (typeof data.meta === 'undefined') data.meta = {};
 | 
			
		||||
            data.meta.letsencrypt_agree = data.meta.letsencrypt_agree == 1;
 | 
			
		||||
            data.meta.dns_challenge = data.meta.dns_challenge == 1;
 | 
			
		||||
            
 | 
			
		||||
            if(!data.meta.dns_challenge){
 | 
			
		||||
                data.meta.dns_provider = undefined;
 | 
			
		||||
                data.meta.dns_provider_credentials = undefined;
 | 
			
		||||
                data.meta.propagation_seconds = undefined;
 | 
			
		||||
            } else {
 | 
			
		||||
                if(data.meta.propagation_seconds === '') data.meta.propagation_seconds = undefined; 
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (typeof data.domain_names === 'string' && data.domain_names) {
 | 
			
		||||
                data.domain_names = data.domain_names.split(',');
 | 
			
		||||
            }
 | 
			
		||||
@@ -116,7 +152,7 @@ module.exports = Mn.View.extend({
 | 
			
		||||
            // Check for any domain names containing wildcards, which are not allowed with letsencrypt
 | 
			
		||||
            if (data.certificate_id === 'new') {                
 | 
			
		||||
                let domain_err = false;
 | 
			
		||||
                if (!data.meta.cloudflare_use) {
 | 
			
		||||
                if (!data.meta.dns_challenge) {
 | 
			
		||||
                    data.domain_names.map(function (name) {
 | 
			
		||||
                        if (name.match(/\*/im)) {
 | 
			
		||||
                            domain_err = true;
 | 
			
		||||
@@ -125,12 +161,9 @@ module.exports = Mn.View.extend({
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (domain_err) {
 | 
			
		||||
                    alert('Cannot request Let\'s Encrypt Certificate for wildcard domains without CloudFlare DNS.');
 | 
			
		||||
                    alert(i18n('ssl', 'no-wildcard-without-dns'));
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                data.meta.cloudflare_use = data.meta.cloudflare_use === '1';
 | 
			
		||||
                data.meta.letsencrypt_agree = data.meta.letsencrypt_agree === '1';                
 | 
			
		||||
            } else {
 | 
			
		||||
                data.certificate_id = parseInt(data.certificate_id, 10);
 | 
			
		||||
            }
 | 
			
		||||
@@ -159,7 +192,15 @@ module.exports = Mn.View.extend({
 | 
			
		||||
                    });
 | 
			
		||||
                })
 | 
			
		||||
                .catch(err => {
 | 
			
		||||
                    alert(err.message);
 | 
			
		||||
                    let more_info = '';
 | 
			
		||||
                    if(err.code === 500 && err.debug){
 | 
			
		||||
                        try{
 | 
			
		||||
                            more_info = JSON.parse(err.debug).debug.stack.join("\n");
 | 
			
		||||
                        } catch(e) {}
 | 
			
		||||
                    }
 | 
			
		||||
                    this.ui.le_error_info[0].innerHTML = `${err.message}${more_info !== '' ? `<pre class="mt-3">${more_info}</pre>`:''}`;
 | 
			
		||||
                    this.ui.le_error_info.show();
 | 
			
		||||
                    this.ui.le_error_info[0].scrollIntoView();
 | 
			
		||||
                    this.ui.buttons.prop('disabled', false).removeClass('btn-disabled');
 | 
			
		||||
                    this.ui.save.removeClass('btn-loading');
 | 
			
		||||
                });
 | 
			
		||||
@@ -169,7 +210,20 @@ module.exports = Mn.View.extend({
 | 
			
		||||
    templateContext: {
 | 
			
		||||
        getLetsencryptEmail: function () {
 | 
			
		||||
            return App.Cache.User.get('email');
 | 
			
		||||
        }
 | 
			
		||||
        },
 | 
			
		||||
        getUseDnsChallenge: function () {
 | 
			
		||||
            return typeof this.meta.dns_challenge !== 'undefined' ? this.meta.dns_challenge : false;
 | 
			
		||||
        },
 | 
			
		||||
        getDnsProvider: function () {
 | 
			
		||||
            return typeof this.meta.dns_provider !== 'undefined' && this.meta.dns_provider != '' ? this.meta.dns_provider : null;
 | 
			
		||||
        },
 | 
			
		||||
        getDnsProviderCredentials: function () {
 | 
			
		||||
            return typeof this.meta.dns_provider_credentials !== 'undefined' ? this.meta.dns_provider_credentials : '';
 | 
			
		||||
        },
 | 
			
		||||
        getPropagationSeconds: function () {
 | 
			
		||||
            return typeof this.meta.propagation_seconds !== 'undefined' ? this.meta.propagation_seconds : '';
 | 
			
		||||
        },
 | 
			
		||||
        dns_plugins: dns_providers,
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    onRender: function () {
 | 
			
		||||
@@ -190,6 +244,9 @@ module.exports = Mn.View.extend({
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // Certificates
 | 
			
		||||
        this.ui.le_error_info.hide();
 | 
			
		||||
        this.ui.dns_challenge_content.hide();
 | 
			
		||||
        this.ui.credentials_file_content.hide();
 | 
			
		||||
        this.ui.letsencrypt.hide();
 | 
			
		||||
        this.ui.certificate_select.selectize({
 | 
			
		||||
            valueField:       'id',
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,7 @@
 | 
			
		||||
        <button type="button" class="close cancel" aria-label="Close" data-dismiss="modal"> </button>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="modal-body has-tabs">
 | 
			
		||||
        <div class="alert alert-danger mb-0 rounded-0" id="le-error-info" role="alert"></div>
 | 
			
		||||
        <form>
 | 
			
		||||
            <ul class="nav nav-tabs" role="tablist">
 | 
			
		||||
                <li role="presentation" class="nav-item"><a href="#details" aria-controls="tab1" role="tab" data-toggle="tab" class="nav-link active"><i class="fe fe-zap"></i> <%- i18n('all-hosts', 'details') %></a></li>
 | 
			
		||||
@@ -141,22 +142,97 @@
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
 | 
			
		||||
                        <!-- CloudFlare -->
 | 
			
		||||
                        <!-- DNS challenge -->
 | 
			
		||||
                        <div class="col-sm-12 col-md-12 letsencrypt">
 | 
			
		||||
                            <div class="form-group">
 | 
			
		||||
                                <label class="custom-switch">
 | 
			
		||||
                                    <input type="checkbox" class="custom-switch-input" name="meta[cloudflare_use]" value="1">
 | 
			
		||||
                                    <input 
 | 
			
		||||
                                        type="checkbox" 
 | 
			
		||||
                                        class="custom-switch-input" 
 | 
			
		||||
                                        name="meta[dns_challenge]" 
 | 
			
		||||
                                        value="1" 
 | 
			
		||||
                                        <%- getUseDnsChallenge() ? 'checked' : '' %>
 | 
			
		||||
                                    >
 | 
			
		||||
                                    <span class="custom-switch-indicator"></span>
 | 
			
		||||
                                    <span class="custom-switch-description"><%= i18n('ssl', 'use-cloudflare') %></span>
 | 
			
		||||
                                    <span class="custom-switch-description"><%= i18n('ssl', 'dns-challenge') %></span>
 | 
			
		||||
                                </label>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <div class="col-sm-12 col-md-12 cloudflare letsencrypt">
 | 
			
		||||
                        <div class="col-sm-12 col-md-12 letsencrypt">
 | 
			
		||||
                            <fieldset class="form-fieldset dns-challenge">
 | 
			
		||||
                                <div class="text-red mb-4"><i class="fe fe-alert-triangle"></i> <%= i18n('ssl', 'certbot-warning') %></div>
 | 
			
		||||
 | 
			
		||||
                                <!-- Certbot DNS plugin selection -->
 | 
			
		||||
                                <div class="row">
 | 
			
		||||
                                    <div class="col-sm-12 col-md-12">
 | 
			
		||||
                                        <div class="form-group">
 | 
			
		||||
                                <label class="form-label">CloudFlare DNS API Token  <span class="form-required">*</span></label>
 | 
			
		||||
                                <input type="text" name="meta[cloudflare_token]" class="form-control" id="cloudflare_token">
 | 
			
		||||
                                            <label class="form-label"><%- i18n('ssl', 'dns-provider') %> <span class="form-required">*</span></label>
 | 
			
		||||
                                            <select 
 | 
			
		||||
                                                name="meta[dns_provider]" 
 | 
			
		||||
                                                id="dns_provider"
 | 
			
		||||
                                                class="form-control custom-select"
 | 
			
		||||
                                            >
 | 
			
		||||
                                                <option 
 | 
			
		||||
                                                    value="" 
 | 
			
		||||
                                                    disabled 
 | 
			
		||||
                                                    hidden
 | 
			
		||||
                                                    <%- getDnsProvider() === null ? 'selected' : '' %>
 | 
			
		||||
                                                >Please Choose...</option>
 | 
			
		||||
                                                <% _.each(dns_plugins, function(plugin_info, plugin_name){ %>
 | 
			
		||||
                                                <option 
 | 
			
		||||
                                                    value="<%- plugin_name %>"
 | 
			
		||||
                                                    <%- getDnsProvider() === plugin_name ? 'selected' : '' %>
 | 
			
		||||
                                                ><%- plugin_info.display_name %></option>
 | 
			
		||||
                                                <% }); %>
 | 
			
		||||
                                            </select>
 | 
			
		||||
                                        </div>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                </div>
 | 
			
		||||
 | 
			
		||||
                                <!-- Certbot credentials file content -->
 | 
			
		||||
                                <div class="row credentials-file-content">
 | 
			
		||||
                                    <div class="col-sm-12 col-md-12">
 | 
			
		||||
                                        <div class="form-group">
 | 
			
		||||
                                            <label class="form-label"><%- i18n('ssl', 'credentials-file-content') %> <span class="form-required">*</span></label>
 | 
			
		||||
                                            <textarea 
 | 
			
		||||
                                                name="meta[dns_provider_credentials]" 
 | 
			
		||||
                                                class="form-control text-monospace" 
 | 
			
		||||
                                                id="dns_provider_credentials" 
 | 
			
		||||
                                            ><%- getDnsProviderCredentials() %></textarea>
 | 
			
		||||
                                            <div class="text-secondary small">
 | 
			
		||||
                                                <i class="fe fe-info"></i> 
 | 
			
		||||
                                                <%= i18n('ssl', 'credentials-file-content-info') %>
 | 
			
		||||
                                            </div>
 | 
			
		||||
                                            <div class="text-red small">
 | 
			
		||||
                                                <i class="fe fe-alert-triangle"></i> 
 | 
			
		||||
                                                <%= i18n('ssl', 'stored-as-plaintext-info') %>
 | 
			
		||||
                                            </div>
 | 
			
		||||
                                        </div>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                </div>
 | 
			
		||||
 | 
			
		||||
                                <!-- DNS propagation delay -->
 | 
			
		||||
                                <div class="row">
 | 
			
		||||
                                    <div class="col-sm-12 col-md-12">
 | 
			
		||||
                                        <div class="form-group mb-0">
 | 
			
		||||
                                            <label class="form-label"><%- i18n('ssl', 'propagation-seconds') %></label>
 | 
			
		||||
                                            <input 
 | 
			
		||||
                                                type="number"
 | 
			
		||||
                                                min="0"
 | 
			
		||||
                                                name="meta[propagation_seconds]" 
 | 
			
		||||
                                                class="form-control" 
 | 
			
		||||
                                                id="propagation_seconds" 
 | 
			
		||||
                                                value="<%- getPropagationSeconds() %>"
 | 
			
		||||
                                            >
 | 
			
		||||
                                            <div class="text-secondary small">
 | 
			
		||||
                                                <i class="fe fe-info"></i> 
 | 
			
		||||
                                                <%= i18n('ssl', 'propagation-seconds-info') %>
 | 
			
		||||
                                            </div>
 | 
			
		||||
                                        </div>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                </div>
 | 
			
		||||
                            </fieldset>
 | 
			
		||||
                        </div>
 | 
			
		||||
 | 
			
		||||
                        <!-- Lets encrypt -->
 | 
			
		||||
                        <div class="col-sm-12 col-md-12 letsencrypt">
 | 
			
		||||
 
 | 
			
		||||
@@ -7,6 +7,8 @@ const certListItemTemplate   = require('../certificates-list-item.ejs');
 | 
			
		||||
const accessListItemTemplate = require('./access-list-item.ejs');
 | 
			
		||||
const CustomLocation         = require('./location');
 | 
			
		||||
const Helpers                = require('../../../lib/helpers');
 | 
			
		||||
const i18n                   = require('../../i18n');
 | 
			
		||||
const dns_providers          = require('../../../../../global/certbot-dns-plugins');
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
require('jquery-serializejson');
 | 
			
		||||
@@ -26,16 +28,20 @@ module.exports = Mn.View.extend({
 | 
			
		||||
        cancel:                   'button.cancel',
 | 
			
		||||
        save:                     'button.save',
 | 
			
		||||
        add_location_btn:         'button.add_location',
 | 
			
		||||
        locations_container:'.locations_container',
 | 
			
		||||
        locations_container:      '.locations_container',
 | 
			
		||||
        le_error_info:            '#le-error-info',
 | 
			
		||||
        certificate_select:       'select[name="certificate_id"]',
 | 
			
		||||
        access_list_select:       'select[name="access_list_id"]',
 | 
			
		||||
        ssl_forced:               'input[name="ssl_forced"]',
 | 
			
		||||
        hsts_enabled:             'input[name="hsts_enabled"]',
 | 
			
		||||
        hsts_subdomains:          'input[name="hsts_subdomains"]',
 | 
			
		||||
        http2_support:            'input[name="http2_support"]',
 | 
			
		||||
        cloudflare_switch:  'input[name="meta[cloudflare_use]"]',
 | 
			
		||||
        cloudflare_token:   'input[name="meta[cloudflare_token]"',
 | 
			
		||||
        cloudflare:         '.cloudflare',
 | 
			
		||||
        dns_challenge_switch:     'input[name="meta[dns_challenge]"]',
 | 
			
		||||
        dns_challenge_content:    '.dns-challenge',
 | 
			
		||||
        dns_provider:             'select[name="meta[dns_provider]"]',
 | 
			
		||||
        credentials_file_content: '.credentials-file-content',
 | 
			
		||||
        dns_provider_credentials: 'textarea[name="meta[dns_provider_credentials]"]',
 | 
			
		||||
        propagation_seconds:      'input[name="meta[propagation_seconds]"]',
 | 
			
		||||
        forward_scheme:           'select[name="forward_scheme"]',
 | 
			
		||||
        letsencrypt:              '.letsencrypt'
 | 
			
		||||
    },
 | 
			
		||||
@@ -49,7 +55,7 @@ module.exports = Mn.View.extend({
 | 
			
		||||
            let id = this.ui.certificate_select.val();
 | 
			
		||||
            if (id === 'new') {
 | 
			
		||||
                this.ui.letsencrypt.show().find('input').prop('disabled', false);
 | 
			
		||||
                this.ui.cloudflare.hide();
 | 
			
		||||
                this.ui.dns_challenge_content.hide();
 | 
			
		||||
            } else {
 | 
			
		||||
                this.ui.letsencrypt.hide().find('input').prop('disabled', true);
 | 
			
		||||
            }
 | 
			
		||||
@@ -95,14 +101,31 @@ module.exports = Mn.View.extend({
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        'change @ui.cloudflare_switch': function() {
 | 
			
		||||
            let checked = this.ui.cloudflare_switch.prop('checked');
 | 
			
		||||
        'change @ui.dns_challenge_switch': function () {
 | 
			
		||||
            const checked = this.ui.dns_challenge_switch.prop('checked');
 | 
			
		||||
            if (checked) {
 | 
			
		||||
                this.ui.cloudflare_token.prop('required', 'required');
 | 
			
		||||
                this.ui.cloudflare.show();
 | 
			
		||||
                this.ui.dns_provider.prop('required', 'required');
 | 
			
		||||
                const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value;
 | 
			
		||||
                if(selected_provider != '' && dns_providers[selected_provider].credentials !== false){
 | 
			
		||||
                    this.ui.dns_provider_credentials.prop('required', 'required');
 | 
			
		||||
                }
 | 
			
		||||
                this.ui.dns_challenge_content.show();
 | 
			
		||||
            } else {
 | 
			
		||||
                this.ui.cloudflare_token.prop('required', false);
 | 
			
		||||
                this.ui.cloudflare.hide();                
 | 
			
		||||
                this.ui.dns_provider.prop('required', false);
 | 
			
		||||
                this.ui.dns_provider_credentials.prop('required', false);
 | 
			
		||||
                this.ui.dns_challenge_content.hide();                
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        'change @ui.dns_provider': function () {
 | 
			
		||||
            const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value;
 | 
			
		||||
            if (selected_provider != '' && dns_providers[selected_provider].credentials !== false) {
 | 
			
		||||
                this.ui.dns_provider_credentials.prop('required', 'required');
 | 
			
		||||
                this.ui.dns_provider_credentials[0].value = dns_providers[selected_provider].credentials;
 | 
			
		||||
                this.ui.credentials_file_content.show();
 | 
			
		||||
            } else {
 | 
			
		||||
                this.ui.dns_provider_credentials.prop('required', false);
 | 
			
		||||
                this.ui.credentials_file_content.hide();                
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
@@ -115,6 +138,7 @@ module.exports = Mn.View.extend({
 | 
			
		||||
 | 
			
		||||
        'click @ui.save': function (e) {
 | 
			
		||||
            e.preventDefault();
 | 
			
		||||
            this.ui.le_error_info.hide();
 | 
			
		||||
 | 
			
		||||
            if (!this.ui.form[0].checkValidity()) {
 | 
			
		||||
                $('<input type="submit">').hide().appendTo(this.ui.form).click().remove();
 | 
			
		||||
@@ -144,6 +168,18 @@ module.exports = Mn.View.extend({
 | 
			
		||||
            data.hsts_subdomains         = !!data.hsts_subdomains;
 | 
			
		||||
            data.ssl_forced              = !!data.ssl_forced;
 | 
			
		||||
            
 | 
			
		||||
            if (typeof data.meta === 'undefined') data.meta = {};
 | 
			
		||||
            data.meta.letsencrypt_agree = data.meta.letsencrypt_agree == 1;
 | 
			
		||||
            data.meta.dns_challenge = data.meta.dns_challenge == 1;
 | 
			
		||||
            
 | 
			
		||||
            if(!data.meta.dns_challenge){
 | 
			
		||||
                data.meta.dns_provider = undefined;
 | 
			
		||||
                data.meta.dns_provider_credentials = undefined;
 | 
			
		||||
                data.meta.propagation_seconds = undefined;
 | 
			
		||||
            } else {
 | 
			
		||||
                if(data.meta.propagation_seconds === '') data.meta.propagation_seconds = undefined; 
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (typeof data.domain_names === 'string' && data.domain_names) {
 | 
			
		||||
                data.domain_names = data.domain_names.split(',');
 | 
			
		||||
            }
 | 
			
		||||
@@ -151,7 +187,7 @@ module.exports = Mn.View.extend({
 | 
			
		||||
            // Check for any domain names containing wildcards, which are not allowed with letsencrypt
 | 
			
		||||
            if (data.certificate_id === 'new') {                
 | 
			
		||||
                let domain_err = false;
 | 
			
		||||
                if (!data.meta.cloudflare_use) {
 | 
			
		||||
                if (!data.meta.dns_challenge) {
 | 
			
		||||
                    data.domain_names.map(function (name) {
 | 
			
		||||
                        if (name.match(/\*/im)) {
 | 
			
		||||
                            domain_err = true;
 | 
			
		||||
@@ -160,12 +196,9 @@ module.exports = Mn.View.extend({
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (domain_err) {
 | 
			
		||||
                    alert('Cannot request Let\'s Encrypt Certificate for wildcard domains without CloudFlare DNS.');
 | 
			
		||||
                    alert(i18n('ssl', 'no-wildcard-without-dns'));
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                data.meta.cloudflare_use = data.meta.cloudflare_use === '1';
 | 
			
		||||
                data.meta.letsencrypt_agree = data.meta.letsencrypt_agree === '1';                
 | 
			
		||||
            } else {
 | 
			
		||||
                data.certificate_id = parseInt(data.certificate_id, 10);
 | 
			
		||||
            }
 | 
			
		||||
@@ -194,7 +227,15 @@ module.exports = Mn.View.extend({
 | 
			
		||||
                    });
 | 
			
		||||
                })
 | 
			
		||||
                .catch(err => {
 | 
			
		||||
                    alert(err.message);
 | 
			
		||||
                    let more_info = '';
 | 
			
		||||
                    if(err.code === 500 && err.debug){
 | 
			
		||||
                        try{
 | 
			
		||||
                            more_info = JSON.parse(err.debug).debug.stack.join("\n");
 | 
			
		||||
                        } catch(e) {}
 | 
			
		||||
                    }
 | 
			
		||||
                    this.ui.le_error_info[0].innerHTML = `${err.message}${more_info !== '' ? `<pre class="mt-3">${more_info}</pre>`:''}`;
 | 
			
		||||
                    this.ui.le_error_info.show();
 | 
			
		||||
                    this.ui.le_error_info[0].scrollIntoView();
 | 
			
		||||
                    this.ui.buttons.prop('disabled', false).removeClass('btn-disabled');
 | 
			
		||||
                    this.ui.save.removeClass('btn-loading');
 | 
			
		||||
                });
 | 
			
		||||
@@ -204,7 +245,20 @@ module.exports = Mn.View.extend({
 | 
			
		||||
    templateContext: {
 | 
			
		||||
        getLetsencryptEmail: function () {
 | 
			
		||||
            return App.Cache.User.get('email');
 | 
			
		||||
        }
 | 
			
		||||
        },
 | 
			
		||||
        getUseDnsChallenge: function () {
 | 
			
		||||
            return typeof this.meta.dns_challenge !== 'undefined' ? this.meta.dns_challenge : false;
 | 
			
		||||
        },
 | 
			
		||||
        getDnsProvider: function () {
 | 
			
		||||
            return typeof this.meta.dns_provider !== 'undefined' && this.meta.dns_provider != '' ? this.meta.dns_provider : null;
 | 
			
		||||
        },
 | 
			
		||||
        getDnsProviderCredentials: function () {
 | 
			
		||||
            return typeof this.meta.dns_provider_credentials !== 'undefined' ? this.meta.dns_provider_credentials : '';
 | 
			
		||||
        },
 | 
			
		||||
        getPropagationSeconds: function () {
 | 
			
		||||
            return typeof this.meta.propagation_seconds !== 'undefined' ? this.meta.propagation_seconds : '';
 | 
			
		||||
        },
 | 
			
		||||
        dns_plugins: dns_providers,
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    onRender: function () {
 | 
			
		||||
@@ -258,6 +312,9 @@ module.exports = Mn.View.extend({
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // Certificates
 | 
			
		||||
        this.ui.le_error_info.hide();
 | 
			
		||||
        this.ui.dns_challenge_content.hide();
 | 
			
		||||
        this.ui.credentials_file_content.hide();
 | 
			
		||||
        this.ui.letsencrypt.hide();
 | 
			
		||||
        this.ui.certificate_select.selectize({
 | 
			
		||||
            valueField:       'id',
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,7 @@
 | 
			
		||||
        <button type="button" class="close cancel" aria-label="Close" data-dismiss="modal"> </button>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="modal-body has-tabs">
 | 
			
		||||
        <div class="alert alert-danger mb-0 rounded-0" id="le-error-info" role="alert"></div>
 | 
			
		||||
        <form>
 | 
			
		||||
            <ul class="nav nav-tabs" role="tablist">
 | 
			
		||||
                <li role="presentation" class="nav-item"><a href="#details" aria-controls="tab1" role="tab" data-toggle="tab" class="nav-link active"><i class="fe fe-zap"></i> <%- i18n('all-hosts', 'details') %></a></li>
 | 
			
		||||
@@ -97,22 +98,97 @@
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
 | 
			
		||||
                        <!-- CloudFlare -->
 | 
			
		||||
                        <!-- DNS challenge -->
 | 
			
		||||
                        <div class="col-sm-12 col-md-12 letsencrypt">
 | 
			
		||||
                            <div class="form-group">
 | 
			
		||||
                                <label class="custom-switch">
 | 
			
		||||
                                    <input type="checkbox" class="custom-switch-input" name="meta[cloudflare_use]" value="1">
 | 
			
		||||
                                    <input 
 | 
			
		||||
                                        type="checkbox" 
 | 
			
		||||
                                        class="custom-switch-input" 
 | 
			
		||||
                                        name="meta[dns_challenge]" 
 | 
			
		||||
                                        value="1" 
 | 
			
		||||
                                        <%- getUseDnsChallenge() ? 'checked' : '' %>
 | 
			
		||||
                                    >
 | 
			
		||||
                                    <span class="custom-switch-indicator"></span>
 | 
			
		||||
                                    <span class="custom-switch-description"><%= i18n('ssl', 'use-cloudflare') %></span>
 | 
			
		||||
                                    <span class="custom-switch-description"><%= i18n('ssl', 'dns-challenge') %></span>
 | 
			
		||||
                                </label>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <div class="col-sm-12 col-md-12 cloudflare letsencrypt">
 | 
			
		||||
                        <div class="col-sm-12 col-md-12 letsencrypt">
 | 
			
		||||
                            <fieldset class="form-fieldset dns-challenge">
 | 
			
		||||
                                <div class="text-red mb-4"><i class="fe fe-alert-triangle"></i> <%= i18n('ssl', 'certbot-warning') %></div>
 | 
			
		||||
 | 
			
		||||
                                <!-- Certbot DNS plugin selection -->
 | 
			
		||||
                                <div class="row">
 | 
			
		||||
                                    <div class="col-sm-12 col-md-12">
 | 
			
		||||
                                        <div class="form-group">
 | 
			
		||||
                                <label class="form-label">CloudFlare DNS API Token  <span class="form-required">*</span></label>
 | 
			
		||||
                                <input type="text" name="meta[cloudflare_token]" class="form-control" id="cloudflare_token">
 | 
			
		||||
                                            <label class="form-label"><%- i18n('ssl', 'dns-provider') %> <span class="form-required">*</span></label>
 | 
			
		||||
                                            <select 
 | 
			
		||||
                                                name="meta[dns_provider]" 
 | 
			
		||||
                                                id="dns_provider"
 | 
			
		||||
                                                class="form-control custom-select"
 | 
			
		||||
                                            >
 | 
			
		||||
                                                <option 
 | 
			
		||||
                                                    value="" 
 | 
			
		||||
                                                    disabled 
 | 
			
		||||
                                                    hidden
 | 
			
		||||
                                                    <%- getDnsProvider() === null ? 'selected' : '' %>
 | 
			
		||||
                                                >Please Choose...</option>
 | 
			
		||||
                                                <% _.each(dns_plugins, function(plugin_info, plugin_name){ %>
 | 
			
		||||
                                                <option 
 | 
			
		||||
                                                    value="<%- plugin_name %>"
 | 
			
		||||
                                                    <%- getDnsProvider() === plugin_name ? 'selected' : '' %>
 | 
			
		||||
                                                ><%- plugin_info.display_name %></option>
 | 
			
		||||
                                                <% }); %>
 | 
			
		||||
                                            </select>
 | 
			
		||||
                                        </div>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                </div>
 | 
			
		||||
 | 
			
		||||
                                <!-- Certbot credentials file content -->
 | 
			
		||||
                                <div class="row credentials-file-content">
 | 
			
		||||
                                    <div class="col-sm-12 col-md-12">
 | 
			
		||||
                                        <div class="form-group">
 | 
			
		||||
                                            <label class="form-label"><%- i18n('ssl', 'credentials-file-content') %> <span class="form-required">*</span></label>
 | 
			
		||||
                                            <textarea 
 | 
			
		||||
                                                name="meta[dns_provider_credentials]" 
 | 
			
		||||
                                                class="form-control text-monospace" 
 | 
			
		||||
                                                id="dns_provider_credentials" 
 | 
			
		||||
                                            ><%- getDnsProviderCredentials() %></textarea>
 | 
			
		||||
                                            <div class="text-secondary small">
 | 
			
		||||
                                                <i class="fe fe-info"></i> 
 | 
			
		||||
                                                <%= i18n('ssl', 'credentials-file-content-info') %>
 | 
			
		||||
                                            </div>
 | 
			
		||||
                                            <div class="text-red small">
 | 
			
		||||
                                                <i class="fe fe-alert-triangle"></i> 
 | 
			
		||||
                                                <%= i18n('ssl', 'stored-as-plaintext-info') %>
 | 
			
		||||
                                            </div>
 | 
			
		||||
                                        </div>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                </div>
 | 
			
		||||
 | 
			
		||||
                                <!-- DNS propagation delay -->
 | 
			
		||||
                                <div class="row">
 | 
			
		||||
                                    <div class="col-sm-12 col-md-12">
 | 
			
		||||
                                        <div class="form-group mb-0">
 | 
			
		||||
                                            <label class="form-label"><%- i18n('ssl', 'propagation-seconds') %></label>
 | 
			
		||||
                                            <input 
 | 
			
		||||
                                                type="number"
 | 
			
		||||
                                                min="0"
 | 
			
		||||
                                                name="meta[propagation_seconds]" 
 | 
			
		||||
                                                class="form-control" 
 | 
			
		||||
                                                id="propagation_seconds" 
 | 
			
		||||
                                                value="<%- getPropagationSeconds() %>"
 | 
			
		||||
                                            >
 | 
			
		||||
                                            <div class="text-secondary small">
 | 
			
		||||
                                                <i class="fe fe-info"></i> 
 | 
			
		||||
                                                <%= i18n('ssl', 'propagation-seconds-info') %>
 | 
			
		||||
                                            </div>
 | 
			
		||||
                                        </div>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                </div>
 | 
			
		||||
                            </fieldset>
 | 
			
		||||
                        </div>
 | 
			
		||||
 | 
			
		||||
                        <!-- Lets encrypt -->
 | 
			
		||||
                        <div class="col-sm-12 col-md-12 letsencrypt">
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,9 @@ const RedirectionHostModel = require('../../../models/redirection-host');
 | 
			
		||||
const template             = require('./form.ejs');
 | 
			
		||||
const certListItemTemplate = require('../certificates-list-item.ejs');
 | 
			
		||||
const Helpers              = require('../../../lib/helpers');
 | 
			
		||||
const i18n                 = require('../../i18n');
 | 
			
		||||
const dns_providers        = require('../../../../../global/certbot-dns-plugins');
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
require('jquery-serializejson');
 | 
			
		||||
require('selectize');
 | 
			
		||||
@@ -18,14 +21,18 @@ module.exports = Mn.View.extend({
 | 
			
		||||
        buttons:                  '.modal-footer button',
 | 
			
		||||
        cancel:                   'button.cancel',
 | 
			
		||||
        save:                     'button.save',
 | 
			
		||||
        le_error_info:            '#le-error-info',
 | 
			
		||||
        certificate_select:       'select[name="certificate_id"]',
 | 
			
		||||
        ssl_forced:               'input[name="ssl_forced"]',
 | 
			
		||||
        hsts_enabled:             'input[name="hsts_enabled"]',
 | 
			
		||||
        hsts_subdomains:          'input[name="hsts_subdomains"]',
 | 
			
		||||
        http2_support:            'input[name="http2_support"]',
 | 
			
		||||
        cloudflare_switch:  'input[name="meta[cloudflare_use]"]',
 | 
			
		||||
        cloudflare_token:   'input[name="meta[cloudflare_token]"',
 | 
			
		||||
        cloudflare:         '.cloudflare',
 | 
			
		||||
        dns_challenge_switch:     'input[name="meta[dns_challenge]"]',
 | 
			
		||||
        dns_challenge_content:    '.dns-challenge',
 | 
			
		||||
        dns_provider:             'select[name="meta[dns_provider]"]',
 | 
			
		||||
        credentials_file_content: '.credentials-file-content',
 | 
			
		||||
        dns_provider_credentials: 'textarea[name="meta[dns_provider_credentials]"]',
 | 
			
		||||
        propagation_seconds:      'input[name="meta[propagation_seconds]"]',
 | 
			
		||||
        letsencrypt:              '.letsencrypt'
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
@@ -34,7 +41,7 @@ module.exports = Mn.View.extend({
 | 
			
		||||
            let id = this.ui.certificate_select.val();
 | 
			
		||||
            if (id === 'new') {
 | 
			
		||||
                this.ui.letsencrypt.show().find('input').prop('disabled', false);
 | 
			
		||||
                this.ui.cloudflare.hide();
 | 
			
		||||
                this.ui.dns_challenge_content.hide();
 | 
			
		||||
            } else {
 | 
			
		||||
                this.ui.letsencrypt.hide().find('input').prop('disabled', true);
 | 
			
		||||
            }
 | 
			
		||||
@@ -80,19 +87,37 @@ module.exports = Mn.View.extend({
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        'change @ui.cloudflare_switch': function() {
 | 
			
		||||
            let checked = this.ui.cloudflare_switch.prop('checked');
 | 
			
		||||
        'change @ui.dns_challenge_switch': function () {
 | 
			
		||||
            const checked = this.ui.dns_challenge_switch.prop('checked');
 | 
			
		||||
            if (checked) {
 | 
			
		||||
                this.ui.cloudflare_token.prop('required', 'required');
 | 
			
		||||
                this.ui.cloudflare.show();
 | 
			
		||||
                this.ui.dns_provider.prop('required', 'required');
 | 
			
		||||
                const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value;
 | 
			
		||||
                if(selected_provider != '' && dns_providers[selected_provider].credentials !== false){
 | 
			
		||||
                    this.ui.dns_provider_credentials.prop('required', 'required');
 | 
			
		||||
                }
 | 
			
		||||
                this.ui.dns_challenge_content.show();
 | 
			
		||||
            } else {
 | 
			
		||||
                this.ui.cloudflare_token.prop('required', false);
 | 
			
		||||
                this.ui.cloudflare.hide();                
 | 
			
		||||
                this.ui.dns_provider.prop('required', false);
 | 
			
		||||
                this.ui.dns_provider_credentials.prop('required', false);
 | 
			
		||||
                this.ui.dns_challenge_content.hide();                
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        'change @ui.dns_provider': function () {
 | 
			
		||||
            const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value;
 | 
			
		||||
            if (selected_provider != '' && dns_providers[selected_provider].credentials !== false) {
 | 
			
		||||
                this.ui.dns_provider_credentials.prop('required', 'required');
 | 
			
		||||
                this.ui.dns_provider_credentials[0].value = dns_providers[selected_provider].credentials;
 | 
			
		||||
                this.ui.credentials_file_content.show();
 | 
			
		||||
            } else {
 | 
			
		||||
                this.ui.dns_provider_credentials.prop('required', false);
 | 
			
		||||
                this.ui.credentials_file_content.hide();                
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        'click @ui.save': function (e) {
 | 
			
		||||
            e.preventDefault();
 | 
			
		||||
            this.ui.le_error_info.hide();
 | 
			
		||||
 | 
			
		||||
            if (!this.ui.form[0].checkValidity()) {
 | 
			
		||||
                $('<input type="submit">').hide().appendTo(this.ui.form).click().remove();
 | 
			
		||||
@@ -110,6 +135,18 @@ module.exports = Mn.View.extend({
 | 
			
		||||
            data.hsts_subdomains    = !!data.hsts_subdomains;
 | 
			
		||||
            data.ssl_forced         = !!data.ssl_forced;
 | 
			
		||||
            
 | 
			
		||||
            if (typeof data.meta === 'undefined') data.meta = {};
 | 
			
		||||
            data.meta.letsencrypt_agree = data.meta.letsencrypt_agree == 1;
 | 
			
		||||
            data.meta.dns_challenge = data.meta.dns_challenge == 1;
 | 
			
		||||
            
 | 
			
		||||
            if(!data.meta.dns_challenge){
 | 
			
		||||
                data.meta.dns_provider = undefined;
 | 
			
		||||
                data.meta.dns_provider_credentials = undefined;
 | 
			
		||||
                data.meta.propagation_seconds = undefined;
 | 
			
		||||
            } else {
 | 
			
		||||
                if(data.meta.propagation_seconds === '') data.meta.propagation_seconds = undefined; 
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (typeof data.domain_names === 'string' && data.domain_names) {
 | 
			
		||||
                data.domain_names = data.domain_names.split(',');
 | 
			
		||||
            }
 | 
			
		||||
@@ -117,7 +154,7 @@ module.exports = Mn.View.extend({
 | 
			
		||||
            // Check for any domain names containing wildcards, which are not allowed with letsencrypt
 | 
			
		||||
            if (data.certificate_id === 'new') {                
 | 
			
		||||
                let domain_err = false;
 | 
			
		||||
                if (!data.meta.cloudflare_use) {
 | 
			
		||||
                if (!data.meta.dns_challenge) {
 | 
			
		||||
                    data.domain_names.map(function (name) {
 | 
			
		||||
                        if (name.match(/\*/im)) {
 | 
			
		||||
                            domain_err = true;
 | 
			
		||||
@@ -126,12 +163,9 @@ module.exports = Mn.View.extend({
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (domain_err) {
 | 
			
		||||
                    alert('Cannot request Let\'s Encrypt Certificate for wildcard domains without CloudFlare DNS.');
 | 
			
		||||
                    alert(i18n('ssl', 'no-wildcard-without-dns'));
 | 
			
		||||
                    return;
 | 
			
		||||
                }             
 | 
			
		||||
 | 
			
		||||
                data.meta.cloudflare_use = data.meta.cloudflare_use === '1';
 | 
			
		||||
                data.meta.letsencrypt_agree = data.meta.letsencrypt_agree === '1';                
 | 
			
		||||
            } else {
 | 
			
		||||
                data.certificate_id = parseInt(data.certificate_id, 10);
 | 
			
		||||
            }
 | 
			
		||||
@@ -160,7 +194,15 @@ module.exports = Mn.View.extend({
 | 
			
		||||
                    });
 | 
			
		||||
                })
 | 
			
		||||
                .catch(err => {
 | 
			
		||||
                    alert(err.message);
 | 
			
		||||
                    let more_info = '';
 | 
			
		||||
                    if(err.code === 500 && err.debug){
 | 
			
		||||
                        try{
 | 
			
		||||
                            more_info = JSON.parse(err.debug).debug.stack.join("\n");
 | 
			
		||||
                        } catch(e) {}
 | 
			
		||||
                    }
 | 
			
		||||
                    this.ui.le_error_info[0].innerHTML = `${err.message}${more_info !== '' ? `<pre class="mt-3">${more_info}</pre>`:''}`;
 | 
			
		||||
                    this.ui.le_error_info.show();
 | 
			
		||||
                    this.ui.le_error_info[0].scrollIntoView();
 | 
			
		||||
                    this.ui.buttons.prop('disabled', false).removeClass('btn-disabled');
 | 
			
		||||
                    this.ui.save.removeClass('btn-loading');
 | 
			
		||||
                });
 | 
			
		||||
@@ -170,7 +212,20 @@ module.exports = Mn.View.extend({
 | 
			
		||||
    templateContext: {
 | 
			
		||||
        getLetsencryptEmail: function () {
 | 
			
		||||
            return App.Cache.User.get('email');
 | 
			
		||||
        }
 | 
			
		||||
        },
 | 
			
		||||
        getUseDnsChallenge: function () {
 | 
			
		||||
            return typeof this.meta.dns_challenge !== 'undefined' ? this.meta.dns_challenge : false;
 | 
			
		||||
        },
 | 
			
		||||
        getDnsProvider: function () {
 | 
			
		||||
            return typeof this.meta.dns_provider !== 'undefined' && this.meta.dns_provider != '' ? this.meta.dns_provider : null;
 | 
			
		||||
        },
 | 
			
		||||
        getDnsProviderCredentials: function () {
 | 
			
		||||
            return typeof this.meta.dns_provider_credentials !== 'undefined' ? this.meta.dns_provider_credentials : '';
 | 
			
		||||
        },
 | 
			
		||||
        getPropagationSeconds: function () {
 | 
			
		||||
            return typeof this.meta.propagation_seconds !== 'undefined' ? this.meta.propagation_seconds : '';
 | 
			
		||||
        },
 | 
			
		||||
        dns_plugins: dns_providers,
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    onRender: function () {
 | 
			
		||||
@@ -191,6 +246,9 @@ module.exports = Mn.View.extend({
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // Certificates
 | 
			
		||||
        this.ui.le_error_info.hide();
 | 
			
		||||
        this.ui.dns_challenge_content.hide();
 | 
			
		||||
        this.ui.credentials_file_content.hide();
 | 
			
		||||
        this.ui.letsencrypt.hide();
 | 
			
		||||
        this.ui.certificate_select.selectize({
 | 
			
		||||
            valueField:       'id',
 | 
			
		||||
 
 | 
			
		||||
@@ -102,7 +102,17 @@
 | 
			
		||||
      "letsencrypt-agree": "I Agree to the <a href=\"{url}\" target=\"_blank\">Let's Encrypt Terms of Service</a>",
 | 
			
		||||
      "delete-ssl": "The SSL certificates attached will NOT be removed, they will need to be removed manually.",
 | 
			
		||||
      "hosts-warning": "These domains must be already configured to point to this installation",
 | 
			
		||||
      "use-cloudflare": "Use CloudFlare DNS verification"      
 | 
			
		||||
      "no-wildcard-without-dns": "Cannot request Let's Encrypt Certificate for wildcard domains when not using DNS challenge",
 | 
			
		||||
      "dns-challenge": "Use a DNS Challenge",
 | 
			
		||||
      "certbot-warning": "This section requires some knowledge about Certbot and its DNS plugins. Please consult the respective plugins documentation.",
 | 
			
		||||
      "dns-provider": "DNS Provider",
 | 
			
		||||
      "please-choose": "Please Choose...",
 | 
			
		||||
      "credentials-file-content": "Credentials File Content",
 | 
			
		||||
      "credentials-file-content-info": "This plugin requires a configuration file containing an API token or other credentials to your provider",
 | 
			
		||||
      "stored-as-plaintext-info": "This data will be stored as plaintext in the database!",
 | 
			
		||||
      "propagation-seconds": "Propagation Seconds",
 | 
			
		||||
      "propagation-seconds-info": "Leave empty to use the plugins default value. Number of seconds to wait for DNS propagation.",
 | 
			
		||||
      "obtaining-certificate-info": "Obtaining certificate... This might take a few minutes."
 | 
			
		||||
    },
 | 
			
		||||
    "proxy-hosts": {
 | 
			
		||||
      "title": "Proxy Hosts",
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										251
									
								
								global/certbot-dns-plugins.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										251
									
								
								global/certbot-dns-plugins.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,251 @@
 | 
			
		||||
/**
 | 
			
		||||
 * This file contains info about available Certbot DNS plugins.
 | 
			
		||||
 * This only works for plugins which use the standard argument structure, so:
 | 
			
		||||
 * --authenticator <plugin-name> --<plugin-name>-credentials <FILE> --<plugin-name>-propagation-seconds <number>
 | 
			
		||||
 *
 | 
			
		||||
 * File Structure:
 | 
			
		||||
 *
 | 
			
		||||
 *  {
 | 
			
		||||
 *    cloudflare: {
 | 
			
		||||
 *      display_name: "Name displayed to the user",
 | 
			
		||||
 *      package_name: "Package name in PyPi repo",
 | 
			
		||||
 *      package_version: "Package version in PyPi repo",
 | 
			
		||||
 *      credentials: `Template of the credentials file`,
 | 
			
		||||
 *      full_plugin_name: "The full plugin name as used in the commandline with certbot, including prefixes, e.g. 'certbot-dns-njalla:dns-njalla'",
 | 
			
		||||
 *      credentials_file: Whether the plugin has a credentials file
 | 
			
		||||
 *    },
 | 
			
		||||
 *    ...
 | 
			
		||||
 *  }
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
  cloudflare: {
 | 
			
		||||
    display_name: "Cloudflare",
 | 
			
		||||
    package_name: "certbot-dns-cloudflare",
 | 
			
		||||
    package_version: "1.8.0",
 | 
			
		||||
    credentials: `# Cloudflare API token
 | 
			
		||||
dns_cloudflare_api_token = 0123456789abcdef0123456789abcdef01234567`,
 | 
			
		||||
    full_plugin_name: "dns-cloudflare",
 | 
			
		||||
  },
 | 
			
		||||
  //####################################################//
 | 
			
		||||
  cloudxns: {
 | 
			
		||||
    display_name: "CloudXNS",
 | 
			
		||||
    package_name: "certbot-dns-cloudxns",
 | 
			
		||||
    package_version: "1.8.0",
 | 
			
		||||
    credentials: `dns_cloudxns_api_key = 1234567890abcdef1234567890abcdef
 | 
			
		||||
dns_cloudxns_secret_key = 1122334455667788`,
 | 
			
		||||
    full_plugin_name: "dns-cloudxns",
 | 
			
		||||
  },
 | 
			
		||||
  //####################################################//
 | 
			
		||||
  corenetworks: {
 | 
			
		||||
    display_name: "Core Networks",
 | 
			
		||||
    package_name: "certbot-dns-corenetworks",
 | 
			
		||||
    package_version: "0.1.4",
 | 
			
		||||
    credentials: `certbot_dns_corenetworks:dns_corenetworks_username = asaHB12r
 | 
			
		||||
certbot_dns_corenetworks:dns_corenetworks_password = secure_password`,
 | 
			
		||||
    full_plugin_name: "certbot-dns-corenetworks:dns-corenetworks",
 | 
			
		||||
  },
 | 
			
		||||
  //####################################################//
 | 
			
		||||
  cpanel: {
 | 
			
		||||
    display_name: "cPanel",
 | 
			
		||||
    package_name: "certbot-dns-cpanel",
 | 
			
		||||
    package_version: "0.2.2",
 | 
			
		||||
    credentials: `certbot_dns_cpanel:cpanel_url = https://cpanel.example.com:2083
 | 
			
		||||
certbot_dns_cpanel:cpanel_username = user
 | 
			
		||||
certbot_dns_cpanel:cpanel_password = hunter2`,
 | 
			
		||||
    full_plugin_name: "certbot-dns-cpanel:cpanel",
 | 
			
		||||
  },
 | 
			
		||||
  //####################################################//
 | 
			
		||||
  digitalocean: {
 | 
			
		||||
    display_name: "DigitalOcean",
 | 
			
		||||
    package_name: "certbot-dns-digitalocean",
 | 
			
		||||
    package_version: "1.8.0",
 | 
			
		||||
    credentials: `dns_digitalocean_token = 0000111122223333444455556666777788889999aaaabbbbccccddddeeeeffff`,
 | 
			
		||||
    full_plugin_name: "dns-digitalocean",
 | 
			
		||||
  },
 | 
			
		||||
  //####################################################//
 | 
			
		||||
  directadmin: {
 | 
			
		||||
    display_name: "DirectAdmin",
 | 
			
		||||
    package_name: "certbot-dns-directadmin",
 | 
			
		||||
    package_version: "0.0.20",
 | 
			
		||||
    credentials: `directadmin_url = https://my.directadminserver.com:2222
 | 
			
		||||
directadmin_username = username
 | 
			
		||||
directadmin_password = aSuperStrongPassword`,
 | 
			
		||||
    full_plugin_name: "certbot-dns-directadmin:directadmin",
 | 
			
		||||
  },
 | 
			
		||||
  //####################################################//
 | 
			
		||||
  dnsimple: {
 | 
			
		||||
    display_name: "DNSimple",
 | 
			
		||||
    package_name: "certbot-dns-dnsimple",
 | 
			
		||||
    package_version: "1.8.0",
 | 
			
		||||
    credentials: `dns_dnsimple_token = MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAw`,
 | 
			
		||||
    full_plugin_name: "dns-dnsimple",
 | 
			
		||||
  },
 | 
			
		||||
  //####################################################//
 | 
			
		||||
  dnsmadeeasy: {
 | 
			
		||||
    display_name: "DNS Made Easy",
 | 
			
		||||
    package_name: "certbot-dns-dnsmadeeasy",
 | 
			
		||||
    package_version: "1.8.0",
 | 
			
		||||
    credentials: `dns_dnsmadeeasy_api_key = 1c1a3c91-4770-4ce7-96f4-54c0eb0e457a
 | 
			
		||||
dns_dnsmadeeasy_secret_key = c9b5625f-9834-4ff8-baba-4ed5f32cae55`,
 | 
			
		||||
    full_plugin_name: "dns-dnsmadeeasy",
 | 
			
		||||
  },
 | 
			
		||||
  //####################################################//
 | 
			
		||||
  dnspod: {
 | 
			
		||||
    display_name: "DNSPod",
 | 
			
		||||
    package_name: "certbot-dns-dnspod",
 | 
			
		||||
    package_version: "0.1.0",
 | 
			
		||||
    credentials: `certbot_dns_dnspod:dns_dnspod_email = "DNSPOD-API-REQUIRES-A-VALID-EMAIL"
 | 
			
		||||
certbot_dns_dnspod:dns_dnspod_api_token = "DNSPOD-API-TOKEN"`,
 | 
			
		||||
    full_plugin_name: "certbot-dns-dnspod:dns-dnspod",
 | 
			
		||||
  },
 | 
			
		||||
  //####################################################//
 | 
			
		||||
  google: {
 | 
			
		||||
    display_name: "Google",
 | 
			
		||||
    package_name: "certbot-dns-google",
 | 
			
		||||
    package_version: "1.8.0",
 | 
			
		||||
    credentials: `{
 | 
			
		||||
  "type": "service_account",
 | 
			
		||||
  ...
 | 
			
		||||
}`,
 | 
			
		||||
    full_plugin_name: "dns-google",
 | 
			
		||||
  },
 | 
			
		||||
  //####################################################//
 | 
			
		||||
  hetzner: {
 | 
			
		||||
    display_name: "Hetzner",
 | 
			
		||||
    package_name: "certbot-dns-hetzner",
 | 
			
		||||
    package_version: "1.0.4",
 | 
			
		||||
    credentials: `certbot_dns_hetzner:dns_hetzner_api_token = 0123456789abcdef0123456789abcdef`,
 | 
			
		||||
    full_plugin_name: "certbot-dns-hetzner:dns-hetzner",
 | 
			
		||||
  },
 | 
			
		||||
  //####################################################//
 | 
			
		||||
  inwx: {
 | 
			
		||||
    display_name: "INWX",
 | 
			
		||||
    package_name: "certbot-dns-inwx",
 | 
			
		||||
    package_version: "2.1.2",
 | 
			
		||||
    credentials: `certbot_dns_inwx:dns_inwx_url = https://api.domrobot.com/xmlrpc/
 | 
			
		||||
certbot_dns_inwx:dns_inwx_username = your_username
 | 
			
		||||
certbot_dns_inwx:dns_inwx_password = your_password
 | 
			
		||||
certbot_dns_inwx:dns_inwx_shared_secret = your_shared_secret optional`,
 | 
			
		||||
    full_plugin_name: "certbot-dns-inwx:dns-inwx",
 | 
			
		||||
  },
 | 
			
		||||
  //####################################################//
 | 
			
		||||
  ispconfig: {
 | 
			
		||||
    display_name: "ISPConfig",
 | 
			
		||||
    package_name: "certbot-dns-ispconfig",
 | 
			
		||||
    package_version: "0.2.0",
 | 
			
		||||
    credentials: `certbot_dns_ispconfig:dns_ispconfig_username = myremoteuser
 | 
			
		||||
certbot_dns_ispconfig:dns_ispconfig_password = verysecureremoteuserpassword
 | 
			
		||||
certbot_dns_ispconfig:dns_ispconfig_endpoint = https://localhost:8080`,
 | 
			
		||||
    full_plugin_name: "certbot-dns-ispconfig:dns-ispconfig",
 | 
			
		||||
  },
 | 
			
		||||
  //####################################################//
 | 
			
		||||
  isset: {
 | 
			
		||||
    display_name: "Isset",
 | 
			
		||||
    package_name: "certbot-dns-isset",
 | 
			
		||||
    package_version: "0.0.3",
 | 
			
		||||
    credentials: `certbot_dns_isset:dns_isset_endpoint="https://customer.isset.net/api"
 | 
			
		||||
certbot_dns_isset:dns_isset_token="<token>"`,
 | 
			
		||||
    full_plugin_name: "certbot-dns-isset:dns-isset",
 | 
			
		||||
  },
 | 
			
		||||
  //####################################################//
 | 
			
		||||
  linode: {
 | 
			
		||||
    display_name: "Linode",
 | 
			
		||||
    package_name: "certbot-dns-linode",
 | 
			
		||||
    package_version: "1.8.0",
 | 
			
		||||
    credentials: `dns_linode_key = 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ64
 | 
			
		||||
dns_linode_version = [<blank>|3|4]`,
 | 
			
		||||
    full_plugin_name: "dns-linode",
 | 
			
		||||
  },
 | 
			
		||||
  //####################################################//
 | 
			
		||||
  luadns: {
 | 
			
		||||
    display_name: "LuaDNS",
 | 
			
		||||
    package_name: "certbot-dns-luadns",
 | 
			
		||||
    package_version: "1.8.0",
 | 
			
		||||
    credentials: `dns_luadns_email = user@example.com
 | 
			
		||||
dns_luadns_token = 0123456789abcdef0123456789abcdef`,
 | 
			
		||||
    full_plugin_name: "dns-luadns",
 | 
			
		||||
  },
 | 
			
		||||
  //####################################################//
 | 
			
		||||
  netcup: {
 | 
			
		||||
    display_name: "netcup",
 | 
			
		||||
    package_name: "certbot-dns-netcup",
 | 
			
		||||
    package_version: "1.0.0",
 | 
			
		||||
    credentials: `dns_netcup_customer_id  = 123456
 | 
			
		||||
dns_netcup_api_key      = 0123456789abcdef0123456789abcdef01234567
 | 
			
		||||
dns_netcup_api_password = abcdef0123456789abcdef01234567abcdef0123`,
 | 
			
		||||
    full_plugin_name: "certbot-dns-netcup:dns-netcup",
 | 
			
		||||
  },
 | 
			
		||||
  //####################################################//
 | 
			
		||||
  njalla: {
 | 
			
		||||
    display_name: "Njalla",
 | 
			
		||||
    package_name: "certbot-dns-njalla",
 | 
			
		||||
    package_version: "0.0.4",
 | 
			
		||||
    credentials: `certbot_dns_njalla:dns_njalla_token = 0123456789abcdef0123456789abcdef01234567`,
 | 
			
		||||
    full_plugin_name: "certbot-dns-njalla:dns-njalla",
 | 
			
		||||
  },
 | 
			
		||||
  //####################################################//
 | 
			
		||||
  nsone: {
 | 
			
		||||
    display_name: "NS1",
 | 
			
		||||
    package_name: "certbot-dns-nsone",
 | 
			
		||||
    package_version: "1.8.0",
 | 
			
		||||
    credentials: `dns_nsone_api_key = MDAwMDAwMDAwMDAwMDAw`,
 | 
			
		||||
    full_plugin_name: "dns-nsone",
 | 
			
		||||
  },
 | 
			
		||||
  //####################################################//
 | 
			
		||||
  ovh: {
 | 
			
		||||
    display_name: "OVH",
 | 
			
		||||
    package_name: "certbot-dns-ovh",
 | 
			
		||||
    package_version: "1.8.0",
 | 
			
		||||
    credentials: `dns_ovh_endpoint = ovh-eu
 | 
			
		||||
dns_ovh_application_key = MDAwMDAwMDAwMDAw
 | 
			
		||||
dns_ovh_application_secret = MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAw
 | 
			
		||||
dns_ovh_consumer_key = MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAw`,
 | 
			
		||||
    full_plugin_name: "dns-ovh",
 | 
			
		||||
  },
 | 
			
		||||
  //####################################################//
 | 
			
		||||
  powerdns: {
 | 
			
		||||
    display_name: "PowerDNS",
 | 
			
		||||
    package_name: "certbot-dns-powerdns",
 | 
			
		||||
    package_version: "0.2.0",
 | 
			
		||||
    credentials: `certbot_dns_powerdns:dns_powerdns_api_url = https://api.mypowerdns.example.org
 | 
			
		||||
certbot_dns_powerdns:dns_powerdns_api_key = AbCbASsd!@34`,
 | 
			
		||||
    full_plugin_name: "certbot-dns-powerdns:dns-powerdns",
 | 
			
		||||
  },
 | 
			
		||||
  //####################################################//
 | 
			
		||||
  rfc2136: {
 | 
			
		||||
    display_name: "RFC 2136",
 | 
			
		||||
    package_name: "certbot-dns-rfc2136",
 | 
			
		||||
    package_version: "1.8.0",
 | 
			
		||||
    credentials: `# Target DNS server
 | 
			
		||||
dns_rfc2136_server = 192.0.2.1
 | 
			
		||||
# Target DNS port
 | 
			
		||||
dns_rfc2136_port = 53
 | 
			
		||||
# TSIG key name
 | 
			
		||||
dns_rfc2136_name = keyname.
 | 
			
		||||
# TSIG key secret
 | 
			
		||||
dns_rfc2136_secret = 4q4wM/2I180UXoMyN4INVhJNi8V9BCV+jMw2mXgZw/CSuxUT8C7NKKFs AmKd7ak51vWKgSl12ib86oQRPkpDjg==
 | 
			
		||||
# TSIG key algorithm
 | 
			
		||||
dns_rfc2136_algorithm = HMAC-SHA512`,
 | 
			
		||||
    full_plugin_name: "dns-rfc2136",
 | 
			
		||||
  },
 | 
			
		||||
  //####################################################//
 | 
			
		||||
  route53: {
 | 
			
		||||
    display_name: "Route 53 (Amazon)",
 | 
			
		||||
    package_name: "certbot-dns-route53",
 | 
			
		||||
    package_version: "1.8.0",
 | 
			
		||||
    credentials: `[default]
 | 
			
		||||
aws_access_key_id=AKIAIOSFODNN7EXAMPLE
 | 
			
		||||
aws_secret_access_key=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY`,
 | 
			
		||||
    full_plugin_name: "dns-route53",
 | 
			
		||||
  },
 | 
			
		||||
  //####################################################//
 | 
			
		||||
  vultr: {
 | 
			
		||||
    display_name: "Vultr",
 | 
			
		||||
    package_name: "certbot-dns-vultr",
 | 
			
		||||
    package_version: "1.0.3",
 | 
			
		||||
    credentials: `certbot_dns_vultr:dns_vultr_key = YOUR_VULTR_API_KEY`,
 | 
			
		||||
    full_plugin_name: "certbot-dns-vultr:dns-vultr",
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
@@ -10,7 +10,7 @@ if hash docker 2>/dev/null; then
 | 
			
		||||
	docker pull "${DOCKER_IMAGE}"
 | 
			
		||||
	cd "${DIR}/.."
 | 
			
		||||
	echo -e "${BLUE}❯ ${CYAN}Building Frontend ...${RESET}"
 | 
			
		||||
	docker run --rm -e CI=true -v "$(pwd)/frontend:/app/frontend" -w /app/frontend "$DOCKER_IMAGE" sh -c "yarn install && yarn build && yarn build && chown -R $(id -u):$(id -g) /app/frontend"
 | 
			
		||||
	docker run --rm -e CI=true -v "$(pwd)/frontend:/app/frontend" -v "$(pwd)/global:/app/global" -w /app/frontend "$DOCKER_IMAGE" sh -c "yarn install && yarn build && yarn build && chown -R $(id -u):$(id -g) /app/frontend"
 | 
			
		||||
	echo -e "${BLUE}❯ ${GREEN}Building Frontend Complete${RESET}"
 | 
			
		||||
else
 | 
			
		||||
	echo -e "${RED}❯ docker command is not available${RESET}"
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,7 @@ DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
 | 
			
		||||
if hash docker-compose 2>/dev/null; then
 | 
			
		||||
	cd "${DIR}/.."
 | 
			
		||||
	echo -e "${BLUE}❯ ${CYAN}Testing Dev Stack ...${RESET}"
 | 
			
		||||
	docker-compose exec -T npm bash -c "cd /app/backend && task test"
 | 
			
		||||
	docker-compose exec -T npm bash -c "cd /app && task test"
 | 
			
		||||
else
 | 
			
		||||
	echo -e "${RED}❯ docker-compose command is not available${RESET}"
 | 
			
		||||
fi
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user