<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>AlternativeBit</title>
    <link>https://alternativebit.fr/tags/nix/</link>
    <description>Recent posts</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language>
    <lastBuildDate>Mon, 01 Jun 2026 10:24:00 +0200</lastBuildDate>
    <atom:link href="https://alternativebit.fr/tags/nix/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Nar-klepto: Guix and Nix Offline Cache</title>
      <link>https://alternativebit.fr/posts/nix-and-guix-offline-hacking/</link>
      <author>picnoir</author>
      <category domain="https://alternativebit.fr/tags/nix">Nix</category><category domain="https://alternativebit.fr/tags/guix">Guix</category>
      <pubDate>Mon, 01 Jun 2026 10:24:00 +0200</pubDate>
      <guid>https://alternativebit.fr/posts/nix-and-guix-offline-hacking/</guid>
      <description>&lt;p&gt;TL;DR: introducing &lt;a href=&#34;https://codeberg.org/picnoir/nar-klepto&#34;&gt;nar-klepto&lt;/a&gt;, the context that led to its inception, and some fun experiments we did last week with it.&lt;/p&gt;
&lt;p&gt;4 years ago, I moved out of Bayonne to a rural area. My quality of life almost instantly skyrocketed; however, I quickly felt a bit frustrated that I could only discuss computer-related things via online video meetings.&lt;/p&gt;
&lt;p&gt;After the 2024 FOSDEM, I realized that if there&amp;rsquo;s no nerds around, why not invite my online friends at my place? And, for the last three years, I&amp;rsquo;ve been renting a very nice place close to where I live for a full week. I invite my favorite online friends there. Each year, a few strangers join, friends of friends, usually becoming new friends by the end of the week.&lt;/p&gt;
&lt;p&gt;This is nice, it makes me happy every time.&lt;/p&gt;
&lt;p&gt;I just came back from this year&amp;rsquo;s edition. Physically a bit exhausted, but mentally fully energized. A full week of nice discussions, drone and RC glider flying, multiplayer videogames, cooking, eating a bit too much, hot spring water, and bike rides!&lt;/p&gt;
&lt;p&gt;The place where we do this is super nice, and pretty remote… and… do not come with any internet connection. The owners do not seem to care at all, it&amp;rsquo;s their fun vacation house, no need for internet there! We&amp;rsquo;re probably the only group renting this and caring about any internet connectivity at the same time. We&amp;rsquo;re probably terminally online.&lt;/p&gt;
&lt;p&gt;The only uplink we have comes from a pretty terrible LTE router&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;. And it&amp;rsquo;s not great. If you&amp;rsquo;re into Guix or NixOS, you probably already see the issue. These distributions are amazing. But boy oh boy, they&amp;rsquo;re network hungry. And boy oh boy, they dislike not being able to reach to their binary caches. Last year, I spent some time creating a solution for this, and it looks like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;./cache.webp&#34; alt=&#34;old japanese laptop running nar clepto. A SSD is attached to it. The computer is plugged to a ethernet switch.&#34;&gt;&lt;/p&gt;
&lt;p&gt;This is a wonderful retro Panasonic CF-SZ5, coupled with an external USB SSD. Turned on, serving NARs 24/7 for a week. Poor thing. This used to be a pretty cheap setup, the SSD probably cost 3 times more than the laptop it&amp;rsquo;s attached to these days.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Always use obsolete hardware running bleeding-edge software.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I love the attitude and what it entails: making some effort to extend the lifespan of rare resources stored in these magic machines. Nice hardware setup to boast about, but that&amp;rsquo;s not really the point of this article. The software it runs is the interesting bit: &lt;a href=&#34;https://codeberg.org/picnoir/nar-klepto&#34;&gt;nar-klepto&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s a collection of 3 programs able to fully backup a Guix or Nixpkgs release. Each and every derivation part of it. That&amp;rsquo;s ~100GB worth of compressed NARs for a Guix release, ~500GB for a Nix one.&lt;/p&gt;
&lt;p&gt;Conceptually speaking, you feed it a list of store paths, and it downloads the relevant narinfos and nar. You can retrieve this list of paths for a particular release using the data service for Guix, Hydra for NixOS. The narinfos are stored in a PostgreSQL DB, the nars on a filesystem. Finally, it comes with an HTTP server, able to send the NARs and narinfos to the Nix and Guix clients. It also provides a NixOS module to set up the server part of it and a Nginx reverse caching proxy. The reverse caching proxy comes in handy when people start to download packages not available in the cache. It could happen if an important security release forces people to upgrade their systems to a new Guix/Nix revision. The reverse proxy cache saves some of the metered LTE bandwidth and some substitution time.&lt;/p&gt;
&lt;p&gt;Downloading a full release comes at a cost for the project&amp;rsquo;s infrastructure, especially for Guix, which does not have a sponsored CDN. This tool is wasteful by nature and is not meant for recurring usage. It&amp;rsquo;s mostly useful for camps, retreats, and conference organisers. That&amp;rsquo;s an adequate tool to provide a one-off cache for a Guix/Nix event, a pretty inadequate one to host in your personal infra and mirror each and every Guix/Nix release.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://no%C3%A9.eu/&#34;&gt;Noé Lopez&lt;/a&gt; packaged it for Guix. He has been the first external contributor&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt; to the project, adding the ability for the cache to advertize itself to the guix clients via zeroconf. Quite a nice feature, I&amp;rsquo;m a bit jaleous not to have it on NixOS! A nice coincidence happened though, adisbladis ended up releasing a similar project for NixOS, with a quite different design the same week: &lt;a href=&#34;https://github.com/adisbladis/nix-cache-beacon&#34;&gt;nix-cache-beacon&lt;/a&gt;. I might add the cache-beacon advertisement directly in nar-klepto in the future.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m pretty happy and proud of releasing this tool. Took me a good while… Close to a year. Last year, a good chunk of my online friends and colleagues jumped onto the vibe coding bandwagon. I was intrigued and did not want to miss out, so I joined the hype through this project. It did not work really well for me, and I have not used these tools since then: the experience was not enjoyable for me, and the result, while working, was a bit too trashy for my taste. There was a lot of unnecessary code duplication, each duplicate being slightly different from the others. I dreaded and procrastinated refactoring the thing for a good year, but I finally bit the bullet two weeks ago, cleaned and rewrote a good chunk of it. I enjoyed the process of desloppifying the project, it was fun. That&amp;rsquo;s good for me, who knows, maybe this will become my job at some point.&lt;/p&gt;
&lt;p&gt;I commissioned &lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt; some artwork from lassulus. The second iteration was the charm! The overall sketchy vibe nicely fits this project&amp;rsquo;s story.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;./nar_klepto.png&#34; alt=&#34;nar klepto logo&#34;&gt;&lt;/p&gt;
&lt;p&gt;During the geek event, I had to download a few things from the cache, and the experience was amazing. The substitution is fast, really fast. That&amp;rsquo;s a combination of two factors:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The narinfos are reachable with a very low latency, &amp;gt;3ms, from the cache. It noticeably speeds up cold evaluation.&lt;/li&gt;
&lt;li&gt;The NARS are available on the lan at a pretty high download speed. Sadly, not saturating a gigabit link. Despite how much andir- loves it, the glinet router needs an upgrade!&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;At some point, we paired with &lt;a href=&#34;https://flokli.de&#34;&gt;flokli&lt;/a&gt;, and tried to ingest the Guix release into a snix store to see what kind of storage gains we could get from chunking the Nars. &lt;a href=&#34;https://im-in.space/@flokli/116619174693467386&#34;&gt;Quite a lot it turns out!&lt;/a&gt;. The 115GB Guix nar collection ended up being ingested into a 21GB snix store. Quite impressive! We also tried to ingest the Nix Nar collection, but the gains were not as jaw-dropping. We&amp;rsquo;re not sure what explains this Guix efficiency. Maybe the lzip -&amp;gt; ztsd compression change, maybe there are some very repeated chunks in the cache. We&amp;rsquo;ll have to investigate more to figure this out.&lt;/p&gt;
&lt;p&gt;All this makes me believe there are ways to improve the substitution story of Guix/Nix. Something I&amp;rsquo;d like to focus a bit on in the near future ;)&lt;/p&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;GL-iNet X300. The hardware is ok, but the software is terrible. A fork of an outdated openwrt revision, a terribly buggy LTE model integration, and, of course, not updated for more than a year. It&amp;rsquo;s not even cheap, don&amp;rsquo;t buy that! Buy anything that can run mainline OpenWRT instead.&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34;&gt;
&lt;p&gt;to be fully honest, he was also part of the retreat and was depending on the cache. Lesson learned: a captive audience is a good bootstrap contributors pool!&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34;&gt;
&lt;p&gt;without paying him, sadly. Times are tough for artists!&amp;#160;&lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</description>
    </item>
    <item>
      <title>Nsncd, Anniversary Updates</title>
      <link>https://alternativebit.fr/posts/nixos/nsncd-some-updates/</link>
      <author>picnoir</author>
      <category domain="https://alternativebit.fr/tags/system-programming">System programming</category><category domain="https://alternativebit.fr/tags/nix">Nix</category>
      <pubDate>Mon, 30 Oct 2023 10:00:00 +0200</pubDate>
      <guid>https://alternativebit.fr/posts/nixos/nsncd-some-updates/</guid>
      <description>&lt;p&gt;Last year, &lt;a href=&#34;https://flokli.de/&#34;&gt;flokli&lt;/a&gt; and I worked towards re-using
