--- title: 'Déployer Hugo via Gitea et Drone-CI' date: '2021-09-12' tags: - Gitea - Hugo - Drone-CI - Sysadmin - Docker - Auto-hébergement - Git --- ## Pourquoi faire simple quand on peut faire compliqué ? De prime-abord, on pourrait se dire : "si je veux un blog, je lance un WordPress comme la moitié du web et je commence à publier en deux minutes". Mais quand on est un vieux routard du web, on a des aspirations et des principes un peu plus élitistes : - WordPress ne génère pas de sites statiques (nativement) - Le fait qu'il soit utilisé [par la moitié d'Internet](https://w3techs.com/technologies/overview/content_management) me révèle que c'est une cible facile pour des personnes ou des robots mal-intentionnés - PHP est indispensable pour faire fonctionner WordPress, même si WordPress est configuré pour générer des pages pseudo-statiques - WordPress est une usine à gaz, qui sort du HTML, du CSS et du Javascript pas optimisés - Le code de WordPress est abominablement dégueulasse, impossible de faire quoique ce soit de propre, ni même de léger - WordPress est impersonnel, ce que je trouve embêtant pour un blog (et encore plus pour un site corporate d'ailleurs) : il doit être le reflet de son auteur ; s'il ressemble à tous les autres, quel intérêt ? (et je vous le dis : ce n'est pas simplement parce que vous customisez un thème qu'on ne voit pas que vous utilisez WordPress...) - WordPress a besoin d'une base de données - L'écrasante majorité des extensions tierces un tant soit peu intéressantes sont payantes ou sur l'immonde modèle du premium Un générateur de sites statiques comme Hugo me permet de : - Séparer l'apparence, le contenu, et l'outil qui construit le site final (je peux utiliser autre chose qu'Hugo si j'en ai envie, moyennant quelques ajustement mineurs). Impossible de faire ça avec WordPress - Publier un site 100% sécurisé : sans PHP, sans formulaires, sans Javascript, le maillon faible de la sécurité de mon site n'est pas mon site - Avoir un suivi de mes modifications (grâce à Git) - Simplifier les processus de sauvegarde et de restauration (en gros, avoir un plan de relance en béton armé en cas de défaillance technique - je peux même faire héberger temporairement mon site ailleurs sans avoir besoin d'aucune autre dépendance qu'un serveur web, impossible de faire ça avec WordPress en un minimum de temps) - Être réellement libre du point de vue logiciel L'utilisation de git dans ce processus est totalement facultative : il est tout à fait possible de construire son site avec Hugo sans tout ce que je vais présenter dans cet article. Néanmoins, cette procédure qui semble fastidieuse de prime-abord est en réalité très puissante et très confortable au quotidien : - Je créé ou modifie mes articles sous la forme de fichiers [Markdown](https://gohugo.io/content-management/formats/) - pratiquement du texte brut - Je les publie sur [ma forge Gitea](https://git.athaliasoft.com/) - Drone-CI fait le reste : construire le site en lançant l'exécutable Hugo et envoyer les fichiers HTML et CSS au serveur de production Pour peu qu'on choisisse un thème existant plutôt qu'en créer un, on peut **réellement** ne se préoccuper que du contenu de son site. ## Composant logiciels Tout passe par des containers (docker, évidemment, mais l'utilisation de podman est possible). Je présume donc que docker est installé et fonctionnel. ### Gitea [Gitea](https://gitea.io/en-us/) est une _forge logicielle_ qui s'appuie sur le système de gestion de code [Git](https://git-scm.com). J'ai choisi Gitea pour sa sobriété, sa légèreté et sa simplicité, mais il existe d'autres forges logicielles : [GitHub](https://github.com) qui appartient à Microsoft et qu'il n'est pas possible d'auto-héberger, ou [Gitlab](https://about.gitlab.com) pour ne citer que ces deux-là. _docker-compose.yml_ : ```yaml {class=not-prose} version: "3" services: gitea: image: gitea/gitea:latest restart: always ports: - "${HTTP_PORT}:3000" - "${SSH_PORT}:22" volumes: - ${DATA_DIR}:/data - /etc/timezone:/etc/timezone:ro - /etc/localtime:/etc/localtime:ro ``` Remplacez les variables en fonction de vos besoins : - `HTTP_PORT` : le port sur lequel les requêtes HTTP arrivent sur votre serveur (pas le port HTTP du container) - `SSH_PORT` : le port du serveur SSH du serveur (pas le port SSH du container) - `DATA_DIR` : le chemin complet du dossier dans lequel stocker les données de Gitea, ou le volume si vous préférez utiliser un volume ### Hugo [Hugo](https://gohugo.io) est un générateur de sites statiques. C'est ce que j'utilise pour créer et maintenir mon blog. Il en existe d'autres, tels que [Jekyll](https://jekyllrb.com). J'ai choisi Hugo pour sa légèreté. L'essentiel du travail avec Hugo se fait sur votre machine locale. Il n'y a rien à installer côté serveur. La construction du site se fera via Drone-CI, dans lequel on va configurer un [_pipeline_](https://docs.drone.io/pipeline/overview/) pour compiler les ressources CSS (et éventuellement Javascript), et créer l'ensemble du site statique dans un container docker. Tous ces fichiers seront ensuite envoyé au serveur web via rsync, scp, ou ce que vous voulez. Pour faire tout cela, il "suffit" d'ajouter un fichier _.drone.yml_ à la racine de votre projet Hugo avec le contenu suivant : ```yaml {class=not-prose} kind: pipeline type: docker name: default steps: - name: submodules image: alpine/git commands: - git submodule update --init --recursive - name: build image: klakegg/hugo:ext-ci commands: - cd themes/blog_theme && npm i && cd - - hugo --gc --minify --environment production - name: deploy image: appleboy/drone-scp settings: host: "10.0.2.1" target: /mnt/volume1/shares/www/richard-dern.fr/www/ source: public/* username: from_secret: ssh_user password: from_secret: ssh_password trigger: branch: - master ``` L'étape _submodules_ (entre les lignes 7 et 10) n'est nécessaire que si le thème que vous utilisez est dans un _submodule_ git dans le répertoire _theme_ de votre projet Hugo. Si le thème est directement dans l'arborescence de votre projet, vous pouvez supprimer les lignes concernées. Le thème que j'ai créé utilise le framework [Tailwind CSS](https://tailwindcss.com), j'ai donc besoin de compiler les ressources via `npm`, ce que je fais à la ligne 15, juste avant de construire toutes les pages du site. Jusqu'à présent, c'est plutôt simple, non ? Ensuite, l'étape la plus compliquée (_deploy_). Il faut créer des _secrets_ dans Drone-CI (ici, `ssh_user` et `ssh_password`), mais nous verrons cela un peu plus bas. Pensez simplement à modifier les valeurs de _host_ et _target_ : l'adresse IP du serveur qui va héberge Hugo et le chemin où les fichiers générés par Hugo seront envoyés. Ceci dit, ici j'utilise `scp`, mais d'autres services sont disponibles. Consultez la [liste des plugins Drone-CI](http://plugins.drone.io) pour utiliser celui qui convient à votre hébergement. Enfin, on a ajouté la directive [_trigger_](https://docs.drone.io/pipeline/triggers/) pour que tout ce processus de publication ne soit déclenché **que** lorsque la branche git _master_ est modifiée, l'idée étant de protéger la branche _master_ en écriture, modifier le contenu dans une nouvelle branche, fusionner avec _master_ une fois terminé, mais là on part dans des considérations philosophiques devops :smile: ### Drone-CI [Drone-CI](https://www.drone.io) est une application d'intégration continue : c'est le genre d'applications qui s'intercalent entre la forge logicielle et les serveurs de mise en production, une position privilégiée qui confère à ces outils beaucoup de puissance et un intérêt considérable. Tests unitaires, construction de ressources, publication automatique, les cas d'usage sont nombreux. La documentation de Drone-CI préconise de l'installer sur une machine physique différente du serveur Gitea afin d'éviter les problèmes et de simplifier la configuration. C'est l'option que j'ai choisi, mais en pratique, si vous savez ce que vous faites, vous ne devriez pas avoir de soucis. Il faut toutefois prendre en considération les ressources du (des) serveurs. Si vous utilisez des Raspberry Pi par exemple, il vaut peut-être mieux lancer Drone-CI sur un Pi dédié à cet usage. Pour que Drone-CI puisse communiquer avec Gitea, il faut aller dans Gitea, votre profil, puis dans l'onglet _Applications_. Là, créez une application _OAuth_. Mettez ce que vous voulez comme nom ("drone", par exemple), et l'URL de redirection, qui correspond à l'URL complète à laquelle vous accèderez à Drone-CI, suffixée par _/login_. Dans mon cas, l'URL de redirection est : > https://ci.athaliasoft.com/login Validez, et Gitea vous communiquera un certain nombre d'informations à renseigner dans le fichier _docker-compose.yml_ pour Drone-CI, notamment un _Client ID_ et un _Client Secret_, à remplacer ci-dessous. _docker-compose.yml_ : ```yaml {class=not-prose} version: "3" services: drone: image: drone/drone restart: always ports: - "${PORT}:80" environment: - DRONE_GITEA_SERVER=${DRONE_GITEA_SERVER} - DRONE_GITEA_CLIENT_ID=${DRONE_GITEA_CLIENT_ID} - DRONE_GITEA_CLIENT_SECRET=${DRONE_GITEA_CLIENT_SECRET} - DRONE_RPC_SECRET=${DRONE_RPC_SECRET} - DRONE_SERVER_HOST=${DRONE_SERVER_HOST} - DRONE_SERVER_PROTO=https volumes: - ${VOLUME_ROOT}:/data ``` - `DRONE_GITEA_SERVER` correspond à l'URL de votre serveur Gitea - `DRONE_RPC_SECRET` est une clé à générer via la commande `openssl rand -hex 16` (par exemple) - `DRONE_SERVER_HOST` est le nom d'hôte correspondant à l'URL de redirection (donc, dans mon cas spécifique, _drone.git.athaliasoft.com_) - `VOLUME_ROOT`, un volume docker ou un répertoire dans lequel seront stockées les données de Drone-CI ### Runners En plus de Drone-CI, il faut configurer des [_runners_](https://docs.drone.io/runner/overview/), c'est-à-dire un ou plusieurs services qui vont exécuter les pipelines configurés. Comme je dispose de plusieurs serveurs, j'ai installé un runner par serveur, ce qui permet d'améliorer les performances générales de Drone-CI, mais il est tout à fait possible (et viable) d'avoir un seul runner installé sur la même machine que Drone-CI. _docker-compose.yml_ : ```yaml {class=not-prose} version: "3" services: runner: image: drone/drone-runner-docker restart: always volumes: - /var/run/docker.sock:/var/run/docker.sock ports: - "3000:3000" environment: - DRONE_RPC_PROTO=http - DRONE_RPC_HOST=drone - DRONE_RPC_SECRET=${DRONE_RPC_SECRET} - DRONE_RUNNER_CAPACITY=${DRONE_RUNNER_CAPACITY} - DRONE_RUNNER_NAME=${DRONE_RUNNER_NAME} ``` Pensez à modifier les variables suivantes pour convenir à votre environnement : - `DRONE_RPC_SECRET`, la même clé que celle définie plus haut pour Drone-CI - `DRONE_RUNNER_CAPACITY`, le nombre de pipelines que le runner peut exécuter en même temps (1 est suffisant pour des environnements modestes, 2 ou plus pour des environnements plus musclés) - `DRONE_RUNNER_NAME`, un nom à attribuer au runner pour savoir quel runner a exécuté quoi et quand ## Performances En voyant tout ça, on pourrait se dire que c'est consommateur de ressources. En réalité, tout cela est très, très léger, et très performant. Il faut moins de 30 secondes pour déployer en production un nouvel article ; une durée qui serait beaucoup - beaucoup - plus courte s'il n'y avait pas l'étape de compilation CSS avec `npm`. La possibilité de tirer partie d'un nombre indéterminé de runners est un gage de scalabilité, même si les performances sont déjà élevées avec un seul serveur, grâce à la sobriété des applications mises en oeuvre. J'aime ce qui est rapide et performant, même si cela nécessite un peu de temps pour être mis en oeuvre. Le processus expliqué ici respecte mon cahier des charges. ## Conclusion J'ai présenté ici le déploiement automatisé d'un site statique créé avec Hugo, puisant dans un serveur Gitea, en construisant le site via Drone-CI avant de copier les fichiers sur le serveur de production. C'est **un** cas d'usage parmi d'autres : je me sers également de cette configuration pour créer automatiquement des releases pour certaines de mes librairies, par exemple. Le simple fait de parcourir [la liste des plugins](http://plugins.drone.io/) de Drone-CI donne un aperçu de ce qu'il est possible de faire _out-of-the-box_, mais en réalité, il n'y a pas vraiment de limite dans la mesure où on peut facilement créer ses propres plugins, puisque ce ne sont que des containers docker !