<?xml version="1.0" encoding="utf-8" standalone="yes"?>

<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Thomas Letan's Blog - meta</title>
    <link>https://soap.coffee/~lthms/tags/meta.html</link>
    <description>Posts tagged "meta"</description>
    <atom:link href="https://soap.coffee/~lthms/tags/meta.xml" rel="self"
               type="application/rss+xml" />
    
    
    <item>
      <title>What happened in February 2026?</title>
      <link>https://soap.coffee/~lthms/posts/february-2026.html</link>
      <guid>https://soap.coffee/~lthms/posts/february-2026.html</guid>
      <pubDate>March 8, 2026</pubDate>
      <description>
        
        &lt;h1&gt;What happened in February 2026?&lt;/h1&gt;&lt;div id=&quot;tags-list&quot;&gt;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#tag&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&amp;nbsp;&lt;a href=&quot;/~lthms/tags/meta.html&quot; class=&quot;tag hover-sky&quot; marked=&quot;&quot;&gt;meta&lt;/a&gt; &lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#tag&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&amp;nbsp;&lt;a href=&quot;/~lthms/tags/vibecoding.html&quot; class=&quot;tag hover-peach&quot; marked=&quot;&quot;&gt;vibecoding&lt;/a&gt; &lt;/div&gt;
&lt;p&gt;Time flies. February was a productive month, and I got many things done—with
the help of my &lt;a href=&quot;https://github.com/letan-assistant&quot; class=&quot;hover-lavender&quot; marked=&quot;&quot;&gt;coding assistant&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#github&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;, that is. I haven’t joined the “I
haven’t written a single line of code for quite some time” flock just yet, but
I am close. At the same time, I feel like I’m still locked in beginner mode.
Like I have still to &lt;em&gt;really&lt;/em&gt; tap into what agentic tools like Claude Code have
to offer.&lt;/p&gt;
&lt;p&gt;For now, the tools may have changed, but the journeys feel somewhat the same as
they used to a few months back. It’s a strange feeling, and I am still trying
to wrap my head around it.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Yet Another Claude Code Wrapper&lt;/h2&gt;
&lt;p&gt;In my previous retrospective, I mentioned CeCe and Vee. The former is a plugin
for turning Anthropic’s model into a “modal” agent that I had vibe “prompted”
with Claude itself. The latter was a second take at the exact same concept.&lt;/p&gt;
&lt;p&gt;A month later, Vee has evolved quite a bit, to a point where the “modal”
aspect of the project lost the front seat. More precisely, Vee has become a
session multiplexer for Claude Code, with built-in support for spawning it in
ephemeral containers with &lt;code class=&quot;hljs&quot;&gt;--dangerously-skip-permissions&lt;/code&gt; enabled. It is also
a playground for me to experiment with the agent. For instance, I built a
feedback system where I can point it to the good and bad things it’s doing—my
feedbacks get saved in a SQLite database and appended in the system prompt
during the next sessions. I’ve also tried to set up a knowledge base with
embeddings. Both approaches have their benefits, but I don’t think I have clear
evidence that they can be a game changer.&lt;/p&gt;
&lt;p&gt;You may remember Vee started out of my frustration to losing control of CeCe’s
internals. Well, Claude Code took over Vee’s codebase quite quickly as well.
But this time, the output was a lot more satisfying. This time, I leaned into
playing the role of a Tech Lead and a QA tester. Seeing the project taking
shape and the tool I wanted becoming a reality that quickly has clear upsides.
I also find it interesting that I have both a fairly good understanding of how
the software works overall—I called most of the architectural choices—&lt;em&gt;and&lt;/em&gt;
very little insight on how the code itself is written. It reminds me of
&lt;code class=&quot;hljs language-bash&quot;&gt;&lt;span class=&quot;hljs-variable&quot;&gt;$WORK&lt;/span&gt;&lt;/code&gt;, where I am involved in some projects without being a direct IC.&lt;/p&gt;
&lt;h2&gt;I Built Myself a Cloudlab&lt;/h2&gt;
&lt;p&gt;In the first half of the month, a colleague of mine was tasked with building
“staging environments &lt;em&gt;à la demande&lt;/em&gt;” for the engineers working on
&lt;code class=&quot;hljs language-bash&quot;&gt;&lt;span class=&quot;hljs-variable&quot;&gt;$WORK&lt;/span&gt;&lt;/code&gt;’s Next Big Thing™. Their answer was a managed Kubernetes cluster
and a bunch of Helm charts—and a small CLI tool to deploy the latter onto the
former. A few hours later, I started building myself a &lt;a href=&quot;https://k3s.io&quot; class=&quot;hover-coral&quot; marked=&quot;&quot;&gt;k3s&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt; cluster. &lt;em&gt;It’ll be
a fun way to get familiar with the stack,&lt;/em&gt; I thought. What emerged almost
resembles a managed cluster of sorts. I called it &lt;a href=&quot;https://github.com/lthms/elsa&quot; class=&quot;hover-lavender&quot; marked=&quot;&quot;&gt;&lt;code class=&quot;hljs&quot;&gt;elsa&lt;/code&gt;&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#github&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;, and it now
hosts &lt;a href=&quot;https://github.com/lthms/cloud-lab&quot; class=&quot;hover-rose&quot; marked=&quot;&quot;&gt;this very website along with a few other self-hosted services I had
under my radar for a while&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#github&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&quot;markdown-alert markdown-alert-note&quot;&gt;&lt;p class=&quot;markdown-alert-title&quot;&gt;&lt;svg class=&quot;octicon octicon-info mr-2&quot; viewbox=&quot;0 0 16 16&quot; version=&quot;1.1&quot; width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.5 7.75A.75.75 0 0 1 7.25 7h1a.75.75 0 0 1 .75.75v2.75h.25a.75.75 0 0 1 0 1.5h-2a.75.75 0 0 1 0-1.5h.25v-2h-.25a.75.75 0 0 1-.75-.75ZM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;Note&lt;/p&gt;&lt;p&gt;Yes, GitHub Pages and the like remain a better, cheaper, superior even way to
host my website. But where is the fun in that? 😁&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;This may come as a surprise, considering &lt;a href=&quot;/~lthms/posts/i-cannot-ssh-into-my-server-anymore.html&quot; class=&quot;hover-sky&quot; marked=&quot;&quot;&gt;I had migrated my website to a new
setup called &lt;code class=&quot;hljs&quot;&gt;tinkerbell&lt;/code&gt;&lt;/a&gt; barely &lt;em&gt;a month ago&lt;/em&gt;. Well, fret not. The
little VPS sure has been retired early, but its legacy has not been lost.
&lt;code class=&quot;hljs&quot;&gt;elsa&lt;/code&gt; was built upon the very same tools (Terraform, CoreOS, and Ignition) and
promise I got hooked on a month ago: that there is freedom in designing a
system whose &lt;em&gt;destruction&lt;/em&gt; is a non-event. If anything, working on &lt;code class=&quot;hljs&quot;&gt;elsa&lt;/code&gt; has
only &lt;em&gt;deepened&lt;/em&gt; my understanding—and my appreciation—of what Fedora has been
pushing for with CoreOS.&lt;/p&gt;
&lt;p&gt;I really, &lt;em&gt;really&lt;/em&gt; want to publish an article about &lt;code class=&quot;hljs&quot;&gt;elsa&lt;/code&gt;. I yet have to find
my angle, though. Funny story, the first couple of paragraphs of this section
were initially intended to be its introduction, until I grew dissatisfied with
them and decided to repurpose them. Which means I need to restart from scratch.
😆&lt;/p&gt;
&lt;p&gt;Anyway, as I was making good progress on &lt;code class=&quot;hljs&quot;&gt;elsa&lt;/code&gt;, I ran into some good
opportunities to contribute to some open source software. I opened two bugfix
PRs over the course of my journey: one for the &lt;a href=&quot;https://github.com/BetterStackHQ/terraform-provider-better-uptime/pull/178&quot; class=&quot;hover-rose&quot; marked=&quot;&quot;&gt;official BetterStack’s
Terraform provider&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#github&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;, and the other for &lt;a href=&quot;https://github.com/vultr/external-dns-vultr-webhook/pull/10&quot; class=&quot;hover-mint&quot; marked=&quot;&quot;&gt;external-dns’ Vultr webhook
sidecar&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#github&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;. That was pretty cool! I want to do that more often.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;We are almost a third into March already, and the month is clearly following
the trend set by its predecessor. As such, I expect to have plenty to write
about in a month’s time, meaning I should be able to keep up with publishing
these retrospective pieces for a while. At least, I hope so!&lt;/p&gt;
        
      </description>
    </item>
    
    
    
    <item>
      <title>What Happened in January 2026?</title>
      <link>https://soap.coffee/~lthms/posts/january-2026.html</link>
      <guid>https://soap.coffee/~lthms/posts/january-2026.html</guid>
      <pubDate>January 30, 2026</pubDate>
      <description>
        
        &lt;h1&gt;What Happened in January 2026?&lt;/h1&gt;&lt;div id=&quot;tags-list&quot;&gt;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#tag&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&amp;nbsp;&lt;a href=&quot;/~lthms/tags/meta.html&quot; class=&quot;tag hover-sky&quot; marked=&quot;&quot;&gt;meta&lt;/a&gt; &lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#tag&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&amp;nbsp;&lt;a href=&quot;/~lthms/tags/vibecoding.html&quot; class=&quot;tag hover-lavender&quot; marked=&quot;&quot;&gt;vibecoding&lt;/a&gt; &lt;/div&gt;
&lt;p&gt;This is my first retrospective in quite a while—I have yet to make writing
these logs a real habit of mine. That being the case, so much happened this
month that this article felt like the obvious thing to do.&lt;/p&gt;
&lt;h2&gt;&lt;code class=&quot;hljs&quot;&gt;tinkerbell&lt;/code&gt;’s First Month&lt;/h2&gt;
&lt;p&gt;On January 5th, I published &lt;a href=&quot;/~lthms/posts/i-cannot-ssh-into-my-server-anymore.html&quot; class=&quot;hover-rose&quot; marked=&quot;&quot;&gt;my account of migrating my website to a completely
new setup&lt;/a&gt;. Not only am I very
proud of this article in and of itself, I am also quite happy that it has
sparked discussions in a few places such as &lt;a href=&quot;https://lobste.rs/s/e7lpyy/i_cannot_ssh_into_my_server_anymore_s_fine&quot; class=&quot;hover-coral&quot; marked=&quot;&quot;&gt;lobste.rs&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt; or the &lt;a href=&quot;https://news.ycombinator.com/item?id=46524390&quot; class=&quot;hover-mint&quot; marked=&quot;&quot;&gt;orange
website&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;. It has also been shared in other places like the &lt;a href=&quot;https://buttondown.com/devopsish/archive/devopsish-293/&quot; class=&quot;hover-periwinkle&quot; marked=&quot;&quot;&gt;DevOps’ish
newsletter&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;&lt;label for=&quot;fn1&quot; class=&quot;sidenote-number margin-toggle&quot;&gt;&lt;/label&gt;&lt;input id=&quot;fn1&quot; type=&quot;checkbox&quot; class=&quot;margin-toggle&quot;&gt;&lt;span class=&quot;note-right sidenote note&quot;&gt;&lt;span class=&quot;footnote-p&quot;&gt;I have also created a &lt;a href=&quot;https://github.com/lthms/tinkerbell&quot; class=&quot;hover-coral&quot; marked=&quot;&quot;&gt;GitHub repository&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#github&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt; to host &lt;code class=&quot;hljs&quot;&gt;tinkerbell&lt;/code&gt;
configuration, and it is quietly farming stars as we speak. &lt;/span&gt;
&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;I don’t write articles with the expectation that they reach a large audience,
but I will admit I had &lt;em&gt;some&lt;/em&gt; ambitions for this one. It’s very fulfilling to
know my experiment report of sorts has caught the attention of my peers, if
only a little.&lt;/p&gt;
&lt;p&gt;At first, I thought I would spend January experimenting with my new playground.
I have a handful of services I want to deploy. Little did I know I would be
sidetracked sharply before I had a chance to get to it.&lt;/p&gt;
&lt;h2&gt;Meeting with Claude Code&lt;/h2&gt;
&lt;p&gt;Somehow, I have managed to go through 2025 while staying afar from the code
agent hype. I gave “vibecoding” a try in May by &lt;a href=&quot;/~lthms/posts/PeerProgrammingWithLLMs.html&quot; class=&quot;hover-peach&quot; marked=&quot;&quot;&gt;chatting with ChatGPT and
Gemini while yak shaving my way towards transcribing YouTube videos in
OCaml&lt;/a&gt;. Even I realized back then that the hype had already moved on from
chat UI towards agents.&lt;/p&gt;
&lt;p&gt;Then, the craziest thing happened. I went on holidays on December 23rd, 2025. Two
weeks later, as I was going back to &lt;code class=&quot;hljs language-bash&quot;&gt;&lt;span class=&quot;hljs-variable&quot;&gt;$WORK&lt;/span&gt;&lt;/code&gt;, Claude Code and its ilk
suddenly felt inevitable. It looks like agentic workflows got very good in a
matter of a few weeks, up to a point where there is a real opportunity cost in
ignoring them altogether. And as I am about to take out a mortgage, it feels
like a bad time to take the risk of falling out of relevance.&lt;/p&gt;
&lt;p&gt;Looking back, I think it started with a simple, genuine suggestion—we were
discussing a fairly ambitious clean-up of our test-suite at &lt;code class=&quot;hljs language-bash&quot;&gt;&lt;span class=&quot;hljs-variable&quot;&gt;$WORK&lt;/span&gt;&lt;/code&gt; when
someone mentioned Claude Code was quite relevant for this kind of tedious,
boilerplate-heavy task. This resonated with all the success stories I was
suddenly exposed to. Before I could realize what was happening, I was caught up
in &lt;a href=&quot;/~lthms/posts/how-i-want-to-use-llms-in-2026.html&quot; class=&quot;hover-lavender&quot; marked=&quot;&quot;&gt;an intense introspective journey&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Part of my answer to the angst of LLMs being on the verge of reshaping the way
I work is &lt;a href=&quot;https://lthms.github.io/cece/&quot; class=&quot;hover-periwinkle&quot; marked=&quot;&quot;&gt;CeCe&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#github&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;&lt;label for=&quot;fn2&quot; class=&quot;sidenote-number margin-toggle&quot;&gt;&lt;/label&gt;&lt;input id=&quot;fn2&quot; type=&quot;checkbox&quot; class=&quot;margin-toggle&quot;&gt;&lt;span class=&quot;note-left sidenote note&quot;&gt;&lt;span class=&quot;footnote-p&quot;&gt;CeCe being a nickname for Claude Code (CC). &lt;/span&gt;
&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;CeCe is a Claude Code plugin &lt;s&gt;I have been working&lt;/s&gt; Claude Code and I have
been working on for the better part of the month, and it has become my sandbox
to experiment with and understand agentic workflows. Configuring Claude Code in
a very opinionated way felt only natural—that’s the only starting point I know.
I am glad I did that, it enabled me to get a better understanding of how Claude
Code actually works.&lt;/p&gt;
&lt;p&gt;Ironically, since CeCe has mostly been written by CeCe itself and because it
grew quite fast as a result, I do not really feel confident in modifying it
myself 😆. I am now trying to &lt;a href=&quot;https://github.com/lthms/vee&quot; class=&quot;hover-lemon&quot; marked=&quot;&quot;&gt;rewrite it&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#github&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt; on my
own, with more structure and “intention.” We will see how it goes.&lt;/p&gt;
&lt;h2&gt;New RSS Feeds&lt;/h2&gt;
&lt;p&gt;On a final note, I am glad to say that this website now features some new RSS
feeds that you may be interested in&lt;label for=&quot;fn3&quot; class=&quot;sidenote-number margin-toggle&quot;&gt;&lt;/label&gt;&lt;input id=&quot;fn3&quot; type=&quot;checkbox&quot; class=&quot;margin-toggle&quot;&gt;&lt;span class=&quot;note-right sidenote note&quot;&gt;&lt;span class=&quot;footnote-p&quot;&gt;That’s actually the very first task I entrusted to Claude Code.
Suffice it to say, it didn&apos;t blink, and navigated through my quite unusual
setup like a champ. &lt;/span&gt;
&lt;/span&gt;: one per &lt;a href=&quot;/~lthms/tags&quot; class=&quot;hover-peach&quot; marked=&quot;&quot;&gt;tags&lt;/a&gt;, and one per
&lt;a href=&quot;/~lthms/series&quot; class=&quot;hover-periwinkle&quot; marked=&quot;&quot;&gt;series&lt;/a&gt;. I have been meaning to implement them since I’ve added my website to
a certain aggregator that now advertises every article I publish that cites one
particular programming language.&lt;/p&gt;
&lt;p&gt;I am glad to know that camel aficionados learnt about my &lt;code class=&quot;hljs&quot;&gt;tinkerbell&lt;/code&gt; setup
simply because I drew a parallel between infrastructure as code and functional
programming. Maybe they don’t share my enthusiasm, though. With this change, I
am one PR away from limiting this aggregator’s scope to simply the articles
featuring the appropriate tag. I think they will find it useful. Maybe you will
too!&lt;/p&gt;
        
      </description>
    </item>
    
    
    
    <item>
      <title>I Cannot SSH Into My Server Anymore (And That’s Fine)</title>
      <link>https://soap.coffee/~lthms/posts/i-cannot-ssh-into-my-server-anymore.html</link>
      <guid>https://soap.coffee/~lthms/posts/i-cannot-ssh-into-my-server-anymore.html</guid>
      <pubDate>January 5, 2026</pubDate>
      <description>
        
        &lt;h1&gt;I Cannot SSH Into My Server Anymore (And That’s Fine)&lt;/h1&gt;&lt;div id=&quot;tags-list&quot;&gt;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#tag&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&amp;nbsp;&lt;a href=&quot;/~lthms/tags/coreos.html&quot; class=&quot;tag hover-coral&quot; marked=&quot;&quot;&gt;coreos&lt;/a&gt; &lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#tag&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&amp;nbsp;&lt;a href=&quot;/~lthms/tags/docker.html&quot; class=&quot;tag hover-lavender&quot; marked=&quot;&quot;&gt;docker&lt;/a&gt; &lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#tag&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&amp;nbsp;&lt;a href=&quot;/~lthms/tags/meta.html&quot; class=&quot;tag hover-mint&quot; marked=&quot;&quot;&gt;meta&lt;/a&gt; &lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#tag&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&amp;nbsp;&lt;a href=&quot;/~lthms/tags/self-hosting.html&quot; class=&quot;tag hover-lavender&quot; marked=&quot;&quot;&gt;self-hosting&lt;/a&gt; &lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#tag&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&amp;nbsp;&lt;a href=&quot;/~lthms/tags/terraform.html&quot; class=&quot;tag hover-lemon&quot; marked=&quot;&quot;&gt;terraform&lt;/a&gt; &lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#tag&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&amp;nbsp;&lt;a href=&quot;/~lthms/tags/vultr.html&quot; class=&quot;tag hover-sky&quot; marked=&quot;&quot;&gt;vultr&lt;/a&gt; &lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;I would like to thank Yann Régis-Gianas, Sylvain Ribstein and Paul Laforgue
