Étendre un blog Hugo jusqu'au protocole Gemini
Introduction
Cela fait plusieurs années qu’à la fois certains amis et quelques articles du Web me rappellent que le protocole Gemini existe.
Définition du protocole Gemini par Wikipédia
Ce dernier (naissance en 2019) se veut être une alternative minimaliste au Web actuel, mais pas autant que le protocole Gopher (existant depuis 1991).
Découverte du protocole Gopher par Wikipédia
Une partie des internautes - sûrement les vieux barbus dont je fais partie, mais ça doit être un cliché de ma part - déplore les apparences trompeuses du Web (feuilles de style CSS mal fichues), les incompatibilités entre navigateurs, les trackers, les publicités, le pistage systématique, les batailles pour la protection des données (RGPD), etc.
Règlement Général sur la Protection des Données (RGPD)
Bref, « on en a gros » !
GIF animé évoquant « On en a gros » de la série TV Kaamelott
C’est ainsi que l’idée est d’avoir un protocole différent, avec un accès spécifique (gemini:// au lieu de http://) par des navigateurs faciles à développer et des serveurs tout aussi faciles à fabriquer/coder.
D’autres en parlent mieux que moi si le cœur vous en dit :
« Gemini, le protocole du slow web » par Ploum
« Décollage : ce blog vient d’être propulsé dans le Geminispace ! » par Flozz
On s’égare un peu.
S’informer ne fait pas de mal, n’est-ce pas ?
Ainsi j’ai voulu faire déborder ce blog sur le Geminispace (l’espace internet composé de tous les sites disponibles en gemini://) et créer ma capsule Gemini ou gemlogs. Ce sont les deux mots que je retrouve fréquemment quand on parle de blog sur l’espace Gemini.
Le plan de cet article va être relativement simple. Nous débuterons par l’usage d’Hugo pour générer le blog au format gemtext (Gemini). Puis nous continuerons avec la mise en place d’un serveur Gemini pour servir le blog ainsi généré. Et finalement nous listerons quelques clients Gemini pour consulter notre nouveau gemlog (blog Gemini).
C’est parti !

Photo trouvée sur le profil de Alan Levine sur Flickr sous Licence Domaine public.
Utiliser les capacités d’Hugo pour générer un blog pour Gemini
J’utilise Hugo depuis 2016, un moteur de blog statique écrit en Go qui génère des dizaines de milliers d’articles en quelques secondes.
Mon article sur Hugo, le moteur de blog statique rapide et moderne
La particularité d’Hugo est donc de générer des fichiers (par exemple HTML) à partir de fichier source (par exemple au format Markdown) en passant par des templates (pour décrire le résultat attendu et remplir les « trous » par le contenu des fichiers sources).
En partant de ce principe, Hugo est capable de créer des fichiers Gemtext (suffixe de fichier gmi), attendus par Gemini.
Format Gemtext du protocole Gemini suivant Wikipédia
« Writing Gemtext (.gmi) » par Flounder (small web)
Remarquez que ce format est relativement simple. On va donc procéder à 2 actions :
- modifier le fichier config.toml pour l’informer de générer des fichiers gmi,
- ajouter des templates à utiliser pour générer les fichiers gmi à partir du contenu markdown déjà fourni.
À noter que plusieurs sites parlent déjà d’utiliser un projet Hugo pour créer du contenu pour Gemini (voire même pour Gopher !) :
« Converting hugo sites to Gopher Hole and/or to Gemini Capsule » par Mike A. Marin
« Une capsule gemini avec Hugo » par Alkahan
« Managing this site and my gemini capsule with Hugo » par bacardi55
Dépôt Github de mkamarin concernant le projet Hugo-2-Gopher-and-Gemini
Le fichier config.toml
Quelques lignes à renseigner dans le fichier config.toml (chez moi au format TOML) suffisent pour déclarer le nouveau besoin.
D’abord la déclaration d’un nouveau type de fichier :
[mediaTypes."text/gemini"]
suffixes = ["gmi"]
Si vous souhaitez créer un fichier de flux Atom (pour vous déclarer sur des aggrégateurs de Gemini), il est aussi nécessaire d’ajouter quelques lignes :
[mediaTypes."application/atom+xml"]
suffixes = ["xml"]
Ensuite on ajoute les différents formats de sorties :
[outputFormats.GEMINI]
mediaType = "text/gemini"
baseName = "index"
isPlainText = true
isHTML = false
protocol = "gemini://"
permalinkable = true
Si vous aviez ajouté un type de fichier atom+xml, n’oubliez pas d’ajouter aussi le format de sortie correspondant :
[outputFormats.ATOM]
mediaType = "application/atom+xml"
baseName = "atom"
isPlainText = false
isHTML = false
protocol = "gemini://"
permalinkable = false
Puis on demande à générer les fichiers en fonction des formats de sortie :
[outputs]
home = ["HTML", "RSS", "GEMINI"]
section = ["HTML", "RSS", "GEMINI"]
page = ["HTML", "GEMINI"]
Si vous vouliez ajouter le format ATOM (par exemple sur votre page d’accueil pour avoir un /atom.xml), il suffit de l’ajouter dans la ligne “home = “ :
[outputs]
home = ["HTML", "RSS", "GEMINI", "ATOM"]
section = ["HTML", "RSS", "GEMINI"]
page = ["HTML", "GEMINI"]
Le nouveau format est donc appelé GEMINI qu’on ajoute aux sorties “home”, “section” et “page”.
À la prochaine compilation, cela va générer des fichiers gmi. Du moins s’il y a des fichiers templates adéquats dans votre structure Hugo 😅.
Quelques templates pour Hugo
Nous avons déclaré 3 sorties (en plus de Gemini). Donc nous avons théoriquement 3 templates à créer :
- un template pour la page d’accueil (layouts/index.gmi),
- un template pour une page seule (layouts/_default/single.gmi),
- et un template pour la liste des articles (layouts/_default/list.gmi) contenus dans le dossier “post”.
On créé donc le fichier layouts/index.gmi pour la page d’accueil :
# {{ .Site.Title }}
## Derniers billets
{{ range first 20 (where site.RegularPages "Type" "in" site.Params.mainSections).ByDate.Reverse }}
=> {{ with .OutputFormats.Get "GEMINI" }}{{ .Permalink }}{{ end }} {{ .Date.Format "2006-01-02" }} — {{ .Title }}
{{ end }}
## Autres pages
{{ with site.GetPage "/post" }}{{ with .OutputFormats.Get "GEMINI" }}=> {{ .Permalink }} Tous les billets
{{ end }}{{ end -}}
=> {{ .Site.BaseURL }} Version HTML de ce site
Je fais simple pour le début :
- le titre du site,
- une liste des 20 derniers billets précédés de la date,
- un lien vers la liste de tous les billets,
- et un lien vers la version HTML du site.
Dans la même veine nous allons créer le fichier layouts/_default/list.gmi pour afficher la liste des articles (contenus dans le dossier « post ») :
# {{ .Title }}
{{ with .Content }}{{ . | plainify }}
{{ end -}}
{{ range .Pages.GroupByDate "2006" }}
## {{ .Key }}
{{ range .Pages.ByDate.Reverse }}
=> {{ with .OutputFormats.Get "GEMINI" }}{{ .Permalink }}{{ end }} {{ .Date.Format "2006-01-02" }} — {{ .Title }}
{{ end }}
{{ end -}}
{{ with site.Home.OutputFormats.Get "GEMINI" }}=> {{ .Permalink }} Retour à l'accueil
{{ end -}}
Il affiche :
- le titre du site,
- éventuellement le contenu d’un _index.md contenu dans le dossier “post”,
- puis affiche les articles groupés par année,
- pour chaque année il affiche la liste des articles sous forme d’un lien composé de la date et du titre de l’article,
- puis à la fin on affiche un lien pour revenir à l’accueil du site web.
Il ne reste que le fichier layouts/_default/single.gmi qui désigne l’affichage qu’aura un article au format Gemini. Je n’ai pas réinventé la roue, j’ai demandé à Internet et voici le résultat :
# {{ .Title }}
{{ .Date.Format "2006-01-02" }}{{ if .Params.tags }} — {{ delimit .Params.tags ", " }}{{ end }}
{{- $content := .Content -}}
{{- /* Blocs de code : on les protège avant de retirer le reste des balises HTML */ -}}
{{- $content = replaceRE `(?s)<pre[^>]*><code[^>]*>(.*?)</code></pre>` "\n```\n$1\n```\n" $content -}}
{{- /* Images -> lien gemtext dédié */ -}}
{{- $content = replaceRE `(?s)<img[^>]*src="([^"]+)"[^>]*alt="([^"]*)"[^>]*/?>` "\n=> $1 $2\n" $content -}}
{{- $content = replaceRE `(?s)<img[^>]*src="([^"]+)"[^>]*/?>` "\n=> $1\n" $content -}}
{{- /* Liens -> ligne "=> url texte" dédiée : gemtext exige que ce marqueur
débute la ligne pour être reconnu comme un lien cliquable, donc on
l'isole avec un saut de ligne avant et après plutôt que de le laisser
inline dans le paragraphe */ -}}
{{- $content = replaceRE `(?s)<a[^>]*href="([^"]+)"[^>]*>(.*?)</a>` "\n=> $1 $2\n" $content -}}
{{- /* Titres */ -}}
{{- $content = replaceRE `(?s)<h[1-6][^>]*>(.*?)</h[1-6]>` "\n### $1\n" $content -}}
{{- /* Listes */ -}}
{{- $content = replaceRE `(?s)<li[^>]*>(.*?)</li>` "* $1" $content -}}
{{- $content = replaceRE `</?(ul|ol)[^>]*>` "\n" $content -}}
{{- /* Paragraphes et sauts de ligne */ -}}
{{- $content = replaceRE `</p>` "\n" $content -}}
{{- $content = replaceRE `<br\s*/?>` "\n" $content -}}
{{- /* Reste des balises HTML : on jette */ -}}
{{- $content = replaceRE `(?s)<[^>]+>` "" $content -}}
{{- $content = htmlUnescape $content -}}
{{ $content }}
{{ with .OutputFormats.Get "HTML" }}=> {{ .Permalink }} Version HTML de cet article
{{ end -}}
{{ if .PrevInSection }}=> {{ with .PrevInSection.OutputFormats.Get "GEMINI" }}{{ .Permalink }}{{ end }} Article précédent : {{ .PrevInSection.Title }}
{{ end -}}
{{ if .NextInSection }}=> {{ with .NextInSection.OutputFormats.Get "GEMINI" }}{{ .Permalink }}{{ end }} Article suivant : {{ .NextInSection.Title }}
{{ end -}}
{{ with site.Home.OutputFormats.Get "GEMINI" }}=> {{ .Permalink }} Retour à l'accueil
{{ end -}}
Le template va :
- afficher le titre,
- afficher la date, puis la liste des tags attribués à cet article,
- afficher le contenu de l’article en remplaçant d’abord les blocs de code, puis les images, puis les liens, puis les titres, puis les listes, puis les paragraphes et sauts de lignes et enfin le reste des balises HTML sont jetées,
- en bas de l’article on va afficher un lien HTML de l’article, un lien vers l’article précédent, un lien vers l’article suivant et finalement un lien pour retourner à l’accueil.
Il y a encore du boulot pour avoir quelque chose de propre, mais c’est un bon début !
On peut tester le résultat en faisant :
hugo && ls ./public
Le dossier « public » devrait a minima contenir un fichier index.gmi.
Passons à la publication du blog (appelé aussi gemlogs).
Servir les fichiers sur l’espace Gemini
À la découverte du protocole Gemini on découvre aussi pléthore d’outils/logiciels. Et même une page Github nommée « awesome-gemini ».
Liste de logiciels connus par le site officiel geminiprotocol.net
Liste sous Github d’outils en tous genres autour du protocole Gemini
Pas facile de faire le tri, alors je suis allé au plus simple : un serveur léger et rapide, relativement maintenu et qui gère le certificat TLS auto-signé de longue durée nécessaire à la création d’un espace Gemini. Un espace seulement (ne gère pas d’hôtes virtuels appelés vhosts).
Je parle d’Agate.
Découvrir le dépôt Github d’Agate
Utiliser le serveur Agate à l’aide de Docker Compose
En bref, j’ai fait un fichier docker-compose.yml et un fichier .env comme ceci (le fichier docker-compose.yml en premier) :
services:
agate:
image: ghcr.io/mbrubeck/agate:3.3.24
container_name: agate
restart: unless-stopped
init: true
# Agate n'a besoin d'aucune capacité Linux particulière.
cap_drop:
- ALL
# Empêche toute élévation de privilèges.
security_opt:
- no-new-privileges:true
# Le système de fichiers du conteneur est en lecture seule.
# Les seuls emplacements accessibles en écriture sont les volumes
# explicitement montés et /tmp.
read_only: true
# Agate n'a pas besoin d'un utilisateur root.
user: "${AGATE_UID}:${AGATE_GID}"
pids_limit: 50
ports:
- "1965:1965/tcp"
volumes:
# Contenu de la capsule Gemini
- ${DATA_DIR:-./data}:/gmi:ro
# Certificats et clés privées persistants
- ${CERTS_DIR:-./certs}:/certs
tmpfs:
- /tmp:rw,noexec,nosuid,nodev,size=16m
command:
- "--hostname"
- "${DOMAIN_URL}"
Et mon fichier .env :
# Nom de domaine de la capsule Gemini
DOMAIN_URL=gemini.example.org
# Répertoire contenant les fichiers Gemtext
DATA_DIR=/srv/data/agate/gmi
# Répertoire persistant contenant les certificats et clés privées
CERTS_DIR=/srv/data/agate/certs
# UID/GID utilisés par Agate dans le conteneur.
# Le répertoire certs et le répertoire logs doivent appartenir à cet UID/GID.
AGATE_UID=65534
AGATE_GID=65534
Les dossiers doivent exister et appartenir au UID/GID pour que tout fonctionne. Donc pensez à changer les permissions desdits dossiers 😉. Par exemple chez moi j’ai dû faire :
cd /srv/data/
mkdir -p agate/gmi
chown 65534:65534 -R ./agate
pour que cela fonctionne.
Ouverture de ports
C’est le port 1965 (sur TCP) que le serveur Gemini utilise. Il faut donc penser à ouvrir le port de votre box, de votre pare-feu ou tout autre élément bloquant les entrées/sorties.
Une fois cela fait, démarrez le serveur Gemini (ici Agate). Quelque chose comme :
docker compose up -d
Vous devriez désormais avoir votre capsule Gemini, bravo !
Mais au fait, comment se rendre sur l’espace Gemini ?
Naviguer dans l’espace Gemini
C’est bien beau d’avoir un serveur Gemini, mais comment y accéder ?
Afin de naviguer dans le Geminispace vous avez besoin d’un navigateur compatible. Si vous êtes patients et méthodique, vous trouverez une liste de clients Gemini sur la page suivante :
Liste de logiciels connus par le site officiel geminiprotocol.net
ou encore sur Github :
Section « clients » de la page Github qui liste des projets pour le protocole Gemini
Et si vous êtes paresseux - comme moi - vous pouvez utiliser l’un de ceux que je propose pour chaque type de navigateurs parmi :
- un client graphique compatible Linux, MacOSX et Windows : Lagrange,
- un client en ligne de commande (sous GNU/Linux) : amfora,
- et une navigation via un proxy HTTP.
Navigateur graphique multiplateformes Lagrange
amfora, un navigateur en ligne de commande
Portail portal.mozz.us pour accéder à Gemini par HTTP
Dans la situation où cela vous débéquette d’installer un énième logiciel, le proxy HTTP est une bonne alternative. Il en existe d’autres évidemment.
Section « web-proxies » de la liste Github « awesome-gemini »
Conclusion
Passer à Gemini depuis Hugo semble se passer relativement bien et assez facilement après quelques bidouilles. Et je dis bien bidouille car, si vous le remarquez bien, ce article a été écrit de manière différente : je place les liens après les phrases et paragraphes.
En effet un lien Gemini commence en début de ligne sous un format spécial :
=> https://perdu.com Je suis perdu
Ainsi, entre le HTML où les liens se trouvent à n’importe quel endroit et le gemtext (format Gemini) dans lequel je voudrais afficher les liens, je dois faire des sauts de ligne et afficher le lien. Ce qui découpe fortement les articles déjà existants.
En dehors de la lisibilité, le blog s’étend désormais sur Gemini depuis le 8 septembre au soir. Vous pouvez le consulter sur un proxy HTTP dont je vous donne le lien ci-après :
Le site Olivier DOSSMANN sur le protocole Gemini via le portail « proxy » portal.mozz.us
Revoir les articles m’a amené à constater qu’il manque encore des sujets de mon ancien blog nommé « Le BlankoJoueb » que vous pouvez trouver à l’adresse suivante (pour les petits curieux) :
J’avance doucement mais sûrement vers le Geminispace dont nous parlerons encore sur ce blog. Ça vaudra le détour avec ce que je vais sortir 😉.
Liens utiles
Voici les liens de l’article par ordre d’apparition :
- Définition du protocole Gemini par Wikipédia
- Découverte du protocole Gopher par Wikipédia
- Règlement Général sur la Protection des Données (RGPD)
- GIF animé évoquant « On en a gros » de la série TV Kaamelott
- « Le protocole Gemini, revenir à du simple et sûr pour distribuer l’information en ligne ? » par Stéphane Bortzmeyer
- « Gemini, le protocole du slow web » par Ploum
- « Décollage : ce blog vient d’être propulsé dans le Geminispace ! » par Flozz
- Profil de Alan Levine sur Flickr
- Mon article sur Hugo, le moteur de blog statique rapide et moderne
- Format Gemtext du protocole Gemini suivant Wikipédia
- « Writing Gemtext (.gmi) » par Flounder (small web)
- « Converting hugo sites to Gopher Hole and/or to Gemini Capsule » par Mike A. Marin
- « Une capsule gemini avec Hugo » par Alkahan
- « Managing this site and my gemini capsule with Hugo » par bacardi55
- Dépôt Github de mkamarin concernant le projet Hugo-2-Gopher-and-Gemini
- Liste de logiciels connus par le site officiel geminiprotocol.net
- Liste sous Github d’outils en tous genres autour du protocole Gemini
- Découvrir le dépôt Github d’Agate
- Section « clients » de la page Github qui liste des projets pour le protocole Gemini
- Navigateur graphique multiplateformes Lagrange
- amfora, un navigateur en ligne de commande
- Portail portal.mozz.us pour accéder à Gemini par HTTP
- Section « web-proxies » de la liste Github « awesome-gemini »
- Le site Olivier DOSSMANN sur le protocole Gemini via le portail « proxy » portal.mozz.us
- Le BlankoJoueb