<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Yet Another SysAdmin Website - Blog</title><link>/blog/</link><description>Recent content in Blog on Yet Another SysAdmin Website</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><atom:link href="/blog/index.xml" rel="self" type="application/rss+xml"/><item><title>A difficult 23.11 nixos upgrade story</title><link>/blog/2024/02/06/a-difficult-23.11-nixos-upgrade-story/</link><pubDate>2024-02-06</pubDate><guid>/blog/2024/02/06/a-difficult-23.11-nixos-upgrade-story/</guid><description>&lt;h2 id="introduction">Introduction&lt;/h2>
&lt;p>Back in December I upgraded my nixos servers from the 23.05 release to 23.11. I had to debug a strange issue where my servers were no longer reachable after rebooting the new version.&lt;/p>
&lt;h2 id="the-problem">The problem&lt;/h2>
&lt;p>I am using LUKS encryption for the root filesystem, and am used to the comfort of unlocking the partition thanks to an SSH server embedded in the initrd. This setup has the security flaw that the initrd could be replaced by a malicious party, but this is not something I am concerned about for personal stuff so please ignore it.&lt;/p>
&lt;p>The following configuration made it work on nixos 23.05:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-nix" data-lang="nix">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span> &lt;span class="n">config&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="n">pkgs&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="o">...&lt;/span> &lt;span class="p">}:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">boot&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">initrd&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">network&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">enable&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">true&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">ssh&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">enable&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">true&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">port&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">22&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">authorizedKeys&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span> &lt;span class="s2">&amp;#34;ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AABCDLOJV3913FRYgCVA2plFB8W8sF9LfbzXZOrxqaOrrwco&amp;#34;&lt;/span> &lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">hostKeys&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span> &lt;span class="s2">&amp;#34;/etc/ssh/ssh_host_rsa_key&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;/etc/ssh/ssh_host_ed25519_key&amp;#34;&lt;/span> &lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="what-happened">What happened&lt;/h2>
&lt;p>Being a good sysadmin I read the &lt;a href="https://nixos.org/manual/nixos/stable/release-notes">release notes&lt;/a> and caught:&lt;/p>
&lt;pre tabindex="0">&lt;code>The boot.initrd.network.udhcp.enable option allows control over DHCP during Stage 1 regardless of what networking.useDHCP is set to.
&lt;/code>&lt;/pre>&lt;p>I thought nothing of it&amp;hellip; But I should have!&lt;/p>
&lt;p>Behind this message is the fact that if you did not set &lt;code>networking.useDHCP = true;&lt;/code> globally, your initrd in nixos 23.11 will no longer do a DHCP lookup. This is a behavioral change I find baffling because it worked perfectly in 23.05! My configuration used DHCP but set explicitly on the interfaces that need it, not globally. As a networking engineer I loathe useless traffic on my networks, this includes DHCP requests for devices that do not need it.&lt;/p>
&lt;p>Nixos 23.11 needs a &lt;code>boot.initrd.network.udhcpc.enable = true;&lt;/code> in order to boot correctly again. Finding this new setting was not too hard - a few minutes of head scratching and intuition did the trick - but as usual I am on the lookout for a learning opportunity.&lt;/p>
&lt;h2 id="configuration-diffs">Configuration diffs&lt;/h2>
&lt;p>The first thing I looked for is a way to diff between two nixos configurations. I ended up disappointed because I did not find a way to do it neither easily nor exhaustively! There are quite advanced things for nix itself, but for nixos it is quite terse.&lt;/p>
&lt;p>The most advanced thing I managed is to have a diff between configurations that were activated on the same machine: diff on just the build server does not work, this needs to happen on the machine where the configuration is deployed live.&lt;/p>
&lt;p>The nixos diffs I managed are limited to installed packages or installed files and their size changes, nothing seems to allow me to dive into what is inside the initrd.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">nix --extra-experimental-features nix-command profile diff-closures --profile /nix/var/nix/profiles/system
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>This upgrade experience did not inspire a lot of confidence in me. Nixos is a great project and I wholeheartedly thank all its contributors for their efforts and dedication, but as a sysadmin this is not the kind of defaults that I ever want to see change silently.&lt;/p>
&lt;p>I still think nixos has great potential and deserves more recognition.&lt;/p></description></item><item><title>Testing opentofu</title><link>/blog/2024/01/31/testing-opentofu/</link><pubDate>2024-01-31</pubDate><guid>/blog/2024/01/31/testing-opentofu/</guid><description>&lt;h2 id="introduction">Introduction&lt;/h2>
&lt;p>This January, the opentofu project announced the general availability of their terraform fork. Not much changes for now between terraform and opentofu (and that is a good thing!), as far as I can tell the announcement was mostly about the new provider registry and of course the truly open source license.&lt;/p>
&lt;h2 id="registry-change">Registry change&lt;/h2>
&lt;p>The opentofu registry already has all the providers you are accustomed to, but your state will need to be migrated with:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">tofu init -upgrade&lt;span class="sb">`&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>For some providers you might encounter the following warning:&lt;/p>
&lt;pre tabindex="0">&lt;code>- Installed cloudflare/cloudflare v4.23.0. Signature validation was skipped due to the registry not containing GPG keys for this provider
&lt;/code>&lt;/pre>&lt;p>This is harmless and will resolve itself when the providers&amp;rsquo; developers provide the public GPG key used to sign their releases to the opentofu registry. The process is very simple thanks to their GitHub workflow automation.&lt;/p>
&lt;h2 id="little-improvements">Little improvements&lt;/h2>
&lt;ul>
&lt;li>&lt;code>tofu init&lt;/code> seems significantly faster than &lt;code>terraform init&lt;/code>.&lt;/li>
&lt;li>You never could interrupt a terraform plan with &lt;code>C-C&lt;/code>. I am so very glad to see that it is not a problem with opentofu! This really needs more advertising: proper Unix signal handling is like a superpower that is too often ignored by modern software.&lt;/li>
&lt;li>&lt;code>tofu test&lt;/code> can be used to assert things about your state and your configuration. I did not play with it yet but it opens &lt;a href="https://opentofu.org/docs/cli/commands/test/">a whole new realm of possibilities&lt;/a>!&lt;/li>
&lt;li>&lt;code>tofu import&lt;/code> can use expressions referencing other values or resources attributes, this is a big deal when handling massive imports!&lt;/li>
&lt;/ul>
&lt;h2 id="eventline-terraform-provider">Eventline terraform provider&lt;/h2>
&lt;p>I did the required pull requests on the &lt;a href="https://github.com/opentofu/registry">opentofu registry&lt;/a> to have my &lt;a href="https://github.com/adyxax/terraform-provider-eventline">Eventline provider&lt;/a> all fixed up and ready to rock!&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>I hope opentofu really takes off, the little improvements they made already feel like a breath of fresh air. Terraform could be so much more!&lt;/p></description></item><item><title>How to resize the persistent volumes of a kubernetes statefulset</title><link>/blog/2024/01/15/how-to-resize-the-persistent-volumes-of-a-kubernetes-statefulset/</link><pubDate>2024-01-15</pubDate><guid>/blog/2024/01/15/how-to-resize-the-persistent-volumes-of-a-kubernetes-statefulset/</guid><description>&lt;h2 id="introduction">Introduction&lt;/h2>
&lt;p>Kubernetes statefulsets are great but they come with their share of limitations. One of those limitations is that you cannot edit or patch many important keys of the YAML spec of an object after it has been created, in particular the requested volume size of the &lt;code>volumeClaimTemplates&lt;/code>.&lt;/p>
&lt;h2 id="how-to">How to&lt;/h2>
&lt;p>The work around consists of deleting the statefulset while leaving the objects created from it intact. In my example, I am resizing the persistent disks for a redis cluster created with the chart from bitnami, from 1GB to 2GB. It lives on a cluster named &lt;code>myth&lt;/code> in the namespace &lt;code>redis&lt;/code>. The statefulset is named &lt;code>redis-node&lt;/code> and spawns three pods and three pvcs.&lt;/p>
&lt;h3 id="storage-class">Storage class&lt;/h3>
&lt;p>First of all you need to ensure the storage class of the persistent volumes supports volume expansion. Most CSI drivers do, but the storage class do not necessarily have it enabled.&lt;/p>
&lt;p>To get the storage class to look for you can use (&lt;code>k&lt;/code> is my shell alias to the &lt;code>kubectl&lt;/code> command):&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">k --context myth -n redis get pvc redis-data-redis-node-0 -o &lt;span class="nv">jsonpath&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;{.spec.storageClassName}&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Let&amp;rsquo;s say that the storage class is named &lt;code>standard&lt;/code>, one of the builtin ones when installing a kubernetes cluster on gcp. Let&amp;rsquo;s inspect it:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">k --context myth get storageclass standard -o &lt;span class="nv">jsonpath&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;{.allowVolumeExpansion}&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>If you get &lt;code>false&lt;/code> or an empty output then your storage class is missing a &lt;code>allowVolumeExpansion: true&lt;/code>. If that is the case, you need to patch your storage class with:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">k --context myth patch storageclass standard --patch &lt;span class="s1">&amp;#39;{&amp;#34;allowVolumeExpansion&amp;#34;: true}&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Note that this object is not namespaced, you are changing this for your whole cluster.&lt;/p>
&lt;h3 id="resizing-the-persistent-volumes">Resizing the persistent volumes&lt;/h3>
&lt;p>Resize the pvcs:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">k --context myth -n redis patch pvc redis-data-redis-node-0 --patch &lt;span class="s1">&amp;#39;{&amp;#34;spec&amp;#34;: {&amp;#34;resources&amp;#34;: {&amp;#34;requests&amp;#34;: {&amp;#34;storage&amp;#34;: &amp;#34;2Gi&amp;#34;}}}}&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">k --context myth -n redis patch pvc redis-data-redis-node-1 --patch &lt;span class="s1">&amp;#39;{&amp;#34;spec&amp;#34;: {&amp;#34;resources&amp;#34;: {&amp;#34;requests&amp;#34;: {&amp;#34;storage&amp;#34;: &amp;#34;2Gi&amp;#34;}}}}&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">k --context myth -n redis patch pvc redis-data-redis-node-2 --patch &lt;span class="s1">&amp;#39;{&amp;#34;spec&amp;#34;: {&amp;#34;resources&amp;#34;: {&amp;#34;requests&amp;#34;: {&amp;#34;storage&amp;#34;: &amp;#34;2Gi&amp;#34;}}}}&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="recreate-the-statefulset">Recreate the statefulset&lt;/h3>
&lt;p>Get the statefulset:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">k --context myth -n redis get statefulset redis-node -o YAML &amp;gt; redis-statefulset.yaml
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Edit this yaml file to change the size in the volumeClaimTemplates, remove the status keys (and their values) in the file.&lt;/p>
&lt;p>With this yaml file ready, we can remove the statefulset without deleting the other kubernetes objects it spawned:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">k --context myth -n redis delete statefulset redis-node --cascade&lt;span class="o">=&lt;/span>orphan
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Recreate the statefulset from the modified yaml:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">k --context myth -n redis apply -f redis-statefulset.yaml
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Beware that this last action will restart the pods.&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>Kubernetes is a convoluted beast, not everything makes sense. Hopefully this work around will be useful to you until the day the developers decide it should be reasonable to be able to resize persistent volumes of statefulsets directly.&lt;/p></description></item><item><title>Migrating miniflux to nixos</title><link>/blog/2024/01/07/migrating-miniflux-to-nixos/</link><pubDate>2024-01-07</pubDate><guid>/blog/2024/01/07/migrating-miniflux-to-nixos/</guid><description>&lt;h2 id="introduction">Introduction&lt;/h2>
&lt;p>I am migrating several services from a k3s kubernetes cluster to a nixos server. Here is how I performed the operation with my &lt;a href="https://miniflux.app/">miniflux rss reader&lt;/a>.&lt;/p>
&lt;h2 id="miniflux-with-nixos">Miniflux with nixos&lt;/h2>
&lt;p>Miniflux is packaged on nixos, but I am used to the container image so I am sticking with it for now.&lt;/p>
&lt;p>Here is the module I wrote to deploy a miniflux container, configure postgresql and borg backups:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-nix" data-lang="nix">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span> &lt;span class="n">config&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="n">lib&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="n">pkgs&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="o">...&lt;/span> &lt;span class="p">}:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">imports&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="sr">../../lib/borg-client.nix&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="sr">../../lib/postgresql.nix&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="sr">../../lib/nginx.nix&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">environment&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">etc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="s2">&amp;#34;borg-miniflux-db.key&amp;#34;&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">mode&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;0400&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">source&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="sr">./borg-db.key&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">services&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">borgbackup&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">jobs&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">let&lt;/span> &lt;span class="n">defaults&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">compression&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;auto,zstd&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">doInit&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">true&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">encryption&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">mode&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;none&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">prune&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">keep&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">daily&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">14&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">weekly&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">4&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">monthly&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">startAt&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;daily&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span> &lt;span class="k">in&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;miniflux-db&amp;#34;&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">defaults&lt;/span> &lt;span class="o">//&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">environment&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">BORG_RSH&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;ssh -i /etc/borg-miniflux-db.key&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">paths&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;/tmp/miniflux.sql&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">postHook&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;rm -f /tmp/miniflux.sql&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">preHook&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;&amp;#39;rm -f /tmp/miniflux.sql; /run/current-system/sw/bin/pg_dump -h localhost -U miniflux -d miniflux &amp;gt; /tmp/miniflux.sql&amp;#39;&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">repo&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;ssh://borg@gcp.adyxax.org/srv/borg/miniflux-db&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">nginx&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">virtualHosts&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="s2">&amp;#34;miniflux.adyxax.org&amp;#34;&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">forceSSL&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">true&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">locations&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;/&amp;#34;&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">proxyPass&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;http://127.0.0.1:8084&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">sslCertificate&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;/etc/nginx/adyxax.org.crt&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">sslCertificateKey&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;/etc/nginx/adyxax.org.key&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">postgresql&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">ensureUsers&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;miniflux&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">ensurePermissions&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="s2">&amp;#34;DATABASE &lt;/span>&lt;span class="se">\&amp;#34;&lt;/span>&lt;span class="s2">miniflux&lt;/span>&lt;span class="se">\&amp;#34;&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;ALL PRIVILEGES&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">ensureDatabases&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;miniflux&amp;#34;&lt;/span>&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">virtualisation&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">oci-containers&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">containers&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">miniflux&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">environment&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">ADMIN_PASSWORD&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">lib&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">removeSuffix&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">builtins&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">readFile&lt;/span> &lt;span class="sr">./admin-password.key&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">ADMIN_USERNAME&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;admin&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">DATABASE_URL&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;postgres://miniflux:&amp;#34;&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">lib&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">removeSuffix&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">builtins&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">readFile&lt;/span> &lt;span class="sr">./database-password.key&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="s2">&amp;#34;@10.88.0.1/miniflux?sslmode=disable&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">RUN_MIGRATIONS&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;1&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">image&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;miniflux/miniflux:2.0.50&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">ports&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;127.0.0.1:8084:8080&amp;#34;&lt;/span>&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="dependencies">Dependencies&lt;/h2>
&lt;p>The dependencies are mostly the same as in &lt;a href="/blog/2023/12/20/migrating-vaultwarden-to-nixos/#dependencies">my article about vaultwarden migration&lt;/a>.&lt;/p>
&lt;h2 id="migration-process">Migration process&lt;/h2>
&lt;p>The first step is obviously to deploy this new configuration to the server, then I need to login and manually restore the backups.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">make run &lt;span class="nv">host&lt;/span>&lt;span class="o">=&lt;/span>dalinar.adyxax.org
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The container will be failing because no password is set on the database user yet, so I stop it:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">systemctl stop podman-miniflux
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>There is only one backup job for miniflux and it holds a dump of the database:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">export&lt;/span> &lt;span class="nv">BORG_RSH&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;ssh -i /etc/borg-miniflux-db.key&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">borg list ssh://borg@gcp.adyxax.org/srv/borg/miniflux-db
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">borg extract ssh://borg@gcp.adyxax.org/srv/borg/miniflux-db::dalinar-miniflux-db-2023-11-20T00:00:01
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">psql -h localhost -U postgres -d miniflux
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Restoring the data itself is done with the psql shell:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">ALTER&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">USER&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">miniflux&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">WITH&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">PASSWORD&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;XXXXXX&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="err">\&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">tmp&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">miniflux&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="k">sql&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Afterwards I clean up the database dump and restart miniflux:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">rm -rf tmp/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">systemctl start podman-miniflux
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>To wrap this up I migrate the DNS records to the new host, update my monitoring system and clean up the namespace on the k3s server.&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>I did all this in november, I have quite the backlog of articles to write!&lt;/p></description></item><item><title>Migrating vaultwarden to nixos</title><link>/blog/2023/12/20/migrating-vaultwarden-to-nixos/</link><pubDate>2023-12-20</pubDate><guid>/blog/2023/12/20/migrating-vaultwarden-to-nixos/</guid><description>&lt;h2 id="introduction">Introduction&lt;/h2>
&lt;p>I am migrating several services from a k3s kubernetes cluster to a nixos server. Here is how I performed the operation with my &lt;a href="https://github.com/dani-garcia/vaultwarden">vaultwarden&lt;/a> password manager.&lt;/p>
&lt;h2 id="vaultwarden-with-nixos">Vaultwarden with nixos&lt;/h2>
&lt;p>Vaultwarden is packaged on nixos, but I am used to the hosting the container image and upgrading it at my own pace so I am sticking with it for now.&lt;/p>
&lt;p>Here is the module I wrote to deploy a vaultwarden container, configure postgresql and borg backups in &lt;code>apps/vaultwarden/app.nix&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-nix" data-lang="nix">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span> &lt;span class="n">config&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="n">lib&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="n">pkgs&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="o">...&lt;/span> &lt;span class="p">}:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">imports&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="sr">../../lib/nginx.nix&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="sr">../../lib/postgresql.nix&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">environment&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">etc&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;borg-vaultwarden-db.key&amp;#34;&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">mode&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;0400&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">source&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="sr">./borg-db.key&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;borg-vaultwarden-storage.key&amp;#34;&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">mode&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;0400&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">source&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="sr">./borg-storage.key&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">services&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">borgbackup&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">jobs&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">let&lt;/span> &lt;span class="n">defaults&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">compression&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;auto,zstd&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">doInit&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">true&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">encryption&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">mode&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;none&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">prune&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">keep&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">daily&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">14&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">weekly&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">4&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">monthly&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">startAt&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;daily&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span> &lt;span class="k">in&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;vaultwarden-db&amp;#34;&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">defaults&lt;/span> &lt;span class="o">//&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">environment&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">BORG_RSH&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;ssh -i /etc/borg-vaultwarden-db.key&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">paths&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;/tmp/vaultwarden.sql&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">postHook&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;rm -f /tmp/vaultwarden.sql&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">preHook&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;&amp;#39;rm -f /tmp/vaultwarden.sql; /run/current-system/sw/bin/pg_dump -h localhost -U vaultwarden -d vaultwarden &amp;gt; /tmp/vaultwarden.sql&amp;#39;&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">repo&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;ssh://borg@gcp.adyxax.org/srv/borg/vaultwarden-db&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;vaultwarden-storage&amp;#34;&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">defaults&lt;/span> &lt;span class="o">//&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">environment&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">BORG_RSH&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;ssh -i /etc/borg-vaultwarden-storage.key&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">paths&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;/srv/vaultwarden&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">repo&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;ssh://borg@gcp.adyxax.org/srv/borg/vaultwarden-storage&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">nginx&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">virtualHosts&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">let&lt;/span> &lt;span class="n">commons&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">forceSSL&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">true&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">locations&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;/&amp;#34;&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">proxyPass&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;http://127.0.0.1:8083&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span> &lt;span class="k">in&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;pass.adyxax.org&amp;#34;&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">commons&lt;/span> &lt;span class="o">//&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">sslCertificate&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;/etc/nginx/adyxax.org.crt&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">sslCertificateKey&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;/etc/nginx/adyxax.org.key&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">postgresql&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">ensureUsers&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;vaultwarden&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">ensureDBOwnership&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">true&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">ensureDatabases&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;vaultwarden&amp;#34;&lt;/span>&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">virtualisation&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">oci-containers&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">containers&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">vaultwarden&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">environment&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">ADMIN_TOKEN&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">builtins&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">readFile&lt;/span> &lt;span class="sr">./argon-token.key&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">DATABASE_MAX_CONNS&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;2&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">DATABASE_URL&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;postgres://vaultwarden:&amp;#34;&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">lib&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">removeSuffix&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">builtins&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">readFile&lt;/span> &lt;span class="sr">./database-password.key&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="s2">&amp;#34;@10.88.0.1/vaultwarden?sslmode=disable&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">image&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;vaultwarden/server:1.30.1&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">ports&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;127.0.0.1:8083:80&amp;#34;&lt;/span>&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">volumes&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span> &lt;span class="s2">&amp;#34;/srv/vaultwarden/:/data&amp;#34;&lt;/span> &lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="dependencies">Dependencies&lt;/h2>
&lt;h3 id="borg">Borg&lt;/h3>
&lt;p>Borg needs to be running on another server with the following configuration stored in my &lt;code>apps/vaultwarden/borg.nix&lt;/code> file:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-nix" data-lang="nix">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span> &lt;span class="n">config&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="n">pkgs&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="o">...&lt;/span> &lt;span class="p">}:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">imports&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="sr">../../lib/borg.nix&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">users&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">users&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">borg&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">openssh&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">authorizedKeys&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">keys&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;command=&lt;/span>&lt;span class="se">\&amp;#34;&lt;/span>&lt;span class="s2">borg serve --restrict-to-path /srv/borg/vaultwarden-db&lt;/span>&lt;span class="se">\&amp;#34;&lt;/span>&lt;span class="s2">,restrict &amp;#34;&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">builtins&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">readFile&lt;/span> &lt;span class="sr">./borg-db.key.pub&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;command=&lt;/span>&lt;span class="se">\&amp;#34;&lt;/span>&lt;span class="s2">borg serve --restrict-to-path /srv/borg/vaultwarden-storage&lt;/span>&lt;span class="se">\&amp;#34;&lt;/span>&lt;span class="s2">,restrict &amp;#34;&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">builtins&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">readFile&lt;/span> &lt;span class="sr">./borg-storage.key.pub&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="postgresql">PostgreSQL&lt;/h3>
&lt;p>My postgreSQL module defines the following global configuration:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-nix" data-lang="nix">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span> &lt;span class="n">config&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="n">lib&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="n">pkgs&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="o">...&lt;/span> &lt;span class="p">}:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">networking&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">firewall&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">interfaces&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="s2">&amp;#34;podman0&amp;#34;&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">allowedTCPPorts&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span> &lt;span class="mi">5432&lt;/span> &lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">services&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">postgresql&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">enable&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">true&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">enableTCPIP&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">true&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">package&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">pkgs&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">postgresql_15&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">authentication&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">pkgs&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">lib&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">mkOverride&lt;/span> &lt;span class="mi">10&lt;/span> &lt;span class="s1">&amp;#39;&amp;#39;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"> #type database DBuser auth-method
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"> local all all trust
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"> # podman
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"> host all all 10.88.0.0/16 scram-sha-256
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"> &amp;#39;&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Since for now I am running nothing outside of containers on this server, I am trusting the unix socket connections. Depending on what you are doing you might want a stronger auth-method there.&lt;/p>
&lt;h3 id="nginx">Nginx&lt;/h3>
&lt;p>My nginx module defines the following global configuration:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-nix" data-lang="nix">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span> &lt;span class="n">config&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="n">lib&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="n">pkgs&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="o">...&lt;/span> &lt;span class="p">}:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">environment&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">etc&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">let&lt;/span> &lt;span class="n">permissions&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="n">mode&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;0400&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">uid&lt;/span>&lt;span class="o">=&lt;/span> &lt;span class="n">config&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">ids&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">uids&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">nginx&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">};&lt;/span> &lt;span class="k">in&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;nginx/adyxax.org.crt&amp;#34;&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">permissions&lt;/span> &lt;span class="o">//&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="n">source&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="sr">../../01-legacy/adyxax.org.crt&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;nginx/adyxax.org.key&amp;#34;&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">permissions&lt;/span> &lt;span class="o">//&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="n">source&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="sr">../../01-legacy/adyxax.org.key&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">networking&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">firewall&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">allowedTCPPorts&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span> &lt;span class="mi">80&lt;/span> &lt;span class="mi">443&lt;/span> &lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">services&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">nginx&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">clientMaxBodySize&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;40M&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">enable&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">true&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">enableReload&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">true&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">recommendedGzipSettings&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">true&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">recommendedOptimisation&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">true&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">recommendedProxySettings&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">true&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="secrets">Secrets&lt;/h3>
&lt;p>There are several secrets referenced in the configuration, these are all git-crypted files:&lt;/p>
&lt;ul>
&lt;li>argon-token.key&lt;/li>
&lt;li>borg-db.key&lt;/li>
&lt;li>borg-storage.key&lt;/li>
&lt;li>database-password.key&lt;/li>
&lt;/ul>
&lt;h2 id="migration-process">Migration process&lt;/h2>
&lt;p>The first step is obviously to deploy this new configuration to the server, then I need to login and manually restore the backups.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">make run &lt;span class="nv">host&lt;/span>&lt;span class="o">=&lt;/span>myth.adyxax.org
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The container will be failing because no password is set on the database user yet, so I stop it:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">systemctl stop podman-vaultwarden
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>There are two backup jobs for vaultwarden: one for its storage and the second one for the database.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">export&lt;/span> &lt;span class="nv">BORG_RSH&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;ssh -i /etc/borg-vaultwarden-storage.key&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">borg list ssh://borg@gcp.adyxax.org/srv/borg/vaultwarden-storage
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">borg extract ssh://borg@gcp.adyxax.org/srv/borg/vaultwarden-storage::dalinar-vaultwarden-storage-2023-11-19T00:00:01
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mv srv/vaultwarden /srv/
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">export&lt;/span> &lt;span class="nv">BORG_RSH&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;ssh -i /etc/borg-vaultwarden-db.key&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">borg list ssh://borg@gcp.adyxax.org/srv/borg/vaultwarden-db
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">borg extract ssh://borg@gcp.adyxax.org/srv/borg/vaultwarden-db::dalinar-vaultwarden-db-2023-11-19T00:00:01
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">psql -h localhost -U postgres -d vaultwarden
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Restoring the data itself is done with the psql shell:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">ALTER&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">USER&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">vaultwarden&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">WITH&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">PASSWORD&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;XXXXX&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="err">\&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">tmp&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">vaultwarden&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="k">sql&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Afterwards I clean up the database dump and restart vaultwarden:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">rm -rf tmp/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">systemctl start podman-vaultwarden
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>To wrap this up I migrate the DNS records to the new host, update my monitoring system and clean up the namespace on the k3s server.&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>Automating things with nixos is satisfying, but it does not abstract all the sysadmin&amp;rsquo;s work away.&lt;/p>
&lt;p>I am not quite satisfied with my borg configuration entries. I should be able to write this more elegantly when I find the time, but it works.&lt;/p></description></item><item><title>Memory difficulties with nixos</title><link>/blog/2023/12/14/memory-difficulties-with-nixos/</link><pubDate>2023-12-14</pubDate><guid>/blog/2023/12/14/memory-difficulties-with-nixos/</guid><description>&lt;h2 id="introduction">Introduction&lt;/h2>
&lt;p>I encountered my first difficulties with nixos which required some ingenuity outside of the natural learning curve.&lt;/p>
&lt;h2 id="on-memory-and-lightweight-software">On memory and lightweight software&lt;/h2>
&lt;p>The VPS hosts I am using are not really beefy. Three of these only have 1GB of ram which is not a lot by today&amp;rsquo;s standards, but quite sufficient for many usages. The services I self host are quite lightweight so I never had problems when running Alpine Linux, Debian, FreeBSD or OpenBSD on these small machines. Of course k3s was reserved for my beefier 2GB hosts, but nixos seemed it could fit. Like any operating system, it consumes little memory at rest.&lt;/p>
&lt;p>The one big memory constraint coming from nixos might not be obvious: it is when rebuilding the configurations! For an almost empty host, very simple configuration and no services besides dhcp, ssh, journal and cron, a nixos configuration build could take about 500MB of ram. That is not negligible but it fit.&lt;/p>
&lt;p>With some services like an irc server, eventline, privatebin and gotosocial, the configuration got more complex and nixos more demanding, consuming about 700MB for a build.&lt;/p>
&lt;h2 id="building-nixos-remotely">Building nixos remotely&lt;/h2>
&lt;p>I hit a wall when I started using a second channel to pull more recent packages. I wanted bleeding edge packages for things like Emacs, but stable ones for all the other parts of the system&amp;hellip; and I could no longer build nixos locally! 1GB is not enough to have the packages sources and resolve dependencies when building the configuration.&lt;/p>
&lt;p>Therefore I started building nixos configurations remotely. My workstation does the heavy lifting of building the configuration then copying all the derivations (target configurations, packages and files) to the hosts.&lt;/p>
&lt;p>Activating the configuration still involves a spike of memory consumption on the hosts of about 500MB, but it is less than the 1.2GB it takes to build the configurations. Despite this, I experienced a few painful out of memory when deploying a new configuration. Now I shutdown the most demanding services before deploying, like gotosocial which can sometimes consume 200MB of ram by itself.&lt;/p>
&lt;h2 id="upgrading-to-2311">Upgrading to 23.11&lt;/h2>
&lt;p>I had a bad experience upgrading from 23.05 to the recent 23.11 release. I do not know how the diffs between configurations are calculated by nix, but I could not deploy on my 1GB hosts!&lt;/p>
&lt;p>I worked around this by using &lt;code>dd&lt;/code> to copy the hard drive images and start them in virtual machines locally. This allowed me to upgrade then copy the images the other way. Still, that is a painful process. The back and forth copying involves a similar process than I described to &lt;a href="/blog/2023/10/04/installing-nixos-on-a-vps/">remount partitions as read-only&lt;/a> in a previous article.&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>Beware if you intend on using nixos on small machines! I will continue experimenting with nix because it still seems worthwhile and I want to continue learning it, but if I end up switching back to another operating system (be it Alpine, Debian or a BSD) it will be because the configuration build process became too painful to bear.&lt;/p></description></item><item><title>Finishing advent of code 2022 in Haskell</title><link>/blog/2023/12/05/finishing-advent-of-code-2022-in-haskell/</link><pubDate>2023-12-05</pubDate><guid>/blog/2023/12/05/finishing-advent-of-code-2022-in-haskell/</guid><description>&lt;h2 id="introduction">Introduction&lt;/h2>
&lt;p>I wrote about doing the &lt;a href="/blog/2023/05/28/advent-of-code-2022-in-zig/">advent of code 2022 in zig&lt;/a>, but I did not complete the year. I stopped on using zig on day 15 when I hit a bug when using hashmaps that I could not solve in time and continued in JavaScript until &lt;a href="https://adventofcode.com/2022/day/22">day 22&lt;/a>. On day 22 part 2, you need to fold a cube and move on it keeping track of your orientation&amp;hellip; It was hard!&lt;/p>
&lt;p>Last week I wanted to warm up for the current advent of code and therefore took it up again&amp;hellip; it was (almost) easy with Haskell!&lt;/p>
&lt;h2 id="day-22---monkey-map">Day 22 - Monkey Map&lt;/h2>
&lt;p>You get an input that looks like this:&lt;/p>
&lt;pre tabindex="0">&lt;code> ...#
.#..
#...
....
...#.......#
........#...
..#....#....
..........#.
...#....
.....#..
.#......
......#.
10R5L5R10L4R5L5
&lt;/code>&lt;/pre>&lt;p>The &lt;code>.&lt;/code> are floor tiles, the &lt;code>#&lt;/code> are impassable walls. You have a cursor starting on the leftmost tile on the first line. The cursor moves and the empty spaces do not exist: if you step out you wrap around: easy enough&amp;hellip; until part 2!&lt;/p>
&lt;p>Here is how I parse the input:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-haskell" data-lang="haskell">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">type&lt;/span> &lt;span class="kt">Line&lt;/span> &lt;span class="ow">=&lt;/span> &lt;span class="kt">V&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="kt">Vector&lt;/span> &lt;span class="kt">Char&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">type&lt;/span> &lt;span class="kt">Map&lt;/span> &lt;span class="ow">=&lt;/span> &lt;span class="kt">V&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="kt">Vector&lt;/span> &lt;span class="kt">Line&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">data&lt;/span> &lt;span class="kt">Instruction&lt;/span> &lt;span class="ow">=&lt;/span> &lt;span class="kt">Move&lt;/span> &lt;span class="kt">Int&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="kt">L&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="kt">R&lt;/span> &lt;span class="kr">deriving&lt;/span> &lt;span class="kt">Show&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">data&lt;/span> &lt;span class="kt">Input&lt;/span> &lt;span class="ow">=&lt;/span> &lt;span class="kt">Input&lt;/span> &lt;span class="kt">Map&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="kt">Instruction&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="kr">deriving&lt;/span> &lt;span class="kt">Show&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">type&lt;/span> &lt;span class="kt">Parser&lt;/span> &lt;span class="ow">=&lt;/span> &lt;span class="kt">Parsec&lt;/span> &lt;span class="kt">Void&lt;/span> &lt;span class="kt">String&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nf">parseMapLine&lt;/span> &lt;span class="ow">::&lt;/span> &lt;span class="kt">Parser&lt;/span> &lt;span class="kt">Line&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nf">parseMapLine&lt;/span> &lt;span class="ow">=&lt;/span> &lt;span class="kr">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">line&lt;/span> &lt;span class="ow">&amp;lt;-&lt;/span> &lt;span class="n">some&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">char&lt;/span> &lt;span class="sc">&amp;#39;.&amp;#39;&lt;/span> &lt;span class="o">&amp;lt;|&amp;gt;&lt;/span> &lt;span class="n">char&lt;/span> &lt;span class="sc">&amp;#39; &amp;#39;&lt;/span> &lt;span class="o">&amp;lt;|&amp;gt;&lt;/span> &lt;span class="n">char&lt;/span> &lt;span class="sc">&amp;#39;#&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">&amp;lt;*&lt;/span> &lt;span class="n">eol&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">return&lt;/span> &lt;span class="o">$&lt;/span> &lt;span class="kt">V&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">generate&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">length&lt;/span> &lt;span class="n">line&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">line&lt;/span> &lt;span class="o">!!&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nf">parseMap&lt;/span> &lt;span class="ow">::&lt;/span> &lt;span class="kt">Parser&lt;/span> &lt;span class="kt">Map&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nf">parseMap&lt;/span> &lt;span class="ow">=&lt;/span> &lt;span class="kr">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">lines&lt;/span> &lt;span class="ow">&amp;lt;-&lt;/span> &lt;span class="n">some&lt;/span> &lt;span class="n">parseMapLine&lt;/span> &lt;span class="o">&amp;lt;*&lt;/span> &lt;span class="n">eol&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">return&lt;/span> &lt;span class="o">$&lt;/span> &lt;span class="kt">V&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">generate&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">length&lt;/span> &lt;span class="n">lines&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">lines&lt;/span> &lt;span class="o">!!&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nf">parseInstruction&lt;/span> &lt;span class="ow">::&lt;/span> &lt;span class="kt">Parser&lt;/span> &lt;span class="kt">Instruction&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nf">parseInstruction&lt;/span> &lt;span class="ow">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">Move&lt;/span> &lt;span class="o">.&lt;/span> &lt;span class="n">read&lt;/span> &lt;span class="o">&amp;lt;$&amp;gt;&lt;/span> &lt;span class="n">some&lt;/span> &lt;span class="n">digitChar&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">&amp;lt;|&amp;gt;&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">char&lt;/span> &lt;span class="sc">&amp;#39;L&amp;#39;&lt;/span> &lt;span class="o">$&amp;gt;&lt;/span> &lt;span class="kt">L&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">&amp;lt;|&amp;gt;&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">char&lt;/span> &lt;span class="sc">&amp;#39;R&amp;#39;&lt;/span> &lt;span class="o">$&amp;gt;&lt;/span> &lt;span class="kt">R&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nf">parseInput&amp;#39;&lt;/span> &lt;span class="ow">::&lt;/span> &lt;span class="kt">Parser&lt;/span> &lt;span class="kt">Input&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nf">parseInput&amp;#39;&lt;/span> &lt;span class="ow">=&lt;/span> &lt;span class="kt">Input&lt;/span> &lt;span class="o">&amp;lt;$&amp;gt;&lt;/span> &lt;span class="n">parseMap&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">&amp;lt;*&amp;gt;&lt;/span> &lt;span class="n">some&lt;/span> &lt;span class="n">parseInstruction&lt;/span> &lt;span class="o">&amp;lt;*&lt;/span> &lt;span class="n">eol&lt;/span> &lt;span class="o">&amp;lt;*&lt;/span> &lt;span class="n">eof&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>In part 2 you learn that your input pattern is in fact 6 squares that can be folded to form a cube. Now instead of simply wrapping the empty spaces, when stepping out you need to find out were you end up on the cube and with which orientation.&lt;/p>
&lt;p>Here is a visualization I made in excalidraw to understand how folding the cube based on my input would work (this does not match the example above but matched the players&amp;rsquo; input):&lt;/p>
&lt;p>&lt;img src="https://files.adyxax.org/www/aoc-2022-22-folding.excalidraw.svg" alt="excalidraw cube folding">&lt;/p>
&lt;p>The whole code is available &lt;a href="https://git.adyxax.org/adyxax/advent-of-code/tree/2022/22-Monkey-Map/second.hs">on my git server&lt;/a> but here is the core of my solver for this puzzle:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-haskell" data-lang="haskell">&lt;span class="line">&lt;span class="cl">&lt;span class="nf">stepOutside&lt;/span> &lt;span class="ow">::&lt;/span> &lt;span class="kt">Map&lt;/span> &lt;span class="ow">-&amp;gt;&lt;/span> &lt;span class="kt">Int&lt;/span> &lt;span class="ow">-&amp;gt;&lt;/span> &lt;span class="kt">Int&lt;/span> &lt;span class="ow">-&amp;gt;&lt;/span> &lt;span class="kt">Int&lt;/span> &lt;span class="ow">-&amp;gt;&lt;/span> &lt;span class="kt">Heading&lt;/span> &lt;span class="ow">-&amp;gt;&lt;/span> &lt;span class="kt">Int&lt;/span> &lt;span class="ow">-&amp;gt;&lt;/span> &lt;span class="kt">Cursor&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nf">stepOutside&lt;/span> &lt;span class="n">m&lt;/span> &lt;span class="n">s&lt;/span> &lt;span class="n">x&lt;/span> &lt;span class="n">y&lt;/span> &lt;span class="n">h&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">t&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">h&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">a&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">N&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="ow">=&lt;/span> &lt;span class="n">proceed&lt;/span> &lt;span class="n">fw&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">fn&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">rx&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="kt">E&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">|&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">t&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">h&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">a&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">W&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="ow">=&lt;/span> &lt;span class="n">proceed&lt;/span> &lt;span class="n">dw&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">ds&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="n">ry&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="kt">E&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">|&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">t&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">h&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">b&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">N&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="ow">=&lt;/span> &lt;span class="n">proceed&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">fw&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">rx&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="n">fs&lt;/span> &lt;span class="kt">N&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">|&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">t&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">h&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">b&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">E&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="ow">=&lt;/span> &lt;span class="n">proceed&lt;/span> &lt;span class="n">ee&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">es&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="n">ry&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="kt">W&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">|&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">t&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">h&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">b&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">S&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="ow">=&lt;/span> &lt;span class="n">proceed&lt;/span> &lt;span class="n">ce&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">cn&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">rx&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="kt">W&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">|&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">t&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">h&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">c&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">W&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="ow">=&lt;/span> &lt;span class="n">proceed&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">dw&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">ry&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="n">dn&lt;/span> &lt;span class="kt">S&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">|&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">t&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">h&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">c&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">E&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="ow">=&lt;/span> &lt;span class="n">proceed&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">bw&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">ry&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="n">bs&lt;/span> &lt;span class="kt">N&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">|&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">t&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">h&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">d&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">N&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="ow">=&lt;/span> &lt;span class="n">proceed&lt;/span> &lt;span class="n">cw&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">cn&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">rx&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="kt">E&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">|&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">t&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">h&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">d&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">W&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="ow">=&lt;/span> &lt;span class="n">proceed&lt;/span> &lt;span class="n">aw&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">as&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="n">ry&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="kt">E&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">|&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">t&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">h&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">E&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="ow">=&lt;/span> &lt;span class="n">proceed&lt;/span> &lt;span class="n">be&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">bs&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="n">ry&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="kt">W&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">|&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">t&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">h&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">S&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="ow">=&lt;/span> &lt;span class="n">proceed&lt;/span> &lt;span class="n">fe&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">fn&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">rx&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="kt">W&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">|&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">t&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">h&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">f&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">W&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="ow">=&lt;/span> &lt;span class="n">proceed&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">aw&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">ry&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="n">an&lt;/span> &lt;span class="kt">S&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">|&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">t&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">h&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">f&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">S&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="ow">=&lt;/span> &lt;span class="n">proceed&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">bw&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">rx&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="n">bn&lt;/span> &lt;span class="kt">S&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">|&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">t&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">h&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">f&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">E&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="ow">=&lt;/span> &lt;span class="n">proceed&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">ew&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">ry&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="n">es&lt;/span> &lt;span class="kt">N&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">where&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="n">tx&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">rx&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="ow">=&lt;/span> &lt;span class="n">x&lt;/span> &lt;span class="p">`&lt;/span>&lt;span class="n">divMod&lt;/span>&lt;span class="p">`&lt;/span> &lt;span class="n">s&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="n">ty&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">ry&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="ow">=&lt;/span> &lt;span class="n">y&lt;/span> &lt;span class="p">`&lt;/span>&lt;span class="n">divMod&lt;/span>&lt;span class="p">`&lt;/span> &lt;span class="n">s&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">t&lt;/span> &lt;span class="ow">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">tx&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">ty&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">proceed&lt;/span> &lt;span class="ow">::&lt;/span> &lt;span class="kt">Int&lt;/span> &lt;span class="ow">-&amp;gt;&lt;/span> &lt;span class="kt">Int&lt;/span> &lt;span class="ow">-&amp;gt;&lt;/span> &lt;span class="kt">Heading&lt;/span> &lt;span class="ow">-&amp;gt;&lt;/span> &lt;span class="kt">Cursor&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">proceed&lt;/span> &lt;span class="n">x&amp;#39;&lt;/span> &lt;span class="n">y&amp;#39;&lt;/span> &lt;span class="n">h&amp;#39;&lt;/span> &lt;span class="ow">=&lt;/span> &lt;span class="kr">case&lt;/span> &lt;span class="n">m&lt;/span> &lt;span class="kt">V&lt;/span>&lt;span class="o">.!&lt;/span> &lt;span class="n">y&amp;#39;&lt;/span> &lt;span class="kt">V&lt;/span>&lt;span class="o">.!&lt;/span> &lt;span class="n">x&amp;#39;&lt;/span> &lt;span class="kr">of&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="sc">&amp;#39;.&amp;#39;&lt;/span> &lt;span class="ow">-&amp;gt;&lt;/span> &lt;span class="n">step&lt;/span> &lt;span class="n">m&lt;/span> &lt;span class="n">s&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">Cursor&lt;/span> &lt;span class="n">x&amp;#39;&lt;/span> &lt;span class="n">y&amp;#39;&lt;/span> &lt;span class="n">h&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">Move&lt;/span> &lt;span class="o">$&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="sc">&amp;#39;#&amp;#39;&lt;/span> &lt;span class="ow">-&amp;gt;&lt;/span> &lt;span class="kt">Cursor&lt;/span> &lt;span class="n">x&lt;/span> &lt;span class="n">y&lt;/span> &lt;span class="n">h&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="n">ax&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">ay&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="ow">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="n">bx&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">by&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="ow">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="n">cx&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">cy&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="ow">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="n">dx&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">dy&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="ow">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="n">ex&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">ey&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="ow">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="n">fx&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">fy&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="ow">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">a&lt;/span> &lt;span class="ow">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">ax&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">ay&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">b&lt;/span> &lt;span class="ow">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">bx&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">by&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">c&lt;/span> &lt;span class="ow">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">cx&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">cy&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">d&lt;/span> &lt;span class="ow">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">dx&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">dy&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">e&lt;/span> &lt;span class="ow">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">ex&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">ey&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">f&lt;/span> &lt;span class="ow">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">fx&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">fy&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="n">an&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">as&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">aw&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">ae&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="ow">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">ay&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">s&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">ay&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="n">s&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">ax&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">s&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">ax&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="n">s&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="n">bn&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">bs&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">bw&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">be&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="ow">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">by&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">s&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">by&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="n">s&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">bx&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">s&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">bx&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="n">s&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="n">cn&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">cs&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">cw&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">ce&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="ow">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">cy&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">s&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">cy&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="n">s&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">cx&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">s&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">cx&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="n">s&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="n">dn&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">ds&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">dw&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">de&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="ow">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">dy&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">s&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">dy&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="n">s&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">dx&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">s&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">dx&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="n">s&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="n">en&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">es&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">ew&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">ee&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="ow">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">ey&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">s&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">ey&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="n">s&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">ex&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">s&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">ex&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="n">s&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="n">fn&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">fs&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">fw&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">fe&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="ow">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">fy&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">s&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">fy&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="n">s&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">fx&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">s&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">fx&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="n">s&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This &lt;code>stepOutside&lt;/code> function takes in argument the map, its size, the cursor&amp;rsquo;s &lt;code>(x, y)&lt;/code> position and heading &lt;code>h&lt;/code>, while i is the number of steps to perform. I first compute on which face the cursor is, and based on its heading where it should end up. I then use the faces coordinates to compute the final position, being careful to follow on the schematic how the transition is performed.&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>The next days where quite a lot easier than this one. Haskell is really a great language for puzzle solving thanks to its excellent parsing capabilities and its incredible type system.&lt;/p>
&lt;p>A great thing that should speak of Haskell&amp;rsquo;s qualities is that it is the second year of advent of code that I completed all 25 days: both times it was thanks to Haskell! I think I should revisit the years 2021 that I did with Go next: I stopped on day 19 because it involved a three dimensional puzzle that was quite difficult.&lt;/p></description></item><item><title>Managing multiple nixos hosts, remotely</title><link>/blog/2023/11/28/managing-multiple-nixos-hosts-remotely/</link><pubDate>2023-11-28</pubDate><guid>/blog/2023/11/28/managing-multiple-nixos-hosts-remotely/</guid><description>&lt;h2 id="introduction">Introduction&lt;/h2>
&lt;p>There seems to be almost too many tools to manage nix configurations with too many different approaches, each with their quirks and learning curve. Googling this issue was more troubling than it should be!&lt;/p>
&lt;p>Therefore I tried to keep things simple and converged on a code organization that I find flexible enough for my current nixos needs without anything more than the standard nix tools.&lt;/p>
&lt;h2 id="repository-layout">Repository layout&lt;/h2>
&lt;p>Here are the directories inside my nixos repository:&lt;/p>
&lt;pre tabindex="0">&lt;code>├── apps
│ ├── eventline
│ ├── files
│ ├── gotosocial
│ ├── miniflux
│ ├── privatebin
│ └── vaultwarden
├── hosts
│ ├── dalinar.adyxax.org
│ ├── gcp.adyxax.org
│ └── myth.adyxax.org
└── lib
└── common
&lt;/code>&lt;/pre>&lt;h3 id="apps">apps&lt;/h3>
&lt;p>The &lt;code>apps&lt;/code> directory contains files and configurations about each application I manage. Here is what an app folder looks like:&lt;/p>
&lt;pre tabindex="0">&lt;code>└── apps
└── eventline
├── app.nix
├── borg-db.key
├── borg-db.key.pub
├── borg.nix
├── eventline-entrypoint
└── eventline.yaml
&lt;/code>&lt;/pre>&lt;p>Each of the app directories has an &lt;code>app.nix&lt;/code> file detailing the nix configuration to deploy the app that will be included by the host running it, and a &lt;code>borg.nix&lt;/code> with the configurations for the host that will be the borg backups target. In my setup each app has its own set of ssh keys (which are encrypted with &lt;code>git-crypt&lt;/code>) for its borg jobs.&lt;/p>
&lt;p>The remaining files are specific to the app. In this example there is a configuration file and a custom entrypoint for a container image.&lt;/p>
&lt;h3 id="hosts">hosts&lt;/h3>
&lt;p>The hosts directory contains the specific configurations and files for each host running nixos. Here is what it looks like:&lt;/p>
&lt;pre tabindex="0">&lt;code>hosts/dalinar.adyxax.org/
├── configuration.nix
├── hardware-configuration.nix
└── wg0.key
&lt;/code>&lt;/pre>&lt;p>The &lt;code>confiuration.nix&lt;/code> currently looks like:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-nix" data-lang="nix">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span> &lt;span class="n">config&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="n">pkgs&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="o">...&lt;/span> &lt;span class="p">}:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">imports&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="sr">./hardware-configuration.nix&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="sr">../../apps/eventline/app.nix&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="sr">../../apps/gotosocial/app.nix&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="sr">../../apps/ngircd.nix&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="sr">../../apps/privatebin/app.nix&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="sr">../../apps/teamspeak.nix&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="sr">../../lib/boot-uefi.nix&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="sr">../../lib/common.nix&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">environment&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">etc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="s2">&amp;#34;wireguard/wg0.key&amp;#34;&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">source&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="sr">./wg0.key&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">networking&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">hostName&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;dalinar&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">wireguard&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">interfaces&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="s2">&amp;#34;wg0&amp;#34;&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">ips&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span> &lt;span class="s2">&amp;#34;10.1.2.11/32&amp;#34;&lt;/span> &lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">listenPort&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">342&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">peers&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span> &lt;span class="n">publicKey&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;7mij2whbm0qMx/D12zdMS5i9lt3ZSI3quNomTI+BSgk=&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">allowedIPs&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span> &lt;span class="s2">&amp;#34;10.1.2.14/32&amp;#34;&lt;/span> &lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">endpoint&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;lumapps-jde.adyxax.org:342&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">systemd&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">network&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">networks&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">wan&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">address&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span> &lt;span class="s2">&amp;#34;2603:c022:c002:8500:e2a4:f02e:43b0:c1d8/128&amp;#34;&lt;/span> &lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">matchConfig&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;eth0&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">networkConfig&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="n">DHCP&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;ipv4&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">IPv6AcceptRA&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">true&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The &lt;code>hardware-configuration.nix&lt;/code> is taken directly from the host machine after its installation.&lt;/p>
&lt;p>The content of &lt;code>wg0.key&lt;/code> is encrypted with &lt;code>git-crypt&lt;/code> too and generated with:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">wg genkey
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="lib">lib&lt;/h3>
&lt;p>The contents of the &lt;code>lib&lt;/code> directory are used either directly from the hosts configurations, or from the apps configurations:&lt;/p>
&lt;pre tabindex="0">&lt;code>lib
├── boot-bios.nix
├── boot-uefi.nix
├── common
│ ├── borg-client.nix
│ ├── check-mk-agent.nix
│ ├── dns.nix
│ ├── mosh.nix
│ ├── network.nix
│ ├── nix.nix
│ ├── openssh.nix
│ ├── tmux.conf
│ ├── tmux.nix
│ └── wireguard.nix
├── common.nix
├── julien.nix
├── luks.nix
├── nginx.nix
└── postgresql.nix
&lt;/code>&lt;/pre>&lt;p>All the files in &lt;code>lib/common/&lt;/code> are included in &lt;code>lib/common.nix&lt;/code>. These are split in self contained logical parts.&lt;/p>
&lt;h2 id="deploying-to-a-remote-host">Deploying to a remote host&lt;/h2>
&lt;p>I use the following &lt;code>GNUmakefile&lt;/code> to deploy from my workstation or from my eventline server to my hosts:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-make" data-lang="make">&lt;span class="line">&lt;span class="cl">&lt;span class="nv">SHELL&lt;/span> &lt;span class="o">:=&lt;/span> bash
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">.SHELLFLAGS&lt;/span> &lt;span class="o">:=&lt;/span> -eu -o pipefail -c
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nf">.ONESHELL&lt;/span>&lt;span class="o">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">.DEFAULT_GOAL&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nb">help&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nf">.DELETE_ON_ERROR&lt;/span>&lt;span class="o">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">MAKEFLAGS&lt;/span> &lt;span class="o">+=&lt;/span> --warn-undefined-variables
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">MAKEFLAGS&lt;/span> &lt;span class="o">+=&lt;/span> --no-builtin-rules
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">##### TASKS ####################################################################
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">&lt;/span>&lt;span class="nf">.PHONY&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="n">run&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nf">run&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="n">mandatory&lt;/span>-&lt;span class="n">host&lt;/span>-&lt;span class="n">param&lt;/span> &lt;span class="c">## make run host=&amp;lt;hostname&amp;gt;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">&lt;/span> nixos-rebuild switch --target-host root@&lt;span class="k">$(&lt;/span>host&lt;span class="k">)&lt;/span> -I nixos-config&lt;span class="o">=&lt;/span>hosts/&lt;span class="k">$(&lt;/span>host&lt;span class="k">)&lt;/span>/configuration.nix
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nf">.PHONY&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="n">update&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nf">update&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="c">## make update
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">&lt;/span> nix-channel --update
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">##### UTILS ####################################################################
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">&lt;/span>&lt;span class="nf">.PHONY&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="n">help&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nf">help&lt;/span>&lt;span class="o">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> @grep -E &lt;span class="s1">&amp;#39;^[a-zA-Z_-]+:.*?## .*$$&amp;#39;&lt;/span> &lt;span class="k">$(&lt;/span>MAKEFILE_LIST&lt;span class="k">)&lt;/span> &lt;span class="p">|&lt;/span> sort &lt;span class="p">|&lt;/span> awk &lt;span class="s1">&amp;#39;BEGIN {FS = &amp;#34;:.*?## &amp;#34;}; {printf &amp;#34;\033[36m%-30s\033[0m %s\n&amp;#34;, $$1, $$2}&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nf">.PHONY&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="n">mandatory&lt;/span>-&lt;span class="n">host&lt;/span>-&lt;span class="n">param&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nf">mandatory-host-param&lt;/span>&lt;span class="o">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">ifndef&lt;/span> &lt;span class="err">host&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nf">@echo &amp;#34;Error&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="n">host&lt;/span> &lt;span class="n">parameter&lt;/span> &lt;span class="n">is&lt;/span> &lt;span class="n">not&lt;/span> &lt;span class="n">set&lt;/span>&amp;#34;; &lt;span class="n">exit&lt;/span> 1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">else&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">ifeq&lt;/span> &lt;span class="err">(&lt;/span>&lt;span class="k">$(&lt;/span>&lt;span class="nv">wildcard&lt;/span> &lt;span class="nv">hosts&lt;/span>/&lt;span class="k">$(&lt;/span>&lt;span class="nv">host&lt;/span>&lt;span class="k">))&lt;/span>&lt;span class="err">,&lt;/span> &lt;span class="err">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nf">@echo &amp;#34;Error&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="n">host&lt;/span> &lt;span class="n">has&lt;/span> &lt;span class="n">no&lt;/span> &lt;span class="n">configuration&lt;/span> &lt;span class="n">in&lt;/span> ./&lt;span class="n">hosts&lt;/span>/&lt;span class="k">$(&lt;/span>&lt;span class="nv">host&lt;/span>&lt;span class="k">)&lt;/span>&amp;#34;; &lt;span class="n">exit&lt;/span> 1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">endif&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">endif&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This way I can &lt;code>make run host=dalinar.adyxax.org&lt;/code> to build locally dalinar&amp;rsquo;s configuration and deploy it remotely.&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>I am quite happy with the simplicity of this system for now. Everything works smoothly and tinkering with the configurations does not involve any magic.&lt;/p>
&lt;p>The one thing I really want to improve is the wireguard peers management which is a lot more involved than it needs to be. I will also explore using custom variables in order to simplify the hosts configurations.&lt;/p>
&lt;p>In the next articles I will detail the code behind some of these apps and lib files.&lt;/p></description></item><item><title>Recovering a nixos installation from a Linux rescue image</title><link>/blog/2023/11/13/recovering-a-nixos-installation-from-a-linux-rescue-image/</link><pubDate>2023-11-13</pubDate><guid>/blog/2023/11/13/recovering-a-nixos-installation-from-a-linux-rescue-image/</guid><description>&lt;h2 id="introduction">Introduction&lt;/h2>
&lt;p>This article explains how to chroot into a nixos system from a Linux rescue image. I recently had to do this while installing a nixos at ovh: I used an UEFI base image I prepared for oracle cloud instead of a legacy BIOS image. I could have just started the copy again using the right image, but it was an opportunity for learning and I took it.&lt;/p>
&lt;h2 id="chrooting-into-a-nixos-system">Chrooting into a nixos system&lt;/h2>
&lt;p>This works from any Linux system given you adjust the device paths. It will mount your nixos and chroot into it:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">mount /dev/sdb2 /mnt/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd&lt;/span> /mnt
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mount -R /dev dev
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mount -R /proc proc
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mount -R /sys sys
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mount /dev/sdb1 boot
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">chroot ./ /nix/var/nix/profiles/system/activate
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">chroot ./ /run/current-system/sw/bin/bash
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>A nixos system needs to have some runtime things populated under &lt;code>/run&lt;/code> in order for it to work correctly, that is the reason for the profile activation step.&lt;/p>
&lt;h2 id="generating-a-new-hardware-configurationnix">Generating a new hardware-configuration.nix&lt;/h2>
&lt;p>Upon installation, a &lt;code>/etc/nixos/hardware-configuration.nix&lt;/code> file is automatically created with specifics of your system. If you need to update it, know that its contents comes from the following command:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">nixos-generate-config --show-hardware-config
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="building-a-new-configuration">Building a new configuration&lt;/h2>
&lt;p>Nixos has a configuration build sandbox that will not work from the chroot. To disable it I had to temporarily set the following in &lt;code>/etc/nix/nix.conf&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">&lt;span class="nv">sandbox&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">false&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Do not forget to reactivate it later!&lt;/p>
&lt;p>Next you will need to have a working DNS to make any meaningful change to a nixos configuration, because it will almost certainly need to download some new derivation. Since the &lt;code>resolv.conf&lt;/code> is a symlink, you need to remove it before writing into it:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">rm /etc/resolv.conf
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">echo&lt;/span> &lt;span class="s1">&amp;#39;nameserver 1.1.1.1&amp;#39;&lt;/span> &amp;gt; /etc/resolv.conf
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>You should now be able to rebuild your system to apply your configuration fix:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">nixos-rebuild --install-bootloader boot
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>Nixos will not break often, and when it does you should be able to simply rollback from your boot loader menu. But if anything worse happens or if you are migrating a nixos installation to another chassis, or salving a hard drive&amp;hellip; now you know how to proceed!&lt;/p></description></item><item><title>Deploying a web application to nixos</title><link>/blog/2023/10/06/deploying-a-web-application-to-nixos/</link><pubDate>2023-10-06</pubDate><guid>/blog/2023/10/06/deploying-a-web-application-to-nixos/</guid><description>&lt;h2 id="introduction">Introduction&lt;/h2>
&lt;p>Gotosocial is a service that was running on one of my FreeBSD servers. Being a simple web application it is a good candidate to showcase what I like most about nixos and its declarative configurations!&lt;/p>
&lt;h2 id="a-bit-about-the-nix-language">A bit about the nix language&lt;/h2>
&lt;p>I recommend you read &lt;a href="https://nixos.wiki/wiki/Overview_of_the_Nix_Language">the official documentation&lt;/a>, but here is the minimal to get you started:&lt;/p>
&lt;ul>
&lt;li>every statement ends with a semicolon.&lt;/li>
&lt;li>The basic block structures are in fact Sets, meaning lists of key-value pairs where the keys are unique.&lt;/li>
&lt;li>The &lt;code>{...}: { }&lt;/code> that structure the whole file is a module definition. In the first curly braces are arguments.&lt;/li>
&lt;li>The &lt;code>let ...; in { }&lt;/code> construct is a way to define local variables for usage in the block following the &lt;code>in&lt;/code>.&lt;/li>
&lt;li>You can write strings with double quotes or double single quotes. This makes it so that you almost never need to escape characters! The double single quotes also allow to write multi line strings that will smartly strip the starting white spaces.&lt;/li>
&lt;li>file system paths are not strings!&lt;/li>
&lt;li>list elements are separated by white spaces.&lt;/li>
&lt;li>You can merge the keys in two sets with &lt;code>//&lt;/code>, often used in conjunction with &lt;code>let&lt;/code> local variables.&lt;/li>
&lt;li>imports work by merging sets and appending lists.&lt;/li>
&lt;/ul>
&lt;p>Statements can be grouped but nothing is mandatory. For example the following are completely equivalent:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-nix" data-lang="nix">&lt;span class="line">&lt;span class="cl">&lt;span class="n">environment&lt;/span> &lt;span class="err">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">etc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="s2">&amp;#34;gotosocial.yaml&amp;#34;&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">mode&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;0444&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">source&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="sr">./gotosocial.yaml&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">systemPackages&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span> &lt;span class="n">pkgs&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">sqlite&lt;/span> &lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-nix" data-lang="nix">&lt;span class="line">&lt;span class="cl">&lt;span class="n">environment&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">etc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="s2">&amp;#34;gotosocial.yaml&amp;#34;&lt;/span> &lt;span class="err">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">mode&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;0444&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">source&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="sr">./gotosocial.yaml&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">environment&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">systemPackages&lt;/span> &lt;span class="err">=&lt;/span> &lt;span class="p">[&lt;/span> &lt;span class="n">pkgs&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">sqlite&lt;/span> &lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-nix" data-lang="nix">&lt;span class="line">&lt;span class="cl">&lt;span class="n">environment&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">etc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="s2">&amp;#34;gotosocial.yaml&amp;#34;&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">mode&lt;/span> &lt;span class="err">=&lt;/span> &lt;span class="s2">&amp;#34;0444&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">environment&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">etc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="s2">&amp;#34;gotosocial.yaml&amp;#34;&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">source&lt;/span> &lt;span class="err">=&lt;/span> &lt;span class="sr">./gotosocial.yaml&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">environment&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">systemPackages&lt;/span> &lt;span class="err">=&lt;/span> &lt;span class="p">[&lt;/span> &lt;span class="n">pkgs&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">sqlite&lt;/span> &lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="configuration">Configuration&lt;/h2>
&lt;p>The following configuration does in order:&lt;/p>
&lt;ul>
&lt;li>Imports the Nginx.nix module defined in the next section.&lt;/li>
&lt;li>Deploys Gotosocial&amp;rsquo;s YAML configuration file.&lt;/li>
&lt;li>Installs &lt;code>sqlite&lt;/code>, necessary for our database backup preHook.&lt;/li>
&lt;li>Defines two Borg backup jobs: one for the SQLite database and one for the local storage.&lt;/li>
&lt;li>Configures an Nginx virtual host.&lt;/li>
&lt;li>Deploys the Gotosocial container.&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-nix" data-lang="nix">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span> &lt;span class="n">config&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="n">pkgs&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="o">...&lt;/span> &lt;span class="p">}:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">imports&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="sr">../lib/nginx.nix&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">environment&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">etc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="s2">&amp;#34;gotosocial.yaml&amp;#34;&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">mode&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;0444&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">source&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="sr">./gotosocial.yaml&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">systemPackages&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span> &lt;span class="n">pkgs&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">sqlite&lt;/span> &lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">services&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">borgbackup&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">jobs&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">let&lt;/span> &lt;span class="n">defaults&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">compression&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;auto,zstd&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">encryption&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">mode&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;none&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">environment&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">BORG_RSH&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;ssh -i /etc/borg.key&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">prune&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">keep&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">daily&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">14&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">weekly&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">4&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">monthly&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">repo&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;ssh://borg@kaladin.adyxax.org/srv/borg/dalinar.adyxax.org&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">startAt&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;daily&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span> &lt;span class="k">in&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;gotosocial-db&amp;#34;&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">defaults&lt;/span> &lt;span class="o">//&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">paths&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;/tmp/gotosocial-sqlite.db&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">postHook&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;rm -f /tmp/gotosocial-sqlite.db&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">preHook&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;&amp;#39;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"> rm -f /tmp/gotosocial-sqlite.db
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"> echo &amp;#39;VACUUM INTO &amp;#34;/tmp/gotosocial-sqlite.db&amp;#34;&amp;#39; | \
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"> /run/current-system/sw/bin/sqlite3 /srv/gotosocial/sqlite.db
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"> &amp;#39;&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;gotosocial-storage&amp;#34;&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">defaults&lt;/span> &lt;span class="o">//&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="n">paths&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;/srv/gotosocial/storage&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">nginx&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">virtualHosts&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="s2">&amp;#34;fedi.adyxax.org&amp;#34;&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">forceSSL&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">true&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">locations&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;/&amp;#34;&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">proxyPass&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;http://127.0.0.1:8082&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">proxyWebsockets&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">true&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">sslCertificate&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;/etc/nginx/adyxax.org.crt&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">sslCertificateKey&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;/etc/nginx/adyxax.org.key&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">virtualisation&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">oci-containers&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">containers&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">gotosocial&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">cmd&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span> &lt;span class="s2">&amp;#34;--config-path&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;/gotosocial.yaml&amp;#34;&lt;/span> &lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">image&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;superseriousbusiness/gotosocial:0.11.1&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">ports&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;127.0.0.1:8082:8080&amp;#34;&lt;/span>&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">volumes&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;/etc/gotosocial.yaml:/gotosocial.yaml:ro&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;/srv/gotosocial/:/gotosocial/storage/&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="nginx">Nginx&lt;/h2>
&lt;p>I will go into details in a next article about imports and how I organize my configurations, just know that in this case imports work intuitively. Here is the &lt;code>lib/nginx.nix&lt;/code> file defining common configuration for Nginx:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-nix" data-lang="nix">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span> &lt;span class="n">config&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="n">pkgs&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="o">...&lt;/span> &lt;span class="p">}:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">environment&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">etc&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">let&lt;/span> &lt;span class="n">permissions&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="n">mode&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;0400&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">uid&lt;/span>&lt;span class="o">=&lt;/span> &lt;span class="n">config&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">ids&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">uids&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">nginx&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">};&lt;/span> &lt;span class="k">in&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;nginx/adyxax.org.crt&amp;#34;&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">permissions&lt;/span> &lt;span class="o">//&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="n">source&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="sr">../../01-legacy/adyxax.org.crt&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;nginx/adyxax.org.key&amp;#34;&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">permissions&lt;/span> &lt;span class="o">//&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="n">source&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="sr">../../01-legacy/adyxax.org.key&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">networking&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">firewall&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">allowedTCPPorts&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span> &lt;span class="mi">80&lt;/span> &lt;span class="mi">443&lt;/span> &lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">services&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">nginx&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">clientMaxBodySize&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;40M&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">enable&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">true&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">enableReload&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">true&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">recommendedGzipSettings&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">true&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">recommendedOptimisation&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">true&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">recommendedProxySettings&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">true&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="deploying">Deploying&lt;/h2>
&lt;p>Being an existing service for me, I transferred gotosocial&amp;rsquo;s storage data and database using rsync. With that done, bringing the service back up was only a matter of migrating the DNS and running the now familiar:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">nixos-rebuild switch
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>I hope you find this way of declaratively configuring a whole operating system as elegant as I do. The nix configuration language is a bit rough, but I find it is not so hard to wrap your head around the basics. When it all clicks it is nice to know that you can reproduce this deployment anywhere just from this configuration!&lt;/p></description></item><item><title>Installing nixos on a vps</title><link>/blog/2023/10/04/installing-nixos-on-a-vps/</link><pubDate>2023-10-04</pubDate><guid>/blog/2023/10/04/installing-nixos-on-a-vps/</guid><description>&lt;h2 id="introduction">Introduction&lt;/h2>
&lt;p>Not many providers consider nixos as a first class citizen, you need a little know how to be able to set it up in a not so friendly environment. Nixos&amp;rsquo;s wiki has several procedures to achieve this but I found those either too complicated or not up to date. This article presents my prefered way to install an operating system somewhere it is not supported to do so, and it works for anything.&lt;/p>
&lt;h2 id="installation">Installation&lt;/h2>
&lt;h3 id="prepare-a-virtual-machine">Prepare a virtual machine&lt;/h3>
&lt;p>If you followed &lt;a href="/blog/2023/09/30/getting-started-with-nixos/">my last article&lt;/a>, you should have a nixos virtual machine ready to go. You just need to upload it somewhere. I chose kaladin.adyxax.org, another one of my machines, and to serve the machine over ssh. Alternatively you could use a web server or even socat/netcat if it strikes your fancy.&lt;/p>
&lt;h3 id="bootstrap-your-vps-or-compute-instance">Bootstrap your vps or compute instance&lt;/h3>
&lt;p>Install your vps or compute instance normally using a Linux distribution (or any of the BSD) that is supported by your provider. Connect to it as root.&lt;/p>
&lt;h3 id="remount-disk-partitions-as-read-only">Remount disk partitions as read only&lt;/h3>
&lt;p>We are going to remount the partitions as the running OS as read only. In order to do that, we are going to shutdown nearly everything! If at some point you lose access to your system, just force reboot it and try again. Our goal is for those commands to run without an error:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">swapoff -a
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mount -o remount,ro /boot
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mount -o remount,ro /
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>If there are other disk partitions mounted, those must be remounted read only as well. Check &lt;code>cat /proc/mounts&lt;/code> if you do not know what to look for.&lt;/p>
&lt;p>Be aware that selinux could block you. If that is the case, deactivate it, reboot and start over.&lt;/p>
&lt;p>On most Linux you can list running services using &lt;code>systemctl|grep running&lt;/code> and begin running &lt;code>systemctl stop&lt;/code> commands on almost anything, just remember to keep what your running session depends on:&lt;/p>
&lt;ul>
&lt;li>init&lt;/li>
&lt;li>session-XX&lt;/li>
&lt;li>user@0 (root) and any user@XX where XX is the uid you connected with&lt;/li>
&lt;/ul>
&lt;p>Everything else should be fair game, what you are looking for are processus that keep files opened for writing. Those can be identified with:&lt;/p>
&lt;ul>
&lt;li>&lt;code>lsof / | awk '$4 ~ /[0-9].*w/'&lt;/code>&lt;/li>
&lt;li>&lt;code>fuser -v -m /&lt;/code>&lt;/li>
&lt;li>&lt;code>ps aux&lt;/code>&lt;/li>
&lt;li>&lt;code>systemctl|grep running&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>Here is a list of what I shutdown on an oracle cloud compute before I could remount / read only:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">systemctl stop smartd
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">systemctl stop rpcbind
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">systemctl stop rpcbind.socket
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">systemctl stop systemd-journald-dev-log.socket
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">systemctl stop systemd-journald.socket
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">systemctl stop systemd-udevd-control.socket
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">systemctl stop systemd-udevd-kernel.socket
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">systemctl stop tuned.service
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">systemctl stop user@1000.service
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">systemctl stop user@989.service
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">systemctl stop rsyslog
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">systemctl stop oswatcher.service
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">systemctl stop oracle-cloud-agent.service
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">systemctl stop oracle-cloud-agent-updater.service
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">systemctl stop gssproxy.service
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">systemctl stop crond.service
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">systemctl stop chronyd.service
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">systemctl stop auditd.service
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">systemctl stop atd.service
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">systemctl stop auditd.service
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">systemctl stop sssd
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">systemctl stop sssd_bd
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">systemctl stop firewalld
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">systemctl stop auditd
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">systemctl stop iscsid
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">systemctl stop iscsid.socket
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">systemctl stop dbus.socket
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">systemctl stop dbus
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">systemctl stop systemd-udevd
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">systemctl stop sshd
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">systemctl stop libstoragemgmt.service
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">systemctl stop irqbalance.service
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">systemctl stop getty@tty1.service
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">systemctl stop serial-getty@ttyS0.service
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Remember, your success condition is to be able to run this without errors:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">swapoff -a
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mount -o remount,ro /boot
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mount -o remount,ro /
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>As soon as this is done and you only have &lt;code>ro&lt;/code> in &lt;code>cat /proc/mounts&lt;/code> for your disk partitions you can stop shutting down services.&lt;/p>
&lt;h3 id="copying-the-virtual-machine-you-prepared">Copying the virtual machine you prepared&lt;/h3>
&lt;p>When successful at remounting your partitions read only, then retrieve your virtual machine image. You will need to copy it directly to the disk, here is how I do it using ssh:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">ssh root@kaladin.adyxax.org &lt;span class="s2">&amp;#34;dd if=/nixos-uefi.raw&amp;#34;&lt;/span> &lt;span class="p">|&lt;/span> dd &lt;span class="nv">of&lt;/span>&lt;span class="o">=&lt;/span>/dev/sda
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="reboot-and-test">Reboot and test&lt;/h2>
&lt;p>Once the copy is complete, you will have to force reboot your machine. After a minute you should be able to ssh to it and get a nixos shell!&lt;/p>
&lt;p>You will need a virtual console or KVM of some sort to debug your image if something went wrong. All providers have this capability, you just have to find it in their webui.&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>I used this procedure successfully on ovh, hetzner, google cloud and on oracle cloud and I believe it should work anywhere. I used it for nixos, but also to install some Gentoo, OpenBSD or FreeBSD where those were not supported either.&lt;/p></description></item><item><title>Getting started with nixos</title><link>/blog/2023/09/30/getting-started-with-nixos/</link><pubDate>2023-09-30</pubDate><guid>/blog/2023/09/30/getting-started-with-nixos/</guid><description>&lt;h2 id="introduction">Introduction&lt;/h2>
&lt;p>After discovering nix I quickly jumped into nixos, the Linux distribution based on nix. It has been a few months now and I very much like nixos&amp;rsquo;s stability and reproducibility. Upgrades went smoothly each time and I migrated quite a few services to a nixos server.&lt;/p>
&lt;h2 id="installation">Installation&lt;/h2>
&lt;h3 id="virtual-machine-bootstrap">Virtual machine bootstrap&lt;/h3>
&lt;p>Installing nixos is really not hard, you quickly get to a basic setup you can completely understand thanks to its declarative nature. When I began tinkering with nixos, my goal was to install it on a vps for which I needed UEFI support, here is how I bootstrapped a virtual machine locally:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">qemu-img create -f raw nixos.raw 4G
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">qemu-system-x86_64 -drive &lt;span class="nv">file&lt;/span>&lt;span class="o">=&lt;/span>nixos.raw,format&lt;span class="o">=&lt;/span>raw,cache&lt;span class="o">=&lt;/span>writeback &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -cdrom Downloads/nixos-minimal-23.05.1994.af8279f65fe-x86_64-linux.iso &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -boot d -machine &lt;span class="nv">type&lt;/span>&lt;span class="o">=&lt;/span>q35,accel&lt;span class="o">=&lt;/span>kvm -cpu host -smp &lt;span class="m">2&lt;/span> -m &lt;span class="m">1024&lt;/span> -vnc :0 &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -device virtio-net,netdev&lt;span class="o">=&lt;/span>vmnic -netdev user,id&lt;span class="o">=&lt;/span>vmnic,hostfwd&lt;span class="o">=&lt;/span>tcp::10022-:22 &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -bios /usr/share/edk2-ovmf/OVMF_CODE.fd
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="partitioning">Partitioning&lt;/h3>
&lt;p>From there, I performed the following simple partitioning (just one big root partition):&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">parted /dev/sda -- mklabel gpt
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">parted /dev/sda -- mkpart ESP fat32 1MB 512MB
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">parted /dev/sda -- &lt;span class="nb">set&lt;/span> &lt;span class="m">1&lt;/span> esp on
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">parted /dev/sda -- mkpart primary 512MB 100%
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mkfs.fat -F &lt;span class="m">32&lt;/span> -n boot /dev/sda1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mkfs.ext4 -L nixos /dev/sda2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mount /dev/disk/by-label/nixos /mnt
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mkdir -p /mnt/boot
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mount /dev/disk/by-label/boot /mnt/boot
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="initial-configuration">Initial configuration&lt;/h3>
&lt;p>The initial configuration is generated with:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">nixos-generate-config --root /mnt
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This will generate a &lt;code>/mnt/etc/nixos/hardware-configuration.nix&lt;/code> with the specifics of your machine along with a basic &lt;code>/mnt/etc/nixos/configuration.nix&lt;/code> that I replaced with:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-nix" data-lang="nix">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span> &lt;span class="n">config&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="n">pkgs&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="o">...&lt;/span> &lt;span class="p">}:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">imports&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="sr">./hardware-configuration.nix&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">boot&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">kernelParams&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;console=ttyS0&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;console=tty1&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;libiscsi.debug_libiscsi_eh=1&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;nvme.shutdown_timeout=10&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">boot&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">loader&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">efi&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">canTouchEfiVariables&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">true&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">systemd-boot&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">enable&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">true&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">environment&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">systemPackages&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">with&lt;/span> &lt;span class="n">pkgs&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">curl&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">tmux&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">vim&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">networking&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">dhcpcd&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">enable&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">false&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">hostname&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;dalinar&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">nameservers&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span> &lt;span class="s2">&amp;#34;1.1.1.1&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;9.9.9.9&amp;#34;&lt;/span> &lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">firewall&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">allowedTCPPorts&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span> &lt;span class="mi">22&lt;/span> &lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">logRefusedConnections&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">false&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">logRefusedPackets&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">false&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">usePredictableInterfaceNames&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">false&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">nix&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">settings&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">auto-optimise-store&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">true&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">extraOptions&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;&amp;#39;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"> min-free = &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nb">toString&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="mi">1024&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="mi">1024&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="mi">1024&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s1">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"> max-free = &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nb">toString&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="mi">2048&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="mi">1024&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="mi">1024&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s1">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"> &amp;#39;&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">gc&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">automatic&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">true&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">dates&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;weekly&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">options&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;--delete-older-than 30d&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">security&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">doas&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">enable&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">true&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">sudo&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">enable&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">false&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">services&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">openssh&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">enable&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">true&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">settings&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">KbdInteractiveAuthentication&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">false&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">settings&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">PasswordAuthentication&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">false&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">resolved&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">enable&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">false&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">systemd&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">network&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">enable&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">true&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">timeZone&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;Europe/Paris&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">users&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">users&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">adyxax&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">description&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;Julien Dessaux&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">extraGroups&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span> &lt;span class="s2">&amp;#34;wheel&amp;#34;&lt;/span> &lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">hashedPassword&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;$y$j9T$Nne7Ad1nxNmluCKBzBG3//$h93j8xxfBUD98f/7nGQqXPeM3QdZatMbzZ0p/G2P/l1&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">home&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;/home/julien&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">isNormalUser&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">true&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">openssh&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">authorizedKeys&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">keys&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span> &lt;span class="s2">&amp;#34;ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILOJV391WFRYgCVA2plFB8W8sF9LfbzXZOrxqaOrrwco adyxax@yen&amp;#34;&lt;/span> &lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">root&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">hashedPassword&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;$y$j8F$ummLlZmPdS1KGxSnwH8CY.$bjvADB9IdfwzO6/2if5Sl9DeCmCRdasknq4IJEAuxyA&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">openssh&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">authorizedKeys&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">keys&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span> &lt;span class="s2">&amp;#34;ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILOJV391WFRYgCVA2plFB8W8sF9LfbzXZOrxqaOrrwco adyxax@yen&amp;#34;&lt;/span> &lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># This value determines the NixOS release from which the default&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># settings for stateful data, like file locations and database versions&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># on your system were taken. It&amp;#39;s perfectly fine and recommended to leave&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># this value at the release version of the first install of this system.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># Before changing this value read the documentation for this option&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># (e.g. man configuration.nix or on https://nixos.org/nixos/options.html).&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">system&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">stateVersion&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;23.05&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># Copy the NixOS configuration file and link it from the resulting system&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># (/run/current-system/configuration.nix). This is useful in case you&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># accidentally delete configuration.nix.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">system&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">copySystemConfiguration&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">true&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This will setup a system that in particular will use the systemd-bootd boot loader in lieu of grub and systemd-networkd instead of NetworkManager. Not much else is going on. The nix section slows builds a bit but greatly reduced disk space consumption.&lt;/p>
&lt;h3 id="installation-1">Installation&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">nixos-install --no-root-passwd
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="rebooting">Rebooting&lt;/h3>
&lt;p>In order to boot on the newlly installed system and not the installer, the virtual machine command needs to be changed, so shutdown your system with:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">halt -p
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>And start it with:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">qemu-system-x86_64 -drive &lt;span class="nv">file&lt;/span>&lt;span class="o">=&lt;/span>nixos.raw,format&lt;span class="o">=&lt;/span>raw,cache&lt;span class="o">=&lt;/span>writeback &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -boot c -machine &lt;span class="nv">type&lt;/span>&lt;span class="o">=&lt;/span>q35,accel&lt;span class="o">=&lt;/span>kvm -cpu host -smp &lt;span class="m">2&lt;/span> -m &lt;span class="m">1024&lt;/span> -vnc :0 &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -device virtio-net,netdev&lt;span class="o">=&lt;/span>vmnic -netdev user,id&lt;span class="o">=&lt;/span>vmnic,hostfwd&lt;span class="o">=&lt;/span>tcp::10022-:22 &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -bios /usr/share/edk2-ovmf/OVMF_CODE.fd
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="updating-the-configuration">Updating the configuration&lt;/h2>
&lt;p>If you change the configuration, you need to rebuild the system with:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">nixos-rebuild switch
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="upgrading">Upgrading&lt;/h2>
&lt;p>You can rebuild your system with the latest nixos packages using:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">nix-channel --update
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">nixos-rebuild switch
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>Installing and tinkering with nixos is quite fun! In the next articles I will explain how I organized my configurations to manage multiple servers, how to use a luks encrypted system and remotely unlock them after rebooting, and how to run the builds for small servers from a much more powerful machine.&lt;/p></description></item><item><title>Getting started with nix</title><link>/blog/2023/09/09/getting-started-with-nix/</link><pubDate>2023-09-09</pubDate><guid>/blog/2023/09/09/getting-started-with-nix/</guid><description>&lt;h2 id="introduction">Introduction&lt;/h2>
&lt;p>I have been using nix for a few months now. It is a modern package manager that focuses on reproducible builds and was a first step before using nixos, a linux distribution based around nix and its capabilities that I find intriguing. Being able to have a fully reproducible system from a declarative configuration is something I find enticing.&lt;/p>
&lt;h2 id="getting-started">Getting started&lt;/h2>
&lt;p>You can get started using nix on any linux distribution, even on macos or windows! You do not need to reinstall anything or boot another operating system: you can install nix and start taking advantage of it anytime anywhere.&lt;/p>
&lt;p>&lt;a href="https://nixos.org/download">The official documentation&lt;/a> (which you should refer to) mentions two alternatives: one which runs a daemon to allow for multiple users to use nix on the same system, and a simpler one without a running daemon which I chose to follow.&lt;/p>
&lt;p>I recommend you audit the installation script, it is always a good idea to do so (and in this case it is quite simple to read what it does), but here are the three installation steps:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">doas mkdir /nix
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">doas chown adyxax /nix
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sh &amp;lt;&lt;span class="o">(&lt;/span>curl -L https://nixos.org/nix/install&lt;span class="o">)&lt;/span> --no-daemon
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>If this completes without error, you now have nix installed and just need to activate it in your shell with:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">source&lt;/span> ~/.nix-profile/etc/profile.d/nix.sh
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>To make this persistent add it where relevant for your shell and distribution, it could be in &lt;code>~/.bashrc&lt;/code>, &lt;code>~/.profile&lt;/code>, &lt;code>~/.zshrc&lt;/code>, etc:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="o">[&lt;/span> -e &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nv">HOME&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">/.nix-profile/etc/profile.d/nix.sh&amp;#34;&lt;/span> &lt;span class="o">]&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">then&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">source&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nv">HOME&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">/.nix-profile/etc/profile.d/nix.sh&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">fi&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="using-nix">Using nix&lt;/h2>
&lt;h3 id="nix-channels">Nix channels&lt;/h3>
&lt;p>By default, your nix installation should use the unstable profile. That just means bleeding edge packages, but I like to be explicit when using bleeding edge stuff therefore I did:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">nix-channel --remove nixpkgs
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">nix-channel --add https://nixos.org/channels/nixos-23.05 nixpkgs
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">nix-channel --add https://nixos.org/channels/nixos-unstable nixpkgs-unstable
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">nix-channel --update
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>23.05 is the current stable release channel at the time of this writing. Please check the current one at the time of your reading and use that.&lt;/p>
&lt;p>Be careful not to change this version number mindlessly as it can affect anything stateful you install with nix. The most common problem you will encounter is about file locations that change with major database versions (for example postgresql14 and 15). Changing this 23.05 version would not migrate your data, so be careful that you can migrate or have migrated all the state from your nix packages which is affected by this kind of version changes. I will write a blog article about this when it happens to me.&lt;/p>
&lt;h3 id="searching-packages">Searching packages&lt;/h3>
&lt;p>The easiest and fastest way is through nixos&amp;rsquo;s website: &lt;a href="https://search.nixos.org/packages?channel=23.05">https://search.nixos.org/packages?channel=23.05&lt;/a>&lt;/p>
&lt;p>If you want to do it from the cli beware that it is a bit slow, particularly on the first run (maybe it is building some cache):&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">$ nix-env -qaP firefox &lt;span class="c1"># short for: nix-env --query --available --attr-path firefox&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">nixpkgs.firefox-esr-102 firefox-102.15.0esr
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">nixpkgs-unstable.firefox-esr-102 firefox-102.15.0esr
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">nixpkgs.firefox-esr firefox-115.2.0esr
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">nixpkgs.firefox-esr-wayland firefox-115.2.0esr
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">nixpkgs-unstable.firefox-esr firefox-115.2.0esr
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">nixpkgs-unstable.firefox-esr-wayland firefox-115.2.0esr
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">nixpkgs.firefox firefox-117.0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">nixpkgs.firefox-wayland firefox-117.0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">nixpkgs-unstable.firefox firefox-117.0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">nixpkgs-unstable.firefox-mobile firefox-117.0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">nixpkgs-unstable.firefox-wayland firefox-117.0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">nixpkgs.firefox-beta firefox-117.0b9
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">nixpkgs.firefox-devedition firefox-117.0b9
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">nixpkgs-unstable.firefox-beta firefox-117.0b9
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">nixpkgs-unstable.firefox-devedition firefox-117.0b9
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>As you can see, the nixpkgs stable channels does not lag behind unstable for most day to day things you would need updated, but it will for more system things or experimental software.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">$ nix-env -qaP gotosocial
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">nixpkgs-unstable.gotosocial gotosocial-0.11.0
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="installing-packages">Installing packages&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">nix-env -iA nixpkgs.emacs29 &lt;span class="c1"># short for: nix-env --install --attr nixpkgs.emacs29&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="listing-installed-packages">Listing installed packages&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">$ nix-env -qs &lt;span class="c1"># short for: nix-env --query --status&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">IPS emacs-29.1
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Note that the installed package name changed completely and no longer reference nixpkgs or nixpkgs-unstable! That comes from the notion of nix derivations which we will not get into in this article.&lt;/p>
&lt;h3 id="upgrading-packages">Upgrading packages&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">nix-channel --update
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">nix-env --upgrade
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="uninstalling-packages">Uninstalling packages&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">nix-env --uninstall emacs-29.1
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="maintaining-nix-itself">Maintaining nix itself&lt;/h2>
&lt;h3 id="updating-nix">Updating nix&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">nix-channel --update
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">nix-env --install --attr nixpkgs.nix nixpkgs.cacert
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="uninstalling-nix">Uninstalling nix&lt;/h3>
&lt;p>If at some point you want to stop using nix and uninstall it, simply run:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">rm -rf &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nv">HOME&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">/.nix-profile&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">doas rm -rf /nix
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>This article is a first overview of nix that can get you started, we did not get into the best parts yet: profile management, rolling back to a previous packages state, packaging software, building container images and of course nixos itself. So much material for future articles!&lt;/p>
&lt;p>I have been a happy Gentoo user for close to twenty years now and do not plan to switch anytime soon for many reasons, but it is nice to have another packages repository to play with.&lt;/p></description></item><item><title>Writing a terraform provider for eventline</title><link>/blog/2023/08/04/writing-a-terraform-provider-for-eventline/</link><pubDate>2023-08-04</pubDate><guid>/blog/2023/08/04/writing-a-terraform-provider-for-eventline/</guid><description>&lt;h2 id="introduction">Introduction&lt;/h2>
&lt;p>I have been using terraform to manage infrastructure both personnaly and at work for several years now and I know this tool quite well. I have been searching for an excuse to write a terraform provider for quite some time in order to dive deeper into terraform and I finally realised that I had just such excuse!&lt;/p>
&lt;p>I started using &lt;a href="https://www.exograd.com/products/eventline/">eventline&lt;/a> when it was released a year ago and have been very happy with it. Turns out I could benefit from a terraform provider to provision identities or jobs when deploying new hosts, so here I go!&lt;/p>
&lt;h2 id="writing-a-terraform-provider">Writing a terraform provider&lt;/h2>
&lt;h3 id="where-to-start">Where to start&lt;/h3>
&lt;p>The recommended way is to fork the &lt;a href="https://github.com/hashicorp/terraform-provider-scaffolding-framework">terraform provider scaffolding framework&lt;/a> repository from Hashicorp. This is what I did, but it came with some frustration. Hashicorp recently deprecated another way of developing terraform providers called SDKv2, thereform the big downside is that almost all the examples, blog posts or existing providers you would like to take inspiration from are all using the old sdk!&lt;/p>
&lt;p>Without good examples, you are left with reading the documentation (which I found a bit lacking) and reading the sources of hashicorp&amp;rsquo;s framework and libraries (which thanks to go&amp;rsquo;s &amp;ldquo;boringness&amp;rdquo; is surprisingly possible, even enjoyable).&lt;/p>
&lt;h3 id="the-project-name">The project name&lt;/h3>
&lt;p>I did not find it explicitely documented so here it is for you: you MUST name your provider&amp;rsquo;s repository &lt;code>terraform-provider-something&lt;/code>, otherwise the builtin CI from the framework repository will not work with some very cryptic errors!&lt;/p>
&lt;h3 id="terraform-types-wrapping">Terraform types wrapping&lt;/h3>
&lt;p>One thing that puzzled me a bit was how to make terraform&amp;rsquo;s schema types work with go types. When writing your datasources and resources, you define your types like this simple:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">type&lt;/span> &lt;span class="nx">ProjectResourceModel&lt;/span> &lt;span class="kd">struct&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Id&lt;/span> &lt;span class="nx">types&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">String&lt;/span> &lt;span class="s">`tfsdk:&amp;#34;id&amp;#34;`&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Name&lt;/span> &lt;span class="nx">types&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">String&lt;/span> &lt;span class="s">`tfsdk:&amp;#34;name&amp;#34;`&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This go type is associated with a schema function that will look like:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">r&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">ProjectResource&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">Schema&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ctx&lt;/span> &lt;span class="nx">context&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Context&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">req&lt;/span> &lt;span class="nx">resource&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">SchemaRequest&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">resp&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">resource&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">SchemaResponse&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">resp&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Schema&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">schema&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Schema&lt;/span>&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Attributes&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kd">map&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="nx">schema&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Attribute&lt;/span>&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;id&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">schema&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">StringAttribute&lt;/span>&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Computed&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">MarkdownDescription&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s">&amp;#34;Project Id&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">PlanModifiers&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="nx">planmodifier&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">String&lt;/span>&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">stringplanmodifier&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">UseStateForUnknown&lt;/span>&lt;span class="p">(),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">schema&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">StringAttribute&lt;/span>&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">MarkdownDescription&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s">&amp;#34;Project name&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Required&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">MarkdownDescription&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s">&amp;#34;Eventline project resource&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>To use this resource, the user of this terraform provider wlil provide a &lt;code>name&lt;/code> and will get back an &lt;code>id&lt;/code>. To use the name in your code, you will need to do:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">var&lt;/span> &lt;span class="nx">data&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">ProjectResourceModel&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">resp&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Diagnostics&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">req&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Plan&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ctx&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">data&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">...&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="nx">resp&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Diagnostics&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">HasError&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">name&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">data&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Name&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">ValueString&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="c1">//get the go string out of the terraform resource schema
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>To provision the Id:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">data&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Id&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">types&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">StringValue&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">id&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">// wraps the go string into the right type for terraform resource schema
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">resp&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Diagnostics&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">resp&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">State&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Set&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ctx&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">data&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">...&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="schema-with-nested-list-attributes">Schema with nested list attributes&lt;/h3>
&lt;p>The examples form hashicorp all reference list with simple types. If you want to better describe your resources and datasources, you will need to write your lists in this manner:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">d&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">ProjectsDataSource&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">Schema&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ctx&lt;/span> &lt;span class="nx">context&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Context&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">req&lt;/span> &lt;span class="nx">datasource&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">SchemaRequest&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">resp&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">datasource&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">SchemaResponse&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">resp&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Schema&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">schema&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Schema&lt;/span>&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Attributes&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kd">map&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="nx">schema&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Attribute&lt;/span>&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;elements&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">schema&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ListNestedAttribute&lt;/span>&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Computed&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">MarkdownDescription&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s">&amp;#34;The list of projects.&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">NestedObject&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">schema&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">NestedAttributeObject&lt;/span>&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Attributes&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kd">map&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="nx">schema&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Attribute&lt;/span>&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;id&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">schema&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">StringAttribute&lt;/span>&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Computed&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">MarkdownDescription&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s">&amp;#34;The identifier of the project.&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">schema&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">StringAttribute&lt;/span>&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Computed&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">MarkdownDescription&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s">&amp;#34;The name of the project.&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">MarkdownDescription&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s">&amp;#34;Use this data source to retrieve information about existing eventline projects.&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="testing-your-work-with-a-provider-override">Testing your work with a provider override&lt;/h3>
&lt;p>In order to develop comfortably your provider, you will need a &lt;code>~/.terraformrc&lt;/code> file that looks like the following:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-hcl" data-lang="hcl">&lt;span class="line">&lt;span class="cl">&lt;span class="n">plugin_cache_dir&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;$HOME/.terraform.d/plugin-cache&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">disable_checkpoint&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kt">true&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">provider_installation&lt;/span> {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">dev_overrides&lt;/span> {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n"> &amp;#34;adyxax/eventline&amp;#34;&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;/home/julien/.go/bin/&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }&lt;span class="c1">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"> # For all other providers, install them directly from their origin provider
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"> # registries as normal. If you omit this, Terraform will _only_ use
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"> # the dev_overrides block, and so no other providers will be available.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">direct&lt;/span> {}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Use the binary subfolder of your $GOPATH and this will work. When you &lt;code>go install&lt;/code> your provider, the resulting binary will get copied there and be picked up by terraform on each &lt;code>plan&lt;/code> or &lt;code>apply&lt;/code>. Yes: the neat thing is that you do not need to run &lt;code>init&lt;/code> constantly!&lt;/p>
&lt;h3 id="provider-documentation">Provider documentation&lt;/h3>
&lt;p>The provider&amp;rsquo;s documentation can be generated with &lt;code>go generate&lt;/code>. It will use the &lt;code>MarddownDescription&lt;/code> attributes you defined in your schema descriptions so make those good entries. As the name suggest, you can use multiline markdown so go crazy with it!&lt;/p>
&lt;p>Another piece to know about is the &lt;code>examples&lt;/code> folder in your repository. If you give it a structure like:&lt;/p>
&lt;pre tabindex="0">&lt;code>examples/
├── data-sources
│   ├── eventline_identities
│   │   └── data-source.tf
│   ├── eventline_jobs
│   │   └── data-source.tf
│   ├── eventline_project
│   │   └── data-source.tf
│   └── eventline_projects
│   └── data-source.tf
├── provider
│   └── provider.tf
├── README.md
└── resources
└── eventline_project
├── import.sh
└── resource.tf
&lt;/code>&lt;/pre>&lt;p>Then your objects documentation will get augmented with useful examples for the users of your provider.&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>Writing a terraform provider is a lot of fun, I recommend it! If you have a piece of software that you wish had a terraform provider, know that it is not that hard to make it a reality.&lt;/p>
&lt;p>Here is &lt;a href="https://git.adyxax.org/adyxax/terraform-provider-eventline/">the repository of my eventline provider&lt;/a> for reference and here is &lt;a href="https://registry.terraform.io/providers/adyxax/eventline/latest/docs">the terraform provider&amp;rsquo;s page&lt;/a>.&lt;/p></description></item><item><title>Space Traders</title><link>/blog/2023/07/08/space-traders/</link><pubDate>2023-07-08</pubDate><guid>/blog/2023/07/08/space-traders/</guid><description>&lt;h2 id="introduction">Introduction&lt;/h2>
&lt;p>A few weeks ago, a friend stumbled upon &lt;a href="https://spacetraders.io/">Space Traders&lt;/a>. He shared the link along with his enthusiasm knowing very well I would not resist its appeal.&lt;/p>
&lt;h2 id="the-game">The game&lt;/h2>
&lt;p>SpaceTraders is an API-based game where you acquire and manage a fleet of ships to explore, trade, and one day fight your way across the galaxy. It is not finished and very much in alpha state. There have been a few bugs but nothing major so far.&lt;/p>
&lt;p>You can use any programming language you want to query the API and control your ships, query market prices or shipyards stocks, explore systems, mine or survey asteroids. You run your code wherever you like, however you like.&lt;/p>
&lt;p>One of the challenges is that you are rate limited to 2 requests per seconds, with a 10 requests burst over 10 seconds. Because of that, any competitive agent will need to be efficient in the commands it sends and the strategy it chooses!&lt;/p>
&lt;h2 id="getting-started">Getting started&lt;/h2>
&lt;p>My recent experiences with Haskell made me itch to get started in this language, but I finally decided against it. I was at a level of proficiency where I know it would have been too ambitious a task. I would have just ended up tinkering with data types and abstractions instead of learning the API and experimenting with the game.&lt;/p>
&lt;p>Therefore I went with (vanilla) JavaScript. It is quite a nice language for prototyping despite its many pitfalls, and I quickly got an agent working its way through the first faction contract. This first contract is like a tutorial for the game and the documentation guides you through it. I refined my agent along the way and am proud to have something that can mine the requested good (selling anything else), then navigate and deliver goods. It loops like that until the contract is fulfilled.&lt;/p>
&lt;p>It might be premature optimisation but I am caching a maximum of information in an SQLite database in order to reduce the amount of API calls my code needs to make. I am taking advantage of SQLite&amp;rsquo;s JSON support to store the JSON data from the API calls, which is a lot easier than expressing all the information in SQL tables, columns and references. I add the necessary index on the JSON fields I query against.&lt;/p>
&lt;p>The network requests are all handled by a queue processor which relies on a priority queue. When the agent needs to make an API call, it places it along with a promise into the priority queue, choosing the right priority depending on the action needed. For example ships actions that will gain credits will take priority over exploration tasks, or market refresh tasks. Centralizing the network requests in this manner allows me to strictly respect the rate limits and not hammer needlessly the game&amp;rsquo;s servers.&lt;/p>
&lt;h2 id="going-further">Going further&lt;/h2>
&lt;p>I started adding more complex behaviors to my ships. For example, a navigation request will check if the ship is docker or not, and undock it if that is the case. Upon arrival it will attempt to refuel. Another example is a navigation request which will check the ship&amp;rsquo;s position for asteroids. If it is not a mining location, the ship will automatically navigate to where it can mine, and refuel if needed.&lt;/p>
&lt;p>With all this implemented, I should begin tackling exploration. My navigation code currently only works in a single system and I need to handle FTL jumps or warp drives depending on the destination.&lt;/p>
&lt;p>I also want to implement automatic ship purchasing depending on the current agent&amp;rsquo;s goals, but I feel limited by JavaScript&amp;rsquo;s dynamic nature when iterating on the code. I am tired of fighting with runtime error and exceptions, therefore I just started rewriting my agent in Haskell.&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>I learned a lot about async in JavaScript with this project! I encourage anyone with a bit of free time to give it a try, be it to learn a new language or improve in one you already know. My code is available &lt;a href="https://git.adyxax.org/adyxax/spacetraders/tree/">on my git server&lt;/a> if you want to have a look. Do not hesitate to reach me on mastodon &lt;a href="https://fedi.adyxax.org/@adyxax">@adyxax@adyxax.org&lt;/a> if you want to discuss space traders!&lt;/p></description></item><item><title>Advent of code 2020 in haskell</title><link>/blog/2023/06/22/advent-of-code-2020-in-haskell/</link><pubDate>2023-06-22</pubDate><guid>/blog/2023/06/22/advent-of-code-2020-in-haskell/</guid><description>&lt;h2 id="introduction">Introduction&lt;/h2>
&lt;p>I did the &lt;a href="https://adventofcode.com/2020/">advent of code 2020&lt;/a> in haskell, I had a great time! I did it following &lt;a href="/blog/2023/05/28/advent-of-code-2022-in-zig/">advent of code 2022 in zig&lt;/a>, while reading &lt;a href="/books/misc/haskell-programming-from-first-principles/">Haskell Programming From First Principles&lt;/a> a few months ago.&lt;/p>
&lt;h2 id="haskell-for-puzzles">Haskell for puzzles&lt;/h2>
&lt;h3 id="parsing">Parsing&lt;/h3>
&lt;p>I used megaparsec extensively, it felt like a cheat code to be able to process the input so easily! This holds especially true for day 4 where you need to parse something like:&lt;/p>
&lt;pre tabindex="0">&lt;code>ecl:gry pid:860033327 eyr:2020 hcl:#fffffd
byr:1937 iyr:2017 cid:147 hgt:183cm
iyr:2013 ecl:amb cid:350 eyr:2023 pid:028048884
hcl:#cfa07d byr:1929
hcl:#ae17e1 iyr:2013
eyr:2024
ecl:brn pid:760753108 byr:1931
hgt:179cm
hcl:#cfa07d eyr:2025 pid:166559648
iyr:2011 ecl:brn hgt:59in
&lt;/code>&lt;/pre>&lt;p>The keys can be in any order so you need to account for permutations. Furthermore, entries each have their own set of rules in order to be valid. For example a height needs to have a unit in cm on inches and be in a certain range, while colors need to start with a hash sign and be composed of 6 hexadecimal digits.&lt;/p>
&lt;p>All this could be done at parsing time, haskell made this almost easy: I kid you not!&lt;/p>
&lt;h3 id="the-type-system">The type system&lt;/h3>
&lt;p>I used and abused the type system in order to have straightforward algorithms where if it compile then it works. A very notable example comes from day 25 where I used the &lt;code>Data.Mod&lt;/code> library to have modulus integers enforced by the type system. That&amp;rsquo;s right, in haskell that is possible!&lt;/p>
&lt;h3 id="performance">Performance&lt;/h3>
&lt;p>Only one puzzle had me reach for optimizations in order to run in less than a second. All the others ran successfully with a simple &lt;code>runghc &amp;lt;solution&amp;gt;.hs&lt;/code>! For this slow one, I sped it up by reaching for:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">ghc --make -O3 first.hs &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="nb">time&lt;/span> ./first
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="memory">Memory&lt;/h3>
&lt;p>I had no memory problems and laziness was not an issue either. Haskell really is a fantastic language.&lt;/p>
&lt;h2 id="solution-templates">Solution Templates&lt;/h2>
&lt;h3 id="simple-parsing">Simple parsing&lt;/h3>
&lt;p>Not all days called for advanced parsing. Some just made me look for a concise way of doing things. Here is (spoiler alert) my solution for the first part of day 6 as an example:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-haskell" data-lang="haskell">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">-- requires cabal install --lib split Unique&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">module&lt;/span> &lt;span class="nn">Main&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">main&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="kr">where&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">import&lt;/span> &lt;span class="nn">Control.Monad&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">void&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nf">when&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">import&lt;/span> &lt;span class="nn">Data.List.Split&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">splitOn&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">import&lt;/span> &lt;span class="nn">Data.List.Unique&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">sortUniq&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">import&lt;/span> &lt;span class="nn">Data.Monoid&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">mconcat&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">import&lt;/span> &lt;span class="nn">System.Exit&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">die&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nf">exampleExpectedOutput&lt;/span> &lt;span class="ow">=&lt;/span> &lt;span class="mi">11&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nf">parseInput&lt;/span> &lt;span class="ow">::&lt;/span> &lt;span class="kt">String&lt;/span> &lt;span class="ow">-&amp;gt;&lt;/span> &lt;span class="kt">IO&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="kt">String&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nf">parseInput&lt;/span> &lt;span class="n">filename&lt;/span> &lt;span class="ow">=&lt;/span> &lt;span class="kr">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">input&lt;/span> &lt;span class="ow">&amp;lt;-&lt;/span> &lt;span class="n">readFile&lt;/span> &lt;span class="n">filename&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">return&lt;/span> &lt;span class="o">$&lt;/span> &lt;span class="n">map&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">sortUniq&lt;/span> &lt;span class="o">.&lt;/span> &lt;span class="n">mconcat&lt;/span> &lt;span class="o">.&lt;/span> &lt;span class="n">lines&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">$&lt;/span> &lt;span class="n">splitOn&lt;/span> &lt;span class="s">&amp;#34;&lt;/span>&lt;span class="se">\n\n&lt;/span>&lt;span class="s">&amp;#34;&lt;/span> &lt;span class="n">input&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nf">compute&lt;/span> &lt;span class="ow">::&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="kt">String&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="ow">-&amp;gt;&lt;/span> &lt;span class="kt">Int&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nf">compute&lt;/span> &lt;span class="ow">=&lt;/span> &lt;span class="n">sum&lt;/span> &lt;span class="o">.&lt;/span> &lt;span class="n">map&lt;/span> &lt;span class="n">length&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nf">main&lt;/span> &lt;span class="ow">::&lt;/span> &lt;span class="kt">IO&lt;/span> &lt;span class="nb">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nf">main&lt;/span> &lt;span class="ow">=&lt;/span> &lt;span class="kr">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">example&lt;/span> &lt;span class="ow">&amp;lt;-&lt;/span> &lt;span class="n">parseInput&lt;/span> &lt;span class="s">&amp;#34;example&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">let&lt;/span> &lt;span class="n">exampleOutput&lt;/span> &lt;span class="ow">=&lt;/span> &lt;span class="n">compute&lt;/span> &lt;span class="n">example&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">when&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">exampleOutput&lt;/span> &lt;span class="o">/=&lt;/span> &lt;span class="n">exampleExpectedOutput&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">die&lt;/span> &lt;span class="o">$&lt;/span> &lt;span class="s">&amp;#34;example failed: got &amp;#34;&lt;/span> &lt;span class="o">++&lt;/span> &lt;span class="n">show&lt;/span> &lt;span class="n">exampleOutput&lt;/span> &lt;span class="o">++&lt;/span> &lt;span class="s">&amp;#34; instead of &amp;#34;&lt;/span> &lt;span class="o">++&lt;/span> &lt;span class="n">show&lt;/span> &lt;span class="n">exampleExpectedOutput&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">input&lt;/span> &lt;span class="ow">&amp;lt;-&lt;/span> &lt;span class="n">parseInput&lt;/span> &lt;span class="s">&amp;#34;input&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">print&lt;/span> &lt;span class="o">$&lt;/span> &lt;span class="n">compute&lt;/span> &lt;span class="n">input&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="advanced-parsing">Advanced parsing&lt;/h3>
&lt;p>Here is (spoiler alert) my solution for the first part of day 24 as an example:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-haskell" data-lang="haskell">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">-- requires cabal install --lib megaparsec parser-combinators&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">module&lt;/span> &lt;span class="nn">Main&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">main&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="kr">where&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">import&lt;/span> &lt;span class="nn">Control.Monad&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">void&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nf">when&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">import&lt;/span> &lt;span class="nn">Data.List&lt;/span> &lt;span class="n">qualified&lt;/span> &lt;span class="n">as&lt;/span> &lt;span class="kt">L&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">import&lt;/span> &lt;span class="nn">Data.Map&lt;/span> &lt;span class="n">qualified&lt;/span> &lt;span class="n">as&lt;/span> &lt;span class="kt">M&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">import&lt;/span> &lt;span class="nn">Data.Maybe&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">fromJust&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">import&lt;/span> &lt;span class="nn">Data.Set&lt;/span> &lt;span class="n">qualified&lt;/span> &lt;span class="n">as&lt;/span> &lt;span class="kt">S&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">import&lt;/span> &lt;span class="nn">Data.Void&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">Void&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">import&lt;/span> &lt;span class="nn">Text.Megaparsec&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">import&lt;/span> &lt;span class="nn">Text.Megaparsec.Char&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">import&lt;/span> &lt;span class="nn">System.Exit&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">die&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nf">exampleExpectedOutput&lt;/span> &lt;span class="ow">=&lt;/span> &lt;span class="mi">10&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">data&lt;/span> &lt;span class="kt">Direction&lt;/span> &lt;span class="ow">=&lt;/span> &lt;span class="kt">E&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="kt">W&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="kt">NE&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="kt">NW&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="kt">SE&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="kt">SW&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">type&lt;/span> &lt;span class="kt">Directions&lt;/span> &lt;span class="ow">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="kt">Direction&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">type&lt;/span> &lt;span class="kt">Coordinates&lt;/span> &lt;span class="ow">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">Int&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">Int&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">Int&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">type&lt;/span> &lt;span class="kt">Floor&lt;/span> &lt;span class="ow">=&lt;/span> &lt;span class="kt">M&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="kt">Map&lt;/span> &lt;span class="kt">Coordinates&lt;/span> &lt;span class="kt">Bool&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">type&lt;/span> &lt;span class="kt">Input&lt;/span> &lt;span class="ow">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="kt">Directions&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">type&lt;/span> &lt;span class="kt">Parser&lt;/span> &lt;span class="ow">=&lt;/span> &lt;span class="kt">Parsec&lt;/span> &lt;span class="kt">Void&lt;/span> &lt;span class="kt">String&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nf">parseDirection&lt;/span> &lt;span class="ow">::&lt;/span> &lt;span class="kt">Parser&lt;/span> &lt;span class="kt">Direction&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nf">parseDirection&lt;/span> &lt;span class="ow">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">string&lt;/span> &lt;span class="s">&amp;#34;se&amp;#34;&lt;/span> &lt;span class="o">*&amp;gt;&lt;/span> &lt;span class="n">return&lt;/span> &lt;span class="kt">SE&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">&amp;lt;|&amp;gt;&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">string&lt;/span> &lt;span class="s">&amp;#34;sw&amp;#34;&lt;/span> &lt;span class="o">*&amp;gt;&lt;/span> &lt;span class="n">return&lt;/span> &lt;span class="kt">SW&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">&amp;lt;|&amp;gt;&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">string&lt;/span> &lt;span class="s">&amp;#34;ne&amp;#34;&lt;/span> &lt;span class="o">*&amp;gt;&lt;/span> &lt;span class="n">return&lt;/span> &lt;span class="kt">NE&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">&amp;lt;|&amp;gt;&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">string&lt;/span> &lt;span class="s">&amp;#34;nw&amp;#34;&lt;/span> &lt;span class="o">*&amp;gt;&lt;/span> &lt;span class="n">return&lt;/span> &lt;span class="kt">NW&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">&amp;lt;|&amp;gt;&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">char&lt;/span> &lt;span class="sc">&amp;#39;e&amp;#39;&lt;/span> &lt;span class="o">*&amp;gt;&lt;/span> &lt;span class="n">return&lt;/span> &lt;span class="kt">E&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">&amp;lt;|&amp;gt;&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">char&lt;/span> &lt;span class="sc">&amp;#39;w&amp;#39;&lt;/span> &lt;span class="o">*&amp;gt;&lt;/span> &lt;span class="n">return&lt;/span> &lt;span class="kt">W&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nf">parseInput&amp;#39;&lt;/span> &lt;span class="ow">::&lt;/span> &lt;span class="kt">Parser&lt;/span> &lt;span class="kt">Input&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nf">parseInput&amp;#39;&lt;/span> &lt;span class="ow">=&lt;/span> &lt;span class="n">some&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">some&lt;/span> &lt;span class="n">parseDirection&lt;/span> &lt;span class="o">&amp;lt;*&lt;/span> &lt;span class="n">optional&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">char&lt;/span> &lt;span class="sc">&amp;#39;&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="sc">&amp;#39;&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="o">&amp;lt;*&lt;/span> &lt;span class="n">eof&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nf">parseInput&lt;/span> &lt;span class="ow">::&lt;/span> &lt;span class="kt">String&lt;/span> &lt;span class="ow">-&amp;gt;&lt;/span> &lt;span class="kt">IO&lt;/span> &lt;span class="kt">Input&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nf">parseInput&lt;/span> &lt;span class="n">filename&lt;/span> &lt;span class="ow">=&lt;/span> &lt;span class="kr">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">input&lt;/span> &lt;span class="ow">&amp;lt;-&lt;/span> &lt;span class="n">readFile&lt;/span> &lt;span class="n">filename&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">case&lt;/span> &lt;span class="n">runParser&lt;/span> &lt;span class="n">parseInput&amp;#39;&lt;/span> &lt;span class="n">filename&lt;/span> &lt;span class="n">input&lt;/span> &lt;span class="kr">of&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">Left&lt;/span> &lt;span class="n">bundle&lt;/span> &lt;span class="ow">-&amp;gt;&lt;/span> &lt;span class="n">die&lt;/span> &lt;span class="o">$&lt;/span> &lt;span class="n">errorBundlePretty&lt;/span> &lt;span class="n">bundle&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">Right&lt;/span> &lt;span class="n">input&amp;#39;&lt;/span> &lt;span class="ow">-&amp;gt;&lt;/span> &lt;span class="n">return&lt;/span> &lt;span class="n">input&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nf">compute&lt;/span> &lt;span class="ow">::&lt;/span> &lt;span class="kt">Input&lt;/span> &lt;span class="ow">-&amp;gt;&lt;/span> &lt;span class="kt">Int&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nf">compute&lt;/span> &lt;span class="n">input&lt;/span> &lt;span class="ow">=&lt;/span> &lt;span class="kt">M&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">size&lt;/span> &lt;span class="o">.&lt;/span> &lt;span class="kt">M&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">filter&lt;/span> &lt;span class="n">id&lt;/span> &lt;span class="o">$&lt;/span> &lt;span class="kt">L&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">foldl&amp;#39;&lt;/span> &lt;span class="n">compute&amp;#39;&lt;/span> &lt;span class="kt">M&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">empty&lt;/span> &lt;span class="n">input&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">where&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">compute&amp;#39;&lt;/span> &lt;span class="ow">::&lt;/span> &lt;span class="kt">Floor&lt;/span> &lt;span class="ow">-&amp;gt;&lt;/span> &lt;span class="kt">Directions&lt;/span> &lt;span class="ow">-&amp;gt;&lt;/span> &lt;span class="kt">Floor&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">compute&amp;#39;&lt;/span> &lt;span class="n">floor&lt;/span> &lt;span class="n">directions&lt;/span> &lt;span class="ow">=&lt;/span> &lt;span class="kr">case&lt;/span> &lt;span class="kt">M&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">lookup&lt;/span> &lt;span class="n">destination&lt;/span> &lt;span class="n">floor&lt;/span> &lt;span class="kr">of&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">Just&lt;/span> &lt;span class="n">f&lt;/span> &lt;span class="ow">-&amp;gt;&lt;/span> &lt;span class="kt">M&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">insert&lt;/span> &lt;span class="n">destination&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">not&lt;/span> &lt;span class="n">f&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="n">floor&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">Nothing&lt;/span> &lt;span class="ow">-&amp;gt;&lt;/span> &lt;span class="kt">M&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">insert&lt;/span> &lt;span class="n">destination&lt;/span> &lt;span class="kt">True&lt;/span> &lt;span class="n">floor&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">where&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">destination&lt;/span> &lt;span class="ow">::&lt;/span> &lt;span class="kt">Coordinates&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">destination&lt;/span> &lt;span class="ow">=&lt;/span> &lt;span class="kt">L&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">foldl&amp;#39;&lt;/span> &lt;span class="n">run&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="n">directions&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">run&lt;/span> &lt;span class="ow">::&lt;/span> &lt;span class="kt">Coordinates&lt;/span> &lt;span class="ow">-&amp;gt;&lt;/span> &lt;span class="kt">Direction&lt;/span> &lt;span class="ow">-&amp;gt;&lt;/span> &lt;span class="kt">Coordinates&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">run&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">y&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">z&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="kt">E&lt;/span> &lt;span class="ow">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">y&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">z&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">run&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">y&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">z&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="kt">W&lt;/span> &lt;span class="ow">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">y&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">z&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">run&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">y&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">z&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="kt">NE&lt;/span> &lt;span class="ow">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">y&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">z&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">run&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">y&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">z&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="kt">SW&lt;/span> &lt;span class="ow">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">y&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">z&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">run&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">y&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">z&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="kt">NW&lt;/span> &lt;span class="ow">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">y&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">z&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">run&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">y&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">z&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="kt">SE&lt;/span> &lt;span class="ow">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">y&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">z&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nf">main&lt;/span> &lt;span class="ow">::&lt;/span> &lt;span class="kt">IO&lt;/span> &lt;span class="nb">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nf">main&lt;/span> &lt;span class="ow">=&lt;/span> &lt;span class="kr">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">example&lt;/span> &lt;span class="ow">&amp;lt;-&lt;/span> &lt;span class="n">parseInput&lt;/span> &lt;span class="s">&amp;#34;example&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">let&lt;/span> &lt;span class="n">exampleOutput&lt;/span> &lt;span class="ow">=&lt;/span> &lt;span class="n">compute&lt;/span> &lt;span class="n">example&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">when&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">exampleOutput&lt;/span> &lt;span class="o">/=&lt;/span> &lt;span class="n">exampleExpectedOutput&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">die&lt;/span> &lt;span class="o">$&lt;/span> &lt;span class="s">&amp;#34;example failed: got &amp;#34;&lt;/span> &lt;span class="o">++&lt;/span> &lt;span class="n">show&lt;/span> &lt;span class="n">exampleOutput&lt;/span> &lt;span class="o">++&lt;/span> &lt;span class="s">&amp;#34; instead of &amp;#34;&lt;/span> &lt;span class="o">++&lt;/span> &lt;span class="n">show&lt;/span> &lt;span class="n">exampleExpectedOutput&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">input&lt;/span> &lt;span class="ow">&amp;lt;-&lt;/span> &lt;span class="n">parseInput&lt;/span> &lt;span class="s">&amp;#34;input&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">print&lt;/span> &lt;span class="o">$&lt;/span> &lt;span class="n">compute&lt;/span> &lt;span class="n">input&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>Learning haskell is worthwhile, it is really a great language with so many qualities. Puzzle solving is a use case where it shines so bright, thanks to its excellent parsing capabilities and its incredible type system.&lt;/p>
&lt;p>A great thing that should speak of haskell&amp;rsquo;s qualities is that it is the first year of advent of code that I completed all 25 days. I should revisit the years 2021 and 2022 that I did with golang and zig respectively and maybe finish those!&lt;/p></description></item></channel></rss>