<rss version="2.0">
  <channel>
    <title>NULCODE</title>
    <link>http://code.nulmail.com</link>
    <description>NULCODE - Blog</description>
    <generator>Zine -- https://zine-ssg.io</generator>
    <language>en-US</language>
    <lastBuildDate>Mon, 26 Oct 2026 21:25:12 +0000</lastBuildDate>
    
      <item>
        <title>RockPro64 LEDs</title>
        <description>&lt;h1&gt;Disclaimer&lt;/h1&gt;&lt;p&gt;This blog post is written based on a session I had with Google Gemini, googling for “RockPro64 toggle GPIO in U-Boot”.&lt;/p&gt;&lt;p&gt;Of course, nearly like always, the register name, page or chapter, were nowhere to be found in linked documentation, great!&lt;/p&gt;&lt;p&gt;The conversation turned to hell, where it was super hard to not ragequit or throw insults at it. Classical, I know…&lt;/p&gt;&lt;p&gt;As the topic might be more interesting than “I’m too stupid to browse the internet nowadays” which could also be a topic itself, I decided to make a blog post out of it.&lt;/p&gt;&lt;p&gt;So, if you’re in YOLO mode, just type this in U-Boot, this is roughly what the fantastic LLM told me to do, what could go wrong?&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;mw.l 0xff730004 0x00000012 &lt;br&gt;mw.l 0xff730000 0x00000012&lt;/p&gt;&lt;/blockquote&gt;&lt;h1&gt;The hardware world&lt;/h1&gt;&lt;p&gt;I’m using a &lt;a href=&quot;https://pine64.org/devices/rockpro64/&quot;&gt;RockPro64&lt;/a&gt; from Pine64 as a server (the famous one with the &lt;a href=&quot;http://code.nulmail.com/blog/raid/&quot;&gt;RAID issue&lt;/a&gt; I’m still fighting with).&lt;/p&gt;&lt;p&gt;I’m not sponsored; a friend of mine bought the board and didn’t use it anymore.&lt;/p&gt;&lt;p&gt;So, the RockPro64 is in its case, the white LED barely visible, and as I cracked down and needed to open the board for further diagnostics anyway, let’s add some LEDs to it.&lt;/p&gt;&lt;p&gt;After consulting the &lt;a href=&quot;https://pine64.org/documentation/ROCKPro64/Board/GPIOs/&quot;&gt;pins mapping table&lt;/a&gt;, let’s go for pins 15 and 16: mapped to GPIO Chip 1, Lines 1 and 4. Why these? Because GPIO1_A1 and GPIO_A4 sound good.&lt;/p&gt;&lt;p&gt;&lt;figure&gt;&lt;img src=&quot;http://code.nulmail.com/blog/rockpro64_leds/setup_rockpro64_leds.png&quot; alt=&quot;Picture of the RockPro64 wiring the pins 15 and 16 to LEDs (with resistors and ground)&quot;&gt;
&lt;figcaption&gt;Picture of the setup&lt;/figcaption&gt;&lt;/figure&gt;&lt;/p&gt;&lt;p&gt;Side notes:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;Always use resistors with LEDs (220 Ohm is my goto value, yes it’s a topic you can spend time with, but avoid wasting time debugging with burnt LEDs).&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Stay away from the kind of cheap bread boards you see on the pictures, I spend ~30 minutes of debugging why the setup didn’t work, just because some columns of the breadboard have shorts of faulty contacts. Great!&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Don’t worry about the hardware details, the only relevant stuff to follow here:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;RockPro64: the System on Chip (consider this to be a CPU if you don’t know this term, that’s fine) is a &lt;strong&gt;Rockchip RK3399&lt;/strong&gt;.&lt;/li&gt;&lt;li&gt;We’re using &lt;strong&gt;CPU Pins&lt;/strong&gt; GPIO1_A1 and GPIO1_A4 as GPIOs.&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;Finding documentation&lt;/h2&gt;&lt;p&gt;So you want to find the reference manual of the CPU you have on your desk, beware and welcome to the fantastic hardware world…&lt;/p&gt;&lt;p&gt;If you want to play a game, go to &lt;a href=&quot;http://www.rock-chips.com&quot;&gt;rock-chips.com&lt;/a&gt; and try to find the reference manual of the RK3399 CPU.&lt;/p&gt;&lt;p&gt;Hint: we’re talking about one or more heavy PDFs, in the ballpark of thousands of pages for such a chip.&lt;/p&gt;&lt;p&gt;The English button is on the top-right corner, don’t thank me: you won’t find any info in English either.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://web.archive.org/web/20261012221034/rock-chips.com&quot;&gt;The wayback machine link just in case&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Did you find something else than the marketing &lt;a href=&quot;https://web.archive.org/web/20260503124555/https://opensource.rock-chips.com/images/d/d7/Rockchip_RK3399_Datasheet_V2.1-20200323.pdf&quot;&gt;datasheet of ~75 pages&lt;/a&gt;?&lt;/p&gt;&lt;p&gt;I tend to use the great &lt;a href=&quot;https://web.archive.org&quot;&gt;wayback machine&lt;/a&gt; to link stuff (disclaimer: I’m financially supporting it). In this specific case it’s mandatory: the datasheet is officially gone.&lt;/p&gt;&lt;p&gt;And the results of the game… Whaaat, you gave up? Ok, me too.&lt;/p&gt;&lt;p&gt;&lt;figure&gt;&lt;img src=&quot;http://code.nulmail.com/blog/rockpro64_leds/rock_chips_no_info_annotated.png&quot; alt=&quot;Home page of rock-chips.com annotated featuring HTTP, dead links and a career link&quot;&gt;
&lt;figcaption&gt;If you didn’t want to play the crappy game, here’s what you missed&lt;/figcaption&gt;&lt;/figure&gt;&lt;/p&gt;&lt;p&gt;Fun fact: I made a typo writing this article and &lt;a href=&quot;https://rock-chip.com/&quot;&gt;discovered that rock-chip without ‘s’ also exists and references Rockchip stuff&lt;/a&gt; fair enough.&lt;/p&gt;&lt;p&gt;Here are the links to the confidential reference manual &lt;a href=&quot;https://www.scs.stanford.edu/~zyedidia/docs/rockchip/rk3399_part1.pdf&quot;&gt;part 1&lt;/a&gt; and &lt;a href=&quot;https://www.scs.stanford.edu/~zyedidia/docs/rockchip/rk3399_part2.pdf&quot;&gt;part 2&lt;/a&gt;… from &lt;a href=&quot;https://www.scs.stanford.edu/&quot;&gt;the Stanford Secure Computer Systems group&lt;/a&gt;; &lt;a href=&quot;https://web.archive.org/web/*/https://www.scs.stanford.edu/~zyedidia/docs/*&quot;&gt;Archived FTP&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Kudos to Stanford and a cool discovery for me: &lt;a href=&quot;https://zyedidia.github.io/&quot;&gt;Zachary Yedidia&lt;/a&gt; who studied there and made them host the refman for us. Definitely prefering linking a low-level hard-core hacker (&lt;a href=&quot;https://zyedidia.github.io/blog/posts/4-incremental-d-knit/&quot;&gt;which also had fun with incremental compilation, in D, not in Zig&lt;/a&gt;) than a random hardware company making its documentation as accessible as usual in this domain.&lt;/p&gt;&lt;p&gt;Yes, this document is probably outdated with a shit ton of erratas barely documented, no I don’t care (plus looked for this documented intensively twice, once really searching for it, another time writing this post wanted to make sure I didn’t miss it).&lt;/p&gt;&lt;p&gt;Here I just want to set a GPIO, so fine I’ll take what I find. If I were to pick a board seriously or evaluate hardware to base something long-term-ish, this would be a red flag.&lt;/p&gt;&lt;p&gt;As this document is officially (still?) confidential, it’s understandable that Pine64 does not host it, but this is still a pain point.&lt;/p&gt;&lt;h2&gt;RTFM&lt;/h2&gt;&lt;p&gt;So, after raging about why the fuck is it so hard to find a document in the hardware world, how do we proceed?&lt;/p&gt;&lt;p&gt;Remember that we used the pins of the RockPro64 mapped to the CPU to GPIO1_A1 and GPIO1_A4. It means we need to find at which address are these pins of the CPU mapped.&lt;/p&gt;&lt;p&gt;Meta: in this kind of overwhelming documents, the stuff working for me: scroll through the table of content and always jump back to it. Do &lt;strong&gt;not&lt;/strong&gt; search in the document yet, taking the risk to end up page 666 and seeing your PDF viewer freezing loading the thousands next results without a way back. Don’t rely on me to advertise LLMs, but generally uploading a few chapters and let them process these looking for specific information works fairly well whereas they just explode if you somehow manage to upload the full PDF.&lt;/p&gt;&lt;p&gt;So, let’s go: I went for the “Part 1” because after opening both, I didn’t see a clear explanation of which one if for what (probably if you register on their website as a manufacturer promising to buy million of chips you get a better explanation of why the reference manual is split in two parts, but not today).&lt;/p&gt;&lt;h3&gt;GPIO Group 1&lt;/h3&gt;&lt;p&gt;&lt;figure&gt;&lt;img src=&quot;http://code.nulmail.com/blog/rockpro64_leds/table_of_content_header.png&quot; alt=&quot;Reference manual table of content header showing the &apos;Address Mapping&apos; section&quot;&gt;
&lt;figcaption&gt;Hey, we’re lucky this time, the memory mapping is the very first chapter.&lt;/figcaption&gt;&lt;/figure&gt;&lt;/p&gt;&lt;p&gt;&lt;figure&gt;&lt;img src=&quot;http://code.nulmail.com/blog/rockpro64_leds/refman_memory_map_gpio1.png&quot; alt=&quot;Reference manual &apos;Address Mapping&apos; section with a table showing the GPIO1 group at address 0xff73_0000&quot;&gt;
&lt;figcaption&gt;We’re greet by the memory mapping, happily showing us where the GPIOs are mapped (if only any info could be found that easily…).&lt;/figcaption&gt;&lt;/figure&gt;&lt;/p&gt;&lt;p&gt;The GPIO1 group is mapped at address &lt;code&gt;$FF73_0000&lt;/code&gt; let’s keep this in mind.&lt;/p&gt;&lt;h3&gt;GPIO lines 1 and 4&lt;/h3&gt;&lt;p&gt;Now, we need to find where the lines 1 and 4 (where we plugged the LEDs) in the GPIO group are mapped.&lt;/p&gt;&lt;p&gt;As everything is well done, you can click on the GPIO1 entry of the table to jump to the relevant page. Nope, just kidding, stop using your Windows tool called a mouse, there is of course, no link here, so go back to the table of content.&lt;/p&gt;&lt;p&gt;&lt;figure&gt;&lt;img src=&quot;http://code.nulmail.com/blog/rockpro64_leds/table_of_content_gpio_chapter.png&quot; alt=&quot;Reference manual table of content showing the &apos;GPIO&apos; section&quot;&gt;
&lt;figcaption&gt;After some scrolling in the table of content, the GPIOs chapter.&lt;/figcaption&gt;&lt;/figure&gt;&lt;/p&gt;&lt;p&gt;Let’s go directly to the registers section of the GPIO chapter.&lt;/p&gt;&lt;p&gt;&lt;figure&gt;&lt;img src=&quot;http://code.nulmail.com/blog/rockpro64_leds/refman_gpio_registers.png&quot; alt=&quot;Reference manual GPIO registers GPIO_SWPORTA_DR and GPIO_SWPORTA_DDR&quot;&gt;
&lt;figcaption&gt;Relevant GPIO registers&lt;/figcaption&gt;&lt;/figure&gt;&lt;/p&gt;&lt;p&gt;The interesting registers for us are:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Data Direction register (GPIO_SWPOARTA_DDR), as we need to specify that the LEDs are output.&lt;/li&gt;&lt;li&gt;Data register (GPIO_SWPOARTA_DR), do we want to set or reset the GPIO.&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Let’s write&lt;/h3&gt;&lt;p&gt;Ok, we have 32 bits to set to say that lines 1 and 4 are output and set high (to 1). How to know what these bits represent?&lt;/p&gt;&lt;p&gt;&lt;figure&gt;&lt;img src=&quot;http://code.nulmail.com/blog/rockpro64_leds/refman_gpio_overview.png&quot; alt=&quot;Reference manual &apos;GPIO Overview&apos; section describing the the bit-signal mapping&quot;&gt;
&lt;figcaption&gt;GPIOs overview: 32 bits - 32 signals&lt;/figcaption&gt;&lt;/figure&gt;&lt;/p&gt;&lt;p&gt;This means that signals (lines) 0 to 31 are mapped to bits 0 to 31. The lines 1 and 4 we’re interested in correspond to bits… 1 and 4, yes you follow.&lt;/p&gt;&lt;p&gt;Let’s set the GPIO1 Lines 1 and 4 to output (a LED is an output device, a keyboard an input device from the CPU perspective).&lt;/p&gt;&lt;p&gt;in binary: 0b0001_0010 (the line 0 is mapped to the bit 0). This is 0x12 in hex.&lt;/p&gt;&lt;h4&gt;Direction: Output&lt;/h4&gt;&lt;p&gt;&lt;figure&gt;&lt;img src=&quot;http://code.nulmail.com/blog/rockpro64_leds/refman_gpio_data_direction_register.png&quot; alt=&quot;Reference manual GPIO Data Direction Register description&quot;&gt;
&lt;figcaption&gt;Data Direction Register: set the LEDs as output&lt;/figcaption&gt;&lt;/figure&gt;&lt;/p&gt;&lt;p&gt;0 is input and 1 is output (what we want for our LEDs): Let’s write 0bXXXX_XXXX_XXX1_XX10 to this register!&lt;/p&gt;&lt;p&gt;What’s the value of ‘X’? I’m &lt;del&gt;lazy&lt;/del&gt; a professional! So, I force the bits I don’t care to their default value (in this case, input: 0). To do this cleanly, it would be a good idea to read the GPIO value and &lt;code&gt;OR&lt;/code&gt; the relevant bits we want to set. We have no idea what the other &lt;strong&gt;thirty&lt;/strong&gt; signals (lines) are mapped to, and we just set them to input writing 0x12…&lt;/p&gt;&lt;p&gt;In U-Boot to write 32 bits, use a long: MemoryWrite.long: &lt;code&gt;mw.l&lt;/code&gt; which takes an address and a value:&lt;/p&gt;&lt;p&gt;&lt;code&gt;mw.l 0xff730004 0x00000012&lt;/code&gt;&lt;/p&gt;&lt;p&gt;Now that the relevant GPIOs are configured as output, let’s set them to high (ON).&lt;/p&gt;&lt;h4&gt;Value&lt;/h4&gt;&lt;p&gt;&lt;figure&gt;&lt;img src=&quot;http://code.nulmail.com/blog/rockpro64_leds/refman_gpio_data_register.png&quot; alt=&quot;Reference manual GPIO Data Register description&quot;&gt;
&lt;figcaption&gt;Data Register: set the LEDs ON&lt;/figcaption&gt;&lt;/figure&gt;&lt;/p&gt;&lt;p&gt;“Value written to this register are output”, so let’s set the LEDs high (set to 1). We can use the same mask as before to set both LEDs. &lt;code&gt;mw.l 0xff730004 0x00000012&lt;/code&gt;&lt;/p&gt;&lt;p&gt;Or control independently line 1 with &lt;code&gt;mw.l 0xff730004 0x00000002&lt;/code&gt; and line 4 with &lt;code&gt;mw.l 0xff730004 0x00000010&lt;/code&gt;&lt;/p&gt;&lt;p&gt;… And the LEDs turn on \o/&lt;/p&gt;&lt;p&gt;I hope some of the hardware / software thin frontier hard to grasp stuff were a bit demistified.&lt;/p&gt;&lt;p&gt;&lt;figure&gt;&lt;img src=&quot;http://code.nulmail.com/blog/rockpro64_leds/rtfm.png&quot; alt=&quot;A distorted metal plate reading &apos;RTFM&apos;&quot;&gt;
&lt;figcaption&gt;It does not have to always end badly Reading The Fucking Manual&lt;/figcaption&gt;&lt;/figure&gt;&lt;/p&gt;&lt;h1&gt;Bonus stages&lt;/h1&gt;&lt;h2&gt;Play with a GPIO under Linux&lt;/h2&gt;&lt;p&gt;Directly writing to &lt;code&gt;/sys/class/gpio/&lt;/code&gt; is deprecated… for a decade.&lt;/p&gt;&lt;p&gt;Use instead &lt;code&gt;gpioset -t0 --chip 1 4=1&lt;/code&gt; for instance to set the GPIO group 1 line 4 to 1.&lt;/p&gt;&lt;h2&gt;Permanent U-Boot config (under Armbian)&lt;/h2&gt;&lt;p&gt;One needs to create a U-Boot script and recreate the image to load this on boot (again, probably a bad idea to set every unused GPIO of the first group as input).&lt;/p&gt;&lt;p&gt;Create a uboot script (for instance in &lt;code&gt;/boot/uboot_leds.cmd&lt;/code&gt;)&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;console&quot;&gt;echo &amp;quot;Turning both LEDs ON&amp;quot;