for their feedback and careful review.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;To kick off 2026, I had clear objectives in mind: decommissioning &lt;code class=&quot;hljs&quot;&gt;moana&lt;/code&gt;, my
trusty $100+/month VPS, and setting up &lt;code class=&quot;hljs&quot;&gt;tinkerbell&lt;/code&gt;, its far less costly
successor.&lt;/p&gt;
&lt;p&gt;On the one hand, I have been using &lt;code class=&quot;hljs&quot;&gt;moana&lt;/code&gt; to self-host a number of services,
and it was very handy to know that I had always a go-to place to experiment
with whatever caught my interest. On the other hand, $100/month is obviously a
lot of money, and looking back at how I used it in 2025, it was not
particularly well spent. It was time to downsize.&lt;/p&gt;
&lt;p&gt;Now that &lt;code class=&quot;hljs&quot;&gt;tinkerbell&lt;/code&gt; is up and running, I cannot even SSH into it. In fact,
&lt;em&gt;nothing&lt;/em&gt; can.&lt;/p&gt;
&lt;p&gt;There is no need. To update one of the services it hosts, I push a new container
image to the appropriate registry with the correct tag. &lt;code class=&quot;hljs&quot;&gt;tinkerbell&lt;/code&gt; will fetch
and deploy it. All on its own.&lt;/p&gt;
&lt;p&gt;In this article, I walk through the journey that led me to the smoke and
mirrors behind this magic trick: &lt;a href=&quot;https://fedoraproject.org/coreos/&quot; class=&quot;hover-peach&quot; marked=&quot;&quot;&gt;Fedora CoreOS&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;, &lt;a href=&quot;https://coreos.github.io/ignition/&quot; class=&quot;hover-mint&quot; marked=&quot;&quot;&gt;Ignition&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#github&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt; and &lt;a href=&quot;https://docs.podman.io/en/latest/markdown/podman-quadlet.1.html&quot; class=&quot;hover-sky&quot; marked=&quot;&quot;&gt;Podman
Quadlets&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt; in the main roles, with &lt;a href=&quot;https://developer.hashicorp.com/terraform&quot; class=&quot;hover-coral&quot; marked=&quot;&quot;&gt;Terraform&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt; as an essential supporting
character. This stack checks all the boxes I care about.&lt;/p&gt;
&lt;div class=&quot;markdown-alert markdown-alert-note&quot;&gt;&lt;p class=&quot;markdown-alert-title&quot;&gt;&lt;svg class=&quot;octicon octicon-info mr-2&quot; viewbox=&quot;0 0 16 16&quot; version=&quot;1.1&quot; width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.5 7.75A.75.75 0 0 1 7.25 7h1a.75.75 0 0 1 .75.75v2.75h.25a.75.75 0 0 1 0 1.5h-2a.75.75 0 0 1 0-1.5h.25v-2h-.25a.75.75 0 0 1-.75-.75ZM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;Note&lt;/p&gt;&lt;p&gt;For interested readers, I have published &lt;code class=&quot;hljs&quot;&gt;tinkerbell&lt;/code&gt;’s &lt;a href=&quot;https://github.com/lthms/tinkerbell&quot; class=&quot;hover-lavender&quot; marked=&quot;&quot;&gt;full setup&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#github&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt; on
GitHub. This article reads as an experiment log, and if you are only
interested in the final result, you should definitely have a look.&lt;/p&gt;
&lt;/div&gt;
&lt;h2&gt;Container-Centric, Declarative, and Low-Maintenance&lt;/h2&gt;
&lt;p&gt;Going into this, I knew I didn’t want to reproduce &lt;code class=&quot;hljs&quot;&gt;moana&lt;/code&gt;’s setup—it was fully
manual&lt;label for=&quot;fn1&quot; class=&quot;sidenote-number margin-toggle&quot;&gt;&lt;/label&gt;&lt;input id=&quot;fn1&quot; type=&quot;checkbox&quot; class=&quot;margin-toggle&quot;&gt;&lt;span class=&quot;note-right sidenote note&quot;&gt;&lt;span class=&quot;footnote-p&quot;&gt;In the end, I never took the time to publish a write-up about it, so
in a nutshell: everything relied on &lt;a href=&quot;https://github.com/lthms/nspawn&quot; class=&quot;hover-periwinkle&quot; marked=&quot;&quot;&gt;a small script&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#github&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt; that enabled
me to create interconnected &lt;a href=&quot;https://man7.org/linux/man-pages/man5/systemd.nspawn.5.html&quot; class=&quot;hover-coral&quot; marked=&quot;&quot;&gt;nspawn containers&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt; on the spot. &lt;/span&gt;
&lt;/span&gt; and I no longer have the time or the motivation to fiddle with
the internals of a server. Instead, I wanted to embrace the principles my
DevOps colleagues had taught me over the past two years.&lt;/p&gt;
&lt;p&gt;My initial idea was to start with this very website, since it was the only
service deployed on &lt;code class=&quot;hljs&quot;&gt;moana&lt;/code&gt; that I really wanted to keep. Since &lt;a href=&quot;./DreamWebsite.html&quot; class=&quot;hover-lavender&quot; marked=&quot;&quot;&gt;I had written
a container image for this website&lt;/a&gt;, I just had to look
for the most straightforward and future-proof way to deploy it in production™—
something I could later extend to deploy more cool projects, if I ever wanted
to&lt;label for=&quot;fn2&quot; class=&quot;sidenote-number margin-toggle&quot;&gt;&lt;/label&gt;&lt;input id=&quot;fn2&quot; type=&quot;checkbox&quot; class=&quot;margin-toggle&quot;&gt;&lt;span class=&quot;note-left sidenote note&quot;&gt;&lt;span class=&quot;footnote-p&quot;&gt;If my goal was limited to host a static website, this whole setup
would arguably be both overengineered and counterproductive. &lt;code class=&quot;hljs&quot;&gt;tinkerbell&lt;/code&gt;
was to become my little foothold in the Internet, though. My “cloud
homelabs,” of sorts. &lt;/span&gt;
&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.docker.com/compose/&quot; class=&quot;hover-peach&quot; marked=&quot;&quot;&gt;Docker Compose&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt; alone wasn’t a good fit. I like compose files, but one needs
to provision and manage a VM to host them. Ansible can provision VMs, but that
road comes with its own set of struggles. Writing good playbooks has always
felt surprisingly difficult to me. In particular, a good playbook is supposed
to handle two very different scenarios—provisioning a brand new machine, and
updating a pre-existing deployment—and I have found it particularly challenging
to ensure that both paths reliably produce the same result.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://kubernetes.io/&quot; class=&quot;hover-lavender&quot; marked=&quot;&quot;&gt;Kubernetes&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt; was &lt;em&gt;very&lt;/em&gt; appealing on paper. I have seen engineers turn compose
files into &lt;a href=&quot;https://helm.sh/docs/topics/charts/&quot; class=&quot;hover-rose&quot; marked=&quot;&quot;&gt;Helm charts&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt; and be done with it. If I could do the same thing,
wouldn’t that be bliss? Unfortunately, Kubernetes is a notoriously complex
stack, resulting from compromises made to address challenges I simply don’t
face. Managed clusters could make things easier, but they aren’t cheap. That
would defeat the initial motivation behind retiring &lt;code class=&quot;hljs&quot;&gt;moana&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://fedoraproject.org/coreos/&quot; class=&quot;hover-peach&quot; marked=&quot;&quot;&gt;CoreOS&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;, being an operating system specifically built to &lt;em&gt;run containers&lt;/em&gt;,
obviously stood out. That said, I had very little intuition on how it could
work in practice. So I started digging. I learned about Ignition first. Its
purpose is to provision a VM exactly once, at first boot. If you need to change
something afterwards, you throw away your VM and create a new one. This may
seem counter-intuitive, but since it eliminates the main reason I was looking
for an alternative to Ansible, I was hooked&lt;label for=&quot;fn3&quot; class=&quot;sidenote-number margin-toggle&quot;&gt;&lt;/label&gt;&lt;input id=&quot;fn3&quot; type=&quot;checkbox&quot; class=&quot;margin-toggle&quot;&gt;&lt;span class=&quot;note-right sidenote note&quot;&gt;&lt;span class=&quot;footnote-p&quot;&gt;CoreOS and Ignition enable me to think about virtual machines the same
way OCaml or Haskell trained me to think about data: as immutable values. &lt;/span&gt;
&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;I found out how to use systemd unit files to start containers via &lt;code class=&quot;hljs&quot;&gt;podman&lt;/code&gt; CLI
commands. That was way too cumbersome, so I pushed on for a way to orchestrate
containers &lt;em&gt;à la&lt;/em&gt; Docker Compose. That’s when I discovered Podman Quadlets and
&lt;a href=&quot;https://docs.podman.io/en/stable/markdown/podman-auto-update.1.html&quot; class=&quot;hover-rose&quot; marked=&quot;&quot;&gt;auto-updates&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;With that, everything clicked. I knew what I wanted to do, and I was very
excited about it.&lt;/p&gt;
&lt;h2&gt;Assembling &lt;code class=&quot;hljs&quot;&gt;tinkerbell&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;For more than a year now, my website has been &lt;a href=&quot;./DreamWebsite.html&quot; class=&quot;hover-lavender&quot; marked=&quot;&quot;&gt;served from RAM by a standalone,
static binary built in OCaml&lt;/a&gt;, with TLS termination handled by Nginx and
&lt;code class=&quot;hljs&quot;&gt;certbot&lt;/code&gt;’s certificates renewal performed by yours truly&lt;label for=&quot;fn4&quot; class=&quot;sidenote-number margin-toggle&quot;&gt;&lt;/label&gt;&lt;input id=&quot;fn4&quot; type=&quot;checkbox&quot; class=&quot;margin-toggle&quot;&gt;&lt;span class=&quot;note-left sidenote note&quot;&gt;&lt;span class=&quot;footnote-p&quot;&gt;I didn’t lie when I said &lt;code class=&quot;hljs&quot;&gt;moana&lt;/code&gt;’s setup was indeed &lt;em&gt;manual&lt;/em&gt;. &lt;/span&gt;
&lt;/span&gt;. I didn’t
have any reason to fundamentally change this architecture. I was simply looking
for a way to automate their deployment.&lt;/p&gt;
&lt;h3&gt;Container-Centric, …&lt;/h3&gt;
&lt;p&gt;The logical thing to do was to have &lt;code class=&quot;hljs&quot;&gt;tinkerbell&lt;/code&gt; run two containers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The reverse proxy:&lt;/strong&gt; I had been firmly on Team Nginx for years now, but
when I heard &lt;a href=&quot;https://caddyserver.com/&quot; class=&quot;hover-coral&quot; marked=&quot;&quot;&gt;Caddy&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt; could “&lt;em&gt;automatically obtain&lt;/em&gt; and &lt;em&gt;renew&lt;/em&gt; TLS
certificates,” I was sold on giving it a try.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The website itself:&lt;/strong&gt; Static binaries can be wrapped inside a container
with close to zero overhead using the &lt;a href=&quot;https://hub.docker.com/_/scratch&quot; class=&quot;hover-lemon&quot; marked=&quot;&quot;&gt;&lt;code class=&quot;hljs&quot;&gt;scratch&lt;/code&gt;&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt; base image, so I
did just that. I published it to a free-plan, public registry hosted on Vultr
that I created for the occasion&lt;label for=&quot;fn5&quot; class=&quot;sidenote-number margin-toggle&quot;&gt;&lt;/label&gt;&lt;input id=&quot;fn5&quot; type=&quot;checkbox&quot; class=&quot;margin-toggle&quot;&gt;&lt;span class=&quot;note-right sidenote note&quot;&gt;&lt;span class=&quot;footnote-p&quot;&gt;Which means getting an offline copy of this website is now as
simple as calling &lt;code class=&quot;hljs&quot;&gt;docker pull ams.vultrcr.com/lthms/www/soap.coffee:live&lt;/code&gt;. &lt;/span&gt;
&lt;/span&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://mermaid.ink/img/pako:eNo9UMtOwzAQ_BVrTyCllZs4JPEBiYZjuQASEnUPTrxJLBK7ch1BafPvuC_2tDM7M7vaA9RWIXBoevtdd9J5snoVhoR6Wpe9RuM3ZDZ7PPKcEs5YciTLtYBSKrUnd--rt3sBm4t-N1atk9uOeG2-0FXY95fB8hZwJOX6A6ud9nj1oFEQQeu0Au7diBEM6AZ5gnA4SQT4DgcUwENrcPRO9gKEmYJtK82ntcPN6ezYdsAb2e8CGrdKenzWMtw0_LMuLERX2tF44AnLziHAD_ATIM3mMY3zlCZskRUPRQR74DGL58WCXrk8S6cIfs9b6TxlWZ4EcsFYkdOgR6W9dS-Xj9bWNLqF6Q97DWuS?type=png&quot;&gt;&lt;figcaption&gt;&lt;p&gt;Nothing beats a straightforward architecture&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Nothing fancy or unexpected here, which made it a good target for a first
deployment. It was time to open Neovim to write some YAML.&lt;/p&gt;
&lt;h3&gt;Declarative, …&lt;/h3&gt;
&lt;p&gt;At this point, the architecture was clear. The next step was to turn it into
something a machine could execute. To that end, I needed two things: first an
Ignition configuration, then a CoreOS VM to run it.&lt;/p&gt;
&lt;h4&gt;The Proof of Concept&lt;/h4&gt;
&lt;p&gt;Ignition configurations (&lt;code class=&quot;hljs&quot;&gt;.ign&lt;/code&gt;) are JSON files primarily intended to be
consumed by machines. They are produced from YAML files using a tool called
&lt;a href=&quot;https://coreos.github.io/butane/&quot; class=&quot;hover-sky&quot; marked=&quot;&quot;&gt;Butane&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#github&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;. For instance, here is the first Butane configuration file I ended up
writing. It provisions a CoreOS VM by creating a new user (&lt;code class=&quot;hljs&quot;&gt;lthms&lt;/code&gt;), along with
a &lt;code class=&quot;hljs&quot;&gt;.ssh/authorized_keys&lt;/code&gt; file allowing me to SSH into the VM&lt;label for=&quot;fn6&quot; class=&quot;sidenote-number margin-toggle&quot;&gt;&lt;/label&gt;&lt;input id=&quot;fn6&quot; type=&quot;checkbox&quot; class=&quot;margin-toggle&quot;&gt;&lt;span class=&quot;note-left sidenote note&quot;&gt;&lt;span class=&quot;footnote-p&quot;&gt;I didn’t know at the time that I would &lt;em&gt;deliberately&lt;/em&gt; remove these
lines from the final Butane file. &lt;/span&gt;
&lt;/span&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-yaml&quot;&gt;&lt;span class=&quot;hljs-attr&quot;&gt;variant:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;fcos&lt;/span&gt;
&lt;span class=&quot;hljs-attr&quot;&gt;version:&lt;/span&gt; &lt;span class=&quot;hljs-number&quot;&gt;1.5&lt;/span&gt;&lt;span class=&quot;hljs-number&quot;&gt;.0&lt;/span&gt;
&lt;span class=&quot;hljs-attr&quot;&gt;passwd:&lt;/span&gt;
  &lt;span class=&quot;hljs-attr&quot;&gt;users:&lt;/span&gt;
    &lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;name:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;lthms&lt;/span&gt;
      &lt;span class=&quot;hljs-attr&quot;&gt;ssh_authorized_keys:&lt;/span&gt;
        &lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;ssh-ed25519&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;AAAAC3NzaC1lZDI1NTE5AAAAIKajIx3VWRjhqIrza4ZnVnnI1g2q6NfMfMOcnSciP1Ws&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;lthms@vanellope&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What’s important to keep in mind is that Ignition runs exactly once, at first