TwoSigma&amp;rsquo;s Nsncd as the main NixOS Nscd daemon.&lt;/p&gt;
&lt;p&gt;What is that even about? Well, Nscd is a Glibc daemon that was
originally meant to cache the host/user resolution requests. It&amp;rsquo;s
mostly obsolete by now, but on NixOS and Guix, we abuse this daemon to
get a stable ABI to load the host NSS modules from a potentially
different glibc version. If you have no idea what I&amp;rsquo;m talking about
here and want to read more about that context, you should probably
read &lt;a href=&#34;https://flokli.de/posts/2022-11-18-nsncd/&#34;&gt;flokli&amp;rsquo;s original release blog
post&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s now been a year since we released the Nsncd host lookups. Since
then, Nsncd is used by default on NixOS in place of Nscd. The
migration has been mostly bugless. Emphasis on the &lt;strong&gt;mostly&lt;/strong&gt;!&lt;/p&gt;
&lt;p&gt;While this switch has been beneficial for most of us, getting rid of
the &amp;ldquo;too-much-caching&amp;rdquo; numerous bugs, some unexpected side effects
appeared. Such as
&lt;a href=&#34;https://github.com/NixOS/nixpkgs/issues/196934&#34;&gt;breaking&lt;/a&gt; the
&lt;code&gt;hostname --fqdn&lt;/code&gt; command.&lt;/p&gt;
&lt;p&gt;Recently, flokli and I ended up under the same Catalonian roof for a
Numtide-organized programming retreat. Kudos to
&lt;a href=&#34;https://zimbatm.com/&#34;&gt;Zimbatm&lt;/a&gt; who handled most of the thankless
logistics to make this happen &amp;lt;3&lt;/p&gt;
&lt;p&gt;After almost a full year of procrastination, turns out that a 10-hour
session of focused work was all it took to &lt;strong&gt;almost&lt;/strong&gt; fix these
long-lingering issues.&lt;/p&gt;
&lt;p&gt;Wait, why did I write almost here? Ah yes. After these 10 initial
hours, I kept uncovering a long series of subtle yet real bugs for
the next 4 days. Turns out that the third full rewrite of the fix was
the charm!&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;What was the root cause of these faulty host resolutions in the end?&lt;/p&gt;
&lt;p&gt;Turns out that, even if the &lt;code&gt;gethostbyname&lt;/code&gt; and &lt;code&gt;gethostbyaddr&lt;/code&gt; libc
functions have been long deprecated in favor of &lt;code&gt;getaddrinfo&lt;/code&gt;, some
pretty critical pieces of software are still using them. You guessed
it: the &lt;code&gt;hostname&lt;/code&gt; command still uses those.&lt;/p&gt;
&lt;p&gt;We originally decided to implement these two functions through
&lt;code&gt;getaddrinfo&lt;/code&gt;. This was a mistake: the legacy operations do not
behave exactly like the new ones.&lt;/p&gt;
&lt;p&gt;To fix this bug, we wrote some FFI bindings for these two legacy
functions. Then, we replaced our dodgy &lt;code&gt;gethostbyname&lt;/code&gt; and
&lt;code&gt;gethostbyaddr&lt;/code&gt; implementation in Nsncd to use these bindings.&lt;/p&gt;
&lt;p&gt;In the meantime, flokli wrote
&lt;a href=&#34;https://github.com/flokli/sockburp&#34;&gt;sockburp&lt;/a&gt;, a debug tool for Unix
sockets. This has been a game changer to detect the wire format
compatibility issues. You can read more about this tool in &lt;a href=&#34;https://flokli.de/posts/2023-10-30-sockburp/&#34;&gt;this
blog post&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We have not managed to upstream all our changes to the TwoSigma repo
yet. NixOS Nsncd is still hosted from our
&lt;a href=&#34;https://github.com/nix-community/nsncd/&#34;&gt;nix-community&lt;/a&gt; fork. We did
not give up on upstreaming it though and hope to do that in the next
few months.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s pretty much all. Until next time, happy and serene hacking
folks :)&lt;/p&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;Ah, you&amp;rsquo;re here for the gory details? Here you go, read
&lt;a href=&#34;https://github.com/nix-community/nsncd/pull/9&#34;&gt;these&lt;/a&gt;
&lt;a href=&#34;https://github.com/nix-community/nsncd/pull/10&#34;&gt;two&lt;/a&gt; PRs history.
My treat!&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</description>
    </item>
    <item>
      <title>The Nix, OpenGL and Ubuntu Integration Nightmare</title>
      <link>https://alternativebit.fr/posts/nixos/nix-opengl-and-ubuntu-integration-nightmare/</link>
      <author>picnoir</author>
      <category domain="https://alternativebit.fr/tags/nix">Nix</category>
      <pubDate>Thu, 20 Apr 2023 10:00:00 +0200</pubDate>
      <guid>https://alternativebit.fr/posts/nixos/nix-opengl-and-ubuntu-integration-nightmare/</guid>
      <description>&lt;p&gt;In this article, we&amp;rsquo;re about to dive into the uncharted OpenGL on Linux waters. After briefly explaining how the OpenGL calls are routed from your application to the GPU, we&amp;rsquo;ll look at the NixOS special case. We’ll then explore how we can run the OpenGL programs built by Nix on a foreign distribution, such as Ubuntu or Fedora. Finally, we’ll introduce &lt;a href=&#34;https://github.com/numtide/nix-gl-host&#34;&gt;NixGLHost&lt;/a&gt; a new approach to solve this problem.&lt;/p&gt;
