Приветствую, товарищи. На моих боевых серверах прекрасный nginx крутится с 2006 года и за годы его администрирования я накопил много конфигов и шаблонов. Я много нахваливал nginx и как-то так вышло, что даже хаб nginx на Хабре тоже завёл я, понты \m/

Друзья попросили поднять им девелоперскую ферму и вместо того, чтобы тащить им свои специфические шаблоны, я вспомнил про интересный проект nginxconfig.io, который и конфиги раскидывает по полкам и для lets encrypt всё готовит итп. Я и подумал, почему бы и нет? Однако, меня бесил тот факт, что nginxconfig предлагает мне скачать zip архив в браузер, не позволяя слить его сразу на сервак средствами wget/fetch/curl. Что за бред, зачем он мне в браузере, мне он нужен на сервере из консоли. Разозлившись, я залез на github посмотреть кишки проекта, что привело к его форку и, как следствие, pull request`у. О котором я бы не стал писать, если бы он не был интересным ;)

image

Конечно, перед тем, как ковырять исходники, я посмотрел откуда хром тянет сгенеренный zip архив с конфигами, а там меня ждал адрес, начинающийся с «blob:», оппа. Уже стало понятно, что по ходу сервис ничего не генерирует, по факту, что это всё делает js. Действительно, zip архив генерирует сам клиент, браузер, javascript. Т.е. прелесть в том, что проект nginxconfig.io может быть просто сохранён как html страница, залит на какой-нибудь narod.ru и он будет работать ) Это очень забавное и интересное решение, однако, оно жутко неудобное для настройки серверов, собственно, именно для того, для чего этот проект и создавался. Качать сгенеренный архив браузером, а потом передавать его на сервер с помощью nc… в 2019 году? Я поставил перед собой задачу найти способ качать полученный конфиг сразу на сервер.

Сделав форк проекта, я начал думать, какие у меня есть варианты. Задача усложнялась тем, что я не хотел отходить от условия, что проект должен оставаться чистым front-end, без какого-либо back-end. Конечно, самое простое решение было бы подтянуть nodejs, и заставить его генерировать архив с конфигами по прямым ссылкам.

Вариантов, собственно, было не много. Точнее, в голову пришёл только один. Нам надо настроить конфиги и получить ссылку, которую сможем скопировать в консоль сервера, чтобы получить zip архив.

Несколько текстовых файлов в получаемом 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');

где content, это полученный 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 автору, т.к. проект активный и живой, мне хочется и обновления от автора видеть и свою кнопочку иметь ) Кому интересно, вот мой форк проекта и сам pull request, где можно посмотреть что я исправил/дополнил.

Всем бодрой разработки :-)

Комментарии (10)


  1. justboris
    18.04.2019 10:18
    +1

    Спасибо что поделились опытом, но, может быть, стоило подождать пока pull-request одобрят и смерджат? Вдруг там в процессе что-нибудь еще выяснится, достойное добавления в статью?


    1. mobilz Автор
      18.04.2019 10:33
      +1

      пфф, этот код не сделать идеальней ;)


      1. therb1
        18.04.2019 22:59
        +1

        image


  1. remzalp
    18.04.2019 10:49

    Я не совсем понял цель всего этого — Вам просто не нравится выгружать файл на сервер?


    Потому что Ваше решение особо ничего удобного к процессу не добавило кроме странного костыля, который просто позволяет более удобно файл текст передать через буфер обмена.


    Такие варианты рассматривались?


    1. WinSCP — CTRL+C -> CTRL+V


    2. Bitvise SSH Client — активируем файловый клиент, потом CTRL+C -> CTRL+V


    3. Putty PSCP/PSFTP + Plink, потребуется настроить сертификаты, потом батником из командной строки:


      pscp -i key.ppk config.zip user@host:~/config.zip
      plink -i key user@host unzip ~/coinfig.zip

      причем второй строкой можно запускать скрипт на сервере, который вообще всё дальше сам сделает, аккуратно разложив файлы по местам и перезагрузив всё нужное.


    4. PhantomJS — не силён, но подозреваю, он сам из консоли сервера по ссылке зайдет и сохранит сгенеренный файл, если его попросит правильно программист, так что можно прямо на сервере запускать



    1. mobilz Автор
      18.04.2019 10:57

      кажется, вы не поняли сути. да, можно пользоваться wins p, putty, я там даже написал, что юзаю nc для этого, но это избыточно. мне нужен конфиг на сервере и я не хочу выступать прокси сервером. но т.к. это чистый html/js, прямой ссылки для скачивания зипа нет и не будет, для этого нужна данная хитрость.

      вам не нужен winscp или другие средства, вы просто сгенерили конфиг, нажали кнопку, перешли в консоль и вставили текст из буфера в косоль


      1. remzalp
        18.04.2019 11:13

        Я долго вчитывался, пытаясь понять — как же сгенеренный браузером файл отдать в curl/wget, но чуда не случилось :)
        Просто более удобное проксирование :(


        Костыли для большего удобства наше всё :) Да, спасибо за ссылку на сервис.


        1. mobilz Автор
          18.04.2019 11:21

          Сравним количество действий, чтобы получить конфиг на сервере?
          В моём случае я часто пользуюсь netcat для подобных целей:


          1. скачать zip на локальную тачку
          2. на сервере запускаем
            nc -l 31337 > config.zip
          3. на клиенте запускаем
            nc 1.2.3.4 31337 < ~/Downloads/config.zip

          не так и сложно, верно? В вашем случае с winscp/putty итп нужно будет ещё запускать и коннектится через эти эти утилиты. А теперь сравните с набором действий, что предлагаю я:


          1. Жмём на кнопку на сайте
          2. Жмём Ctrl+V в консоли сервера

          хз, может для вас это не такая частая задача и запустить winscp не проблема раз в год, меня же частенько бесит, что какие-то файлы приходится передавать на сервер через nc


          1. Andrusha
            18.04.2019 13:54

            Кстати, можно смонтировать директорию с сервера по NFS/SSHFS и сохранять файл прямо на неё.


            1. ponich
              18.04.2019 14:24

              Там же все на фронте. Сервер только статику отдает


              1. mobilz Автор
                18.04.2019 14:30

                речь про то, что на винде можно примонтировать (да и не только на винде, но автор коммента говорит о винде) удалённую sshfs (обычный ssh/sftp туннель) и сохранять сгенерированный конфиг сразу на примонтированный диск. Таких решений можно придумать много, но смысл поста был в том, чтобы вообще не пользоваться внешними ресурсами