&lt;span class=&quot;prompt&quot;&gt;#&lt;/span&gt; &lt;span class=&quot;command&quot;&gt;Set GPIO1 lines (bits) 1 and 4 to output&lt;/span&gt;
mw.l 0xff730004 0x00000012

&lt;span class=&quot;prompt&quot;&gt;#&lt;/span&gt; &lt;span class=&quot;command&quot;&gt;Set GPIO1 lines 1 and 4 to high&lt;/span&gt;
mw.l 0xff730000 0x00000012
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, recreate the image (fixup.scr is a hard-coded Armbian output name, you may have to adapt this part).&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;console&quot;&gt;mkimage -C none -A arm64 -T script -d /boot/uboot_leds.cmd /boot/fixup.scr
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Loud ugliness (under Linux)&lt;/h2&gt;&lt;p&gt;As you may have guessed, having LEDs in a closed box is not that ideal and there is no buzzer, by experience I cannot rely on the network, so to have even more fun I hi-jacked the fan (which turns anyway at the incredible speed of 0 RPM to cool down a board and 2 SSDs doing nothing most of the time).&lt;/p&gt;&lt;p&gt;By hi-jacking I mean: let’s spin it in an SOS pattern if something goes wrong so that you have audible feedback while passing by instead of actively having to peak at the box from time to time to check if a LED is on.&lt;/p&gt;&lt;p&gt;Depressing quick but &lt;strong&gt;really&lt;/strong&gt; dirty echo:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;console&quot;&gt;echo 255 &amp;gt; /sys/devices/platform/pwm-fan/hwmon/hwmon3/pwm1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Really dirty because… actually someone else plays around with the fan along with you echoing in the PWM (who could have forsee this?).&lt;/p&gt;&lt;p&gt;Which means that the real code I use echoes periodically to make a Morse dot or dash.&lt;/p&gt;&lt;p&gt;Plus, don’t do that if you really need your fan btw…&lt;/p&gt;&lt;p&gt;That said, it works good enough for my use case; Spoiler alert: someone asked me why is the server doing weird noises, the question you don’t want to hear after a normal shitty day.&lt;/p&gt;</description>
        <link>http://code.nulmail.com/blog/rockpro64_leds/</link>
        <pubDate>Mon, 26 Oct 2026 19:00:00 +0000</pubDate>
        <guid>http://code.nulmail.com/blog/rockpro64_leds/</guid>
      </item>
    
      <item>
        <title>NixOS re-re-re-build</title>
        <description>&lt;h1&gt;Slowly filling up &lt;code&gt;/boot&lt;/code&gt;&lt;/h1&gt;&lt;div class=&quot;block&quot;&gt;&lt;h1&gt;Someone on this blog wrote about NixOS:&lt;/h1&gt;&lt;p&gt;&lt;a href=&quot;http://code.nulmail.com/blog/nixos_init_first_days/&quot;&gt;Having a look at the garbage collector: after a few weeks (85 builds), 141MB free over 1GB on /boot good to keep in mind…&lt;/a&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;So guess what, &lt;code&gt;/boot&lt;/code&gt; went full.&lt;/p&gt;&lt;p&gt;Actually it’s worse than that: I didn’t care, because I thought that I would just need to invoke the garbage collector to fix the issue when it will show up.&lt;/p&gt;&lt;h1&gt;Garbage collecting&lt;/h1&gt;&lt;p&gt;So, let’s run &lt;code&gt;sudo nix-collect-garbage --delete-older-than 7d&lt;/code&gt; as planned… and it doesn’t clean up &lt;code&gt;/boot&lt;/code&gt;&lt;/p&gt;&lt;p&gt;Searching online for the correct nix tool to do this and the stuff popping up look like:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Don’t let this happen, fix your config (fair enough, that’s a valid answer, but not to this problem)&lt;/li&gt;&lt;li&gt;&lt;code&gt;sudo nix-collect-garbage --delete-older-than 7d&lt;/code&gt; yes, if &lt;code&gt;/boot&lt;/code&gt; is not &lt;strong&gt;already&lt;/strong&gt; full&lt;/li&gt;&lt;li&gt;Complain about the fact NixOS does not prevent this to happen (to be fair I’m not aware of a distro preventing this, and this is an issue I quite respect as it is often noticed the hard way on the next boot)&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;What bothered me a bit (and that’s why actually this post exits) is that there does not seem to be a “clean” nix way to do this.&lt;/p&gt;&lt;p&gt;So, maybe I didn’t find the clean solution I looked for but what at least worked for me (and should work on any system): delete manually a few entries in &lt;code&gt;/boot/EFI/nixos/&lt;/code&gt;&lt;/p&gt;&lt;p&gt;I only deleted the last two oldest kernels and initrd sets, don’t need to nuke everything, as I wanted to check that the garbage collector worked as intended, when it can.&lt;/p&gt;&lt;p&gt;&lt;code&gt;sudo nix-collect-garbage --delete-older-than 7d&lt;/code&gt;&lt;/p&gt;&lt;p&gt;Yes… this cleans &lt;code&gt;/nix/store&lt;/code&gt;, but I couldn’t find a message about removing stuff in &lt;code&gt;/boot&lt;/code&gt; on success (if &lt;code&gt;/boot&lt;/code&gt; is full, it does not report anything either…).&lt;/p&gt;&lt;p&gt;Now, building is possible again &lt;code&gt;nixos-rebuild switch --flake /etc/nixos&lt;/code&gt; and &lt;code&gt;df&lt;/code&gt; reports a sane &lt;code&gt;/boot&lt;/code&gt; again.&lt;/p&gt;&lt;p&gt;I’d like to investigate the internals of NixOS to understand why &lt;code&gt;nix-collect-garbage&lt;/code&gt; cannot delete stuff in this very situation or who &lt;em&gt;exactly&lt;/em&gt; deletes stuff.&lt;/p&gt;&lt;h1&gt;Notes&lt;/h1&gt;&lt;p&gt;NixOS gives a great feeling of resilience. If you want to sneakily nuke NixOS, maybe start with &lt;code&gt;dd if=/dev/urandom of=/boot/let_us_fill_this_boot_partition&lt;/code&gt; and break the current system.&lt;/p&gt;&lt;p&gt;Apparently you also can run in a very similar issue if /tmp fills up (iirc a full /var on Debian also leads to interesting issues).&lt;/p&gt;&lt;p&gt;There is &lt;a href=&quot;https://github.com/NixOS/nixpkgs/issues/23926&quot;&gt;this fairly old NixOS open issue about this&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Use something like &lt;code&gt;boot.loader.systemd-boot.configurationLimit = 5;&lt;/code&gt; in your config.&lt;/p&gt;</description>
        <link>http://code.nulmail.com/blog/nixos_full/</link>
        <pubDate>Wed, 21 Oct 2026 18:30:00 +0000</pubDate>
        <guid>http://code.nulmail.com/blog/nixos_full/</guid>
      </item>
    
      <item>
        <title>Let&apos;s try something new! Rust?</title>
        <description>&lt;h1&gt;Picking up a new programming language&lt;/h1&gt;&lt;p&gt;I wanted to give Rust a fair try, the last time I didn’t go far and the only stuff I truly remember about Rust is this tiny detail, which still puzzles me:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;rust&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;constructor constant function&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;punctuation_bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;punctuation_bracket&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;punctuation_bracket&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;constructor constant&quot;&gt;a&lt;/span&gt; = &lt;span class=&quot;constant_builtin&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;punctuation_delimiter&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;comment&quot;&gt;// a is an integer&lt;/span&gt;
  &lt;span class=&quot;comment&quot;&gt;// a = &amp;quot;2&amp;quot;; // failure: sting assignment to an integer, ok&lt;/span&gt;
  &lt;span class=&quot;keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;constructor constant&quot;&gt;a&lt;/span&gt; = &lt;span class=&quot;string&quot;&gt;&amp;quot;2&amp;quot;&lt;/span&gt;&lt;span class=&quot;punctuation_delimiter&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;comment&quot;&gt;// compiles... WTF!? Yes it&amp;apos;s re-defined, but this design choice...&lt;/span&gt;