&lt;h2 id=&#34;openglcuda-on-linux&#34;&gt;OpenGL/CUDA on Linux?&lt;/h2&gt;
&lt;p&gt;Everybody, including your favorite pet play video games. Your favorite go-to desktop application is relying on GPU acceleration to render on your screen, and AI is taking over the world. Welcome to 2023. In the last 15 years, GPUs transitioned from being this gamer-specific niche hardware to being this globally-used parrallel platform.&lt;/p&gt;
&lt;p&gt;There are a lot of GPU-related APIs in the wild. OpenGL, OpenCL, Vulkan, DirectX, Metal, etc.. In this article, we&amp;rsquo;ll focus on a very small subset of them, OpenGL and CUDA. Even more precisely, OpenGL and CUDA on Linux. How are these two APIs working?&lt;/p&gt;
&lt;p&gt;Linux being Linux, there&amp;rsquo;s no simple answer to that. In most setups, and for OpenGL alone, there&amp;rsquo;s no big unified API. There are actually two concurrent ones, acting as an interface between the actual OpenGL program and the desktop environment.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&#34;https://registry.khronos.org/OpenGL/index_gl.php#apispecs&#34;&gt;GLX&lt;/a&gt;&lt;/strong&gt;: the legacy one, a Xorg-specific API.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&#34;https://www.khronos.org/egl/&#34;&gt;EGL&lt;/a&gt;&lt;/strong&gt;: the new kid in town. The interface is used by Wayland and Android among others.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To be clear: these are API specifications, not implementations. For these two APIs we have multiple implementations. Some are proprietary, and directly written by the hardware vendors. Some others are open source, sometimes implemented by hardware vendors, sometimes by courageous volunteers.&lt;/p&gt;
&lt;p&gt;There are currently two major OpenGL implementations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&#34;https://www.mesa3d.org/&#34;&gt;Mesa&lt;/a&gt;&lt;/strong&gt;: the main OpenSource implementation. It&amp;rsquo;s actually more of a meta-driver, it includes drivers for a wide range of hardware: Intel integrated GPUs, AMD discrete GPUs, some ARM Mali, and a lot more.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;NVIDIA&lt;/strong&gt;: a proprietary implementation, maintained by Nvidia itself. That&amp;rsquo;s the optimal way to drive your Nvidia graphics card on Linux.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These drivers are usually split into two parts. A thin kernel-space part is in charge of the low-level communication with the GPU, and a thicker user-space part is in charge of translating the OpenGL instructions into something the small kernel-space part can understand. Conceptually speaking, the kernel space part acts as a proxy to the actual hardware, nothing more.&lt;/p&gt;
&lt;p&gt;These driver stacks, on top of providing the low-level code in charge of talking to the GPU, are supplying implementations for various interfaces. Among those, are GLX and EGL. To sum things up, you have a GLX and EGL implementation for each and every driver. EG. &lt;code&gt;libGLX_nvidia.so&lt;/code&gt;, &lt;code&gt;libGLX_mesa.so&lt;/code&gt;, etc.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Hey, you&amp;rsquo;re lying! There is a unified API!&lt;/p&gt;
&lt;p&gt;I already used OpenGL, I know for a fact that my program is linked against &lt;code&gt;libGL.so&lt;/code&gt;, not to one of these weird-ass API!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;You&amp;rsquo;re absolutely right! But what if I told you that &lt;code&gt;libGL.so&lt;/code&gt; is actually a lie?&lt;/p&gt;
&lt;p&gt;I omitted  an important part of this OpenGL stack on purpose: &lt;a href=&#34;https://gitlab.freedesktop.org/glvnd/libglvnd&#34;&gt;libglvnd&lt;/a&gt;. Originally developed by NVIDIA before being maintained by the freedesktop community, this library aims to be the thing your library is going to link against. It&amp;rsquo;s not doing anything by itself, it just dispatches the OpenGL calls to the most appropriate APIs. Through various heuristics, libglvnd figures out through which interface the OpenGL instructions should go. Roughly speaking, if the target surface is rendered through Wayland, it&amp;rsquo;ll use the EGL implementation. If it&amp;rsquo;s rendered through Xorg, the GLX implementation. It also uses some more heuristics and config files to figure out which driver (Mesa, Nvidia, etc.) should be used.&lt;/p&gt;
&lt;p&gt;What about CUDA? Well, for this one, we don&amp;rsquo;t really have to bother with interoperability; NVIDIA is controlling the whole chain, from the user-space SDK to the silicon after all. The overall call graph is much simpler, although harder to integrate. Your CUDA program is linked against the &lt;code&gt;libcuda.so&lt;/code&gt; shared library. This library directly communicates with the thin kernel-space driver in charge to send the CUDA operations to the actual GPU.&lt;/p&gt;
&lt;p&gt;Overall, from a bird&amp;rsquo;s-eye view, the overall OpenGL graphic stack looks like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://alternativebit.fr/images/nix-gl-host/GL-dispatch-bare.svg&#34; alt=&#34;Diagram summarizing this paragraph. It shows how the OpenGL program, libglvnd, the EGL, and GLX implementation interact with each other.&#34;&gt;&lt;/p&gt;
&lt;h2 id=&#34;how-does-nix-deal-with-that&#34;&gt;How Does Nix Deal With That?&lt;/h2&gt;
&lt;p&gt;Now that we roughly see how the high-level components fit together in the Linux space, we know there&amp;rsquo;s a layer dynamically dispatching the graphics calls to the most relevant driver depending on the current context. It works reasonably well. Well enough to power most of the modern days GPU-fueled AI revolution. Sadly, the cost of dynamic dispatching is a loss of determinism.&lt;/p&gt;
&lt;h3 id=&#34;nixos&#34;&gt;NixOS&lt;/h3&gt;
&lt;p&gt;If your heart rate bumped slightly when reading the previous paragraph, chances are you already drank the NixOS kool-aid and are anticipating a fair share of pain integrating this with NixOS. And for good reason! How can we expect a statically-defined Nix closure to work on each and every end-user GPU? We surely can&amp;rsquo;t include all the existing drivers in the closure, it&amp;rsquo;d be massive!&lt;/p&gt;
&lt;p&gt;Don&amp;rsquo;t worry too much though, we don&amp;rsquo;t have to get that hardcore. Spoiler alert: I&amp;rsquo;m about to uncover a dirty-hack. You know, the dirty-hack rendering this very web-page (if you&amp;rsquo;re reading this on NixOS).&lt;/p&gt;
&lt;p&gt;NixOS has a magical &lt;code&gt;/run/opengl-driver&lt;/code&gt; directory. In this directory, you can find all the libs you&amp;rsquo;ll need to drive the GPU attached to your machine. OpenGL, Vulkan rendering, OpenCL computing, VA-API video acceleration. You name it!&lt;/p&gt;
&lt;p&gt;This directory — we&amp;rsquo;ll call it &amp;ldquo;link farm&amp;rdquo; from now on, because, well, that&amp;rsquo;s what it really is! — is defined on a system level via the &lt;code&gt;hardware.opengl&lt;/code&gt; &lt;a href=&#34;https://github.com/NixOS/nixpkgs/blob/45ce4de0fb6a9a9f1c9d4f675369a743e7c7729d/pkgs/build-support/add-opengl-runpath/default.nix#L3&#34;&gt;NixOS module&lt;/a&gt;. Overall, it means the GPU driver is defined through the NixOS &lt;strong&gt;system&lt;/strong&gt; closure, and exposed at runtime through the &lt;code&gt;/run/opengl-driver&lt;/code&gt; link farm. The OpenGL program derivations have no knowledge of the actual OpenGL implementation. They simply link to &lt;code&gt;libGL.so&lt;/code&gt; AKA. libglvnd, the generic dispatch layer.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Okay, great, we have the drivers in a weird &lt;code&gt;/run/opengl-driver&lt;/code&gt; location containing a link farm pointing to the actual GPU driver.&lt;/p&gt;
&lt;p&gt;Now, how can my Nix-built OpenGL program find these libraries?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Well, it&amp;rsquo;s Nixpkgs! Obviously, we&amp;rsquo;re injecting them through a wrapper. &lt;a href=&#34;https://github.com/NixOS/nixpkgs/blob/45ce4de0fb6a9a9f1c9d4f675369a743e7c7729d/pkgs/build-support/add-opengl-runpath/default.nix#L3&#34;&gt;The wrapper we&amp;rsquo;re talking about&lt;/a&gt; is called &lt;code&gt;addOpenGLRunpath&lt;/code&gt;. It injects &lt;code&gt;/run/opengl-drivers&lt;/code&gt; to the library path through an &lt;code&gt;LD_LIBRARY_PATH&lt;/code&gt; entry. At runtime, Libglvnd will load these libraries and dispatch them the OpenGL calls.&lt;/p&gt;
&lt;p&gt;On a foreign distribution, where &lt;code&gt;/run/opengl-driver&lt;/code&gt; won&amp;rsquo;t exist, the dynamic loader simply ignores this library path entry. The wrapper essentially acts as a no-op on a non-NixOS distribution.&lt;/p&gt;
&lt;p&gt;In a nutshell:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;NixOS&lt;/strong&gt; stores the host system GPU drivers in a well-known directory.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Nixpkgs&lt;/strong&gt; injects this well-known directory to the OpenGL program library path through a wrapper.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;what-about-running-nix-on-a-foreign-distro&#34;&gt;What About Running Nix on a Foreign Distro?&lt;/h3&gt;
&lt;p&gt;Now, how would you run a Nix OpenGL program on a foreign Linux distribution such as Ubuntu?&lt;/p&gt;
&lt;p&gt;All of a sudden there is no more &lt;code&gt;/run/opengl-driver&lt;/code&gt;. This is where it gets hairy!&lt;/p&gt;
&lt;p&gt;The go-to solution these days is &lt;a href=&#34;https://github.com/guibou/nixGL&#34;&gt;NixGL&lt;/a&gt;. It&amp;rsquo;s a wrapper you apply on top of an OpenGL derivation. With a few clever heuristics, it figures out which driver your host system expects at &lt;strong&gt;build-time&lt;/strong&gt;. It then builds the relevant driver derivation (Mesa, NVidia, Bumblebee) and generates a wrapper injecting the driver dynamic libraries to the OpenGL process load path.&lt;/p&gt;
&lt;p&gt;Remember what I told you during the introduction: The GPU driver is split between a thick user-space library and a thin kernel-space module. What we have here is only the user-space side of the story. The user-space part of the driver is distributed through Nix, the kernel-space one by the host distribution. One might fear hitting some protocol-level incompatibilities between the user space driver and the kernel-space one. And for good reason! It could definitely happen. In practice though, it&amp;rsquo;s good enough: the NixGL heuristics tend to be fairly clever at figuring out which userspace library to use.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s definitely a tradeoff. The GPU driver ends up living in the Nix closure. All of sudden, your Nix closure is tailored towards a specific GPU and won&amp;rsquo;t be generic anymore. This approach requires you to precisely know the target hardware specification during the Nix build time. If you want to deploy an application on N computers, you&amp;rsquo;ll have to conceptually &lt;code&gt;nix build&lt;/code&gt; the application N times. You&amp;rsquo;ll need a detailed inventory of the hardware you want to deploy on, or have the build happening on the end-user machine, which is not always possible.&lt;/p&gt;
&lt;h2 id=&#34;kludgy-mikey-likey&#34;&gt;Kludgy! Mikey Likey!&lt;/h2&gt;
&lt;p&gt;Could we have an alternate approach to solve this Nix-OpenGL-on-a-foreign-distro problem? An alternative not requiring us to include the OpenGL driver part of the deployed closure, but rather have this managed by the host distribution? Kind of what we have on NixOS?&lt;/p&gt;
&lt;p&gt;I think it&amp;rsquo;s fair to assume the host distribution is smart enough to properly set up the graphic stack. What if we glued the host user-space GPU environment to the Nix program instead of using a Nixpkgs-provided one? This thought was the starting point of &lt;a href=&#34;https://github.com/numtide/nix-gl-host&#34;&gt;NixGLHost&lt;/a&gt;, a new way of running your OpenGL Nix programs on a foreign distro.&lt;/p&gt;
&lt;p&gt;This approach is pretty Nix-heretic. We&amp;rsquo;re injecting some host DSOs&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; into the Nix closure. Nix is specifically designed to limit this kind of interaction with the host system! That being said, we&amp;rsquo;re being heretic but cautious: we only inject the entry points for EGL/GLX/CUDA. We patch the entry points&amp;rsquo; ELF runpaths to point to a place containing their own dependencies. This acts as a minimal sandboxing setup.&lt;/p&gt;
&lt;p&gt;I won&amp;rsquo;t dive too much into the implementation details here, you can check out the &lt;a href=&#34;https://github.com/numtide/nix-gl-host/blob/main/INTERNALS.md&#34;&gt;internals.md&lt;/a&gt; file if you&amp;rsquo;re interested in the nitty-gritty details.&lt;/p&gt;
&lt;p&gt;Overall, this experimental new wrapper re-uses the drivers provisioned by your host distribution. The kernel-space module and user-space library are both provisioned by the host system, we&amp;rsquo;re sure they won&amp;rsquo;t get out of sync. The graphic driver stays out of the Nix closure, the closure stays truly generic and will work on any GPU.&lt;/p&gt;
&lt;p&gt;This experiment turned out to work great for the use cases we tested it against so far. I managed to run DaVinci Resolve and a couple of videogames built by Nix on a Ubuntu machine. A customer is actually already using the prototype in production&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;h3 id=&#34;whats-next-for-nixglhost&#34;&gt;What&amp;rsquo;s Next for NixGLHost?&lt;/h3&gt;
&lt;p&gt;The next obvious step is to extend the supported OpenGL implementations. At the moment, we only support the NVIDIA proprietary driver. It&amp;rsquo;d be great to support Mesa as well. Technically, it&amp;rsquo;s not a hard thing to do. Sadly, the Python prototype codebase is quite messy and difficult to extend as it is.&lt;/p&gt;
&lt;p&gt;The wrapper is also pretty slow to run, ~200ms on a hot cache. Most of this time is spent spinning up the CPython interpreter. Starting a whole interpreter for a very short-lived program turned out to be very costly, who could have guessed! Should we embrace the meme and &lt;a href=&#34;https://www.urbandictionary.com/define.php?term=riir&#34;&gt;RIIR&lt;/a&gt; the project? I personally think it might be a good idea and started to work on that.&lt;/p&gt;
&lt;p&gt;We also should think about improving the sandboxing. At the moment, we&amp;rsquo;re injecting the host DSOs&lt;sup id=&#34;fnref1:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; symbols directly into the program&amp;rsquo;s global symbol table. If, by misfortune, the Nix program ends up using the same library as the GPU driver but with a slightly incompatible version, all hell breaks loose! There are some ways to go around that, we could leverage the &lt;code&gt;dlmopen&lt;/code&gt; function to load the driver DSOs&lt;sup id=&#34;fnref2:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; to another namespace. We could potentially leverage &lt;a href=&#34;https://gitlab.collabora.com/vivek/libcapsule&#34;&gt;libcapsule&lt;/a&gt; to perform such a &amp;ldquo;namespaced&amp;rdquo; dynamic library loading.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s see how it goes, I&amp;rsquo;ll probably post a follow-up here when we&amp;rsquo;ll reach the next major milestone :)&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Greets:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://flokli.de/&#34;&gt;flokli&lt;/a&gt; for enduring my terrible hacks and brainstorming this problem space with me &amp;lt;3&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://numtide.com/&#34;&gt;Numtide&lt;/a&gt; and &lt;a href=&#34;https://ottomotors.com/&#34;&gt;Otto Motors&lt;/a&gt; for trusting me on this and funding most of the NixGLHost work &amp;lt;3&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/guibou&#34;&gt;Guibou&lt;/a&gt; for writing &lt;a href=&#34;https://github.com/guibou/nixGL&#34;&gt;NixGL&lt;/a&gt; &amp;lt;3&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://alexarenoire.medium.com/&#34;&gt;Alex&lt;/a&gt; and &lt;a href=&#34;https://tazj.in/&#34;&gt;tazjin&lt;/a&gt; for proof-reading this article &amp;lt;3&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://en.wikipedia.org/wiki/Dynamic_linker&#34;&gt;Dynamic Shared Object&lt;/a&gt;, a shared library.&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref1:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref2:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34;&gt;
&lt;p&gt;This is an alpha release, I&amp;rsquo;m not saying you should use it in production as well. Use this with caution, it may eat your kittens!&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</description>
    </item>
    <item>
      <title>Nix Substitution: the Way Forward</title>
      <link>https://alternativebit.fr/posts/nixos/future-of-nix-substitution/</link>
      <author>picnoir</author>
      <category domain="https://alternativebit.fr/tags/nix">Nix</category><category domain="https://alternativebit.fr/tags/guix">Guix</category>
      <pubDate>Thu, 31 Mar 2022 08:18:00 +0200</pubDate>
      <guid>https://alternativebit.fr/posts/nixos/future-of-nix-substitution/</guid>
      <description>&lt;h2 id=&#34;abstract&#34;&gt;Abstract&lt;/h2&gt;
