mirror of
https://github.com/digitalocean/nginxconfig.io.git
synced 2025-08-08 09:02:45 +08:00
Conf diffing?!
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
import qs from 'qs';
|
||||
import sslProfiles from '../../util/ssl_profiles';
|
||||
import exportData from '../../util/export_data';
|
||||
import websiteConf from './website.conf';
|
||||
|
||||
export default (domains, global) => {
|
||||
@@ -6,7 +8,9 @@ export default (domains, global) => {
|
||||
|
||||
// Source
|
||||
config['# Generated by nginxconfig.io'] = '';
|
||||
config[`# ${window.location.protocol}//${window.location.host}${window.location.pathname}${window.location.search}`] = '';
|
||||
const data = exportData(domains.map((domain, index) => [domain, index]).filter(d => d[0] !== null), global);
|
||||
const query = qs.stringify(data, { allowDots: true });
|
||||
config[`# ${window.location.protocol}//${window.location.host}${window.location.pathname}${query.length ? '?' : ''}${query}`] = '';
|
||||
|
||||
// Basic nginx conf
|
||||
config.user = global.nginx.user.computed;
|
||||
@@ -68,17 +72,20 @@ export default (domains, global) => {
|
||||
config.http.push(['ssl_session_cache', 'shared:SSL:10m']);
|
||||
config.http.push(['ssl_session_tickets', 'off']);
|
||||
|
||||
if (sslProfiles[global.https.sslProfile.computed].dh_param_size) {
|
||||
config.http.push(['# Diffie-Hellman parameter for DHE ciphersuites', '']);
|
||||
config.http.push(['ssl_dhparam', `${global.nginx.nginxConfigDirectory.computed.replace(/\/+$/, '')}/dhparam.pem`]);
|
||||
}
|
||||
const sslProfile = sslProfiles[global.https.sslProfile.computed];
|
||||
if (sslProfile) {
|
||||
if (sslProfile.dh_param_size) {
|
||||
config.http.push(['# Diffie-Hellman parameter for DHE ciphersuites', '']);
|
||||
config.http.push(['ssl_dhparam', `${global.nginx.nginxConfigDirectory.computed.replace(/\/+$/, '')}/dhparam.pem`]);
|
||||
}
|
||||
|
||||
config.http.push([`# ${sslProfiles[global.https.sslProfile.computed].name} configuration`, '']);
|
||||
config.http.push(['ssl_protocols', sslProfiles[global.https.sslProfile.computed].protocols.join(' ')]);
|
||||
if (sslProfiles[global.https.sslProfile.computed].ciphers.length)
|
||||
config.http.push(['ssl_ciphers', sslProfiles[global.https.sslProfile.computed].ciphers.join(':')]);
|
||||
if (sslProfiles[global.https.sslProfile.computed].server_preferred_order)
|
||||
config.http.push(['ssl_prefer_server_ciphers', 'on']);
|
||||
config.http.push([`# ${sslProfile.name} configuration`, '']);
|
||||
config.http.push(['ssl_protocols', sslProfile.protocols.join(' ')]);
|
||||
if (sslProfile.ciphers.length)
|
||||
config.http.push(['ssl_ciphers', sslProfile.ciphers.join(':')]);
|
||||
if (sslProfile.server_preferred_order)
|
||||
config.http.push(['ssl_prefer_server_ciphers', 'on']);
|
||||
}
|
||||
|
||||
config.http.push(['# OCSP Stapling', '']);
|
||||
config.http.push(['ssl_stapling', 'on']);
|
||||
|
@@ -17,7 +17,8 @@ limitations under the License.
|
||||
import 'babel-polyfill';
|
||||
|
||||
import Vue from 'vue';
|
||||
import App from './templates/app.vue';
|
||||
import './util/prism_bundle';
|
||||
import App from './templates/app';
|
||||
import i18n from './i18n';
|
||||
|
||||
document.head.title = i18n.templates.app.title;
|
||||
|
@@ -435,10 +435,12 @@ $highlight: #f2c94c;
|
||||
// Fix Bulma interfering with Prism
|
||||
.token {
|
||||
&.number,
|
||||
&.tag {
|
||||
&.tag,
|
||||
&.entity,
|
||||
&.operator,
|
||||
&.url {
|
||||
background: transparent;
|
||||
border-radius: initial;
|
||||
color: inherit;
|
||||
display: initial;
|
||||
font-size: inherit;
|
||||
margin: initial;
|
||||
@@ -451,4 +453,16 @@ $highlight: #f2c94c;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.code-toolbar {
|
||||
> .toolbar {
|
||||
right: calc(.2em + 16px);
|
||||
}
|
||||
}
|
||||
|
||||
mark {
|
||||
background: rgba($highlight, .45);
|
||||
color: inherit;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
@@ -66,11 +66,11 @@ limitations under the License.
|
||||
<div :class="`column ${splitColumn ? 'is-half' : 'is-full'} is-full-mobile is-full-tablet`">
|
||||
<h2>Config files</h2>
|
||||
<div class="columns is-multiline">
|
||||
<div v-for="(conf, i) in confFiles"
|
||||
:class="`column ${confFiles.length > 1 && !splitColumn ? 'is-half' : 'is-full'} is-full-mobile is-full-tablet`"
|
||||
<div v-for="conf in confFilesOutput"
|
||||
:class="`column ${confFilesOutput.length > 1 && !splitColumn ? 'is-half' : 'is-full'} is-full-mobile is-full-tablet`"
|
||||
>
|
||||
<h3>{{ nginxDir }}/{{ conf[0] }}</h3>
|
||||
<Prism :key="`${conf[0]}${i}`" language="nginx" :code="conf[1]"></Prism>
|
||||
<pre><code ref="files" class="language-nginx" v-html="conf[1]"></code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -83,8 +83,10 @@ limitations under the License.
|
||||
|
||||
<script>
|
||||
import clone from 'clone';
|
||||
import Prism from 'vue-prism-component';
|
||||
import 'prismjs/components/prism-nginx';
|
||||
import { diffLines } from 'diff';
|
||||
import escape from 'escape-html';
|
||||
import deepEqual from 'deep-equal';
|
||||
import Prism from 'prismjs';
|
||||
import Header from 'do-vue/src/templates/header';
|
||||
import Footer from 'do-vue/src/templates/footer';
|
||||
import isChanged from '../util/is_changed';
|
||||
@@ -99,7 +101,6 @@ limitations under the License.
|
||||
export default {
|
||||
name: 'App',
|
||||
components: {
|
||||
Prism,
|
||||
Header,
|
||||
Footer,
|
||||
Domain,
|
||||
@@ -114,6 +115,9 @@ limitations under the License.
|
||||
active: 0,
|
||||
ready: false,
|
||||
splitColumn: false,
|
||||
confWatcherWaiting: false,
|
||||
confFilesPrevious: [],
|
||||
confFilesOutput: [],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -127,19 +131,22 @@ limitations under the License.
|
||||
return generators(this.$data.domains.filter(d => d !== null), this.$data.global);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
confFiles(newConf, oldConf) {
|
||||
if (this.$data.confWatcherWaiting) return;
|
||||
|
||||
// Set that we're waiting for changes to stop
|
||||
this.$data.confWatcherWaiting = true;
|
||||
this.$data.confFilesPrevious = oldConf;
|
||||
|
||||
// Check next tick to see if anything has changed again
|
||||
this.$nextTick(() => this.checkChange(newConf));
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
// If there is no query param, add one default domain and we're ready
|
||||
if (!window.location.search.length) {
|
||||
this.$data.domains.push(clone(Domain.delegated));
|
||||
this.$data.ready = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Import any data from the URL query params
|
||||
// Import any data from the URL query params, defaulting to one domain
|
||||
// The config file watcher will handle setting the app as ready
|
||||
importData(window.location.search, this.$data.domains, this.$data.global, this.$nextTick);
|
||||
|
||||
// After two ticks (one tick to set watched data), we are ready
|
||||
this.$nextTick(() => this.$nextTick(() => this.$data.ready = true));
|
||||
},
|
||||
methods: {
|
||||
changes(index) {
|
||||
@@ -161,6 +168,88 @@ limitations under the License.
|
||||
this.$set(this.$data.domains, index, null);
|
||||
if (this.$data.active === index) this.$data.active = this.$data.domains.findIndex(d => d !== null);
|
||||
},
|
||||
highlightFiles() {
|
||||
for (const file of this.$refs.files)
|
||||
Prism.highlightElement(file, true);
|
||||
},
|
||||
checkChange(oldConf) {
|
||||
// If nothing has changed for a tick, we can use the config files
|
||||
if (deepEqual(oldConf, this.confFiles)) {
|
||||
// If this is the initial data load on app start, don't diff and set ourselves as ready
|
||||
if (!this.$data.ready) {
|
||||
this.$data.confFilesOutput = this.confFiles;
|
||||
this.$nextTick(() => {
|
||||
this.$data.confWatcherWaiting = false;
|
||||
this.highlightFiles();
|
||||
this.$data.ready = true;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, do the diff!
|
||||
this.updateDiff(this.confFiles, this.$data.confFilesPrevious);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check next tick to see if anything has changed again
|
||||
this.$nextTick(() => this.checkChange(this.confFiles));
|
||||
},
|
||||
updateDiff(newConf, oldConf) {
|
||||
// Work through each file in the new config
|
||||
const newFiles = [];
|
||||
for (const [newFileName, newFileConf] of newConf) {
|
||||
|
||||
// If a file with the same name existed before, diff!
|
||||
// TODO: Handle diffing across file renames (eg. when a user changes a domain name)
|
||||
const old = oldConf && oldConf.find(c => c[0] === newFileName);
|
||||
if (old) {
|
||||
// Get the diff
|
||||
const diff = diffLines(old[1], newFileConf);
|
||||
|
||||
// Wrap additions in <mark>, drop removals
|
||||
const output = diff.map((change, index, array) => {
|
||||
if (change.removed) return '';
|
||||
|
||||
const escaped = escape(change.value);
|
||||
|
||||
// Don't mark as diff if nothing changed
|
||||
if (!change.added) return escaped;
|
||||
|
||||
// Don't mark as diff if only whitespace changed
|
||||
if (index > 0 && array[index - 1].removed) {
|
||||
if (array[index - 1].value.replace(/\s/g, '') === change.value.replace(/\s/g, '')) {
|
||||
return escaped;
|
||||
}
|
||||
}
|
||||
if (index < array.length - 1 && array[index + 1].removed) {
|
||||
if (array[index + 1].value.replace(/\s/g, '') === change.value.replace(/\s/g, '')) {
|
||||
return escaped;
|
||||
}
|
||||
}
|
||||
|
||||
// Mark the diff, without highlighting whitespace
|
||||
return escaped
|
||||
.split('\n')
|
||||
.map(part => part.replace(/^(\s*)(.*)(\s*)$/, '$1<mark>$2</mark>$3'))
|
||||
.join('\n');
|
||||
}).join('');
|
||||
|
||||
// Store
|
||||
newFiles.push([newFileName, output]);
|
||||
continue;
|
||||
}
|
||||
|
||||
// No diffing, just store the new file
|
||||
newFiles.push([newFileName, newFileConf]);
|
||||
}
|
||||
this.$data.confFilesOutput = newFiles;
|
||||
|
||||
// Highlight in-browser (using web workers so UI isn't blocked) once these files are rendered
|
||||
this.$nextTick(() => {
|
||||
this.$data.confWatcherWaiting = false;
|
||||
this.highlightFiles();
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@@ -5,50 +5,50 @@
|
||||
<p>
|
||||
Comment out SSL related directives in the configuration:
|
||||
<br />
|
||||
<Prism language="bash" :code="`sed -i -r 's/(listen .*443)/\\1;#/g; s/(ssl_(certificate|certificate_key|trusted_certificate) )/#;#\\1/g' ${sitesAvailable}`"></Prism>
|
||||
</p>
|
||||
<pre><code ref="commentOut" class="language-bash"></code></pre>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<p>
|
||||
Reload your NGINX server:
|
||||
<br />
|
||||
<Prism language="bash" code="sudo nginx -t && sudo systemctl reload nginx"></Prism>
|
||||
</p>
|
||||
<pre><code ref="reload" class="language-bash">sudo nginx -t && sudo systemctl reload nginx</code></pre>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<p>
|
||||
Obtain SSL certificates from Let's Encrypt using Certbot:
|
||||
<br />
|
||||
<Prism language="bash" :code="certbotCmds"></Prism>
|
||||
</p>
|
||||
<pre><code ref="certBot" class="language-bash"></code></pre>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<p>
|
||||
Uncomment SSL related directives in the configuration:
|
||||
<br />
|
||||
<Prism language="bash" :code="`sed -i -r 's/#?;#//g' ${sitesAvailable}`"></Prism>
|
||||
</p>
|
||||
<pre><code ref="unComment" class="language-bash"></code></pre>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<p>
|
||||
Reload your NGINX server:
|
||||
<br />
|
||||
<Prism language="bash" code="sudo nginx -t && sudo systemctl reload nginx"></Prism>
|
||||
</p>
|
||||
<pre><code ref="reload2" class="language-bash">sudo nginx -t && sudo systemctl reload nginx</code></pre>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<p>
|
||||
Configure Certbot to reload NGINX when it successfully renews certificates:
|
||||
<br />
|
||||
<Prism language="bash" code="echo -e '#!/bin/bash\nnginx -t && systemctl reload nginx' | sudo tee /etc/letsencrypt/renewal-hooks/post/nginx-reload.sh"></Prism>
|
||||
<br />
|
||||
<Prism language="bash" code="sudo chmod a+x /etc/letsencrypt/renewal-hooks/post/nginx-reload.sh"></Prism>
|
||||
</p>
|
||||
<pre><code ref="renewal" class="language-bash">echo -e '#!/bin/bash\nnginx -t && systemctl reload nginx' | sudo tee /etc/letsencrypt/renewal-hooks/post/nginx-reload.sh</code></pre>
|
||||
<br />
|
||||
<pre><code ref="chmod" class="language-bash">sudo chmod a+x /etc/letsencrypt/renewal-hooks/post/nginx-reload.sh</code></pre>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
@@ -67,17 +67,13 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Prism from 'vue-prism-component';
|
||||
import 'prismjs/components/prism-bash';
|
||||
import Prism from 'prismjs';
|
||||
import i18n from '../../i18n';
|
||||
|
||||
export default {
|
||||
name: 'SetupCertbot',
|
||||
display: 'Certbot',
|
||||
key: 'certbot',
|
||||
components: {
|
||||
Prism,
|
||||
},
|
||||
props: {
|
||||
data: Object,
|
||||
},
|
||||
@@ -101,17 +97,35 @@
|
||||
}
|
||||
return false;
|
||||
},
|
||||
sitesAvailable() {
|
||||
if (!this.$props.data.global.tools.modularizedStructure.computed) return `${this.nginxDir}/nginx.conf`;
|
||||
|
||||
const enabledAvailable = this.$props.data.global.tools.symlinkVhost.computed ? 'available' : 'enabled';
|
||||
return this.$props.data.domains
|
||||
.filter(domain => domain.https.certType.computed === 'letsEncrypt')
|
||||
.map(domain => `${this.nginxDir}/sites-${enabledAvailable}/${domain.server.domain.computed}.conf`)
|
||||
.join(' ');
|
||||
},
|
||||
watch: {
|
||||
'$props.data.domains': {
|
||||
handler() {
|
||||
this.certbotCmds();
|
||||
this.sitesAvailable();
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
'$props.data.global.tools': {
|
||||
handler() {
|
||||
this.sitesAvailable();
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => this.certbotCmds());
|
||||
this.$nextTick(() => this.sitesAvailable());
|
||||
this.$nextTick(() => Prism.highlightElement(this.$refs.reload, true));
|
||||
this.$nextTick(() => Prism.highlightElement(this.$refs.reload2, true));
|
||||
this.$nextTick(() => Prism.highlightElement(this.$refs.renewal, true));
|
||||
this.$nextTick(() => Prism.highlightElement(this.$refs.chmod, true));
|
||||
},
|
||||
methods: {
|
||||
certbotCmds() {
|
||||
return this.$props.data.domains
|
||||
if (!this.$refs.certBot) return;
|
||||
|
||||
this.$refs.certBot.textContent = this.$props.data.domains
|
||||
.filter(domain => domain.https.certType.computed === 'letsEncrypt')
|
||||
.map(domain => (
|
||||
[
|
||||
@@ -124,6 +138,26 @@
|
||||
'-n --agree-tos --force-renewal',
|
||||
].filter(x => x !== null).join(' ')
|
||||
)).join('\n');
|
||||
|
||||
this.$nextTick(() => Prism.highlightElement(this.$refs.certBot, true));
|
||||
},
|
||||
sitesAvailable() {
|
||||
if (!this.$refs.commentOut) return;
|
||||
if (!this.$refs.unComment) return;
|
||||
|
||||
const enabledAvailable = this.$props.data.global.tools.symlinkVhost.computed ? 'available' : 'enabled';
|
||||
const sitesAvailable = this.$props.data.global.tools.modularizedStructure.computed
|
||||
? this.$props.data.domains
|
||||
.filter(domain => domain.https.certType.computed === 'letsEncrypt')
|
||||
.map(domain => `${this.nginxDir}/sites-${enabledAvailable}/${domain.server.domain.computed}.conf`)
|
||||
.join(' ')
|
||||
: `${this.nginxDir}/nginx.conf`;
|
||||
|
||||
this.$refs.commentOut.textContent = `sed -i -r 's/(listen .*443)/\\1;#/g; s/(ssl_(certificate|certificate_key|trusted_certificate) )/#;#\\1/g' ${sitesAvailable}`;
|
||||
this.$refs.unComment.textContent = `sed -i -r 's/#?;#//g' ${sitesAvailable}`;
|
||||
|
||||
this.$nextTick(() => Prism.highlightElement(this.$refs.commentOut, true));
|
||||
this.$nextTick(() => Prism.highlightElement(this.$refs.unComment, true));
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@@ -38,10 +38,10 @@ export default (query, domains, global, nextTick) => {
|
||||
ignoreQueryPrefix: true,
|
||||
allowDots: true,
|
||||
decoder(value) {
|
||||
value = decodeURIComponent(value);
|
||||
|
||||
// If it's a set of digits, parse it as a float
|
||||
if (/^(\d+|\d*\.\d+)$/.test(value)) {
|
||||
return parseFloat(value);
|
||||
}
|
||||
if (/^(\d+|\d*\.\d+)$/.test(value)) return parseFloat(value);
|
||||
|
||||
// If it matches a keyword, convert it
|
||||
let keywords = {
|
||||
@@ -50,9 +50,7 @@ export default (query, domains, global, nextTick) => {
|
||||
null: null,
|
||||
undefined: undefined,
|
||||
};
|
||||
if (value in keywords) {
|
||||
return keywords[value];
|
||||
}
|
||||
if (value in keywords) return keywords[value];
|
||||
|
||||
// Otherwise, leave it as is
|
||||
return value;
|
||||
|
7
src/nginxconfig/util/prism_bundle.js
Normal file
7
src/nginxconfig/util/prism_bundle.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import 'prismjs';
|
||||
import 'prismjs/components/prism-nginx';
|
||||
import 'prismjs/components/prism-bash';
|
||||
import 'prismjs/plugins/keep-markup/prism-keep-markup';
|
||||
import 'prismjs/plugins/toolbar/prism-toolbar';
|
||||
import 'prismjs/plugins/toolbar/prism-toolbar.css';
|
||||
import 'prismjs/plugins/copy-to-clipboard/prism-copy-to-clipboard';
|
Reference in New Issue
Block a user