Programmer la publication d’un article Cobalt

Parmi les choses qui étaient très pratiques sous WordPress et que j’ai perdues en passant à Cobalt, il y a le fait de pouvoir programmer à l’avance la publication d’un article. Il existe plein de moyens de le faire depuis un dépôt GitHub, mais étant auto-hébergé, je ne peux pas utiliser les mêmes outils. Mais étant auto-hébergé, justement, j’ai la main sur ce que je peux faire sur mon serveur, et après quelques scripts, j’ai pu à nouveau programmer mes posts à l’avance !

Préparer les scripts

Tous les scripts dont je vais parler sont rangés dans ~/bin, et ce dossier est présent dans le PATH du shell.

Préparer le répertoire de travail

On va appeler user l’utilisateur qui a les droits sur le dossier qui va contenir la racine web :

~/www

Dans ce dossier, on va cloner le dépôt du blog pour obtenir la version à partir de laquelle le site sera construit :

$ cd ~/www
$ git clone https://mon_serveur_git/blog.git

On obtient alors :

~/www/blog

Quand Cobalt génère le site web, il le fait dans le dossier _site depuis la racine du dépôt :

~/www/blog/_site

Ce dossier est celui qui doit être configuré dans le serveur web (Apache ou nginx, par exemple) pour être la racine web de votre site.

Définir un hook git

Tout d’abord, j’ai défini un hook du côté de Gitea. Un hook est une action à exécuter à un certain moment. Ici, j’ai créé un hook lorsque le serveur a reçu un push.

Étant donné que Gitea est lancé par supervisor qui lui-même a l’air d’être lancé en root, j’ai préféré limiter ce qui est fait à ce moment-là, par sécurité. Je me contente donc de lancer un script en tant qu’utilisateur user.

Mettre à jour le dépôt de travail

Une fois qu’on a donné la main à user, c’est là que le travail commence. Première chose à faire quand un push a été détecté, mettre à jour le dépôt de user, et c’est par ça que commence le script update_blog.sh :

#!/bin/sh
unset GIT_DIR
cd ~/www/blog/
git pull > ~/git.log 2> ~/err.log
git log -n 1 --format=%B | gawk -f ~/bin/process_git_command

L’astuce ici, c’est le unset GIT_DIR. Cette variable d’environnement est affectée par Gitea et si on ne l’enlève pas, le git pull échoue. J’avoue ne pas avoir tout saisi, mais ça fonctionne ainsi. 😅 Notez la création de fichiers de logs pour avoir un peu de retours. La ligne en git log récupère le dernier message de commit et l’envoie à gawk pour analyse, et déterminer ce qu’il y a faire.

Parser le message de commit

Voici le contenu du script awk :

BEGIN { regex="^(cobalt_[a-z]+)( (\"([A-Za-z0-9:+ ]+)\" )?(.*))?(\n.*)?" }
{ if (match($0, regex, part)) {
  switch (part[1])
  {
  case "cobalt_build":
    print "exec cobalt_build";
    system("exec ~/bin/build.sh > ~/cobalt_build.log");
    break;
  case "cobalt_schedule":
    if (part[4] == "") {
      print "No date given";
    } else if (part[5] == "") {
      print "No file given";
    } else {
      print "exec cobalt_schedule";
      system("exec ~/bin/schedule.sh \""part[4]"\" \""part[5]"\" \
                    2> ~/cobalt_schedule.log");
    }
    break;
  case "cobalt_cancel":
    if (part[5] == "") {
      print "No name given";  
    } else {
      print "exec cobalt_cancel";
      system("exec ~/bin/cancel_schedule.sh \""part[5]"\" \
                    > ~/cobalt_cancel.log");
      break;
    }
    break;
  default:
    print "Unknown command";
    break;
  }
  } else { print "No match"; }
}

Ce script va parser la première ligne du message de commit à la recherche d’une commande reconnue. Vous pouvez donc toujours renseigner vos commits avec d’autres infos à partir de la 2e ligne.

La regex du script va découper la première ligne pour stocker dans le tableau part les différentes correspondances, s’il y en a. Celles qui nous intéressent se trouvent dans les cases 1, 4 et 5, respectivement le nom de la commande, la date et le nom de fichier sans extension. On teste donc part[1] pour voir si une commande s’y trouve, sinon on imprime Unknown command.