&lt;p&gt;Nix and Guix have the unfortunate reputation to require a lot of bandwidth to distribute software. This reputation is sadly grounded. It seems like explicitly pointing to your dependencies comes with an overhead cost in terms of download size. Or does it?&lt;/p&gt;
&lt;p&gt;Currently, both Guix and Nix can substitute pre-built store paths from a binary cache using the Nix Archive (NAR) format. After conducting several benchmarks, we conclude that the substitution granularity should be finer to improve the substitution performance. We can increase the substitution granularity either by substituting files individually or by substituting chunked individual files.&lt;/p&gt;
&lt;p&gt;If you are already familiar with the Nix and Guix substitution mechanism, feel free to ignore the next two sections and directly jump to the &lt;a href=&#34;#rethinking-the-substitution-granularity&#34;&gt;&amp;ldquo;Rethinking The Substitution Granularity&amp;rdquo;&lt;/a&gt; one.&lt;/p&gt;
&lt;h2 id=&#34;you-said-substitution&#34;&gt;You Said Substitution?&lt;/h2&gt;
&lt;p&gt;We usually don&amp;rsquo;t want to rebuild the whole world locally. When we want to install Firefox on a laptop, we&amp;rsquo;d rather not build it from the source on the machine itself but instead download a pre-built version from a cache. For this purpose, Nix provides a store path substitution mechanism.&lt;/p&gt;
&lt;p&gt;Instead of building a derivation locally, we substitute the local build with a pre-built artifact stored in a binary cache. The store paths are getting transferred between the binary cache and the local machine using the NAR archive format. This format can be seen as a compressed store path. Meaning we&amp;rsquo;ll have a NAR archive per top-level store path we&amp;rsquo;d like to substitute.&lt;/p&gt;
&lt;p&gt;A NAR is always identified using a hash derived from its content. NARs are content-addressed.&lt;/p&gt;
&lt;p&gt;In contrast to that, both the Nix/Guix derivations and the store paths they generate are input-addressed&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;: their hashes are directly derived from their dependencies hashes.&lt;/p&gt;
&lt;p&gt;You now see the issue: on one hand, we have input-addressed derivations and store paths, on the other hand, content-addressed NARs.&lt;/p&gt;
&lt;p&gt;Starting from an input-addressed derivation hash, how are we supposed to figure out the name of the content-addressed NAR we want to download? In other words, how can we figure out the output hash of a derivation without building it first?&lt;/p&gt;
&lt;p&gt;Introducing the &lt;strong&gt;NARinfo&lt;/strong&gt; file. Let&amp;rsquo;s take an example:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-txt&#34; data-lang=&#34;txt&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;» curl https://cache.nixos.org/r5sjd57x0r07bwgipryaxqdkx1gglhiy.narinfo
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;StorePath: /nix/store/r5sjd57x0r07bwgipryaxqdkx1gglhiy-libidn2-2.3.2
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;URL: nar/010zh5j819qyz6zai6h2hn3d7i406g00m2g4g62bq7m8g91553yb.nar.xz
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Compression: xz
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;FileHash: sha256:010zh5j819qyz6zai6h2hn3d7i406g00m2g4g62bq7m8g91553yb
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;FileSize: 63248
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;NarHash: sha256:1npw0jz1cw4k9x25f2vsdhsa5cf9568j46bpz2768a1izqj5n9lf
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;NarSize: 260816
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;References: 0z7sqj4pilbqyp45ix5b0mdgn9xlb024-libunistring-0.9.10 r5sjd57x0r07bwgipryaxqdkx1gglhiy-libidn2-2.3.2
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Deriver: 0n91syjwrhmng41f8d23ad0sl4a6ic4g-libidn2-2.3.2.drv
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Sig: cache.nixos.org-1:G9PxMT0/nd9ELwL3BBeqWtb2ohMiqw4T4FUwFlAU9M0E45mKg77BzL9gJQ3wH4oYtsa61MV5uzNLPFvK8ZbyCA==
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To substitute the path &lt;code&gt;/nix/store/r5sjd57x0r07bwgipryaxqdkx1gglhiy-libidn2-2.3.2&lt;/code&gt; from the &lt;code&gt;cache.nixos.org&lt;/code&gt; binary cache, we&amp;rsquo;ll first have to download its associated NARinfo file. That NARinfo file is addressed using the same input-addressed hash as its associated store path: &lt;code&gt;r5sjd57x0r07bwgipryaxqdkx1gglhiy&lt;/code&gt;. The NARinfo will contain some metadata needed to perform the substitution, but also, and most importantly, the URL from which we can download the content-addressed NAR.&lt;/p&gt;
&lt;p&gt;To sum up, to perform a substitution, the Nix/Guix client will:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Compute the input-addressed store path of the derivation.&lt;/li&gt;
&lt;li&gt;Fetch the associated &lt;code&gt;.narinfo&lt;/code&gt; from the binary cache it wants to substitute from.&lt;/li&gt;
&lt;li&gt;Download the content-addressed NARs referenced by the &lt;code&gt;.narinfo&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Unpack the downloaded NARs to the local Nix store.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;substitution-hell&#34;&gt;Substitution Hell&lt;/h2&gt;
&lt;p&gt;Now that we established how the Nix substitution mechanism works, let&amp;rsquo;s have a thought experiment: what will happen if we bump the version of a derivation living deep in the Nixpkgs/Guix dependency tree? Something like &lt;code&gt;curl&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;curl&lt;/code&gt; store path being input-addressed, its name will change for sure. Likewise, its dependencies are also input-addressed, their name will also change. Overall, the &lt;code&gt;curl&lt;/code&gt; store path change will propagate throughout all the Nixpkgs/Guix dependency graph.&lt;/p&gt;
&lt;p&gt;You might say:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Hey! That&amp;rsquo;s not a big deal.&lt;/p&gt;
&lt;p&gt;The NARs are content addressed, this change of input hash shouldn&amp;rsquo;t propagate there.&lt;/p&gt;
&lt;p&gt;The binary cache might have to re-build the whole dependency graph, however, outside of curl, the content of the derivations should remain the same. So, in the end, most of the NARs should remain the same. The clients shouldn&amp;rsquo;t have to re-download all the store paths depending on curl.&lt;/p&gt;
&lt;p&gt;We won&amp;rsquo;t have to re-download massive derivations like Firefox and Chromium for a simple curl bump.&lt;/p&gt;
&lt;p&gt;Right?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And you&amp;rsquo;d be right except for one thing: in the Nix/Guix model, everything is explicit. Every time you&amp;rsquo;ll refer to the curl binary, you&amp;rsquo;ll have to explicitly refer to its store path &lt;code&gt;/nix/${inputHash}-curl/bin/curl&lt;/code&gt;. Its &lt;strong&gt;input-addressed&lt;/strong&gt; store path! This sneaky reference will manage to propagate the input-adressed instability to the output-addressed NARs.&lt;/p&gt;
&lt;p&gt;Coming back to your example: both Chromium and Firefox depend on curl. You&amp;rsquo;ll have to re-download both of these massive store path for every curl bump. Hopefully, you won&amp;rsquo;t mind downloading a couple of gigabytes for a sub-kB string change? Do you? :P&lt;/p&gt;
&lt;p&gt;One has to think: could we improve this situation?&lt;/p&gt;
&lt;h2 id=&#34;rethinking-the-substitution-granularity&#34;&gt;Rethinking the Substitution Granularity&lt;/h2&gt;
&lt;p&gt;Content addressing the substitution archives is definitely a good idea. It brings some hash stability to this unstable input-addressed scheme. However, as we saw earlier, because of the store path references, the input-addressed hash instability ends up propagating through the substitution mechanism.&lt;/p&gt;
&lt;p&gt;To improve the substitution stability, we could think about two solutions:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Remove the store references from the NAR and replace them on the fly when decompressing the NAR into the actual Nix/Guix store.&lt;/li&gt;
&lt;li&gt;Increase the substitution granularity.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I&amp;rsquo;m going to ignore that first solution for this post and focus on the &amp;ldquo;Increase the substitution granularity&amp;rdquo; solution.&lt;/p&gt;
&lt;p&gt;The Nix and Guix communities came up with two solutions based on this finer granularity substitution idea: &lt;a href=&#34;https://github.com/flokli/nix-casync/&#34;&gt;nix-casync&lt;/a&gt; and a file-based substitution discussed in the &lt;a href=&#34;https://lists.gnu.org/archive/html/guix-devel/2021-01/msg00079.html&#34;&gt;Guix mailing list&lt;/a&gt;.&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;Living in the countryside, my only Internet connection is a weak LTE link. I have 3 NixOS and 1 Guix system at home. Any mass-rebuild takes me at least 2 hours to substitute on a good day.&lt;/p&gt;
&lt;p&gt;Needless to say, I have a personal urge to improve that situation!&lt;/p&gt;
&lt;p&gt;Before blindly stepping into what looks like a multi-year-long sustained implementation effort, I wanted to gather some hard data showing how much we could gain from increasing the substitution granularity. It&amp;rsquo;s benchmarking time.&lt;/p&gt;
&lt;h2 id=&#34;benchmarking-fine-granularity-substitution-mechanisms&#34;&gt;Benchmarking Fine Granularity Substitution Mechanisms&lt;/h2&gt;
&lt;p&gt;For various scenarios, we&amp;rsquo;re going to build a Nix closure for 2 Nixpkgs commits. Then, we&amp;rsquo;ll simulate an update from the first commit to the second one and see how much data we&amp;rsquo;ll need to download from the binary cache.&lt;/p&gt;
&lt;p&gt;For each scenario, we&amp;rsquo;re going to evaluate the 4 following substitution techniques:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Nar substitution&lt;/strong&gt;: This is the substitution model currently used by the NixOS and Guix projects, it&amp;rsquo;ll act as our metrics baseline. It consists in archiving and compressing a full store path. In this benchmark, we&amp;rsquo;ll identify each NAR by its filename derived from the &lt;code&gt;sha256sum&lt;/code&gt; of its content.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Casync substitution&lt;/strong&gt;: This is an experimental substitution mechanism implemented via the &lt;a href=&#34;https://github.com/flokli/nix-casync&#34;&gt;nix-casync&lt;/a&gt; project. Starting from a NAR, we decompress it, serialize all the files in a single stream, and chunk it into smaller bits before compressing them again. In this benchmark, we&amp;rsquo;ll identify each casync chunk by its filename, itself already derived from the &lt;code&gt;sha256&lt;/code&gt; sum of its content.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;File-based substitution&lt;/strong&gt;: This is the substitution method &lt;a href=&#34;https://lists.gnu.org/archive/html/guix-devel/2021-01/msg00079.html&#34;&gt;the Guix&lt;/a&gt; project brainstormed around. Each store gets substituted separately. In this benchmark, we&amp;rsquo;ll identify these files using the &lt;code&gt;sha256&lt;/code&gt; sum of their content.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;XZ-Compressed File-based substitution&lt;/strong&gt;: similar to the File-based substitution except each file is individually compressed using the xz compression mechanism (profile 6 extreme).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If you&amp;rsquo;re interested in the nitty-gritty details, the source code and the raw data behind these benchmarks live &lt;a href=&#34;https://github.com/PicNoir/nix-guix-substitution-benchmark&#34;&gt;in this git repository&lt;/a&gt;. You can also have a look at the &lt;a href=&#34;https://alternativebit.fr/assets/substitution-benchmark-jupyter-notebook.html&#34;&gt;Jupyter notebook I used to crunch the benchmarks data&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Note: we&amp;rsquo;re using NixOS/Nixpkgs for all these benchmarks. However, provided Guix currently uses the same substitution mechanism, you can safely assume the same conclusions holds for it as well.&lt;/p&gt;
&lt;h3 id=&#34;mass-rebuild&#34;&gt;Mass Rebuild&lt;/h3&gt;
&lt;p&gt;Let&amp;rsquo;s kill the suspense right away and have a look at the most interesting scenario first. We&amp;rsquo;re going to simulate a NixOS closure update after the &lt;a href=&#34;https://github.com/NixOS/nixpkgs/pull/148396&#34;&gt;staging-next 2021-12-03&lt;/a&gt; merge containing, among other things, a &lt;code&gt;curl&lt;/code&gt; update. As we already established in the &amp;ldquo;&lt;a href=&#34;#substitution-hell&#34;&gt;Substitution Hell&lt;/a&gt;&amp;rdquo; section, a curl bump will propagate through the whole NixOS dependency graph. That&amp;rsquo;s the worst-case scenario in terms of substitution: the mass rebuild.&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;re going to use a &lt;a href=&#34;https://github.com/PicNoir/nix-guix-substitution-benchmark/blob/314f0e9fed0245161861ea333f737f6d9d28b955/machine.nix&#34;&gt;NixOS machine description&lt;/a&gt; depending on several diverse language stacks (C, Python, BEAM) and simulate a closure update after this mass rebuild.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s see how the four substitution techniques perform:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://alternativebit.fr/images/nix-substitution/mass-rebuild-diagram.svg&#34; alt=&#34;Mass Rebuild Diagram&#34;&gt;&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Name&lt;/th&gt;
					&lt;th&gt;Closure Size (MB)&lt;/th&gt;
					&lt;th&gt;Downloaded Size (MB)&lt;/th&gt;
					&lt;th&gt;Re-used Size (MB)&lt;/th&gt;
					&lt;th&gt;DL Savings Compared to NAR (%)&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;NAR&lt;/td&gt;
					&lt;td&gt;386.06&lt;/td&gt;
					&lt;td&gt;372.99&lt;/td&gt;
					&lt;td&gt;13.0702&lt;/td&gt;
					&lt;td&gt;0&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Casync&lt;/td&gt;
					&lt;td&gt;608.652&lt;/td&gt;
					&lt;td&gt;192.195&lt;/td&gt;
					&lt;td&gt;416.457&lt;/td&gt;
					&lt;td&gt;48.4719&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;File&lt;/td&gt;
					&lt;td&gt;1652.19&lt;/td&gt;
					&lt;td&gt;705.665&lt;/td&gt;
					&lt;td&gt;973.348&lt;/td&gt;
					&lt;td&gt;-89.1915&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Compressed File&lt;/td&gt;
					&lt;td&gt;476.523&lt;/td&gt;
					&lt;td&gt;229.489&lt;/td&gt;
					&lt;td&gt;247.034&lt;/td&gt;
					&lt;td&gt;38.4732&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;First, the good news: increasing the substitution granularity definitively works! The casync-based substitution almost cuts by half the amount of data we need to download for this mass rebuild.&lt;/p&gt;