boot. Then it is never used again. This single fact has far-reaching
consequences, and is the reason why any meaningful change implies replacing the
machine, not modifying it.&lt;/p&gt;
&lt;p&gt;Before going any further, I wanted to understand how the actual deployment was
going to work. I generated the Ignition configuration file.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-bash&quot;&gt;butane main.bu &amp;gt; main.ign
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, I decided to investigate how to define the Vultr VM in Terraform. The
resulting configuration is twofold. First, we need to configure Terraform to be
able to interact with the Vultr API, using the &lt;a href=&quot;https://registry.terraform.io/providers/vultr/vultr/latest/docs&quot; class=&quot;hover-periwinkle&quot; marked=&quot;&quot;&gt;Vultr provider&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;. Second, I
needed to &lt;a href=&quot;https://registry.terraform.io/providers/vultr/vultr/latest/docs/resources/instance&quot; class=&quot;hover-peach&quot; marked=&quot;&quot;&gt;create the VM&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;&lt;label for=&quot;fn7&quot; class=&quot;sidenote-number margin-toggle&quot;&gt;&lt;/label&gt;&lt;input id=&quot;fn7&quot; type=&quot;checkbox&quot; class=&quot;margin-toggle&quot;&gt;&lt;span class=&quot;note-right sidenote note&quot;&gt;&lt;span class=&quot;footnote-p&quot;&gt;For discovering what values to put in most fields, &lt;code class=&quot;hljs&quot;&gt;vultr-cli&lt;/code&gt; is
pretty convenient. Kudos to the Vultr team for making it in the first
place. &lt;/span&gt;
&lt;/span&gt; and feed it the Ignition
configuration.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-hcl&quot;&gt;resource &quot;vultr_instance&quot; &quot;tinkerbell&quot; {
  region = &quot;cdg&quot;
  plan = &quot;vc2-1c-1gb&quot;
  os_id = &quot;391&quot;

  label = &quot;tinkerbell&quot;
  hostname = &quot;tinkerbell&quot;

  user_data = file(&quot;main.ign&quot;)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And that was it. I invoked &lt;code class=&quot;hljs&quot;&gt;terraform apply&lt;/code&gt;, waited for a little while, then
SSHed into the newly created VM with my &lt;code class=&quot;hljs&quot;&gt;lthms&lt;/code&gt; user. Sure enough, the
&lt;code class=&quot;hljs&quot;&gt;tinkerbell&lt;/code&gt; VM was now listed in the Vultr web interface. I explored for a
little while, then called &lt;code class=&quot;hljs&quot;&gt;terraform destroy&lt;/code&gt; and rejoiced when everything
worked as expected.&lt;/p&gt;
&lt;h4&gt;The MVP&lt;/h4&gt;
&lt;p&gt;At this point, I was basically done with Terraform, and I just needed to write
the Butane configuration that would bring my containers to life. As I mentioned
earlier, the first approach I tried was to define a systemd service responsible
for invoking &lt;code class=&quot;hljs&quot;&gt;podman&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-yaml&quot;&gt;&lt;span class=&quot;hljs-attr&quot;&gt;systemd:&lt;/span&gt;
  &lt;span class=&quot;hljs-attr&quot;&gt;units:&lt;/span&gt;
    &lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;name:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;soap.coffee.service&lt;/span&gt;
      &lt;span class=&quot;hljs-attr&quot;&gt;enabled:&lt;/span&gt; &lt;span class=&quot;hljs-literal&quot;&gt;true&lt;/span&gt;
      &lt;span class=&quot;hljs-attr&quot;&gt;contents:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;|
        [Unit]
        Description=Web Service
        After=network-online.target
        Wants=network-online.target
&lt;/span&gt;
        [&lt;span class=&quot;hljs-string&quot;&gt;Service&lt;/span&gt;]
        &lt;span class=&quot;hljs-string&quot;&gt;ExecStart=/usr/bin/podman&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;run&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;\&lt;/span&gt;
          &lt;span class=&quot;hljs-string&quot;&gt;--name&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;soap.coffee&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;\&lt;/span&gt;
          &lt;span class=&quot;hljs-string&quot;&gt;-p&lt;/span&gt; &lt;span class=&quot;hljs-number&quot;&gt;8901&lt;/span&gt;&lt;span class=&quot;hljs-string&quot;&gt;:8901&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;\&lt;/span&gt;
          &lt;span class=&quot;hljs-string&quot;&gt;--restart=always&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;\&lt;/span&gt;
          &lt;span class=&quot;hljs-string&quot;&gt;ams.vultrcr.com/lthms/www/soap.coffee:latest&lt;/span&gt;
        &lt;span class=&quot;hljs-string&quot;&gt;ExecStop=/usr/bin/podman&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;stop&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;soap.coffee&lt;/span&gt;

        [&lt;span class=&quot;hljs-string&quot;&gt;Install&lt;/span&gt;]
        &lt;span class=&quot;hljs-string&quot;&gt;WantedBy=multi-user.target&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Adding this entry in my Butane configuration and redeploying &lt;code class=&quot;hljs&quot;&gt;tinkerbell&lt;/code&gt; got
me exactly what I wanted. My website was up and running. For the sake of
getting something working first, I added the necessary configuration for Caddy
(the container and the provisioning of its configuration file), redeployed
&lt;code class=&quot;hljs&quot;&gt;tinkerbell&lt;/code&gt; again, only to realize I also needed to create a network so that
the two containers could talk together. After half an hour or so, I got
everything working, but was left with a sour taste in my mouth.&lt;/p&gt;
&lt;p&gt;This would simply not do. I wasn’t defining anything, I was writing a shell
script in the most cumbersome way possible.&lt;/p&gt;
&lt;p&gt;Then, I remembered my initial train of thought and started to search for a way
to have Docker Compose work on CoreOS&lt;label for=&quot;fn8&quot; class=&quot;sidenote-number margin-toggle&quot;&gt;&lt;/label&gt;&lt;input id=&quot;fn8&quot; type=&quot;checkbox&quot; class=&quot;margin-toggle&quot;&gt;&lt;span class=&quot;note-left sidenote note&quot;&gt;&lt;span class=&quot;footnote-p&quot;&gt;Well, Podman Compose, I guess. &lt;/span&gt;
&lt;/span&gt;. That is when I discovered
Quadlet, whose &lt;a href=&quot;https://github.com/containers/quadlet&quot; class=&quot;hover-mint&quot; marked=&quot;&quot;&gt;initial repository does a good job justifying its
existence&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#github&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;&lt;label for=&quot;fn9&quot; class=&quot;sidenote-number margin-toggle&quot;&gt;&lt;/label&gt;&lt;input id=&quot;fn9&quot; type=&quot;checkbox&quot; class=&quot;margin-toggle&quot;&gt;&lt;span class=&quot;note-right sidenote note&quot;&gt;&lt;span class=&quot;footnote-p&quot;&gt;This repository is now archived, since Quadlet has got merged
upstream. &lt;/span&gt;
&lt;/span&gt;. In particular,&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;With quadlet, you describe how to run a container in a format that is very
similar to regular systemd config files. From these actual systemd
configurations are automatically generated (using &lt;a href=&quot;https://github.com/containers/quadlet&quot; class=&quot;hover-peach&quot; marked=&quot;&quot;&gt;systemd generators&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#github&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;To give a concrete example, here is the &lt;code class=&quot;hljs&quot;&gt;.container&lt;/code&gt; file I wrote for my
website server.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-ini&quot;&gt;&lt;span class=&quot;hljs-section&quot;&gt;[Container]&lt;/span&gt;
&lt;span class=&quot;hljs-attr&quot;&gt;ContainerName&lt;/span&gt;=soap.c&lt;span class=&quot;hljs-literal&quot;&gt;off&lt;/span&gt;ee
&lt;span class=&quot;hljs-attr&quot;&gt;Image&lt;/span&gt;=ams.vultrcr.com/lthms/www/soap.c&lt;span class=&quot;hljs-literal&quot;&gt;off&lt;/span&gt;ee:live

&lt;span class=&quot;hljs-section&quot;&gt;[Service]&lt;/span&gt;
&lt;span class=&quot;hljs-attr&quot;&gt;Restart&lt;/span&gt;=always

&lt;span class=&quot;hljs-section&quot;&gt;[Install]&lt;/span&gt;
&lt;span class=&quot;hljs-attr&quot;&gt;WantedBy&lt;/span&gt;=multi-user.target
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I wasn’t wasting my time teaching systemd how to start containers anymore. I
was now declaring what should exist, so that systemd—repurposed for the
occasion as a container orchestrator—could take care of the rest.&lt;/p&gt;
&lt;div class=&quot;markdown-alert markdown-alert-tip&quot;&gt;&lt;p class=&quot;markdown-alert-title&quot;&gt;&lt;svg class=&quot;octicon octicon-light-bulb mr-2&quot; viewbox=&quot;0 0 16 16&quot; version=&quot;1.1&quot; width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;M8 1.5c-2.363 0-4 1.69-4 3.75 0 .984.424 1.625.984 2.304l.214.253c.223.264.47.556.673.848.284.411.537.896.621 1.49a.75.75 0 0 1-1.484.211c-.04-.282-.163-.547-.37-.847a8.456 8.456 0 0 0-.542-.68c-.084-.1-.173-.205-.268-.32C3.201 7.75 2.5 6.766 2.5 5.25 2.5 2.31 4.863 0 8 0s5.5 2.31 5.5 5.25c0 1.516-.701 2.5-1.328 3.259-.095.115-.184.22-.268.319-.207.245-.383.453-.541.681-.208.3-.33.565-.37.847a.751.751 0 0 1-1.485-.212c.084-.593.337-1.078.621-1.489.203-.292.45-.584.673-.848.075-.088.147-.173.213-.253.561-.679.985-1.32.985-2.304 0-2.06-1.637-3.75-4-3.75ZM5.75 12h4.5a.75.75 0 0 1 0 1.5h-4.5a.75.75 0 0 1 0-1.5ZM6 15.25a.75.75 0 0 1 .75-.75h2.5a.75.75 0 0 1 0 1.5h-2.5a.75.75 0 0 1-.75-.75Z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;Tip&lt;/p&gt;&lt;p&gt;If your containers are basically ignored by systemd, be smarter than me. Do
not try to blindly change your &lt;code class=&quot;hljs&quot;&gt;.container&lt;/code&gt; files and redeploy your VM in a
very painful and frustrating loop. Simply ask systemd for the generator logs.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs&quot;&gt;sudo journalctl -b | grep -i quadlet 
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;I excitedly turned &lt;code class=&quot;hljs&quot;&gt;caddy.service&lt;/code&gt; into &lt;code class=&quot;hljs&quot;&gt;caddy.container&lt;/code&gt;, redeployed
&lt;code class=&quot;hljs&quot;&gt;tinkerbell&lt;/code&gt;, ran into the exact same issue I had encountered before and
discovered the easiest way for two Quadlet-defined containers to talk to each
other was to introduce a &lt;a href=&quot;https://docs.podman.io/en/latest/_static/api.html?version=latest#tag/pods&quot; class=&quot;hover-lemon&quot; marked=&quot;&quot;&gt;&lt;em&gt;pod&lt;/em&gt;&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;. Unlike Docker Compose which uses DNS
over a bridge network, a pod shares the network namespace, allowing containers
to communicate over &lt;em&gt;localhost&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;To define a pod, one needs to create a &lt;code class=&quot;hljs&quot;&gt;.pod&lt;/code&gt; file, and to reference it in
their &lt;code class=&quot;hljs&quot;&gt;.container&lt;/code&gt; files using the &lt;code class=&quot;hljs&quot;&gt;PodName=&lt;/code&gt; configuration option. A “few”
redeployments later, I got everything working again, and I was ready to call it
a day.&lt;/p&gt;
&lt;p&gt;And with that, &lt;code class=&quot;hljs&quot;&gt;tinkerbell&lt;/code&gt; was basically ready.&lt;/p&gt;
&lt;div class=&quot;markdown-alert markdown-alert-caution&quot;&gt;&lt;p class=&quot;markdown-alert-title&quot;&gt;&lt;svg class=&quot;octicon octicon-stop mr-2&quot; viewbox=&quot;0 0 16 16&quot; version=&quot;1.1&quot; width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;M4.47.22A.749.749 0 0 1 5 0h6c.199 0 .389.079.53.22l4.25 4.25c.141.14.22.331.22.53v6a.749.749 0 0 1-.22.53l-4.25 4.25A.749.749 0 0 1 11 16H5a.749.749 0 0 1-.53-.22L.22 11.53A.749.749 0 0 1 0 11V5c0-.199.079-.389.22-.53Zm.84 1.28L1.5 5.31v5.38l3.81 3.81h5.38l3.81-3.81V5.31L10.69 1.5ZM8 4a.75.75 0 0 1 .75.75v3.5a.75.75 0 0 1-1.5 0v-3.5A.75.75 0 0 1 8 4Zm0 8a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;Caution&lt;/p&gt;&lt;p&gt;I’ve later learned that restarting a container that is part of a pod will
have the (to me, unexpected) side-effect to restart all the other containers
of that pod.&lt;/p&gt;
&lt;/div&gt;
&lt;h3&gt;And Low-Maintenance&lt;/h3&gt;
&lt;p&gt;Now, the end of the previous section might have given you pause.&lt;/p&gt;
&lt;p&gt;Even a static website like this one isn’t completely “stateless.” Not only does
Caddy require a configuration file to do anything meaningful, but it is also a
stateful application as it manages TLS certificates over time. Besides, I &lt;em&gt;do&lt;/em&gt;
publish technical write-ups from time to time&lt;label for=&quot;fn10&quot; class=&quot;sidenote-number margin-toggle&quot;&gt;&lt;/label&gt;&lt;input id=&quot;fn10&quot; type=&quot;checkbox&quot; class=&quot;margin-toggle&quot;&gt;&lt;span class=&quot;note-left sidenote note&quot;&gt;&lt;span class=&quot;footnote-p&quot;&gt;Two in 2025, that’s true. But 2026 is only starting, you never know
what might come! &lt;/span&gt;
&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Was I really at peace with having to destroy and redeploy &lt;code class=&quot;hljs&quot;&gt;tinkerbell&lt;/code&gt; every
time I need to change anything on my website?&lt;/p&gt;
&lt;p&gt;On the one hand, &lt;em&gt;yes&lt;/em&gt;. I believe I could live with that. I modify my website
only a handful of times even in good months, I think my audience could survive
with a minute of downtime before being allowed to read my latest pieces. It may
be an unpopular opinion, but considering my actual use case, it &lt;em&gt;was&lt;/em&gt; good
enough. Even the fact that I do not store the TLS certificates obtained by
Caddy anywhere persistent should not be an issue. I mean, Let’s Encrypt has
fairly generous weekly issuance limits per domain &lt;label for=&quot;fn11&quot; class=&quot;sidenote-number margin-toggle&quot;&gt;&lt;/label&gt;&lt;input id=&quot;fn11&quot; type=&quot;checkbox&quot; class=&quot;margin-toggle&quot;&gt;&lt;span class=&quot;note-right sidenote note&quot;&gt;&lt;span class=&quot;footnote-p&quot;&gt;How do I know that? Well... I might have hit the limit while hacking my
way to a working setup. &lt;/span&gt;
&lt;/span&gt;. I should be fine.&lt;/p&gt;
&lt;p&gt;On the other hand, the setup was starting to grow on me, and I have &lt;em&gt;other&lt;/em&gt; use
cases in mind that could be a good fit for it. So I started researching again,
this time to understand how a deployment philosophy so focused on immutability
was managing what seemed to be conflicting requirements.&lt;/p&gt;
&lt;p&gt;I went down other rabbit holes, looking for answers. The discovery that stood
out the most to me—to the point where it became the hook of this article—was
Podman auto-updates.&lt;/p&gt;
&lt;p&gt;To deploy a new version of a containerized application, you pull the new image
and restart the container. When you commit to this pattern, why should you be
the one performing this action? Instead, your VM can regularly check registries
for new images, and &lt;em&gt;update the required containers when necessary&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;In practice, Podman made this approach trivial to put in place. I just needed
to label my containers with &lt;code class=&quot;hljs&quot;&gt;io.containers.autoupdate&lt;/code&gt; set to &lt;code class=&quot;hljs&quot;&gt;registry&lt;/code&gt;,
enable the &lt;code class=&quot;hljs&quot;&gt;podman-auto-update&lt;/code&gt; timer&lt;label for=&quot;fn12&quot; class=&quot;sidenote-number margin-toggle&quot;&gt;&lt;/label&gt;&lt;input id=&quot;fn12&quot; type=&quot;checkbox&quot; class=&quot;margin-toggle&quot;&gt;&lt;span class=&quot;note-left sidenote note&quot;&gt;&lt;span class=&quot;footnote-p&quot;&gt;By default, the timer is triggered once a day, which felt
unnecessarily long, so I decided to make it hourly instead. &lt;/span&gt;
&lt;/span&gt;, and that was it. Now, every
time I update the tag &lt;code class=&quot;hljs&quot;&gt;www/soap.coffee:live&lt;/code&gt; to point to a newer version of my
image, my website is updated within the hour.&lt;/p&gt;
&lt;p&gt;And that is when the final piece clicked. At this point, publishing an image
becomes the only deployment step. I didn’t need SSH anymore.&lt;/p&gt;
&lt;h2&gt;The Road Ahead&lt;/h2&gt;
&lt;p&gt;&lt;code class=&quot;hljs&quot;&gt;tinkerbell&lt;/code&gt; has been running for a few days now, and I am quite pleased with
the system I have put in place. In retrospect, none of this is particularly
novel. It feels more like I am converging toward a set of practices the
industry has been gravitating toward for years.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;/~lthms/img/iac-meme.jpg&quot;&gt;&lt;figcaption&gt;&lt;p&gt;A man looking at the “CoreOS &amp;amp; Quadlets” butterfly and wondering whether he’s looking at Infrastructure as Code. I’m not entirely sure of the answer.&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;The journey is far from being over, though. &lt;code class=&quot;hljs&quot;&gt;tinkerbell&lt;/code&gt; is up and running, and
it served you this HTML page just fine, but the moment I put SSH out of the
picture, it became a black box. Aside from some hardware metrics kindly
provided by the Vultr dashboard, I have no real visibility into what’s going on
inside. That is fine for now, but it is not a place I want to stay in forever.
I plan to spend a few more weekends building an observability stack&lt;label for=&quot;fn13&quot; class=&quot;sidenote-number margin-toggle&quot;&gt;&lt;/label&gt;&lt;input id=&quot;fn13&quot; type=&quot;checkbox&quot; class=&quot;margin-toggle&quot;&gt;&lt;span class=&quot;note-right sidenote note&quot;&gt;&lt;span class=&quot;footnote-p&quot;&gt;Oh, and maybe I will move these TLS certificates in a block storage or
something. That could be a good idea. &lt;/span&gt;
&lt;/span&gt;.
That will come in handy when things go wrong—as they inevitably do. I would
rather have the means to understand failures than guess my way around them.&lt;/p&gt;
&lt;p&gt;Did I ever mention I am an enthusiastic Opentelemetry convert?&lt;/p&gt;
        
      </description>
    </item>
    
    
    
    <item>
      <title>What Happened in 2024?</title>
      <link>https://soap.coffee/~lthms/posts/December2024.html</link>
      <guid>https://soap.coffee/~lthms/posts/December2024.html</guid>
      <pubDate>January 1, 2025</pubDate>
      <description>
        
        &lt;h1&gt;What Happened in 2024?&lt;/h1&gt;&lt;div id=&quot;tags-list&quot;&gt;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#tag&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&amp;nbsp;&lt;a href=&quot;/~lthms/tags/meta.html&quot; class=&quot;tag hover-periwinkle&quot; marked=&quot;&quot;&gt;meta&lt;/a&gt; &lt;/div&gt;
&lt;p&gt;We are done with 2024, and now is a good time to reflect on what has happened
over the past 12 months. I was not planning to, but &lt;a href=&quot;https://www.paulox.net/2024/12/31/my-2024-in-review/&quot; class=&quot;hover-lavender&quot; marked=&quot;&quot;&gt;my feed convinced me to
give it a try&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;. Plus, it is a good opportunity to revive my
“&lt;a href=&quot;series/Retrospectives.html&quot; class=&quot;hover-sky&quot; marked=&quot;&quot;&gt;Retrospective&lt;/a&gt;” series.&lt;/p&gt;
&lt;h2&gt;Free and Open Source Software&lt;/h2&gt;
&lt;p&gt;I’ve been a “prolific contributor” at &lt;code class=&quot;hljs language-bash&quot;&gt;&lt;span class=&quot;hljs-variable&quot;&gt;$WORK&lt;/span&gt;&lt;/code&gt;, but less so with my
personal projects.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/lthms/spatial-shell&quot; class=&quot;hover-peach&quot; marked=&quot;&quot;&gt;Spatial Shell&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#github&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt; remains my most “popular” project&lt;label for=&quot;fn1&quot; class=&quot;sidenote-number margin-toggle&quot;&gt;&lt;/label&gt;&lt;input id=&quot;fn1&quot; type=&quot;checkbox&quot; class=&quot;margin-toggle&quot;&gt;&lt;span class=&quot;note-right sidenote note&quot;&gt;&lt;span class=&quot;footnote-p&quot;&gt;We should reach 100 stars on GitHub in 2025 😅. &lt;/span&gt;
&lt;/span&gt;, but
except for a very minor 7th release in January, I have not touched it. It’s
basically a done project, and I very much enjoy using it on a daily basis. My
main regret is that, contrary to what is stated in its README, Spatial Shell
does not work &lt;em&gt;at all&lt;/em&gt; with i3.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/lthms/ezjsonm-encoding&quot; class=&quot;hover-rose&quot; marked=&quot;&quot;&gt;&lt;code class=&quot;hljs&quot;&gt;ezjsonm-encoding&lt;/code&gt;&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#github&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt; was initially written for Spatial Shell, but I
turned it into its own OCaml package in 2024. It is a JSON-only encoding
library heavily borrowing on &lt;a href=&quot;https://ocaml.org/p/data-encoding/latest&quot; class=&quot;hover-mint&quot; marked=&quot;&quot;&gt;Data-encoding API&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;, but with a
more flexible default behavior for object parsing. I enjoyed writing the
documentation, inspired by a tweet from &lt;a href=&quot;https://bsky.app/profile/chshersh.com&quot; class=&quot;hover-lemon&quot; marked=&quot;&quot;&gt;Dmitrii Kovanikov&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#bsky&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;&lt;label for=&quot;fn2&quot; class=&quot;sidenote-number margin-toggle&quot;&gt;&lt;/label&gt;&lt;input id=&quot;fn2&quot; type=&quot;checkbox&quot; class=&quot;margin-toggle&quot;&gt;&lt;span class=&quot;note-left sidenote note&quot;&gt;&lt;span class=&quot;footnote-p&quot;&gt;Yes, cool kids moved to Bluesky in 2024, and Dmitrii is definitely a
cool kid. &lt;/span&gt;
&lt;/span&gt;.
That being said, I have never bothered to benchmark this package properly, so
if performances are important, it may not be a good fit for you.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/lthms/jsonrpc2&quot; class=&quot;hover-lavender&quot; marked=&quot;&quot;&gt;&lt;code class=&quot;hljs&quot;&gt;jsonrpc2&lt;/code&gt;&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#github&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt; is, as of January 1st, 2025, an experiment in
providing a general-purpose framework for servers and clients communicating
with the &lt;a href=&quot;https://www.jsonrpc.org/specification&quot; class=&quot;hover-mint&quot; marked=&quot;&quot;&gt;JSON RPC 2.0&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt; protocol. I quite like the API I’m proposing there,
and maybe I’ll try to polish and publish it in 2025.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/lthms/bepo-tsrn.nvim&quot; class=&quot;hover-lemon&quot; marked=&quot;&quot;&gt;&lt;code class=&quot;hljs&quot;&gt;bepo-tsrn.nvim&lt;/code&gt;&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#github&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt; is another thing I have done for myself, but
published as if it was a public good. Now, instead of having to copy/paste
the same Neovim configuration file on every computer I use, I can just type
&lt;code class=&quot;hljs&quot;&gt;yay -S neovim-bepo-tsrn-git&lt;/code&gt; and be done with it.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/lthms/celtchar&quot; class=&quot;hover-rose&quot; marked=&quot;&quot;&gt;celtchar&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#github&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt; has seen its first commits since 2021, which is not nothing. As a
reminder, celtchar is a little tool I have written to generate ebooks and
static websites for the stories I write; and as I was doing the 2024 edition
of &lt;a href=&quot;https://nanowrimo.org&quot; class=&quot;hover-peach&quot; marked=&quot;&quot;&gt;NaNoWriMo&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;, I found myself in need to add a missing feature (supporting
books split in parts, not only chapters).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Overall, I’ve been defaulting to OCaml for the past two years or so, and I am
starting to think it is time to widen my perspective again. I will probably
start with relearning Go&lt;label for=&quot;fn3&quot; class=&quot;sidenote-number margin-toggle&quot;&gt;&lt;/label&gt;&lt;input id=&quot;fn3&quot; type=&quot;checkbox&quot; class=&quot;margin-toggle&quot;&gt;&lt;span class=&quot;note-right sidenote note&quot;&gt;&lt;span class=&quot;footnote-p&quot;&gt;I don’t know why, but I have been mildly obsessed with this language
lately. &lt;/span&gt;
&lt;/span&gt;.&lt;/p&gt;
&lt;h2&gt;Blog posts&lt;/h2&gt;
&lt;p&gt;2024 was not a very productive year when it comes to this website. I have
published 5 articles, which is half the number of publications of 2023. As a
logical consequence, not a lot of folks have visited my website this year.
Funnily enough, the &lt;a href=&quot;SpatialShell6.html&quot; class=&quot;hover-lavender&quot; marked=&quot;&quot;&gt;most read article&lt;/a&gt; (by far) in 2024 was published in
2023&lt;label for=&quot;fn4&quot; class=&quot;sidenote-number margin-toggle&quot;&gt;&lt;/label&gt;&lt;input id=&quot;fn4&quot; type=&quot;checkbox&quot; class=&quot;margin-toggle&quot;&gt;&lt;span class=&quot;note-left sidenote note&quot;&gt;&lt;span class=&quot;footnote-p&quot;&gt;To be fair, it was published on December 30, 2023. &lt;/span&gt;
&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;/~lthms/img/2024.png&quot;&gt;&lt;figcaption&gt;&lt;p&gt;Yes, 2024 was a quiet year for this website&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;That being said, I am quite happy with the content published in 2024.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;GitMaintenanceSshEncryptedKeys.html&quot; class=&quot;hover-rose&quot; marked=&quot;&quot;&gt;Using &lt;code class=&quot;hljs&quot;&gt;git maintenance&lt;/code&gt; with Encrypted SSH Keys&lt;/a&gt; is the first article I
published in 2024. It is a direct consequence of my trip to Brussels in
February to attend to &lt;a href=&quot;https://archive.fosdem.org/2024/&quot; class=&quot;hover-periwinkle&quot; marked=&quot;&quot;&gt;FOSDEM&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;. If you haven’t already, you should watch
&lt;a href=&quot;https://www.youtube.com/watch?v=aolI_Rz0ZqY&amp;amp;pp=ygUZc28geW91IHRoaW5rIHlvdSBrbm93IGdpdA%3D%3D&quot; class=&quot;hover-sky&quot; marked=&quot;&quot;&gt;Scott Chacon’s talk&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#youtube&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt; about Git less known commands; it is the only
reason why I learned about &lt;code class=&quot;hljs&quot;&gt;git maintenance&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;LUKSEncryptedVPS.html&quot; class=&quot;hover-lemon&quot; marked=&quot;&quot;&gt;Installing a LUKS-Encrypted Arch Linux on a Vultr VPS&lt;/a&gt; is mostly a
gift I have made to Future Me. It is a very specific how-to that I can use to
quickly set up a new server with disk encryption. Funny story, I was planning
to publish a follow-up about &lt;a href=&quot;https://github.com/lthms/nspawn&quot; class=&quot;hover-sky&quot; marked=&quot;&quot;&gt;how I use systemd-nspawn to run my web services
in containers&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#github&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;, but no matter how many times I tried, I’ve never
quite found a good way to tell this story. As I plan to educate myself on
Kubernetes in 2025, it is not clear I will ever publish it now.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;BepoNvim.html&quot; class=&quot;hover-rose&quot; marked=&quot;&quot;&gt;Introducing &lt;code class=&quot;hljs&quot;&gt;bepo-tsrn.nvim&lt;/code&gt;&lt;/a&gt; is probably the less useful article I
have published in 2024, considering I expect the userbase to &lt;code class=&quot;hljs&quot;&gt;bepo-tsrn.nvim&lt;/code&gt;
to stick to 1 until the very end&lt;label for=&quot;fn5&quot; class=&quot;sidenote-number margin-toggle&quot;&gt;&lt;/label&gt;&lt;input id=&quot;fn5&quot; type=&quot;checkbox&quot; class=&quot;margin-toggle&quot;&gt;&lt;span class=&quot;note-right sidenote note&quot;&gt;&lt;span class=&quot;footnote-p&quot;&gt;But who knows? Maybe one of you will prove me wrong! &lt;/span&gt;
&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;VestigialStructures.html&quot; class=&quot;hover-peach&quot; marked=&quot;&quot;&gt;On Vestigial Structures&lt;/a&gt; hardly qualifies as a blog post, and is
mostly a joke. It is also the only content on my website that was mostly
generated by ChatGPT, and it is flagged as such. I don’t like using AI to
&lt;em&gt;write&lt;/em&gt;, but I do appreciate having a reviewer always at hand.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;DreamWebsite.html&quot; class=&quot;hover-mint&quot; marked=&quot;&quot;&gt;Serving This Article from RAM for Fun and No Real Benefit&lt;/a&gt; was very
fun to write. This little experiment was stuck in my head for basically two
years, and it turned out basically exactly as I had pictured it. That being
said, I want to learn about CDNs now.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Overall, I still enjoy having my own little corner of the Internet, but if there is
one thing I’d like to improve in 2025, it is its reach. I’d like you folks to
run into my website, instead of having to promote it every time I write
something. 2025, the year of SEO?&lt;/p&gt;
&lt;h2&gt;&lt;code class=&quot;hljs language-bash&quot;&gt;&lt;span class=&quot;hljs-variable&quot;&gt;$WORK&lt;/span&gt;&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;2024 started with my decision to go back to a Software Engineering position,
after giving an honest try at being an Engineering Manager in late 2023.&lt;/p&gt;
&lt;p&gt;I want to remember 2024 for two things.&lt;/p&gt;
&lt;p&gt;This year, more than ever, I have tried to appreciate my work beyond my
individual contributions. I am confident in my programming skills (although I
have so much to learn), but being an accomplished engineer is much more than
contributing code. Making sure every engineer in the team can work to the best
of their current ability, fostering a work environment favoring growth and
initiative, estimating as precisely as possible the amount of time needed to
deliver the next important thing, collaborating efficiently with non-technical
teams... I am becoming increasingly interested in these areas.&lt;/p&gt;
&lt;p&gt;Besides, this year was all about delivering and deploying in production. It’s
been a &lt;a href=&quot;https://medium.com/etherlink/post-mortem-etherlink-mainnet-beta-public-endpoint-denial-of-service-cfcaf1a7bb77&quot; class=&quot;hover-lavender&quot; marked=&quot;&quot;&gt;wild ride&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;, and I loved it even if it was very demanding. After having
mostly contributed to R&amp;amp;D projects, the focus on UX, backward compatibility,
etc. was very refreshing. I learned so much through the year, and had many
opportunities to make significant impacts.&lt;/p&gt;
&lt;p&gt;In 2025, I want to keep learning about software engineering, and maybe start
sharing my thoughts on the subject on my website.&lt;/p&gt;
&lt;h2&gt;Talks&lt;/h2&gt;
&lt;p&gt;I gave only one talk in 2024, at a conference called &lt;a href=&quot;https://ethcc.io/&quot; class=&quot;hover-rose&quot; marked=&quot;&quot;&gt;EthCC&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;. It was actually a
follow-up to the talk I gave the year before. You can watch me deliver the talk
&lt;a href=&quot;https://ethcc.io/archives/being-a-stage-2-rollup-from-day-1-etherlinks-journey&quot; class=&quot;hover-sky&quot; marked=&quot;&quot;&gt;here&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;, but if you are more into written content, I have also published
a &lt;a href=&quot;BeingStage2Rollup.html&quot; class=&quot;hover-lavender&quot; marked=&quot;&quot;&gt;transcript&lt;/a&gt; on this very website. I actually loved writing it down, and plan
to systematically publish similar content for every recorded talk I will give
in the future.&lt;/p&gt;
&lt;p&gt;I also had the opportunity to participate in a &lt;a href=&quot;https://x.com/etherlink/status/1852378990712930664&quot; class=&quot;hover-rose&quot; marked=&quot;&quot;&gt;Twitter Space&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#twitter&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This year, my public speaking opportunities were all &lt;code class=&quot;hljs language-bash&quot;&gt;&lt;span class=&quot;hljs-variable&quot;&gt;$WORK&lt;/span&gt;&lt;/code&gt;-related. I
would like to change this in the future, because there are enough events out
there for me to start speaking about something other than work&lt;label for=&quot;fn6&quot; class=&quot;sidenote-number margin-toggle&quot;&gt;&lt;/label&gt;&lt;input id=&quot;fn6&quot; type=&quot;checkbox&quot; class=&quot;margin-toggle&quot;&gt;&lt;span class=&quot;note-left sidenote note&quot;&gt;&lt;span class=&quot;footnote-p&quot;&gt;It’s too late to apply to FOSDEM, but maybe I can find something to
say to an event later in the year! I think. &lt;/span&gt;
&lt;/span&gt;.&lt;/p&gt;
&lt;h2&gt;Sport&lt;/h2&gt;
&lt;p&gt;This year was a bit of a disappointment, sport-wise. I tried several times to
get back to running regularly, and failed miserably. I went to Lyon for a 10km
run without proper training, and skipped the half-marathon I signed up for. I
should update my &lt;a href=&quot;/~lthms/running.html&quot; class=&quot;hover-sky&quot; marked=&quot;&quot;&gt;Running Log&lt;/a&gt; nonetheless. I started swimming
regularly during the Summer, only to pierce my earlobes in September 😅.&lt;/p&gt;
&lt;p&gt;Let’s hope I do better in 2025! I am planning to register for the &lt;a href=&quot;https://triathlondeauville.com/&quot; class=&quot;hover-lavender&quot; marked=&quot;&quot;&gt;Triathlon de
Deauville&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt; with my sister. That promises to be fun! And I
want to commit to the &lt;a href=&quot;https://vredestein.20kmparis.com/&quot; class=&quot;hover-mint&quot; marked=&quot;&quot;&gt;20km de Paris&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt; as well.&lt;/p&gt;
&lt;p&gt;On the bright side, I have started to use my bike again. I love riding around
Paris, especially at night.&lt;/p&gt;
&lt;h2&gt;Final Notes&lt;/h2&gt;
&lt;p&gt;Overall, I’ve devoted a large part of my time to &lt;code class=&quot;hljs language-bash&quot;&gt;&lt;span class=&quot;hljs-variable&quot;&gt;$WORK&lt;/span&gt;&lt;/code&gt; in 2024.
Hopefully, I will find a better balance over the course of 2025, which should
give me more time to explore and experiment more things.&lt;/p&gt;
&lt;p&gt;Anyway, happy new year everyone! And happy Dry January!&lt;/p&gt;
        
      </description>
    </item>
    
    
    
    <item>
      <title>Serving This Article from RAM for Fun and No Real Benefit</title>
      <link>https://soap.coffee/~lthms/posts/DreamWebsite.html</link>
      <guid>https://soap.coffee/~lthms/posts/DreamWebsite.html</guid>
      <pubDate>December 25, 2024</pubDate>
      <description>
        
        &lt;h1&gt;Serving This Article from RAM for Fun and No Real Benefit&lt;/h1&gt;&lt;div id=&quot;tags-list&quot;&gt;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#tag&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&amp;nbsp;&lt;a href=&quot;/~lthms/tags/docker.html&quot; class=&quot;tag hover-lavender&quot; marked=&quot;&quot;&gt;docker&lt;/a&gt; &lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#tag&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&amp;nbsp;&lt;a href=&quot;/~lthms/tags/meta.html&quot; class=&quot;tag hover-sky&quot; marked=&quot;&quot;&gt;meta&lt;/a&gt; &lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#tag&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&amp;nbsp;&lt;a href=&quot;/~lthms/tags/ocaml.html&quot; class=&quot;tag hover-peach&quot; marked=&quot;&quot;&gt;ocaml&lt;/a&gt; &lt;/div&gt;
&lt;p&gt;In 2022, Xe Iaso published a &lt;a href=&quot;https://xeiaso.net/talks/how-my-website-works/&quot; class=&quot;hover-periwinkle&quot; marked=&quot;&quot;&gt;transcript of their talk on how their website was
working at the time&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;. In a nutshell, their approach consisted of a
server preprocessing the website from its source at startup, then serving its
contents from memory. If you have not already, I can only encourage you to read
the article or watch the talk, as the story they tell is very interesting. For
me personally, it sparked a question: what if, instead of preprocessing the
website at startup, one decided to embed the already preprocessed website
within the program of the HTTP server tasked to serve it?&lt;/p&gt;
&lt;p&gt;Fast-forward today, and this question has finally been answered. The webpage
you are currently reading has been served to you by an ad hoc HTTP server built
with &lt;a href=&quot;https://aantron.github.io/dream/&quot; class=&quot;hover-sky&quot; marked=&quot;&quot;&gt;Dream&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#github&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;, whose binary is the only file I need to push to my server to
deploy the latest version of my website. I have actually deployed it, and it’s
been serving the contents of this website for more than a week now.&lt;/p&gt;
&lt;p&gt;What did I learn from this fun, little experiment? Basically, that this
approach changes nothing, as far as &lt;a href=&quot;https://chromewebstore.google.com/detail/lighthouse/blipmdconlkpinefehnmjammfjpmpbjk?pli=1&quot; class=&quot;hover-lavender&quot; marked=&quot;&quot;&gt;Lighthouse&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt; and my monitoring is
concerned. I couldn’t find any meaningful differences between a static website
served by Nginx, a piece of software with thousands and thousands of
engineering work behind it, and my little toy web server pieced together in an
hour or so. Still. It was fun, so why not write about it?&lt;/p&gt;
&lt;p&gt;This article is a kind of experience report. I’ll dive into what I have done to
turn my website into a single, static binary. Not only does it mean writing
some OCaml, which is always fun, but it also requires understanding a little
some key HTTP headers, as well as using Docker to build easily deployable
binaries. All in all, I hope it will be an interesting read for the curious
minds.&lt;/p&gt;
&lt;h2&gt;Embedding My Website in a Binary&lt;/h2&gt;
&lt;p&gt;Not much had changed much since &lt;a href=&quot;August2022.html&quot; class=&quot;hover-rose&quot; marked=&quot;&quot;&gt;I stopped using &lt;strong&gt;&lt;code class=&quot;hljs&quot;&gt;cleopatra&lt;/code&gt;&lt;/strong&gt; to generate
this website&lt;/a&gt;, and &lt;a href=&quot;Thanks2023.html&quot; class=&quot;hover-lavender&quot; marked=&quot;&quot;&gt;the article I published in 2023 still
stands&lt;/a&gt;. In a nutshell, I work in the &lt;code class=&quot;hljs&quot;&gt;site/&lt;/code&gt; directory, and &lt;a href=&quot;https://soupault.app&quot; class=&quot;hover-lemon&quot; marked=&quot;&quot;&gt;soupault&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;
generates my website in the &lt;code class=&quot;hljs&quot;&gt;out/~lthms&lt;/code&gt; directory, thanks to a collection of
built-in and ad hoc plugins&lt;label for=&quot;fn1&quot; class=&quot;sidenote-number margin-toggle&quot;&gt;&lt;/label&gt;&lt;input id=&quot;fn1&quot; type=&quot;checkbox&quot; class=&quot;margin-toggle&quot;&gt;&lt;span class=&quot;note-right sidenote note&quot;&gt;&lt;span class=&quot;footnote-p&quot;&gt;For instance, Markdown footnotes are turned into side notes with
a soupault plugin. &lt;/span&gt;
&lt;/span&gt;. To deploy the website, I was relying
on &lt;code class=&quot;hljs&quot;&gt;rsync&lt;/code&gt; to sync the contents of the &lt;code class=&quot;hljs&quot;&gt;out/~lthms&lt;/code&gt; directory with the
directory statically served by a Nginx instance on my personal server.&lt;/p&gt;
&lt;p&gt;The first step of my little toy project is to actually embed the output of
soupault into an OCaml program.&lt;/p&gt;
&lt;p&gt;That’s where &lt;a href=&quot;https://github.com/mirage/ocaml-crunch&quot; class=&quot;hover-coral&quot; marked=&quot;&quot;&gt;ocaml-crunch&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#github&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt; comes in handy. It is a &lt;a href=&quot;https://mirage.io/&quot; class=&quot;hover-sky&quot; marked=&quot;&quot;&gt;MirageOS&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt; project, whose
only job is to generate an OCaml module from a file system directory. It is
straightforward to use it from Dune.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-lisp&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;; file: out/dune&lt;/span&gt;
(&lt;span class=&quot;hljs-name&quot;&gt;rule&lt;/span&gt;
 (&lt;span class=&quot;hljs-name&quot;&gt;target&lt;/span&gt; website_content.ml)
 (&lt;span class=&quot;hljs-name&quot;&gt;deps&lt;/span&gt; (&lt;span class=&quot;hljs-name&quot;&gt;source_tree&lt;/span&gt; ~lthms))
 (&lt;span class=&quot;hljs-name&quot;&gt;action&lt;/span&gt;
  (&lt;span class=&quot;hljs-name&quot;&gt;run&lt;/span&gt; ocaml-crunch -m plain -o %{target} -s ~lthms)))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This snippet generates the &lt;code class=&quot;hljs&quot;&gt;website_content.ml&lt;/code&gt; module, which we can then
expose through a library with the &lt;code class=&quot;hljs&quot;&gt;library&lt;/code&gt; stanza.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-lisp&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;; file: out/dune&lt;/span&gt;
(&lt;span class=&quot;hljs-name&quot;&gt;library&lt;/span&gt;
 (&lt;span class=&quot;hljs-name&quot;&gt;name&lt;/span&gt; website_content))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And we are basically done. Excluding an &lt;code class=&quot;hljs&quot;&gt;Internal&lt;/code&gt; module, the signature of
&lt;code class=&quot;hljs&quot;&gt;Website_content&lt;/code&gt; is pretty straightforward.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-ocaml&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;val&lt;/span&gt; file_list : &lt;span class=&quot;hljs-built_in&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;hljs-built_in&quot;&gt;list&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;val&lt;/span&gt; read : &lt;span class=&quot;hljs-built_in&quot;&gt;string&lt;/span&gt; -&amp;gt; &lt;span class=&quot;hljs-built_in&quot;&gt;string&lt;/span&gt; option
&lt;span class=&quot;hljs-keyword&quot;&gt;val&lt;/span&gt; hash : &lt;span class=&quot;hljs-built_in&quot;&gt;string&lt;/span&gt; -&amp;gt; &lt;span class=&quot;hljs-built_in&quot;&gt;string&lt;/span&gt; option
&lt;span class=&quot;hljs-keyword&quot;&gt;val&lt;/span&gt; size : &lt;span class=&quot;hljs-built_in&quot;&gt;string&lt;/span&gt; -&amp;gt; &lt;span class=&quot;hljs-built_in&quot;&gt;int&lt;/span&gt; option
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Serving the content with Dream&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://aantron.github.io/dream/&quot; class=&quot;hover-lemon&quot; marked=&quot;&quot;&gt;Dream&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#github&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt; is a cool project, and provides a straightforward API that we can
leverage to turn our list of in-memory files into an HTTP server.&lt;/p&gt;
&lt;h3&gt;Naive Approach&lt;/h3&gt;
&lt;p&gt;Our goal now is to create a &lt;code class=&quot;hljs&quot;&gt;Dream.handler&lt;/code&gt; for each item in
&lt;code class=&quot;hljs language-ocaml&quot;&gt;file_list&lt;/code&gt;. Done naively (as was my first attempt), it gives you
something of the form:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-ocaml&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; make_handler ~content path =
  &lt;span class=&quot;hljs-type&quot;&gt;Dream&lt;/span&gt;.get path (&lt;span class=&quot;hljs-keyword&quot;&gt;fun&lt;/span&gt; req -&amp;gt;
    &lt;span class=&quot;hljs-type&quot;&gt;Lwt&lt;/span&gt;.return (&lt;span class=&quot;hljs-type&quot;&gt;Dream&lt;/span&gt;.response content)))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Which we can use to build the main route we will then pass to &lt;code class=&quot;hljs&quot;&gt;Dream.router&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-ocaml&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; website_route =
  &lt;span class=&quot;hljs-type&quot;&gt;Dream&lt;/span&gt;.scope &lt;span class=&quot;hljs-string&quot;&gt;&quot;~lthms&quot;&lt;/span&gt; &lt;span class=&quot;hljs-literal&quot;&gt;[]&lt;/span&gt;
  @@ &lt;span class=&quot;hljs-type&quot;&gt;List&lt;/span&gt;.map
       (&lt;span class=&quot;hljs-keyword&quot;&gt;fun&lt;/span&gt; path -&amp;gt;
         &lt;span class=&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; content = &lt;span class=&quot;hljs-type&quot;&gt;Option&lt;/span&gt;.get (&lt;span class=&quot;hljs-type&quot;&gt;Website_content&lt;/span&gt;.read path) &lt;span class=&quot;hljs-keyword&quot;&gt;in&lt;/span&gt;
         make_handler ~content path)
       &lt;span class=&quot;hljs-type&quot;&gt;Website_content&lt;/span&gt;.file_list
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With this approach, we build our handlers once, and then the lookup is done by
Dream’s router. It could be an interesting experiment to see if doing the
lookup ourselves is more performant (since Dream’s router is very generic,
while in our case we don’t really need to parse anything). I remember Xe
routing is basically going through a linked list, which seems strange at first,
but works very well in practice because they have ordered said list with the
most recent articles up front, and everybody comes to their website to read the
latest article anyway.&lt;/p&gt;
&lt;p&gt;It does not take an extensive QA process to figure out that this approach
is far from being enough. To name a few things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;My website assumes &lt;code class=&quot;hljs&quot;&gt;http://path/index.html&lt;/code&gt; can be accessed with
&lt;code class=&quot;hljs&quot;&gt;http://path/&lt;/code&gt; or &lt;code class=&quot;hljs&quot;&gt;http://path&lt;/code&gt;. Our little snippet does not handle this.&lt;/li&gt;
&lt;li&gt;Browsers expect the &lt;code class=&quot;hljs&quot;&gt;Content-Type&lt;/code&gt; headers to be correctly set. To give an
example, they won&apos;t load a CSS file if the &lt;code class=&quot;hljs&quot;&gt;Content-Type&lt;/code&gt; header is not set
to &lt;code class=&quot;hljs&quot;&gt;text/css&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Browsers work best for websites that take the time to provide caching
directives. Our little snippet does not care to do so.&lt;/li&gt;
&lt;li&gt;Even if my website is rather lightweight&lt;label for=&quot;fn2&quot; class=&quot;sidenote-number margin-toggle&quot;&gt;&lt;/label&gt;&lt;input id=&quot;fn2&quot; type=&quot;checkbox&quot; class=&quot;margin-toggle&quot;&gt;&lt;span class=&quot;note-left sidenote note&quot;&gt;&lt;span class=&quot;footnote-p&quot;&gt;20MBytes at the time of writing the first version of this article. &lt;/span&gt;
&lt;/span&gt;, compressing the response
of our HTTP server for clients that support it is always a good idea.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is a gentle reminder of all the things Nginx can do for you with very
little configuration.&lt;/p&gt;
&lt;h3&gt;Handling &lt;code class=&quot;hljs&quot;&gt;index.html&lt;/code&gt; Synonyms&lt;/h3&gt;
&lt;p&gt;This one is rather simple. For files named &lt;code class=&quot;hljs&quot;&gt;index.html&lt;/code&gt;, we need 3 handers, not
just one. We can achieve this with an additional helper
&lt;code class=&quot;hljs&quot;&gt;make_handler_remove_suffix&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-ocaml&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; make_handler_remove_suffix ~content path suffix
    =
  &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;hljs-type&quot;&gt;String&lt;/span&gt;.ends_with ~suffix path &lt;span class=&quot;hljs-keyword&quot;&gt;then&lt;/span&gt;
    &lt;span class=&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; alt_path =
      &lt;span class=&quot;hljs-type&quot;&gt;String&lt;/span&gt;.sub path &lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt; (&lt;span class=&quot;hljs-type&quot;&gt;String&lt;/span&gt;.length path - &lt;span class=&quot;hljs-type&quot;&gt;String&lt;/span&gt;.length suffix)
    &lt;span class=&quot;hljs-keyword&quot;&gt;in&lt;/span&gt;
    [ make_handler ~content alt_path ]
  &lt;span class=&quot;hljs-keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;hljs-literal&quot;&gt;[]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Updating the &lt;code class=&quot;hljs&quot;&gt;website_route&lt;/code&gt; definition to use &lt;code class=&quot;hljs&quot;&gt;make_handler_remove_suffix&lt;/code&gt; is
