Генерація конфігів для nginx, історія одного pull request

Вітаю, товариші. На моїх бойових серверах чудовий Nginx крутиться з 2006 року і за роки його адміністрування я нагромадив багато конфігів та шаблонів. Я багато нахвалював nginx і якось так вийшло, що навіть хаб nginx на хабрі теж завів я, понти m/
Друзі попросили підняти їм девелоперську ферму і замість тягнути їм свої специфічні шаблони я згадав про цікавий проект nginxconfig.io, який і конфіги розкидає по полицях і для lets encrypt все готує ітп. Я й подумав, чому б і ні? Однак, мені дратував той факт, що nginxconfig пропонує мені завантажити zip архів у браузер, не дозволяючи злити його відразу на сервак за допомогою wget/fetch/curl. Що за маячня, навіщо вона мені в браузері, мені вона потрібна на сервері з консолі. Розлютившись, я заліз на github подивитися кишки проекту, що призвело до його форки і, як наслідок, pull request`у. Про яке я б не став писати, якби він не був цікавим 😉

Генерація конфігів для nginx, історія одного pull request

Звичайно, перед тим, як колупати вихідники, я подивився звідки хром тягне згенерований zip архів з конфігами, а там на мене чекав адресу, що починається з «blob:», оппа. Вже стало зрозуміло, що під час сервіс нічого не генерує, за фактом, що це все робить js. Справді, архів zip генерує сам клієнт, браузер, javascript. Тобто. краса в тому, що проект nginxconfig.io може бути просто збережений як html сторінка, залитий на який-небудь narod.ru і він буде працювати) Це дуже кумедне і цікаве рішення, проте, воно дуже незручне для налаштування серверів, власне, саме для того, для чого цей проект і створювався. Завантажити згенерований архів браузером, а потім передавати його на сервер за допомогою nc… у 2019 році? Я поставив собі завдання знайти спосіб качати отриманий конфіг відразу на сервер.
Зробивши форк проекту, я почав думати, які маю варіанти. Завдання ускладнювалося тим, що я не хотів відходити від умови, що проект повинен залишатися чистим front-end без будь-якого back-end. Звичайно, найпростіше рішення було б підтягнути nodejs і змусити його генерувати архів із конфігами за прямими посиланнями.
Варіантів, власне, було небагато. Точніше, на думку прийшов тільки один. Нам потрібно налаштувати конфіги та отримати посилання, яке зможемо скопіювати в консоль сервера, щоб отримати архів zip.
Кілька текстових файлів у архіві, що отримується, важили зовсім небагато, буквально кілька кілобайт. Очевидним рішенням було отримати base64 рядок із згенерованого zip архіву та кинути її в буфер, тоді як на сервері командою в консолі

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

ми могли б створити цей самий zip файл.

nginxconfig.io був написаний на AngularJS, навіть не уявляю, які кілометри коду були б потрібні, якби автор не вибрав реактивний js фреймворк. Зате чудово уявляю, наскільки простіше і красивіше можна було б все це реалізувати на VueJS, хоча це зовсім інша тема.
У сурсах проекту бачимо метод генерації 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',
	});
};

все досить просто, за допомогою бібліотеки jszip створюється zip, куди кладуться файли конфігурацій. Після створення zip архіву, js згодовує його браузеру за допомогою бібліотеки FileSaver.js:

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

де вміст, це отриманий blob об'єкт zip архіву.

Ок, все, що мені треба було зробити, це додати ще одну кнопку поруч і при натисканні на неї не зберігати отриманий zip архів у браузер, а отримувати з нього base64 код. Трохи пошаманивши, я отримав 2 методи, замість одного 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,/, '');
			// в переменной base64 как раз нужный мне zip архив в виде base64 строки
		}
	});

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

Як ви могли помітити, генерацію самого zip архіву я виніс у приватний метод generateZip, та й т.к. це AngularJS, та й сам автор дотримується колбеків, не став реалізовувати його через проміси. downloadZip як і раніше на виході робив saveAs, тоді як downloadBase64 робив трохи інше. Ми створюємо FileReader об'єкт, що прийшов до нас у html5 і цілком уже доступний для використання. Який, свого часу, вміє з blob робити base64 рядок, точніше він робить DataURL рядок, але це не так важливо, т.к. DataURL містить те, що нам потрібно. Бінго, невелика заковирка чекала на мене при спробі покласти все це в буфер. Автор використав у проекті бібліотеку clipboardjsяка дозволяє працювати з буфером обміну без flash об'єктів, на основі виділеного тексту. Спочатку я вирішив класти мій base64 в елемент з display:none;, але в такому випадку мені не виходило покласти його в буфер обміну, т.к. виділення немає. Тому замість display:none; я зробив

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

що дозволило мені приховати елемент з очей і за фактом залишити його на сторінці. Вуаля, завдання виконано, при натисканні на мою кнопку, в буфер містився рядок виду:

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

яку я просто вставляв у консоль на сервері і відразу отримував zip архів з усіма конфігами.
Ну і, звісно, ​​я закинув pull request автору, т.к. проект активний і живий, мені хочеться і оновлення від автора бачити і свою кнопку мати ) Кому цікаво, ось мій форк проекту і сам запит тягнути, де можна подивитися, що я виправив/доповнив.
Всім бадьорої розробки

Генерація конфігів для nginx, історія одного pull request

Джерело: habr.com

Додати коментар або відгук