&lt;p&gt;YAY!&lt;/p&gt;
&lt;p&gt;The xz-compressed file-based substitution sits in the same ballpark, saving us ~40% worth of data to download.&lt;/p&gt;
&lt;p&gt;We can already see two other things from this experiment:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Without proper compression, the file-based substitution performs about half as good as the NAR substitution.&lt;/li&gt;
&lt;li&gt;Looking at the closure size of the various substitution methods, we can realize that the finer the substitution granularity gets, the worse the compression algorithms are performing. In other words, the massive gain in terms of re-used data/downloaded data ratio (~75% for casync) does not directly translate into download savings. We probably want to spend some time investigating the state-of-the-art WRT. compression algorithm specialized in small files.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Provided these results, I&amp;rsquo;ll omit the uncompressed file substitution-related data for the next benchmarks: it&amp;rsquo;s consistently worse than the compressed counterpart in terms of performance and hurts the readability of the diagrams.&lt;/p&gt;
&lt;h3 id=&#34;channel-jump&#34;&gt;Channel Jump&lt;/h3&gt;
&lt;p&gt;In this scenario, provided the &lt;a href=&#34;https://github.com/NinjaTrappeur/nix-guix-substitution-benchmark/blob/314f0e9fed0245161861ea333f737f6d9d28b955/machine.nix&#34;&gt;same NixOS machine closure&lt;/a&gt; we used in the Mass Rebuild scenario, we&amp;rsquo;ll simulate a jump from the NixOS-21.11 stable channel to NixOS-unstable.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://alternativebit.fr/images/nix-substitution/channel-jump-diagram.svg&#34; alt=&#34;Channel Jump Diagram&#34;&gt;&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Name&lt;/th&gt;
					&lt;th&gt;Closure Size (MB)&lt;/th&gt;
					&lt;th&gt;Downloaded Size (MB)&lt;/th&gt;
					&lt;th&gt;Re-used Size (MB)&lt;/th&gt;
					&lt;th&gt;DL Savings Compared to NAR (%)&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;NAR&lt;/td&gt;
					&lt;td&gt;387.458&lt;/td&gt;
					&lt;td&gt;375.167&lt;/td&gt;
					&lt;td&gt;12.2905&lt;/td&gt;
					&lt;td&gt;0&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Casync&lt;/td&gt;
					&lt;td&gt;610.886&lt;/td&gt;
					&lt;td&gt;310.541&lt;/td&gt;
					&lt;td&gt;300.345&lt;/td&gt;
					&lt;td&gt;17.2261&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Compressed File&lt;/td&gt;
					&lt;td&gt;478.12&lt;/td&gt;
					&lt;td&gt;307.479&lt;/td&gt;
					&lt;td&gt;170.642&lt;/td&gt;
					&lt;td&gt;18.0423&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;I did not expect much savings here, but surprisingly enough, we&amp;rsquo;ll shave about 18% worth of downloaded data using the compressed file-based substitution or casync.&lt;/p&gt;