&lt;span class=&quot;punctuation_bracket&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This tiny snippet compiles fine, with unused warnings. A &lt;a href=&quot;https://godbolt.org/z/YGr4oh4zG&quot;&gt;warning free version on Compiler Explorer&lt;/a&gt; &lt;a href=&quot;https://godbolt.org/#g:!((g:!((g:!((h:codeEditor,i:(filename:&apos;1&apos;,fontScale:14,fontUsePx:&apos;0&apos;,j:1,lang:rust,selection:(endColumn:2,endLineNumber:10,positionColumn:2,positionLineNumber:10,selectionStartColumn:2,selectionStartLineNumber:10,startColumn:2,startLineNumber:10),source:&apos;fn+main()+%7B%0A++++let+a+%3D+2%3B%0A++++if+a+%3D%3D+2+%7B%0A++++++++println!!(%22Ok,+defined+an+integer%22)%3B%0A++++%7D%0A++++let+a+%3D+%222%22%3B%0A++++if+a+%3D%3D+%222%22+%7B%0A++++++++println!!(%22Like+really+redefining+a+is+OK,+no+warning!!%3F%22)%3B%0A++++%7D%0A%7D&apos;),l:&apos;5&apos;,n:&apos;0&apos;,o:&apos;Rust+source+%231&apos;,t:&apos;0&apos;)),k:100,l:&apos;4&apos;,m:50,n:&apos;0&apos;,o:&apos;&apos;,s:0,t:&apos;0&apos;),(g:!((h:executor,i:(argsPanelShown:&apos;1&apos;,compilationPanelShown:&apos;0&apos;,compiler:r1990,compilerName:&apos;&apos;,compilerOutShown:&apos;0&apos;,execArgs:&apos;&apos;,execStdin:&apos;&apos;,fontScale:14,fontUsePx:&apos;0&apos;,j:1,lang:rust,libs:!(),options:&apos;&apos;,overrides:!((name:edition,value:&apos;2024&apos;)),runtimeTools:!(),source:1,stdinPanelShown:&apos;1&apos;,wrap:&apos;1&apos;),l:&apos;5&apos;,n:&apos;0&apos;,o:&apos;Executor+rustc+1.99.0+(Rust,+Editor+%231)&apos;,t:&apos;0&apos;)),header:(),l:&apos;4&apos;,m:50,n:&apos;0&apos;,o:&apos;&apos;,s:0,t:&apos;0&apos;)),l:&apos;3&apos;,n:&apos;0&apos;,o:&apos;&apos;,t:&apos;0&apos;)),version:4&quot;&gt;Or use this full link just in case&lt;/a&gt;&lt;/p&gt;&lt;h1&gt;Zig philosophy&lt;/h1&gt;&lt;p&gt;I discovered Zig reading somewhere that it wanted to be a better C, and I chuckled (like really, how robust are their shoulders to support their heads while saying this?), but then wanted to have a look at its &lt;a href=&quot;https://ziglang.org/&quot;&gt;official website&lt;/a&gt; to confirm this by myself, just to discard it from my programming languages to check list.&lt;/p&gt;&lt;p&gt;Actually, I realized quickly it has many good ideas, not only stuff you would expect from modern programming languages (readable compile time stuff for instance), but also some specifics (avoiding hidden control flow / memory allocations).&lt;/p&gt;&lt;h2&gt;Everything’s monolithic.&lt;/h2&gt;&lt;p&gt;Zig comes with a compiler (no shit…), a standard library, but also a package manager, unit tests framework, formatter, fuzzer, …&lt;/p&gt;&lt;p&gt;I had no strong opinion about this, but the more I dig into Python and see people using multiple package managers, formatters and tests frameworks in a single project &lt;strong&gt;without noticing it&lt;/strong&gt; or understanding why it’s fishy and then complaining that nothing’s compatible, the more I think it’s not a bad idea to centralize stuff.&lt;/p&gt;&lt;p&gt;Installing Zig means downloading and extracting a ~55 MB (at the time of writing) .tar.xz and… that’s it.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;console&quot;&gt;&lt;span class=&quot;prompt&quot;&gt;$&lt;/span&gt; &lt;span class=&quot;command&quot;&gt;wget https://ziglang.org/download/0.17.0/zig-x86_64-linux-0.17.0.tar.xz&lt;/span&gt;
--2026-10-18 08:47:32--  https://ziglang.org/download/0.17.0/zig-x86_64-linux-0.17.0.tar.xz
Resolving ziglang.org (ziglang.org)... 2a01:4f9:3051:4bd2::2, 65.109.105.178
Connecting to ziglang.org (ziglang.org)|2a01:4f9:3051:4bd2::2|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 57332648 (55M) [application/octet-stream]
Saving to: ‘zig-x86_64-linux-0.17.0.tar.xz’

zig-x86_64-linux-0.17.0.tar.x 100%[===============================================&amp;gt;]  54.68M  6.18MB/s    in 9.6s    

2026-10-18 08:47:42 (5.69 MB/s) - ‘zig-x86_64-linux-0.17.0.tar.xz’ saved [57332648/57332648]

&lt;span class=&quot;prompt&quot;&gt;$&lt;/span&gt; &lt;span class=&quot;command&quot;&gt;ls -lh zig-x86_64-linux-0.17.0.tar.xz&lt;/span&gt;
-rw-r----- 1 jp jp 55M Oct  2 21:55 zig-x86_64-linux-0.17.0.tar.xz

&lt;span class=&quot;prompt&quot;&gt;$&lt;/span&gt; &lt;span class=&quot;command&quot;&gt;tar xf zig-x86_64-linux-0.17.0.tar.xz&lt;/span&gt;

