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,
The generation of the site-map requires two tricks:
do-nothingfunction as publishing action (
(defun do-nothing (a b c) nil)
reformat the entry to deal with the subdirectory
: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
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
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
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
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
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
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
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
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
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
easy. Reader, this package needs love and any help is welcome.
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!