&lt;p&gt;This scenario does not make total sense from a pragmatic point of view as-is: you rarely jump a NixOS machine closure from a stable to unstable NixOS channel. It however does make sense in the context of a dev machine. You often have to jump between different projects having their own dev &lt;code&gt;nix-shell&lt;/code&gt; closures pointing to different Nixpkgs channels.&lt;/p&gt;
&lt;p&gt;The good and unexpected news is: even in that scenario, we can expect some substantial gains in terms of the amount of data we&amp;rsquo;ll need to download!&lt;/p&gt;
&lt;h3 id=&#34;single-derivation-bump&#34;&gt;Single Derivation Bump&lt;/h3&gt;
&lt;p&gt;Now, one may ask: can we expect any savings for a single derivation substitution?&lt;/p&gt;
&lt;p&gt;Ludovic Courtès recently &lt;a href=&#34;https://lists.gnu.org/archive/html/guix-devel/2020-12/msg00258.html&#34;&gt;posted a summary of his findings&lt;/a&gt; on that matter in the Guix mailing list for various binaries containing various amounts of binary data and text.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s replicate this experiment with our benchmark. We&amp;rsquo;re going to simulate a version bump for Firefox, Gimp, Emacs, and OpenMPI. To do so, we&amp;rsquo;ll build the two derivations before and after the version bump, and from there see how much data we&amp;rsquo;ll be able to re-use and how much data we&amp;rsquo;ll need to download.&lt;/p&gt;
&lt;h4 id=&#34;firefox&#34;&gt;Firefox&lt;/h4&gt;
&lt;p&gt;&lt;img src=&#34;https://alternativebit.fr/images/nix-substitution/firefox-bump.svg&#34; alt=&#34;Firefox bump results bar chart diagram. See the table below for a screen reader accessible alternative&#34;&gt;&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Name&lt;/th&gt;
					&lt;th&gt;Closure Size (MB)&lt;/th&gt;
					&lt;th&gt;Downloaded Size (MB)&lt;/th&gt;
					&lt;th&gt;Re-used Size (MB)&lt;/th&gt;
					&lt;th&gt;DL Savings Compared to NAR (%)&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;NAR&lt;/td&gt;
					&lt;td&gt;219.352&lt;/td&gt;
					&lt;td&gt;56.4315&lt;/td&gt;
					&lt;td&gt;162.92&lt;/td&gt;
					&lt;td&gt;0&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Casync&lt;/td&gt;
					&lt;td&gt;342.925&lt;/td&gt;
					&lt;td&gt;55.871&lt;/td&gt;
					&lt;td&gt;287.054&lt;/td&gt;
					&lt;td&gt;0.993342&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Compressed File&lt;/td&gt;
					&lt;td&gt;260.424&lt;/td&gt;
					&lt;td&gt;55.8295&lt;/td&gt;
					&lt;td&gt;204.594&lt;/td&gt;
					&lt;td&gt;1.06682&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 id=&#34;gimp&#34;&gt;Gimp&lt;/h4&gt;