Lancer la génération

La première commande à être reconnaissable est cobalt_build sans aucun paramètre. Si cette commande est présente, alors awk lancera le script build.sh :

#!/bin/sh
cd ~/www/blog
cobalt build > ~/cobalt_build.log

Rien de bien sorcier ici, on se rend dans le dossier de travail et on lance Cobalt.

Programmer la publication différée

La commande cobalt_schedule nécessite 2 paramètres : une date et un nom de fichier. Par exemple : cobalt_schedule "11:12 Feb 14" table-rase-et-cobalt Si c’est bon, alors on exécute le script schedule.sh :

#!/bin/bash
job="$(echo "~/bin/publish.sh \"$2\" && \
     git commit -a -m \"published $2.md\" && \
     git push && ~/bin/build.sh" | at $1 |& grep job)"
echo "$job $2" >> ~/bin/jobs_list

Déjà, notez que c’est un script bash et non sh. J’expliquerai pourquoi plus bas. Dans ce script, $1 est la date et $2 le nom de fichier. La date est passée à la commande Linux at et doit donc respecter un format accepté par cette dernière.

Il se passe pas mal de choses ici, alors je vais décomposer un peu. 🙂

On commence par appeler le script publish.sh en lui donnant le nom de fichier :

cd ~/www/blog
cobalt publish ./src/posts/$1.md >> ~/cobalt_publish.log \
2> ~/cobalt_publish_err.log

Ceci va modifier le fichier en le mettant à la date courante et enlever le flag qui le maintenait à l’état de brouillon. Mais ça ne lance pas la génération !

Comme on a modifié un fichier du dépôt git de travail, il faut envoyer la modification au dépôt maître. C’est le travail des deux commandes git : on commit avec un message et on push sur le dépôt maître. Attention ici à ne pas mettre une commande cobalt_schedule en message de commit, ça ferait une boucle infinie !

Enfin, on appelle la commande shell at avec la date qui va produire en sortie quelques messages qu’on filtre avec grep pour ne garder que celui qui donne le job programmé avec son id. On pipe le résultat de at avec |& pour passer à grep le canal d’erreur standard au lieu de la sortie standard. Je ne sais pas pourquoi at affiche le résultat de son exécution sur l’erreur standard… et |& n’est pas supporté par sh, voilà le pourquoi du bash.

Le tout est dans un $() pour affecter la sortie de cet ensemble de commandes à la variable job qu’on utilise pour enregistrer les informations du job ainsi que le fichier concerné dans un fichier jobs_list qui nous servira pour la dernière commande supportée.

Annuler la publication différée

Pour annuler une planification, la commande cobalt_cancel nécessite le nom du fichier sans extension (ou une partie de celui-ci, attention toutefois aux ambiguïtés !) en paramètre. Par exemple : cobalt_cancel table-rase.

La commande at permet l’annulation d’un job programmé, mais seulement si on connaît l’id du job à annuler. C’est pour ça qu’on a sauvé les infos du job en les associant au fichier concerné, car sinon on ne pourrait pas retrouver le job voulu.

Le script d’annulation cancel_schedule.sh est comme suit :

#!/bin/sh
cat ~/bin/jobs_list | gawk -v name="$1" '
$0 ~ name { if (NF > 0) {
    system("atrm "$2);
    }}    
' && sed -i "/$1/d" ~/bin/jobs_list

Il prend en paramètre le nom de fichier dont la planification est à annuler. On lit toute la liste de jobs et on la passe à gawk pour qu’il trouve la ligne qui contient le nom de fichier. S’il le trouve, il appelle atrm avec l’id du job qui se situe dans le champ $2 et on appelle ensuite sed pour effacer cette ligne du fichier.

Conclusion

C’est sûr, c’est beaucoup plus complexe que sous WordPress, où on planifie une publication en 2 clics. Sans compter les plugins qui rendent cette planification encore plus ergonomique. Mais avec un peu d’huile de coude, j’ai un système qui me convient pour atteindre le même but tout en gardant les avantages d’un blog statique. De plus, j’ai appris à me servir de awk, de at, réappris à modifier un fichier avec sed et révisé quelques notions de script shell. Donc un peu de travail, mais ça fait partie de la philosophie d’un tel système de blog ! En espérant que ça puisse vous être utile aussi !