quite easy as well.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-patch&quot;&gt; let website_route =
   Dream.scope &quot;~lthms&quot; []
&lt;span class=&quot;hljs-deletion&quot;&gt;-  @@ List.map&lt;/span&gt;
&lt;span class=&quot;hljs-addition&quot;&gt;+  @@ List.concat_map&lt;/span&gt;
        (fun path -&amp;gt;
          let content = Option.get (Website_content.read path) in
&lt;span class=&quot;hljs-deletion&quot;&gt;-         make_handler ~content path)&lt;/span&gt;
&lt;span class=&quot;hljs-addition&quot;&gt;+         if path = &quot;index.html&quot; then&lt;/span&gt;
&lt;span class=&quot;hljs-addition&quot;&gt;+           (* Special case to deal with &quot;index.html&quot; which needs to be&lt;/span&gt;
&lt;span class=&quot;hljs-addition&quot;&gt;+              recognized by the route &quot;/&quot; *)&lt;/span&gt;
&lt;span class=&quot;hljs-addition&quot;&gt;+           [&lt;/span&gt;
&lt;span class=&quot;hljs-addition&quot;&gt;+             make_handler ~content &quot;/&quot;;&lt;/span&gt;
&lt;span class=&quot;hljs-addition&quot;&gt;+             make_handler ~content &quot;&quot;;&lt;/span&gt;
&lt;span class=&quot;hljs-addition&quot;&gt;+             make_handler ~content &quot;index.html&quot;;&lt;/span&gt;
&lt;span class=&quot;hljs-addition&quot;&gt;+           ]&lt;/span&gt;
&lt;span class=&quot;hljs-addition&quot;&gt;+         else&lt;/span&gt;
&lt;span class=&quot;hljs-addition&quot;&gt;+           make_handler_remove_suffix ~content path&lt;/span&gt;
&lt;span class=&quot;hljs-addition&quot;&gt;+             &quot;/index.html&quot;&lt;/span&gt;
&lt;span class=&quot;hljs-addition&quot;&gt;+           @ make_handler_remove_suffix ~content&lt;/span&gt;
&lt;span class=&quot;hljs-addition&quot;&gt;+               path &quot;index.html&quot;&lt;/span&gt;
&lt;span class=&quot;hljs-addition&quot;&gt;+           @ [ make_handler ~content path ])&lt;/span&gt;
        Website_content.file_list
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With that, &lt;code class=&quot;hljs&quot;&gt;https://soap.coffee/~lthms/posts/index.html&lt;/code&gt; returns the same pages
as &lt;code class=&quot;hljs&quot;&gt;https://soap.coffee/~lthms/posts&lt;/code&gt; or &lt;code class=&quot;hljs&quot;&gt;https://soap.coffee/~lthms/posts&lt;/code&gt;.
Check.&lt;/p&gt;
&lt;h3&gt;Supporting &lt;code class=&quot;hljs&quot;&gt;Content-Type&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code class=&quot;hljs&quot;&gt;Content-Type&lt;/code&gt; is an HTTP header which is used by the receiver of the HTTP
message (whether it is a request or a response) to interpret its content.&lt;/p&gt;
&lt;p&gt;For instance, when building a RPC API, &lt;code class=&quot;hljs&quot;&gt;Content-Type&lt;/code&gt; is used by the server to
know how to parse the request body (&lt;code class=&quot;hljs language-http&quot;&gt;&lt;span class=&quot;hljs-attribute&quot;&gt;Content-Type&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;: &lt;/span&gt;application/json&lt;/code&gt; or
&lt;code class=&quot;hljs language-http&quot;&gt;&lt;span class=&quot;hljs-attribute&quot;&gt;Content-Type&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;: &lt;/span&gt;application/octet-stream&lt;/code&gt; being two popular choices, for
JSON or binary encoding, respectively).&lt;/p&gt;
&lt;p&gt;In our case, the &lt;code class=&quot;hljs&quot;&gt;Content-Type&lt;/code&gt; header is used by the HTTP server to
communicate the nature of the content to browsers. For my website, I can just
use the file extensions to infer the correct header to set. First, we list the
extensions that are actually used.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-ocaml&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; content_types =
  [
    (&lt;span class=&quot;hljs-string&quot;&gt;&quot;.html&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&quot;text/html&quot;&lt;/span&gt;);
    (&lt;span class=&quot;hljs-string&quot;&gt;&quot;.css&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&quot;text/css&quot;&lt;/span&gt;);
    (&lt;span class=&quot;hljs-string&quot;&gt;&quot;.xml&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&quot;text/xml&quot;&lt;/span&gt;);
    (&lt;span class=&quot;hljs-string&quot;&gt;&quot;.png&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&quot;image/png&quot;&lt;/span&gt;);
    (&lt;span class=&quot;hljs-string&quot;&gt;&quot;.svg&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&quot;image/svg+xml&quot;&lt;/span&gt;);
    (&lt;span class=&quot;hljs-string&quot;&gt;&quot;.gz&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&quot;application/gzip&quot;&lt;/span&gt;);
    (&lt;span class=&quot;hljs-string&quot;&gt;&quot;.pub&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&quot;text/plain&quot;&lt;/span&gt;);
  ]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A header in Dream is encoded as a &lt;code class=&quot;hljs language-ocaml&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;string&lt;/span&gt; * &lt;span class=&quot;hljs-built_in&quot;&gt;string&lt;/span&gt;&lt;/code&gt; value, with the
