Emacs + Org + Guix + GitLabCI = this website ♥ (updated 2021-02-19)

structured procrastination

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:

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:

#+SETUPFILE: ../templates/style.org

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!

Footnotes:

1

Give a look a this tutorial initiated by the awesome Berty. :-)

2

Although it perfectly works to quickly check the rendering on simple text-based browser.


© 2014-2024 Simon Tournier <simon (at) tournier.info >

(last update: 2024-04-12 Fri 20:39)