Configuratiegeneratie voor nginx, het verhaal van één pull request

Hallo vrienden. Op mijn servers draait het prachtig met alles wat daarin al aanwezig is, en de inhoud van de map sinds 2006 en door de jaren heen heb ik veel configuraties en sjablonen verzameld. Ik heb veel lof gehad voor nginx en zo is het gekomen dat ik zelfs de nginx hub op Habr heb opgezet, wat een show-off m/
Vrienden vroegen me om een ontwikkelingsboerderij op te zetten en in plaats van mijn specifieke sjablonen naar hen te sturen, herinnerde ik me een interessant project nginxconfig.io, dat configuraties netjes organiseert en alles voorbereidt voor Let's Encrypt en zo verder. Ik dacht: waarom niet? Maar het irriteerde me dat nginxconfig me vroeg om een zip-archief in de browser te downloaden, waardoor ik het niet rechtstreeks met wget/fetch/curl op mijn server kon krijgen. Wat een onzin, waarom zou ik het in de browser willen, ik heb het op de server via de console nodig. Gefrustreerd ging ik naar GitHub om de interne werking van het project te bekijken, wat leidde tot een fork en, als gevolg daarvan, een pull request. Waarover ik niet had willen schrijven als het niet interessant was geweest 😉

Configuratiegeneratie voor nginx, het verhaal van één pull request

Natuurlijk, voordat ik in de bronbestanden begon te rommelen, keek ik waar Chrome het gegenereerde zip-archief met de configuraties vandaan haalt, en daar wachtte een adres op me dat begon met "blob:", oh kijk. Het werd al duidelijk dat de service eigenlijk niets genereerde; het was in feite allemaal javascript. Inderdaad, het zip-archief wordt door de client, de browser, javascript gegenereerd. Dat wil zeggen, het mooie is dat het project nginxconfig.io gewoon kan worden opgeslagen als een html-pagina, geüpload naar een of andere narod.ru en het zal werken ) Dit is een zeer grappige en interessante oplossing, maar het is ontzettend onhandig voor het configureren van servers, precies waarvoor dit project is gemaakt. Het downloaden van het gegenereerde archief via de browser en het vervolgens naar de server overbrengen met nc… in 2019? Ik stelde mezelf de taak om een manier te vinden om de gegenereerde configuratie direct naar de server te uploaden.
Na het maken van een fork van het project begon ik na te denken over de mogelijkheden die ik had. De uitdaging werd bemoeilijkt doordat ik niet wilde afwijken van de voorwaarde dat het project een schone front-end moest blijven, zonder enige back-end. Natuurlijk zou de eenvoudigste oplossing zijn om nodejs te gebruiken en het te dwingen om het archief met configuraties te genereren met directe links.
Er waren in feite niet veel mogelijkheden. Sterker nog, er kwam maar één idee bij me op. We moeten de configuraties instellen en een link verkrijgen die we in de console van de server kunnen plakken om het zip-archief te krijgen.
Enkele tekstbestanden in het ontvangen zip-archief waren heel klein, letterlijk een paar kilobytes. Een voor de hand liggende oplossing was om een base64-string te verkrijgen van het gegenereerde zip-archief en deze in het geheugen te plaatsen, terwijl op de server de opdracht in de console werd gegeven.

echo 'base64string' | base64 --decode > config.zip

we zouden dit zip-bestand kunnen aanmaken.

nginxconfig.io werd geschreven in AngularJS, ik kan me niet voorstellen hoeveel kilometers code nodig zouden zijn geweest als de auteur geen reactieve js-framework had gekozen. Maar ik kan me heel goed voorstellen hoe veel eenvoudiger en mooier het allemaal had kunnen worden geïmplementeerd in VueJS, hoewel dat weer een heel ander onderwerp is.
In de projectbronnen zien we de methode voor het genereren van het zip-archief:

$scope.downloadZip = function() {
	var zip = new JSZip();

	var sourceCodes = $window.document.querySelectorAll('main .file .code.source');

	for (var i = 0; i < sourceCodes.length; i++) {
		var sourceCode = sourceCodes[i];

		var name	= sourceCode.dataset.filename;
		var content	= sourceCode.children[0].children[0].innerText;

		if (!$scope.isSymlink() && name.match(/^sites-available\/)) {
			name = name.replace(/^sites-available\//, 'sites-enabled/');
		}

		zip.file(name, content);

		if (name.match(/^sites-available\/)) {
			zip.file(name.replace(/^sites-available\//, 'sites-enabled/'), '../' + name, {
				unixPermissions: parseInt('120755', 8),
			});
		}
	}

	zip.generateAsync({
		type: 'blob',
		platform: 'UNIX',
	}).then(function(content) {
		saveAs(content, 'nginxconfig.io-' + $scope.getDomains().join(',') + '.zip');
	});

	gtag('event', $scope.getDomains().join(','), {
		event_category: 'download_zip',
	});
};

het is allemaal vrij eenvoudig, met behulp van de bibliotheek jszip wordt een zip aangemaakt, waarin configuratiebestanden worden geplaatst. Na het maken van het zip-archief, voedt js het aan de browser met behulp van de bibliotheek FileSaver.js:

saveAs(content, 'nginxconfig.io-' + $scope.getDomains().join(',') + '.zip');

waarbij content het verkregen blob-object van het zip-archief is.

Oké, alles wat ik hoefde te doen, was nog een knop erbij te plaatsen en bij het klikken daarop het verkregen zip-archief niet in de browser op te slaan, maar de base64-code te verkrijgen. Na wat knutselen kreeg ik 2 methoden in plaats van één downloadZip:

$scope.downloadZip = function() {
	generateZip(function (content) {
		saveAs(content, 'nginxconfig.io-' + $scope.getDomains().join(',') + '.zip');
	});

	gtag('event', $scope.getDomains().join(','), {
		event_category: 'download_zip',
	});
};
$scope.downloadBase64 = function() {
	generateZip(function (content) {
		var reader = new FileReader();
		reader.readAsDataURL(content);
		reader.onloadend = function() {
			var base64 = reader.result.replace(/^data:.+;base64,/, '');
			// in de variabele base64 zit precies het zip-archief dat ik nodig had in de vorm van een base64-string
		}
	});

	gtag('event', $scope.getDomains().join(','), {
		event_category: 'download_base64',
	});
};

Zoals je hebt kunnen merken, heb ik de generatie van het zip-bestand naar een privé-methode generateZip verplaatst. Aangezien dit AngularJS is en de auteur vasthoudt aan callbacks, heb ik ervoor gekozen om het niet met promises te implementeren. downloadZip blijft saveAs gebruiken, terwijl downloadBase64 iets anders deed. We creëren een FileReader-object, dat we in html5 hebben ontvangen. beschikbaar Dit kan, in zijn tijd, een blob omzetten in een base64-string, om precies te zijn, maakt het een DataURL-string, maar dat is voor ons minder belangrijk, aangezien DataURL bevat precies wat we nodig hebben. Bingo, er wachtte een kleine uitdaging op me toen ik probeerde dit alles in het klembord te plaatsen. De auteur gebruikte in het project de bibliotheek clipboardjs, die het mogelijk maakt om met het klembord te werken zonder flash-objecten, op basis van de geselecteerde tekst. Aanvankelijk besloot ik mijn base64 in een element met display:none; te plaatsen, maar in dat geval kon ik het niet in het klembord plaatsen, omdat er geen selectie plaatsvond. Daarom, in plaats van display:none;, heb ik

position: absolute;
z-index: -1;
opacity: 0;

geïnteresseerd, wat me in staat stelde het element uit het zicht te verbergen en het effectief op de pagina te laten staan. Hooray, de taak is voltooid; bij het klikken op mijn knop wordt de string toegevoegd aan het klembord zoals:

echo 'base64string' | base64 --decode > config.zip

die ik gewoon in de console op de server plakte en onmiddellijk een zip-bestand met alle configuraties ontving.
Natuurlijk heb ik de auteur een pull request gestuurd, aangezien het project actief en levendig is. Ik wil updates van de auteur zien en mijn knop hebben. Voor degenen die geïnteresseerd zijn, hier is mijn fork van het project en zelf pull request, waar je kunt zien wat ik heb gecorrigeerd/toegevoegd.
Allen veel ontwikkelplezier)

Configuratiegeneratie voor nginx, het verhaal van één pull request

Bron: habr.com

Koop betrouwbare webhosting met bescherming tegen DDoS, VPS VDS servers 🔥 Koop betrouwbare webhosting met bescherming tegen DDoS, VPS VDS servers | ProHoster