first &lt;code class=&quot;hljs language-ocaml&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;string&lt;/span&gt;&lt;/code&gt; being the header name and the second being the header
value.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-ocaml&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; content_type_header path =
  &lt;span class=&quot;hljs-type&quot;&gt;List&lt;/span&gt;.filter_map
    (&lt;span class=&quot;hljs-keyword&quot;&gt;fun&lt;/span&gt; (ext, content_type) -&amp;gt;
      &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;hljs-type&quot;&gt;String&lt;/span&gt;.ends_with ~suffix:ext path &lt;span class=&quot;hljs-keyword&quot;&gt;then&lt;/span&gt;
        &lt;span class=&quot;hljs-type&quot;&gt;Some&lt;/span&gt; (&lt;span class=&quot;hljs-string&quot;&gt;&quot;Content-Type&quot;&lt;/span&gt;, content_type)
      &lt;span class=&quot;hljs-keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;hljs-type&quot;&gt;None&lt;/span&gt;)
    content_types
  |&amp;gt; assert_f
       ~error_msg:&lt;span class=&quot;hljs-type&quot;&gt;Format&lt;/span&gt;.(sprintf &lt;span class=&quot;hljs-string&quot;&gt;&quot;Unsupported file type %s&quot;&lt;/span&gt; path)
       (( &amp;lt;&amp;gt; ) &lt;span class=&quot;hljs-literal&quot;&gt;[]&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;with &lt;code class=&quot;hljs language-ocaml&quot;&gt;assert_f&lt;/code&gt; being defined as follows.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-ocaml&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; assert_f ~error_msg f v =
  &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; f v &lt;span class=&quot;hljs-keyword&quot;&gt;then&lt;/span&gt; v &lt;span class=&quot;hljs-keyword&quot;&gt;else&lt;/span&gt; failwith error_msg
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code class=&quot;hljs language-ocaml&quot;&gt;assert_f&lt;/code&gt; is used to enforce that I don’t deploy a website which
contains route lacking a &lt;code class=&quot;hljs&quot;&gt;Content-Type&lt;/code&gt; header. For instance, if I remove the
&lt;code class=&quot;hljs language-ocaml&quot;&gt;&lt;span class=&quot;hljs-string&quot;&gt;&quot;html&quot;&lt;/span&gt;&lt;/code&gt; entry of the &lt;code class=&quot;hljs language-ocaml&quot;&gt;content_type&lt;/code&gt; list, I get this error
when I try to execute the server.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs&quot;&gt;Fatal error: exception Failure(&quot;Unsupported file type index.html&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is because the headers are only computed once, when each &lt;code class=&quot;hljs&quot;&gt;route&lt;/code&gt; are
defined. This is a key principle of this project: compute once, serve many
time&lt;label for=&quot;fn3&quot; class=&quot;sidenote-number margin-toggle&quot;&gt;&lt;/label&gt;&lt;input id=&quot;fn3&quot; type=&quot;checkbox&quot; class=&quot;margin-toggle&quot;&gt;&lt;span class=&quot;note-right sidenote note&quot;&gt;&lt;span class=&quot;footnote-p&quot;&gt;I would love to get a compilation error instead (considering there
are no runtime values involved), but have not looked into this just yet. &lt;/span&gt;
&lt;/span&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-patch&quot;&gt; let website_route =
   Dream.scope &quot;~lthms&quot; []
   @@ List.concat_map
        (fun path -&amp;gt;
          let content = Option.get (Website_content.read path) in
&lt;span class=&quot;hljs-addition&quot;&gt;+         let headers = content_type_header path in&lt;/span&gt;
          if path = &quot;index.html&quot; then
            (* Special case to deal with &quot;index.html&quot; which needs to be
               recognized by the route &quot;/&quot; *)
            [
&lt;span class=&quot;hljs-deletion&quot;&gt;-             make_handler ~content &quot;/&quot;;&lt;/span&gt;
&lt;span class=&quot;hljs-deletion&quot;&gt;-             make_handler ~content &quot;&quot;;&lt;/span&gt;
&lt;span class=&quot;hljs-deletion&quot;&gt;-             make_handler ~content &quot;index.html&quot;;&lt;/span&gt;
&lt;span class=&quot;hljs-addition&quot;&gt;+             make_handler ~headers ~content &quot;/&quot;;&lt;/span&gt;
&lt;span class=&quot;hljs-addition&quot;&gt;+             make_handler ~headers ~content &quot;&quot;;&lt;/span&gt;
&lt;span class=&quot;hljs-addition&quot;&gt;+             make_handler ~headers ~content &quot;index.html&quot;;&lt;/span&gt;
            ]
          else
&lt;span class=&quot;hljs-deletion&quot;&gt;-           make_handler_remove_suffix ~content path&lt;/span&gt;
&lt;span class=&quot;hljs-addition&quot;&gt;+           make_handler_remove_suffix ~headers ~content path&lt;/span&gt;
              &quot;/index.html&quot;
&lt;span class=&quot;hljs-deletion&quot;&gt;-           @ make_handler_remove_suffix ~content&lt;/span&gt;
&lt;span class=&quot;hljs-addition&quot;&gt;+           @ make_handler_remove_suffix ~headers ~content&lt;/span&gt;
                path &quot;index.html&quot;
&lt;span class=&quot;hljs-deletion&quot;&gt;-           @ [ make_handler ~content path ])&lt;/span&gt;
&lt;span class=&quot;hljs-addition&quot;&gt;+           @ [ make_handler ~headers ~content path ])&lt;/span&gt;
        Website_content.file_list
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(The changes in &lt;code class=&quot;hljs&quot;&gt;make_handler&lt;/code&gt; and &lt;code class=&quot;hljs&quot;&gt;make_handler_remove_prefix&lt;/code&gt; are left as an
exercise to enthusiast readers)&lt;/p&gt;
&lt;h3&gt;Compressing if Requested&lt;/h3&gt;
&lt;p&gt;Nowadays, computations are cheap, while downloading data costs time (and
sometimes money). As a consequence, it is often a good idea for a server to
compress a large HTTP response, and browsers do ask them to do so, by setting
the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Encoding&quot; class=&quot;hover-periwinkle&quot; marked=&quot;&quot;&gt;&lt;code class=&quot;hljs language-http&quot;&gt;Accept-Encoding&lt;/code&gt;&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt; header of their requests.&lt;/p&gt;
&lt;p&gt;The value of the &lt;code class=&quot;hljs language-http&quot;&gt;Accept-Encoding&lt;/code&gt; header is a comma-separated list of
supported compression algorithms, optionally ordered with a priority value &lt;code class=&quot;hljs&quot;&gt;q&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;For instance, &lt;code class=&quot;hljs language-http&quot;&gt;&lt;span class=&quot;hljs-attribute&quot;&gt;Accept-Encoding&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;: &lt;/span&gt;gzip;q=0.5, deflate;q=0.3, identity&lt;/code&gt;
tells you that the browser supports three encoding methods: &lt;code class=&quot;hljs&quot;&gt;gzip&lt;/code&gt;, &lt;code class=&quot;hljs&quot;&gt;deflate&lt;/code&gt;
and &lt;code class=&quot;hljs&quot;&gt;identity&lt;/code&gt; (no compression), and the browser prefers &lt;code class=&quot;hljs&quot;&gt;gzip&lt;/code&gt; over &lt;code class=&quot;hljs&quot;&gt;deflate&lt;/code&gt;.
Besides, the request can provide several &lt;code class=&quot;hljs&quot;&gt;Accept-Encoding&lt;/code&gt; headers instead of
just one, so we can have&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-http&quot;&gt;&lt;span class=&quot;hljs-attribute&quot;&gt;Accept-Encoding&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;: &lt;/span&gt;gzip;q=0.5
&lt;span class=&quot;hljs-attribute&quot;&gt;Accept-Encoding&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;: &lt;/span&gt;deflate;q=0.3
&lt;span class=&quot;hljs-attribute&quot;&gt;Accept-Encoding&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;: &lt;/span&gt;identity
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code class=&quot;hljs&quot;&gt;String&lt;/code&gt; module provides everything we need to check if a browser
supports gzip as an encoding method&lt;label for=&quot;fn4&quot; class=&quot;sidenote-number margin-toggle&quot;&gt;&lt;/label&gt;&lt;input id=&quot;fn4&quot; type=&quot;checkbox&quot; class=&quot;margin-toggle&quot;&gt;&lt;span class=&quot;note-left sidenote note&quot;&gt;&lt;span class=&quot;footnote-p&quot;&gt;Spoiler: they do. I was even wondering at some point if I could
just &lt;em&gt;always&lt;/em&gt; return GZIP-compressed values, ignoring the
&lt;code class=&quot;hljs language-http&quot;&gt;Accept-Encoding&lt;/code&gt; header altogether. If you do that, though, &lt;code class=&quot;hljs&quot;&gt;curl&lt;/code&gt;
becomes annoying to use (it does not uncompress the response automatically,
and instead complains about being about to write binary to the standard
output). &lt;/span&gt;
&lt;/span&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-ocaml&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;(* For [method(; q=val)?], returns [method], except if
   [q=0]. *)&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; to_directive str =
  &lt;span class=&quot;hljs-keyword&quot;&gt;match&lt;/span&gt; &lt;span class=&quot;hljs-type&quot;&gt;String&lt;/span&gt;.split_on_char &lt;span class=&quot;hljs-string&quot;&gt;&apos;;&apos;&lt;/span&gt; str |&amp;gt; &lt;span class=&quot;hljs-type&quot;&gt;List&lt;/span&gt;.map &lt;span class=&quot;hljs-type&quot;&gt;String&lt;/span&gt;.trim &lt;span class=&quot;hljs-keyword&quot;&gt;with&lt;/span&gt;
  | [ x ] -&amp;gt; &lt;span class=&quot;hljs-type&quot;&gt;Some&lt;/span&gt; x
  | [ x; y ] -&amp;gt; (
      &lt;span class=&quot;hljs-keyword&quot;&gt;match&lt;/span&gt; &lt;span class=&quot;hljs-type&quot;&gt;String&lt;/span&gt;.split_on_char &lt;span class=&quot;hljs-string&quot;&gt;&apos;=&apos;&lt;/span&gt; y |&amp;gt; &lt;span class=&quot;hljs-type&quot;&gt;List&lt;/span&gt;.map &lt;span class=&quot;hljs-type&quot;&gt;String&lt;/span&gt;.trim &lt;span class=&quot;hljs-keyword&quot;&gt;with&lt;/span&gt;
      | [ &lt;span class=&quot;hljs-string&quot;&gt;&quot;q&quot;&lt;/span&gt;; &lt;span class=&quot;hljs-string&quot;&gt;&quot;0&quot;&lt;/span&gt; ] -&amp;gt; &lt;span class=&quot;hljs-type&quot;&gt;None&lt;/span&gt;
      | [ &lt;span class=&quot;hljs-string&quot;&gt;&quot;q&quot;&lt;/span&gt;; _ ] -&amp;gt; &lt;span class=&quot;hljs-type&quot;&gt;Some&lt;/span&gt; x
      | _ -&amp;gt; &lt;span class=&quot;hljs-type&quot;&gt;None&lt;/span&gt;)
  | _ -&amp;gt; &lt;span class=&quot;hljs-type&quot;&gt;None&lt;/span&gt;

&lt;span class=&quot;hljs-comment&quot;&gt;(* [contains ~value:v header] returns [true] if [v] is a
   supported method listed in [header]. *)&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; contains ~&lt;span class=&quot;hljs-keyword&quot;&gt;value&lt;/span&gt; header =
  &lt;span class=&quot;hljs-type&quot;&gt;String&lt;/span&gt;.split_on_char &lt;span class=&quot;hljs-string&quot;&gt;&apos;,&apos;&lt;/span&gt; header
  |&amp;gt; &lt;span class=&quot;hljs-type&quot;&gt;List&lt;/span&gt;.to_seq |&amp;gt; &lt;span class=&quot;hljs-type&quot;&gt;Seq&lt;/span&gt;.map &lt;span class=&quot;hljs-type&quot;&gt;String&lt;/span&gt;.trim
  |&amp;gt; &lt;span class=&quot;hljs-type&quot;&gt;Seq&lt;/span&gt;.filter_map to_directive
  |&amp;gt; &lt;span class=&quot;hljs-type&quot;&gt;Seq&lt;/span&gt;.exists (( = ) &lt;span class=&quot;hljs-keyword&quot;&gt;value&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We use &lt;code class=&quot;hljs language-ocaml&quot;&gt;contains&lt;/code&gt; to tell us if we can return a compressed response,
which leaves us with one final question: how to compress said response?&lt;/p&gt;
&lt;p&gt;The OCaml ecosystem seems to have picked &lt;a href=&quot;https://ocaml.org/p/camlzip/latest&quot; class=&quot;hover-sky&quot; marked=&quot;&quot;&gt;camlzip&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt; library when GZIP is
involved&lt;label for=&quot;fn5&quot; class=&quot;sidenote-number margin-toggle&quot;&gt;&lt;/label&gt;&lt;input id=&quot;fn5&quot; type=&quot;checkbox&quot; class=&quot;margin-toggle&quot;&gt;&lt;span class=&quot;note-right sidenote note&quot;&gt;&lt;span class=&quot;footnote-p&quot;&gt;You know it is a legitimate OCaml library when one of the top-level
modules &lt;a href=&quot;https://ocaml.org/p/camlzip/latest/doc/Zlib/&quot; class=&quot;hover-peach&quot; marked=&quot;&quot;&gt;is not documented at all&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;. &lt;/span&gt;
&lt;/span&gt;. What is surprising with this library is that it does not
support in-memory compression: the functions expect channels, not
&lt;code class=&quot;hljs language-ocaml&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;bytes&lt;/span&gt;&lt;/code&gt;. That is quite annoying, because we are specifically doing this
&lt;strong&gt;not&lt;/strong&gt; to use files.&lt;/p&gt;
&lt;p&gt;The Internet is helpful here, and quickly suggests using pipes. It works when
you remember –or figure out– that pipes are a blocking mechanism: one does not
just write a buffer of arbitrary size in a pipe, because after something like
4KBytes, writing becomes blocking until a read happens to free some space.
That’s not a big problem: we can read and write concurrently to the pipe using
threads, and OCaml 5 makes it quite easy to do so with the &lt;code class=&quot;hljs language-ocaml&quot;&gt;&lt;span class=&quot;hljs-type&quot;&gt;Domain&lt;/span&gt;&lt;/code&gt;
module.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-ocaml&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; gzip content =
  &lt;span class=&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; inc, ouc = &lt;span class=&quot;hljs-type&quot;&gt;Unix&lt;/span&gt;.pipe &lt;span class=&quot;hljs-literal&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;in&lt;/span&gt;
  &lt;span class=&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; ouc = &lt;span class=&quot;hljs-type&quot;&gt;Gzip&lt;/span&gt;.open_out_chan ~level:&lt;span class=&quot;hljs-number&quot;&gt;6&lt;/span&gt; &lt;span class=&quot;hljs-type&quot;&gt;Unix&lt;/span&gt;.(out_channel_of_descr ouc) &lt;span class=&quot;hljs-keyword&quot;&gt;in&lt;/span&gt;
  &lt;span class=&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; _writer =
    &lt;span class=&quot;hljs-type&quot;&gt;Domain&lt;/span&gt;.spawn (&lt;span class=&quot;hljs-keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;hljs-literal&quot;&gt;()&lt;/span&gt; -&amp;gt;
        &lt;span class=&quot;hljs-type&quot;&gt;Gzip&lt;/span&gt;.output_substring ouc content &lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;hljs-type&quot;&gt;String&lt;/span&gt;.(length content);
        &lt;span class=&quot;hljs-type&quot;&gt;Gzip&lt;/span&gt;.close_out ouc)
  &lt;span class=&quot;hljs-keyword&quot;&gt;in&lt;/span&gt;
  &lt;span class=&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; res = &lt;span class=&quot;hljs-type&quot;&gt;In_channel&lt;/span&gt;.input_all &lt;span class=&quot;hljs-type&quot;&gt;Unix&lt;/span&gt;.(in_channel_of_descr inc) &lt;span class=&quot;hljs-keyword&quot;&gt;in&lt;/span&gt;
  &lt;span class=&quot;hljs-type&quot;&gt;Unix&lt;/span&gt;.close inc;
  res
&lt;/code&gt;&lt;/pre&gt;
&lt;div class=&quot;markdown-alert markdown-alert-caution&quot;&gt;&lt;p class=&quot;markdown-alert-title&quot;&gt;&lt;svg class=&quot;octicon octicon-stop mr-2&quot; viewbox=&quot;0 0 16 16&quot; version=&quot;1.1&quot; width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;M4.47.22A.749.749 0 0 1 5 0h6c.199 0 .389.079.53.22l4.25 4.25c.141.14.22.331.22.53v6a.749.749 0 0 1-.22.53l-4.25 4.25A.749.749 0 0 1 11 16H5a.749.749 0 0 1-.53-.22L.22 11.53A.749.749 0 0 1 0 11V5c0-.199.079-.389.22-.53Zm.84 1.28L1.5 5.31v5.38l3.81 3.81h5.38l3.81-3.81V5.31L10.69 1.5ZM8 4a.75.75 0 0 1 .75.75v3.5a.75.75 0 0 1-1.5 0v-3.5A.75.75 0 0 1 8 4Zm0 8a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;Caution&lt;/p&gt;&lt;p&gt;As rightfully &lt;a href=&quot;https://discuss.ocaml.org/t/serving-this-article-from-ram-with-dream-for-fun-and-no-real-benefit/15856/6?u=lthms&quot; class=&quot;hover-coral&quot; marked=&quot;&quot;&gt;pointed out&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt; by &lt;a href=&quot;https://erratique.ch/contact.en&quot; class=&quot;hover-peach&quot; marked=&quot;&quot;&gt;Daniel Bünzli&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;, the &lt;code class=&quot;hljs&quot;&gt;gzip&lt;/code&gt;
function presented in this article is full of shortcomings. To quote the
message, &lt;em&gt;the function can leak fds in case of errors and domains are not
meant to be used that way (it’s rather spawn one long running domain per CPU
you have). It’s not necessarily more complicated to correct it to use
Fun.protect invocations to make sure all your fds get closed even if the
function blows up and use Thread.create so that the netizens cut and paste
correct code.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In my opinion, this implementation is “good enough” for my use case, which is
compressing arbitrary strings before the HTTP server is even started. If it
were to be called in the handlers themselves, then definitely, it would not
be suitable.&lt;/p&gt;
&lt;p&gt;Keep that in mind if you want to borrow this code.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;We know how to decide whether to compress or not, and how to compress. The next
step is to modify &lt;code class=&quot;hljs&quot;&gt;make_handler&lt;/code&gt; accordingly.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-patch&quot;&gt;&lt;span class=&quot;hljs-deletion&quot;&gt;-let make_handler ~headers ~content path =&lt;/span&gt;
&lt;span class=&quot;hljs-addition&quot;&gt;+let make_handler ~headers ~gzip_content ~content path =&lt;/span&gt;
&lt;span class=&quot;hljs-addition&quot;&gt;+  let gzip_headers = (&quot;Content-Encoding&quot;, &quot;gzip&quot;) :: headers in&lt;/span&gt;
   Dream.get path (fun req -&amp;gt;
&lt;span class=&quot;hljs-deletion&quot;&gt;-    Lwt.return (Dream.response content)))&lt;/span&gt;
&lt;span class=&quot;hljs-addition&quot;&gt;+      match Dream.headers req &quot;Accept-Encoding&quot; with&lt;/span&gt;
&lt;span class=&quot;hljs-addition&quot;&gt;+      | accepted_encodings&lt;/span&gt;
&lt;span class=&quot;hljs-addition&quot;&gt;+        when List.exists (contains ~value:&quot;gzip&quot;) accepted_encodings -&amp;gt;&lt;/span&gt;
&lt;span class=&quot;hljs-addition&quot;&gt;+          Lwt.return @@ Dream.response ~headers:gzip_headers gzip_content&lt;/span&gt;
&lt;span class=&quot;hljs-addition&quot;&gt;+      | _ -&amp;gt; Lwt.return @@ Dream.response ~headers content)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code class=&quot;hljs&quot;&gt;gzip_content&lt;/code&gt; is computed only once (using our &lt;code class=&quot;hljs&quot;&gt;gzip&lt;/code&gt; function), and passed to
&lt;code class=&quot;hljs&quot;&gt;make_handler&lt;/code&gt;. This way, the only computation the handler needs to do is to
“parse” &lt;code class=&quot;hljs&quot;&gt;Accept-Encoding&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Caching&lt;/h3&gt;
&lt;p&gt;Compressing a page to reduce the number of bytes a browser needs to download is
fine. Letting the browser know it does not need to download anything because
it&apos;s previous version is still accurate is better.&lt;/p&gt;
&lt;p&gt;This is achieved through two complementary mechanisms: &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag&quot; class=&quot;hover-lavender&quot; marked=&quot;&quot;&gt;entity tags&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;
(&lt;code class=&quot;hljs language-http&quot;&gt;ETag&lt;/code&gt;)&lt;label for=&quot;fn6&quot; class=&quot;sidenote-number margin-toggle&quot;&gt;&lt;/label&gt;&lt;input id=&quot;fn6&quot; type=&quot;checkbox&quot; class=&quot;margin-toggle&quot;&gt;&lt;span class=&quot;note-left sidenote note&quot;&gt;&lt;span class=&quot;footnote-p&quot;&gt;My first encounter with entity tags was around the time GDPR was a
hot topic, because you can use them as a cheap replacement for cookies to
&lt;a href=&quot;https://levelup.gitconnected.com/no-cookies-no-problem-using-etags-for-user-tracking-3e745544176b&quot; class=&quot;hover-rose&quot; marked=&quot;&quot;&gt;track your users&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;. I remained at the surface level at the time,
it was fun learning more about them through this little project. &lt;/span&gt;
&lt;/span&gt;, and &lt;a href=&quot;https://developer.mozilla.org/fr/docs/Web/HTTP/Headers/Cache-Control&quot; class=&quot;hover-lavender&quot; marked=&quot;&quot;&gt;cache policies&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt; (&lt;code class=&quot;hljs language-http&quot;&gt;Cache-Control&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Entity tags are used to identify a resource, and are expected to change
every time the resource is updated. The general workflow goes like this: the
first time a browser requests &lt;code class=&quot;hljs&quot;&gt;https://soap.coffee/~lthms/index.html&lt;/code&gt;, it
caches the result along with the value of the &lt;code class=&quot;hljs language-http&quot;&gt;ETag&lt;/code&gt; header. The next
time it needs the page, it adds the header &lt;code class=&quot;hljs language-http&quot;&gt;If-None-Match&lt;/code&gt;, with the
ETag as its value. For such requests, the server is expected to return an empty
response with HTTP code 304 (&lt;em&gt;Not Modified&lt;/em&gt;).&lt;/p&gt;
&lt;p&gt;I decided to use the sha256 hash algorithm to compute the entity tag of each
resource of my website. The &lt;a href=&quot;https://ocaml.org/p/sha/latest&quot; class=&quot;hover-lemon&quot; marked=&quot;&quot;&gt;sha&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt; OCaml library looked like a good enough
candidate.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-ocaml&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;(* in `website_route` *)&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; etag = &lt;span class=&quot;hljs-type&quot;&gt;Sha256&lt;/span&gt;.(&lt;span class=&quot;hljs-built_in&quot;&gt;string&lt;/span&gt; content |&amp;gt; to_hex) &lt;span class=&quot;hljs-keyword&quot;&gt;in&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Interestingly, one question I had to answer was whether the entity tag of a
page needed to be different whether it was compressed or not. Internet almost
unanimously answered yes. So be it. We just need to keep in mind ETag values
are expected to be surrounded by quotes, and we are good to go. It is just a
matter of suffixing the ETag with &lt;code class=&quot;hljs&quot;&gt;+gzip&lt;/code&gt; in the compressed case.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-patch&quot;&gt;&lt;span class=&quot;hljs-deletion&quot;&gt;-let make_handler ~headers ~gzip_content ~content path =&lt;/span&gt;
&lt;span class=&quot;hljs-deletion&quot;&gt;-  let gzip_headers = (&quot;Content-Encoding&quot;, &quot;gzip&quot;) :: headers in&lt;/span&gt;
&lt;span class=&quot;hljs-addition&quot;&gt;+let make_handler ~headers ~etag ~gzip_content ~content path =&lt;/span&gt;
&lt;span class=&quot;hljs-addition&quot;&gt;+  let etag_gzip = Format.sprintf &quot;\&quot;%s+gzip\&quot;&quot; etag in&lt;/span&gt;
&lt;span class=&quot;hljs-addition&quot;&gt;+  let etag = Format.sprintf &quot;\&quot;%s\&quot;&quot; etag in&lt;/span&gt;
&lt;span class=&quot;hljs-addition&quot;&gt;+  let gzip_headers =&lt;/span&gt;
&lt;span class=&quot;hljs-addition&quot;&gt;+    (&quot;Content-Encoding&quot;, &quot;gzip&quot;) :: (&quot;ETag&quot;, etag_gzip) :: headers in&lt;/span&gt;
&lt;span class=&quot;hljs-addition&quot;&gt;+  let identity_headers = (&quot;ETag&quot;, etag) :: headers in&lt;/span&gt;
   Dream.get path (fun req -&amp;gt;
&lt;span class=&quot;hljs-deletion&quot;&gt;-      match Dream.headers req &quot;Accept-Encoding&quot; with&lt;/span&gt;
&lt;span class=&quot;hljs-deletion&quot;&gt;-      | accepted_encodings&lt;/span&gt;
&lt;span class=&quot;hljs-deletion&quot;&gt;-        when List.exists (contains ~value:&quot;gzip&quot;) accepted_encodings -&amp;gt;&lt;/span&gt;
&lt;span class=&quot;hljs-deletion&quot;&gt;-          Lwt.return @@ Dream.response ~headers:gzip_headers gzip_content&lt;/span&gt;
&lt;span class=&quot;hljs-deletion&quot;&gt;-      | _ -&amp;gt; Lwt.return @@ Dream.response ~headers content)&lt;/span&gt;
&lt;span class=&quot;hljs-addition&quot;&gt;+      match Dream.headers req &quot;If-None-Match&quot; with&lt;/span&gt;
&lt;span class=&quot;hljs-addition&quot;&gt;+      | [ previous_etag ] when previous_etag = etag || previous_etag = etag_gzip&lt;/span&gt;
&lt;span class=&quot;hljs-addition&quot;&gt;+        -&amp;gt;&lt;/span&gt;
&lt;span class=&quot;hljs-addition&quot;&gt;+          Lwt.return&lt;/span&gt;
&lt;span class=&quot;hljs-addition&quot;&gt;+          @@ Dream.response&lt;/span&gt;
&lt;span class=&quot;hljs-addition&quot;&gt;+               ~headers:((&quot;ETag&quot;, previous_etag) :: headers)&lt;/span&gt;
&lt;span class=&quot;hljs-addition&quot;&gt;+               ~code:304 &quot;&quot;&lt;/span&gt;
&lt;span class=&quot;hljs-addition&quot;&gt;+      | _ -&amp;gt; (&lt;/span&gt;
&lt;span class=&quot;hljs-addition&quot;&gt;+          match Dream.headers req &quot;Accept-Encoding&quot; with&lt;/span&gt;
&lt;span class=&quot;hljs-addition&quot;&gt;+          | accepted_encodings&lt;/span&gt;
&lt;span class=&quot;hljs-addition&quot;&gt;+            when List.exists (contains ~value:&quot;gzip&quot;) accepted_encodings -&amp;gt;&lt;/span&gt;
&lt;span class=&quot;hljs-addition&quot;&gt;+              Lwt.return @@ Dream.response ~headers:gzip_headers gzip_content&lt;/span&gt;
&lt;span class=&quot;hljs-addition&quot;&gt;+          | _ -&amp;gt; Lwt.return @@ Dream.response ~headers:identity_headers content))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Entity tags are useful, but you still need the browser to make an HTTP request
every single time you visit the website. By setting a cache policy, we can
remove even remove the need for this request most of the time. The
&lt;code class=&quot;hljs language-http&quot;&gt;Cache-Control&lt;/code&gt; header is used to set a number of parameters, including
the &lt;code class=&quot;hljs&quot;&gt;max-age&lt;/code&gt; value (in seconds).&lt;/p&gt;
&lt;p&gt;In my Nginx configuration, I had set &lt;code class=&quot;hljs&quot;&gt;max-age&lt;/code&gt; to a year for images. I did
the same thing here. Besides, I decided to set &lt;code class=&quot;hljs&quot;&gt;max-age&lt;/code&gt; for other resources
to 5 minutes. This seems like a good compromise: since my website does not
change very often, it is very unlikely that you happen to visit it when I
publish new content. Setting a 5-minute cache policy should let my readers
download each resource only once, yet get the freshest version at their next
visit&lt;label for=&quot;fn7&quot; class=&quot;sidenote-number margin-toggle&quot;&gt;&lt;/label&gt;&lt;input id=&quot;fn7&quot; type=&quot;checkbox&quot; class=&quot;margin-toggle&quot;&gt;&lt;span class=&quot;note-right sidenote note&quot;&gt;&lt;span class=&quot;footnote-p&quot;&gt;Dealing with the &lt;code class=&quot;hljs language-http&quot;&gt;Cache-Control&lt;/code&gt; header is basically the same
exercise as setting the correct &lt;code class=&quot;hljs language-http&quot;&gt;Content-Type&lt;/code&gt; header, and this
article is already long enough, which is why there is no diff or snippet in
this section. &lt;/span&gt;
&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;And with this, we are done. We get a standalone library to server our website
in a browser-friendly manner, which I can theoretically use to replace my
current Nginx-powered setup. Although… is it &lt;em&gt;that&lt;/em&gt; simple?&lt;/p&gt;
&lt;h2&gt;Building and Deploying the Website&lt;/h2&gt;
&lt;p&gt;Files are quite easy to share and deploy. As I mentioned earlier in this
article, you just need to &lt;code class=&quot;hljs language-bash&quot;&gt;rsync&lt;/code&gt; them and be done with it. &lt;em&gt;Binaries&lt;/em&gt;,
on the other hand… One cannot just assume a binary build on a machine X will
work on another machine Y. Some additional works need to be done.&lt;/p&gt;
&lt;p&gt;The most straightforward solution I know is to rely on static binaries. &lt;a href=&quot;OCamlStaticBinaries.html&quot; class=&quot;hover-rose&quot; marked=&quot;&quot;&gt;I have
already written about how to generate static binaries for OCaml
projects&lt;/a&gt;, so I had a pretty strong head start, but one drawback of the
approach I’ve described there (and that I have been using for &lt;a href=&quot;https://github.com/lthms/spatial-shell/releases&quot; class=&quot;hover-lemon&quot; marked=&quot;&quot;&gt;Spatial Shell’s
releases&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#github&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;&lt;label for=&quot;fn8&quot; class=&quot;sidenote-number margin-toggle&quot;&gt;&lt;/label&gt;&lt;input id=&quot;fn8&quot; type=&quot;checkbox&quot; class=&quot;margin-toggle&quot;&gt;&lt;span class=&quot;note-left sidenote note&quot;&gt;&lt;span class=&quot;footnote-p&quot;&gt;Not that there were many of them lately. &lt;/span&gt;
&lt;/span&gt;) is that it is rather slow (I have a script creating
a new local switch each time) and requires static libraries to be installed
(which Arch Linux does not provide).&lt;/p&gt;
&lt;p&gt;And so, I figured, why not build the static binary in Docker? This allows me to
use Alpine to get a static version of my system dependencies, and can be quite
fast thanks to &lt;a href=&quot;https://docs.docker.com/build/cache/&quot; class=&quot;hover-coral&quot; marked=&quot;&quot;&gt;Docker build cache&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;. The Dockerfile is quite simple:
one stage for building the system and OCaml dependencies, and one stage for
building the static binary.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-dockerfile&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;FROM&lt;/span&gt; alpine:&lt;span class=&quot;hljs-number&quot;&gt;3.21&lt;/span&gt; AS build_environment

&lt;span class=&quot;hljs-comment&quot;&gt;# Use alpine /bin/ash and set shell options&lt;/span&gt;
&lt;span class=&quot;hljs-comment&quot;&gt;# See https://docs.docker.com/build/building/best-practices/#using-pipes&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;SHELL&lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt; [&lt;span class=&quot;hljs-string&quot;&gt;&quot;/bin/ash&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&quot;-euo&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&quot;pipefail&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&quot;-c&quot;&lt;/span&gt;]&lt;/span&gt;

&lt;span class=&quot;hljs-keyword&quot;&gt;USER&lt;/span&gt; root
&lt;span class=&quot;hljs-keyword&quot;&gt;WORKDIR&lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt; /root&lt;/span&gt;

&lt;span class=&quot;hljs-keyword&quot;&gt;RUN&lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt; apk add autoconf automake bash build-base ca-certificates opam gcc \ &lt;/span&gt;
  git rsync gmp-dev libev-dev openssl-libs-static pkgconf zlib-static \
  openssl-dev zlib-dev
&lt;span class=&quot;hljs-keyword&quot;&gt;RUN&lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt; opam init --bare --&lt;span class=&quot;hljs-built_in&quot;&gt;yes&lt;/span&gt; --disable-sandboxing&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;COPY&lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt; makefile dune-project .&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;RUN&lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt; make _opam/.init OCAML=&lt;span class=&quot;hljs-string&quot;&gt;&quot;ocaml-option-static,ocaml-option-no-compression,ocaml.5.2.1&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;RUN&lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt; &lt;span class=&quot;hljs-built_in&quot;&gt;eval&lt;/span&gt; $(opam &lt;span class=&quot;hljs-built_in&quot;&gt;env&lt;/span&gt;) &amp;amp;&amp;amp; make server-deps&lt;/span&gt;

&lt;span class=&quot;hljs-keyword&quot;&gt;FROM&lt;/span&gt; build_environment AS builder

&lt;span class=&quot;hljs-keyword&quot;&gt;COPY&lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt; server ./server&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;COPY&lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt; out ./out&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;COPY&lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt; dune .&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;RUN&lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt; &lt;span class=&quot;hljs-built_in&quot;&gt;eval&lt;/span&gt; $(opam &lt;span class=&quot;hljs-built_in&quot;&gt;env&lt;/span&gt;) &amp;amp;&amp;amp; dune build server/main.exe --profile=static&lt;/span&gt;

&lt;span class=&quot;hljs-keyword&quot;&gt;FROM&lt;/span&gt; alpine:&lt;span class=&quot;hljs-number&quot;&gt;3.21&lt;/span&gt; AS soap.coffee

&lt;span class=&quot;hljs-keyword&quot;&gt;COPY&lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt; --from=builder /root/_build/default/server/main.exe /bin/soap.coffee&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, building my static binary becomes as simple as:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-bash&quot;&gt;docker build . -f ./build.Dockerfile \
  --target soap.coffee \
  -t soap.coffee:latest
