Generowanie konfiguracji dla nginx, historia jednego pull requesta

Witam, towarzysze. Na moich serwerach bojowych działa doskonale nginx od 2006 roku, a przez lata administracji uzbierałem wiele konfiguracji i szablonów. Wiele razy chwaliłem nginx i jakoś tak się złożyło, że nawet stworzyłem hub nginx na hackernoonie.
Przyjaciele poprosili mnie o zbudowanie farmy deweloperskiej i zamiast przynosić im swoje specyficzne szablony, przypomniałem sobie o ciekawym projekcie nginxconfig.io, który rozdziela pliki konfiguracyjne i przygotowuje wszystko dla lets encrypt itd. Pomyślałem, czemu by nie? Jednak denerwowało mnie to, że nginxconfig oferuje mi pobranie archiwum zip w przeglądarce, nie pozwalając na bezpośrednie pobranie na serwer za pomocą wget/fetch/curl. Co za bzdura, po co mi to w przeglądarce, potrzebuję to na serwerze z konsoli. Wkurzyłem się, zajrzałem na github, żeby zobaczyć wnętrzności projektu, co doprowadziło do jego forka i, w konsekwencji, pull requestu. O którym bym nie pisał, gdyby nie był interesujący 😉

Generowanie konfiguracji dla nginx, historia jednego pull requesta

Oczywiście, zanim zacząłem grzebać w kodzie źródłowym, sprawdziłem, skąd Chrome pobiera wygenerowane archiwum zip z konfiguracjami, a tam czekał na mnie adres zaczynający się od „blob:”, oho. Już stało się jasne, że usługa nic nie generuje, w rzeczywistości wszystko robi javascript. Rzeczywiście, archiwum zip generuje sam klient, przeglądarka, javascript. Tzn. urok projektu nginxconfig.io może polegać na tym, że może być po prostu zapisany jako strona html, umieszczony na jakiejś narod.ru i będzie działał :) To bardzo zabawne i ciekawe rozwiązanie, jednak jest strasznie niewygodne do konfigurowania serwerów, właśnie po to ten projekt został stworzony. Pobierać wygenerowane archiwum przez przeglądarkę, a potem przesyłać je na serwer za pomocą nc… w 2019 roku? Postawiłem przed sobą zadanie, aby znaleźć sposób na pobranie otrzymanej konfiguracji bezpośrednio na serwer.
Zrobiwszy forka projektu, zacząłem myśleć, jakie mam możliwości. Problem się komplikuje, ponieważ nie chciałem odchodzić od założenia, że projekt powinien pozostać czystym front-endem, bez jakiegokolwiek back-endu. Oczywiście, najprostszym rozwiązaniem byłoby wciągnięcie nodejs i zmuszenie go do generowania archiwum z konfiguracjami na podstawie bezpośrednich linków.
Opcji tak naprawdę było niewiele. Aż do głowy przyszła mi tylko jedna. Musimy skonfigurować pliki konfiguracyjne i uzyskać link, który będziemy mogli skopiować do konsoli serwera, aby otrzymać archiwum zip.
Kilka plików tekstowych w otrzymanym archiwum zip ważyło zaledwie kilka kilobajtów. Oczywistym rozwiązaniem było uzyskanie ciągu base64 z wygenerowanego archiwum zip i wrzucenie go do schowka, podczas gdy na serwerze za pomocą polecenia w konsoli.

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

moglibyśmy utworzyć ten archiwum zip.

nginxconfig.io został napisany w AngularJS, nawet nie wyobrażam sobie, ile kilometrów kodu byłoby potrzebnych, gdyby autor nie wybrał reaktywnego frameworka js. Ale wyobrażam sobie, jak dużo prościej i piękniej można byłoby to wszystko zrealizować w VueJS, chociaż to już zupełnie inna historia.
W źródłach projektu widzimy metodę generowania archiwum zip:

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

wszystko jest dość proste, za pomocą biblioteki jszip tworzy się zip, do którego trafiają pliki konfiguracyjne. Po utworzeniu archiwum zip, js przekazuje je przeglądarce za pomocą biblioteki FileSaver.js:

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

gdzie content to otrzymany obiekt blob archiwum zip.

Ok, wszystko co musiałem zrobić, to dodać jeszcze jeden przycisk obok i po naciśnięciu na niego nie zapisywać uzyskanego archiwum zip w przeglądarce, a uzyskać z niego kod base64. Po kilku zabiegach otrzymałem 2 metody zamiast jednej 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,/, '');
			// w zmiennej base64 znajduje się potrzebne mi archiwum zip w postaci ciągu base64
		}
	});

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

Jak mogliście zauważyć, generację samego archiwum zip przeniosłem do prywatnej metody generateZip, a ponieważ jest to AngularJS i sam autor stosuje callbacki, nie zdecydowałem się na wdrożenie tego za pomocą obietnic. downloadZip nadal wykonywał saveAs, podczas gdy downloadBase64 robił nieco co innego. Tworzymy obiekt FileReader, który pojawił się w HTML5 i jest już całkiem dostępny do użycia. Który, w swoim czasie, potrafi przekształcić blob w ciąg base64, dokładniej tworzy on ciąg DataURL, ale to nie ma dla nas tak dużego znaczenia, ponieważ DataURL zawiera dokładnie to, czego potrzebujemy. Bingo, mała zagwozdka czekała na mnie przy próbie umieszczenia tego wszystkiego w schowku. Autor użył w projekcie biblioteki clipboardjs, która pozwala pracować ze schowkiem bez obiektów flash, w oparciu o zaznaczony tekst. Początkowo postanowiłem umieścić mój base64 w elemencie z display:none;, ale w takim przypadku nie udało mi się umieścić go w schowku, ponieważ zaznaczenie się nie pojawia. Dlatego zamiast display:none; zrobiłem

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

co pozwoliło mi ukryć element z oczu i właściwie pozostawić go na stronie. Voilà, zadanie wykonane, po naciśnięciu mojego przycisku, do schowka trafiała ciąg o postaci:

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

który po prostu wkleiłem w konsolę na serwerze i od razu otrzymałem zip archiwum ze wszystkimi konfiguracjami.
No i, oczywiście, wysłałem pull request do autora, ponieważ projekt jest aktywny i żywy, chcę widzieć aktualizacje od autora i mieć swój przycisk ) Dla zainteresowanych, oto mój fork projektu i sam pull request, gdzie można zobaczyć, co poprawiłem/uzupełniłem.
Wszystkim życzliwego programowania )

Generowanie konfiguracji dla nginx, historia jednego pull requesta

Źródło: habr.com

Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS 🔥 Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS | ProHoster