Generation of configs for nginx, history of one pull request

Greetings, comrades. Beautiful on my combat servers has been spinning since 2006 and over the years of its administration I have accumulated a lot of configs and templates. I praised nginx a lot and somehow it turned out that I even started the nginx hub on Habré, show off m/
Friends asked me to raise a development farm for them, and instead of dragging them my specific templates, I remembered an interesting project nginxconfig.io, which scatters the configs on the shelves and prepares everything for lets encrypt, etc. I thought, why not? However, I was infuriated by the fact that nginxconfig offers me to download the zip archive into the browser, not allowing me to merge it directly to the server using wget/fetch/curl. What nonsense, why do I need it in the browser, I need it on the server from the console. Angered, I went to github to look at the guts of the project, which led to its fork and, as a result, a pull request. Which I wouldn't write about if it wasn't interesting 😉

Generation of configs for nginx, history of one pull request

Of course, before picking the sources, I looked where chrome was pulling the generated zip archive with configs from, and there the address starting with “blob:” was waiting for me, oppa. It has already become clear that the service does not generate anything along the way, in fact, js does it all. Indeed, the zip archive is generated by the client itself, the browser, javascript. Those. the beauty is that the project nginxconfig.io can be simply saved as an html page, uploaded to some narod.ru and it will work ) This is a very funny and interesting solution, however, it is terribly inconvenient for setting up servers, in fact, for exactly what this project was created for. Download the generated archive with a browser, and then transfer it to the server using nc ... in 2019? I set myself the task of finding a way to download the resulting config directly to the server.
After forking the project, I started thinking about my options. The task was complicated by the fact that I did not want to deviate from the condition that the project should remain a clean front-end, without any back-end. Of course, the simplest solution would be to pull up nodejs and make it generate an archive with configs via direct links.
There weren't many options, actually. In fact, only one came to mind. We need to set up the configs and get a link that we can copy to the server console to get the zip archive.
Several text files in the resulting zip archive weighed quite a bit, just a few kilobytes. The obvious solution was to get the base64 string from the generated zip archive and throw it into the buffer, while on the server with the command in the console

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

we could create this same zip file.

nginxconfig.io was written in AngularJS, I can’t even imagine what kilometers of code would have been required if the author hadn’t chosen a reactive js framework. But I can perfectly imagine how much easier and more beautiful it would be to implement all this on VueJS, although this is a completely different topic.
In the project resources, we see a method for generating a zip archive:

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

everything is quite simple, using the library jszip a zip is created, where the configuration files are placed. After creating a zip archive, js feeds it to the browser using the library FileSaver.js:

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

where content is the received blob object of the zip archive.

Ok, all I had to do was add another button next to it and when you click on it, do not save the resulting zip archive to the browser, but get the base64 code from it. With a little trickery, I got 2 methods, instead of one 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',
	});
};

As you may have noticed, I moved the generation of the zip archive itself into the private generateZip method, and so on. this is AngularJS, and the author himself adheres to callbacks, did not implement it through promises. downloadZip still did saveAs in the output, while downloadBase64 did a slightly different thing. We create a FileReader object that came to us in html5 and is already quite available for use. Which, at one time, can make a base64 string from a blob, or rather, it makes a DataURL string, but this is not so important for us, because The DataURL contains exactly what we need. Bingo, there was a little snag when I tried to put it all in the buffer. The author used the library in the project clipboardjs, which allows you to work with the clipboard without flash objects, based on the selected text. Initially, I decided to put my base64 in an element with display:none;, but in this case, I could not put it on the clipboard, because extraction does not occur. Therefore, instead of display:none; I did

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

which allowed me to hide the element from view and in fact leave it on the page. Voila, the task is completed, when I clicked on my button, a line like this was placed in the buffer:

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

which I just inserted into the console on the server and immediately received a zip archive with all the configs.
And, of course, I threw a pull request to the author, because. the project is active and lively, I want to see updates from the author and have my own button) For those who are interested, here my fork project and myself Pull request, where you can see what I corrected / supplemented.
All vigorous development)

Generation of configs for nginx, history of one pull request

Source: habr.com

Add a comment