&lt;span class=&quot;prompt&quot;&gt;$&lt;/span&gt; &lt;span class=&quot;command&quot;&gt;zig-x86_64-linux-0.17.0/zig version&lt;/span&gt;
0.17.0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Congratulation, you installed zig!&lt;/p&gt;&lt;p&gt;Oh btw, why 55 MB, isn’t it quite heavy? After decompression, the zig binary takes roughly half of the extracted weight. Something I didn’t mention is that Zig embedds a C compiler (note that Zig does not transpiles to C though). The rest of the weight mostly comes from the libc (42% you cannot compete with this).&lt;/p&gt;&lt;p&gt;&lt;figure&gt;&lt;img src=&quot;http://code.nulmail.com/blog/zig_init/zig_tarball_dust.png&quot; alt=&quot;dust ran on zig-x86_64-linux-0.17.0&quot;&gt;
&lt;figcaption&gt;du-dust from extracted zig tarball&lt;/figcaption&gt;&lt;/figure&gt;&lt;/p&gt;&lt;p&gt;I left the raw dust output because maybe you noticed on the screenshot, the presence of multiple C libraries, and maybe also the unexpected presence of mingw in a &lt;em&gt;x86_64-linux&lt;/em&gt; so, cannot the Zig maintainers properly pack their artifacts or is there a real reason?&lt;/p&gt;&lt;p&gt;Zig is actually a cross-compiler.&lt;/p&gt;&lt;p&gt;Cross-compilation in Zig is still a topic I need to deep dive into. Zig can cross compile for &lt;strong&gt;many&lt;/strong&gt; targets (different BSD flavors, arm-linux-gnueabihf, the dreaded Windows, …). Actually the Zig build system can be a drop in replacement to cmake, autotools, …&lt;/p&gt;&lt;p&gt;For which exacts targets can you compile you may ask? Many! This time, no full output, because…&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;console&quot;&gt;&lt;span class=&quot;prompt&quot;&gt;$&lt;/span&gt; &lt;span class=&quot;command&quot;&gt;zig targets | wc -l&lt;/span&gt;
35009
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Zig respects the C &lt;a href=&quot;https://en.wikipedia.org/wiki/Application_binary_interface&quot;&gt;ABI&lt;/a&gt;. Zig competes sanely with C, it embraces C not trashes it. The decades of legacy still lives on, and Zig can help you with it instead of telling you to re-write everything with the new shit.&lt;/p&gt;&lt;h1&gt;Killer features&lt;/h1&gt;&lt;h2&gt;Incremental compilation&lt;/h2&gt;&lt;p&gt;The idea is simple: compile only what you need to compile: if you modify one local variable without side effects, do you need to recompile the full project?&lt;/p&gt;&lt;p&gt;This is the kind of black magic I didn’t even realize it was possible before learning the Zig core team worked on it. I mean, binary patching is a thing (and, no I don’t mean asking a LLM to not modify some specific sources and… noticing this fucker patched the binary instead). But, hotfixing with the linker, like really? You need to know about dependencies, side-effects, linker sections, …&lt;/p&gt;&lt;p&gt;If you want to dive in this topic, &lt;a href=&quot;https://mlugg.co.uk&quot;&gt;Matthew Lugg&lt;/a&gt; from the core team wrote a &lt;a href=&quot;https://mlugg.co.uk/posts/incremental-compilation-internals/&quot;&gt;blog post&lt;/a&gt; about this; definitely worth multiple reads.&lt;/p&gt;&lt;p&gt;Oh, btw yes, the core team implemented their own linker.&lt;/p&gt;&lt;h2&gt;Optimization&lt;/h2&gt;&lt;p&gt;We’re talking about custom linker, custom C compiler, everything done the Zig way, so what we just throw away decades of optimization in other toolchains? Actually no: LLVM is still used as the backend in optimization mode (&lt;code&gt;--release=fast&lt;/code&gt;)&lt;/p&gt;&lt;p&gt;The custom backend is used for debugging, incremental compilation, … but the LLVM one is used (so far?) for optimization.&lt;/p&gt;&lt;p&gt;For the size optimization side, the standard library is optional, you can go bare-metal.&lt;/p&gt;&lt;h2&gt;Simplicity&lt;/h2&gt;&lt;p&gt;This language actually focus on simplicity, or in other words, forcing the user to understand what’s going on.&lt;/p&gt;&lt;p&gt;You want memory? Ok, how do you want it allocated?&lt;/p&gt;&lt;p&gt;You want to print something? Oh buffered print this time, fine, where is &lt;em&gt;your&lt;/em&gt; buffer?&lt;/p&gt;&lt;p&gt;We constantly hear about C++ devs arguing that there is no overhead in C++ which can be true, if you know exactly what’s going on.&lt;/p&gt;&lt;p&gt;Either &lt;code&gt;perf&lt;/code&gt; says: nope, it’s slower than you thought, or you hear a dev mumbling something like “ok this operator overload so, this constructor will be called, then Short String Optimization kicks in, …” and after a few minutes, arguing against &lt;code&gt;perf&lt;/code&gt; results.&lt;/p&gt;&lt;p&gt;In Zig, reading the code gives you clear hints on what’s going on. For instance, if a function needs to allocate memory, it will likely take an allocator as argument.&lt;/p&gt;&lt;h2&gt;The best of all worlds&lt;/h2&gt;&lt;p&gt;Zig feels like having the best of all worlds:&lt;/p&gt;&lt;p&gt;It can feel like an interpreted language, without compilation time, but with compilation checks before blindly running a binary crashing with a parse error.&lt;/p&gt;&lt;p&gt;Fair comparisons show C-runtime performances levels.&lt;/p&gt;&lt;p&gt;If your code is too slow, measuring will likely not pinpoint to unknown regions, where you ponder why a &lt;code&gt;strlen&lt;/code&gt; coming directly from GCC is magically called, but more on your handwritten code.&lt;/p&gt;&lt;h1&gt;Drawbacks of Zig&lt;/h1&gt;&lt;p&gt;None, next section… No? Ok.&lt;/p&gt;&lt;h2&gt;Evolving fast&lt;/h2&gt;&lt;p&gt;Zig is under heavy development. The core team regularly breaks stuff and &lt;strong&gt;removes&lt;/strong&gt; features. Blindly upgrading to the next release can be a breeze, where the formatter will do the job for you (no kidding) or a painful death.&lt;/p&gt;&lt;p&gt;Try to keep an eye on the devlog, the forums or ideally on &lt;a href=&quot;https://codeberg.org/ziglang/ziglang.org&quot;&gt;the repo on codeberg&lt;/a&gt; for instance to have a feeling on what you should already modify or stop doing.&lt;/p&gt;&lt;h2&gt;Documentation&lt;/h2&gt;&lt;p&gt;The documentation is really poor (let’s troll: there is no documentation), but the sources are &lt;strong&gt;readable&lt;/strong&gt;. I mean it: I have a fair C or C++ experience, using these professionally for most of my career. For me, opening the C standard library is doable, the C++ one is a no-go, always ending up with more questions, assuming I found what I was looking for.&lt;/p&gt;&lt;p&gt;The Zig sources often contains unit tests. You can rely on these and they give you a quick and meaningful way to use some feature.&lt;/p&gt;&lt;h2&gt;The language can feel too simple.&lt;/h2&gt;&lt;p&gt;Consider this: there is no direct support for classes; indeed &lt;a href=&quot;https://en.wikipedia.org/wiki/Object-oriented_programming&quot;&gt;OOP&lt;/a&gt; is not the only truth, don’t forget about &lt;a href=&quot;https://www.youtube.com/watch?v=rX0ItVEVjHc&quot;&gt;Data Oriented Design&lt;/a&gt; or the &lt;a href=&quot;https://www.youtube.com/watch?v=IroPQ150F6c&quot;&gt;Zig version&lt;/a&gt;).&lt;/p&gt;&lt;h1&gt;Let’s try it&lt;/h1&gt;&lt;p&gt;I don’t have a fancy ritual to share while picking a new programming language. I do something like this:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Try to grasp the idea of the language (Which problem does it solve?): Read the &lt;a href=&quot;https://ziglang.org/&quot;&gt;home page&lt;/a&gt;&lt;/li&gt;&lt;li&gt;Have a &lt;a href=&quot;https://ziglang.org/learn/samples/&quot;&gt;glance at the syntax&lt;/a&gt;&lt;/li&gt;&lt;li&gt;Scroll through the &lt;a href=&quot;https://ziglang.org/documentation/master/&quot;&gt;documentation&lt;/a&gt;&lt;/li&gt;&lt;li&gt;Browse randomly its &lt;a href=&quot;https://ziglang.org/documentation/master/std/&quot;&gt;standard library&lt;/a&gt;&lt;/li&gt;&lt;li&gt;Follow some &lt;a href=&quot;https://codeberg.org/ziglings/exercises&quot;&gt;tutorials&lt;/a&gt;&lt;/li&gt;&lt;li&gt;Begin writing trivial programs (let’s say “Guess the number”)&lt;/li&gt;&lt;li&gt;Write a bit more complex program (for instance a &lt;a href=&quot;https://sr.ht/~jp_foss/chezz/&quot;&gt;chess&lt;/a&gt; or &lt;a href=&quot;https://sr.ht/~jp_foss/towerz/&quot;&gt;a tower defense&lt;/a&gt; I’m both not proud about)&lt;/li&gt;&lt;li&gt;Start a nice but not too hard-core project, let’s say &lt;a href=&quot;https://sr.ht/~jp_foss/DiMethylGlycine/&quot;&gt;a Game Boy emulator&lt;/a&gt;, how hard can it be?&lt;/li&gt;&lt;/ul&gt;&lt;h1&gt;Bonus&lt;/h1&gt;&lt;ul&gt;&lt;li&gt;The Zig Software Foundation is a non-profit corporation (read: more resilience to entshitification).&lt;/li&gt;&lt;li&gt;The sources are under the permissive MIT license.&lt;/li&gt;&lt;li&gt;The core team switched to &lt;a href=&quot;https://codeberg.org/&quot;&gt;Codeberg&lt;/a&gt; over github&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ziglang.org/code-of-conduct/#strict-no-llm-no-ai-policy&quot;&gt;Strict no LLM policy&lt;/a&gt; (a really hard-core one, including translation)&lt;/li&gt;&lt;li&gt;The Zig Software Foundation supports FOSS projects they rely on (musl for instance)&lt;/li&gt;&lt;/ul&gt;&lt;pre&gt;&lt;code class=&quot;console&quot;&gt;&lt;span class=&quot;prompt&quot;&gt;$&lt;/span&gt; &lt;span class=&quot;command&quot;&gt;zig zen&lt;/span&gt;

 * Communicate intent precisely.
 * Edge cases matter.
 * Favor reading code over writing code.
 * There is an idiomatic way to do it.
 * Runtime crashes are better than bugs.
 * Compile errors are better than runtime crashes.
 * Incremental improvements.
 * Avoid local maximums.
 * Reduce the amount one must remember.
 * Focus on logic, not style.
 * Resource allocation may fail.
 * Resource deallocation must succeed.

