Emacs + Org + Guix + GitLabCI = this website ♥ (updated 2021-02-19)
Who hasn't explained how to build a static website? Mathieu did using Haunt which is appealing because it is Guile-Scheme. The downside: it only supports Markdown. For context, I previously ran Jekyll then Hugo1 as static website generators and I found them a bit too complex to maintain for my needs. Pierre comes with an «hackable website system» using Org. Let detail my hacks!
What I want is:
- write Org-mode files using Emacs,
- run the exact same stack for writing locally and serving elsewhere,
- use common Continuous Integration for building and serving.
This post discusses these plumbing items. For the visual rendering, I borrow this CSS which I tweak for my taste; thanks to them!
From tree to publish
Org-mode is major mode for Emacs. Well, Org provides utilities for many many things such as: keeping notes, authoring documents, computational notebooks, literate programming, maintaining to-do lists, planning projects, etc. Let focus here on only one aspect: its publishing system.
(Aside, Emacs furnishes Org by default. Every Emacs user should learn the basics of this light markup language and all the niceties around. It is a game changer for scaling and keeping busy. Closing parenthesis.)
The configuration of the Org publishing system is well documented. It is a plain Emacs Lisp file; with all the edges. You start with a simple org-publish-project-alist and you end with various helpers. Hands dirty but happy to rack your brain!
In short, the tree looks like:
├── publish.el └── src ├── css ├── index.org ├── posts │ ├── 2019-03-04-derivee-dual │ └── 2019-12-12-bluehats.org ├── projects.org └── templates
where the file publish.el
contains all the configuration required by Org,
then I simply run emacs -l publish.el
. The folder css
contains all the
CSS rendering. Because, I do not want to type long header with various
obscure options, the folder templates
contains such and it is included with
something along these lines:
The footer and the header are managed by publish.el
using simple variables
and all works out-of-the-box from the Org documentation and example. So far,
so good.
The generation of the site-map requires two tricks:
one
do-nothing
function as publishing action (publishing-function
):(defun do-nothing (a b c) nil)
reformat the entry to deal with the subdirectory
posts/
and customize::sitemap-format-entry ,(lambda (entry style project) ;; adapt `org-publish-sitemap-default-entry' ;; from ox-publish.el (cond ((not (directory-name-p entry)) (format "/%s/ | [[file:%s][*%s*]]" (format-time-string "%Y-%m-%d" (org-publish-find-date entry project)) entry (org-publish-find-title entry project))) ((eq style 'tree) ;; Return only last subdir. (file-name-nondirectory (directory-file-name entry))) (t entry)))
Serve locally, Emacs great again!
Now the website locally builds fine. How to browse the result? Using the embedded EWW browser… na! Not this kind of browsing2. We need to start a HTTP server. Let use the simplest one implemented in Emacs Lisp: simple httpd.
The configuration is straightforward for our needs,
(require 'simple-httpd) (setq httpd-root "." httpd-port 8080) (httpd-start)
and the simple emacs -l serve.el
does the job. Give a look at
http://localhost:8080
.
Guixify!
After building and browsing locally using the Emacs at hand, I want to know
that everything is fine. I do not want to miss a personal configuration
polluting the website configuration. Let use the --container
feature from
guix environment
. To do so, one needs to define first a manifest file
containing all the packages required. Such manifest.scm
file reads:
(specifications->manifest '("sassc" "emacs-minimal" "emacs-org" "emacs-htmlize" "emacs-simple-httpd"))
which is just the list of the packages. Nothing fancy! Here we are, second let build in a isolated container the website using Guix:
guix environment -C -m manifest.scm -E TERM \ -- emacs -q --batch \ -l publish.el \ --eval="(org-publish \"site\")"
and similarly, the HTTP server can be up using in addition --network
to
allow the traffic.
Hum, I have no guarantee that the exact same packages will be returned by
this command after updating Guix (guix pull
). Therefore, the current
state of Guix may be stored in the file channels.scm
with the command
guix describe -f channels > channels.scm
. Now, prefixing the Guix
commands by guix time-machine
, i.e.,
guix time-machine -C channels.scm -- environment ...
fixes the issue. Everyone will build and serve the same website as me. Cool, isn't it?
Deploy using GitlabCI
That coin has two sides: configure the GitlabCI via the file
.gitlab-ci.yml
and provide a Docker image. The first part is as simple as
the documentation says, it reads:
pages: image: name: zimoun/blog:v1 script: - emacs --batch -l publish.el --eval="(org-publish \"site\")" artifacts: paths: - public only: - master
Now, it is only missing the production of the Docker image zimoun/blog:v1
.
Let use the well-known guix pack -f docker
:
guix time-machine -C channels.scm \ -- pack -f docker -m manifest.scm \ -C none \ -S /bin=bin \ -S /lib=lib \ -S /share=share \ -S /etc=etc \ --save-provenance
Almost there, let re-tag this image. For now, the only way is to load locally this image and apply the tag using the Docker tools. Something along these lines:
docker load < /gnu/store/...-docker-pack.tar docker tag <IMAGE-ID> zimoun/blog:v1
and the result is pushed to any registry using docker push
. Deployment
done!
You have your habits with Docker tools. This Docker image could be used to build or serve locally the website, in order to check differently that all is fine, for instance,
docker run -v `pwd`:`pwd` -w `pwd` -ti <TAG> \ emacs --batch -l publish.el --eval="(org-publish \"site\")"
A story about eggs and basket. If the DockerHub registry (where the image had been pushed) is removed, the Docker image is lost. Using Guix, this very same image could be rebuilt identically, whatever when or where. And because this source of this website is archived by Software Heritage, it adds robustness to this «when and where».
Ah last cherry, if an user has only this binary Docker image produced by
Guix, they can still extract information from it: after unpacking the image,
simply apply the options --export-manifest
and --export-channels
from
guix package
. The Docker image is not a smoothie anymore!
¬ Conclusion : To ∞ ∧ Beyond
Org has minimal built-in support for mathematical and it is extendable using \(\LaTeX\) via MathJax, I guess. Hence, I am using the same daily environment to write materials for serious scientific business and for fun blogging recreation.
This publishing system for static website is far from perfect. For instance, it should scale poorly. However, because it is based on Org, it allows to produce some outputs at build-time. For example, all the blocks « Hi … Bye. » from the article Calcul de Dérivées via les nombres duaux are computed by GitLabCI using the Org feature babel and then automatically included in the final document. All the code at Supplementary is automatically extracted at build-time using the Org tangle feature.
Even, each post can use different Docker images, i.e., different
manifest.scm
and channels.scm
files. The publish.el
file needs some
tweaks in agreement with .gitlab-ci.yml
and it just works.
Other possible roads would be to use Guix for building and then again Guix
for deploying this website. In other words, on one hand, replace the
scripts by something which allows to run guix build -f website.scm
. On
the other hand, drop the Docker part and replace by instead a Guix System
configuration. For instance, provide a file os.scm
describing the
configuration of Guix System able to host the website and upload the result
to a service such as DigitalOcean. Who knows if I will give it a try.
The Bad and the Ugly
Emacs user generally wants to read, eval, print and loop. On principle, it
is possible: read and eval publish.el
then run the usual export Org
command shortcut C-c-C-e
choosing P
. However, for some reasons, it does
not work as simply as it should be.
Emacs-Guix is not in enough good shape to make the use of guix environment
easy. Reader, this package needs love and any help is welcome.
The package htmlize
has an issue with the Lisp quote. It is waiting a
closing quote when it never happens because (this 'example)
is
syntactically correct despite the fact the number of quote is odd.
Backquote makes the issue messier.
Tagging is not built-in. It is an useful feature that Jekyll or Hugo provides. It allows to structure the contents and help the reader to discover potential other interesting material. On the same side, there is no built-in RSS feed. Emacs packages are floating around… but still.
Join the fun, use GNU Guix!