Génération de configurations pour nginx, l'histoire d'une pull request

Bonjour, camarades. Sur mes serveurs de production, un magnifique nginx fonctionne depuis 2006 et au fil des ans, j'ai accumulé de nombreuses configurations et modèles. J'ai beaucoup loué nginx et il se trouve même que j'ai créé le hub nginx sur Habr, un peu de frime m/
Des amis m'ont demandé de mettre en place une ferme de développement et au lieu de leur apporter mes modèles spécifiques, je me suis souvenu d'un projet intéressant nginxconfig.io, qui organise les configurations et prépare tout pour lets encrypt, etc. J'ai pensé, pourquoi pas ? Cependant, j'étais irrité par le fait que nginxconfig me propose de télécharger une archive zip dans le navigateur, me permettant pas de le transférer directement sur le serveur par wget/fetch/curl. Quelle absurdité, pourquoi en aurais-je besoin dans le navigateur ? Je en veux sur le serveur depuis la console. Ennuyé, je suis allé sur github pour voir les entrailles du projet, ce qui a conduit à son fork et, par conséquent, à une demande de tirage. Dont je n'aurais pas parlé s'il n'était pas intéressant 😉

Génération de configurations pour nginx, l'histoire d'une pull request

Bien sûr, avant de fouiller dans le code source, j'ai vérifié d'où Chrome télécharge l'archive zip générée avec les configurations, et là, j'ai trouvé une adresse commençant par « blob: », étonnant. On a déjà compris que le service ne génère rien, en fait, tout cela est fait par js. En effet, c'est le client, le navigateur, javascript qui génère l'archive zip. En d'autres termes, la beauté du projet nginxconfig.io peut simplement être sauvegardée comme une page html, téléchargée sur un site comme narod.ru et elle fonctionnera ) C'est une solution très amusante et intéressante, cependant, elle est extrêmement peu pratique pour configurer des serveurs, en fait, précisément pour cela que ce projet a été créé. Télécharger l'archive générée via le navigateur, puis la transférer sur le serveur avec nc... en 2019 ? Je me suis fixé comme objectif de trouver un moyen de télécharger la configuration obtenue directement sur le serveur.
Ayant effectué un fork du projet, j'ai commencé à réfléchir aux options dont je disposais. La tâche était compliquée par le fait que je ne voulais pas m'éloigner de la condition que le projet doive rester un front-end pur, sans aucun back-end. Bien sûr, la solution la plus simple aurait été de tirer nodejs et de le forcer à générer l'archive de configurations à partir de liens directs.
Il n'y avait en fait pas beaucoup d'options. En fait, une seule m'est venue à l'esprit. Nous devons configurer les configurations et obtenir un lien que nous pourrions copier dans la console du serveur pour obtenir l'archive zip.
Quelques fichiers texte dans l'archive zip reçue pesaient très peu, à peine quelques kilooctets. Une solution évidente était d'obtenir la chaîne base64 de l'archive zip générée et de la mettre dans le tampon, tandis que sur le serveur, la commande dans la console

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

nous pourrions créer ce fichier zip.

nginxconfig.io était écrit en AngularJS, je ne peux même pas imaginer combien de kilomètres de code seraient nécessaires si l'auteur n'avait pas choisi un framework JS réactif. En revanche, je peux parfaitement imaginer à quel point tout cela aurait été plus simple et plus beau à réaliser sur VueJS, bien que ce soit là un tout autre sujet.
Dans les sources du projet, nous voyons une méthode pour générer une archive 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',
	});
};

tout est assez simple, avec l'aide de la bibliothèque jszip un zip est créé, où les fichiers de configuration sont placés. Après la création de l'archive zip, js l'envoie au navigateur grâce à la bibliothèque FileSaver.js:

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

où content est l'objet blob obtenu de l'archive zip.

D'accord, tout ce que je devais faire était d'ajouter un autre bouton à côté et, en cliquant dessus, de ne pas enregistrer l'archive zip obtenue dans le navigateur, mais d'en obtenir le code base64. Après avoir un peu bricolé, j'ai obtenu 2 méthodes, au lieu d'une, 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,/, '');
			// dans la variable base64 se trouve exactement l'archive zip dont j'ai besoin sous forme de chaîne base64
		}
	});

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

Comme vous avez pu le remarquer, la génération du fichier zip lui-même a été déléguée à une méthode privée generateZip, et comme il s'agit d'AngularJS, et que l'auteur utilise des callbacks, je n'ai pas choisi de l'implémenter via des promesses. La méthode downloadZip continue d'utiliser saveAs, tandis que downloadBase64 fait quelque chose d'un peu différent. Nous créons un objet FileReader, arrivé avec HTML5 et déjà tout à fait disponible prêt à être utilisé. Celui-ci sait, à l'époque, transformer un blob en chaîne base64, en réalité, il génère une chaîne DataURL, mais cela n'a pas d'importance pour nous, car la DataURL contient exactement ce dont nous avons besoin. Bingo, j'ai rencontré une petite complexité en essayant de placer tout cela dans le presse-papiers. L'auteur a utilisé dans le projet la bibliothèque clipboardjs, qui permet de travailler avec le presse-papiers sans objets Flash, en se basant sur le texte sélectionné. Au départ, j'avais décidé de mettre ma base64 dans un élément avec display:none;, mais dans ce cas, je ne parvenais pas à y placer quoi que ce soit dans le presse-papiers, car il n'y avait pas de sélection. Ainsi, au lieu de display:none;, j'ai fait

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

ce qui m'a permis de cacher l'élément des yeux tout en le laissant sur la page. Voilà, mission accomplie, lorsque j'appuyais sur mon bouton, la chaîne était placée dans le presse-papiers :

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

que je copiais simplement dans la console sur le serveur et je recevais immédiatement un fichier zip avec toutes les configurations.
Et bien sûr, j'ai soumis une pull request à l'auteur, car le projet est actif et vivant, j'ai envie de voir les mises à jour de l'auteur et d'avoir mon petit bouton) Pour ceux qui sont intéressés, voici mon fork du projet et la pull request, où vous pouvez voir ce que j'ai corrigé/ajouté.
À tous, bon développement )

Génération de configurations pour nginx, l'histoire d'une pull request

Source : habr.com

Acheter un hébergement fiable pour les sites avec protection DDoS, serveurs VPS VDS 🔥 Acheter un hébergement fiable pour les sites avec protection DDoS, serveurs VPS VDS | ProHoster