<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Nix on Theo's ʕ•ᴥ•ʔ Park</title><link>https://theopark.me/tags/nix/</link><description>Recent content in Nix on Theo's ʕ•ᴥ•ʔ Park</description><generator>Hugo -- gohugo.io</generator><language>en-US</language><copyright>Copyright © 2026, Theo P.</copyright><lastBuildDate>Wed, 19 Aug 2026 11:06:00 -0400</lastBuildDate><atom:link href="https://theopark.me/tags/nix/index.xml" rel="self" type="application/rss+xml"/><item><title>Partial(?) Dendritic Pattern: My new NixOS Config Organization Scheme</title><link>https://theopark.me/blog/2026-08-19-partial-dendritic-pattern/</link><pubDate>Wed, 19 Aug 2026 11:06:00 -0400</pubDate><guid>https://theopark.me/blog/2026-08-19-partial-dendritic-pattern/</guid><description>I migrated to NixOS (and nix-darwin) about 6 months ago, and it quickly became the operating system for me. Even before discovering Nix, I meticulously organized my Dotfiles and did regular system cleanups, operating on the philosophy: &amp;ldquo;if it&amp;rsquo;s not in my dotfiles repo, it doesn&amp;rsquo;t exist on my system.&amp;rdquo;
Looking back, I wonder why it took me so long to adopt NixOS. It achieves exactly what I want, which is a system entirely defined by configuration files I control.</description><content:encoded><![CDATA[<p>I migrated to <a href="https://nixos.org/">NixOS</a> (and <a href="https://github.com/nix-darwin/nix-darwin">nix-darwin</a>) about 6 months ago, and it quickly became <em>the</em> operating system for me.
Even before discovering Nix, I meticulously organized my Dotfiles and did regular system cleanups, operating on the philosophy: &ldquo;if it&rsquo;s not in my dotfiles repo, it doesn&rsquo;t exist on my system.&rdquo;</p>
<p>Looking back, I wonder why it took me so long to adopt NixOS.
It achieves exactly what I want, which is a system entirely defined by configuration files I control.</p>
<p><img alt="NixOS + Niri Screenshot" src="https://raw.githubusercontent.com/theopn/haunted-tiles/refs/heads/main/assets/niri-sc.png"></p>
<p>While I spent a considerable amount of time learning the ropes of Nix and NixOS, I&rsquo;ll save the dive into why I love it (as well as its flaws, including why I might eventually drop nix-darwin or home-manager) for another time.</p>
<p>Instead, this post is dedicated to showcasing the new organization scheme I &ldquo;developed&rdquo; for my Nix configuration.</p>
<h2 id="my-previous-pattern">My Previous Pattern</h2>
<p>When I first migrated to NixOS and nix-darwin, I used a very simple flake organization scheme:</p>





<pre tabindex="0"><code>$ tree
.
├── flake.nix
├── home-manager
│   ├── config
│   │   ├── bat.nix
│   │   ├── btop.nix
│   │   ├── ...
│   │   └── a-bunch-of-other-config.nix
│   ├── home.nix            # main import + fallthrough config for home-manager
│   ├── linux.nix           # Linux specific config import
│   └── nixvim
│       ├── ...
│       └── plugins
│           └── ...
└── hosts
    ├── beauvoir                        # Mac Mini
    │   ├── configuration.nix
    │   ├── ...
    │   └── extra-nix-darwin-config.nix
    └── wittgenstein                    # Framework 13
        ├── configuration.nix
        ├── hardware-configuration.nix
        ├── ...
        └── extra-nixos-config.nix</code></pre><p>In this setup, my flake.nix declared outputs like so:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="n">nixosConfigurations</span><span class="o">.</span><span class="n">wittgenstein</span> <span class="err">=</span> <span class="n">nixpkgs</span><span class="o">.</span><span class="n">lib</span><span class="o">.</span><span class="n">nixosSystem</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  <span class="n">system</span> <span class="o">=</span> <span class="s2">&#34;x86_64-linux&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  <span class="n">modules</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">nixos-hardware</span><span class="o">.</span><span class="n">nixosModules</span><span class="o">.</span><span class="n">framework-amd-ai-300-series</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="sr">./hosts/wittgenstein/configuration.nix</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="n">home-manager</span><span class="o">.</span><span class="n">nixosModules</span><span class="o">.</span><span class="n">home-manager</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="p">(</span><span class="n">mkHomeManager</span> <span class="s2">&#34;theopn&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">      <span class="n">home-manager</span><span class="o">.</span><span class="n">users</span><span class="o">.</span><span class="n">theopn</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="n">imports</span> <span class="o">=</span><span class="p">[</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">          <span class="n">nixvim</span><span class="o">.</span><span class="n">homeModules</span><span class="o">.</span><span class="n">nixvim</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">          <span class="sr">./home-manager/home.nix</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">          <span class="sr">./home-manager/linux.nix</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="p">];</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">  <span class="p">];</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="p">};</span></span></span></code></pre></div><p>This is a very classic scheme where files are strictly organized by scope (NixOS, home-manager, nix-darwin).
The biggest benefit of this approach is that it is incredibly easy to understand, especially when adding a new feature.</p>
<p>Let&rsquo;s say I want to add a Tailscale configuration, which needs to live at the NixOS system level.
I would just create a new file at <code>/hosts/wittgenstein/tailscale.nix</code> and add it to the imports in <code>configuration.nix</code> (or modify <code>configuration.nix</code> directly).</p>
<p><code>/hots/wittgenstein/tailscale.nix</code>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="p">{</span> <span class="n">config</span><span class="o">,</span> <span class="n">pkgs</span><span class="o">,</span> <span class="o">...</span> <span class="p">}:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  <span class="n">services</span><span class="o">.</span><span class="n">tailscale</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  <span class="n">networking</span><span class="o">.</span><span class="n">nftables</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  <span class="n">networking</span><span class="o">.</span><span class="n">firewall</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="n">trustedInterfaces</span> <span class="o">=</span> <span class="p">[</span> <span class="s2">&#34;tailscale0&#34;</span> <span class="p">];</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="n">allowedUDPPorts</span> <span class="o">=</span> <span class="p">[</span> <span class="n">config</span><span class="o">.</span><span class="n">services</span><span class="o">.</span><span class="n">tailscale</span><span class="o">.</span><span class="n">port</span> <span class="p">];</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">  <span class="n">systemd</span><span class="o">.</span><span class="n">services</span><span class="o">.</span><span class="n">tailscaled</span><span class="o">.</span><span class="n">serviceConfig</span><span class="o">.</span><span class="n">Environment</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="s2">&#34;TS_DEBUG_FIREWALL_MODE=nftables&#34;</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">  <span class="p">];</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p><code>/hosts/wittgenstein/configuration.nix</code>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="p">{</span> <span class="n">config</span><span class="o">,</span> <span class="n">lib</span><span class="o">,</span> <span class="n">pkgs</span><span class="o">,</span> <span class="o">...</span> <span class="p">}:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  <span class="n">imports</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">      <span class="sr">./hardware-configuration.nix</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">       <span class="c1">##### NEW IMPORT STATEMENT #####</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">      <span class="sr">./tailscale.nix</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">       <span class="c1">##### NEW IMPORT STATEMENT #####</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="p">];</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">  <span class="c1"># Boot settings</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">  <span class="n">boot</span><span class="o">.</span><span class="n">loader</span><span class="o">.</span><span class="n">systemd-boot</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">  <span class="n">boot</span><span class="o">.</span><span class="n">loader</span><span class="o">.</span><span class="n">efi</span><span class="o">.</span><span class="n">canTouchEfiVariables</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">  <span class="n">boot</span><span class="o">.</span><span class="n">kernelPackages</span> <span class="o">=</span> <span class="n">pkgs</span><span class="o">.</span><span class="n">linuxPackages_latest</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl">  <span class="c1"># rest of configuration.nix</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>The exact same idea applies to home-manager; I would make a new file and import it into the main home.nix.</p>
<p><code>/home-manager/config/ripgrep.nix</code>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="ln">1</span><span class="cl"><span class="p">{</span> <span class="o">...</span> <span class="p">}:</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">  <span class="n">programs</span><span class="o">.</span><span class="n">ripgrep</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">    <span class="n">arguments</span> <span class="o">=</span> <span class="p">[</span> <span class="s2">&#34;--hidden&#34;</span> <span class="s2">&#34;--glob=!.git/&#34;</span> <span class="p">];</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p><code>/home-manager/home.nix</code>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="p">{</span> <span class="n">pkgs</span><span class="o">,</span> <span class="n">lib</span><span class="o">,</span> <span class="n">config</span><span class="o">,</span> <span class="o">...</span> <span class="p">}:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  <span class="n">imports</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="sr">./config/bat.nix</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="sr">./config/btop.nix</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="c1">#...</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="c1">##### NEW IMPORT STATEMENT #####</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="sr">./config/ripgrep.nix</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="c1">##### NEW IMPORT STATEMENT #####</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">  <span class="p">];</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl">  <span class="n">home</span><span class="o">.</span><span class="n">sessionVariables</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="n">XDG_SCREENSHOTS_DIR</span> <span class="o">=</span> <span class="s2">&#34;</span><span class="si">${</span><span class="n">config</span><span class="o">.</span><span class="n">home</span><span class="o">.</span><span class="n">homeDirectory</span><span class="si">}</span><span class="s2">/Pictures&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="n">XDG_PICTURES_DIR</span> <span class="o">=</span> <span class="s2">&#34;</span><span class="si">${</span><span class="n">config</span><span class="o">.</span><span class="n">home</span><span class="o">.</span><span class="n">homeDirectory</span><span class="si">}</span><span class="s2">/Pictures&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl">  <span class="c1"># rest of home.nix</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>Frankly, I like this pattern.
I recommend it to anyone who wants to avoid unnecessary complications in their config.
However, it suffers from one fatal flaw: scattered configuration.</p>
<p>Let&rsquo;s say I&rsquo;m configuring Swaylock.
I can just make <code>swaylock.nix</code> with the following config and add it to <code>home.nix</code>, right?</p>
<p><code>/home-manager/config/swaylock.nix</code></p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="p">{</span> <span class="n">config</span><span class="o">,</span> <span class="o">...</span> <span class="p">}:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  <span class="n">programs</span><span class="o">.</span><span class="n">swaylock</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">settings</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">      <span class="n">daemonize</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">      <span class="c1"># allows fingerprint sensor -&gt; RET to unlock</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">      <span class="n">ignore-empty-password</span> <span class="o">=</span> <span class="no">false</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">      <span class="n">show-failed-attempts</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">      <span class="n">show-keyboard-layout</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">      <span class="n">image</span> <span class="o">=</span> <span class="s2">&#34;</span><span class="si">${</span><span class="n">config</span><span class="o">.</span><span class="n">home</span><span class="o">.</span><span class="n">homeDirectory</span><span class="si">}</span><span class="s2">/.local/share/theoshell/sway/lockscreen.png&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>Nope! You also need to register Swaylock with PAM, which has to happen at the NixOS system level. (Swaylock also has annoying behavior when used with fprintd, so a manual override in the PAM configuration is required).</p>
<p><code>/hosts/wittgenstein/swaylock.nix</code></p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="p">{</span> <span class="o">...</span> <span class="p">}:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  <span class="c1"># register swaylock to /etc/pam.d/</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  <span class="n">security</span><span class="o">.</span><span class="n">pam</span><span class="o">.</span><span class="n">services</span><span class="o">.</span><span class="n">swaylock</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="c1"># https://www.reddit.com/r/NixOS/comments/16oiazf/swaylock_fprintd_fingerprint_reader_issues/</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">text</span> <span class="o">=</span> <span class="s1">&#39;&#39;
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s1">      # Try password first
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s1">      auth sufficient pam_unix.so try_first_pass likeauth nullok nodelay
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s1">      # Then fprintd
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s1">      auth sufficient pam_fprintd.so
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s1">      # Fallback
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s1">      auth include login
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s1">    &#39;&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>Now, the configuration for a single tool is scattered across multiple directories.
This creates a readability and organization problem, especially as you scale up to multiple machines.
You might think importing the home-manager module is enough to get a working setup on a new laptop, only to have it break because you forgot the 5-line system-level PAM config buried in another directory.</p>
<h3 id="partial-dendritic-pattern">Partial(?) Dendritic Pattern</h3>
<p>The <a href="https://github.com/mightyiam/dendritic">Dendritic pattern</a> by mightyiam has become quite popular within the Nix community - and for good reasons.
While there are a few ways to achieve this architecture, most users use <a href="https://github.com/hercules-ci/flake-parts">flake-parts</a> to split configurations into modules and pair it with <a href="https://github.com/denful/import-tree">import-tree</a> to automatically scan and merge them.</p>
<p>This approach offers a lot of benefits, but what I appreciate most is that it allows you to organize Nix files by feature rather than by scope.
It perfectly solves the scattered config problem: you can have a single <code>modules/swaylock.nix</code> file that contains everything related to Swaylock.</p>
<p>However, fully adopting the Dendritic pattern comes with its own set of drawbacks, which I will discuss later.</p>
<p>So I found a middle ground.
I keep my core system configurations in the <code>/hosts</code> directory, and they are configured exactly how you would set up a standard NixOS/nix-darwin system.
But for all my custom configurations like apps, CLI tools, and window managers, I adopted a &ldquo;partial&rdquo; Dendritic pattern using flake-parts and import-tree.</p>
<p>Here is a simplified overview of the resulting tree:</p>





<pre tabindex="0"><code>$ tree
.
├── flake.nix
├── hosts
│   └── beauvoir
│       └── configuration.nix
│   └── wittgenstein
│       ├── configuration.nix
│       └── hardware-configuration.nix
└── modules
    ├── apps
    │   └── modules...
    ├── cli
    │   └── modules...
    ├── mac
    │   └── modules...
    └── wm
        └── more-modules...</code></pre><h4 id="adding-a-new-module">Adding a New Module</h4>
<p>Let&rsquo;s go back to the Swaylock example.
With the Dendritic pattern, I can define both the system-level and user-level configurations in a single file:</p>
<p><code>modules/wm/swaylock.nix</code></p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  <span class="n">flake</span><span class="o">.</span><span class="n">modules</span><span class="o">.</span><span class="n">nixos</span><span class="o">.</span><span class="n">swaylock</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="c1"># register swaylock with PAM</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="c1"># https://www.reddit.com/r/NixOS/comments/16oiazf/swaylock_fprintd_fingerprint_reader_issues/</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">security</span><span class="o">.</span><span class="n">pam</span><span class="o">.</span><span class="n">services</span><span class="o">.</span><span class="n">swaylock</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">      <span class="n">text</span> <span class="o">=</span> <span class="s1">&#39;&#39;
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s1">        # Try password first
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s1">        auth sufficient pam_unix.so try_first_pass likeauth nullok nodelay
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s1">        # Then fprintd
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s1">        auth sufficient pam_fprintd.so
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s1">        # Fallback
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s1">        auth include login
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s1">      &#39;&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl">  <span class="n">flake</span><span class="o">.</span><span class="n">modules</span><span class="o">.</span><span class="n">homeManager</span><span class="o">.</span><span class="n">swaylock</span> <span class="o">=</span> <span class="p">{</span> <span class="n">config</span><span class="o">,</span> <span class="o">...</span> <span class="p">}:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="n">programs</span><span class="o">.</span><span class="n">swaylock</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">      <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">      <span class="n">settings</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="n">daemonize</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="c1"># allows fingerprint sensor -&gt; RET to unlock</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="n">ignore-empty-password</span> <span class="o">=</span> <span class="no">false</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="n">show-failed-attempts</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">        <span class="n">show-keyboard-layout</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">
</span></span><span class="line"><span class="ln">27</span><span class="cl">        <span class="n">image</span> <span class="o">=</span> <span class="s2">&#34;</span><span class="si">${</span><span class="n">config</span><span class="o">.</span><span class="n">home</span><span class="o">.</span><span class="n">homeDirectory</span><span class="si">}</span><span class="s2">/.local/share/theoshell/sway/lockscreen.png&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><blockquote>
<p>[!NOTE]
Thanks to import-tree, you can name and nest subdirectories inside <code>modules/</code> however you please.</p>
</blockquote>
<p>If you prefer, instead of creating a dedicated <code>swaylock</code> module, you could group it under a broader name (e.g., <code>flake.modules.homeManager.wm-tools</code>, bundling it alongside tools like Swayidle and Swaybg).
I typically avoid this, though, because it sacrifices granularity.
If I for some reason decide to swap Swaylock for Hyprlock on just one machine, having them bundled together makes that much harder.</p>
<h4 id="adding-a-new-module--a-new-system-flake-output">Adding a New Module &amp; a New System (Flake Output)</h4>
<p>Once you&rsquo;ve declared a module, adding it to a system is self-explanatory.
Here is a look at the Flake output portion of my <code>flake.nix</code>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="ln"> 1</span><span class="cl">  <span class="n">nixosConfigurations</span><span class="o">.</span><span class="n">wittgenstein</span> <span class="err">=</span> <span class="n">nixpkgs</span><span class="o">.</span><span class="n">lib</span><span class="o">.</span><span class="n">nixosSystem</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="n">system</span> <span class="o">=</span> <span class="s2">&#34;x86_64-linux&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">specialArgs</span> <span class="o">=</span> <span class="p">{</span> <span class="k">inherit</span> <span class="n">inputs</span><span class="p">;</span> <span class="p">};</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">modules</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">      <span class="n">nixos-hardware</span><span class="o">.</span><span class="n">nixosModules</span><span class="o">.</span><span class="n">framework-amd-ai-300-series</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">      <span class="sr">./hosts/wittgenstein/configuration.nix</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">      <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="n">imports</span> <span class="o">=</span> <span class="k">with</span> <span class="n">self</span><span class="o">.</span><span class="n">modules</span><span class="o">.</span><span class="n">nixos</span><span class="p">;</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">          <span class="c1">##### added system level PAM settings for swaylock #####</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">          <span class="n">linux-base</span> <span class="n">niri</span> <span class="n">polkit</span> <span class="n">swaylock</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="p">];</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl">      <span class="n">home-manager</span><span class="o">.</span><span class="n">nixosModules</span><span class="o">.</span><span class="n">home-manager</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">      <span class="c1"># Calling a custom home-manager declaration function.</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">      <span class="c1"># Please see my repository for the full `flake.nix`.</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">      <span class="p">(</span><span class="n">mkHomeManager</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="n">saymyname</span> <span class="o">=</span> <span class="s2">&#34;theopn&#34;</span><span class="p">;</span> <span class="c1"># you&#39;re goddamn right</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="n">theosHomeManagerModules</span> <span class="o">=</span> <span class="k">with</span> <span class="n">self</span><span class="o">.</span><span class="n">modules</span><span class="o">.</span><span class="n">homeManager</span><span class="p">;</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">          <span class="n">base</span> <span class="n">linux-theme</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">          <span class="c1">##### added home-manager config module for Swaylock #####</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">          <span class="n">swaylock</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">        <span class="p">];</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">      <span class="p">})</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">
</span></span><span class="line"><span class="ln">28</span><span class="cl">    <span class="p">];</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="err">}</span><span class="p">;</span></span></span></code></pre></div><p>One caveat is that because <code>nixos.swaylock</code> and <code>homeManager.swaylock</code> belong to two entirely different module systems (even though they are in the same file), I still have to import them in both places.
It is still miles better than having the actual configurations scattered across multiple files, but it is an inefficiency I want to fix eventually.</p>
<p>Looking at this, you can also see how easy it is to add a completely new system.
You just copy and paste a default <code>configuration.nix</code> into a new directory under <code>/hosts</code>, create a new flake output, and add the modules you need for that specific machine.</p>
<h3 id="why-not-full-dendritic-pattern">Why Not Full Dendritic Pattern?</h3>
<p>The strict Dendritic pattern turns <em>everything</em>, including core system configurations, into Flake modules of equal importance.
Everything lives in the <code>modules/</code> directory, and your host-specific hardware configuration is no exception.
However, at least to mere casual Nix user like myself, this isn&rsquo;t very intuitive.</p>
<p>Intuitively, enabling a bootloader and installing a kernel package are fundamentally different, and much more system-specific, than configuring a terminal emulator.
I believe core system foundations deserve their own directory and a simpler Nix pattern, one that can still be easily understood (and theoretically function) even if abstractions like import-tree or flake-parts break.</p>
<p>Another problem I have with the full Dendritic pattern is that it can be hard to identify the actual entry points.
In my configuration, every file in the modules/ directory is simply a module, and the only entry point is <code>flake.nix</code>.</p>
<p>To summarize the benefits of my partial pattern:</p>
<ul>
<li>Easy System Maintenance: Anything in the <code>/hosts</code> directory can be easily <code>diff</code>ed against the auto-generated <code>configuration.nix</code> and <code>hardware-configuration.nix</code>, making system-level debugging much easier.</li>
<li>Clearer File Roles: Every file in the <code>/modules</code> directory (unless prefixed by <code>_</code>) declares module(s).
There is no need to guess what a file does or where the entry point is.</li>
<li>Centralized Overview: All modules are imported directly in <code>flake.nix</code>, so you get a clear overview of exactly what features a system contains just by looking at one file.</li>
<li>Feature Isolation: It preserves the &ldquo;one file per feature&rdquo; philosophy of the Dendritic pattern, keeping the <code>/modules</code> directory organized.</li>
</ul>
]]></content:encoded></item></channel></rss>