Together, we serve the users!
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Notes&lt;/h2&gt;&lt;p&gt;I like to troll (if possible Windows, but apparently there’s not too much left of it to troll), but I don’t really like flame wars.&lt;/p&gt;&lt;p&gt;If you absolutely want a A Vs B programming language topic, do not consider Rust Vs Zig but maybe more Rust Vs C++ and Zig Vs C.&lt;/p&gt;</description>
        <link>http://code.nulmail.com/blog/zig_init/</link>
        <pubDate>Sun, 18 Oct 2026 10:00:00 +0000</pubDate>
        <guid>http://code.nulmail.com/blog/zig_init/</guid>
      </item>
    
      <item>
        <title>Time to look for a job</title>
        <description>&lt;h1&gt;The various reasons I’m fed up with my current situation&lt;/h1&gt;&lt;h2&gt;AI, “What a pleasant surprise!”&lt;/h2&gt;&lt;p&gt;Let’s put this out of the way: I hate AI, for various reasons, but let’s focus on the job topic:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Takes the fun out of programming&lt;/strong&gt; It’s a job, so, invalid argument&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Ecological disaster&lt;/strong&gt; I’m currently working in the consumer electronics industry, so, invalid argument too&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;The main issue in a working environment is to make it clear that a proof of concept is not a product. Taking shortcuts to test or demo something makes sense. AI is good at it and blazing fast.&lt;/p&gt;&lt;p&gt;I would be surprised if a Software company would have a strong no-AI policy, but “How many percents of your core product code is generated by LLMs?” is definitely on my questions list.&lt;/p&gt;&lt;p&gt;An analogy to trigger you: would you build a 3D renderer based on a demo of the &lt;a href=&quot;https://en.wikipedia.org/wiki/Demoscene&quot;&gt;demoscene&lt;/a&gt;?&lt;/p&gt;&lt;h2&gt;Lessons learned&lt;/h2&gt;&lt;p&gt;In every company and likely any project released or canceled ends in the same way:&lt;/p&gt;&lt;p&gt;After doing, a ton of things, including a lot of failures over years. At some point a full review is done. Its name is variable: call it “retrospective”, “review” or my favorite: “lessons learned”. Why my favorite you may ask?&lt;/p&gt;&lt;div class=&quot;block&quot;&gt;&lt;h1&gt;Spoiler Alert&lt;/h1&gt;&lt;p&gt;We never learn anything.&lt;br&gt;“Ah shit, here we go again.”&lt;/p&gt;&lt;/div&gt;&lt;p&gt;In the best case scenario, a few important mistakes are avoided. From the critical ones, nearly everything is repeated over time.&lt;/p&gt;&lt;p&gt;The classics from what I saw. In &lt;em&gt;EVERY&lt;/em&gt; &lt;em&gt;SINGLE&lt;/em&gt; &lt;em&gt;FUCKING&lt;/em&gt; &lt;em&gt;PROJECT&lt;/em&gt; I worked on in a professional environment:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;There is no clear architecture or no architect on fairly large projects: Every team work in their area, despite being all the time in meeting no information is shared. You end up with double work, non compatible interfaces, breaking stuff you didn’t know existed.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Unrealistic plan from the beginning or sudden plan change: everybody wonders how unrealistic (or “Sporty” in management language) is the planing. If you think no one cares, remember you need to rush, and not only in the end.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Really debug only in the end.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;On top of that, add normal mistakes you cannot easily prevent if you don’t know them and, the critical failures you make because you absolutely want to do something different way.&lt;/p&gt;&lt;h2&gt;Build crap with gold&lt;/h2&gt;&lt;p&gt;I work with some really talented people, but the project quality is so far below from what a normal human being would expect from these profiles… that’s something I cannot explain and that’s killing me.&lt;/p&gt;&lt;p&gt;I asked for fun from time to time who in my team would feel confident to release the current state of our repo as an open source project. We’re all ashamed of this quality.&lt;/p&gt;&lt;p&gt;Let’s troll a bit: consider the monsters working at Microsoft and look at the crappy Windows… what kind of dissonance is this?&lt;/p&gt;&lt;h2&gt;Never fix, just workaround&lt;/h2&gt;&lt;p&gt;Why don’t we just fix things when we can work around them faster? … And end up spending an absurd amount of time fixing stupid mistakes afterwards.&lt;/p&gt;&lt;p&gt;In the fantastic times of AI, this is a bigger problem than ever. Things are not compatible? Just vibe-code a wrapper. It works, why should you read the code? Someone complained that it stopped working? Re-ask your favorite agent, tokens are cheap (so far).&lt;/p&gt;&lt;p&gt;By experience, if you want to &lt;strong&gt;fix&lt;/strong&gt; something working often, you have some options:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Don’t consider fixing it&lt;/li&gt;&lt;li&gt;Attempt to quickly fix it and abort&lt;/li&gt;&lt;li&gt;Write a ticket which will rot in the backlog:&lt;br&gt;You won’t fix it because it’s low priority, and the day it became a real problem you will notice it will take a tremendous effort to fix as so much stuff was added… &lt;br&gt;But the priority will change from “won’t do” to “blocker”.&lt;/li&gt;&lt;li&gt;Do “Guerilla Software” as a colleague named it: don’t tell your boss you’re doing it, just fix it properly, eventually over time.&lt;/li&gt;&lt;/ul&gt;&lt;h1&gt;Ok, so what now?&lt;/h1&gt;&lt;p&gt;These main reasons lead me to believe it’s time, not to look for yet another job in yet another company, but to truly change things.&lt;/p&gt;&lt;p&gt;Let’s throw away decades of experience in software and start building buggies: ~400 kg, a ~75 HP electric motor, ~100 km autonomy: Something approaching the kind of cars we really need to go from A to B in a fun way.&lt;/p&gt;&lt;p&gt;No ok, I’m joking… Even if I’m pissed off with the car industry and if I would break down that’s probably what I’d try to do, that’s another topic.&lt;/p&gt;&lt;p&gt;Giving up on software is not an option for me: this is my main hobby. I can understand the argument “You’re paid for doing your job, so don’t give a shit about what you deliver”, but that’s not me. &lt;br&gt;By the way, let’s be honest, the overly comfortable situation some magical keyword on a CV add to your income is just insane.&lt;/p&gt;&lt;p&gt;The ideal working environment for me (likely not realistic to check everything, but I can use it as a reference… and depress later on, signing a contract).&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;Work in open source environment. Everything I do &lt;strong&gt;must&lt;/strong&gt; be open source&lt;br&gt;I need to be sure of my stuff before pushing it. We properly review before we release something broken (oh, and the CI pipeline is reliable). &lt;br&gt;As a bonus, looking at the sources before signing up is possible.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Work for a non-profit foundation. &lt;br&gt;Hopefully less bullshit, nothing should be hidden&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;No vibe coding (using an LLM is fine if done responsibly, but if you need to maintain generated stuff you didn’t even read, maintain it without me).&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Not a single Windows PC around (&lt;strong&gt;including&lt;/strong&gt; sales, HRs, …).&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;div class=&quot;block&quot;&gt;&lt;h1&gt;Fun fact (ok, depressing fact)&lt;/h1&gt;&lt;p&gt;As some project in a company where advertised as “Open Source”. What did I do being a new employee and discovering these projects? &lt;br&gt;Yes indeed, I searched for their repos from home! But guess what “Open Source” does not mean Open Source, it’s just internal open source.&lt;/p&gt;&lt;/div&gt;&lt;p&gt;If for some reason, you may consider it, you can find my redacted CV &lt;a href=&quot;http://code.nulmail.com/cv_anonymous.pdf&quot;&gt;here.&lt;/a&gt; Feel free to contact me.&lt;/p&gt;</description>
        <link>http://code.nulmail.com/blog/time_to_look_for_a_job/</link>
        <pubDate>Sun, 11 Oct 2026 20:00:00 +0000</pubDate>
        <guid>http://code.nulmail.com/blog/time_to_look_for_a_job/</guid>
      </item>
    
      <item>
        <title>NixOS after a few days</title>
        <description>&lt;h1&gt;Introduction&lt;/h1&gt;&lt;p&gt;This is the follow-up of the article &lt;a href=&quot;http://code.nulmail.com/blog/nixos_init/&quot;&gt;NixOS init&lt;/a&gt;&lt;/p&gt;&lt;p&gt;So, how does NixOS feel after a few days of usage?&lt;/p&gt;&lt;p&gt;The learning curve is a wall. Picture a deadly, cold wall loosing itself in the fog (picture it in your mind, leave Midjourney alone).&lt;/p&gt;&lt;p&gt;Passed the installation and minor-tweaking the default configuration, the following days are horrible, but let’s dive in the details of the journey before I forget them (it’s not like I could but still…).&lt;/p&gt;&lt;h2&gt;Just trolling&lt;/h2&gt;&lt;p&gt;The first days feel like the &lt;a href=&quot;https://nixos.org/&quot;&gt;NixOS home page&lt;/a&gt; which greets your with a language you never saw, without syntax highlighting.&lt;/p&gt;&lt;p&gt;&lt;figure&gt;&lt;img src=&quot;http://code.nulmail.com/blog/nixos_init_first_days/nixos_home_page_bright.png&quot; alt=&quot;Picture of the home page&quot;&gt;
&lt;figcaption&gt;The NixOS home page&lt;/figcaption&gt;&lt;/figure&gt; Okay, I have to admit: this is actually cool &lt;a href=&quot;https://asciinema.org/&quot;&gt;asciinema&lt;/a&gt; demonstrating some NixOS features a nice way.&lt;/p&gt;&lt;h1&gt;The issues I faced as a beginner&lt;/h1&gt;&lt;h2&gt;The broken plan&lt;/h2&gt;&lt;p&gt;The first plan looked like this:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Learn about Nix (the language)&lt;/li&gt;&lt;li&gt;Learn about nix (the package manager)&lt;/li&gt;&lt;li&gt;Learn about NixOS&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Nix (the language)&lt;/h3&gt;&lt;p&gt;The language is pretty straight forward (didn’t use a functional programming language for a too long time, but Nix is not overly complex):&lt;/p&gt;&lt;p&gt;Except a few tricks I needed to look up while reading some configuration code, the rest is readable. I’m definitely not a Nix expert, just have the level to read basic Nix code.&lt;/p&gt;&lt;p&gt;I likely invested a bit too much time in the language learning for a kickstart but definitely not a waste of time. Just to try NixOS, probably a grasp on attribute sets is enough ;)&lt;/p&gt;&lt;p&gt;Consider this analogy: learning shell scripting to edit your &lt;code&gt;.bashrc&lt;/code&gt; This is definitely not wrong but maybe consider postponing.&lt;/p&gt;&lt;h3&gt;nixpkgs (the package manager)&lt;/h3&gt;&lt;p&gt;The &lt;a href=&quot;https://nixos.org/manual/nixpkgs/stable/&quot;&gt;manual&lt;/a&gt; is between really useful and a joke: probably exhaustive list of every available packages with scarce details.&lt;/p&gt;&lt;p&gt;But actually the packages configuration sources are readable.&lt;/p&gt;&lt;h3&gt;NixOS&lt;/h3&gt;&lt;p&gt;It’s just about editing the config, what could possibly go wrong?&lt;/p&gt;&lt;h2&gt;So many questions&lt;/h2&gt;&lt;p&gt;So, I had a rough idea how what is taking care of what, where are the clearly delimited boundaries. In theory…&lt;/p&gt;&lt;p&gt;One of the first question poping really fast while digging around: “Should I use Flakes?”&lt;/p&gt;&lt;p&gt;The main arguments for the flakes: “Everyone’s using flakes, use them!”… cool but try with arguments… and rabbit hole discovered!&lt;/p&gt;&lt;p&gt;Officially they’re instable, and their RFC is still on a branch which PR was declined, but indeed for new configuration you should use flakes.&lt;/p&gt;&lt;p&gt;Flakes:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;https://github.com/NixOS/rfcs/pull/49&quot;&gt;Pull Request &lt;em&gt;closed&lt;/em&gt; from 2019&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://github.com/tweag/rfcs/blob/flakes/rfcs/0049-flakes.md&quot;&gt;Not-merged RFC&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Some background: I come from Debian which means “stability” to me. Yes it’s anoying that some packages are too old but I generally handle these ones without package manager (compiling from sources for intance, looking at you Emacs).&lt;/p&gt;&lt;p&gt;My flakes interpretation: Make the build reproducible, pinning every used package version in a flake.lock (which I .gitignored seeing this generated file fith this extension poping up btw…).&lt;/p&gt;&lt;p&gt;Oh btw, as you use flakes, use &lt;code&gt;nix-command&lt;/code&gt; (still unsure why they always seem to come together and what other experimental stuff I should have a look at).&lt;/p&gt;&lt;p&gt;One of the next question is about nixos / nixpkg unstable. Both nixos-unstable and nixpkgs-unstable follow the master branch of the Nixpkgs repository, but if anyway everything’s pinned, if it works, it should be stable-ish right?&lt;/p&gt;&lt;p&gt;The most answers about picking any solution sound like “Hey, it’s your OS so it’s your call”&lt;/p&gt;&lt;h2&gt;Main issue with the plan&lt;/h2&gt;&lt;p&gt;I wanted to make everything correctly, from the beginning (sounds good? Was likely a mistake).&lt;/p&gt;&lt;p&gt;This implies bringing up flakes, trying to understand modules, and use home-manager while having no idea of NixOS.&lt;/p&gt;&lt;p&gt;Which means: instead of pasting some stuff accumulated over years in &lt;code&gt;~/.config&lt;/code&gt; I thought I would very soon use home manager so denied this intermediate step.&lt;/p&gt;&lt;p&gt;This also implies: No editor config, no bash aliases (okay, cracking down and adding aliases manually from time to time to the current bash instance or just prefering &lt;code&gt;ssh&lt;/code&gt;) but you get the idea.&lt;/p&gt;&lt;p&gt;So to summarize the first few days: It’s horrible! But let’s see after a few weeks.&lt;/p&gt;&lt;h1&gt;NixOS after these steps&lt;/h1&gt;&lt;p&gt;So I learned the hard way to&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;Read the error messages, not using &lt;code&gt;--show-trace&lt;/code&gt; without a good reason.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Use &lt;code&gt;--dry-activate&lt;/code&gt; / &lt;code&gt;--dry-build&lt;/code&gt;&lt;/p&gt;&lt;p&gt;It is much more user-friendly to read something like:&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;“would remove group ‘networkmanager’”&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;or&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;“would remove user my_only_user”&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;and pondering if a rebuild switch is still a good idea instead of wondering why the &lt;code&gt;ssh&lt;/code&gt; connection dropped or why login directly or through &lt;code&gt;ssh&lt;/code&gt; just doesn’t work anymore.&lt;/p&gt;&lt;p&gt;Actually, I have git hooks running &lt;code&gt;nixos-rebuild&lt;/code&gt; with &lt;code&gt;dry-build&lt;/code&gt; / &lt;code&gt;dry-activate&lt;/code&gt;, just in case.&lt;/p&gt;&lt;p&gt;That said, a very good feature of NixOS is to forbid generating an OS without a user (I tested, yes &lt;code&gt;root&lt;/code&gt; is still there).&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;File not found likely means “Hey, you forgot to &lt;code&gt;git add&lt;/code&gt;”&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;“everything’s obvious, afterwards”.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;According to me, enforcing a VCS is a good feature.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;The sources of upstream packages are readable.&lt;/p&gt;&lt;p&gt;To be brutal, maybe more readable than the man page which just blindly list options for a package… which would be super cool… if you could easily install packages offline.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;Config failure&lt;/h2&gt;&lt;p&gt;This is still a recurrent issue for me.&lt;/p&gt;&lt;p&gt;Some packages are still commented out because they need to be built, … on every &lt;code&gt;rebuild switch&lt;/code&gt; which I don’t understand.&lt;/p&gt;&lt;p&gt;Why are not every or none package are built, why some are? This is still an open question.&lt;/p&gt;&lt;p&gt;I was debugging my i3 config, while being logged in and was happy that everything finally worked (forgot the modifier key…). The day after I could not log in: LightDM reported: “Failed to start session”&lt;/p&gt;&lt;p&gt;This happened multiple times to me (I know, I’m the culprit): on the list of errors where you start to consider testing if roll-backing works:&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;error: executing “git”: No such file or directory&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;Note that git not needed by &lt;code&gt;nix-env&lt;/code&gt;, so just start an environment with git.&lt;/p&gt;&lt;p&gt;As a side effect, I’m rebooting even more than a Windows user (ok not that much but still a few times a day to make sure the config did not break).&lt;/p&gt;&lt;h2&gt;What stays to learn&lt;/h2&gt;&lt;p&gt;… Everything I guess, but maybe urgently:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;Having a look at the garbage collector: after a few weeks (85 builds), 141MB free over 1GB on /boot good to keep in mind…&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;nix-env nix-shell nix-daemon nix-your-grandmother&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Out of curiosity: are python virtual environments or docker still a thing? I would have expect a sound “no” but the NixOS homepage has a section about &lt;a href=&quot;https://nixos.org/#asciinema-demo-example_4&apos;&quot;&gt;“Minimal docker images”&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;As I’m thinking my configuration to work on multiple machines, cross-building would be interesting.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Find a way to search for a package using a cache (so in less than 30 seconds).&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;How to properly debug a configuration (of course not that I need this but…)&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h1&gt;Special mention&lt;/h1&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;&lt;a href=&quot;https://www.vimjoyer.com&quot;&gt;The vimjoyer&lt;/a&gt; - “Learn Nix” course - Videos on youtube about specific features&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Some friends which shared / explained their configs to me.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;</description>
        <link>http://code.nulmail.com/blog/nixos_init_first_days/</link>
        <pubDate>Tue, 22 Sep 2026 22:00:00 +0000</pubDate>
        <guid>http://code.nulmail.com/blog/nixos_init_first_days/</guid>
      </item>
    
      <item>
        <title>A RAID failure is no worries! Right? RIGHT?</title>
        <description>&lt;h1&gt;Let’s have a peaceful evening&lt;/h1&gt;&lt;p&gt;Note: Mainly for brevity, some commands / output were modified, but the essence was kept.&lt;/p&gt;&lt;p&gt;After noticing that a service from a server was no longer available, the journey started with a &lt;code&gt;ls&lt;/code&gt; … which resulted in I/O errors.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;console&quot;&gt;&lt;span class=&quot;prompt&quot;&gt;$&lt;/span&gt; &lt;span class=&quot;command&quot;&gt;ls test&lt;/span&gt;
