diff --git a/src/nginxconfig/generators/conf/nginx.conf.js b/src/nginxconfig/generators/conf/nginx.conf.js
index b0b08b9..21fb126 100644
--- a/src/nginxconfig/generators/conf/nginx.conf.js
+++ b/src/nginxconfig/generators/conf/nginx.conf.js
@@ -107,29 +107,39 @@ export default (domains, global) => {
config.http.push(['ssl_stapling', 'on']);
config.http.push(['ssl_stapling_verify', 'on']);
- if (global.https.ocspCloudflare.computed
- || global.https.ocspGoogle.computed
- || global.https.ocspOpenDns.computed) {
- const ips = [];
- if (global.https.ocspCloudflare.computed) {
- if (['ipv4', 'both'].includes(global.https.ocspCloudflareType.computed))
- ips.push('1.1.1.1', '1.0.0.1');
- if (['ipv6', 'both'].includes(global.https.ocspCloudflareType.computed))
- ips.push('[2606:4700:4700::1111]', '[2606:4700:4700::1001]');
- }
- if (global.https.ocspGoogle.computed) {
- if (['ipv4', 'both'].includes(global.https.ocspGoogleType.computed))
- ips.push('8.8.8.8', '8.8.4.4');
- if (['ipv6', 'both'].includes(global.https.ocspGoogleType.computed))
- ips.push('[2001:4860:4860::8888]', '[2001:4860:4860::8844]');
- }
- if (global.https.ocspOpenDns.computed) {
- if (['ipv4', 'both'].includes(global.https.ocspOpenDnsType.computed))
- ips.push('208.67.222.222', '208.67.220.220');
- if (['ipv6', 'both'].includes(global.https.ocspOpenDnsType.computed))
- ips.push('[2620:119:35::35]', '[2620:119:53::53]');
- }
+ const ips = [];
+ if (global.https.ocspCloudflare.computed) {
+ if (['ipv4', 'both'].includes(global.https.ocspCloudflareType.computed))
+ ips.push('1.1.1.1', '1.0.0.1');
+ if (['ipv6', 'both'].includes(global.https.ocspCloudflareType.computed))
+ ips.push('[2606:4700:4700::1111]', '[2606:4700:4700::1001]');
+ }
+ if (global.https.ocspGoogle.computed) {
+ if (['ipv4', 'both'].includes(global.https.ocspGoogleType.computed))
+ ips.push('8.8.8.8', '8.8.4.4');
+ if (['ipv6', 'both'].includes(global.https.ocspGoogleType.computed))
+ ips.push('[2001:4860:4860::8888]', '[2001:4860:4860::8844]');
+ }
+ if (global.https.ocspOpenDns.computed) {
+ if (['ipv4', 'both'].includes(global.https.ocspOpenDnsType.computed))
+ ips.push('208.67.222.222', '208.67.220.220');
+ if (['ipv6', 'both'].includes(global.https.ocspOpenDnsType.computed))
+ ips.push('[2620:119:35::35]', '[2620:119:53::53]');
+ }
+ if (global.https.ocspQuad9.computed) {
+ if (['ipv4', 'both'].includes(global.https.ocspQuad9Type.computed))
+ ips.push('9.9.9.9', '149.112.112.112');
+ if (['ipv6', 'both'].includes(global.https.ocspQuad9Type.computed))
+ ips.push('[2620:fe::fe]', '[2620:fe::9]');
+ }
+ if (global.https.ocspVerisign.computed) {
+ if (['ipv4', 'both'].includes(global.https.ocspVerisignType.computed))
+ ips.push('64.6.64.6', '64.6.65.6');
+ if (['ipv6', 'both'].includes(global.https.ocspVerisignType.computed))
+ ips.push('[2620:74:1b::1:1]', '[2620:74:1c::2:2]');
+ }
+ if (ips.length) {
config.http.push(['resolver', `${ips.join(' ')} valid=60s`]);
config.http.push(['resolver_timeout', '2s']);
}
diff --git a/src/nginxconfig/i18n/en/templates/global_sections/https.js b/src/nginxconfig/i18n/en/templates/global_sections/https.js
index 6b3f483..985b362 100644
--- a/src/nginxconfig/i18n/en/templates/global_sections/https.js
+++ b/src/nginxconfig/i18n/en/templates/global_sections/https.js
@@ -27,6 +27,8 @@ export default {
cloudflareResolver: 'Cloudflare Resolver',
googlePublicDns: 'Google Public DNS',
openDns: 'OpenDNS',
+ quad9: 'Quad9',
+ verisign: 'Verisign',
letsEncryptWebroot: `${common.letsEncrypt} webroot`,
mozillaModern: `${mozilla} Modern`,
mozillaIntermediate: `${mozilla} Intermediate`,
diff --git a/src/nginxconfig/templates/app.vue b/src/nginxconfig/templates/app.vue
index 6ee0349..1b75a8d 100644
--- a/src/nginxconfig/templates/app.vue
+++ b/src/nginxconfig/templates/app.vue
@@ -70,8 +70,8 @@ limitations under the License.
@@ -223,6 +223,7 @@ limitations under the License.
},
letsEncryptEmail: {
default: '',
+ computed: 'info@example.com', // No default value, but a default computed
enabled: true,
},
sslCertificate: {
@@ -346,6 +347,23 @@ limitations under the License.
},
deep: true,
},
+ // Ensure there is a default email for Let's Encrypt
+ '$props.data.letsEncryptEmail': {
+ handler(data) {
+ if (!data.computed.trim()) {
+ data.computed = `info@${this.$parent.$props.data.server.domain.computed}`;
+ }
+ },
+ deep: true,
+ },
+ '$parent.$props.data.server.domain': {
+ handler(data) {
+ if (!this.$props.data.letsEncryptEmail.value.trim()) {
+ this.$props.data.letsEncryptEmail.computed = `info@${data.computed}`;
+ }
+ },
+ deep: true,
+ },
},
};
diff --git a/src/nginxconfig/templates/domain_sections/server.vue b/src/nginxconfig/templates/domain_sections/server.vue
index c6e7d12..a7789fe 100644
--- a/src/nginxconfig/templates/domain_sections/server.vue
+++ b/src/nginxconfig/templates/domain_sections/server.vue
@@ -182,15 +182,14 @@ limitations under the License.
i18n,
};
},
- computed: computedFromDefaults(defaults, 'server'), // Getters & setters for the delegated data
+ computed: computedFromDefaults(defaults, 'server'), // Getters & setters for the delegated data
watch: {
'$props.data.domain': {
handler(data) {
- // This might cause recursion, but seems not to
-
- // Ignore www. if given
+ // Ignore www. if given, enable WWW subdomain
if (data.computed.startsWith('www.')) {
data.computed = data.computed.slice(4);
+ this.wwwSubdomain = true;
}
// Use default if empty
diff --git a/src/nginxconfig/templates/global_sections/https.vue b/src/nginxconfig/templates/global_sections/https.vue
index c96058e..3d2a6ac 100644
--- a/src/nginxconfig/templates/global_sections/https.vue
+++ b/src/nginxconfig/templates/global_sections/https.vue
@@ -122,6 +122,48 @@ limitations under the License.
+
+
+
+
+
+ {{ i18n.templates.globalSections.https.quad9 }}
+
+
+
+
+
+
+
+
+
+ {{ i18n.templates.globalSections.https.verisign }}
+
+
+
+
@@ -195,6 +237,16 @@ limitations under the License.
enabled: true,
},
ocspOpenDnsType: clone(ipType),
+ ocspQuad9: {
+ default: false,
+ enabled: true,
+ },
+ ocspQuad9Type: clone(ipType),
+ ocspVerisign: {
+ default: false,
+ enabled: true,
+ },
+ ocspVerisignType: clone(ipType),
letsEncryptRoot: {
default: '/var/www/_letsencrypt/',
enabled: true,
@@ -238,6 +290,14 @@ limitations under the License.
handler: validOptionCheck,
deep: true,
},
+ '$props.data.ocspQuad9Type': {
+ handler: validOptionCheck,
+ deep: true,
+ },
+ '$props.data.ocspVerisignType': {
+ handler: validOptionCheck,
+ deep: true,
+ },
'$parent.$parent.$data.domains': {
handler(data) {
let httpsEnabled = false, leEnabled = false;
diff --git a/src/nginxconfig/templates/global_sections/security.vue b/src/nginxconfig/templates/global_sections/security.vue
index bea9109..b808241 100644
--- a/src/nginxconfig/templates/global_sections/security.vue
+++ b/src/nginxconfig/templates/global_sections/security.vue
@@ -151,7 +151,7 @@ limitations under the License.
computed: {
...computedFromDefaults(defaults, 'security'), // Getters & setters for the delegated data
hasWordPress() {
- return this.$parent.$parent.$data.domains.some(d => d.php.wordPressRules.computed);
+ return this.$parent.$parent.$data.domains.some(d => d && d.php.wordPressRules.computed);
},
hasUnsafeEval() {
return this.$props.data.contentSecurityPolicy.computed.includes('\'unsafe-eval\'');
diff --git a/src/nginxconfig/util/backwards_compatibility.js b/src/nginxconfig/util/backwards_compatibility.js
index f6cf422..abadd0f 100644
--- a/src/nginxconfig/util/backwards_compatibility.js
+++ b/src/nginxconfig/util/backwards_compatibility.js
@@ -121,8 +121,8 @@ export default data => {
// If not a global setting and if this is an integer
// Then, this is probably an old domain, so we'll try to convert it as such
if (!isNaN(parseInt(key))) {
- data.domains = data.domains || [];
- data.domains.push(data[key]);
+ data.domains = isObject(data.domains) ? data.domains : {};
+ data.domains[key] = data[key];
}
}
@@ -130,35 +130,35 @@ export default data => {
data.global = {...(data.global || {}), ...mappedGlobal};
// Handle converting domain settings
- if ('domains' in data && (Array.isArray(data.domains) || isObject(data.domains))) {
- // Ensure we're working with an array
- const values = isObject(data.domains) ? Object.values(data.domains) : data.domains;
+ if ('domains' in data && isObject(data.domains)) {
+ for (const key in data.domains) {
+ // Don't include inherited props
+ if (!Object.prototype.hasOwnProperty.call(data.domains, key)) continue;
- for (let i = 0; i < values.length; i++) {
// Check this is an object
- if (!isObject(values[i])) continue;
+ if (!isObject(data.domains[key])) continue;
// Hold any mapped data
const mappedData = {};
// Handle converting old domain settings to new ones
- for (const key in values[i]) {
- if (!Object.prototype.hasOwnProperty.call(values[i], key)) continue;
- if (isObject(values[i][key])) continue;
+ for (const key2 in data.domains[key]) {
+ // Don't include inherited props
+ if (!Object.prototype.hasOwnProperty.call(data.domains[key], key2)) continue;
+
+ // Don't convert objects
+ if (isObject(data.domains[key][key2])) continue;
// Map old settings to their new ones
- if (key in domainMap) {
- const map = domainMap[key];
+ if (key2 in domainMap) {
+ const map = domainMap[key2];
mappedData[map[0]] = mappedData[map[0]] || {};
- mappedData[map[0]][map[1]] = map.length < 3 ? values[i][key] : map[2](values[i][key]);
+ mappedData[map[0]][map[1]] = map.length < 3 ? data.domains[key][key2] : map[2](data.domains[key][key2]);
}
}
// Overwrite mapped properties
- values[i] = {...values[i], ...mappedData};
+ data.domains[key] = {...data.domains[key], ...mappedData};
}
-
- // Store the updated domain data
- data.domains = values;
}
};
diff --git a/src/nginxconfig/util/import_data.js b/src/nginxconfig/util/import_data.js
index 9405b19..aa80eb6 100644
--- a/src/nginxconfig/util/import_data.js
+++ b/src/nginxconfig/util/import_data.js
@@ -54,6 +54,7 @@ export default (query, domains, global, nextTick) => {
const data = qs.parse(query, {
ignoreQueryPrefix: true,
allowDots: true,
+ parseArrays: false,
decoder(value) {
value = decodeURIComponent(value);
@@ -78,25 +79,23 @@ export default (query, domains, global, nextTick) => {
backwardsCompatibility(data);
// Handle domains
- if ('domains' in data) {
- // Check its an array or object
- if (Array.isArray(data.domains) || isObject(data.domains)) {
- // Ensure we're working with an array
- const values = isObject(data.domains) ? Object.values(data.domains) : data.domains;
-
- // Work through each potential domain
- for (const domainData of values) {
- // Check this is an object
- if (!isObject(domainData)) continue;
-
- // Create a new domain (assume it has had custom user settings)
- const domainImported = clone(Domain.delegated);
- domainImported.hasUserInteraction = true;
- domains.push(domainImported);
-
- // Apply the initial values on the next Vue tick, once the watchers are ready
- nextTick(() => applyCategories(domainData, domainImported));
+ if ('domains' in data && isObject(data.domains)) {
+ // Work through all valid integer keys in the object
+ const keys = Object.keys(data.domains).map(x => parseInt(x)).filter(x => !isNaN(x));
+ for (let i = 0; i < Math.max(...keys) + 1; i++) {
+ // If the key doesn't exist or this isn't a valid object, assume it was an untouched example domain
+ if (!keys.includes(i) || !isObject(data.domains[i])) {
+ domains.push(clone(Domain.delegated));
+ continue;
}
+
+ // Create a new domain (assume it has had custom user settings)
+ const domainImported = clone(Domain.delegated);
+ domainImported.hasUserInteraction = true;
+ domains.push(domainImported);
+
+ // Apply the initial values on the next Vue tick, once the watchers are ready
+ nextTick(() => applyCategories(data.domains[i], domainImported));
}
} else {
// If no configured domains, add a single default