&lt;p&gt;&lt;img src=&#34;https://alternativebit.fr/images/nix-substitution/gimp-bump.svg&#34; alt=&#34;Gimp bump results bar chart diagram. See the table below for a screen reader accessible alternative&#34;&gt;&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Name&lt;/th&gt;
					&lt;th&gt;Closure Size (MB)&lt;/th&gt;
					&lt;th&gt;Downloaded Size (MB)&lt;/th&gt;
					&lt;th&gt;Re-used Size (MB)&lt;/th&gt;
					&lt;th&gt;DL Savings Compared to NAR (%)&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;NAR&lt;/td&gt;
					&lt;td&gt;247.506&lt;/td&gt;
					&lt;td&gt;20.2708&lt;/td&gt;
					&lt;td&gt;227.236&lt;/td&gt;
					&lt;td&gt;0&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Casync&lt;/td&gt;
					&lt;td&gt;363.372&lt;/td&gt;
					&lt;td&gt;13.0572&lt;/td&gt;
					&lt;td&gt;350.315&lt;/td&gt;
					&lt;td&gt;35.586&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Compressed File&lt;/td&gt;
					&lt;td&gt;300.3&lt;/td&gt;
					&lt;td&gt;10.1874&lt;/td&gt;
					&lt;td&gt;290.113&lt;/td&gt;
					&lt;td&gt;49.7434&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 id=&#34;emacs&#34;&gt;Emacs&lt;/h4&gt;
