{"version":"https://jsonfeed.org/version/1.1","title":"RGBCube","home_page_url":"https://rgbcu.be/","feed_url":"https://rgbcu.be/blog.json","description":"The home directory and journal of RGBCube and his work.","language":"en","authors":[{"name":"RGBCube"}],"icon":"/assets/icons/icon.webp","favicon":"/assets/icons/icon.webp","items":[{"id":"https://rgbcu.be/blog/htmnix/","url":"https://rgbcu.be/blog/htmnix/","title":"HTMNIX","content_html":"\n  <p>So, you may have seen the <a href=\"https://github.com/RGBCube/HTMNIX\">HTMNIX</a> project\nI've been working on the last few weeks. If not, no worries. Here is a Nix\nsnippet that uses it:</p>\n<div class=\"transform-[rotateX(180deg)] overflow-x-auto\"><pre class=\"transform-[rotateX(180deg)]\"><code class=\"language-nix hljs\"><span class=\"token-symbol\">&lt;html&gt;</span>\n  <span class=\"token-symbol\">&lt;head&gt;</span>\n    <span class=\"token-symbol\">&lt;title&gt;</span><span class=\"token-string\">\"Hello, Internet!\"</span><span class=\"token-operator\">&lt;</span>.title<span class=\"token-operator\">&gt;</span>\n  <span class=\"token-operator\">&lt;</span>.head<span class=\"token-operator\">&gt;</span>\n  <span class=\"token-symbol\">&lt;body&gt;</span>\n      <span class=\"token-symbol\">&lt;p&gt;</span><span class=\"token-string\">\"Yep, this is 100% Nix!\"</span><span class=\"token-operator\">&lt;</span>.p<span class=\"token-operator\">&gt;</span>\n\n      <span class=\"token-operator\">&lt;</span>img.<span class=\"token-operator\">&gt;</span>{<span class=\"token-attr\">src</span><span class=\"token-operator\">=</span><span class=\"token-string\">\"/foo.png\"</span>; <span class=\"token-attr\">alt</span><span class=\"token-operator\">=</span><span class=\"token-string\">\"Attributes also work!\"</span>;}\n  <span class=\"token-operator\">&lt;</span>.body<span class=\"token-operator\">&gt;</span>\n<span class=\"token-operator\">&lt;</span>.html<span class=\"token-operator\">&gt;</span>\n</code></pre></div>\n<p>You are probably thinking furiously right now, maybe you've noticed something:</p>\n<blockquote>\n<p>Aha! In Nix, <code>&lt;foo&gt;</code> is used to find stuff from the Nix path like so:</p>\n<div class=\"transform-[rotateX(180deg)] overflow-x-auto\"><pre class=\"transform-[rotateX(180deg)]\"><code class=\"language-nix hljs\"><span class=\"token-built_in\">import</span> <span class=\"token-symbol\">&lt;nixpkgs&gt;</span> {}\n</code></pre></div>\n<p>That means you have to add hundreds of elements to your Nix Path to make this\nwork?</p>\n</blockquote>\n<p>You are somewhat correct. But not quite.</p>\n<p>Nix <code>&lt;foo&gt;</code> expressions actually boil down to a call of the builtin\n<code>__findFile</code>, like so:</p>\n<div class=\"transform-[rotateX(180deg)] overflow-x-auto\"><pre class=\"transform-[rotateX(180deg)]\"><code class=\"language-nix hljs\"><span class=\"token-comment\"># nix-instantiate --parse --expr \"&lt;foo&gt;\"</span>\n\n(__findFile __nixPath <span class=\"token-string\">\"foo\"</span>)\n</code></pre></div>\n<blockquote>\n<p>In case you didn't know,\n<a href=\"https://nixos.org/manual/nix/stable/command-ref/nix-instantiate.html\"><code>nix-instantiate</code></a>\nis a nice tool to see what your Nix code is desugared and un-precedence'd\ninto.</p>\n</blockquote>\n<p>Aha! So this means we can override the builtin <code>__findFile</code> and put whatever we\nwould like in its place. So this will work:</p>\n<div class=\"transform-[rotateX(180deg)] overflow-x-auto\"><pre class=\"transform-[rotateX(180deg)]\"><code class=\"language-nix hljs\"><span class=\"token-keyword\">let</span>\n  <span class=\"token-attr\">__findFile</span> <span class=\"token-operator\">=</span> <span class=\"token-params\">nixPath:</span> <span class=\"token-params\">name:</span> {\n    <span class=\"token-attr\">content</span> <span class=\"token-operator\">=</span> <span class=\"token-string\">\"&lt;<span class=\"token-subst\">${name}</span>&gt;\"</span>;\n  };\n<span class=\"token-keyword\">in</span>\n<span class=\"token-symbol\">&lt;foo&gt;</span>\n</code></pre></div>\n<p>Evaluating this (by running <code>nix eval -f test.nix</code>), we get\n<code>{ content = \"&lt;foo&gt;\"; }</code></p>\n<p>So, then. How do we make it work for multiple tags, all coming after one another\n(and attribute sets, strings, etc.)?</p>\n<p>Another hack! We need to set the\n<a href=\"https://noogle.dev/md/tutorials/functors\">magic <code>__functor</code> attribute</a> of the\nattrset we return, so we can call our set and have it store the tags inside it\n(while also preserving its callability!).</p>\n<p>We can do that like so:</p>\n<div class=\"transform-[rotateX(180deg)] overflow-x-auto\"><pre class=\"transform-[rotateX(180deg)]\"><code class=\"language-nix hljs\"><span class=\"token-keyword\">let</span>\n  <span class=\"token-attr\">__findFile</span> <span class=\"token-operator\">=</span> <span class=\"token-params\">nixPath:</span> <span class=\"token-params\">name:</span> {\n    <span class=\"token-attr\">content</span> <span class=\"token-operator\">=</span> <span class=\"token-string\">\"&lt;<span class=\"token-subst\">${name}</span>&gt;\"</span>;\n    <span class=\"token-attr\">__functor</span> <span class=\"token-operator\">=</span> <span class=\"token-params\">self:</span> <span class=\"token-params\">next:</span> self <span class=\"token-operator\">//</span> {\n      <span class=\"token-attr\">content</span> <span class=\"token-operator\">=</span> self.content <span class=\"token-operator\">+</span> next;\n    };\n  };\n<span class=\"token-keyword\">in</span>\n<span class=\"token-symbol\">&lt;foo&gt;</span>\n<span class=\"token-string\">\"bar\"</span>\n<span class=\"token-string\">\"baz\"</span>\n</code></pre></div>\n<p>Great news! When we evaluate this, we get\n<code>{ __functor = &lt;LAMBDA&gt;; content = \"&lt;foo&gt;barbaz\"; }</code>.</p>\n<p>We can also add a case to check if the next element is a tag, and use its\ncontent if it is:</p>\n<div class=\"transform-[rotateX(180deg)] overflow-x-auto\"><pre class=\"transform-[rotateX(180deg)]\"><code class=\"language-nix hljs\"><span class=\"token-keyword\">let</span>\n  <span class=\"token-attr\">__findFile</span> <span class=\"token-operator\">=</span> <span class=\"token-params\">nixPath:</span> <span class=\"token-params\">name:</span> {\n    <span class=\"token-attr\">content</span> <span class=\"token-operator\">=</span> <span class=\"token-string\">\"&lt;<span class=\"token-subst\">${name}</span>&gt;\"</span>;\n    <span class=\"token-attr\">__functor</span> <span class=\"token-operator\">=</span> <span class=\"token-params\">self:</span> <span class=\"token-params\">next:</span> self <span class=\"token-operator\">//</span> {\n      <span class=\"token-attr\">content</span> <span class=\"token-operator\">=</span> self.content <span class=\"token-operator\">+</span> (<span class=\"token-keyword\">if</span> next <span class=\"token-operator\">?</span> content <span class=\"token-keyword\">then</span> next.content <span class=\"token-keyword\">else</span> next);\n    };\n  };\n<span class=\"token-keyword\">in</span>\n<span class=\"token-symbol\">&lt;foo&gt;</span>\n<span class=\"token-string\">\"bar\"</span>\n<span class=\"token-string\">\"baz\"</span>\n<span class=\"token-symbol\">&lt;endfoo&gt;</span>\n</code></pre></div>\n<p>Enter another hack! We can utilize the <code>outPath</code> property that exists on\nderivations and gets returned whenever you call <code>toString</code> with an attrset that\nhas the property to make our code a little simpler:</p>\n<div class=\"transform-[rotateX(180deg)] overflow-x-auto\"><pre class=\"transform-[rotateX(180deg)]\"><code class=\"language-nix hljs\"><span class=\"token-keyword\">let</span>\n  <span class=\"token-attr\">__findFile</span> <span class=\"token-operator\">=</span> <span class=\"token-params\">nixPath:</span> <span class=\"token-params\">name:</span> {\n    <span class=\"token-attr\">outPath</span> <span class=\"token-operator\">=</span> <span class=\"token-string\">\"&lt;<span class=\"token-subst\">${name}</span>&gt;\"</span>;\n    <span class=\"token-attr\">__functor</span> <span class=\"token-operator\">=</span> <span class=\"token-params\">self:</span> <span class=\"token-params\">next:</span> self <span class=\"token-operator\">//</span> {\n      <span class=\"token-attr\">outPath</span> <span class=\"token-operator\">=</span> self.outPath <span class=\"token-operator\">+</span> <span class=\"token-built_in\">toString</span> next;\n    };\n  };\n<span class=\"token-keyword\">in</span>\n<span class=\"token-symbol\">&lt;foo&gt;</span>\n<span class=\"token-string\">\"bar\"</span>\n<span class=\"token-string\">\"baz\"</span>\n<span class=\"token-number\">123</span>\n<span class=\"token-symbol\">&lt;endfoo&gt;</span>\n</code></pre></div>\n<p>We also got support for other types for free, as well!</p>\n<p>These are all the hidden builtins that\n<a href=\"https://github.com/RGBCube/HTMNIX\">HTMNIX</a> depends on and extends upon, making\nHTML in Nix an actually usable reality. It also has extra logic like turning\nattribute sets into HTML tags, which is fairly trivial compared to actually\ndiscovering these hidden builtins in the first place.</p>\n<p>You can read more about it in the project's README and see\n<a href=\"https://github.com/RGBCube/NixSite\">an example site using it</a>.</p>\n<p>I might even try to port this site to HTMNIX to ensure it is usable with more\ncomplex setups :-)</p>\n<p>Soon, maybe...</p>\n<hr>\n<p>Thanks for reading my first ever proper blog post! :-)</p>\n\n\n  \n","tags":["html","nix/os"],"date_published":"2024-03-04T00:00:00.000Z"},{"id":"https://rgbcu.be/blog/cosmic-drift/","url":"https://rgbcu.be/blog/cosmic-drift/","title":"Cosmic Drift","content_html":"\n  <p>So, every day I wake up at 6:55, get dressed by 7, walk to the bus stop by 7:13\nand board the bus around 7:17. Today was different.</p>\n<p>My alarm that I have set for 6:55 rang at 7:12 and as a result, I missed the\nbus.</p>\n<blockquote>\n<p>No, I didn't sleep in. There was no note in the UI saying this was a repeat\nalarm, which there is if you snooze it or let it expire.</p>\n</blockquote>\n<p>Surely something was happening. Machines don't break whenever they want, they're\nmostly deterministic. And I doubt Samsung engineers wrote code to delay the\nalarm by 12 minutes on the date after April 1st.</p>\n<p>So, <em>what</em> was happening? I entered the Python repl to test out a theory:</p>\n<div class=\"transform-[rotateX(180deg)] overflow-x-auto\"><pre class=\"transform-[rotateX(180deg)]\"><code class=\"language-py hljs language-python\"><span class=\"token-meta\">&gt;&gt;&gt; </span>alarm_time = <span class=\"token-number\">6</span>*<span class=\"token-number\">60</span>*<span class=\"token-number\">60</span> + <span class=\"token-number\">55</span>*<span class=\"token-number\">60</span>\n<span class=\"token-meta\">&gt;&gt;&gt; </span>alarm_time_real = <span class=\"token-number\">7</span>*<span class=\"token-number\">60</span>*<span class=\"token-number\">60</span> + <span class=\"token-number\">12</span>*<span class=\"token-number\">60</span>\n<span class=\"token-meta\">&gt;&gt;&gt; </span>alarm_time_real - alarm_time\n<span class=\"token-number\">1020</span>\n</code></pre></div>\n<p>The total time was off by about 1020 seconds. Give or take 60, as my phone\ndoesn't display the seconds of the time.</p>\n<blockquote>\n<p>Since I'm using a Samsung SM-B310E, I assumed it uses seconds to store the\ntime. You can't even see seconds noted anywhere so I feel this is a normal\nassumption. Even if it is false, the math still adds up for milliseconds.</p>\n</blockquote>\n<p>Wow, I thought. That's really close to 1024 (which is 2 to the power of 10).\nMaybe the 11th bit got flipped, making it increment 1024?</p>\n<div class=\"transform-[rotateX(180deg)] overflow-x-auto\"><pre class=\"transform-[rotateX(180deg)]\"><code class=\"language-py hljs language-python\"><span class=\"token-meta\">&gt;&gt;&gt; </span><span class=\"token-built_in\">bin</span>(alarm_time)\n<span class=\"token-string\">'0b110000101000100'</span>\n<span class=\"token-meta\">&gt;&gt;&gt; </span>alarm_time |= <span class=\"token-number\">1</span> &lt;&lt; <span class=\"token-number\">10</span>\n<span class=\"token-meta\">&gt;&gt;&gt; </span><span class=\"token-built_in\">bin</span>(alarm_time)\n<span class=\"token-string\">'0b110010101000100'</span>\n<span class=\"token-meta\">&gt;&gt;&gt; </span>alarm_time - alarm_time_real\n-<span class=\"token-number\">4</span>\n</code></pre></div>\n<p>Aha! So the 11th bit got flipped by something. And that something was probably a\ncosmic ray.</p>\n<blockquote>\n<p><strong>EDIT:</strong> It was not a cosmic ray. As pointed out by\n<a href=\"https://lobste.rs/s/jb1o6q/cosmic_drift#c_1ztluj\">@BenjaminRi's comment on lobste.rs</a>\nit most likely was either a bug or storage corruption as the alarm ran late\nthe next day. You should still create more than one alarm just in case if you\nare using a phone prone to this, however.</p>\n</blockquote>\n<p>My main takeaway from this event is to create more than one alarm, for extra\nredundancy. Who knew being prone to sleeping in could save you from your alarm\ngetting shifted 12 minutes into the future :^).</p>\n\n\n  \n","tags":["time","unix-timestamps"],"date_published":"2024-04-02T00:00:00.000Z"},{"id":"https://rgbcu.be/blog/swap-i/","url":"https://rgbcu.be/blog/swap-i/","title":"Swap the ı and i key on your keyboard for faster modal editing","content_html":"\n  <p>If you have ever used a Turkish-Q keyboard in combination with a modal editor\nbefore, you might have noticed that the <code>i</code> key is pretty far off to the side:</p>\n<p><img src=\"https://rgbcu.be/assets/images/turkish-q-i.webp\" alt=\"The placement of the  key on the Turkish-Q layout\"></p>\n<p>This blog post will guide you on how to swap the <code>ı</code> key with the <code>i</code> key (but\nnot the <code>I</code> and <code>İ</code> keys). This will be a great change if you write primarily in\nEnglish but need the Turkish symbols sometimes.</p>\n<blockquote>\n<p>There is <code>tr(us)</code> in the <code>xkeyboard_config</code> package that does something\nsimilar to this:</p>\n<div class=\"transform-[rotateX(180deg)] overflow-x-auto\"><pre class=\"transform-[rotateX(180deg)]\"><code class=\"language-cpp hljs\"><span class=\"token-comment\">// The basic Turkish layout with \"i\" and \"ı\" swapped.</span>\n<span class=\"token-comment\">// Originally by Ali Riza KESKIN &lt;parduscix@yandex.ru&gt;, 2021.</span>\npartial\nxkb_symbols <span class=\"token-string\">\"us\"</span> {\n\n    include <span class=\"token-string\">\"tr(basic)\"</span>\n\n    name[Group1]=<span class=\"token-string\">\"Turkish (i and ı swapped)\"</span>;\n\n    key &lt;AC11&gt;  { type[group1] = <span class=\"token-string\">\"FOUR_LEVEL_SEMIALPHABETIC\"</span>,\n                  [          i,         I,     paragraph,          none ]};\n    key &lt;AD08&gt;  { type[group1] = <span class=\"token-string\">\"FOUR_LEVEL_SEMIALPHABETIC\"</span>,\n                  [  idotless,  Iabovedot,    apostrophe,    dead_caron ]};\n};\n</code></pre></div>\n<p>However, this only swaps the uppercase letters, so the <code>i</code> key is unchanged\nbut the uppercase of that key is <code>I</code> like in English. However, this is usually\nnot desired as this still reduces your typing speed (as the <code>iI</code> key is too\nfar).</p>\n</blockquote>\n<p>Let's create our own layout that does something similar but swaps the lowercase\nletters instead. Here is the code for that:</p>\n<div class=\"transform-[rotateX(180deg)] overflow-x-auto\"><pre class=\"transform-[rotateX(180deg)]\"><code class=\"language-cpp hljs\"><span class=\"token-keyword\">default</span> partial\nxkb_symbols <span class=\"token-string\">\"basic\"</span> {\n    include <span class=\"token-string\">\"tr(basic)\"</span>\n\n    name[Group1]=<span class=\"token-string\">\"Turkish (i and ı swapped correctly)\"</span>;\n\n    key &lt;AC11&gt;  { type[group1] = <span class=\"token-string\">\"FOUR_LEVEL_SEMIALPHABETIC\"</span>,\n                [  idotless, Iabovedot, paragraph , none       ]};\n    key &lt;AD08&gt;  { type[group1] = <span class=\"token-string\">\"FOUR_LEVEL_SEMIALPHABETIC\"</span>,\n                [  i       , I        , apostrophe, dead_caron ]};\n};\n</code></pre></div>\n<p>The <code>default</code> key is needed because we are going to make this a standalone file.\nSave this to <code>~/.config/xkb/symbols/tr-swapped-i</code> and you can tell your WM/DE to\nuse the <code>tr-swapped-i</code> XKB layout.</p>\n<p>In Hyprland (The WM I use) you can do it like this:</p>\n<div class=\"transform-[rotateX(180deg)] overflow-x-auto\"><pre class=\"transform-[rotateX(180deg)]\"><code class=\"language-py hljs language-python\"><span class=\"token-comment\"># In ~/.config/hypr/hyprland.conf</span>\n<span class=\"token-built_in\">input</span> {\n  kb_layout = <span class=\"token-string\">\"tr-swapped-i\"</span>\n}\n</code></pre></div>\n<p>That should swap the <code>ı</code> and <code>i</code> keys on your WM/DE successfully. However, we\nare not done yet.</p>\n<p>The TTY (swap to TTY 2 by doing <code>CTRL-ALT-F2</code>) still doesn't use this layout,\nwhich is a problem. And it seems that the format the TTY uses is a little\ndifferent. Looking at the <code>kbd</code> package, it uses a format called <code>.map</code>.</p>\n<p>Here is a <code>.map</code> file that overrides the <code>trq</code> layout and swaps the <code>ı</code> and <code>i</code>\nkeys:</p>\n<div class=\"transform-[rotateX(180deg)] overflow-x-auto\"><pre class=\"transform-[rotateX(180deg)]\"><code class=\"language-py hljs language-python\">include <span class=\"token-string\">\"/usr/share/keymaps/i386/qwerty/trq.map\"</span>\n\nkeycode <span class=\"token-number\">23</span> = i\n  altgr       keycode <span class=\"token-number\">23</span> = +icircumflex\n  altgr shift keycode <span class=\"token-number\">23</span> = +Icircumflex\n\nkeycode <span class=\"token-number\">40</span> = +dotlessi +Idotabove\n</code></pre></div>\n<p>Save it to the directory where you store your maps.</p>\n<p>Note that the path <code>/usr/share/keymaps</code> might differ based on your distro. You\ncan check what it is it by doing <code>man loadkeys</code> and scrolling to the bottom.</p>\n<p>After that, consult your distro's docs on how to change the system keyboard\nlocale.</p>\n<p>This is how it is done on NixOS:</p>\n<div class=\"transform-[rotateX(180deg)] overflow-x-auto\"><pre class=\"transform-[rotateX(180deg)]\"><code class=\"language-nix hljs\">{ pkgs, ... }: {\n  <span class=\"token-attr\">console.keyMap</span> <span class=\"token-operator\">=</span> pkgs.writeText <span class=\"token-string\">\"trq-swapped-i.map\"</span> <span class=\"token-string\">''\n    include \"<span class=\"token-subst\">${pkgs.kbd}</span>/share/keymaps/i386/qwerty/trq.map\"\n\n    keycode 23 = i\n      altgr       keycode 23 = +icircumflex\n      altgr shift keycode 23 = +Icircumflex\n\n    keycode 40 = +dotlessi +Idotabove\n  ''</span>;\n}\n</code></pre></div>\n<p>And this is how it is done on Arch:</p>\n<div class=\"transform-[rotateX(180deg)] overflow-x-auto\"><pre class=\"transform-[rotateX(180deg)]\"><code class=\"language-sh hljs language-bash\"><span class=\"token-built_in\">cat</span> &lt;&lt; :end\ninclude <span class=\"token-string\">\"/usr/share/kbd/keymaps/i386/qwerty/trq.map\"</span>\n\nkeycode 23 = i\n  altgr       keycode 23 = +icircumflex\n  altgr <span class=\"token-built_in\">shift</span> keycode 23 = +Icircumflex\n\nkeycode 40 = +dotlessi +Idotabove\n:end &gt; /usr/share/kbd/keymaps/i386/qwerty/trq-swapped-i.map\n\nlocalectl set-keymap --no-convert tr-swapped-i\n</code></pre></div>\n<p>After that, your TTY should also use this locale.</p>\n<p>Gotta love having to mess with deep parts of your system because the Turkish-Q\nlayout did the big mistake of reusing the <code>i</code> key as <code>ı</code>!</p>\n<p>At least I'll type and edit faster now...</p>\n<p><a href=\"https://github.com/RGBCube/NCC/commit/6d18066eb5ccefa4539205f3d6721e4a8ff8b97e\">Here is the commit where I implemented this, might be worth a look if you use NixOS.</a></p>\n\n\n  \n","tags":["localisation","modal-editors"],"date_published":"2024-05-20T00:00:00.000Z"},{"id":"https://rgbcu.be/blog/gitignore/","url":"https://rgbcu.be/blog/gitignore/","title":".gitignore is inherently Sisyphean","content_html":"\n  <p>You just started a new project. You ran <code>cargo init</code>, <code>poetry init</code> and\n<code>go mod init</code>.</p>\n<p>Those commands created the necessary files to work, it also added the following\nlines to your .gitignore:</p>\n<div class=\"transform-[rotateX(180deg)] overflow-x-auto\"><pre class=\"transform-[rotateX(180deg)]\"><code class=\"language-ignore\">target\n__pycache__\nbin\n</code></pre></div>\n<p>All great. You continue implementing features, and when the time comes, you\npublish your project to your Git hosting platform of choice.</p>\n<p>People start to get interested in your project. One even decides that he's going\nto implement a new feature! Literally free work done for you!</p>\n<p>Alright. That person uses his code editor and tools bundled with his operating\nsystem to implement a very cool new feature. He then submits the merge request.</p>\n<p>You start reviewing the code and notice a file quite out of place: <code>.DS_Store</code>.\nYou ask the person what it is, he says he has no clue.</p>\n<p><img src=\"https://rgbcu.be/assets/images/github-ds-store-mr-list.webp\" alt=\"Hundreds of thousands of merge requests on GitHub trying\nto gitignore .DS_Store\"></p>\n<p>Whatever. You just delete the file from the branch and add the file's name to\nthe repositories gitignore:</p>\n<div class=\"transform-[rotateX(180deg)] overflow-x-auto\"><pre class=\"transform-[rotateX(180deg)]\"><code class=\"language-ignore\">target\n__pycache__\nbin\n.DS_Store\n</code></pre></div>\n<p>Nice. Now the code is on master, and your repository only contains relevant\ninformation.</p>\n<p>Then, someone using an IDE created using web technologies submits another merge\nrequest. You look at it, and see that there is a whole directory that is\nirrelevant. You tell that person to delete the directory from the branch and add\nit to the gitignore. The gitignore lives on:</p>\n<div class=\"transform-[rotateX(180deg)] overflow-x-auto\"><pre class=\"transform-[rotateX(180deg)]\"><code class=\"language-ignore\">target\n__pycache__\nbin\n.DS_Store\n.vscode\n</code></pre></div>\n<p>Then, someone that uses IntelliJ IDEA commits five hundred XML files and the\n<code>.idea</code> directory. You repeat this process:</p>\n<div class=\"transform-[rotateX(180deg)] overflow-x-auto\"><pre class=\"transform-[rotateX(180deg)]\"><code class=\"language-ignore\">target\n__pycache__\nbin\n.DS_Store\n.vscode\n.idea\n</code></pre></div>\n<p>Years pass. Now your gitignore is hundreds of lines long, yet people keep\naccidentally committing in test scripts, foo, a, qux, data.tar.gz, start.sh,\nbin-release, cat, asd, fgsgskfh.</p>\n<p>Hell. You feel like a mythic god undergoing punishment for cheating death and\ndeceiving the underworld.</p>\n<p><img src=\"https://rgbcu.be/assets/images/sisyphus-ds-store.webp\" alt=\"Sisyphus pushing up a boulder that has .DS_Store written\non it\"></p>\n<p>How will you escape this endless loop of ignoring files that sneak in? Maybe by\neducating every single merge request author? Nope, that definitely won't work,\nthere should be a way to automatically handle this with tooling, rather than\nsubjective human communication.</p>\n<p>Luckily, you realize that you can turn the blacklist of files (the gitignore)\ninto a whitelist, by just ignoring everything and manually un-ignoring desired\nfiles. You change your gitignore to this:</p>\n<div class=\"transform-[rotateX(180deg)] overflow-x-auto\"><pre class=\"transform-[rotateX(180deg)]\"><code class=\"language-ignore\">*\n\n!.gitignore\n\n# whitelist `src` directories and their children, regardless of place\n!src/\n!src/**/\n!src/**/*.rs\n!Cargo.{toml,lock}\n\n# whitelist root `pysrc` directory\n!/pysrc/\n!/pysrc/*.py\n!pyproject.toml\n!poetry.lock\n\n!/cmd/\n!/cmd/*.go\n!main.go\n!go.{mod,sum}\n\n!/docs/\n!/docs/*.md\n</code></pre></div>\n<p>Now, nobody can accidentally commit undesired files, as git automatically\nignores them all and only allows the files that are explicitly whitelisted. It's\nalso future proof, future proof until an IDE decides to use the <code>src/ide.rs</code>\nfile as a convenient way of storing project specific configuration. And\nhopefully that future never comes.</p>\n<p>You feel relieved.</p>\n\n\n  \n","tags":["vcs"],"date_published":"2024-09-30T00:00:00.000Z"},{"id":"https://rgbcu.be/blog/why-cores/","url":"https://rgbcu.be/blog/why-cores/","title":"Why more /sys/class/hwmon//temp_label than CPU cores?","content_html":"\n  <p>So, I am currently working on a Linux tool+daemon to replace\n<a href=\"https://github.com/AdnanHodzic/auto-cpufreq\"><code>auto-cpufreq</code></a> with a more\nefficient, tunable and observable alternative.</p>\n<p>And obviously, to tune a CPU well, knowing how hot it is is a requirement.</p>\n<p>Then, how do you actually see how hot your CPU is in Linux? Using\n<code>/sys/class/hwmon</code> of course.</p>\n<p>This path contains hardware monitoring devices. In the laptop I'm testing this\non, <code>hwmon4</code> under this directory corresponds to the CPU.</p>\n<p>Let's run a <code>tree</code>:</p>\n<div class=\"transform-[rotateX(180deg)] overflow-x-auto\"><pre class=\"transform-[rotateX(180deg)]\"><code class=\"language-no-highlight\">/sys/class/hwmon/hwmon4 -&gt; ../../devices/platform/coretemp.0/hwmon/hwmon4\n├── device -&gt; ../../../coretemp.0\n│   ├── driver_override\n│   ├── hwmon\n│   ├── modalias\n│   ├── power\n│   ├── subsystem -&gt; ../../../bus/platform\n│   └── uevent\n├── name\n├── power\n│   ├── autosuspend_delay_ms\n│   ├── control\n│   ├── runtime_active_time\n│   ├── runtime_status\n│   └── runtime_suspended_time\n├── subsystem -&gt; ../../../../../class/hwmon  [recursive, not followed]\n├── temp10_crit\n├── temp10_crit_alarm\n├── temp10_input\n├── temp10_label\n├── temp10_max\n├── temp14_crit\n├── temp14_crit_alarm\n├── temp14_input\n├── temp14_label\n├── temp14_max\n├── temp18_crit\n├── temp18_crit_alarm\n├── temp18_input\n├── temp18_label\n├── temp18_max\n├── temp1_crit\n├── temp1_crit_alarm\n├── temp1_input\n├── temp1_label\n├── temp1_max\n├── temp22_crit\n├── temp22_crit_alarm\n├── temp22_input\n├── temp22_label\n├── temp22_max\n├── temp26_crit\n├── temp26_crit_alarm\n├── temp26_input\n├── temp26_label\n├── temp26_max\n├── temp2_crit\n├── temp2_crit_alarm\n├── temp2_input\n├── temp2_label\n├── temp2_max\n├── temp30_crit\n├── temp30_crit_alarm\n├── temp30_input\n├── temp30_label\n├── temp30_max\n├── temp34_crit\n├── temp34_crit_alarm\n├── temp34_input\n├── temp34_label\n├── temp34_max\n├── temp35_crit\n├── temp35_crit_alarm\n├── temp35_input\n├── temp35_label\n├── temp35_max\n├── temp36_crit\n├── temp36_crit_alarm\n├── temp36_input\n├── temp36_label\n├── temp36_max\n├── temp37_crit\n├── temp37_crit_alarm\n├── temp37_input\n├── temp37_label\n├── temp37_max\n├── temp38_crit\n├── temp38_crit_alarm\n├── temp38_input\n├── temp38_label\n├── temp38_max\n├── temp39_crit\n├── temp39_crit_alarm\n├── temp39_input\n├── temp39_label\n├── temp39_max\n├── temp40_crit\n├── temp40_crit_alarm\n├── temp40_input\n├── temp40_label\n├── temp40_max\n├── temp41_crit\n├── temp41_crit_alarm\n├── temp41_input\n├── temp41_label\n├── temp41_max\n├── temp42_crit\n├── temp42_crit_alarm\n├── temp42_input\n├── temp42_label\n├── temp42_max\n├── temp43_crit\n├── temp43_crit_alarm\n├── temp43_input\n├── temp43_label\n├── temp43_max\n├── temp44_crit\n├── temp44_crit_alarm\n├── temp44_input\n├── temp44_label\n├── temp44_max\n├── temp45_crit\n├── temp45_crit_alarm\n├── temp45_input\n├── temp45_label\n├── temp45_max\n├── temp46_crit\n├── temp46_crit_alarm\n├── temp46_input\n├── temp46_label\n├── temp46_max\n├── temp47_crit\n├── temp47_crit_alarm\n├── temp47_input\n├── temp47_label\n├── temp47_max\n├── temp48_crit\n├── temp48_crit_alarm\n├── temp48_input\n├── temp48_label\n├── temp48_max\n├── temp49_crit\n├── temp49_crit_alarm\n├── temp49_input\n├── temp49_label\n├── temp49_max\n├── temp6_crit\n├── temp6_crit_alarm\n├── temp6_input\n├── temp6_label\n├── temp6_max\n└── uevent\n</code></pre></div>\n<p>Let's <code>cat</code> all the <code>_label</code> files:</p>\n<div class=\"transform-[rotateX(180deg)] overflow-x-auto\"><pre class=\"transform-[rotateX(180deg)]\"><code class=\"language-no-highlight\">/sys/class/hwmon/hwmon4/temp1_label:\nPackage id 0\n/sys/class/hwmon/hwmon4/temp2_label:\nCore 0\n/sys/class/hwmon/hwmon4/temp6_label:\nCore 4\n/sys/class/hwmon/hwmon4/temp10_label:\nCore 8\n/sys/class/hwmon/hwmon4/temp14_label:\nCore 12\n/sys/class/hwmon/hwmon4/temp18_label:\nCore 16\n/sys/class/hwmon/hwmon4/temp22_label:\nCore 20\n/sys/class/hwmon/hwmon4/temp26_label:\nCore 24\n/sys/class/hwmon/hwmon4/temp30_label:\nCore 28\n/sys/class/hwmon/hwmon4/temp34_label:\nCore 32\n/sys/class/hwmon/hwmon4/temp35_label:\nCore 33\n/sys/class/hwmon/hwmon4/temp36_label:\nCore 34\n/sys/class/hwmon/hwmon4/temp37_label:\nCore 35\n/sys/class/hwmon/hwmon4/temp38_label:\nCore 36\n/sys/class/hwmon/hwmon4/temp39_label:\nCore 37\n/sys/class/hwmon/hwmon4/temp40_label:\nCore 38\n/sys/class/hwmon/hwmon4/temp41_label:\nCore 39\n/sys/class/hwmon/hwmon4/temp42_label:\nCore 40\n/sys/class/hwmon/hwmon4/temp43_label:\nCore 41\n/sys/class/hwmon/hwmon4/temp44_label:\nCore 42\n/sys/class/hwmon/hwmon4/temp45_label:\nCore 43\n/sys/class/hwmon/hwmon4/temp46_label:\nCore 44\n/sys/class/hwmon/hwmon4/temp47_label:\nCore 45\n/sys/class/hwmon/hwmon4/temp48_label:\nCore 46\n/sys/class/hwmon/hwmon4/temp49_label:\nCore 47\n</code></pre></div>\n<p>Notice something? I do:</p>\n<ul>\n<li><code>temp1_label</code> is <code>Package id 0</code> - what is that?</li>\n<li>The core numbers make no sense. This device only has 32 cores, there shouldn't\nbe any gaps within numbers and the numbers shouldn't go that high.</li>\n</ul>\n<p>The explanation for the first point is simple, looking at the kernel\n<a href=\"https://github.com/torvalds/linux/blob/ec7714e4947909190ffb3041a03311a975350fe0/drivers/hwmon/coretemp.c#L348\"><code>coretemp.c</code></a>\nimplementation, we can see that it is the temperature of the CPU as a whole:</p>\n<div class=\"transform-[rotateX(180deg)] overflow-x-auto\"><pre class=\"transform-[rotateX(180deg)]\"><code class=\"language-c hljs\"><span class=\"token-type\">static</span> <span class=\"token-type\">ssize_t</span> <span class=\"token-title function_\">show_label</span><span class=\"token-params\">(\n  <span class=\"token-keyword\">struct</span> device *dev,\n  <span class=\"token-keyword\">struct</span> device_attribute *devattr,\n  <span class=\"token-type\">char</span> *buf\n)</span> {\n  <span class=\"token-class\"><span class=\"token-keyword\">struct</span> <span class=\"token-title\">platform_data</span> *<span class=\"token-title\">pdata</span> =</span> dev_get_drvdata(dev);\n  <span class=\"token-class\"><span class=\"token-keyword\">struct</span> <span class=\"token-title\">temp_data</span> *<span class=\"token-title\">tdata</span> =</span> container_of(devattr, <span class=\"token-keyword\">struct</span> temp_data, sd_attrs[ATTR_LABEL]);\n\n  <span class=\"token-keyword\">if</span> (is_pkg_temp_data(tdata))\n    <span class=\"token-keyword\">return</span> <span class=\"token-built_in\">sprintf</span>(buf, <span class=\"token-string\">\"Package id %u\\n\"</span>, pdata-&gt;pkg_id);\n\n  <span class=\"token-keyword\">return</span> <span class=\"token-built_in\">sprintf</span>(buf, <span class=\"token-string\">\"Core %u\\n\"</span>, tdata-&gt;cpu_core_id);\n}\n</code></pre></div>\n<p>That leaves us the second question. Why do we have CPUs 1, 2, 6, 10, 14, 18, 22,\n26, 30, 34-49, instead of the expected 0-31?</p>\n<p>It turns out that CPU It's common for chip manufacturers to disable faulty or\ndegraded cores before shipping. Or if a lower tier SKU<sup><a id=\"ref:1\" href=\"https://rgbcu.be/blog/why-cores/#fn:1\">^1</a></sup> is\nselling more, cores of higher tier SKUs are disabled to match expectations.</p>\n<p>This process is called <code>binning</code>, and it exists to not waste silicon. Since\nsilicon manufacturing is not perfect, some CPUs are more faulty, thus slower\nthan others. The process determines how faulty a CPU is and sorts them into\n\"bins\". This is also why lower tier CPUs who are almost exactly the same as\nhigher tier CPUs exist.</p>\n<p>The likelihood of faulty silicon also increases with the smaller the\narchitecture size gets (the Apple M4 is 4nm, which is crazy), so this method of\nrecycling worse chips is becoming much more valuable by the day.</p>\n<p>So, in summary the 32 core CPU I was testing this on was most likely just the 64\ncore version with a bunch of the cores disabled.</p>\n\n\n  <ol id=\"footnotes\"><hr><li id=\"fn:1\">Stock Keeping Unit<sub><a href=\"https://rgbcu.be/blog/why-cores/#ref:1\">..?</a></sub></li></ol>\n","tags":["hardware"],"date_published":"2025-06-05T00:00:00.000Z"},{"id":"https://rgbcu.be/blog/nginix/","url":"https://rgbcu.be/blog/nginix/","title":"NgiNix","content_html":"\n  <p><a href=\"https://nginx.org/\">Nginx</a> automatically creates\n<a href=\"https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/ETag\">ETag</a>\nheaders for static files. But it does it in such a way that is suboptimal.</p>\n<p>I'll explain why that is, and how the\n<a href=\"https://github.com/NixOS/nixpkgs/blob/8c742e834fd39a9b912a9237b5a6453cb4ec222b/pkgs/servers/http/nginx/generic.nix\">Nixpkgs distribution of Nginx</a>\ndoes it better.</p>\n<p>First, let's take a look at how\n<a href=\"https://github.com/nginx/nginx/blob/5b8a5c08ce28639e788734b2528faad70baa113c/src/http/ngx_http_core_module.c#L1681-L1716\">Nginx sets Etags</a>:</p>\n<div class=\"transform-[rotateX(180deg)] overflow-x-auto\"><pre class=\"transform-[rotateX(180deg)]\"><code class=\"language-c hljs\"><span class=\"token-comment\">// in ngx_http_set_etag @ src/http/ngx_http_core_module.c:1681</span>\n\n<span class=\"callout\">1</span>\netag = ngx_list_push(&amp;r-&gt;headers_out.headers);\n<span class=\"token-keyword\">if</span> (etag == <span class=\"token-literal\">NULL</span>) {\n    <span class=\"token-keyword\">return</span> NGX_ERROR;\n}\n\n<span class=\"callout\">2</span>\netag-&gt;hash = <span class=\"token-number\">1</span>;\netag-&gt;next = <span class=\"token-literal\">NULL</span>;\nngx_str_set(&amp;etag-&gt;key, <span class=\"token-string\">\"ETag\"</span>);\n\n<span class=\"callout\">3</span>\netag-&gt;value.data = ngx_pnalloc(r-&gt;pool, NGX_OFF_T_LEN + NGX_TIME_T_LEN + <span class=\"token-number\">3</span>);\n<span class=\"token-keyword\">if</span> (etag-&gt;value.data == <span class=\"token-literal\">NULL</span>) {\n    etag-&gt;hash = <span class=\"token-number\">0</span>;\n    <span class=\"token-keyword\">return</span> NGX_ERROR;\n}\n\n<span class=\"callout\">4</span>\netag-&gt;value.len = ngx_sprintf(etag-&gt;value.data, <span class=\"token-string\">\"\\\"%xT-%xO\\\"\"</span>,\n                              r-&gt;headers_out.last_modified_time,\n                              r-&gt;headers_out.content_length_n)\n                  - etag-&gt;value.data;\n</code></pre></div>\n<ol>\n<li>\n<p>Here we create an empty header entry in the requests's outgoing headers.</p>\n</li>\n<li>\n<p>We initialize it, and set its key to \"ETag\".</p>\n</li>\n<li>\n<p>We then allocate memory for the ETag value from the current request's arena.\nThe size is calculated to be large enough to hold the timestamp\n(<code>NGX_TIME_T_LEN</code>), the content length (<code>NGX_OFF_T_LEN</code>), and extra\ncharacters (two quotes \" and a hyphen -).</p>\n</li>\n<li>\n<p>And then we fill the header's value to a string like <code>\"5f7e1b2a-1c8f\"</code> using\nthe last modified time of the file being served and its content length.\nSomething weird is <code>- etag-&gt;value.data</code>, which is required because\n<code>ngx_sprintf</code> returns a pointer to the end of the string. We do it to get the\nstart.</p>\n</li>\n</ol>\n<p>Result: The outgoing request now contains an <code>Etag</code> header, which is based off\nof the file's length and last modified date.</p>\n<a href=\"https://rgbcu.be/blog/nginix/#but-wait\"><h2 id=\"but-wait\">But wait...</h2></a>\n<p>What if we use a tool that aims to produce\n<a href=\"https://reproducible-builds.org/\">reproducible build artifacts</a> to build our\nwebsite, and we just so happen to modify a file in such a way that doesn't\nchange the size of its contents? (more common than you think)</p>\n<blockquote>\n<p>For example:\nNix<sup><a id=\"ref:1\" href=\"https://rgbcu.be/blog/nginix/#fn:1\">^1</a></sup>,\nwhich <em>always</em> sets the last-modified timestamps for files under the Nix\nstore<sup><a id=\"ref:2\" href=\"https://rgbcu.be/blog/nginix/#fn:2\">^2</a></sup> to <code>1</code>, which corresponds to January 1st, 1970,\n00:00:01 UTC.</p>\n</blockquote>\n<p>In that case, neither the last modified date or the content length would change.\nSo the <code>ETag</code> header would stay <strong>constant, which would make the client assume\nthe asset has not changed</strong>.</p>\n<a href=\"https://rgbcu.be/blog/nginix/#silently-broken\"><h2 id=\"silently-broken\">Silently Broken</h2></a>\n<p>The caching on your website being silently broken is a monumental issue, and has\nto be fixed. The way Nixpkgs does it is through\n<a href=\"https://github.com/NixOS/nixpkgs/blob/8c742e834fd39a9b912a9237b5a6453cb4ec222b/pkgs/servers/http/nginx/nix-etag-1.15.4.patch\">this patch:</a></p>\n<div class=\"transform-[rotateX(180deg)] overflow-x-auto\"><pre class=\"transform-[rotateX(180deg)]\"><code class=\"language-patch hljs language-diff\">This patch makes it possible to serve static content from Nix store paths, by\nusing the hash of the store path for the ETag header.\n\n<span class=\"token-comment\">diff --git a/src/http/ngx_http_core_module.c b/src/http/ngx_http_core_module.c</span>\n<span class=\"token-comment\">index 97a91aee2..2d07d71e6 100644</span>\n<span class=\"token-comment\">--- a/src/http/ngx_http_core_module.c</span>\n<span class=\"token-comment\">+++ b/src/http/ngx_http_core_module.c</span>\n<span class=\"token-meta\">@@ -1676,6 +1676,8 @@</span> ngx_http_set_etag(ngx_http_request_t *r)\n {\n     ngx_table_elt_t           *etag;\n     ngx_http_core_loc_conf_t  *clcf;\n     <span class=\"callout\">2</span>\n<span class=\"token-addition\">+    u_char                    *real, *ptr1, *ptr2;</span>\n<span class=\"token-addition\">+    ngx_err_t                 err;</span>\n\n     clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);\n\n<span class=\"token-meta\">@@ -1692,16 +1694,82 @@</span> ngx_http_set_etag(ngx_http_request_t *r)\n     etag-&gt;next = NULL;\n     ngx_str_set(&amp;etag-&gt;key, \"ETag\");\n\n     <span class=\"callout\">1</span>\n<span class=\"token-deletion\">-    etag-&gt;value.data = ngx_pnalloc(r-&gt;pool, NGX_OFF_T_LEN + NGX_TIME_T_LEN + 3);</span>\n<span class=\"token-deletion\">-    if (etag-&gt;value.data == NULL) {</span>\n<span class=\"token-deletion\">-        etag-&gt;hash = 0;</span>\n<span class=\"token-deletion\">-        return NGX_ERROR;</span>\n<span class=\"token-addition\">+    // Upstream nginx uses file mod timestamp and content-length for Etag, but</span>\n<span class=\"token-addition\">+    // files in the Nix store have their timestamps reset, so that doesn't work.</span>\n<span class=\"token-addition\">+    // Instead, when serving from the Nix store, we use the hash from the store</span>\n<span class=\"token-addition\">+    // path and content-length.</span>\n<span class=\"token-addition\">+    //</span>\n<span class=\"token-addition\">+    // Every file in under the given store path will share the same store path</span>\n<span class=\"token-addition\">+    // hash. It is fine to serve different resources with the same Etag, but</span>\n<span class=\"token-addition\">+    // different representations of the same resource (eg the same file, but</span>\n<span class=\"token-addition\">+    // gzip-compressed) should have different Etags. Thus, we also append</span>\n<span class=\"token-addition\">+    // content-length, which should be different when the response is compressed</span>\n<span class=\"token-addition\">+</span>\n<span class=\"token-addition\">+    err = ngx_errno; <span class=\"callout\">3</span></span>\n<span class=\"token-addition\">+    real = ngx_realpath(clcf-&gt;root.data, NULL); <span class=\"callout\">4</span></span>\n<span class=\"token-addition\">+    ngx_set_errno(err); <span class=\"callout\">5</span></span>\n<span class=\"token-addition\">+</span>\n<span class=\"token-addition\">+    #define NIX_STORE_DIR \"@nixStoreDir@\" <span class=\"callout\">6</span></span>\n<span class=\"token-addition\">+    #define NIX_STORE_LEN @nixStoreDirLen@</span>\n<span class=\"token-addition\">+</span>\n<span class=\"token-addition\">+    if (r-&gt;headers_out.last_modified_time == 1 <span class=\"callout\">7</span></span>\n<span class=\"token-addition\">+        &amp;&amp; real != NULL</span>\n<span class=\"token-addition\">+        &amp;&amp; !ngx_strncmp(real, NIX_STORE_DIR, NIX_STORE_LEN)</span>\n<span class=\"token-addition\">+        &amp;&amp; real[NIX_STORE_LEN] == '/'</span>\n<span class=\"token-addition\">+        &amp;&amp; real[NIX_STORE_LEN + 1] != '\\0')</span>\n<span class=\"token-addition\">+    {</span>\n<span class=\"token-addition\">+        // extract the hash from a path formatted like</span>\n<span class=\"token-addition\">+        // /nix/store/hashhere1234-pname-1.0.0</span>\n<span class=\"token-addition\">+        // +1 to skip the leading /</span>\n<span class=\"token-addition\">+        ptr1 = real + NIX_STORE_LEN + 1; <span class=\"callout\">8</span></span>\n<span class=\"token-addition\">+</span>\n<span class=\"token-addition\">+        ptr2 = (u_char *) ngx_strchr(ptr1, '-');</span>\n<span class=\"token-addition\">+</span>\n<span class=\"token-addition\">+        if (ptr2 == NULL) {</span>\n<span class=\"token-addition\">+            ngx_free(real);</span>\n<span class=\"token-addition\">+            etag-&gt;hash = 0;</span>\n<span class=\"token-addition\">+            return NGX_ERROR;</span>\n<span class=\"token-addition\">+        }</span>\n<span class=\"token-addition\">+</span>\n<span class=\"token-addition\">+        *ptr2 = '\\0'; <span class=\"callout\">9</span></span>\n<span class=\"token-addition\">+</span>\n<span class=\"token-addition\">+        // hash + content-length + quotes and hyphen. Note that the</span>\n<span class=\"token-addition\">+        // content-length part of the string can vary in length.</span>\n<span class=\"token-addition\">+        etag-&gt;value.data = ngx_pnalloc(r-&gt;pool, ngx_strlen(ptr1) + NGX_OFF_T_LEN + 3);</span>\n<span class=\"token-addition\">+</span>\n<span class=\"token-addition\">+        if (etag-&gt;value.data == NULL) {</span>\n<span class=\"token-addition\">+            ngx_free(real);</span>\n<span class=\"token-addition\">+            etag-&gt;hash = 0;</span>\n<span class=\"token-addition\">+            return NGX_ERROR;</span>\n<span class=\"token-addition\">+        }</span>\n<span class=\"token-addition\">+</span>\n<span class=\"token-addition\">+</span>\n<span class=\"token-addition\">+        // set value.data content to \"{hash}-{content-length}\" (including quote</span>\n<span class=\"token-addition\">+        // marks), and set value.len to the length of the resulting string</span>\n<span class=\"token-addition\">+        etag-&gt;value.len = ngx_sprintf(etag-&gt;value.data, \"\\\"\\%s-%xO\\\"\",</span>\n<span class=\"token-addition\">+                                      ptr1,</span>\n<span class=\"token-addition\">+                                      r-&gt;headers_out.content_length_n)</span>\n<span class=\"token-addition\">+                          - etag-&gt;value.data;</span>\n<span class=\"token-addition\">+</span>\n<span class=\"token-addition\">+        ngx_http_clear_last_modified(r);</span>\n<span class=\"token-addition\">+    } else { <span class=\"callout\">10</span></span>\n<span class=\"token-addition\">+        // outside of Nix store, use the upstream Nginx logic for etags</span>\n<span class=\"token-addition\">+</span>\n<span class=\"token-addition\">+        etag-&gt;value.data = ngx_pnalloc(r-&gt;pool, NGX_OFF_T_LEN + NGX_TIME_T_LEN + 3);</span>\n<span class=\"token-addition\">+</span>\n<span class=\"token-addition\">+        if (etag-&gt;value.data == NULL) {</span>\n<span class=\"token-addition\">+            ngx_free(real);</span>\n<span class=\"token-addition\">+            etag-&gt;hash = 0;</span>\n<span class=\"token-addition\">+            return NGX_ERROR;</span>\n<span class=\"token-addition\">+        }</span>\n<span class=\"token-addition\">+</span>\n<span class=\"token-addition\">+        etag-&gt;value.len = ngx_sprintf(etag-&gt;value.data, \"\\\"%xT-%xO\\\"\",</span>\n<span class=\"token-addition\">+                                      r-&gt;headers_out.last_modified_time,</span>\n<span class=\"token-addition\">+                                      r-&gt;headers_out.content_length_n)</span>\n<span class=\"token-addition\">+                          - etag-&gt;value.data;</span>\n     }\n\n<span class=\"token-deletion\">-    etag-&gt;value.len = ngx_sprintf(etag-&gt;value.data, \"\\\"%xT-%xO\\\"\",</span>\n<span class=\"token-deletion\">-                                  r-&gt;headers_out.last_modified_time,</span>\n<span class=\"token-deletion\">-                                  r-&gt;headers_out.content_length_n)</span>\n<span class=\"token-deletion\">-                      - etag-&gt;value.data;</span>\n<span class=\"token-addition\">+    ngx_free(real);</span>\n\n     r-&gt;headers_out.etag = etag;\n</code></pre></div>\n<p>The way this works is as follows:</p>\n<ol>\n<li>\n<p>The old logic, explained above is deleted.</p>\n</li>\n<li>\n<p>We create 4 new variables, 3 used for path manipulation and 1 for storing\n<code>errno</code>.</p>\n</li>\n<li>\n<p>Save the current value of <code>errno</code> (ugh) to a local.</p>\n</li>\n<li>\n<p>Then we try to resolve <code>clcf-&gt;root.data</code>. <code>clcf-&gt;root.data</code> is the path\nspecified in the root directive of the Nginx configuration and could be a\nsymbolic link (e.g., it could be <code>/var/www</code> which might be a symlink to a Nix\nstore path<sup><a id=\"ref:3\" href=\"https://rgbcu.be/blog/nginix/#fn:3\">^3</a></sup>). <code>ngx_realpath</code> resolves all symbolic\nlinks and returns the canonical, absolute path. This is necessary to reliably\ncheck if the path is inside the Nix store.</p>\n</li>\n<li>\n<p>Restore the value of <code>errno</code>, as the previous function call might have\nchanged it. We handle the error case of <code>ngx_realpath</code> later.</p>\n</li>\n<li>\n<p>These are <strong>Nix placeholders</strong>, aka they get replaced by the path of the Nix\nstore at build time. The reason we do not hardcode <code>/nix/store</code> is because\n<a href=\"https://nix.dev/manual/nix/2.29/store/store-path.html#store-directory\">Nix's store path, when on the local filesystem, is arbitrary</a>.\nThis makes our patch portable.</p>\n</li>\n<li>\n<p><strong>The main condition:</strong> We check if the <code>r-&gt;headers_out.last_modified_time</code>\nis <code>1</code>, which is what Nix sets file's last modified times to, we then check\nif the canonical path resolution has succeeded (<code>real != NULL</code>), if the\ncanonical path starts with the Nix store path (<code>NIX_STORE_DIR</code>), and if the\nstore path has anything after it (<code>@[next] != '/' &amp;&amp; @[next + 1] != NULL</code>).</p>\n</li>\n<li>\n<p>(If the condition is true) We then extract the hash. A path like\n<code>/nix/store/6bxcxc6xvg5xv70z55adcwhgik5m41a0-package-1.0.0/bin/package</code> gets\nturned into <code>6bxcxc6xvg5xv70z55adcwhgik5m41a0</code>.</p>\n</li>\n<li>\n<p>(If the condition is true) <em>SIDENOTE:</em> This is a nice hack for one of many\ndesign failures in C, this one being NULL-terminated strings. We just place a\n<code>0</code> byte at the index we want to end our string out for efficiency, instead\nof creating a copy.</p>\n</li>\n<li>\n<p>(If the condition is false) We continue on with the old logic, as explained\nat the top.</p>\n</li>\n</ol>\n<p>We then set the value of the <code>ETag</code> header to <code>\"{hash}-{content-length}\"</code>, the\nreason for the content length being explained in the comment in the patch:</p>\n<blockquote>\n<p>It is fine to serve different resources with the same Etag, but different\nrepresentations of the same resource (eg the same file, but gzip-compressed)\nshould have different Etags. Thus, we also append content-length, which should\nbe different when the response is compressed</p>\n</blockquote>\n<p>This way, by depending on the Nix store path instead of the last modification\ndate, we can serve files more reliably and with less<sup><a id=\"ref:4\" href=\"https://rgbcu.be/blog/nginix/#fn:4\">^4</a></sup> issues.</p>\n<p>I hope you learned something in this blog post, and perhaps a new way to\nfingerprint Nginx installs :).</p>\n<p><img src=\"https://rgbcu.be/assets/images/nixpkgs-nginx-etag.webp\" alt=\"Nixpkgs built Nginx-served ETag header\"></p>\n\n\n  <ol id=\"footnotes\"><hr><li id=\"fn:1\"><a href=\"https://nix.dev/\">A hermetic, declarative, reproducible and input(and soon content)-addressed build system.</a><sub><a href=\"https://rgbcu.be/blog/nginix/#ref:1\">..?</a></sub></li><hr><li id=\"fn:2\">The place Nix stores all build results, <em>usually</em> <code>/nix/store</code>. Every\n\"Nix store path\" is represented like so:\n<code>/nix/store/6bxcxc6xvg5xv70z55adcwhgik5m41a0-package-1.0.0</code>, where the hash is\nderived from its inputs<sub><a href=\"https://rgbcu.be/blog/nginix/#ref:2\">..?</a></sub></li><hr><li id=\"fn:3\">Please don't do this.<sub><a href=\"https://rgbcu.be/blog/nginix/#ref:3\">..?</a></sub></li><hr><li id=\"fn:4\">No, not none. You can\nstill cause unreproducible builds with Nix and have the store path not change,\nat least with input-addressed derivations (derivation = the smallest Nix build\nunit).<sub><a href=\"https://rgbcu.be/blog/nginix/#ref:4\">..?</a></sub></li></ol>\n","tags":["nginx","nix/os"],"date_published":"2025-06-09T00:00:00.000Z"},{"id":"https://rgbcu.be/blog/derive-broken/","url":"https://rgbcu.be/blog/derive-broken/","title":"#[derive(Clone)] is broken","content_html":"\n  <div class=\"transform-[rotateX(180deg)] overflow-x-auto\"><pre class=\"transform-[rotateX(180deg)]\"><code class=\"language-rs hljs language-rust\"><span class=\"token-keyword\">use</span> std::sync::Arc;\n\n<span class=\"token-keyword\">struct</span> <span class=\"token-title class_\">NoClone</span>;\n\n<span class=\"token-meta\">#[derive(Clone)]</span>\n<span class=\"token-keyword\">struct</span> <span class=\"token-title class_\">WrapArc</span>&lt;T&gt;(Arc&lt;T&gt;);\n\n<span class=\"token-keyword\">fn</span> <span class=\"token-title function_\">main</span>() {\n  <span class=\"token-keyword\">let</span> <span class=\"token-variable\">foo</span> = <span class=\"token-title function_ invoke__\">WrapArc</span>(Arc::<span class=\"token-title function_ invoke__\">new</span>(NoClone));\n  <span class=\"token-keyword\">let</span> <span class=\"token-variable\">foo_</span> = foo.<span class=\"token-title function_ invoke__\">clone</span>();\n}\n</code></pre></div>\n<p>Do you think this code should compile?</p>\n<p>What about the following code:</p>\n<div class=\"transform-[rotateX(180deg)] overflow-x-auto\"><pre class=\"transform-[rotateX(180deg)]\"><code class=\"language-rs hljs language-rust\"><span class=\"token-keyword\">struct</span> <span class=\"token-title class_\">AlwaysEq</span>&lt;T&gt;(T);\n\n<span class=\"token-keyword\">impl</span>&lt;T&gt; <span class=\"token-built_in\">PartialEq</span> <span class=\"token-keyword\">for</span> <span class=\"token-title class_\">AlwaysEq</span>&lt;T&gt; {\n   <span class=\"token-keyword\">fn</span> <span class=\"token-title function_\">eq</span>(&amp;<span class=\"token-keyword\">self</span>, _other: &amp;<span class=\"token-keyword\">Self</span>) <span class=\"token-punctuation\">-&gt;</span> <span class=\"token-type\">bool</span> {\n      <span class=\"token-literal\">true</span>\n   }\n}\n\n<span class=\"token-keyword\">impl</span>&lt;T&gt; <span class=\"token-built_in\">Eq</span> <span class=\"token-keyword\">for</span> <span class=\"token-title class_\">AlwaysEq</span>&lt;T&gt; {}\n\n<span class=\"token-keyword\">struct</span> <span class=\"token-title class_\">NotEq</span>;\n\n<span class=\"token-meta\">#[derive(PartialEq, Eq)]</span>\n<span class=\"token-keyword\">struct</span> <span class=\"token-title class_\">WrapAlwaysEq</span>&lt;T&gt;(AlwaysEq&lt;T&gt;);\n\n<span class=\"token-keyword\">fn</span> <span class=\"token-title function_\">assert_is_eq</span>(_: <span class=\"token-keyword\">impl</span> <span class=\"token-title class_\">Eq</span>) {}\n\n<span class=\"token-keyword\">fn</span> <span class=\"token-title function_\">main</span>() {\n   <span class=\"token-keyword\">let</span> <span class=\"token-variable\">x</span> = <span class=\"token-title function_ invoke__\">WrapAlwaysEq</span>(<span class=\"token-title function_ invoke__\">AlwaysEq</span>(NotEq));\n   <span class=\"token-title function_ invoke__\">assert_is_eq</span>(x);\n}\n</code></pre></div>\n<p>The second example is a bit far fetched, but you probably answered yes.</p>\n<p>But\n<a href=\"https://play.rust-lang.org/?version=stable&amp;mode=debug&amp;edition=2024&amp;gist=0d9a89eccb4773ce32e7a3cec5cf8603\">neither</a>\n<a href=\"https://play.rust-lang.org/?version=stable&amp;mode=debug&amp;edition=2024&amp;gist=30233aea9cf20c3299cd5c1fc6e7ec8c\">do</a>.</p>\n<a href=\"https://rgbcu.be/blog/derive-broken/#why-not\"><h1 id=\"why-not\">Why not?</h1></a>\n<p>The\n<a href=\"https://github.com/rust-lang/rust/blob/0c4fa2690de945f062668acfc36b3f8cfbd013e2/compiler/rustc_builtin_macros/src/deriving/clone.rs\">implementation of <code>#[derive(Clone)] in the Rust compiler</code></a>\ngenerates a <code>Clone</code> implementation with the following requirements on the\nderived type:</p>\n<ul>\n<li>All fields must be <code>Clone</code>.</li>\n<li>All generic parameters must be <code>Clone</code>.</li>\n</ul>\n<p>Can you spot the issue here? It's the latter requirement: <strong>we cannot just\nrequire all generic parameters to be <code>Clone</code>, as we cannot assume they are used\nin such a way that requires them to be cloned</strong>.<sup><a id=\"ref:1\" href=\"https://rgbcu.be/blog/derive-broken/#fn:1\">^1</a></sup></p>\n<p>This applies to practically all builtin derive traits, such as <code>Clone</code>,\n<code>PartialEq</code>, <code>Eq</code>, or\n<a href=\"https://play.rust-lang.org/?version=stable&amp;mode=debug&amp;edition=2024&amp;gist=b419e34c9f00d0fca92c40739f6c9fb2\">even <code>Debug</code></a>.</p>\n<a href=\"https://rgbcu.be/blog/derive-broken/#what-can-we-do-to-fix-this\"><h1 id=\"what-can-we-do-to-fix-this\">What can we do to fix this?</h1></a>\n<p>There are two solutions to this. Both require deleting that second requirement.</p>\n<a href=\"https://rgbcu.be/blog/derive-broken/#the-hard-way\"><h2 id=\"the-hard-way\">The hard way</h2></a>\n<p>We could create a Rust RFC, hopefully not bikeshed it to death, and get it\nstabilized in the next Rust edition as it is a breaking change.<sup><a id=\"ref:2\" href=\"https://rgbcu.be/blog/derive-broken/#fn:2\">^2</a></sup></p>\n<p>This would take 4+ years to stabilize and be available to everyone. That sucks,\nbut is the correct thing to do in the long-term.</p>\n<a href=\"https://rgbcu.be/blog/derive-broken/#the-quick-way\"><h2 id=\"the-quick-way\">The quick way</h2></a>\n<p>We can just write our own macro that generates the following code:</p>\n<div class=\"transform-[rotateX(180deg)] overflow-x-auto\"><pre class=\"transform-[rotateX(180deg)]\"><code class=\"language-rs hljs language-rust\"><span class=\"token-comment\">/* input */</span>\n<span class=\"token-meta\">#[derive(CustomClone)]</span>\n<span class=\"token-keyword\">struct</span> <span class=\"token-title class_\">WrapArc</span>&lt;T&gt;(Arc&lt;T&gt;);\n\n<span class=\"token-comment\">/* generated code */</span>\n<span class=\"token-keyword\">impl</span>&lt;T&gt; <span class=\"token-built_in\">Clone</span> <span class=\"token-keyword\">for</span> <span class=\"token-title class_\">WrapArc</span>&lt;T&gt;\n<span class=\"token-keyword\">where</span>\n   Arc&lt;T&gt;: <span class=\"token-built_in\">Clone</span>,\n   <span class=\"token-comment\">// and so on, `FieldType: DerivedTrait` for each field</span>\n{\n   <span class=\"token-comment\">// ...</span>\n}\n</code></pre></div>\n<p>This does the job correctly.</p>\n<p>And it's not even hard to do. I know people who do this internally in their\ncompany codebases - it's not much code.</p>\n<p>So I've <a href=\"https://github.com/JelteF/derive_more/issues/490\">opened an issue</a>\nabout replicating the builtin derive traits in a less restrictive and thus\ncorrect way in the <code>derive_more</code> crate's GitHub repository. The reason I chose\nthis crate is because it already has a lot of users and is the main place for\nderive implementations.</p>\n<p>Replicating already-existing behaviour of the std may not be in the scope of the\ncrate, which is a perfectly fine stance to take. If that doesn't get accepted,\nI'll probably create my own crate and release it on\n<a href=\"https://crates.io/\">crates.io</a>.</p>\n<p>Stay tuned, I'll update this blog post.</p>\n\n\n  <ol id=\"footnotes\"><hr><li id=\"fn:1\">The reason this is the way it\nis is probably because Rust's type system wasn't powerful enough for this to be\nimplemented back in the pre-1.0 days. Or it was just a simple oversight that got\nstabilized.<sub><a href=\"https://rgbcu.be/blog/derive-broken/#ref:1\">..?</a></sub></li><hr><li id=\"fn:2\">Surely it is a\nbreaking change, or the compiler people would've fixed it already. Right?<sub><a href=\"https://rgbcu.be/blog/derive-broken/#ref:2\">..?</a></sub></li></ol>\n","tags":["rust"],"date_published":"2025-07-04T00:00:00.000Z"},{"id":"https://rgbcu.be/blog/no-gnulib/","url":"https://rgbcu.be/blog/no-gnulib/","title":"Say NO to Gnulib","content_html":"\n  <p>If you are a GNU maintainer and are not willing to be criticized by people sick\nof your bullshit software, do not read this post.</p>\n<p>Also, obligatory notice on every post that touches GNU: No, nothing in this post\nis political. I am perfectly fine with using GNU licensed software, and find it\nmore favourable than MIT licensed software as it protects user freedoms. (My\npersonal choice however is MPL, as it doesn't deem your software dead on arrival\nat places that actually make money and feed hungry mouths)</p>\n<hr>\n<a href=\"https://rgbcu.be/blog/no-gnulib/#say-no-to-gnulib\"><h1 id=\"say-no-to-gnulib\">Say NO to Gnulib</h1></a>\n<p>The commonly overlooked but also very important argument for dropping GNU\ncoreutils (or any other GNU tool that depends on Gnulib) for anything other than\nthe most core-level bootstrapping needs is that it depends on Gnulib.</p>\n<p>Gnulib is a huge blob of C and 80 thousand lines of m4 that has crusted over 30+\nyears and is almost impossible to build correctly and hermetically, has to be\nmanually patched in every single program that embeds it (if trying to be\nhermetic, and correct), <sup><a id=\"ref:1\" href=\"https://rgbcu.be/blog/no-gnulib/#fn:1\">^1</a></sup> rewards bad OSes, makes good OSes shrivel in pain,\nmakes packagers go bald &amp; makes issues hard to diagnose and debug.</p>\n<p>It's so incredibly easy to build it wrong and create a shitty non-reproducible\ndistro (and it is built non-hermetically by default). At least in Rust (or in\nother ecosystems, such as Go) and the general ecosystem of Rust, <code>Cargo.toml</code> is\npretty well-defined and <code>build.rs</code> scripts don't do anything that insane. (Hell,\neven the <a href=\"https://lib.rs/cc\">C compilation tools used inside crates</a> are shared\ndeps and are well-defined).</p>\n<p>I don't trust the average distro to build any toolchain made by GNU properly,\nand I do not trust them to produce a proper set of system tools either because\nof Gnulib and other bad build tools.</p>\n<p>I do however trust the average distro (not Debian, they are lower than average\nand suck at packaging <a href=\"https://rgbcu.be/blog/no-gnulib/#debian-sucks-at-packaging\">(Yes, really.)</a>) to build\nUutils tools &amp; any other Rust tool correctly, because it is pretty relatively\nstraightforward compared to hundreds of thousands of lines of hard to audit &amp;\nancient m4. It also won't misbehave or segfault when you interact with a\nmisbehaving NTFS partition.</p>\n<p>I hope Uutils coreutils &amp; Uutils findutils and so on achieve near perfect\ncompliance so I do not need to serve GNU tools to my users.</p>\n<blockquote>\n<p>Also, keep in mind that it doesn't have to be Rust or uutils or anything that\nis shilled by the \"Rust Strike Force\" in particular. I would also welcome the\nGNU people fixing up their shitty build tooling &amp; making their software behave\ncorrectly. But that isn't happening any time soon, so we are starting from\nscratch &amp; fixing (quite) a few itches on the way.</p>\n</blockquote>\n<p>Read more about this on the\n<a href=\"https://gitlab.com/sortix/sortix/-/wikis/Gnulib\">Sortix wiki.</a></p>\n<a href=\"https://rgbcu.be/blog/no-gnulib/#debian-sucks-at-packaging\"><h1 id=\"debian-sucks-at-packaging\">Debian sucks at packaging</h1></a>\n<p>Yes, really.</p>\n<p>From\n<a href=\"https://www.phoronix.com/news/Debian-Orphans-Bcachefs-Tools\">Phoronix, on bcachefs-tools being \"impossible to maintain in a package collection\":</a></p>\n<blockquote>\n<p>So, back in April the Rust dependencies for bcachefs-tools in Debian didn’t at\nall match the build requirements. I got some help from the Rust team who says\nthat the common practice is to relax the dependencies of Rust software so that\nit builds in Debian. So errno, which needed the exact version 0.2, was relaxed\nso that it could build with version 0.4 in Debian, udev 0.7 was relaxed for\n0.8 in Debian, memoffset from 0.8.5 to 0.6.5, paste from 1.0.11 to 1.08 and\nbindgen from 0.69.9 to 0.66.</p>\n<p>I found this a bit disturbing, but it seems that some Rust people have lots of\nconfidence that if something builds, it will run fine. And at least it did\nbuild, and the resulting binaries did work, although I’m personally still not\nvery comfortable or confident about this approach (perhaps that might change\nas I learn more about Rust).</p>\n<p><strong>With that in mind, at this point you may wonder how any distribution could\nsanely package this. The problem is that they can’t. Fedora and other\ndistributions with stable releases take a similar approach to what we’ve done\nin Debian, while distributions with much more relaxed policies (like Arch)\ninclude all the dependencies as they are vendored upstream.</strong></p>\n</blockquote>\n<p>Incredibly foolish. You are not supposed to package every single crate manually,\nand you should not be anyway.</p>\n<p>The way you should package any programming language that has a widely used and\ngenerally well-defined and static build system is to generate (unlisted) package\ndefinitions from packages using a script or tool (such as\n<code>cargo metadata -&gt; parse json -&gt; puke out package manifests</code>), and only add\nextra configuration to packages that depend on anything external (such as a C\nlibrary, or CMake, or perl, or Go for some godforsaken reason (Why,\n<code>aws-lc-sys</code>, why?)).</p>\n<p>It's also not hard to use a dependency solver algorithm to try and deduplicate\nall crates required in the whole package repository using a pre-made library,\nsuch as <a href=\"https://lib.rs/crates/pubgrub\"><code>lib.rs/pubgrub</code></a>. You can have the best\nof all worlds.</p>\n<p>In general - Debian is a distro stuck in the 90s that assumes every language\necosystem is as fragmented, differing and inconsistent as C's. That's not the\ncase anymore, Debian maintainers should wake up from their slumber and modernize\ntheir tools, automating way more. The future is not C<sup><a id=\"ref:2\" href=\"https://rgbcu.be/blog/no-gnulib/#fn:2\">^2</a></sup>, and a good distro\ncannot assume that.</p>\n<blockquote>\n<p>And so, my adventure with bcachefs-tools comes to an end. I’d advise that if\nyou consider using bcachefs for any kind of production use in the near future,\nyou first consider how supportable it is long-term, and whether there’s really\nanyone at all that is succeeding in providing stable support for it.</p>\n</blockquote>\n<p>It's trivial to support!\n<a href=\"https://github.com/NixOS/nixpkgs/blob/6e987485eb2c77e5dcc5af4e3c70843711ef9251/pkgs/by-name/bc/bcachefs-tools/package.nix\">Here is what Nixpkgs, the biggest Nix package\ncollection, Nixpkgs, does</a> -\nlook, it's all 140 lines of code!</p>\n<a href=\"https://rgbcu.be/blog/no-gnulib/#but-it-can-be-even-better\"><h2 id=\"but-it-can-be-even-better\">But it can be even better</h2></a>\n<p>Nixpkgs has <a href=\"https://noogle.dev/f/pkgs/buildRustCrate\"><code>pkgs.buildRustCrate</code></a>,\nto build crates without Cargo, but currently doesn't use it for most packages,\nso it doesn't have crate-level incremental rebuilds. This may change in the\nfuture, and when it does, compiling Rust programs will take a fraction of the\ntime because you aren't building dependencies over and over and over and over\nagain, and can utilize <code>cache.nixos.org</code>, or any other cache.</p>\n<p>It will also decrease the amount of lines you'll have to write in Nixpkgs\npackage specifications, because you'll no longer have to specify all external\ndependencies for a program. Why? Because external, non-Cargo managed\ndependencies will be configured in a\n<a href=\"https://github.com/NixOS/nixpkgs/blob/f101cc2c243f0f3869f9a214d71b736c66b5317a/pkgs/build-support/rust/default-crate-overrides.nix\"><code>per-crate basis,</code></a>\nso a top-level Rust program that uses a crate that requires <code>liburing</code> won't\nactually see <code>liburing</code> when being compiled.</p>\n<p>And here's another great part: Since the <code>pkgs</code> view is not a 1:1 map of all the\nderivations (build units, but in a fancy Nix / Functional Software Deployment\nModel way), these generated definitions won't pollute search or interfere with\nusers. It's just a plus in almost every way imagineable.<sup><a id=\"ref:3\" href=\"https://rgbcu.be/blog/no-gnulib/#fn:3\">^3</a></sup></p>\n<blockquote>\n<p>You can actually use this right now with\n<a href=\"https://github.com/cargo2nix/cargo2nix\"><code>cargo2nix</code></a> or\n<a href=\"https://github.com/nix-community/crate2nix\"><code>crate2nix</code></a>, though expect some\nheadaches. (I recently spent a few hours getting <code>crate2nix</code> to work on a huge\ncodebase with a lot of native dependencies, and <code>aws-lc-sys</code> was the biggest\nheadache on darwin)</p>\n<details>\n<summary>The extra crate overrides I ended up with, if it helps.</summary>\n<div class=\"transform-[rotateX(180deg)] overflow-x-auto\"><pre class=\"transform-[rotateX(180deg)]\"><code class=\"language-nix hljs\">{ pkgs, ... }:\n{\n  <span class=\"token-attr\">openssl</span> <span class=\"token-operator\">=</span> <span class=\"token-params\">old:</span> {\n    <span class=\"token-attr\">buildInputs</span> <span class=\"token-operator\">=</span> old.buildInputs <span class=\"token-keyword\">or</span> [ ] <span class=\"token-operator\">++</span> [\n      pkgs.openssl\n    ];\n  };\n\n  <span class=\"token-attr\">openssl-sys</span> <span class=\"token-operator\">=</span> <span class=\"token-params\">old:</span> {\n    <span class=\"token-attr\">RUSTFLAGS</span> <span class=\"token-operator\">=</span> <span class=\"token-string\">\"--cfg ossl111 --cfg ossl110 --cfg ossl101\"</span>;\n\n    <span class=\"token-string\">\"<span class=\"token-subst\">${pkgs.stdenv.buildPlatform.rust.cargoEnvVarTarget}</span>_OPENSSL_DIR\"</span> <span class=\"token-operator\">=</span> <span class=\"token-string\">\"<span class=\"token-subst\">${pkgs.openssl.dev}</span>\"</span>;\n    <span class=\"token-attr\">OPENSSL_NO_VENDOR</span> <span class=\"token-operator\">=</span> <span class=\"token-string\">\"1\"</span>;\n    <span class=\"token-attr\">OPENSSL_STATIC</span> <span class=\"token-operator\">=</span> <span class=\"token-string\">\"0\"</span>;\n  };\n\n  <span class=\"token-attr\">aws-lc-sys</span> <span class=\"token-operator\">=</span> <span class=\"token-params\">old:</span> {\n    <span class=\"token-attr\">nativeBuildInputs</span> <span class=\"token-operator\">=</span> old.nativeBuildInputs <span class=\"token-keyword\">or</span> [ ] <span class=\"token-operator\">++</span> [\n      pkgs.cmake\n      pkgs.go\n      pkgs.libclang\n      pkgs.perl\n      pkgs.nasm\n    ];\n    <span class=\"token-attr\">env.CFLAGS</span> <span class=\"token-operator\">=</span> <span class=\"token-string\">\"-D_DARWIN_C_SOURCE\"</span>; <span class=\"token-comment\"># Hours of my life wasted to figure this out.</span>\n  };\n\n  <span class=\"token-attr\">librocksdb-sys</span> <span class=\"token-operator\">=</span> <span class=\"token-params\">old:</span> {\n    <span class=\"token-attr\">buildInputs</span> <span class=\"token-operator\">=</span> old.buildInputs <span class=\"token-keyword\">or</span> [ ] <span class=\"token-operator\">++</span> [\n      pkgs.zlib\n      pkgs.zstd\n      pkgs.bzip2\n      pkgs.lz4\n    ];\n  };\n\n  <span class=\"token-attr\">tokio</span> <span class=\"token-operator\">=</span> <span class=\"token-params\">old:</span> {\n    <span class=\"token-attr\">extraRustcOpts</span> <span class=\"token-operator\">=</span> old.extraRustcOpts <span class=\"token-keyword\">or</span> [ ] <span class=\"token-operator\">++</span> [\n      <span class=\"token-string\">\"--cfg tokio_unstable\"</span>\n    ];\n  };\n\n  <span class=\"token-attr\">uvm_syn</span> <span class=\"token-operator\">=</span> <span class=\"token-params\">old:</span> {\n    <span class=\"token-attr\">extraRustcOpts</span> <span class=\"token-operator\">=</span> old.extraRustcOpts <span class=\"token-keyword\">or</span> [ ] <span class=\"token-operator\">++</span> [\n      <span class=\"token-string\">\"-Awarnings\"</span>\n    ];\n  };\n\n  <span class=\"token-attr\">pyo3_build_config</span> <span class=\"token-operator\">=</span> <span class=\"token-params\">old:</span> {\n    <span class=\"token-attr\">buildInputs</span> <span class=\"token-operator\">=</span> old.buildInputs <span class=\"token-keyword\">or</span> [ ] <span class=\"token-operator\">++</span> [\n      pkgs.python313\n    ];\n  };\n}\n</code></pre></div>\n<p>I also had to patch the <code>Cargo.nix</code> generator because it was not handling or\neven exposing a way to enable extra cfg flags separately, but maybe this is\nfixed in <code>cargo2nix</code>?</p>\n<p>Anyway, we do not talk about the general state of Nix/Nixpkgs/NixOS tooling.\n(But it's still better than every other distro out there)</p>\n</details>\n</blockquote>\n<a href=\"https://rgbcu.be/blog/no-gnulib/#takeaway\"><h2 id=\"takeaway\">Takeaway...?</h2></a>\n<p><strong>Package management and build systems aren't hard, your tools are just bad.</strong></p>\n<p>That's why I'm working on a new system named \"Cull\", which will hopefully solve\na lot of these problems (and thus fix the mistakes of Nix). Stay tuned!</p>\n<p>It will also be cross platform\n(Linux/BSDs/Darwin/Windows/&lt;insert-your-favourite-os-that-has-rust-support-here&gt;),\nand cacheable at the expression level. No waiting for your system closure to\nevaluate for 5 minutes.</p>\n\n\n  <ol id=\"footnotes\"><hr><li id=\"fn:1\">Gnulib is not a library, but a collection of source\nfiles that you are supposed to embed into your program, so you can expect to be\npulling your hair out as it is well-rooted into most programs that embed it.\nHave fun packaging it all!<sub><a href=\"https://rgbcu.be/blog/no-gnulib/#ref:1\">..?</a></sub></li><hr><li id=\"fn:2\">Nor is it Rust, but\nthat's the best we have right now &amp; it is pretty damn good!<sub><a href=\"https://rgbcu.be/blog/no-gnulib/#ref:2\">..?</a></sub></li><hr><li id=\"fn:3\">A negative would be\nthe sandbox creation overhead of <code>nix-daemon</code>, but that shouldn't be significant\nfor most of Rust builds from scratch, and for those that are, the already-cached\ncrates will mitigate the losses pretty easily.<sub><a href=\"https://rgbcu.be/blog/no-gnulib/#ref:3\">..?</a></sub></li></ol>\n","tags":["build-systems"],"date_published":"2025-07-20T00:00:00.000Z"},{"id":"https://rgbcu.be/blog/3-json/","url":"https://rgbcu.be/blog/3-json/","title":"3-json","content_html":"\n  <p>From the\n<a href=\"https://oldmanprogrammer.net/source.php?dir=projects/tree/CHANGES\"><code>tree(1)</code> release notes</a>,\nversion 2.0.0:</p>\n<blockquote>\n<ul>\n<li>Output un-indented JSON on file descriptor 3 (\"stddata\") automatically if\nfile descriptor 3 is present (currently Linux only.) Maybe switch to BSON.</li>\n</ul>\n</blockquote>\n<p>Surely, nothing will happen if I just assume that the existence of a specific\nfile descriptor implies something, as nobody is crazy or stupid enough to\nhardcode such a thing? <strong><em>&lt;insert sounds of cars crashing in the\nbackground&gt;</em></strong></p>\n<p>Oops (from the <code>tree</code>version 2.0.2 release notes):</p>\n<blockquote>\n<ul>\n<li>Okay, apparently the stddata addition is causing havoc (who knew how many\nscripts just haphazardly hand programs random file descriptors, that's\nsurely not a problem.) Going forward the stddata option will only work if\nthe environment variable STDDATA_FD is present or set to the descriptor to\nproduce the JSON output on.</li>\n</ul>\n</blockquote>\n<p>Assuming anything about the users environment is and will always be a mistake.\nBut it's still cool we can have <code>tree</code> output structured data using\n<code>STDDATA_FD</code>:</p>\n<div class=\"transform-[rotateX(180deg)] overflow-x-auto\"><pre class=\"transform-[rotateX(180deg)]\"><code class=\"language-json hljs\">&gt; STDDATA_FD=<span class=\"token-number\">1</span> tree\n\n<span class=\"token-punctuation\">[</span><span class=\"token-punctuation\">{</span><span class=\"token-attr\">\"type\"</span><span class=\"token-punctuation\">:</span><span class=\"token-string\">\"directory\"</span><span class=\"token-punctuation\">,</span><span class=\"token-attr\">\"name\"</span><span class=\"token-punctuation\">:</span><span class=\"token-string\">\".\"</span><span class=\"token-punctuation\">,</span><span class=\"token-attr\">\"contents\"</span><span class=\"token-punctuation\">:</span><span class=\"token-punctuation\">[</span><span class=\"token-punctuation\">{</span><span class=\"token-attr\">\"type\"</span><span class=\"token-punctuation\">:</span><span class=\"token-string\">\"file\"</span><span class=\"token-punctuation\">,</span><span class=\"token-attr\">\"name\"</span><span class=\"token-punctuation\">:</span><span class=\"token-string\">\"2024-01-01-test.md\"</span><span class=\"token-punctuation\">}</span><span class=\"token-punctuation\">,</span><span class=\"token-punctuation\">{</span><span class=\"token-attr\">\"type\"</span><span class=\"token-punctuation\">:</span><span class=\"token-string\">\"file\"</span><span class=\"token-punctuation\">,</span><span class=\"token-attr\">\"name\"</span><span class=\"token-punctuation\">:</span><span class=\"token-string\">\"2024-03-04-htmnix.md\"</span><span class=\"token-punctuation\">}</span><span class=\"token-punctuation\">,</span><span class=\"token-punctuation\">{</span><span class=\"token-attr\">\"type\"</span><span class=\"token-punctuation\">:</span><span class=\"token-string\">\"file\"</span><span class=\"token-punctuation\">,</span><span class=\"token-attr\">\"name\"</span><span class=\"token-punctuation\">:</span><span class=\"token-string\">\"2024-04-02-cosmic-drift.md\"</span><span class=\"token-punctuation\">}</span><span class=\"token-punctuation\">,</span><span class=\"token-punctuation\">{</span><span class=\"token-attr\">\"type\"</span><span class=\"token-punctuation\">:</span><span class=\"token-string\">\"file\"</span><span class=\"token-punctuation\">,</span><span class=\"token-attr\">\"name\"</span><span class=\"token-punctuation\">:</span><span class=\"token-string\">\"2024-04-15-nix-iceberg.md\"</span><span class=\"token-punctuation\">}</span><span class=\"token-punctuation\">,</span><span class=\"token-punctuation\">{</span><span class=\"token-attr\">\"type\"</span><span class=\"token-punctuation\">:</span><span class=\"token-string\">\"file\"</span><span class=\"token-punctuation\">,</span><span class=\"token-attr\">\"name\"</span><span class=\"token-punctuation\">:</span><span class=\"token-string\">\"2024-05-20-swap-i.md\"</span><span class=\"token-punctuation\">}</span><span class=\"token-punctuation\">,</span><span class=\"token-punctuation\">{</span><span class=\"token-attr\">\"type\"</span><span class=\"token-punctuation\">:</span><span class=\"token-string\">\"file\"</span><span class=\"token-punctuation\">,</span><span class=\"token-attr\">\"name\"</span><span class=\"token-punctuation\">:</span><span class=\"token-string\">\"2024-09-30-gitignore.md\"</span><span class=\"token-punctuation\">}</span><span class=\"token-punctuation\">,</span><span class=\"token-punctuation\">{</span><span class=\"token-attr\">\"type\"</span><span class=\"token-punctuation\">:</span><span class=\"token-string\">\"file\"</span><span class=\"token-punctuation\">,</span><span class=\"token-attr\">\"name\"</span><span class=\"token-punctuation\">:</span><span class=\"token-string\">\"2025-06-05-why-cores.md\"</span><span class=\"token-punctuation\">}</span><span class=\"token-punctuation\">,</span><span class=\"token-punctuation\">{</span><span class=\"token-attr\">\"type\"</span><span class=\"token-punctuation\">:</span><span class=\"token-string\">\"file\"</span><span class=\"token-punctuation\">,</span><span class=\"token-attr\">\"name\"</span><span class=\"token-punctuation\">:</span><span class=\"token-string\">\"2025-06-09-nginix.md\"</span><span class=\"token-punctuation\">}</span><span class=\"token-punctuation\">,</span><span class=\"token-punctuation\">{</span><span class=\"token-attr\">\"type\"</span><span class=\"token-punctuation\">:</span><span class=\"token-string\">\"file\"</span><span class=\"token-punctuation\">,</span><span class=\"token-attr\">\"name\"</span><span class=\"token-punctuation\">:</span><span class=\"token-string\">\"2025-06-20-intro-cab.md\"</span><span class=\"token-punctuation\">}</span><span class=\"token-punctuation\">,</span><span class=\"token-punctuation\">{</span><span class=\"token-attr\">\"type\"</span><span class=\"token-punctuation\">:</span><span class=\"token-string\">\"file\"</span><span class=\"token-punctuation\">,</span><span class=\"token-attr\">\"name\"</span><span class=\"token-punctuation\">:</span><span class=\"token-string\">\"2025-07-04-derive-broken.md\"</span><span class=\"token-punctuation\">}</span><span class=\"token-punctuation\">,</span><span class=\"token-punctuation\">{</span><span class=\"token-attr\">\"type\"</span><span class=\"token-punctuation\">:</span><span class=\"token-string\">\"file\"</span><span class=\"token-punctuation\">,</span><span class=\"token-attr\">\"name\"</span><span class=\"token-punctuation\">:</span><span class=\"token-string\">\"2025-07-20-no-gnulib.md\"</span><span class=\"token-punctuation\">}</span><span class=\"token-punctuation\">,</span><span class=\"token-punctuation\">{</span><span class=\"token-attr\">\"type\"</span><span class=\"token-punctuation\">:</span><span class=\"token-string\">\"file\"</span><span class=\"token-punctuation\">,</span><span class=\"token-attr\">\"name\"</span><span class=\"token-punctuation\">:</span><span class=\"token-string\">\"_data.ts\"</span><span class=\"token-punctuation\">}</span><span class=\"token-punctuation\">]</span><span class=\"token-punctuation\">}</span><span class=\"token-punctuation\">,</span><span class=\"token-punctuation\">{</span><span class=\"token-attr\">\"type\"</span><span class=\"token-punctuation\">:</span><span class=\"token-string\">\"report\"</span><span class=\"token-punctuation\">,</span><span class=\"token-attr\">\"directories\"</span><span class=\"token-punctuation\">:</span><span class=\"token-number\">1</span><span class=\"token-punctuation\">,</span><span class=\"token-attr\">\"files\"</span><span class=\"token-punctuation\">:</span><span class=\"token-number\">12</span><span class=\"token-punctuation\">}</span><span class=\"token-punctuation\">]</span>\n</code></pre></div>\n<p>Also, obligatory <a href=\"https://nushell.sh/\">nushell</a> mention (using the <code>markdown</code>\ntable style because for some reason the fancy characters don't render properly\nwith my webfont):</p>\n<div class=\"transform-[rotateX(180deg)] overflow-x-auto\"><pre class=\"transform-[rotateX(180deg)]\"><code class=\"language-sh hljs language-bash\">&gt; STDDATA_FD=1 tree | from json\n\n|-#-|---<span class=\"token-built_in\">type</span>----|-name-|------------------contents-------------------|-directories-|-files-|\n| 0 | directory | .    | |-#--|-<span class=\"token-built_in\">type</span>-|------------name-------------| |    nope     | nope  |\n|   |           |      | |  0 | file | 2024-01-01-test.md          | |             |       |\n|   |           |      | |  1 | file | 2024-03-04-htmnix.md        | |             |       |\n|   |           |      | |  2 | file | 2024-04-02-cosmic-drift.md  | |             |       |\n|   |           |      | |  3 | file | 2024-04-15-nix-iceberg.md   | |             |       |\n|   |           |      | |  4 | file | 2024-05-20-swap-i.md        | |             |       |\n|   |           |      | |  5 | file | 2024-09-30-gitignore.md     | |             |       |\n|   |           |      | |  6 | file | 2025-06-05-why-cores.md     | |             |       |\n|   |           |      | |  7 | file | 2025-06-09-nginix.md        | |             |       |\n|   |           |      | |  8 | file | 2025-06-20-intro-cab.md     | |             |       |\n|   |           |      | |  9 | file | 2025-07-04-derive-broken.md | |             |       |\n|   |           |      | | 10 | file | 2025-07-20-no-gnulib.md     | |             |       |\n|   |           |      | | 11 | file | _data.ts                    | |             |       |\n| 1 | report    | nope |                    nope                     |           1 |    12 |\n</code></pre></div>\n\n\n  \n","tags":["cli"],"date_published":"2025-07-21T00:00:00.000Z"},{"id":"https://rgbcu.be/blog/nix-koans/","url":"https://rgbcu.be/blog/nix-koans/","title":"Nix Koans","content_html":"\n  <a href=\"https://rgbcu.be/blog/nix-koans/#nix-koans\"><h1 id=\"nix-koans\">Nix Koans</h1></a>\n<p>Inspired by the legendary\n<a href=\"https://people.csail.mit.edu/gregs/ll1-discuss-archive-html/msg03277.html\">Qc Na</a>\nand <a href=\"https://stevelosh.com/blog/2013/04/git-koans/\">Git Koans</a>, which was in\nturn inspired by <a href=\"https://blog.sanctum.geek.nz/vim-koans/\">Vim Koans</a>.</p>\n<a href=\"https://rgbcu.be/blog/nix-koans/#immutable\"><h1 id=\"immutable\">Immutable</h1></a>\n<p>A site reliability engineer rushed into the Nix Store.</p>\n<p>\"The whole front aisle is rotten!\" he cried to the clerk.</p>\n<p>They seized the tainted goods, but the goods were fastened to the shelves, and\nthe shelves to the floor.</p>\n<p>A passerby said: \"Build a new shelf, and move the door.\"</p>\n<p>\"There is no time\", cried the clerk. Then she shouted: \"mount -o remount,rw\n/nix/store!\"</p>\n<p>At last, something budged. The building collapsed, its foundations swapped for\nenlightenment.</p>\n<a href=\"https://rgbcu.be/blog/nix-koans/#walls-of-purity\"><h1 id=\"walls-of-purity\">Walls of Purity</h1></a>\n<p>A novice was running a free advertising campaign for Master Nix.</p>\n<p>\"What Master Nix builds today, he will build in ten years!\" he shouted to the\nmarketplace. \"A build within his garden knows neither the day, nor the hour!\"</p>\n<p>Master Nix, who was passing, handed the novice a derivation. \"Print my\npamphlets,\" he said.</p>\n<p>The novice took the derivation to the garden, and printed the pamphlets. Each\npamphlet bore its day and hour of printing.</p>\n<p>\"A breach in the walls!\" cried the novice.</p>\n<p>Master Nix said nothing. He struck the day and hour from the template, and\nprinted again with <code>--impure</code>. The pamphlets agreed. He tore down the garden\nwalls with <code>__noChroot = true</code>, and printed once more, in the open air. The\npamphlets agreed.</p>\n<p>\"Master, with the walls torn, what keeps the world out of the build?\"</p>\n<p>\"What kept the hour out, when they stood?\"</p>\n<p>The marketplace went quiet.</p>\n<p>At that moment, the novice was enlightened.</p>\n<a href=\"https://rgbcu.be/blog/nix-koans/#derivation\"><h1 id=\"derivation\">Derivation</h1></a>\n<p>A functional programmer was trying to convince Master Nix that his project was\nworthy of a derivation.</p>\n<p>Master Nix eventually answered the call, evaluated the programmer, and fulfilled\nhis wish.</p>\n<p>The programmer copied the bestowed gift onto a portable storage device, and as\nhe turned to depart, Master Nix asked:</p>\n<p>\"And its closure?\"</p>\n<p>The programmer rummaged through his code, and only found self-contained\nfunctions. \"There are no closures in my program\" he said.</p>\n<p>Master Nix said nothing.</p>\n<p>At the demonstration, the programmer was englightened.</p>\n<a href=\"https://rgbcu.be/blog/nix-koans/#the-old-tongue\"><h1 id=\"the-old-tongue\">The Old Tongue</h1></a>\n<p>A new student of Master Nix was learning the basics.</p>\n<p>Proud of his first package, he showed the master his work: \"nix-build hello.nix\"</p>\n<p>Master Nix struck him on the head with a metal bat. \"We do not speak the old\ntongue.\"</p>\n<p>The student learned the new tongue, in which a silence stands where the hyphen\nstood. He spoke it beautifully: nix build, nix shell, nix run. In time he grew\nzealous, and corrected the older students when they slipped.</p>\n<p>One day, wishing he knew how the temple was kept, he opened the scripts of the\nmasters. nix-instantiate, nix-env, nix-prefetch-url, nix-store. The old tongue,\nfrom the first scroll to the last.</p>\n<p>\"Master!\" he cried. \"Your own scriptures-\"</p>\n<p>Master Nix threw him into the well.</p>\n<p>At the bottom, stood enlightenment.</p>\n<a href=\"https://rgbcu.be/blog/nix-koans/#cross\"><h1 id=\"cross\">Cross</h1></a>\n<p>A poor devops engineer who was assigned a teletype of unusual thinness was\nlimping along in despair, for his masters demanded x86_64-linux, and the\nteletype knew it not.</p>\n<p>One day, he heard a colleague speak of <code>pkgsCross</code>, by which a machine may build\nfor a land it will never see. That night, he evaluated <code>pkgsCross.gnu64</code>, and\nthe teletype began to build the world.</p>\n<p>binutils crossed. glibc crossed. Then a package, wishing to test what it had\nbuilt, tried to run it; but what it had built was of the far land, and the\nteletype knew it not. A thousand errors followed. The engineer read the first,\nand wept.</p>\n<p>An old operator looked over his shoulder.</p>\n<p>\"A fine terminal,\" he said. \"But one needs a computer to build.\"</p>\n<p>The engineer wrote in his configuration:\n<code>builders = ssh-ng://elsewhere x86_64-linux</code>. The teletype clattered through the\nnight, printing the labors of a distant machine.</p>\n<p>At dawn, the engineer was enlightened.</p>\n\n\n  \n","tags":["nix/os"],"date_published":"2026-06-13T00:00:00.000Z"}]}