ls: reading directory &amp;apos;/raid/&amp;apos;: Input/output error
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Investigating the root cause&lt;/h2&gt;&lt;pre&gt;&lt;code class=&quot;console&quot;&gt;&lt;span class=&quot;prompt&quot;&gt;$&lt;/span&gt; &lt;span class=&quot;command&quot;&gt;cat /proc/mdstat&lt;/span&gt;
md0 : broken raid1 sdb[1](F) sda[0]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Yes, &lt;em&gt;broken&lt;/em&gt; is the word you don’t want to read.&lt;/p&gt;&lt;p&gt;What does &lt;code&gt;dmesg&lt;/code&gt; think about that?&lt;/p&gt;&lt;pre&gt;&lt;code&gt;...
[    4.113971] ata1.00: 2000409264 sectors, multi 1: LBA48 NCQ (depth 32), AA
[    4.112887] ata1.00: ATA-9: Verbatim Vi550 S3, W0704A0, max UDMA/133
...
[    4.607618] ata2.00: failed to IDENTIFY (INIT_DEV_PARAMS failed, err_mask=0x80)
...
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Ok, so on the SATA but, only one disk says Hi.&lt;/p&gt;&lt;h2&gt;It would be nice to fix the issue&lt;/h2&gt;&lt;p&gt;In order to investigate further, let’s unmount the RAID:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;console&quot;&gt;&lt;span class=&quot;prompt&quot;&gt;$&lt;/span&gt; &lt;span class=&quot;command&quot;&gt;sudo umount /dev/mapper/vg_raid-lv_data&lt;/span&gt;
umount: /raid: target is busy.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let’s try to at least umount everything properly, stoping some services known to attempt to access the raid.&lt;/p&gt;&lt;p&gt;Ok still not &lt;code&gt;umount&lt;/code&gt;-able, so &lt;code&gt;lsof&lt;/code&gt; please help:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;console&quot;&gt;&lt;span class=&quot;prompt&quot;&gt;$&lt;/span&gt; &lt;span class=&quot;command&quot;&gt;sudo lsof | grep /raid&lt;/span&gt;
lsof: no pwd entry for UID 999
lsof: WARNING: can&amp;apos;t stat() tracefs file system /sys/kernel/debug/tracing
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Plenty of errors (but only 2 categories).&lt;/p&gt;&lt;p&gt;On this server, the Root File System is not in the RAID array (read: it can properly boot without RAID), so in &lt;del&gt;despair&lt;/del&gt; expert mode, I rebooted the server.&lt;/p&gt;&lt;p&gt;$&gt; shutdown now&lt;/p&gt;&lt;h2&gt;LUKS on top of RAID&lt;/h2&gt;&lt;pre&gt;&lt;code class=&quot;console&quot;&gt;&lt;span class=&quot;prompt&quot;&gt;$&lt;/span&gt; &lt;span class=&quot;command&quot;&gt;&amp;gt; sudo cryptsetup luksOpen /dev/md0 crypt_raid&lt;/span&gt;
Device /dev/md0 does not exist or access denied.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;… oh, ok let’s mount manually&lt;/p&gt;&lt;p&gt;There are (normally 2 disks; but here was only /dev/sda listed by &lt;code&gt;lsblk&lt;/code&gt; so let’s use this one)&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;console&quot;&gt;&lt;span class=&quot;prompt&quot;&gt;$&lt;/span&gt; &lt;span class=&quot;command&quot;&gt;sudo mdadm --assemble --run /dev/md0 /dev/sda&lt;/span&gt;
mdadm: no recogniseable superblock on /dev/sda
mdadm: /dev/sda has no superblock - assembly aborted
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ok, if the mirror RAID’s broken because of 1 disk failure, it’s quite interesting right?&lt;/p&gt;&lt;p&gt;Hey, come on, I already saw some I/O errors in the past, this is fine, let’s do a proper power-cycle, it will work. $&gt; shutdown now Unplugged the power supply, waited ~2 minutes. … the issue persists, it’s not a Microslop product (not that they work after a power cycle either…)&lt;/p&gt;&lt;p&gt;Spoiler alert: this power-cycle is a big mistake for the post-mortem investigation.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;console&quot;&gt;&lt;span class=&quot;prompt&quot;&gt;$&lt;/span&gt; &lt;span class=&quot;command&quot;&gt;lsblk -o NAME,VENDOR,MODEL&lt;/span&gt;
sda          ATA      Verbatim Vi550 S3
sdb          ATA      SD Ultra 3D 1TB
└─md0

&lt;span class=&quot;prompt&quot;&gt;$&lt;/span&gt; &lt;span class=&quot;command&quot;&gt;sudo mdadm --examine /dev/sda&lt;/span&gt;
mdadm: No md superblock detected on /dev/sda.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ok, no /dev/sda&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;console&quot;&gt;&lt;span class=&quot;prompt&quot;&gt;$&lt;/span&gt; &lt;span class=&quot;command&quot;&gt;sudo mdadm --examine /dev/sdb&lt;/span&gt;
/dev/sdb:
...
     Raid level : raid1
...
    Data Offset : 264192 sectors
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ok, so I started to be lost, but thought “hey, who care about RAID, we just need data, so let’s skip the header”. After getting mad at the offset needed by &lt;code&gt;losetup&lt;/code&gt;, browsing the internet brought the guess: 512 bytes per sectors (of course). (I genuinely do not know where to get this information from actually…). So 265_192 * 512 = 135_266_304&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;console&quot;&gt;&lt;span class=&quot;prompt&quot;&gt;$&lt;/span&gt; &lt;span class=&quot;command&quot;&gt;&amp;gt; sudo losetup --find --show --read-only --offset 135266304 /dev/sda&lt;/span&gt;
/dev/loop0

&lt;span class=&quot;prompt&quot;&gt;$&lt;/span&gt; &lt;span class=&quot;command&quot;&gt;sudo cryptsetup luksOpen /dev/loop0 crypt_raid&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Oh, it works! And took only ~1.5 hours so far…&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;console&quot;&gt;&lt;span class=&quot;prompt&quot;&gt;$&lt;/span&gt; &lt;span class=&quot;command&quot;&gt;sudo mount /dev/mapper/crypt_raid /raid&lt;/span&gt;
mount: /raid: unknown filesystem type &amp;apos;LVM2_member&amp;apos;.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;… but I forgot about LVM&lt;/p&gt;&lt;h1&gt;LVM on top of LUKS, on top of RAID.&lt;/h1&gt;&lt;pre&gt;&lt;code class=&quot;console&quot;&gt;&lt;span class=&quot;prompt&quot;&gt;$&lt;/span&gt; &lt;span class=&quot;command&quot;&gt;sudo fsck /dev/mapper/vg_raid-lv_data&lt;/span&gt;
fsck.ext4: Input/output error while recovering journal of /dev/mapper/vg_raid-lv_data
fsck.ext4: unable to set superblock flags on /dev/mapper/vg_raid-lv_data
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Noooo… oh wait, the &lt;code&gt;losetup&lt;/code&gt; mount was done in read only, and as a bonus for the good news, I’ll spare you the &lt;code&gt;vgchange -an&lt;/code&gt; / luksClose / re-&lt;code&gt;losetup&lt;/code&gt; / re-luksOpen&lt;/p&gt;&lt;p&gt;and the mighty and dreaded &lt;code&gt;fsck&lt;/code&gt; said: “clean”. Mhhhnn’k&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;console&quot;&gt;&lt;span class=&quot;prompt&quot;&gt;#&lt;/span&gt; &lt;span class=&quot;command&quot;&gt;mount /dev/mapper/vg_raid-lv_data /raid&lt;/span&gt;