&lt;p&gt;&lt;img src=&#34;https://alternativebit.fr/images/nix-substitution/emacs-bump.svg&#34; alt=&#34;Emacs bump results bar chart diagram. See the table below for a screen reader accessible alternative&#34;&gt;&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Name&lt;/th&gt;
					&lt;th&gt;Closure Size (MB)&lt;/th&gt;
					&lt;th&gt;Downloaded Size (MB)&lt;/th&gt;
					&lt;th&gt;Re-used Size (MB)&lt;/th&gt;
					&lt;th&gt;DL Savings Compared to NAR (%)&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;NAR&lt;/td&gt;
					&lt;td&gt;110.331&lt;/td&gt;
					&lt;td&gt;39.7443&lt;/td&gt;
					&lt;td&gt;70.5862&lt;/td&gt;
					&lt;td&gt;0&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Casync&lt;/td&gt;
					&lt;td&gt;177.734&lt;/td&gt;
					&lt;td&gt;44.7264&lt;/td&gt;
					&lt;td&gt;133.007&lt;/td&gt;
					&lt;td&gt;-12.5355&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Compressed File&lt;/td&gt;
					&lt;td&gt;136.6&lt;/td&gt;
					&lt;td&gt;40.7356&lt;/td&gt;
					&lt;td&gt;95.8641&lt;/td&gt;
					&lt;td&gt;-2.49415&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 id=&#34;open-mpi&#34;&gt;Open MPI&lt;/h4&gt;
&lt;p&gt;&lt;img src=&#34;https://alternativebit.fr/images/nix-substitution/openmpi-bump.svg&#34; alt=&#34;Open MPI bump results bar chart diagram. See the table below for a screen reader accessible alternative&#34;&gt;&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Name&lt;/th&gt;
					&lt;th&gt;Closure Size (MB)&lt;/th&gt;
					&lt;th&gt;Downloaded Size (MB)&lt;/th&gt;
					&lt;th&gt;Re-used Size (MB)&lt;/th&gt;
					&lt;th&gt;DL Savings Compared to NAR (%)&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;NAR&lt;/td&gt;
					&lt;td&gt;139.54&lt;/td&gt;
					&lt;td&gt;3.33596&lt;/td&gt;
					&lt;td&gt;136.204&lt;/td&gt;
					&lt;td&gt;0&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Casync&lt;/td&gt;
					&lt;td&gt;214.708&lt;/td&gt;
					&lt;td&gt;4.38082&lt;/td&gt;
					&lt;td&gt;210.328&lt;/td&gt;
					&lt;td&gt;-31.3209&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Compressed File&lt;/td&gt;
					&lt;td&gt;167.518&lt;/td&gt;
					&lt;td&gt;3.26096&lt;/td&gt;
					&lt;td&gt;164.257&lt;/td&gt;
					&lt;td&gt;2.24831&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Once again, looking at the closure sizes, we can see we are experiencing a loss in terms of compression efficiency. We have to pay back that loss with an even greater substitution performance to be better or even on par with the NAR-based substitution.&lt;/p&gt;
&lt;p&gt;So, does a increase in substitution granularity helps when substituting a single derivation?&lt;/p&gt;
&lt;p&gt;The answer is: it depends.&lt;/p&gt;
&lt;p&gt;While it&amp;rsquo;s clear that on a NixOS closure level the gains are almost always considerable, on a single derivation substitution, the gains will greatly depend on the content of the derivation. In the case of a derivation mostly containing text files such as Gimp, we can expect lot of savings. In the case of a derivation mostly consisting of binary data (Firefox, Emacs), the gains are likely to be residual.&lt;/p&gt;
&lt;p&gt;Casync is performing consistently worse than the compressed files approach. This can probably be explained by two things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Loss of the file boundaries: the chunks are generated from a serialized stream containing the whole store path content. It&amp;rsquo;d probably be a good idea to instead chunk the files individually in our use case.&lt;/li&gt;
&lt;li&gt;Shy compression: the Casync closure size is almost twice (!!) as big as the NAR one. While the small size of the chunks can explain to some extent this compression efficiency loss, &lt;code&gt;nix-casync&lt;/code&gt; is also using lzma to compress its chunks. Moving to more aggressive xz compression could be a good idea here.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;long-road-ahead&#34;&gt;Long Road Ahead&lt;/h2&gt;
&lt;p&gt;So, what&amp;rsquo;s the take-away here?&lt;/p&gt;
&lt;p&gt;Well, to me, the main one is that the current Nix/Guix substitution bloat story is not helpless. We can expect some serious improvements by increasing the substitution granularity. Be it using a file-based substitution mechanism or using a chunk-based one.&lt;/p&gt;
&lt;p&gt;It would probably be worth investing some resources into pushing these substitution mechanisms to a production-ready state. We should also seriously consider using them as the default substitution mechanism once they&amp;rsquo;re ready. And potentially give up on the NAR-based approach.&lt;/p&gt;
&lt;p&gt;Increasing the substitution granularity won&amp;rsquo;t sadly come for free in terms of complexity. It&amp;rsquo;ll affect a lot of things, possibly including rethinking the binary cache signature mechanism. We&amp;rsquo;ll likely need quite some time before being able to confidently replace the NAR-based substitution mechanism. It&amp;rsquo;s definitely not a weekend project.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Special thanks to &lt;a href=&#34;https://profpatsch.de&#34;&gt;Profpatsch&lt;/a&gt; for his spot on review.&lt;/p&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;Full disclosure, while it&amp;rsquo;s okay to omit them in the context of this article, there are two notable exceptions to this input-addressed nature:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Fixed Output Derivations (FODs): they are, as their name implies, already output-addressed. We know, at build time, their content hash. We use them to deterministically download stable artifacts from the Internet. EG. bootstrap seed, source code, binaries…&lt;/li&gt;
&lt;li&gt;A content-addressed store mode is available through an experimental feature. See &lt;a href=&#34;https://github.com/NixOS/rfcs/blob/master/rfcs/0062-content-addressed-paths.md&#34;&gt;RFC#62&lt;/a&gt; for more informations.&lt;/li&gt;
&lt;/ul&gt;
&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/li&gt;
&lt;li id=&#34;fn:2&#34;&gt;
&lt;p&gt;Bias Warning: the main author of nix-casync is an online friend.&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</description>
    </item>
    </channel>
</rss>
