Greetings, comrades. On my production servers, a wonderful has been running since 2006, and over the years of managing it, I've accumulated many configs and templates. I've praised nginx a lot, and it so happened that I even created the nginx hub on Habr as well.
Friends asked me to set up a development farm for them, and instead of hauling over my specific templates, I remembered an interesting project , which organizes configs neatly and prepares everything for Let's Encrypt, etc. I thought, why not? However, I was annoyed by the fact that nginxconfig offers me to download a zip file in the browser, not allowing me to pull it directly to the server via wget/fetch/curl. What nonsense is this? Why do I need it in the browser? I need it on the server from the console. Annoyed, I dove into GitHub to look at the project's internals, which led to forking it and, as a consequence, a pull request. I wouldnāt have written about it if it weren't interesting š
Of course, before digging into the source code, I checked where Chrome pulls the generated zip file of configs from, and there I found an address starting with 'blob:', oh hey. It was already clear that the service doesn't generate anything; in fact, it's all done by JS. Indeed, the zip file is generated by the clientāthe browser, using JavaScript. The beauty of the project is that it can simply be saved as an HTML page, uploaded to some , and it will work ) This is a very funny and interesting solution, but itās terribly inconvenient for setting up servers, which is precisely what this project was created for. Downloading the generated archive through the browser and then transferring 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 began to think about what options I had. The task was complicated by the fact that I didn't 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 in Node.js and make it generate the archive with the configs from direct links.
In reality, there weren't many options. More precisely, only one came to mind. We need to set up the configs and get a link that we can copy into the server console to get the zip archive.
Several text files in the received zip archive weighed very little, literally only a few kilobytes. An obvious solution was to obtain a base64 string from the generated zip archive and place it in the clipboard, while on the server, using the command line.
echo 'base64string' | base64 --decode > config.zipwe could create this very zip file.
was written in AngularJS; I can't even imagine how many kilometers of code would have been required if the author hadn't chosen a reactive JS framework. However, I can easily envision how much simpler and more elegantly it could have been implemented in VueJS, though thatās an entirely different topic.
In the project sources, we see the 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 a zip is created, where the configuration files are placed. After creating the zip archive, the JS feeds it to the browser using the library :
saveAs(content, 'nginxconfig.io-' + $scope.getDomains().join(',') + '.zip');
where content is the obtained blob object of the zip archive.
Okay, all I needed to do was add another button next to it, and when pressing it, instead of saving the obtained zip archive in the browser, get the base64 code from it. After a bit of tweaking, 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,/, '');
// in the variable base64 is just the zip archive I need in the form of a base64 string
}
});
gtag('event', $scope.getDomains().join(','), {
event_category: 'download_base64',
});
};
As you may have noticed, I moved the generation of the zip archive itself into a private method called generateZip. Since this is AngularJS, and the author relies on callbacks, I opted not to implement it using promises. The downloadZip still calls saveAs on output, while downloadBase64 does something a bit different. We create a FileReader object, which has come to us in HTML5 and is quite usable. for use. It can convert blobs into base64 strings; specifically, it generates a DataURL string, but that's not crucial for us since the DataURL contains exactly what we need. Bingo, a slight hiccup awaited me when I tried to place all this into the clipboard. The author used the library , which allows working with the clipboard without flash objects, based on selected text. Initially, I decided to place my base64 in an element with display:none;, but in that case, I couldn't copy it to the clipboard as no selection occurs. Therefore, instead of display:none;, I set
position: absolute;
z-index: -1;
opacity: 0;
which allowed me to hide the element from view while actually keeping it on the page. Voila, the task was accomplished; when I clicked my button, a string of the type was placed in the clipboard:
echo 'base64string' | base64 --decode > config.zipwhich I simply pasted into the console on the server and immediately received the zip archive with all configurations.
And, of course, I submitted a pull request to the author since the project is active and alive. I want to see updates from the author and have my button as well. For those interested, here is of the project and the , where you can see what I've fixed/added.
Happy coding to everyone)
Source: habr.com
