Histoire de blog : mise en place d'un CMS par dessus Hugo
Cela fait un moment que je voulais m’occuper un peu de ce blog pour être en mesure de publier des nouvelles un peu plus souvent que tous les 3-4 ans :)
La stack
J’ai désormais retenu la stack suivante
- Un petit serveur à 5€ / mois chez Scaleway et fonctionnant sur Debian Trixie. ça remplacera avantageusement les pages perso Free qui ne permettaient pas un déploiement commode ni la mise en place du https
- Caddy pour le serveur web (notamment car il rend super simple la gestion des certificats https et la fonction reverse proxy). Il faut bien sûr posséder son nom de domaine (je me fournis chez OVH)
- Hugo pour générer ce blog. J’avoue que je n’ai pas fait un benchmark exhaustif des concurrents (e.g. Zola, Pelican, et bien d’autres ont l’air très bien). J’ai choisi Hugo car il était populaire (ce qui signifiait que de nombreux thèmes existaient déjà, notamment m10c sur lequel je me suis basé), et qu’il était écrit en Go ce qui signifiait un unique binaire statique sans autre dépendance. Je savais dans tous les cas que je souhaitais un site statique et non un CMS comme Wordpress (pour des considérations de vitesse mais aussi de sécurité)
- Forgejo pour héberger mes dépôts git (dont l’historique de ce blog)
- Sveltia pour rajouter malgré tout une couche d’édition (à l’instar d’un CMS, mais avec un site statique en sortie). Ce dernier m’a donné un tout petit peu de fil à retordre à cause des problématiques de CORS qu’on va voir plus bas.
Je vais surtout parler ici de la mise en place de Sveltia, qui a impliqué pas mal de tatonnement vis à vis des interactions avec Caddy, Forgejo, Hugo, etc. Je n’ai pas forcément fait les choses de manière optimale et les devops de passage me diront sûrement qu’il existe des manières plus propres de procéder.
Sveltia
Dans l’arborescence Hugo, créer un dossier static/admin et y créer les deux fichiers suivants
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="robots" content="noindex" />
<title>Sveltia CMS</title>
</head>
<body>
<script src="https://unpkg.com/@sveltia/cms/dist/sveltia-cms.js"></script>
</body>
</html>
Pas beson d’installer quoi que ce soit d’autre, sveltia sera téléchargé automatiquement via le CDN.
config.yml
backend:
name: gitea
base_url: https://git.karteum.ovh
api_root: https://git.karteum.ovh/api/v1
repo: adrien/blog
branch: main
app_id: APP_ID_depuis_forgejo
auth_type: pkce
media_folder: "static/images/uploads"
public_folder: "/images/uploads"
collections:
- name: "posts"
label: "Posts"
folder: "content/posts"
create: true
slug: "{{year}}-{{month}}-{{day}}-{{slug}}"
fields:
- { label: "Title", name: "title", widget: "string" }
- { label: "Date", name: "date", widget: "datetime", format: "YYYY-MM-DDTHH:mm:ss.SSSZ" }
- { label: "Draft", name: "draft", widget: "boolean", default: true }
- { label: "Body", name: "body", widget: "markdown" }
Parmi les soucis rencontrés
- Contrairement à ce que dit la doc, le bouton “authentification par token” n’apparaît pas tout seul (je considère que c’est un bug) => j’ai dû mettre une app_id et un début de configuration Oauth - et à partir de là Sveltia a accepté d’afficher les deux boutons “connextion oauth” et “connexion par token” (qui est ce que j’utilise)
- hugo refusait initialement de prendre en compte la création de nouveaux posts. J’ai compris plus tard que le format de date généré par défaut par Sveltia ne lui plaisait pas. Ceci a été résolu avec
format: "YYYY-MM-DDTHH:mm:ss.SSSZ" - Surtout, j’ai rencontré divers soucis soit au moment de s’authentifier vers Forgejo, soit au moment de créer un post. J’ai compris progressivement qu’il s’agissait d’un problème de CORS entre git.karteum.ovh et blog.karteum.ovh
Forgejo
Il n’existe pas de package Debian pour Forgejo. Mais il s’agit d’un binaire unique donc très facile à déployer.
J’ai créé un utilisateur “forgejo”, placé ledit binaire à la racine de son HOME, et créé une unité systemd /lib/systemd/system/forgejo.service
[Service]
User=forgejo
Group=forgejo
ExecStart=/home/forgejo/forgejo web
(Par défaut il se lance sur le port TCP 3000. On verra plus bas la configuration reverse-proxy associée avec Caddy).
J’ai ensuite créé mon repository “blog”, dans lequel j’ai importé l’amorce de blog créé avec Hugo sur ma machine.
Dans configuration -> paramètres utilisateurs -> applications, j’ai créé un jeton d’accès (ne pas oublier de le copier quelque part, par exemple dans Keypass). C’est de ça que je me sers pour l’authentification depuis Sveltia.
Pour générer le site, pas besoin de déployer l’artillerie lourde comme Forgejo Actions, un simple hook git suffit. Dans les paramètres du dépôt git pour le blog, j’ai ajouté la clé publique de l’utilisateur forgejo dans “clés de déploiement”. Ensuite, j’ai créé un fichier data/forgejo-repositories/adrien/blog.git/hooks/post-receive.d/deploy avec ceci
#!/bin/sh
HOME=/home/forgejo # Sinon git assigne par défaut /home/forgejo/data/home, qui empêche d'utiliser la clé SSH
DIR=/home/forgejo/data/home/blog
LOG=/home/forgejo/deploy.log
exec >$LOG 2>&1
unset GIT_DIR # Important, sinon git pull refuse de s'exécuter dans le contexte d'appel du hook
cd $DIR
/usr/bin/git pull
/usr/bin/hugo --source "$DIR" --destination "/var/www/blog"
N.B. je conseille de s’occuper de CORS dans la configuration de Caddy et non dans celle de Forgejo (même si ce dernier permet de régler différentes choses dans custom/conf/app.ini, mais je me suis heurté à différents soucis et à des conflits avec Caddy).
Caddy
En tâtonnant j’ai finalement réussi à faire marcher Sveltia avec les réglages suivants dans /etc/caddy/Caddyfile (la zone DNS définit bien sûr git.karteum.ovh et blog.karteum.ovh en renvoyant vers l’adresse IP de cette machine chez Scaleway)
(cors) {
@cors_preflight method OPTIONS
@cors header Origin {args.0}
handle @cors_preflight {
header Access-Control-Allow-Origin "{args.0}"
header Access-Control-Allow-Methods "GET, POST, PUT, PATCH, DELETE, OPTIONS"
header Access-Control-Allow-Headers "Authorization,Content-Type"
header Access-Control-Allow-Credentials true
header Access-Control-Max-Age "3600"
respond "" 204
#respond @options 204
}
handle @cors {
header Access-Control-Allow-Origin "{args.0}"
header Access-Control-Allow-Credentials true
header Access-Control-Expose-Headers "Link"
}
}
git.karteum.ovh {
import cors {http.request.header.Origin}
reverse_proxy :3000 {
header_up Authorization {http.request.header.Authorization}
}
}
blog.karteum.ovh {
root * /var/www/blog
file_server browse
encode gzip zstd
}