&lt;span class=&quot;prompt&quot;&gt;$&lt;/span&gt; &lt;span class=&quot;command&quot;&gt;ls /raid&lt;/span&gt;
-- Works, just works :D
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So now, as you can guess after the hassle it’s &lt;del&gt;bed&lt;/del&gt; BACKUP time! I do make backups, but… they stay on this very RAID, I just &lt;code&gt;scp&lt;/code&gt; / test them from time to time, … manually (I know…).&lt;/p&gt;&lt;h1&gt;Just hotswap the disk, trivial&lt;/h1&gt;&lt;p&gt;So, no data loss, everything’s cool, was an adventure through the evening, nice, everything’s ends well! Right? … Just a simple question: which disk is the culprit? I mean /dev/sda or sata1 are not that explicit to me. Why does a disk not report to the SATA bus… and the other one has a broken header?&lt;/p&gt;&lt;p&gt;I left some hints here and there which you may have noticed: The RAID is composed of 2 disks.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;One from Verbatim&lt;/li&gt;&lt;li&gt;One from SanDisk (“SD” as VENDOR in &lt;code&gt;lsblk&lt;/code&gt; output)&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Please note that I’m not sponsored nor want to bash a manufacturer, it’s just easier to name a disk by its manufacturer than UUID for instance.&lt;/p&gt;&lt;h2&gt;Confusing output&lt;/h2&gt;&lt;p&gt;Initial boot: md0 : broken raid1 sdb[1](F) sda[0]&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;console&quot;&gt;&lt;span class=&quot;prompt&quot;&gt;$&lt;/span&gt; &lt;span class=&quot;command&quot;&gt;lsblk -o NAME,VENDOR,MODEL /dev/sdb&lt;/span&gt;
NAME                  VENDOR   MODEL
sdb                   ATA      SD Ultra 3D 1TB
└─md0
  └─crypt_raid
    └─vg_raid-lv_data
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The SanDisk is the culprit, easy! And, after the first power cycle (&lt;code&gt;shutdown -r now&lt;/code&gt;), only the Verbatim is on the SATA bus (mapped as /dev/sda)&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;console&quot;&gt;&lt;span class=&quot;prompt&quot;&gt;$&lt;/span&gt; &lt;span class=&quot;command&quot;&gt;&amp;gt; dmesg&lt;/span&gt;
[    4.112887] ata1.00: ATA-9: Verbatim Vi550 S3, W0704A0, max UDMA/133
...
[    4.183576] sd 0:0:0:0: [sda] Attached SCSI disk
[    4.602340] ata2: SATA link up 6.0 Gbps (SStatus 133 SControl 300)
[    4.607618] ata2.00: failed to IDENTIFY (INIT_DEV_PARAMS failed, err_mask=0x80)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And now comes the second power cycle (&lt;code&gt;shutdown&lt;/code&gt; + no power supply for about two minutes) into play.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;console&quot;&gt;&lt;span class=&quot;prompt&quot;&gt;$&lt;/span&gt; &lt;span class=&quot;command&quot;&gt;&amp;gt; cat /proc/mdstat&lt;/span&gt;
Personalities : [raid0] [raid1] [raid4] [raid5] [raid6] [raid10] 
md0 : inactive sdb[1](S)
      976630488 blocks super 1.2

&lt;span class=&quot;prompt&quot;&gt;$&lt;/span&gt; &lt;span class=&quot;command&quot;&gt;&amp;gt; sudo mdadm --examine /dev/sda&lt;/span&gt;
mdadm: No md superblock detected on /dev/sda.

&lt;span class=&quot;prompt&quot;&gt;$&lt;/span&gt; &lt;span class=&quot;command&quot;&gt;&amp;gt; lsblk -o NAME,VENDOR,MODEL&lt;/span&gt;
sda          ATA      Verbatim Vi550 S3
sdb          ATA      SD Ultra 3D 1TB
└─md0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It confirms everything: the SanDisk is dead as /de… oh no wait this time, the Verbatim is not in the RAID array, but the SanDisk is.&lt;/p&gt;&lt;p&gt;A day after the party, re-reading the logs confirmed what I first thought: I don’t understand what’s going on.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Why is one disk not reporting on the bus?&lt;/li&gt;&lt;li&gt;Why does one disk have sudden Software issues (header broken) &lt;em&gt;at the same time&lt;/em&gt;?&lt;/li&gt;&lt;li&gt;Why does this seem random?&lt;/li&gt;&lt;li&gt;Could it be that the issue lays one level deeper (motherboard)?&lt;/li&gt;&lt;li&gt;&lt;del&gt;Could it be that&lt;/del&gt; I understand nothing&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;And some side quests pops up:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Should I switch to HDDs? (these two SSDs are about 3 years old, nearly never under heavy load)&lt;/li&gt;&lt;li&gt;Is ext4 a good idea?&lt;/li&gt;&lt;li&gt;Should a disk be “mostly write”?&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;As you can see, this is still under investigation, but I wanted to re-read logs carefully, avoid guessing on what I did and now, I have a documentation of what happened / was done.&lt;/p&gt;&lt;p&gt;So, see you hopefully in a later blog post with answers.&lt;/p&gt;</description>
        <link>http://code.nulmail.com/blog/raid/</link>
        <pubDate>Wed, 16 Sep 2026 19:00:00 +0000</pubDate>
        <guid>http://code.nulmail.com/blog/raid/</guid>
      </item>
    
      <item>
        <title>NixOS installation</title>
        <description>&lt;h1&gt;Installation&lt;/h1&gt;&lt;p&gt;For a long time I wanted to give NixOS a try. I finally got into it.&lt;/p&gt;&lt;p&gt;So, the first step are just like a normal distribution: &lt;code&gt;dd&lt;/code&gt; an iso on a USB stick and you’re good to go. (yes, you can boot over network too to flex around; that’s on my list).&lt;/p&gt;&lt;p&gt;As I’m lazy, I took the graphical installation. Then, as an expert, I clicked next &gt; next &gt; next &gt; reboot.&lt;/p&gt;&lt;h1&gt;Packages installation&lt;/h1&gt;&lt;p&gt;The main config file seems usable, the comments provide a fair quick reference.&lt;/p&gt;&lt;p&gt;This pointed me right to the keyboard layout -&gt; I can type instead of looking at my keyboard… which has no printed keys.&lt;/p&gt;&lt;p&gt;The packages installation is really clean in my opinion: just list what to install. I find this way neat as you instantly see which packages you installed.&lt;/p&gt;&lt;p&gt;I know, package managers can list installed packages (we can depress together if you’re using a Debian based distribution: &lt;code&gt;apt list --installed&lt;/code&gt; reports more than 3k installed packages on my system).&lt;/p&gt;&lt;p&gt;The point of having a list screaming at you is to ponder if you need the bloat you’re not using while scrolling it. (I’m a maniac keeping this kind of lists alphabetically sorted if you wondered).&lt;/p&gt;&lt;p&gt;That said, let’s add &lt;code&gt;i3&lt;/code&gt; for me to not jump through the window (pun intended) attempting to use a mouse. And the moment of truth: &lt;code&gt;sudo nixos-rebuild switch&lt;/code&gt; Works as promised, nice!&lt;/p&gt;&lt;p&gt;Let’s then add &lt;code&gt;ghostty&lt;/code&gt; (always wanted to really give this terminal emulator a shot). Now I’m no longer dying from xterm with black on nuclear blast light. So, a paranoiac rebuild + power cycle, to see that everything works as expected (to be fair, we’re talking about the package manager of the OS, no big wonder it just works).&lt;/p&gt;&lt;p&gt;With the emergency topics off the way, I can use my brain to think about packages, no longer about brightness. So, I added a bunch of packages (something between the lines: nvim, emacs, zig, gcc), rebuild… forever …&lt;/p&gt;&lt;p&gt;With the CPU going 100% (guessed from the fan noise at least). Untils boom! The terminal just closed. Wondering about the stability of ghostty (I needed a culprit), I retried, same issue.&lt;/p&gt;&lt;p&gt;After some trivial monitoring during the build, the CPU went indeed berserk, and the RAM went low, dangerously low. On terminal crash, checking the system logs, journalctl happily repported the Out Of Memory Killer kicked in…&lt;/p&gt;&lt;p&gt;Searching around in the man to discover that &lt;code&gt;--cores&lt;/code&gt; &lt;code&gt;--max-jobs&lt;/code&gt; &lt;code&gt;--show-trace&lt;/code&gt; may be useful with sound values. … Until after a few Out Of Memory issues I though (“hey, come on in 2026, mutli-core is a far too new fancy technology so, let’s use 1 core and 1 job”). Taking forever, while the RAM usage continued to raise I searched for another solution and, good news, it’s a known issue, no worries… just need a swap.&lt;/p&gt;&lt;p&gt;Dooming myself for not having thinking during the installation, I then saw the swap is actually a file (nice!). Ok, so with a swap, everything’s building fine \o/&lt;/p&gt;&lt;p&gt;But &lt;em&gt;why&lt;/em&gt; is anything being built in the first place? I mean, it is really good to have the option to build from sources, but often your package manager defaults to build from sources XOr download, so ok, Nix does both… but depending on what.&lt;/p&gt;&lt;p&gt;Sorry I don’t know yet &lt;em&gt;why&lt;/em&gt;, but it’s really triggering me, so I’ll need to investigate xD&lt;/p&gt;&lt;h1&gt;Summary of the first steps&lt;/h1&gt;&lt;p&gt;I took a few good decisions (yes, this happens sometimes). Concretely:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;Initialize a git repo in /etc/nixos/ (I really wondered for a while if it’s the proper NixOS way, but I think it’s much better than nothing) Do this! (and do this as soon as possible; in my case the first commit a bit late: keyboard config, survival packages are already there)&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Add &lt;em&gt;ONE&lt;/em&gt; package at a time, commit often. Adding nvim did not trigger a deadly compilation; adding emacs did. (Maybe I should not add an OS in an OS next time).&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h1&gt;Next steps&lt;/h1&gt;&lt;p&gt;The installation opened a scarry number of rabbit holes (flakes? overlays? Nix? Normal user? Home manager? What is nixos-rebuild doing and why? …?).&lt;/p&gt;&lt;p&gt;A lot of topics remains, for instance I want to configure some packages, let’s say a trivial &lt;code&gt;.bashrc&lt;/code&gt; I feel this should not be done through a classic &lt;code&gt;scp another_machine:.bashrc .&lt;/code&gt; So I’ll need to find the Nix way, but that will be for another post.&lt;/p&gt;</description>
        <link>http://code.nulmail.com/blog/nixos_init/</link>
        <pubDate>Sun, 06 Sep 2026 19:00:00 +0000</pubDate>
        <guid>http://code.nulmail.com/blog/nixos_init/</guid>
      </item>
    
      <item>
        <title>How do editors handle directory creation on save</title>
        <description>&lt;h1&gt;Introduction&lt;/h1&gt;&lt;div class=&quot;block&quot;&gt;&lt;h1&gt;NOTE&lt;/h1&gt;&lt;p&gt;Unless a post is about vscode or eclipse, no editor flame war is intended.&lt;/p&gt;&lt;/div&gt;&lt;p&gt;To keep the comparison fair, only the defaults of the editors are used… which often show that any editor will have at some point interesting default behaviors…&lt;/p&gt;&lt;h2&gt;Emacs&lt;/h2&gt;&lt;p&gt;Emacs is started as &lt;code&gt;emacs --no-init-file ...&lt;/code&gt; (aka &lt;code&gt;emacs -q&lt;/code&gt;)&lt;/p&gt;&lt;p&gt;Editing a file which path traverse non exiting directories triggers a warning:&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;Use M-x make-directory RET RET to create the directory and its parents&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;On save attempt, you’re explicitly asked:&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;Directory ‘/tmp/lol/’ does not exist; create? (y or n)&lt;/p&gt;&lt;/blockquote&gt;&lt;h2&gt;vi(m)&lt;/h2&gt;&lt;p&gt;Considering vi and vim, not nvim here. Starting vi / vim with &lt;code&gt;--clean&lt;/code&gt;&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;“/tmp/lol/hello” [New DIRECTORY]&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;So yes, vim, just like, vi displays a warning.&lt;/p&gt;&lt;p&gt;On save, oh boy… you’re on your own and I needed to search for the way to create directories.&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;“/tmp/lol/hello” E212: Can’t open file for writing Press ENTER or type command to continue&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;The no-config remember-able solution I can come up with is calling a shell command:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;:!mkdir -p %:h
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;nvim&lt;/h2&gt;&lt;p&gt;Interestingly enough, nvim shows nothing interesting in the status line opening a file behind a non-existing path.&lt;/p&gt;&lt;p&gt;On save, save error message shown as in vi(m) but a cleaner solution is available.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;:w ++p
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;Helix&lt;/h2&gt;&lt;p&gt;Nothing shown opening a file behind a non-existing path.&lt;/p&gt;&lt;p&gt;On save:&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;can’t save file, parent directory does not exist (use w! to create it)&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;from the &lt;code&gt;:w!&lt;/code&gt; pop-up help message: “Force write changes to disk creating necessary subdirectories.”&lt;/p&gt;&lt;h1&gt;Conclusion&lt;/h1&gt;&lt;p&gt;Yes these editors are configurable but still, the default behavior of vi / vim / nvim is horrible, just telling you: “you’re doomed” and you realisticaly end up with Ctrl-z to your shell and running mkdir because there’s just nothing to remember.&lt;/p&gt;&lt;p&gt;In my opinion, there is no clear winner here. Helix has the best defaults: it’s not proposing you to create directories for you like Emacs does, it teaches you how to do it.&lt;/p&gt;&lt;p&gt;That said lacking a useful warning right after starting your editor telling you that the file you’re editing is not empty but lays behind non-existing directories is really useful.&lt;/p&gt;&lt;p&gt;Use case: copy-paste a path to a file from a failing service and not pondering why nothing was logged as the file appears empty, but knowing that the directory does not exist, so maybe the service did not start at all… (btw, a silent-ish warning is the best according to me: you don’t have to acknowledge anything, you can type right away if you want to create a file; if not, the warning will trigger you).&lt;/p&gt;</description>
        <link>http://code.nulmail.com/blog/editor_create_directory_on_save/</link>
        <pubDate>Thu, 27 Aug 2026 20:00:00 +0000</pubDate>
        <guid>http://code.nulmail.com/blog/editor_create_directory_on_save/</guid>
      </item>
    
      <item>
        <title>Keyboard layouts</title>
        <description>&lt;p&gt;A long time ago, I wanted to write a blog (&lt;em&gt;cough&lt;/em&gt; &lt;em&gt;cough&lt;/em&gt;) and the topic I had in mind at that time was about keyboard layouts (I didn’t even know about custom keyboards, lucky me).&lt;/p&gt;&lt;p&gt;So, some background: I picked up programming relatively late, using the layout coming out of the box on my country (where you need a combo using Alt-Gr to insert brackets or braces just to pick an example).&lt;/p&gt;&lt;p&gt;… Just giving you some time to locate the Alt-Gr key on your keyboard (or remind you that you may need it on your fancy custom layout) …&lt;/p&gt;&lt;p&gt;Shortly after, I joined the university, where I switched to a qwerty layout, not to be cool but only because the keyboards we had there where qwerty labeled and… I was not able to touch type (it’s a skill I tried to pick up in panic days before the university thinking everyone would be able to, but had just broken basics, and anyway surounded by many people in the same situation).&lt;/p&gt;&lt;p&gt;Fun fact, when possible, I tried to pick up one of the very few non-qwerty labeled keyboards in the university, because nearly every single keyboard had broken feet but non-qwerty keyboards where deserted so, with feet, &lt;em&gt;always&lt;/em&gt;. As touch typing slowly became a thing, this was really nice.&lt;/p&gt;&lt;p&gt;A failure I made and still can’t properly fix it: I remapped the backspace key on the capslock, easily reachable… allowing much more typos. I unfortunately still have a far too reachable capslock.&lt;/p&gt;&lt;p&gt;Later on, after years of programming for a living, it pissed me off to not be able to cleanly touch type, and as some colleagues transited to dvorak or colemark, I was motivated to follow but wondered what would be the state of the art layout.&lt;/p&gt;&lt;p&gt;Not-so-quick tries and errors and extensive search lead me to the holly &lt;a href=&quot;https://workmanlayout.org/&quot;&gt;workman layout&lt;/a&gt; (&lt;a href=&quot;https://web.archive.org/web/20260808152235/https://workmanlayout.org/&quot;&gt;Wayback machine link&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;&lt;figure&gt;&lt;img src=&quot;http://code.nulmail.com/blog/keyboard_layouts/layout.png&quot; alt=&quot;Picture of the workman layout&quot;&gt;
