<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Webhook on Zwindler's Reflection</title><link>https://blog.zwindler.fr/tags/webhook/</link><description>Recent content in Webhook on Zwindler's Reflection</description><generator>Hugo -- gohugo.io</generator><language>fr</language><copyright>Licensed under CC BY-SA 4.0</copyright><lastBuildDate>Thu, 26 Feb 2026 08:00:00 +0200</lastBuildDate><atom:link href="https://blog.zwindler.fr/tags/webhook/index.xml" rel="self" type="application/rss+xml"/><item><title>Kyverno a tué mon API Server. Encore.</title><link>https://blog.zwindler.fr/2026/02/26/kyverno-a-tue-mon-api-server-encore/</link><pubDate>Thu, 26 Feb 2026 08:00:00 +0200</pubDate><guid>https://blog.zwindler.fr/2026/02/26/kyverno-a-tue-mon-api-server-encore/</guid><description>&lt;img src="https://blog.zwindler.fr/2026/02/0_days_without_kyverno.webp" alt="Featured image of post Kyverno a tué mon API Server. Encore." /&gt;&lt;h2 id="le-retour-de-la-vengeance"&gt;Le retour de la vengeance
&lt;/h2&gt;&lt;p&gt;Vous avez peut-être lu &lt;a class="link" href="https://blog.zwindler.fr/2023/11/30/kubernetes-error-etcdserver-mvcc-database-space-exceeded/" &gt;mon article précédent sur etcd il y a 3 ans&lt;/a&gt;. Si vous vous en souvenez bien, le crash, c&amp;rsquo;était etcd, mais le vrai coupable, c&amp;rsquo;était Kyverno. J&amp;rsquo;adore Kyverno. C&amp;rsquo;est vraiment un logiciel que j&amp;rsquo;aime beaucoup. Notamment parce que c&amp;rsquo;est puissant(issime). J&amp;rsquo;ai d&amp;rsquo;ailleurs écrit &lt;a class="link" href="https://blog.zwindler.fr/2022/08/01/vos-politiques-de-conformite-sur-kubernetes-avec-kyverno/" &gt;un article d&amp;rsquo;intro&lt;/a&gt; et &lt;a class="link" href="https://blog.zwindler.fr/2022/09/05/vos-politiques-de-conformite-sur-kubernetes-avec-kyverno-part2/" &gt;un second qui va plus loin&lt;/a&gt; sur le sujet.&lt;/p&gt;
&lt;p&gt;Mais le nombre d&amp;rsquo;incidents et de side effects chelou que ça provoque. Mamamia&amp;hellip; C&amp;rsquo;est pas le premier incident de l&amp;rsquo;année que j&amp;rsquo;ai avec Kyverno (oui, on est en février) mais comme celui-là est rigolo, je vous le partage.&lt;/p&gt;
&lt;p&gt;Lors d&amp;rsquo;une opération de maintenance de routine pour mettre à jour un cluster Kubernetes vers la version &lt;strong&gt;1.34&lt;/strong&gt; (depuis la 1.32), on s&amp;rsquo;est retrouvé face au scénario redouté par tout admin kube : un API Server totalement injoignable au redémarrage des nœuds du Control Plane.&lt;/p&gt;
&lt;p&gt;Ce qui ressemblait initialement à une erreur réseau classique s&amp;rsquo;est avéré être un &lt;strong&gt;deadlock&lt;/strong&gt; subtil entre les nouvelles fonctionnalités réseau natives de Kubernetes et notre cher Kyverno 😘.&lt;/p&gt;
&lt;p&gt;Spoiler : c&amp;rsquo;était pas un problème réseau. C&amp;rsquo;est jamais un problème réseau. Enfin si, des fois. Mais pas cette fois.&lt;/p&gt;
&lt;h2 id="lupgrade-qui-commence-bien"&gt;L&amp;rsquo;upgrade qui commence bien
&lt;/h2&gt;&lt;p&gt;Bon, un upgrade de Kubernetes, c&amp;rsquo;est devenu assez banal à ce stade. On fait ça régulièrement, on a nos procédures, on est des pros (si si). On passe de la 1.32 à la 1.34 en un seul commit, sans faire le petit saut par la 1.33 comme recommandé.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;TKT FRR.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Dans le contexte technique dont je parle, tout est géré as code. De la création des machines jusqu&amp;rsquo;au déploiement de Talos, en passant par les MachineConfig (les CustomResources pour modifier&amp;hellip; et bien, la machine).&lt;/p&gt;
&lt;p&gt;Pour plus d&amp;rsquo;infos, voir &lt;a class="link" href="https://docs.siderolabs.com/talos/v1.12/reference/configuration/v1alpha1/config#machineconfig" target="_blank" rel="noopener"
&gt;la documentation Talos sur les Machine Configs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Le premier cluster qu&amp;rsquo;on teste n&amp;rsquo;a qu&amp;rsquo;un seul control plane (me demandez pas pourquoi, ça n&amp;rsquo;aurait probablement rien changé). Talos relance l&amp;rsquo;API Server dans la nouvelle version et là&amp;hellip; rien.&lt;/p&gt;
&lt;p&gt;Les logs de l&amp;rsquo;API Server &amp;ldquo;chelous&amp;rdquo; (terme technique) parlent d&amp;rsquo;eux-mêmes :&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code class="language-log" data-lang="log"&gt;I0224 15:32:50.979280 1 default_servicecidr_controller.go:166] Creating default ServiceCIDR with CIDRs: [10.1.0.0/20]
W0224 15:32:50.984784 1 dispatcher.go:225] rejected by webhook &amp;#34;validate.kyverno.svc-fail&amp;#34;:
admission webhook &amp;#34;validate.kyverno.svc-fail&amp;#34; denied the request:
Get &amp;#34;https://10.1.0.1:443/api&amp;#34;: dial tcp 10.1.0.1:443: connect: operation not permitted
I0224 15:32:50.985342 1 event.go:389] &amp;#34;Event occurred&amp;#34; kind=&amp;#34;ServiceCIDR&amp;#34;
apiVersion=&amp;#34;networking.k8s.io/v1&amp;#34; type=&amp;#34;Warning&amp;#34;
reason=&amp;#34;KubernetesDefaultServiceCIDRError&amp;#34;
message=&amp;#34;The default ServiceCIDR can not be created&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;😬😬😬&lt;/p&gt;
&lt;h2 id="la-root-cause--un-magnifique-cercle-vicieux"&gt;La root cause : un magnifique cercle vicieux
&lt;/h2&gt;&lt;p&gt;Après investigation, on a découvert que l&amp;rsquo;incident était le résultat d&amp;rsquo;une collision entre une évolution du core de Kubernetes et notre configuration Kyverno. Un beau cas d&amp;rsquo;école de deadlock.&lt;/p&gt;
&lt;p&gt;Décomposons le mécanisme :&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Le nouveau Kind &lt;code&gt;ServiceCIDR&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Dans les versions récentes (v1.33+), Kubernetes migre la gestion des plages d&amp;rsquo;IP de services vers des objets dédiés nommés &lt;code&gt;ServiceCIDR&lt;/code&gt;. Au premier démarrage après l&amp;rsquo;upgrade, l&amp;rsquo;API Server tente de créer automatiquement l&amp;rsquo;objet par défaut (par exemple &lt;code&gt;10.1.0.0/20&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Pour les curieux, &lt;a class="link" href="https://github.com/kubernetes/enhancements/issues/1880" target="_blank" rel="noopener"
&gt;la KEP-1880&lt;/a&gt; et &lt;a class="link" href="https://kubernetes.io/docs/reference/kubernetes-api/cluster-resources/service-cidr-v1/" target="_blank" rel="noopener"
&gt;la documentation officielle du ServiceCIDR&lt;/a&gt; détaillent cette évolution.&lt;/p&gt;
&lt;p&gt;C&amp;rsquo;est nouveau, c&amp;rsquo;est propre, c&amp;rsquo;est bien pensé. Sauf que&amp;hellip;&lt;/p&gt;
&lt;ol start="2"&gt;
&lt;li&gt;Interception par le Webhook Kyverno&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Kyverno, configuré avec une &lt;code&gt;failurePolicy: Fail&lt;/code&gt; (parce qu&amp;rsquo;on est des gens sérieux qui ne laissent pas passer n&amp;rsquo;importe quoi en prod), est programmé pour intercepter les créations de ressources afin de les valider, et &lt;strong&gt;faire échouer le call si Kyverno ne répond pas&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Y compris le &lt;code&gt;ServiceCIDR&lt;/code&gt; fraîchement créé par l&amp;rsquo;API Server lui-même.&lt;/p&gt;
&lt;ol start="3"&gt;
&lt;li&gt;Deadlock&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Et c&amp;rsquo;est là que ça devient magnifique (&amp;ldquo;la douleur peut être une source de plaisir&amp;rdquo; - Mozinor) :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;L&amp;rsquo;API Server suspend la création du &lt;code&gt;ServiceCIDR&lt;/code&gt; en attendant le &amp;ldquo;OK&amp;rdquo; de Kyverno&lt;/li&gt;
&lt;li&gt;Pour contacter le service Kyverno, l&amp;rsquo;API Server doit router la requête via l&amp;rsquo;IP du service Kubernetes (en général &lt;code&gt;10.1.0.1&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sauf que&lt;/strong&gt; la couche réseau (routage vers les services) ne peut pas s&amp;rsquo;initialiser tant que l&amp;rsquo;objet &lt;code&gt;ServiceCIDR&lt;/code&gt; n&amp;rsquo;est pas validé et créé&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;C&amp;rsquo;est l&amp;rsquo;œuf et la poule, version &amp;ldquo;j&amp;rsquo;ai les clés de la voiture dans la voiture fermée à clé&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;PTSD. Oui ça m&amp;rsquo;est arrivé. Dans le désert. Sans réseau.&lt;/p&gt;
&lt;ol start="4"&gt;
&lt;li&gt;Profit.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;L&amp;rsquo;API Server tombe en timeout ou renvoie une erreur &lt;code&gt;connect: operation not permitted&lt;/code&gt; en tentant de joindre le webhook, bloquant ainsi sa propre initialisation. CrashLoopBackOff sur l&amp;rsquo;API Server. :D&lt;/p&gt;
&lt;h2 id="sortir-du-deadlock"&gt;Sortir du deadlock
&lt;/h2&gt;&lt;p&gt;Pour sortir de ce deadlock, il faut bypasser temporairement la couche d&amp;rsquo;admission. Facile, non ?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Le workaround &amp;ldquo;habituel&amp;rdquo; : inutile&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Les deadlocks avec Kyverno, on a l&amp;rsquo;habitude à ce stade. Normalement, comme &lt;code&gt;kube-system&lt;/code&gt; est ignoré, on peut simplement se connecter avec un kube-config de secours (breaking glass, on est en OIDC normalement) qui a le cluster role cluster-admin et supprimer les validating webhooks de Kyverno :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl delete validatingwebhookconfiguration kyverno-resource-validating-webhook-cfg
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Sauf que là&lt;/strong&gt;, c&amp;rsquo;est l&amp;rsquo;API Server qui ne démarre même pas. Mon &lt;code&gt;kubectl&lt;/code&gt; ne risque pas de fonctionner !&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Le vrai workaround : désactiver les webhooks au boot&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;La solution qu&amp;rsquo;on a choisie a été de modifier la configuration de l&amp;rsquo;API Server pour désactiver temporairement les webhooks de validation au démarrage. Mon éminent collègue Maxime a édité à chaud le machine config (avec un accès &lt;code&gt;talosctl&lt;/code&gt; breaking glass) &lt;a class="link" href="https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#how-do-i-turn-off-an-admission-controller" target="_blank" rel="noopener"
&gt;pour ajouter le flag suivant&lt;/a&gt; directement dans les &lt;code&gt;extraArgs&lt;/code&gt; de l&amp;rsquo;API server :&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;--disable-admission-plugins=ValidatingAdmissionWebhook
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Pour ceux qui ne savent pas trop comment ça fonctionne l&amp;rsquo;admission control dans Kubernetes, sachez juste qu&amp;rsquo;il y a une liste de plugins &amp;ldquo;par défaut&amp;rdquo; mais que tout est débrayable. Je ferai peut-être un jour un deep dive sur l&amp;rsquo;admission control dans Kubernetes, c&amp;rsquo;est fascinant ;).&lt;/p&gt;
&lt;p&gt;Avec ce flag, l&amp;rsquo;API Server peut enfin créer ses objets &lt;code&gt;ServiceCIDR&lt;/code&gt; sans demander la permission à personne (on bypass complètement tous les mécanismes de validation que Kyverno ou assimilé &lt;em&gt;enforce&lt;/em&gt;), le réseau s&amp;rsquo;initialise, Kyverno démarre, et on peut ensuite retirer le flag et relancer proprement.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;L&amp;rsquo;option &amp;ldquo;golri&amp;rdquo; qu&amp;rsquo;on n&amp;rsquo;a pas testée&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Personnellement, je trouvais très rigolo l&amp;rsquo;idée d&amp;rsquo;aller modifier directement la base etcd pour supprimer la clé du webhook qui posait problème (là aussi en passant par &lt;code&gt;talosctl&lt;/code&gt;) :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Exemple via etcdctl&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;etcdctl del /registry/admissionregistration.k8s.io/validatingwebhookconfigurations/kyverno-resource-validating-webhook-cfg
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Mes collègues étaient moins chauds : &amp;ldquo;Ouais mais tu comprends, si on casse etcd ça va être pénible&amp;rdquo;. On a joué la sécurité avec le flag. Je suis super déçu, on n&amp;rsquo;a pas testé 😂.&lt;/p&gt;
&lt;h2 id="la-solution-permanente--les-matchconditions"&gt;La solution permanente : les MatchConditions
&lt;/h2&gt;&lt;p&gt;OK, maintenant que le cluster est reparti, comment on s&amp;rsquo;assure que ça ne se reproduise pas au prochain upgrade ?&lt;/p&gt;
&lt;p&gt;La solution propre consiste à utiliser les &lt;code&gt;matchConditions&lt;/code&gt; (introduites en Kubernetes 1.27) au niveau de la &lt;code&gt;ValidatingWebhookConfiguration&lt;/code&gt;. Ça permet d&amp;rsquo;exclure les ressources critiques de bootstrap réseau &lt;strong&gt;avant même&lt;/strong&gt; que la requête ne tente de sortir de l&amp;rsquo;API Server vers le pod Kyverno.&lt;/p&gt;
&lt;p&gt;Voir &lt;a class="link" href="https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#matching-requests-matchconditions" target="_blank" rel="noopener"
&gt;la doc officielle sur les matchConditions&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;On utilisait déjà cette option pour limiter le trafic parfois abusif de Kyverno (si vous administrez du Kyverno, vous savez de quoi je parle) sur un certain nombre d&amp;rsquo;événements (on écroule l&amp;rsquo;API server et/ou Kyverno en CPU ou RAM, selon les cas). Il a suffi d&amp;rsquo;ajouter les exclusions pour les nouveaux types :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Exclusion des ressources de bootstrap réseau pour éviter le deadlock&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;matchConditions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;exclude-ServiceCIDR&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;expression&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;!(request.kind.kind == &amp;#34;ServiceCIDR&amp;#34;)&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;exclude-IPAddress&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;expression&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;!(request.kind.kind == &amp;#34;IPAddress&amp;#34;)&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Avec ça, quand l&amp;rsquo;API Server crée un &lt;code&gt;ServiceCIDR&lt;/code&gt; au boot, la requête ne passe même plus par le webhook Kyverno. Pas de dépendance circulaire, pas de deadlock, tout le monde est content.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion
&lt;/h2&gt;&lt;p&gt;Comme dirait notre cher (non) président :&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;qui aurait pu prévoir ?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Bon ok, il suffisait de lire les release notes de Kubernetes 1.33. Cela étant dit, on a un cluster de staging, il sert à ça. On a cassé staging, no big deal&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2026/02/bigdeal.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Peut-être qu&amp;rsquo;on lira quand même, la prochaine fois ?&lt;/p&gt;</description></item><item><title>Automatiser son site Hugo sans Github Action ou Vercel</title><link>https://blog.zwindler.fr/2023/08/01/automatiser-hugo-sans-github-action/</link><pubDate>Tue, 01 Aug 2023 06:00:00 +0200</pubDate><guid>https://blog.zwindler.fr/2023/08/01/automatiser-hugo-sans-github-action/</guid><description>&lt;img src="https://blog.zwindler.fr/2021/11/wordpress-to-hugo.webp" alt="Featured image of post Automatiser son site Hugo sans Github Action ou Vercel" /&gt;&lt;h2 id="contexte"&gt;Contexte
&lt;/h2&gt;&lt;p&gt;Fin 2021, après plus de 10 ans à écrire des articles de blog tech sur Wordpress, je prenais la décision radicale d&amp;rsquo;arrêter de maintenir cette bouse infâme (je mâche mes mots) et de partir sur des articles rédigés en markdown et un site statique avec Hugo.&lt;/p&gt;
&lt;p&gt;Au tout début, je gérais le blog moi-même mais assez vite j&amp;rsquo;en ai eu assez de devoir aller rebuild le blog à chaque modification. J&amp;rsquo;ai rapidement mis un cron qui faisait des &lt;code&gt;git pull&lt;/code&gt; régulièrement, mais on ne va pas se mentir, c&amp;rsquo;est assez crado&amp;hellip;&lt;/p&gt;
&lt;p&gt;Finalement, on m&amp;rsquo;a rapidement pointé que je devrais arrêter de m&amp;rsquo;embêter et utiliser &lt;a class="link" href="https://www.netlify.com/" target="_blank" rel="noopener"
&gt;Netlify&lt;/a&gt; ou &lt;a class="link" href="https://vercel.com/" target="_blank" rel="noopener"
&gt;Vercel&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;J&amp;rsquo;étais pas super chaud, car ça allait à l&amp;rsquo;encontre de ma volonté d&amp;rsquo;auto héberger mon contenu et de limiter l&amp;rsquo;impact sur la vie privée de mes lecteurs, mais finalement, l&amp;rsquo;expérience utilisateur sur Vercel était tellement bonne que j&amp;rsquo;avais craqué. Je ferai peut-être un article là-dessus d&amp;rsquo;ailleurs, c&amp;rsquo;est la première fois que j&amp;rsquo;ai compris (ressenti) l&amp;rsquo;intérêt d&amp;rsquo;un PaaS.&lt;/p&gt;
&lt;p&gt;Mais aujourd&amp;rsquo;hui, je reviens sur cette décision et j&amp;rsquo;essaye de voir ce qu&amp;rsquo;il est nécessaire de mettre en place pour disposer soi-même d&amp;rsquo;un site hugo qui se refresh tout seul dès qu&amp;rsquo;un commit arrive sur le dépôt git, sans utiliser de plateforme type Vercel ni d&amp;rsquo;outils types Github action+pages.&lt;/p&gt;
&lt;h2 id="prérequis"&gt;Prérequis
&lt;/h2&gt;&lt;p&gt;Je pars du principe que vous avez déjà un site statique généré avec Hugo. Si ce n&amp;rsquo;est pas le cas je vous invite à aller lire mes articles précédents sur le sujet (&lt;a class="link" href="https://blog.zwindler.fr/2019/06/10/comment-migrer-de-wordpress-a-hugo/" &gt;premiers pas&lt;/a&gt;, &lt;a class="link" href="https://blog.zwindler.fr/2019/06/18/mes-stats-de-visites-hugo-sans-google-analytics-avec-matomo/" &gt;stats avec matomo&lt;/a&gt;, &lt;a class="link" href="https://blog.zwindler.fr/2021/11/22/jai-enfin-migre-de-wordpress-a-hugo-partie-1/" &gt;migration wordpress vers hugo&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Vous avez donc un serveur Linux sur lequel vous avez la main pour installer des choses et un dépôt git (sous Github dans mon cas, mais on peut probablement adapter l&amp;rsquo;article pour autre chose).&lt;/p&gt;
&lt;p&gt;Hugo étant d&amp;rsquo;abord un générateur de site statique, je vais utiliser nginx en frontal pour servir les fichiers qu&amp;rsquo;on va générer. Jusqu&amp;rsquo;à il y a peu, l&amp;rsquo;usage du mode &amp;ldquo;server&amp;rdquo; de hugo n&amp;rsquo;était d&amp;rsquo;ailleurs pas conseillé en production (mais ça ne semble plus être le cas, je n&amp;rsquo;ai pas vu de warning dernièrement ?).&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;apt install nginx git
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="déposer-les-sources-sur-le-serveur"&gt;Déposer les sources sur le serveur
&lt;/h2&gt;&lt;p&gt;Dans mon cas, les sources de mon blog ne sont pas disponibles publiquement (parce que j&amp;rsquo;ai pas envie, c&amp;rsquo;est comme ça ¯\_(ツ)_/¯).&lt;/p&gt;
&lt;p&gt;Il faut donc préalablement se loguer en SSH avec git avant de pouvoir &lt;em&gt;pull&lt;/em&gt; le code. Et comme je n&amp;rsquo;ai pas envie de laisser ma clé privée sur le serveur qui va héberger mon blog, il faut que j&amp;rsquo;utilise une autre clé.&lt;/p&gt;
&lt;p&gt;Dans mon cas, Github, qui héberge le code markdown de mon blog, a une notion de &amp;ldquo;deploy keys&amp;rdquo;, qui servent justement pour ça :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://docs.github.com/en/authentication/connecting-to-github-with-ssh/managing-deploy-keys" target="_blank" rel="noopener"
&gt;docs.github.com/en/authentication/connecting-to-github-with-ssh/managing-deploy-keys&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Je vais donc créer une clé sur le serveur qui héberge le blog :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ssh-keygen -t ed25519 -C &lt;span class="s2"&gt;&amp;#34;xxx@xxx.tld&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Generating public/private ed25519 key pair.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Enter file in which to save the key &lt;span class="o"&gt;(&lt;/span&gt;/home/toto/.ssh/id_ed25519&lt;span class="o"&gt;)&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;[&lt;/span&gt;...&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Et je la copie dans le dossier de &lt;em&gt;www-data&lt;/em&gt; (l&amp;rsquo;utilisateur nginx sous Ubuntu)&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;chmod &lt;span class="m"&gt;600&lt;/span&gt; /root/.ssh/id_ed25519
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo mkdir /var/www/.ssh/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo cp ~/.ssh/id_ed25519* /var/www/.ssh/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;chown www-data: /var/www/.ssh/*
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Une fois la clé créée, on doit l&amp;rsquo;ajouter dans Github.com. Je ne lui donne que les droits &amp;ldquo;read only&amp;rdquo; puisque le but c&amp;rsquo;est seulement de récupérer le code et générer le site statique, pas qu&amp;rsquo;on puisse éditer le code (&lt;em&gt;cé pour la sécuritay&lt;/em&gt;).&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2023/07/github-deploy-keys2.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Je retourne ensuite sur mon serveur et j&amp;rsquo;essaye de télécharger les sources et le thème:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /usr/share/nginx/html
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;GIT_SSH_COMMAND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ssh -i /var/www/.ssh/id_ed25519 -o IdentitiesOnly=yes&amp;#39;&lt;/span&gt; git clone git@github.com:zwindler/blog.example.org.git
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; blog.example.org/themes
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git submodule add -f https://github.com/CaiJimmy/hugo-theme-stack.git
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;chown -R www-data: /usr/share/nginx/html/blog.example.org
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="webhook"&gt;Webhook
&lt;/h2&gt;&lt;p&gt;Pour réagir à l&amp;rsquo;arrivée d&amp;rsquo;un nouveau commit sur notre dépôt source, on va utiliser 2 choses :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;un projet qui s&amp;rsquo;appelle sobrement &amp;ldquo;Webhook&amp;rdquo;.&lt;/li&gt;
&lt;li&gt;configurer un webhook dans github, qui va envoyer l&amp;rsquo;info que quelque chose est arrivé sur le dépôt (mais on verra ça plus tard)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Revenons à &amp;ldquo;Webhook&amp;rdquo; dans un premier temps :&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;webhook is a lightweight configurable tool written in Go, that allows you to easily create HTTP endpoints (hooks) on your server, which you can use to execute configured commands. You can also pass data from the HTTP request (such as headers, payload or query variables) to your commands. webhook also allows you to specify rules which have to be satisfied in order for the hook to be triggered.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;On récupère la dernière release et déposer le binaire sur notre serveur :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/adnanh/webhook/releases/tag/2.8.1" target="_blank" rel="noopener"
&gt;github.com/adnanh/webhook/releases/tag/2.8.1&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;VERSION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;2.8.1&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;wget https://github.com/adnanh/webhook/releases/download/&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;VERSION&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;/webhook-linux-amd64.tar.gz
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;tar -xvf webhook*.tar.gz
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo mv webhook-linux-amd64/webhook /usr/local/bin
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;rm -rf webhook-linux-amd64*
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;webhook --version
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Une fois que c&amp;rsquo;est fait, on va générer une chaîne aléatoire qui va nous servir de &amp;ldquo;secret&amp;rdquo; qui sera partagé entre le binaire &lt;em&gt;webhook&lt;/em&gt; qui tourne sur notre serveur et un &lt;em&gt;webhook&lt;/em&gt; sur Github.com :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;WHSECRET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;uuidgen &lt;span class="p"&gt;|&lt;/span&gt; base64&lt;span class="sb"&gt;`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;On crée un fichier de configuration &lt;code&gt;hook.json&lt;/code&gt; pour déclarer nos webhooks&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;mkdir&lt;/span&gt; &lt;span class="err"&gt;/etc/webhook&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;cat&lt;/span&gt; &lt;span class="err"&gt;&amp;gt;&lt;/span&gt; &lt;span class="err"&gt;/etc/webhook/hook.json&lt;/span&gt; &lt;span class="err"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="err"&gt;EOF&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;id&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;redeploy&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;execute-command&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;/usr/share/nginx/html/blog.example.org/blog_refresh.sh&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;command-working-directory&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;/usr/share/nginx/html/blog.example.org&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;trigger-rule&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;match&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;type&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;payload-hmac-sha1&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;secret&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;$WHSECRET&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;parameter&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;source&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;header&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;X-Hub-Signature&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;EOF&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Ici, quand le serveur va recevoir l&amp;rsquo;url /hooks/redeploy, on va lancer le script &lt;code&gt;/usr/share/nginx/html/blog.example.org/blog_refresh.sh&lt;/code&gt; depuis le répertoire /usr/share/nginx/html/blog.example.org.&lt;/p&gt;
&lt;p&gt;Charge à nous de créer un script qui va soit :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;récupérer les sources (git pull)&lt;/li&gt;
&lt;li&gt;relancer un build&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Voilà à quoi pourrait ressembler le script &lt;code&gt;blog_refresh.sh&lt;/code&gt; :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#!/bin/bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /usr/share/nginx/html/blog.example.org
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;#pull latest version&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;GIT_SSH_COMMAND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ssh -i /var/www/.ssh/id_ed25519 -o IdentitiesOnly=yes&amp;#39;&lt;/span&gt; git clone git@github.com:zwindler/blog.example.org.git
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;#refresh theme submodule&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git submodule init
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git submodule update
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;#rebuild (fire and forget)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;hugo --minify --gc &lt;span class="p"&gt;&amp;amp;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;On va ensuite créer un service systemd qui va lancer le binaire &lt;em&gt;webhook&lt;/em&gt; en tâche de fond&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cat &amp;gt; /etc/systemd/system/webhook.service &lt;span class="s"&gt;&amp;lt;&amp;lt; EOF
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;[Unit]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;Description=Simple Golang webhook server
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;ConditionPathExists=/usr/local/bin/webhook
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;After=network.target
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;[Service]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;User=www-data
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;Group=www-data
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;Type=simple
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;WorkingDirectory=/usr/share/nginx/html/blog.example.org
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;ExecStart=/usr/local/bin/webhook -ip 127.0.0.1 -hooks /etc/webhook/hook.json -verbose
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;Restart=on-failure
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;[Install]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;WantedBy=default.target
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;EOF&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; webhook
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;systemctl start webhook
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Côté Github.com, on crée le webhook qui va contacter le serveur webhook de la façon suivante :&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2023/07/github-webhook.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;On a maintenant un serveur web qui est en attente de certains événements webhooks et qui va lancer un script si jamais l&amp;rsquo;URL de webhook est appelée par Github.com.&lt;/p&gt;
&lt;h2 id="configuration-nginx"&gt;Configuration nginx
&lt;/h2&gt;&lt;p&gt;Il reste maintenant toute la configuration de notre nginx en frontal à faire. En partant du principe qu&amp;rsquo;on vient juste de faire l&amp;rsquo;installation du package, on commence par supprimer le fichier &lt;code&gt;/etc/nginx/sites-enabled/default&lt;/code&gt;, et on en crée un nouveau :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;rm /etc/nginx/sites-enabled/default
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cat &amp;gt; /etc/nginx/sites-available/blog.example.org.conf &lt;span class="s"&gt;&amp;lt;&amp;lt; EOF
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;# Root configuration
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;server {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; listen 80;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; server_name blog.example.org;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; location /hooks/ {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; proxy_pass http://127.0.0.1:9000/hooks/;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; error_page 404 /404.html;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; location / {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; root /usr/share/nginx/html/blog.example.org/public;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; index index.html;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;EOF&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ln -s /etc/nginx/sites-&lt;span class="o"&gt;{&lt;/span&gt;available,enabled&lt;span class="o"&gt;}&lt;/span&gt;/blog.example.org.conf
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;On vérifie que la configuration est valide et si oui on redémarre nginx&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;nginx -t
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;systemctl reload nginx
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;A partir de maintenant, toutes les URLs qui arriveront jusqu&amp;rsquo;à votre serveur en /hooks seront redirigées sur le logiciel &amp;ldquo;webhook&amp;rdquo; et le reste ira sur votre site statique (/usr/share/nginx/html/blog.example.org/public).&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;curl https://blog.example.org/hooks/redeploy -L
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Hook rules were not satisfied.%
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Ici le hook renvoie une erreur, car on a pas envoyé le token secret mais c&amp;rsquo;est probable que ça fonctionne. Toutes les autres pages renvoient bien une page du blog :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;curl https://blog.example.org/
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;&amp;lt;!DOCTYPE html&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;&amp;lt;html lang=&amp;#34;fr-fr&amp;#34; dir=&amp;#34;ltr&amp;#34;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; &amp;lt;head&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; &amp;lt;meta name=&amp;#34;generator&amp;#34; content=&amp;#34;Hugo 0.115.1&amp;#34;&amp;gt;&amp;lt;meta charset=&amp;#39;utf-8&amp;#39;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;[...]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;On peut maintenant envoyer des commits et voir si ça déclenche des rebuilds de notre blog. Ou alors, on peut récupérer un push précédent et cliquer sur &amp;ldquo;Redeliver&amp;rdquo; si on veut trigger un rebuild du blog.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2023/07/github-redelivery.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;root@hugo:~# systemctl status webhook
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;* webhook.service - Simple Golang webhook server
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Loaded: loaded (/etc/systemd/system/webhook.service; enabled; vendor preset: enabled)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Active: active (running) since Mon 2023-07-31 07:34:35 UTC; 33s ago
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Main PID: 900109 (webhook)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Tasks: 15 (limit: 4574)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Memory: 240.4M
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; CPU: 45.504s
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; CGroup: /system.slice/webhook.service
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; |-900109 /usr/local/bin/webhook -ip 127.0.0.1 -hooks /etc/webhook/hook.json -verbose
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; `-900161 hugo --minify --gc
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Jul 31 07:34:35 hugo webhook[900109]: [webhook] 2023/07/31 07:34:35 attempting to load hooks from /etc/webhook/hook.json
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Jul 31 07:34:35 hugo webhook[900109]: [webhook] 2023/07/31 07:34:35 found 1 hook(s) in file
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Jul 31 07:34:35 hugo webhook[900109]: [webhook] 2023/07/31 07:34:35 loaded: redeploy
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Jul 31 07:34:35 hugo webhook[900109]: [webhook] 2023/07/31 07:34:35 serving hooks on http://127.0.0.1:9000/hooks/{id}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Jul 31 07:34:35 hugo webhook[900109]: [webhook] 2023/07/31 07:34:35 os signal watcher ready
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Jul 31 07:34:41 hugo webhook[900109]: [webhook] 2023/07/31 07:34:41 [f089a0] incoming HTTP POST request from 127.0.0.1:43258
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Jul 31 07:34:41 hugo webhook[900109]: [webhook] 2023/07/31 07:34:41 [f089a0] redeploy got matched
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Jul 31 07:34:41 hugo webhook[900109]: [webhook] 2023/07/31 07:34:41 [f089a0] redeploy hook triggered successfully
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Jul 31 07:34:41 hugo webhook[900109]: [webhook] 2023/07/31 07:34:41 [f089a0] 200 | 0 B | 1.151688ms | 127.0.0.1:9000 | POST /hooks/redeploy
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Jul 31 07:34:41 hugo webhook[900109]: [webhook] 2023/07/31 07:34:41 [f089a0] executing /usr/share/nginx/html/blog.example.org/blog_refresh.sh
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;[...]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Jul 31 07:37:28 hugo webhook[900109]: Pages | 3572
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Jul 31 07:37:28 hugo webhook[900109]: Paginator pages | 516
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Jul 31 07:37:28 hugo webhook[900109]: Non-page files | 14
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Jul 31 07:37:28 hugo webhook[900109]: Static files | 2282
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Jul 31 07:37:28 hugo webhook[900109]: Processed images | 2
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Jul 31 07:37:28 hugo webhook[900109]: Aliases | 1563
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Jul 31 07:37:28 hugo webhook[900109]: Sitemaps | 1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Jul 31 07:37:28 hugo webhook[900109]: Cleaned | 0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Jul 31 07:37:28 hugo webhook[900109]: Total in 166131 ms
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Jul 31 07:37:28 hugo webhook[900109]: [webhook] 2023/07/31 07:37:28 [f089a0] finished handling redeploy
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="conclusion"&gt;Conclusion
&lt;/h2&gt;&lt;p&gt;Si vous êtes à l&amp;rsquo;aise avec Linux et nginx, ces quelques manipulations sont assez rapides à faire, il n&amp;rsquo;y a rien de vraiment complexe.&lt;/p&gt;
&lt;p&gt;Cependant, ça demande à l&amp;rsquo;usage un peu d&amp;rsquo;administration et de vouloir mettre les mains dans le cambouis (avoir une VM à gérer). Je ne peux reprocher à personne de préférer l&amp;rsquo;utilisation de Github/Gitlab pages, couplé à de Github actions/Gitlab runners, voire de tout déléguer à Vercel/Netlify&amp;hellip;&lt;/p&gt;
&lt;p&gt;Mais j&amp;rsquo;avais envie de reprendre la main sur le hosting de mon blog (avec tous les inconvénients que ça va avoir en termes de maintien en condition opérationnelle), notamment pour disposer à nouveau de l&amp;rsquo;IPv6 (perdue lors du passage à Vercel) et une navigation sur mon blog plus respectueuse de votre vie privée (pas que j&amp;rsquo;aie pas confiance en Vercel, mais bon&amp;hellip;).&lt;/p&gt;
&lt;p&gt;Et c&amp;rsquo;est en revanche beaucoup plus simple que d&amp;rsquo;héberger son propre serveur Gitlab + Gitlab runners + Gitlab pages ;-).&lt;/p&gt;
&lt;p&gt;Donc, pour mon usage, ça fait le taf :)&lt;/p&gt;
&lt;h2 id="ressources-additionnelles"&gt;Ressources additionnelles
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://ansonvandoren.com/posts/deploy-hugo-from-github/" target="_blank" rel="noopener"
&gt;ansonvandoren.com/posts/deploy-hugo-from-github/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://magefan.com/blog/configure-webhooks-in-github" target="_blank" rel="noopener"
&gt;magefan.com/blog/configure-webhooks-in-github&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item></channel></rss>