docker create --name soap-coffee-build soap.coffee:latest
docker &lt;span class=&quot;hljs-built_in&quot;&gt;cp&lt;/span&gt; soap-coffee-build:/bin/soap.coffee .
docker &lt;span class=&quot;hljs-built_in&quot;&gt;rm&lt;/span&gt; -f soap-coffee-build
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code class=&quot;hljs language-bash&quot;&gt;docker &lt;span class=&quot;hljs-built_in&quot;&gt;cp&lt;/span&gt;&lt;/code&gt; does not work on an image, but on a container, so we need to
create one which can be destroyed shortly after.&lt;/p&gt;
&lt;p&gt;This little binary weights 38MBytes, which seems relatively reasonable to me,
considering my website weights 20MBytes. I guess it could be easy to reduce
this size by embedding the compressed version of my articles and images,
instead of the uncompressed one. But really, for my website, I’m not really
interested in investing the extra effort.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I would not recommend anyone to use this in production for anything remotely
important, but from my perspective, it was both fun and insightful. I was able
to refresh my memories about HTTP “internal,” among other things.&amp;nbsp;Again, as
far as I can tell, deploying my website this way did not bring me any benefit,
performance-wise; even worse, I am pretty sure the Dream server will not behave
as well as Nginx when it comes to handling the load (since it is limited to one
core, instead of several with Nginx).&lt;/p&gt;
&lt;p&gt;That being said, I am way too invested now… which is why, yes, you are
reading a blog post served to you directly from memory 🎉.&lt;/p&gt;
        
      </description>
    </item>
    
    
    
    <item>
      <title>The Free and Open Source Software Projects This Website is Built Upon in June 2023</title>
      <link>https://soap.coffee/~lthms/posts/Thanks2023.html</link>
      <guid>https://soap.coffee/~lthms/posts/Thanks2023.html</guid>
      <pubDate>June 23, 2023</pubDate>
      <description>
        
        &lt;h1&gt;The Free and Open Source Software Projects This Website is Built Upon in June 2023&lt;/h1&gt;&lt;div id=&quot;tags-list&quot;&gt;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#tag&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&amp;nbsp;&lt;a href=&quot;/~lthms/tags/meta.html&quot; class=&quot;tag hover-rose&quot; marked=&quot;&quot;&gt;meta&lt;/a&gt; &lt;/div&gt;
&lt;p&gt;In the past, I had a page called “Thanks!” dedicated to listing the free and
open source software projects I was relying on to create this website. Sadly,
this page was dropped during my latest overhaul, which is a shame because I do
think it is important to acknowledge these fantastic projects without which
this website would not exist.&lt;/p&gt;
&lt;p&gt;That being said, I do want to approach this exercise differently this time.
Instead of keeping one page up to date with the latest software stack I am
using, I plan to publish a new article every now and then&lt;label for=&quot;fn1&quot; class=&quot;sidenote-number margin-toggle&quot;&gt;&lt;/label&gt;&lt;input id=&quot;fn1&quot; type=&quot;checkbox&quot; class=&quot;margin-toggle&quot;&gt;&lt;span class=&quot;note-right sidenote note&quot;&gt;&lt;span class=&quot;footnote-p&quot;&gt;I really like the idea of keeping track of all the wonderful
tools I was lucky enough to find out and use over the years. &lt;/span&gt;
&lt;/span&gt;.&lt;/p&gt;
&lt;h2&gt;Authoring Contents&lt;/h2&gt;
&lt;p&gt;As mentioned in &lt;a href=&quot;/~lthms/posts/May2023.html&quot; class=&quot;hover-coral&quot; marked=&quot;&quot;&gt;my latest retrospective&lt;/a&gt;, I have
simplified how I write my contents. Nowadays, all my write-ups
are written in Markdown, and I use
&lt;a href=&quot;https://github.com/markdown-it/markdown-it&quot; class=&quot;hover-lavender&quot; marked=&quot;&quot;&gt;markdown-it&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#github&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt; to parse them and
generate nice and fancy HTML documents.&lt;/p&gt;
&lt;p&gt;In addition to the base parser that markdown-it is, I am relying on an
ever-growing collection of plugins:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.npmjs.com/package/markdown-it-footnote&quot; class=&quot;hover-sky&quot; marked=&quot;&quot;&gt;markdown-it-footnote&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.npmjs.com/package/markdown-it-figure&quot; class=&quot;hover-lemon&quot; marked=&quot;&quot;&gt;markdown-it-figure&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.npmjs.com/package/markdown-it-highlightjs&quot; class=&quot;hover-peach&quot; marked=&quot;&quot;&gt;markdown-it-highlightjs&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.npmjs.com/package/markdown-it-meta&quot; class=&quot;hover-lemon&quot; marked=&quot;&quot;&gt;markdown-it-meta&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.npmjs.com/package/markdown-it-custom-block&quot; class=&quot;hover-lavender&quot; marked=&quot;&quot;&gt;markdown-it-custom-block&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.npmjs.com/package/markdown-it-mermaid&quot; class=&quot;hover-periwinkle&quot; marked=&quot;&quot;&gt;markdown-it-mermaid&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.npmjs.com/package/@ryanxcharles/markdown-it-katex&quot; class=&quot;hover-coral&quot; marked=&quot;&quot;&gt;markdown-it-katex from &lt;em&gt;@ryanxcharles&lt;/em&gt;&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As a consequence, this website also benefits from two very nice projects:
&lt;a href=&quot;https://highlightjs.org/&quot; class=&quot;hover-rose&quot; marked=&quot;&quot;&gt;highlight.js&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt; for syntax highlighting, and
&lt;a href=&quot;https://katex.org/&quot; class=&quot;hover-lemon&quot; marked=&quot;&quot;&gt;&lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mtext&gt;KaTeX&lt;/mtext&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;\KaTeX&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&quot;katex-html&quot; aria-hidden=&quot;true&quot;&gt;&lt;span class=&quot;base&quot;&gt;&lt;span class=&quot;strut&quot; style=&quot;height:0.8988em;vertical-align:-0.2155em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord text&quot;&gt;&lt;span class=&quot;mord textrm&quot;&gt;K&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:-0.17em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;vlist-t&quot;&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.6833em;&quot;&gt;&lt;span style=&quot;top:-2.905em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:2.7em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;&lt;span class=&quot;mord textrm mtight sizing reset-size6 size3&quot;&gt;A&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:-0.15em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord text&quot;&gt;&lt;span class=&quot;mord textrm&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:-0.1667em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;vlist-t vlist-t2&quot;&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.4678em;&quot;&gt;&lt;span style=&quot;top:-2.7845em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;&lt;span class=&quot;mord textrm&quot;&gt;E&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-s&quot;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.2155em;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:-0.125em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord textrm&quot;&gt;X&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt; for displaying mathematics equations.&lt;/p&gt;
&lt;h2&gt;Frontend&lt;/h2&gt;
&lt;p&gt;In addition to &lt;a href=&quot;https://www.npmjs.com/package/normalize.css&quot; class=&quot;hover-rose&quot; marked=&quot;&quot;&gt;normalize.css&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt; to
“reset” the CSS defaults of various browsers, this website could not exist as it
is now without the awesome &lt;a href=&quot;https://edwardtufte.github.io/tufte-css/&quot; class=&quot;hover-coral&quot; marked=&quot;&quot;&gt;Tufte
CSS&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#github&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt; project. The sidenotes of this
website could not have existed without it.&lt;/p&gt;
&lt;h2&gt;HTML Generation&lt;/h2&gt;
&lt;p&gt;While markdown-it is doing the heavy lifting of turning my Markdown contents
in HTML pages, &lt;a href=&quot;https://soupault.app&quot; class=&quot;hover-periwinkle&quot; marked=&quot;&quot;&gt;soupault&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt; is the goldsmith which pieces
these pages together into a coherent whole.&lt;/p&gt;
&lt;p&gt;I really enjoy using soupault for my website. In a nutshell, this tool enables
me to customize the pages of my website with an API very similar to what
Javascript offers, but instead of the computation happening on the client side
every time the page is loaded, it is done on my laptop once, just before I
push the new pages. Just an example: for my sidenote pages, I was able to reuse
the markdown-it-footnote with zero modification, and I just had to write a
simple Lua plugin to customize its output to my (humble) needs.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;/~lthms/running.html&quot; class=&quot;hover-coral&quot; marked=&quot;&quot;&gt;running log&lt;/a&gt; is also another good example of an ad hoc page
I can generate using a simple Lua script.&lt;/p&gt;
        
      </description>
    </item>
    
    
    
    <item>
      <title>What happened since December 2022?</title>
      <link>https://soap.coffee/~lthms/posts/May2023.html</link>
      <guid>https://soap.coffee/~lthms/posts/May2023.html</guid>
      <pubDate>May 18, 2023</pubDate>
      <description>
        
        &lt;h1&gt;What happened since December 2022?&lt;/h1&gt;&lt;div id=&quot;tags-list&quot;&gt;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#tag&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&amp;nbsp;&lt;a href=&quot;/~lthms/tags/emacs.html&quot; class=&quot;tag hover-lavender&quot; marked=&quot;&quot;&gt;emacs&lt;/a&gt; &lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#tag&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&amp;nbsp;&lt;a href=&quot;/~lthms/tags/meta.html&quot; class=&quot;tag hover-lemon&quot; marked=&quot;&quot;&gt;meta&lt;/a&gt; &lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#tag&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&amp;nbsp;&lt;a href=&quot;/~lthms/tags/neovim.html&quot; class=&quot;tag hover-sky&quot; marked=&quot;&quot;&gt;neovim&lt;/a&gt; &lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#tag&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&amp;nbsp;&lt;a href=&quot;/~lthms/tags/releases.html&quot; class=&quot;tag hover-mint&quot; marked=&quot;&quot;&gt;releases&lt;/a&gt; &lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#tag&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&amp;nbsp;&lt;a href=&quot;/~lthms/tags/spatial-shell.html&quot; class=&quot;tag hover-sky&quot; marked=&quot;&quot;&gt;spatial-shell&lt;/a&gt; &lt;/div&gt;