&lt;figcaption&gt;The workman layout from OJ Bucao&lt;/figcaption&gt;&lt;/figure&gt;&lt;/p&gt;&lt;p&gt;OJ Bucao, its creator holds a long and understandable description why where other layouts not that practical and overall, he measured not the travel distance of your fingers to reach a random key, but the cost of reaching keys to make digraphs (compare extending your index Vs your pinky for instance and typing ‘th’ Vs typing ‘ue’: which bigram is more useful in english; On this post draft: 1 match for ‘ue’ but 29 matches for ‘th’).&lt;/p&gt;&lt;p&gt;This layout came with bonuses:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Nearly no cheating possibility. You &lt;em&gt;need&lt;/em&gt; to touch type (it still breaks my brain to have a look at keyboard labels while typing).&lt;/li&gt;&lt;li&gt;The slight strain I had in my wrists calmed down for a while.&lt;/li&gt;&lt;/ul&gt;&lt;div class=&quot;block&quot;&gt;&lt;h1&gt;WARNING&lt;/h1&gt;&lt;p&gt;Do seek professional medical advice, do not read a blog for medical issues.&lt;/p&gt;&lt;/div&gt;&lt;p&gt;To be fair, the workman layout comes with some drawbacks:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Your editor bindings / muscle memory are off.&lt;/li&gt;&lt;li&gt;The Ctrl-c / Ctrl-v mainstream shortcuts are off-by-one.&lt;/li&gt;&lt;li&gt;Using this layout makes you an alien in random companies (if you already experienced being the only non Windows user in a whole department, this is worse).&lt;/li&gt;&lt;li&gt;Gaming is painful as you need to remap everything… if you’re using a normal keyboard.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;In general, learning a layout is hard. If the layout is really good, it’s definitely worth it, even if you’re only writing AI prompts ;)&lt;/p&gt;&lt;div class=&quot;block&quot;&gt;&lt;h1&gt;Fun facts&lt;/h1&gt;&lt;p&gt;Learning the workman programmer variant is still on my list&lt;br&gt;(and writing this article re-motivates me to attempt a transition).&lt;/p&gt;&lt;p&gt;I contaminated one person so far with the holly workman layout, will you be the next one?&lt;/p&gt;&lt;p&gt;Writing this blog post was my oldest ticket.&lt;/p&gt;&lt;/div&gt;</description>
        <link>http://code.nulmail.com/blog/keyboard_layouts/</link>
        <pubDate>Wed, 19 Aug 2026 19:00:00 +0000</pubDate>
        <guid>http://code.nulmail.com/blog/keyboard_layouts/</guid>
      </item>
    
      <item>
        <title>First Post: zine init</title>
        <description>&lt;h1&gt;Reboot of a writing space&lt;/h1&gt;&lt;p&gt;I didn’t write anything in the last &lt;em&gt;years&lt;/em&gt; and was never able to keep a correct writing rhythm. The main reasons are:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;Motivation: Let’s face it: this is still a major issue, but if I don’t start now, I’ll delay forever and regret it (actually, I really hope I’ll be able to populate this site, sharing my small knowledge)&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Fatalism: “Why should I write something about X, if anyway you can ask a LLM about X and likely get a better answer?” =&gt; I don’t care anymore&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;Against fatalism&lt;/h2&gt;&lt;p&gt;As more and more of the Software I see tend to take a weird direction, I admit that Zig, mostly, is my light in the dark: the philosophy of Zig in my words: You don’t have to stick with bad designs, nothing’s impossible, undoable.&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;“We are the ones making computers do what they do”&lt;/strong&gt; Andrew Kelly - &lt;a href=&quot;https://youtu.be/zLZwpH5lCD4&quot;&gt;Don’t take the Black Pill - talk given at the Software Should Work 2026&lt;/a&gt; (at &lt;a href=&quot;https://youtu.be/zLZwpH5lCD4?t=2084&quot;&gt;this timestamp&lt;/a&gt;)&lt;/p&gt;&lt;/blockquote&gt;&lt;blockquote&gt;&lt;p&gt;“Owning your content means that you will be unaffected by enshitification of platforms that would otherwise keep your data hostage. It also is the single most effective thing you can do as an individual to take power away from platforms, all while protecting your own immediate interests.” - Loris Cro - Zine example&lt;/p&gt;&lt;/blockquote&gt;&lt;h1&gt;Why, the fuck, Zine?&lt;/h1&gt;&lt;p&gt;I like the idea of static website (just focus on the content).&lt;/p&gt;&lt;p&gt;As a Zig fan I soon enough discovered Zine, but didn’t give it a shot, as:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;org-mode in Emacs worked okay-ish for me&lt;/li&gt;&lt;li&gt;I found it really overkill to have like Super MD + Super HTML + Zig… for a static website (compared to Hugo for instance), like what, you need to come up with your own languages, come on&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;So, what changed my mind?&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;After more than 10 years (oh boy…) of using Emacs, I switched to VS Co… NOPE, nice try, I switched to Neovim this means that I respect both editors, and overall, can troll both (and by the way if we could stop this weird war and trash together vscode and its funny ecosystem, it would be great…) so… even if vim org-mode is a thing, the full automation I had in Emacs was not matching the level of automation I like in nvim.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Actually Super HTML really has an edge with validation: HTML is really permissive (maybe a bit too much, but for a browser whatever happens the show must go on, can’t just abort displaying a page).&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;The benefit of &lt;a href=&quot;https://github.com/kristoff-it/superhtml&quot;&gt;SuperHTML&lt;/a&gt; is validation; &lt;a href=&quot;https://github.com/kristoff-it/supermd&quot;&gt;SuperMD&lt;/a&gt; is merely an implementation detail while fixing markdown issues&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;a href=&quot;https://zine-ssg.io/&quot;&gt;Zine&lt;/a&gt; just works and comes out of the box with this nice &lt;code&gt;zig build --watch&lt;/code&gt; like feature I’m so used to.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;div class=&quot;block&quot;&gt;&lt;h1&gt;WARNING&lt;/h1&gt;&lt;p&gt;I only a few &lt;em&gt;hours&lt;/em&gt; of practical “experience” with Zine.&lt;/p&gt;&lt;/div&gt;&lt;p&gt;In summary:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;maintainable templates&lt;/li&gt;&lt;li&gt;no tool specific magic&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;So far, it works for &lt;em&gt;me&lt;/em&gt;. It’s up to you to find the best solution for &lt;em&gt;you&lt;/em&gt;.&lt;/p&gt;</description>
        <link>http://code.nulmail.com/blog/first-post/</link>
        <pubDate>Wed, 12 Aug 2026 20:30:00 +0000</pubDate>
        <guid>http://code.nulmail.com/blog/first-post/</guid>
      </item>
    
  </channel>
</rss>
