Conf diffing?!

This commit is contained in:
MattIPv4
2020-05-22 21:17:01 +01:00
parent c6880c1222
commit 17261e8bbd
9 changed files with 406 additions and 66 deletions

View File

@@ -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>

View File

@@ -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));
},
},
};