&lt;p&gt;Initially, I started this “What happened” series as an exercise to publish
more regularly on this website. Suffice to say, I haven’t done a particularly
impressive job in that regard, which only means I have a lot of room for
improvement.&lt;/p&gt;
&lt;p&gt;Anyway, if the first few months of 2023 has been mostly &lt;code class=&quot;hljs language-bash&quot;&gt;&lt;span class=&quot;hljs-variable&quot;&gt;$WORK&lt;/span&gt;&lt;/code&gt; focus,
the same cannot be said for April and May. For one, I have started
&lt;a href=&quot;/~lthms/running.html&quot; class=&quot;hover-periwinkle&quot; marked=&quot;&quot;&gt;running&lt;/a&gt; again. But this is only the tip of the iceberg.&lt;/p&gt;
&lt;h2&gt;Spatial Shell got its first releases&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/lthms/spatial-shell&quot; class=&quot;hover-lavender&quot; marked=&quot;&quot;&gt;Spatial Shell&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#github&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt; is probably my hobby
project I am most excited about. The &lt;a href=&quot;/~lthms/posts/CFTSpatialShell.html&quot; class=&quot;hover-peach&quot; marked=&quot;&quot;&gt;“call for testers”
article&lt;/a&gt; I have published recently managed to
catch the attention of a few folks&lt;label for=&quot;fn1&quot; class=&quot;sidenote-number margin-toggle&quot;&gt;&lt;/label&gt;&lt;input id=&quot;fn1&quot; type=&quot;checkbox&quot; class=&quot;margin-toggle&quot;&gt;&lt;span class=&quot;note-right sidenote note&quot;&gt;&lt;span class=&quot;footnote-p&quot;&gt;You want to hear a lesson I learned the hard way just after publishing
it? Before calling for testers, it is better to &lt;a href=&quot;https://github.com/lthms/spatial-shell/issues/2#issuecomment-1527193430&quot; class=&quot;hover-coral&quot; marked=&quot;&quot;&gt;be sure your project can
actually be compiled easily by the potential
volunteers&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#github&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;. &lt;/span&gt;
&lt;/span&gt;. The perspective to publish such a
write-up was a very strong source of motivation for me to clean up a project I
was using daily for several months now, and I am very satisfied with the
result.&lt;/p&gt;
&lt;p&gt;Mass adoption is still a distant horizon, but still, the project is now
mainstream enough that it has already been mentioned in &lt;a href=&quot;https://discuss.ocaml.org/t/window-manager-xmonad-in-ocaml/12048/4&quot; class=&quot;hover-rose&quot; marked=&quot;&quot;&gt;a random topic on the
OCaml discourse by someone who isn’t
me&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;. 🎉&lt;/p&gt;
&lt;p&gt;This led me to formally release a first version of Spatial Shell in the end of
April, and a second today. For the first time, I have also published &lt;a href=&quot;https://aur.archlinux.org/packages/spatial-shell&quot; class=&quot;hover-lemon&quot; marked=&quot;&quot;&gt;an
Archlinux package&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;, to make
the life of potential early adopters even easier. Do not hesitate to upvote it
so that it can find its way to the &lt;code class=&quot;hljs&quot;&gt;extra&lt;/code&gt; repository some day.&lt;/p&gt;
&lt;h2&gt;Goodbye Emacs! Remember me, Neovim?&lt;/h2&gt;
&lt;p&gt;In 2015, I started using Coq for my PhD thesis and at the time, there was no
real support for (Neo)vim&lt;label for=&quot;fn2&quot; class=&quot;sidenote-number margin-toggle&quot;&gt;&lt;/label&gt;&lt;input id=&quot;fn2&quot; type=&quot;checkbox&quot; class=&quot;margin-toggle&quot;&gt;&lt;span class=&quot;note-left sidenote note&quot;&gt;&lt;span class=&quot;footnote-p&quot;&gt;The situation later improved. Nowadays, you can implement your theories
using &lt;a href=&quot;https://github.com/whonore/Coqtail&quot; class=&quot;hover-rose&quot; marked=&quot;&quot;&gt;Coqtail&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#github&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;, and &lt;a href=&quot;https://github.com/ejgallego/coq-lsp&quot; class=&quot;hover-mint&quot; marked=&quot;&quot;&gt;Coq
LSP&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#github&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt; will probably become a viable
and interesting setup in a near future. &lt;/span&gt;
&lt;/span&gt;. Everyone was using &lt;a href=&quot;https://proofgeneral.github.io/&quot; class=&quot;hover-rose&quot; marked=&quot;&quot;&gt;Proof
General&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#github&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt; and Emacs, so I was left with little
choice but to follow through. With only my courage and the &lt;a href=&quot;https://juanjoalvarez.net/posts/2014/vim-emacsevil-chaotic-migration-guide/&quot; class=&quot;hover-lavender&quot; marked=&quot;&quot;&gt;good advice of a
fellow “vimer” who had also made a similar
journey&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;,
I started using Emacs.&lt;/p&gt;
&lt;p&gt;Fast forward 8 years later, and my &lt;a href=&quot;https://src.soap.coffee/dotfiles/emacs.d&quot; class=&quot;hover-peach&quot; marked=&quot;&quot;&gt;Emacs
configuration&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt; has become a project
of its own. Overall, I was pretty happy with my setup, but in the same time, I
always remained a bit nostalgic of my Neovim days. This is probably why I
decided to give this old friend a try when my company bought me a new laptop. I
also used this as an opportunity to try out this LSP-thing everyone was talking
about.&lt;/p&gt;
&lt;p&gt;It has been a month now, and I do not plan to come back to my previous habits.
There are still some few edges here and there, I still need to get my head
around lua, but LSP is nice, and plugins like
&lt;a href=&quot;https://github.com/nvim-telescope/telescope.nvim&quot; class=&quot;hover-periwinkle&quot; marked=&quot;&quot;&gt;telescope&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#github&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt; are simply too
beautiful.&lt;/p&gt;
&lt;p&gt;That being said, there was one aspect of moving from Emacs to Neovim I had not
anticipated: Org mode. Which constitutes a perfect transition to the next
session.&lt;/p&gt;
&lt;h2&gt;Website redesign, again&lt;/h2&gt;
&lt;p&gt;Did you notice this website has been revamping recently? The changes are
actually deeper than “just” a redesign, to a point where I had to port &lt;em&gt;all&lt;/em&gt; my
write-ups to a different markup language&lt;label for=&quot;fn3&quot; class=&quot;sidenote-number margin-toggle&quot;&gt;&lt;/label&gt;&lt;input id=&quot;fn3&quot; type=&quot;checkbox&quot; class=&quot;margin-toggle&quot;&gt;&lt;span class=&quot;note-right sidenote note&quot;&gt;&lt;span class=&quot;footnote-p&quot;&gt;Are you starting to understand why “Org mode” was the perfect
transition to move on to this section? &lt;/span&gt;
&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Why, you ask? Well, it’s actually pretty simple: as time goes, I’ve grown
lazier.&lt;/p&gt;
&lt;p&gt;Let me give you some context. Until very recently, my website was built around
the idea to have literate programming as a first-class citizen of my author
tools. For instance, you can have a look at &lt;a href=&quot;/~lthms/posts/CleopatraV1.html&quot; class=&quot;hover-lemon&quot; marked=&quot;&quot;&gt;what used to be the literate
program which was responsible for generating the
website&lt;/a&gt;. Similarly, most of &lt;a href=&quot;/~lthms/tags/coq.html&quot; class=&quot;hover-coral&quot; marked=&quot;&quot;&gt;my write-ups about
Coq&lt;/a&gt; were actually Coq documents. Literate programming is
actually a very nice paradigm for authoring technical contents, because it
gives you the tools to keep said contents accurate and up-to-date. In a
nutshell, you cannot have a typo in one of your code snippets which would
prevent it from compiling, because you actually
compile the snippet and catch the typo when you try to generate your website.
Or at least, it is what I used to do.&lt;/p&gt;
&lt;p&gt;I decided to stop because, for all its benefits, this approach has one major
drawback: it is hard to maintain. I had invested quite some time and efforts to
keep my website sources under control, but it really was an everyday fight.
There are some strange things which start happening when you fully commit to
this, as I think I did. For instance, software dependencies tie your article
together. Suddenly, you cannot talk about this new fancy feature of the latest
Coq release without upgrading &lt;em&gt;all&lt;/em&gt; your write-ups implemented as Coq
documents&lt;label for=&quot;fn4&quot; class=&quot;sidenote-number margin-toggle&quot;&gt;&lt;/label&gt;&lt;input id=&quot;fn4&quot; type=&quot;checkbox&quot; class=&quot;margin-toggle&quot;&gt;&lt;span class=&quot;note-left sidenote note&quot;&gt;&lt;span class=&quot;footnote-p&quot;&gt;Well, in theory you can. Just have each Coq document specifies the
Coq version it requires, and support this level of customization in your
build toolchain. But then, your blog takes forever to build from a cold
repository. &lt;/span&gt;
&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;That being said, most of the work had already been done. This website &lt;em&gt;was&lt;/em&gt; a
collection of literate programs, and I was pretty proud of the state of things.
I could deal with the annoyances&lt;label for=&quot;fn5&quot; class=&quot;sidenote-number margin-toggle&quot;&gt;&lt;/label&gt;&lt;input id=&quot;fn5&quot; type=&quot;checkbox&quot; class=&quot;margin-toggle&quot;&gt;&lt;span class=&quot;note-right sidenote note&quot;&gt;&lt;span class=&quot;footnote-p&quot;&gt;Like using Coqdoc syntax to write my articles, for instance. I could
write about how the Coqdoc syntax irks me for ages. &lt;/span&gt;
&lt;/span&gt;. But then, as I explained in the
previous section, I decided to move away from Emacs. The first time I tried to
start a new write-up, it hit me.&lt;/p&gt;
&lt;p&gt;I used to write most of my contents using Org mode. Org mode, also known as
&lt;em&gt;the&lt;/em&gt; Emacs markup language.&lt;/p&gt;
&lt;p&gt;I know of at least &lt;a href=&quot;https://github.com/nvim-orgmode/orgmode&quot; class=&quot;hover-mint&quot; marked=&quot;&quot;&gt;one “Org plugin” for
Neovim&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#github&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;, but instead of giving it a
try, I decided to use this opportunity to tackle my “maintenance problem” once
and for all. &lt;em&gt;I gave up on literate programming for this website.&lt;/em&gt; As a result,
this website is now generated from Markdown files only (using
&lt;a href=&quot;https://github.com/markdown-it/markdown-it&quot; class=&quot;hover-lavender&quot; marked=&quot;&quot;&gt;markdown-it&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#github&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt; with many plugins).
As a consequence, the generated HTML is way more “predictable.” This was enough
to motivate me at giving a try at &lt;a href=&quot;https://soupault.app/reference-manual/#metadata-extraction-and-rendering&quot; class=&quot;hover-peach&quot; marked=&quot;&quot;&gt;Soupault’s
indexes&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;,
which are way more powerful than I anticipated. Now, this website has&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Tags. Each write-up can be labeled with as many tags as I want, there is &lt;a href=&quot;/~lthms/tags&quot; class=&quot;hover-coral&quot; marked=&quot;&quot;&gt;a
page which lists all the tags used in the website&lt;/a&gt;, and each tag has
its own page (for instance, the &lt;a href=&quot;/~lthms/tags/coq.html&quot; class=&quot;hover-periwinkle&quot; marked=&quot;&quot;&gt;&lt;code class=&quot;hljs&quot;&gt;coq&lt;/code&gt; tag&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;A &lt;a href=&quot;/~lthms/posts/index.xml&quot; class=&quot;hover-peach&quot; marked=&quot;&quot;&gt;RSS feed&lt;/a&gt;. It was actually one of the main features I
really wanted to get with this revamp.&lt;/li&gt;
&lt;li&gt;Automatically generated list of articles in the &lt;a href=&quot;/~lthms/&quot; class=&quot;hover-mint&quot; marked=&quot;&quot;&gt;home page&lt;/a&gt;, for each
series (see the &lt;a href=&quot;/~lthms/series/Ltac.html&quot; class=&quot;hover-peach&quot; marked=&quot;&quot;&gt;Ltac series&lt;/a&gt; for instance). Before, I was
publishing “curated indexes,” or put in other words: I was writing these
indexes myself, by hand. And again, I’ve grown lazier.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It took me a week to go through this rework. Translating manually every write-up
was tedious, to say the least, as was implementing the Lua plugins for Soupault
since I have neither proficiency nor tooling to help me write Lua code. But I
am very glad for the final result.&lt;/p&gt;
&lt;p&gt;Also, I have invested in an Antidote license, so hopefully, this website will
have fewer typos and English butchering as of now. A clean text, delivered with
a nice and simple design, from a sane and maintainable &lt;a href=&quot;https://src.soap.coffee/soap.coffee/lthms.git/&quot; class=&quot;hover-lavender&quot; marked=&quot;&quot;&gt;Git
repository&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;
        
      </description>
    </item>
    
    
    
    <item>
      <title>What happened in September 2022?</title>
      <link>https://soap.coffee/~lthms/posts/September2022.html</link>
      <guid>https://soap.coffee/~lthms/posts/September2022.html</guid>
      <pubDate>September 18, 2022</pubDate>
      <description>
        
        &lt;h1&gt;What happened in September 2022?&lt;/h1&gt;&lt;div id=&quot;tags-list&quot;&gt;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#tag&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&amp;nbsp;&lt;a href=&quot;/~lthms/tags/spatial-shell.html&quot; class=&quot;tag hover-peach&quot; marked=&quot;&quot;&gt;spatial-shell&lt;/a&gt; &lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#tag&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&amp;nbsp;&lt;a href=&quot;/~lthms/tags/meta.html&quot; class=&quot;tag hover-lemon&quot; marked=&quot;&quot;&gt;meta&lt;/a&gt; &lt;/div&gt;
&lt;p&gt;It is September 18 today, and it has already been a month since I
decided to start these retrospectives. This means it is time to take a
step back and reflect of what happened these past few thirty days or
so&lt;label for=&quot;fn1&quot; class=&quot;sidenote-number margin-toggle&quot;&gt;&lt;/label&gt;&lt;input id=&quot;fn1&quot; type=&quot;checkbox&quot; class=&quot;margin-toggle&quot;&gt;&lt;span class=&quot;note-right sidenote note&quot;&gt;&lt;span class=&quot;footnote-p&quot;&gt;There is the shocking news that I have started to use syntax
highlighting again. But let’s not linger too much into it just yet. &lt;/span&gt;
&lt;/span&gt;.&lt;/p&gt;
&lt;h2&gt;Spatial Sway&lt;/h2&gt;
&lt;p&gt;A few days after publishing my August Retrospective, I have learned
the existence of &lt;a href=&quot;https://material-shell.com&quot; class=&quot;hover-mint&quot; marked=&quot;&quot;&gt;Material Shell&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;, an extension for
GNOME 3 that provides a very interesting user experience.&lt;/p&gt;
&lt;p&gt;I tried it for a few hours, but the thing kept crashing (it’s
probably on me, I did not even remember I had Gnome installed on my
machine, and I would not be surprised the culprit was my dusty setup
rather than Material Shell itself). The experience remained very
promising, though. Their “spatial model” especially felt like a very
good fit for me. Basically, the main idea is that you have a grid of
windows, with your workspaces acting as the rows. You can navigate
horizontally (from one workspace to another), or horizontally, and
you choose how many windows you want to see at once on your screen.&lt;/p&gt;
&lt;p&gt;And so for a few hours, I was a bit frustrated by the situation…
until I learned about how one can actually manage and extend Sway
(the Wayland compositor I use for several years now) thanks to its IPC
protocol.  I spend like three days experimenting, first in Rust, then in
OCaml&lt;label for=&quot;fn2&quot; class=&quot;sidenote-number margin-toggle&quot;&gt;&lt;/label&gt;&lt;input id=&quot;fn2&quot; type=&quot;checkbox&quot; class=&quot;margin-toggle&quot;&gt;&lt;span class=&quot;note-left sidenote note&quot;&gt;&lt;span class=&quot;footnote-p&quot;&gt;This was actually an interesting thought process. I am using OCaml at
&lt;code class=&quot;hljs language-bash&quot;&gt;&lt;span class=&quot;hljs-variable&quot;&gt;$WORK&lt;/span&gt;&lt;/code&gt; for about more than a year now.&lt;/span&gt;
&lt;span class=&quot;footnote-p&quot;&gt;I have curated a setup that works pretty well, and I am familiar with the
development tools. On the contrary, I had not written a line of Rust for at
least a year, my Emacs configuration for this language was broken, and I
had lost all my fluency in this language. Still, I was not expecting to
pick OCaml when I started this project. &lt;/span&gt;
&lt;/span&gt;, and by the end of the week, I had a first working prototype I
called &lt;a href=&quot;https://github.com/lthms/spatial-shell&quot; class=&quot;hover-peach&quot; marked=&quot;&quot;&gt;Spatial Sway&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#github&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;. It works pretty
well; well enough that I am using it daily for several weeks now. It feels
clunky at times, but it works well, and I have been able to write a
&lt;a href=&quot;https://github.com/Alexays/Waybar&quot; class=&quot;hover-mint&quot; marked=&quot;&quot;&gt;Waybar&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#github&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt; configuration heavily inspired on
Material Shell UI.&lt;/p&gt;
&lt;p&gt;Overall, I am pretty satisfied with this turnout. Writing a hobbyist
software project is always nice, but the ones you can integrate in
your daily workflow are the best one. The last time I did that was
&lt;a href=&quot;https://sr.ht/~lthms/keyrd&quot; class=&quot;hover-lavender&quot; marked=&quot;&quot;&gt;&lt;strong&gt;keyrd&lt;/strong&gt;&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;, my little keystrokes counting
daemon&lt;label for=&quot;fn3&quot; class=&quot;sidenote-number margin-toggle&quot;&gt;&lt;/label&gt;&lt;input id=&quot;fn3&quot; type=&quot;checkbox&quot; class=&quot;margin-toggle&quot;&gt;&lt;span class=&quot;note-right sidenote note&quot;&gt;&lt;span class=&quot;footnote-p&quot;&gt;19,970,965 since I started using it at the time of writing this
article &lt;/span&gt;
&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Anyway, lots remains to be said about Spatial Sway, but I might save
it for a bit later. I still have some key features to implement
(notably, moving a window to another workspace), then I will
probably try to advertise it a bit. I am under the impression this
project could be of interest for others, and I would love to see it
used by folks willing to give a Material Shell-like experience a
try, without having to deal with Gnome Shell. By the way,
considering Sway is a drop-in replacement for i3, and that it
implements the exact same IPC protocol, there is no reason why
Spatial Sway is actually Sway specific, and I will rename it Spatial
Shell at some point.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;/~lthms/img/spatial-sway-preview.png&quot;&gt;&lt;figcaption&gt;&lt;p&gt;Mandatory screenshot of Spatial Sway.&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;This Website&lt;/h2&gt;
&lt;p&gt;On a side note, I have started to refine the layout of this website
a bit. Similarly, I have written a new, curated home page where I
want to highlight the most recent things I have published on the
Internet.&lt;/p&gt;
&lt;p&gt;I have been experimenting with
&lt;a href=&quot;https://github.com/cpitclaudel/alectryon/&quot; class=&quot;hover-mint&quot; marked=&quot;&quot;&gt;Alectryon&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#github&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt; as a way to replace
&lt;code class=&quot;hljs&quot;&gt;coqdoc&lt;/code&gt;, to improve the readability of my Coq-related articles. Unfortunately,
it looks like this tool is missing &lt;a href=&quot;https://github.com/cpitclaudel/alectryon/issues/86&quot; class=&quot;hover-lemon&quot; marked=&quot;&quot;&gt;a key feature I
need&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#github&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;. I might try to get
my hand dirty and implement it myself if I find the time and the motivation
in the following weeks.&lt;/p&gt;
&lt;p&gt;Finally, reading about how &lt;a href=&quot;https://xeiaso.net/talks/how-my-website-works&quot; class=&quot;hover-mint&quot; marked=&quot;&quot;&gt;Xe Iaso’s talk about how she generates her
blog&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt; was very inspiring to me.
I can only suggest that you have a look.&lt;/p&gt;
&lt;p&gt;Though not to the same extent, I also think I have spent way too much effort in
my website. Most of my Coq-related articles are actual Coq program, expect the
articles about &lt;code class=&quot;hljs&quot;&gt;coqffi&lt;/code&gt; which are Org mode literate programs. Hell, this website
itself used to be a literate program of the sort, until I stopped using my
homegrown literate programming toolchain &lt;strong&gt;&lt;code class=&quot;hljs&quot;&gt;cleopatra&lt;/code&gt;&lt;/strong&gt; last month. At some
point, I have even spent a bit of time to ensure most of the pages of this
website were granted a 100/100 on websites like PageSpeed Insight&lt;label for=&quot;fn4&quot; class=&quot;sidenote-number margin-toggle&quot;&gt;&lt;/label&gt;&lt;input id=&quot;fn4&quot; type=&quot;checkbox&quot; class=&quot;margin-toggle&quot;&gt;&lt;span class=&quot;note-left sidenote note&quot;&gt;&lt;span class=&quot;footnote-p&quot;&gt;Good news, I’ve just checked, and it still is! &lt;/span&gt;
&lt;/span&gt;. I
had almost forgotten.&lt;/p&gt;
&lt;p&gt;A lot remains to be done, but watching this talk made me reflect on
the job done. And opened my eyes to a new perspective, too. We will
see what translates into reality.&lt;/p&gt;
        
      </description>
    </item>
    
    
    
    <item>
      <title>What happened in August 2022?</title>
      <link>https://soap.coffee/~lthms/posts/August2022.html</link>
      <guid>https://soap.coffee/~lthms/posts/August2022.html</guid>
      <pubDate>August 15, 2022</pubDate>
      <description>
        
        &lt;h1&gt;What happened in August 2022?&lt;/h1&gt;&lt;div id=&quot;tags-list&quot;&gt;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#tag&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&amp;nbsp;&lt;a href=&quot;/~lthms/tags/emacs.html&quot; class=&quot;tag hover-mint&quot; marked=&quot;&quot;&gt;emacs&lt;/a&gt; &lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#tag&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&amp;nbsp;&lt;a href=&quot;/~lthms/tags/meta.html&quot; class=&quot;tag hover-lemon&quot; marked=&quot;&quot;&gt;meta&lt;/a&gt; &lt;/div&gt;
&lt;p&gt;Without further ado, let’s take a look at what was achieved
for the last thirty days or so.&lt;/p&gt;
&lt;h2&gt;Emacs&lt;/h2&gt;
&lt;p&gt;I have started tweaking and improving my Emacs
&lt;a href=&quot;https://src.soap.coffee/dotfiles/emacs.d.git&quot; class=&quot;hover-peach&quot; marked=&quot;&quot;&gt;configuration&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;
again&lt;label for=&quot;fn1&quot; class=&quot;sidenote-number margin-toggle&quot;&gt;&lt;/label&gt;&lt;input id=&quot;fn1&quot; type=&quot;checkbox&quot; class=&quot;margin-toggle&quot;&gt;&lt;span class=&quot;note-right sidenote note&quot;&gt;&lt;span class=&quot;footnote-p&quot;&gt;After having used Emacs for seven years now, I am nowhere close
to consider my configuration as a done project. I really envy developers
who are using their editor with little to no customization. &lt;/span&gt;
&lt;/span&gt;.&lt;/p&gt;
&lt;h3&gt;Theme Selection Menu&lt;/h3&gt;
&lt;p&gt;The change I am the most excited about is that I have &lt;em&gt;finally&lt;/em&gt; reduced the
boilerplate in need to write to use a new theme. I am very indecisive when
it comes to theming. I like to have my choices, and I get tired of any
color scheme pretty quickly. As a consequence, I introduced a customizable
variable to let me select a theme dynamically, and have this choice persist
across Emacs session.&lt;/p&gt;
&lt;p&gt;I have a Hydra menu that allows me to select which theme I want to
use for the time being. It looks like this.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;/~lthms/img/select-theme.png&quot;&gt;&lt;figcaption&gt;&lt;p&gt;A Hydra menu for selecting a theme.&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;But adding new entries to this menu was very cumbersome, and mostly
boilerplate that I know a good macro could abstract away. And I can
finally report that I was right all along. I have my macros now,
and they allow me to have the Hydra menu above generated with these
simple lines of code.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-lisp&quot;&gt;(&lt;span class=&quot;hljs-name&quot;&gt;use-theme&lt;/span&gt; ancientless &lt;span class=&quot;hljs-string&quot;&gt;&quot;a&quot;&lt;/span&gt; &lt;span class=&quot;hljs-symbol&quot;&gt;:straight&lt;/span&gt; &lt;span class=&quot;hljs-literal&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;hljs-symbol&quot;&gt;:load-path&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&quot;~/.emacs.d/lisp&quot;&lt;/span&gt;)
(&lt;span class=&quot;hljs-name&quot;&gt;use-theme&lt;/span&gt; darkless &lt;span class=&quot;hljs-string&quot;&gt;&quot;d&quot;&lt;/span&gt; &lt;span class=&quot;hljs-symbol&quot;&gt;:straight&lt;/span&gt; &lt;span class=&quot;hljs-literal&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;hljs-symbol&quot;&gt;:load-path&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&quot;~/.emacs.d/lisp&quot;&lt;/span&gt;)
(&lt;span class=&quot;hljs-name&quot;&gt;use-theme&lt;/span&gt; brightless &lt;span class=&quot;hljs-string&quot;&gt;&quot;b&quot;&lt;/span&gt; &lt;span class=&quot;hljs-symbol&quot;&gt;:straight&lt;/span&gt; &lt;span class=&quot;hljs-literal&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;hljs-symbol&quot;&gt;:load-path&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&quot;~/.emacs.d/lisp&quot;&lt;/span&gt;)
(&lt;span class=&quot;hljs-name&quot;&gt;use-theme&lt;/span&gt; monotropic &lt;span class=&quot;hljs-string&quot;&gt;&quot;m&quot;&lt;/span&gt;)
(&lt;span class=&quot;hljs-name&quot;&gt;use-theme&lt;/span&gt; monokai &lt;span class=&quot;hljs-string&quot;&gt;&quot;M&quot;&lt;/span&gt;)
(&lt;span class=&quot;hljs-name&quot;&gt;use-theme&lt;/span&gt; nothing &lt;span class=&quot;hljs-string&quot;&gt;&quot;n&quot;&lt;/span&gt;)
(&lt;span class=&quot;hljs-name&quot;&gt;use-theme&lt;/span&gt; eink &lt;span class=&quot;hljs-string&quot;&gt;&quot;e&quot;&lt;/span&gt;)
(&lt;span class=&quot;hljs-name&quot;&gt;use-theme&lt;/span&gt; dracula &lt;span class=&quot;hljs-string&quot;&gt;&quot;D&quot;&lt;/span&gt;)
(&lt;span class=&quot;hljs-name&quot;&gt;use-theme&lt;/span&gt; chocolate &lt;span class=&quot;hljs-string&quot;&gt;&quot;c&quot;&lt;/span&gt;)
(&lt;span class=&quot;hljs-name&quot;&gt;use-themes-from&lt;/span&gt; tao-theme
                 &apos;((&lt;span class=&quot;hljs-string&quot;&gt;&quot;tl&quot;&lt;/span&gt; . tao-yang)
                   (&lt;span class=&quot;hljs-string&quot;&gt;&quot;td&quot;&lt;/span&gt; . tao-yin)))
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Eldoc and Flycheck Popups&lt;/h3&gt;
&lt;p&gt;I have been experimenting with several combinations of packages to
have Eldoc and Flycheck using pop-up-like mechanisms to report
things to me, instead of the echo area.&lt;/p&gt;
&lt;p&gt;The winning setup for now is the one that uses the &lt;a href=&quot;https://github.com/cpitclaudel/quick-peek&quot; class=&quot;hover-rose&quot; marked=&quot;&quot;&gt;&lt;code class=&quot;hljs&quot;&gt;quick-peek&lt;/code&gt;
package&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#github&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;. That is,
&lt;a href=&quot;https://github.com/flycheck/flycheck-inline&quot; class=&quot;hover-coral&quot; marked=&quot;&quot;&gt;&lt;code class=&quot;hljs&quot;&gt;flycheck-inline&lt;/code&gt;&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#github&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt; (customized to
use &lt;code class=&quot;hljs&quot;&gt;quick-peek&lt;/code&gt;, as suggested in their README), and
&lt;a href=&quot;https://melpa.org/#/eldoc-overlay&quot; class=&quot;hover-periwinkle&quot; marked=&quot;&quot;&gt;&lt;code class=&quot;hljs&quot;&gt;eldoc-overlay&lt;/code&gt;&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;. This works well enough,
so the pop-ups of eldoc are maybe a bit too distracting.&lt;/p&gt;
&lt;p&gt;#&lt;a href=&quot;/~lthms/img/flycheck-inline.png&quot; class=&quot;hover-coral&quot; marked=&quot;&quot;&gt;&lt;code class=&quot;hljs&quot;&gt;flycheck-inline&lt;/code&gt; in action with an OCaml compilation error.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;In my quest for pop-ups, I ran into several issues with the packages I tried
out. For instance, &lt;a href=&quot;https://github.com/casouri/eldoc-box&quot; class=&quot;hover-peach&quot; marked=&quot;&quot;&gt;&lt;code class=&quot;hljs&quot;&gt;eldoc-box&lt;/code&gt;&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#github&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt; was very
nice, but also very slow for some reason. It turns out there was an issue
about that slowness, wherein the culprit was identified. This allowed me to
&lt;a href=&quot;https://github.com/casouri/eldoc-box/pull/48&quot; class=&quot;hover-rose&quot; marked=&quot;&quot;&gt;submit a pull request that got merged rather
quickly&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#github&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Similarly, after a packages update, I discovered
&lt;a href=&quot;https://github.com/flycheck/flycheck-ocaml&quot; class=&quot;hover-mint&quot; marked=&quot;&quot;&gt;&lt;code class=&quot;hljs&quot;&gt;flycheck-ocaml&lt;/code&gt;&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#github&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt; was no longer
working, and &lt;a href=&quot;https://github.com/flycheck/flycheck-ocaml/pull/14&quot; class=&quot;hover-rose&quot; marked=&quot;&quot;&gt;submit a patch to fix the
issue&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#github&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;This Website&lt;/h2&gt;
&lt;p&gt;I have not been investing a lot of time in this website for the past
six years or so. This month, things change a bit on that side too.&lt;/p&gt;
&lt;h3&gt;New Contents&lt;/h3&gt;
&lt;p&gt;First, I have published a (short) article on &lt;a href=&quot;/~lthms/posts/RankNTypesInOCaml.html&quot; class=&quot;hover-peach&quot; marked=&quot;&quot;&gt;higher-order
polymorphism in OCaml&lt;/a&gt;. The goal was for me to
log somewhere the solution for an odd problem I was confronted to at
&lt;code class=&quot;hljs language-bash&quot;&gt;&lt;span class=&quot;hljs-variable&quot;&gt;$WORK&lt;/span&gt;&lt;/code&gt;, but the resulting article was not doing a great job as
conveying this. In particular, two comments on Reddit motivated me to rework
it, and I am glad I did. I hope you enjoy the retake.&lt;/p&gt;
&lt;p&gt;Once this was out of the way, I decided that generating this website was taking
way too much time for no good enough reason. The culprit was &lt;strong&gt;&lt;code class=&quot;hljs&quot;&gt;cleopatra&lt;/code&gt;&lt;/strong&gt;, a
toolchain I had developed in 2020 to integrate the build process of this
website as additional contents that I thought might interest people. The sad
things were: &lt;strong&gt;&lt;code class=&quot;hljs&quot;&gt;cleopatra&lt;/code&gt;&lt;/strong&gt; was adding a significant overhead, and I never take
the time to actually document them properly.&lt;/p&gt;
&lt;h3&gt;Under the Hood&lt;/h3&gt;
&lt;p&gt;Overall, the cost of using &lt;strong&gt;&lt;code class=&quot;hljs&quot;&gt;cleopatra&lt;/code&gt;&lt;/strong&gt; was not worth the burden, and so I
got rid of it. Fortunately, it was not very difficult, since the job of
&lt;strong&gt;&lt;code class=&quot;hljs&quot;&gt;cleopatra&lt;/code&gt;&lt;/strong&gt; was to extracting the generation processes from org files; I
just add to implement a small &lt;code class=&quot;hljs&quot;&gt;makefile&lt;/code&gt; to make use of these files, without
having to rely on &lt;strong&gt;&lt;code class=&quot;hljs&quot;&gt;cleopatra&lt;/code&gt;&lt;/strong&gt; anymore.&lt;/p&gt;
&lt;p&gt;This was something I was pondering to do for a long time, and as
often in these circumstances, this gave me the extra motivation I
needed to tackle other ideas I had in mind for this website. This
is why now, rather than starting one Emacs process per Org file I
have to process, my build toolchain starts one Emacs server, and
later uses &lt;code class=&quot;hljs&quot;&gt;emacsclient&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Now, most of the build time is spent by &lt;a href=&quot;https://soupault.app&quot; class=&quot;hover-periwinkle&quot; marked=&quot;&quot;&gt;soupault&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;. I guess
I will have to spend some time on the Lua plugins I have developed for it at
some point.&lt;/p&gt;
&lt;h2&gt;A New Mailing List&lt;/h2&gt;
&lt;p&gt;Finally, I have created &lt;a href=&quot;https://lists.sr.ht/~lthms/public-inbox&quot; class=&quot;hover-rose&quot; marked=&quot;&quot;&gt;a public
mailing&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt; list that is available if you
want to start a discussion on one of my articles. Don’t hesitate to use it, or
to register to it!&lt;/p&gt;
        
      </description>
    </item>
    
    
    
    <item>
      <title>A Literate Toolchain To Build This Website</title>
      <link>https://soap.coffee/~lthms/posts/CleopatraV1.html</link>
      <guid>https://soap.coffee/~lthms/posts/CleopatraV1.html</guid>
      <pubDate>February 4, 2020</pubDate>
      <description>
        
        &lt;h1&gt;A Literate Toolchain To Build This Website&lt;/h1&gt;&lt;div id=&quot;tags-list&quot;&gt;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#tag&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&amp;nbsp;&lt;a href=&quot;/~lthms/tags/meta.html&quot; class=&quot;tag hover-sky&quot; marked=&quot;&quot;&gt;meta&lt;/a&gt; &lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#tag&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&amp;nbsp;&lt;a href=&quot;/~lthms/tags/literate-programming.html&quot; class=&quot;tag hover-lemon&quot; marked=&quot;&quot;&gt;literate-programming&lt;/a&gt; &lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#tag&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&amp;nbsp;&lt;a href=&quot;/~lthms/tags/emacs.html&quot; class=&quot;tag hover-sky&quot; marked=&quot;&quot;&gt;emacs&lt;/a&gt; &lt;/div&gt;
&lt;div class=&quot;markdown-alert markdown-alert-important&quot;&gt;&lt;p class=&quot;markdown-alert-title&quot;&gt;&lt;svg class=&quot;octicon octicon-report mr-2&quot; viewbox=&quot;0 0 16 16&quot; version=&quot;1.1&quot; width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;M0 1.75C0 .784.784 0 1.75 0h12.5C15.216 0 16 .784 16 1.75v9.5A1.75 1.75 0 0 1 14.25 13H8.06l-2.573 2.573A1.458 1.458 0 0 1 3 14.543V13H1.75A1.75 1.75 0 0 1 0 11.25Zm1.75-.25a.25.25 0 0 0-.25.25v9.5c0 .138.112.25.25.25h2a.75.75 0 0 1 .75.75v2.19l2.72-2.72a.749.749 0 0 1 .53-.22h6.5a.25.25 0 0 0 .25-.25v-9.5a.25.25 0 0 0-.25-.25Zm7 2.25v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 9a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;Important&lt;/p&gt;&lt;p&gt;What you are reading is actually the rendered version of a Markdown document
that was manually “translated” from the Org original document, named
&lt;code class=&quot;hljs&quot;&gt;Bootstrap.org&lt;/code&gt;. &lt;code class=&quot;hljs&quot;&gt;Bootstrap.org&lt;/code&gt; was probably complete gibberish for anyone
who isn’t me. This version was actually heavily reworked to try to fix that.&lt;/p&gt;
&lt;p&gt;In any case, the tool-suite described here has not been used to generate this
website for a while now.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;A literate program is a particular type of software program where code is not
directly written in source files, but rather in a text document as code
snippets. In essence, literate programming allows for writing in the same place
both the software program and its technical documentation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code class=&quot;hljs&quot;&gt;cleopatra&lt;/code&gt;&lt;/strong&gt; is a “literate toolchain” I have implemented to build this
website, and you are currently reading it&lt;label for=&quot;fn1&quot; class=&quot;sidenote-number margin-toggle&quot;&gt;&lt;/label&gt;&lt;input id=&quot;fn1&quot; type=&quot;checkbox&quot; class=&quot;margin-toggle&quot;&gt;&lt;span class=&quot;note-right sidenote note&quot;&gt;&lt;span class=&quot;footnote-p&quot;&gt;This sentence was true when this article was published, but things
have changed since then. &lt;/span&gt;
&lt;/span&gt;. That is, &lt;strong&gt;&lt;code class=&quot;hljs&quot;&gt;cleopatra&lt;/code&gt;&lt;/strong&gt; is
both the build system and an article of this website! To achieve this,
&lt;strong&gt;&lt;code class=&quot;hljs&quot;&gt;cleopatra&lt;/code&gt;&lt;/strong&gt; has been written as a collection of org files which can be
either “tangled” using &lt;a href=&quot;https://orgmode.org/worg/org-contrib/babel/&quot; class=&quot;hover-periwinkle&quot; marked=&quot;&quot;&gt;Babel&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt; or
“exported” as a HTML document. Tangling here refers to extract marked code
blocks into files.&lt;/p&gt;
&lt;p&gt;The page you are currently reading is &lt;strong&gt;&lt;code class=&quot;hljs&quot;&gt;cleopatra&lt;/code&gt;&lt;/strong&gt; entry point. Its
primary purpose is to define two Makefiles —&lt;code class=&quot;hljs&quot;&gt;makefile&lt;/code&gt; and &lt;code class=&quot;hljs&quot;&gt;bootstrap.mk&lt;/code&gt;—
and the necessary emacs-lisp script to tangle this document.&lt;/p&gt;
&lt;p&gt;On the one hand, &lt;code class=&quot;hljs&quot;&gt;makefile&lt;/code&gt; is the main entrypoint of &lt;strong&gt;&lt;code class=&quot;hljs&quot;&gt;cleopatra&lt;/code&gt;&lt;/strong&gt;. It
serves two purposes:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;It initiates a few global variables, and&lt;/li&gt;
&lt;li&gt;It provides a rule to tangle this document, that is to update itself and
&lt;code class=&quot;hljs&quot;&gt;bootstrap.mk&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;On the other hand, &lt;code class=&quot;hljs&quot;&gt;bootstrap.mk&lt;/code&gt; is used to declare the various “generation
processes” used to generate this website.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;hljs&quot;&gt;makefile&lt;/code&gt; and the emacs-lisp scripts are versioned, because they are necessary
to bootstrap &lt;strong&gt;&lt;code class=&quot;hljs&quot;&gt;cleopatra&lt;/code&gt;&lt;/strong&gt;; but since they are also defined in this document,
it means &lt;strong&gt;&lt;code class=&quot;hljs&quot;&gt;cleopatra&lt;/code&gt;&lt;/strong&gt; can update itself, in some sense. This is to be kept in mind
when modifying this document to hastily.&lt;/p&gt;
&lt;h2&gt;Global Constants and Variables&lt;/h2&gt;
&lt;p&gt;First, &lt;code class=&quot;hljs&quot;&gt;makefile&lt;/code&gt; defines several global “constants”&lt;label for=&quot;fn2&quot; class=&quot;sidenote-number margin-toggle&quot;&gt;&lt;/label&gt;&lt;input id=&quot;fn2&quot; type=&quot;checkbox&quot; class=&quot;margin-toggle&quot;&gt;&lt;span class=&quot;note-left sidenote note&quot;&gt;&lt;span class=&quot;footnote-p&quot;&gt;As far as I know, &lt;code class=&quot;hljs&quot;&gt;make&lt;/code&gt; does not support true &lt;em&gt;constant&lt;/em&gt; values,
It is assumed generation processes will not modify them. &lt;/span&gt;
&lt;/span&gt;. In a nutshell,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;hljs language-bash&quot;&gt;&lt;span class=&quot;hljs-variable&quot;&gt;$ROOT&lt;/span&gt;&lt;/code&gt;  tells Emacs where the root of your website sources is, so
that tangled output filenames can be given relative to it rather than the org
files.  So for instance, the &lt;code class=&quot;hljs&quot;&gt;block_src&lt;/code&gt; tangle parameter for &lt;code class=&quot;hljs&quot;&gt;Makefile&lt;/code&gt;
looks like &lt;code class=&quot;hljs language-lisp&quot;&gt;:tangle Makefile&lt;/code&gt;, instead of &lt;code class=&quot;hljs language-lisp&quot;&gt;:tangle ../../Makefile&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;hljs language-bash&quot;&gt;&lt;span class=&quot;hljs-variable&quot;&gt;$CLEODIR&lt;/span&gt;&lt;/code&gt; tells &lt;strong&gt;&lt;code class=&quot;hljs&quot;&gt;cleopatra&lt;/code&gt;&lt;/strong&gt; where its sources live. If you place
it inside the &lt;code class=&quot;hljs&quot;&gt;site/&lt;/code&gt; directory (as it is intended), and you enable the use
of &lt;code class=&quot;hljs&quot;&gt;org&lt;/code&gt; files to author your contents, then &lt;strong&gt;&lt;code class=&quot;hljs&quot;&gt;cleopatra&lt;/code&gt;&lt;/strong&gt; documents will
be part of your website. If you don’t want that, just move the directory
outside the &lt;code class=&quot;hljs&quot;&gt;site/&lt;/code&gt; directory, and update the &lt;code class=&quot;hljs language-bash&quot;&gt;&lt;span class=&quot;hljs-variable&quot;&gt;$CLEODIR&lt;/span&gt;&lt;/code&gt; variable
accordingly.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For this website, these constants are defined as follows&lt;label for=&quot;fn3&quot; class=&quot;sidenote-number margin-toggle&quot;&gt;&lt;/label&gt;&lt;input id=&quot;fn3&quot; type=&quot;checkbox&quot; class=&quot;margin-toggle&quot;&gt;&lt;span class=&quot;note-right sidenote note&quot;&gt;&lt;span class=&quot;footnote-p&quot;&gt;I will use a comment in the first line to recall to which file a
given block code is expected to be tangled. &lt;/span&gt;
&lt;/span&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-makefile&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;# makefile:&lt;/span&gt;
ROOT := &lt;span class=&quot;hljs-variable&quot;&gt;$(&lt;span class=&quot;hljs-built_in&quot;&gt;shell&lt;/span&gt; pwd)&lt;/span&gt;
CLEODIR := site/cleopatra
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We then introduce two variables to list the output of the generation processes,
with two purposes in mind: keeping the &lt;code class=&quot;hljs&quot;&gt;.gitignore&lt;/code&gt; up-to-date automatically,
and providing rules to remove them.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;hljs language-bash&quot;&gt;&lt;span class=&quot;hljs-variable&quot;&gt;$ARTIFACTS&lt;/span&gt;&lt;/code&gt; lists the short-term artifacts which can be removed
frequently without too much hassle. They will be removed by &lt;code class=&quot;hljs&quot;&gt;make clean&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;hljs language-bash&quot;&gt;&lt;span class=&quot;hljs-variable&quot;&gt;$CONFIGURE&lt;/span&gt;&lt;/code&gt; lists the long-term artifacts whose generation can be
time consuming. They will only be removed by&lt;code class=&quot;hljs&quot;&gt;~make cleanall&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-makefile&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;# makefile:&lt;/span&gt;
ARTIFACTS := build.log
CONFIGURE :=

clean :
	@rm -rf ${ARTIFACTS}

cleanall : clean
	@rm -rf ${CONFIGURE}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Generation processes can declare new build outputs using the &lt;code class=&quot;hljs&quot;&gt;+=&lt;/code&gt; assignement
operators. Using another operator will likely provoke an undesirable result.&lt;/p&gt;
&lt;h2&gt;Tangling Org Documents&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;&lt;code class=&quot;hljs&quot;&gt;cleopatra&lt;/code&gt;&lt;/strong&gt; is a literate program implemented with Org mode, an Emacs major
editing mode. We provide the necessary bits to easily tangle Org documents.&lt;/p&gt;
&lt;p&gt;The configuration of Babel is done using an emacs lisp script called
&lt;code class=&quot;hljs&quot;&gt;tangle-org.el&lt;/code&gt; whose status is similar to &lt;code class=&quot;hljs&quot;&gt;Makefile&lt;/code&gt;. It is part of the
bootstrap process, and therefore lives “outside” of &lt;strong&gt;&lt;code class=&quot;hljs&quot;&gt;cleopatra&lt;/code&gt;&lt;/strong&gt; (it is not
deleted with &lt;code class=&quot;hljs&quot;&gt;make clean&lt;/code&gt; for instance).  However, it is overwritten when this
file is tangled. If you try to modify it and find that &lt;strong&gt;&lt;code class=&quot;hljs&quot;&gt;cleopatra&lt;/code&gt;&lt;/strong&gt; does not
work properly, you should restore it.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-lisp&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;;;; tangle-org.el:&lt;/span&gt;
(&lt;span class=&quot;hljs-name&quot;&gt;require&lt;/span&gt; &apos;org)
(&lt;span class=&quot;hljs-name&quot;&gt;cd&lt;/span&gt; (&lt;span class=&quot;hljs-name&quot;&gt;getenv&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&quot;ROOT&quot;&lt;/span&gt;))
(&lt;span class=&quot;hljs-name&quot;&gt;setq&lt;/span&gt; org-confirm-babel-evaluate &lt;span class=&quot;hljs-literal&quot;&gt;nil&lt;/span&gt;)
(&lt;span class=&quot;hljs-name&quot;&gt;setq&lt;/span&gt; org-src-preserve-indentation &lt;span class=&quot;hljs-literal&quot;&gt;t&lt;/span&gt;)
(&lt;span class=&quot;hljs-name&quot;&gt;add-to-list&lt;/span&gt; &apos;org-babel-default-header-args
             &apos;(&lt;span class=&quot;hljs-symbol&quot;&gt;:mkdirp&lt;/span&gt; . &lt;span class=&quot;hljs-string&quot;&gt;&quot;yes&quot;&lt;/span&gt;))
(&lt;span class=&quot;hljs-name&quot;&gt;org-babel-do-load-languages&lt;/span&gt;
 &apos;org-babel-load-languages
 &apos;((shell . &lt;span class=&quot;hljs-literal&quot;&gt;t&lt;/span&gt;)))
(&lt;span class=&quot;hljs-name&quot;&gt;org-babel-tangle&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We define variables that ensure that the &lt;code class=&quot;hljs language-bash&quot;&gt;&lt;span class=&quot;hljs-variable&quot;&gt;$ROOT&lt;/span&gt;&lt;/code&gt; environment variable is
set and &lt;code class=&quot;hljs&quot;&gt;tangle-org.el&lt;/code&gt; is loaded when using Emacs.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-makefile&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;# makefile:&lt;/span&gt;
EMACSBIN := emacs
EMACS := ROOT=&lt;span class=&quot;hljs-string&quot;&gt;&quot;${ROOT}&quot;&lt;/span&gt; ${EMACSBIN}
TANGLE := --batch \
          --load=&lt;span class=&quot;hljs-string&quot;&gt;&quot;${ROOT}/scripts/tangle-org.el&quot;&lt;/span&gt; \
          2&amp;gt;&amp;gt; build.log
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally, we introduce a &lt;a href=&quot;https://www.gnu.org/software/make/manual/html_node/Canned-Recipes.html#Canned-Recipes&quot; class=&quot;hover-peach&quot; marked=&quot;&quot;&gt;canned
recipe&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;
to seamlessly tangle a given file&lt;label for=&quot;fn4&quot; class=&quot;sidenote-number margin-toggle&quot;&gt;&lt;/label&gt;&lt;input id=&quot;fn4&quot; type=&quot;checkbox&quot; class=&quot;margin-toggle&quot;&gt;&lt;span class=&quot;note-left sidenote note&quot;&gt;&lt;span class=&quot;footnote-p&quot;&gt;It was the first time I had used canned recipes, and I don’t think I
had the opportunity to re-use it ever again. &lt;/span&gt;
&lt;/span&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-makefile&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;# makefile:&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;define&lt;/span&gt; emacs-tangle =
echo &lt;span class=&quot;hljs-string&quot;&gt;&quot;  tangle  &lt;span class=&quot;hljs-variable&quot;&gt;$&amp;lt;&lt;/span&gt;&quot;&lt;/span&gt;
${EMACS} &lt;span class=&quot;hljs-variable&quot;&gt;$&amp;lt;&lt;/span&gt; ${TANGLE}
&lt;span class=&quot;hljs-keyword&quot;&gt;endef&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Updating &lt;code class=&quot;hljs&quot;&gt;.gitignore&lt;/code&gt; Automatically&lt;/h2&gt;
&lt;p&gt;Assuming each generation process correctly defines its &lt;code class=&quot;hljs language-bash&quot;&gt;&lt;span class=&quot;hljs-variable&quot;&gt;$ARTIFACTS&lt;/span&gt;&lt;/code&gt;
and &lt;code class=&quot;hljs language-bash&quot;&gt;&lt;span class=&quot;hljs-variable&quot;&gt;$CONFIGURE&lt;/span&gt;&lt;/code&gt; variables, we have all the information we need to
update &lt;code class=&quot;hljs&quot;&gt;.gitignore&lt;/code&gt; automatically.&lt;/p&gt;
&lt;p&gt;This is done by adding markers in &lt;code class=&quot;hljs&quot;&gt;.gitignore&lt;/code&gt; to define a region under the
control of &lt;strong&gt;&lt;code class=&quot;hljs&quot;&gt;cleopatra&lt;/code&gt;&lt;/strong&gt;, and writing a script to update said region after
each build.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-bash&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;# update-gitignore.sh:&lt;/span&gt;
BEGIN_MARKER=&lt;span class=&quot;hljs-string&quot;&gt;&quot;# begin generated files&quot;&lt;/span&gt;
END_MARKER=&lt;span class=&quot;hljs-string&quot;&gt;&quot;# begin generated files&quot;&lt;/span&gt;

&lt;span class=&quot;hljs-comment&quot;&gt;# remove the previous list of generated files to ignore&lt;/span&gt;
sed -i -e &lt;span class=&quot;hljs-string&quot;&gt;&quot;/&lt;span class=&quot;hljs-variable&quot;&gt;${BEGIN_MARKER}&lt;/span&gt;/,/&lt;span class=&quot;hljs-variable&quot;&gt;${END_MARKER}&lt;/span&gt;/d&quot;&lt;/span&gt; .gitignore
&lt;span class=&quot;hljs-comment&quot;&gt;# remove trailing empty lines&lt;/span&gt;
sed -i -e :a -e &lt;span class=&quot;hljs-string&quot;&gt;&apos;/^\n*$/{$d;N;};/\n$/ba&apos;&lt;/span&gt; .gitignore

&lt;span class=&quot;hljs-comment&quot;&gt;# output the list of files to ignore&lt;/span&gt;
&lt;span class=&quot;hljs-built_in&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&quot;&quot;&lt;/span&gt; &amp;gt;&amp;gt; .gitignore
&lt;span class=&quot;hljs-built_in&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;hljs-variable&quot;&gt;${BEGIN_MARKER}&lt;/span&gt; &amp;gt;&amp;gt; .gitignore
&lt;span class=&quot;hljs-keyword&quot;&gt;for&lt;/span&gt; f &lt;span class=&quot;hljs-keyword&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;hljs-variable&quot;&gt;$@&lt;/span&gt;; &lt;span class=&quot;hljs-keyword&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;hljs-built_in&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&quot;&lt;span class=&quot;hljs-variable&quot;&gt;${f}&lt;/span&gt;&quot;&lt;/span&gt; &amp;gt;&amp;gt; .gitignore
&lt;span class=&quot;hljs-keyword&quot;&gt;done&lt;/span&gt;
&lt;span class=&quot;hljs-built_in&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;hljs-variable&quot;&gt;${END_MARKER}&lt;/span&gt; &amp;gt;&amp;gt; .gitignore
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code class=&quot;hljs&quot;&gt;ignore&lt;/code&gt; rule of &lt;code class=&quot;hljs&quot;&gt;makefile&lt;/code&gt; is defined as follows.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-makefile&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;# makefile:&lt;/span&gt;
ignore :
	@echo &lt;span class=&quot;hljs-string&quot;&gt;&quot;  update  gitignore&quot;&lt;/span&gt;
	@scripts/update-gitignore.sh \
	   ${ARTIFACTS} \
	   ${CONFIGURE}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Bootstrapping&lt;/h2&gt;
&lt;p&gt;The core purpose of &lt;code class=&quot;hljs&quot;&gt;makefile&lt;/code&gt; remains to bootstrap the chain of generation
processes. This chain is divided into three stages: &lt;code class=&quot;hljs&quot;&gt;prebuild&lt;/code&gt;, &lt;code class=&quot;hljs&quot;&gt;build&lt;/code&gt;, and
&lt;code class=&quot;hljs&quot;&gt;postbuild&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This translates as follows in &lt;code class=&quot;hljs&quot;&gt;makefile&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-makefile&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;# makefile:&lt;/span&gt;
default : postbuild ignore

init :
	@rm -f build.log

prebuild : init

build : prebuild

postbuild : build

.PHONY : init prebuild build postbuild ignore
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A &lt;em&gt;generation process&lt;/em&gt; in &lt;strong&gt;&lt;code class=&quot;hljs&quot;&gt;cleopatra&lt;/code&gt;&lt;/strong&gt; is a Makefile which provides rules for
these three stages, along with the utilities used by these rules. More
precisely, a generation process &lt;code class=&quot;hljs&quot;&gt;proc&lt;/code&gt; is defined in &lt;code class=&quot;hljs&quot;&gt;proc.mk&lt;/code&gt;. The rules of
&lt;code class=&quot;hljs&quot;&gt;proc.mk&lt;/code&gt; for each stage are expected to be prefixed by &lt;code class=&quot;hljs&quot;&gt;proc-&lt;/code&gt;, &lt;em&gt;e.g.&lt;/em&gt;,
&lt;code class=&quot;hljs&quot;&gt;proc-prebuild&lt;/code&gt; for the &lt;code class=&quot;hljs&quot;&gt;prebuild&lt;/code&gt; stage.&lt;/p&gt;
&lt;p&gt;Eventually, the following dependencies are expected between within the chain of
generation processes for every generation process.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-makefile&quot;&gt;prebuild : proc-prebuild
build : proc-build
postbuild : proc-postbuild

proc-build : proc-prebuild
proc-postbuild : proc build
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&lt;code class=&quot;hljs&quot;&gt;cleopatra&lt;/code&gt;&lt;/strong&gt; is a literate toolchain whose main purpose is to allow me to
turn the scripts I wrote to generate my website into blogposts of said website.
As such, it allows me to implement the generation processes using Org mode,
which means that before being able to start generating HTML files,
&lt;strong&gt;&lt;code class=&quot;hljs&quot;&gt;cleopatra&lt;/code&gt;&lt;/strong&gt; has to tangle the generation processes.&lt;/p&gt;
&lt;p&gt;To achieve this, &lt;strong&gt;&lt;code class=&quot;hljs&quot;&gt;cleopatra&lt;/code&gt;&lt;/strong&gt; relies on a particular behavior of &lt;code class=&quot;hljs&quot;&gt;make&lt;/code&gt;
regarding the &lt;code class=&quot;hljs&quot;&gt;include&lt;/code&gt; directive. If there exists a rule to generate a
Makefile used as an operand of &lt;code class=&quot;hljs&quot;&gt;include&lt;/code&gt;, &lt;code class=&quot;hljs&quot;&gt;make&lt;/code&gt; will use this rule to update
(if necessary) said Makefile before actually including it.&lt;/p&gt;
&lt;p&gt;Therefore, the rules of the following form achieve our ambition of extensibility.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-makefile&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;include&lt;/span&gt; ${PROC}.mk

prebuild : ${PROC}-prebuild
build : ${PROC}-build
postbuild : ${PROC}-postbuild

${PROC}-prebuild : ${PROC}.mk ${AUX}
${PROC}-build : ${PROC}-prebuild
${PROC}-postbuild : ${PROC}-build

${PROC}.mk ${AUX} &amp;amp;:\
   ${CLEODIR}/${IN}
	@$(emacs-tangle)

CONFIGURE += ${PROC}.mk ${AUX}

.PHONY : ${PROC}-prebuild \
         ${PROC}-build \
         ${PROC}-postbuild
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;where&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;hljs language-bash&quot;&gt;&lt;span class=&quot;hljs-variable&quot;&gt;$IN&lt;/span&gt;&lt;/code&gt; is the Org document which contains the generation process code&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;hljs language-bash&quot;&gt;&lt;span class=&quot;hljs-variable&quot;&gt;$PROC&lt;/span&gt;&lt;/code&gt; is the name of the generation process&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;hljs language-bash&quot;&gt;&lt;span class=&quot;hljs-variable&quot;&gt;$AUX&lt;/span&gt;&lt;/code&gt; lists the utilities of the generation process tangled from
&lt;code class=&quot;hljs language-bash&quot;&gt;&lt;span class=&quot;hljs-variable&quot;&gt;$IN&lt;/span&gt;&lt;/code&gt; with &lt;code class=&quot;hljs language-bash&quot;&gt;&lt;span class=&quot;hljs-variable&quot;&gt;$PROC&lt;/span&gt;.mk&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We use &lt;code class=&quot;hljs&quot;&gt;&amp;amp;:&lt;/code&gt; is used in place of &lt;code class=&quot;hljs&quot;&gt;:&lt;/code&gt; to separate the target from its
dependencies in the “tangle rule.”&lt;label for=&quot;fn5&quot; class=&quot;sidenote-number margin-toggle&quot;&gt;&lt;/label&gt;&lt;input id=&quot;fn5&quot; type=&quot;checkbox&quot; class=&quot;margin-toggle&quot;&gt;&lt;span class=&quot;note-right sidenote note&quot;&gt;&lt;span class=&quot;footnote-p&quot;&gt;Yet another obscure Makefile trick I have never encountered again. &lt;/span&gt;
&lt;/span&gt; This tells &lt;code class=&quot;hljs&quot;&gt;make&lt;/code&gt; that the recipe of this
rule generates all these files.&lt;/p&gt;
&lt;p&gt;Rather than writing these rules manually for each generation process we want to
define, we rely on to &lt;a href=&quot;https://orgmode.org/worg/org-tutorials/org-latex-export.html&quot; class=&quot;hover-sky&quot; marked=&quot;&quot;&gt;noweb of
Babel&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;. We call
&lt;code class=&quot;hljs&quot;&gt;extends&lt;/code&gt; the primitive to generate new generation processes.&lt;/p&gt;
&lt;p&gt;We derive the rule to tangle &lt;code class=&quot;hljs&quot;&gt;bootstrap.mk&lt;/code&gt; using &lt;code class=&quot;hljs&quot;&gt;extends&lt;/code&gt;, using the following Org mode syntax.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs&quot;&gt;#+BEGIN_SRC makefile :noweb yes
# makefile:
&amp;lt;&amp;lt;extends(IN=&quot;Bootstrap.org&quot;, PROC=&quot;bootstrap&quot;, AUX=&quot;scripts/update-gitignore.sh&quot;)&amp;gt;&amp;gt;
#+END_SRC
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;which gives the following result.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-makefile&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;include&lt;/span&gt; bootstrap.mk

prebuild : bootstrap-prebuild
build : bootstrap-build
postbuild : bootstrap-postbuild

bootstrap-prebuild : bootstrap.mk scripts/update-gitignore.sh
bootstrap-build : bootstrap-prebuild
bootstrap-postbuild : bootstrap-build

bootstrap.mk scripts/update-gitignore.sh &amp;amp;:\
   ${CLEODIR}/Bootstrap.org
	@$(emacs-tangle)

CONFIGURE += bootstrap.mk scripts/update-gitignore.sh

.PHONY : bootstrap-prebuild \
         bootstrap-build \
         bootstrap-postbuild
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These are the last lines of &lt;code class=&quot;hljs&quot;&gt;makefile&lt;/code&gt;. The rest of the generation processes will be
declared in &lt;code class=&quot;hljs&quot;&gt;bootstrap.mk&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Generation Processes&lt;/h2&gt;
&lt;p&gt;In this section, we construct &lt;code class=&quot;hljs&quot;&gt;bootstrap.mk&lt;/code&gt; by enumerating the generation
processes that are currently used to generate the website you are reading.&lt;/p&gt;
&lt;p&gt;We recall that each generation process shall&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Define &lt;code class=&quot;hljs&quot;&gt;proc-prebuild&lt;/code&gt;, &lt;code class=&quot;hljs&quot;&gt;proc-build&lt;/code&gt;, and &lt;code class=&quot;hljs&quot;&gt;proc-postbuild&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Declare dependencies between stages of generation processes&lt;/li&gt;
&lt;li&gt;Declare build outputs (see &lt;code class=&quot;hljs&quot;&gt;ARTIFACTS&lt;/code&gt; and &lt;code class=&quot;hljs&quot;&gt;CONFIGURE&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Theming and Templating&lt;/h3&gt;
&lt;p&gt;The
&lt;a href=&quot;https://src.soap.coffee/soap.coffee/lthms.git/tree/site/cleopatra/Theme.org?id=9329e9883a52eb95c0803a46560c396d149ef2c6&quot; class=&quot;hover-peach&quot; marked=&quot;&quot;&gt;&lt;code class=&quot;hljs&quot;&gt;theme&lt;/code&gt;&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;
generation process controls the general appearance of the website. More
precisely, it introduces the main template used by &lt;code class=&quot;hljs&quot;&gt;soupault&lt;/code&gt;
(&lt;code class=&quot;hljs&quot;&gt;main/templates.html&lt;/code&gt;), and the main SASS sheet used by this template.&lt;/p&gt;
&lt;p&gt;If a generation process produces a set of styles within a specific SASS files,
the current approach is&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;To make this file a dependency of &lt;code class=&quot;hljs&quot;&gt;theme-build&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;To modify &lt;code class=&quot;hljs&quot;&gt;style/main.sass&lt;/code&gt; in &lt;code class=&quot;hljs&quot;&gt;theme&lt;/code&gt;
to import this file&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Eventually, the second step will be automated, but, in the meantime,
this customization is mandatory.&lt;/p&gt;
&lt;h3&gt;Configuring Soupault&lt;/h3&gt;
&lt;p&gt;The
&lt;a href=&quot;https://src.soap.coffee/soap.coffee/lthms.git/tree/site/cleopatra/Soupault.org?id=9329e9883a52eb95c0803a46560c396d149ef2c6&quot; class=&quot;hover-lemon&quot; marked=&quot;&quot;&gt;&lt;code class=&quot;hljs&quot;&gt;soupault&lt;/code&gt;&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;
generation configures and run &lt;code class=&quot;hljs&quot;&gt;soupault&lt;/code&gt;, in order to generate a static
website.&lt;/p&gt;
&lt;p&gt;If a generation process &lt;code class=&quot;hljs&quot;&gt;proc&lt;/code&gt; produces files that will eventually be
integrated to your website, its &lt;code class=&quot;hljs&quot;&gt;proc-build&lt;/code&gt; recipe needs to be executed
&lt;em&gt;before&lt;/em&gt; the &lt;code class=&quot;hljs&quot;&gt;soupault-build&lt;/code&gt; recipe. This can be enforced by making the
dependency explicit to &lt;code class=&quot;hljs&quot;&gt;make&lt;/code&gt;, &lt;em&gt;i.e.&lt;/em&gt;,&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-makefile&quot;&gt;soupault-build : proc-build
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Eventually, generation processes shall be allowed to produce specific &lt;code class=&quot;hljs&quot;&gt;soupault&lt;/code&gt;
widgets to be integrated into &lt;code class=&quot;hljs&quot;&gt;soupault.conf&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Authoring Contents&lt;/h3&gt;
&lt;p&gt;The fact that &lt;strong&gt;&lt;code class=&quot;hljs&quot;&gt;cleopatra&lt;/code&gt;&lt;/strong&gt; is a literate program which gradually generates
itself was not intended: it is a consequence of my desire to be able to easily
use whatever format I so desire for writing my contents, and Org documents in
particular.&lt;/p&gt;
&lt;p&gt;In the present website, contents can be written in the following format:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;HTML Files:&lt;/strong&gt; This requires no particular set-up, since HTML is the &lt;em&gt;lingua
franca&lt;/em&gt; of &lt;code class=&quot;hljs&quot;&gt;soupault&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Regular Coq files:&lt;/strong&gt; Coq is a system which allows writing machine-checked
proofs, and it comes with a source “prettifier” called &lt;code class=&quot;hljs&quot;&gt;coqdoc&lt;/code&gt;. &lt;a href=&quot;https://src.soap.coffee/soap.coffee/lthms.git/tree/site/cleopatra/Contents/Coq.org?id=9329e9883a52eb95c0803a46560c396d149ef2c6&quot; class=&quot;hover-rose&quot; marked=&quot;&quot;&gt;Learn more
about the generation process for Coq
files&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Org documents:&lt;/strong&gt; Emacs comes with a powerful editing mode called &lt;a href=&quot;https://orgmode.org/&quot; class=&quot;hover-peach&quot; marked=&quot;&quot;&gt;Org
mode&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;, and Org documents are really pleasant to work
with. &lt;a href=&quot;https://src.soap.coffee/soap.coffee/lthms.git/tree/site/cleopatra/Contents/Org.org?id=9329e9883a52eb95c0803a46560c396d149ef2c6&quot; class=&quot;hover-sky&quot; marked=&quot;&quot;&gt;Learn more about the generation process for Org
documents&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
        
      </description>
    </item>
    
    
  </channel>
</rss>
