<?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>NixOS on paradigmatic.systems</title>
    <link>https://paradigmatic.systems/tags/nixos/</link>
    <description>Recent content in NixOS on paradigmatic.systems</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language>
    <lastBuildDate>Sun, 09 Aug 2026 14:30:00 +0000</lastBuildDate><atom:link href="https://paradigmatic.systems/tags/nixos/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Groundhog: A Simple Autonomous Agent</title>
      <link>https://paradigmatic.systems/posts/groundhog/</link>
      <pubDate>Sun, 09 Aug 2026 14:30:00 +0000</pubDate>
      
      <guid>https://paradigmatic.systems/posts/groundhog/</guid>
      <description>&lt;p&gt;Well, I&amp;rsquo;ve finally done it. After &lt;a href=&#34;https://paradigmatic.systems/posts/superfluous-abstractions&#34;&gt;commentating&lt;/a&gt; on the &lt;a href=&#34;https://paradigmatic.systems/posts/control-plane-context&#34;&gt;state&lt;/a&gt; of &lt;a href=&#34;https://paradigmatic.systems/posts/agent-bootstrapping&#34;&gt;agent&lt;/a&gt; development for some time, I&amp;rsquo;ve thrown my own project into the exponentially more illegible mix of vibe-coded slop.&lt;/p&gt;
&lt;p&gt;At the same time I&amp;rsquo;ve taken the leap into the undeniably funky realm of running an LLM as a persistent virtual assistant. This post introduces my new &lt;a href=&#34;https://github.com/roriholm/groundhog&#34;&gt;Groundhog&lt;/a&gt; project. It&amp;rsquo;s an extremely thin orchestration layer, defined at the &lt;code&gt;systemd&lt;/code&gt; level in a 200-line Nix module.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Well, I&rsquo;ve finally done it. After <a href="/posts/superfluous-abstractions">commentating</a> on the <a href="/posts/control-plane-context">state</a> of <a href="/posts/agent-bootstrapping">agent</a> development for some time, I&rsquo;ve thrown my own project into the exponentially more illegible mix of vibe-coded slop.</p>
<p>At the same time I&rsquo;ve taken the leap into the undeniably funky realm of running an LLM as a persistent virtual assistant. This post introduces my new <a href="https://github.com/roriholm/groundhog">Groundhog</a> project. It&rsquo;s an extremely thin orchestration layer, defined at the <code>systemd</code> level in a 200-line Nix module.</p>
<h2 id="the-state-of-play">The State of Play</h2>
<p>I held out for a long time on adopting harness-powered workflows. I preferred high-granularity manual control offered by Zed. This gave me ways to control</p>
<ul>
<li>cost. My token spend was a few dollars per month, even with (to me) heavy usage.</li>
<li>outcome. <code>(model quality) x (harness quality)</code> was simply not at a threshold where I&rsquo;d have a successful outcome.</li>
</ul>
<p>However, the tooling evolved out from under me, and I&rsquo;ve had to thoroughly rebase my assumptions. The two forcing factors were:</p>
<ol>
<li>My preferred Zed workflows were deprecated. The Text Thread feature was removed in favor of agent-only structure.</li>
<li>the Claude Code harness evolved, eventually addressing all of my <a href="/posts/control-plane-context">gripes</a> and achieving high success rates with the same models.</li>
</ol>
<p>Additionaly, Claude Code authentiates with a Pro account and I&rsquo;ve <em>never</em> run up against limits. Thus I&rsquo;ve finally arrived at the common consensus that you simply can&rsquo;t beat Claude Code as a driver. I have ceased to struggle and joined the Borg, like in The Star War.</p>
<h2 id="autonomy">Autonomy</h2>
<p>I&rsquo;ve felt a twinge of FOMO at autonomous agent projects like OpenClaw. Of course I want a little digital assistant looking out for my interests, keeping tabs on things I care about and pinging me when I need to know something.</p>
<p>There&rsquo;s one big problem: None of these projects has ever come remotely close to my comfort zone to run or interact with.</p>
<p>There&rsquo;s a tendency to bloat, especially once you get these things working on themselves. You expect me to install this app that&rsquo;s 15,000 lines written in some wretched goblin language like Python or Javascript? You must be getting out of town.</p>
<h2 id="obsidian-workflows">Obsidian workflows</h2>
<p>I&rsquo;m a longtime pre-AI user of Obsidian. I self-host with <code>syncthing</code> to keep notes synced between my phone and computers. In my day-to-day work I naturally fell into a workflow that some have dubbed the &ldquo;Second Brain&rdquo; system. It&rsquo;s the logical conclusion of both</p>
<ol>
<li>Managing your own knowledge base and</li>
<li>Creating context for AI agents.</li>
</ol>
<p>It just involves an extra step for the agent. Here&rsquo;s a few simple examples of prompts for Claude Code, where I have an Obsidian vault sitting at <code>~/o</code></p>
<blockquote>
<p>Read the design document at ~/o/topic/design.md. Investigate the codebase and update the document with any potential blockers. Create a plan.md with a step-by-step breakdown.</p></blockquote>
<blockquote>
<p>Execute on step 1 of ~/o/topic/plan.md.</p></blockquote>
<p>Essentially you want to keep a durable view of reality that&rsquo;s shared by consensus across agent sessions. Nobody needs to juggle sessions and figure out which chat to resume.</p>
<h2 id="groundhog">Groundhog</h2>
<p>Anyways, all this is to say that these 3 facts came together:</p>
<ol>
<li>Claude Code is now good enough to do anything</li>
<li>I appreciate a graph-based (as opposed to linear) approach to managing durable knowledge</li>
<li>I&rsquo;d like to give my system a little more autonomy</li>
</ol>
<p>This is a problem that&rsquo;s solved at the <code>systemd</code> level with a few lines of Nix. The <a href="https://github.com/roriholm/groundhog">groundhog</a> module definition slots into my VPS config like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-nix" data-lang="nix"><span style="display:flex;"><span>inputs<span style="color:#f92672">.</span>groundhog <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>    url <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;github:roriholm/groundhog&#34;</span>;
</span></span><span style="display:flex;"><span>};
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>services<span style="color:#f92672">.</span>groundhog <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>  enable <span style="color:#f92672">=</span> <span style="color:#66d9ef">true</span>;
</span></span><span style="display:flex;"><span>  tokenFile <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;/etc/groundhog/oauth-token&#34;</span>;
</span></span><span style="display:flex;"><span>};
</span></span></code></pre></div><p>An initial vault gets bootstrapped and registered with Syncthing. Then there&rsquo;s simple logic:</p>
<ol>
<li>A note syncing to the <code>/inbox</code> folder triggers a Claude Code instance.</li>
<li>The CC instance has access to the vault and is instructed to drain the inbox to end the wake cycle.</li>
<li>A configurable &ldquo;heartbeat&rdquo; wakeup triggers routine tasks.</li>
</ol>
<p>That&rsquo;s all that&rsquo;s happening at the system level. The behavior and vault structure emerge from usage. We don&rsquo;t need MCPs, complex integrations, and expansive harness logic. Claude already knows how to navigate through Markdown links, and knows what to do!</p>
<h2 id="extension">Extension</h2>
<p><strong>The control plane is shoved entirely into the context!</strong></p>
<p>Scheduled tasks are introduced by <em>conventions</em>. The heartbeat prompt routes to a <code>CLAUDE.md</code> which gives instructions. Those instructions route to a <code>scheduled_tasks.md</code>. Every line of that looks like:</p>
<p><code>do once a week, last done 8/9/2026, route/to/task.md</code></p>
<p>CC is smart enough to downselect and fan out subagents. Maybe <code>task.md</code> just says to check the price of Bitcoin and mention in my daily note if it&rsquo;s above $100k. (Just kidding don&rsquo;t care!). None of the scheduling logic ends up in my harness!</p>
<h2 id="security">Security</h2>
<p>Groundhog runs CC with <code>--dangerously-skip-permissions</code>. The agent is an unprivileged system user in a <code>systemd</code> jail. Everything is read-only except the vault and its own home. What the jail can&rsquo;t bound is the network, the inbox should be treated (guarded) like a shell.</p>
<h2 id="the-dream">The Dream</h2>
<p>AI is kind of a trap for a lot of computer-touching individuals. Lots of raw brainpower has been dumped into agent frameworks and harnesses. There&rsquo;s a dopamine hit of seeing an agent start to improve itself. My goal has been largely to remain aware while resisting the mind virus. Most of these projects are dead ends - <a href="https://vintagedata.org/blog/posts/model-is-the-product">Commentators</a> have noted the dynamics at play lead to vertical absorption of anything interesting. If your product is a thin wrapper around a Claude model, then there&rsquo;s not much barrier to them just implementing it.</p>
<p>Fear of being left behind drives a lot of AI-adjacent development. Instead I would like to engage with hope: that this technology should allow us to achieve more in less time, to make better decisions, and to be more aware of the world and the things that we care about.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Decoupling My Deployment Model</title>
      <link>https://paradigmatic.systems/posts/decoupling-deployment-model/</link>
      <pubDate>Sat, 11 Jul 2026 07:22:30 -0400</pubDate>
      
      <guid>https://paradigmatic.systems/posts/decoupling-deployment-model/</guid>
      <description>&lt;p&gt;In my previous &lt;a href=&#34;https://paradigmatic.systems/posts/setting-up-deploy-rs&#34;&gt;introduction to deploy-rs&lt;/a&gt; I shared a particular model of NixOS server deployment that I found very interesting:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Different components (websites) are treated as flake inputs.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;nginx&lt;/code&gt; config refers to the packaged outputs.&lt;/li&gt;
&lt;li&gt;The entire server only builds if all the components build.&lt;/li&gt;
&lt;li&gt;Deployments are atomic and minimal; the exact system is copied up.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This idea becomes particularly powerful once you have multiple nodes and can write &lt;a href=&#34;https://paradigmatic.systems/posts/integration-containers&#34;&gt;integration tests&lt;/a&gt; for your entire system then deploy across multiple machines with a single command.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In my previous <a href="/posts/setting-up-deploy-rs">introduction to deploy-rs</a> I shared a particular model of NixOS server deployment that I found very interesting:</p>
<ol>
<li>Different components (websites) are treated as flake inputs.</li>
<li>The <code>nginx</code> config refers to the packaged outputs.</li>
<li>The entire server only builds if all the components build.</li>
<li>Deployments are atomic and minimal; the exact system is copied up.</li>
</ol>
<p>This idea becomes particularly powerful once you have multiple nodes and can write <a href="/posts/integration-containers">integration tests</a> for your entire system then deploy across multiple machines with a single command.</p>
<p>But for my personal use case I started to notice some slight friction. Say I&rsquo;m updating this website with a new post. I&rsquo;m working in its repo. Once I&rsquo;m done, I tab back to the server config repo, run a <code>nix flake update</code> to pull in and re-pin the website, then <code>deploy</code>. It makes logical sense but doesn&rsquo;t feel as ergonomic as it could.</p>
<p>The system also breaks down with more <em>granular</em> deployments. Say I want to give an <em>untrusted tenant</em> the ability to update <code>paradigmatic.systems</code> without interfering with the other services running on the same server. It&rsquo;s not an unusual use case, and <code>deploy-rs</code> is designed with it in mind. Now I&rsquo;ve finally taken the time to migrate from <em>node-based</em> to <em>profile-based</em> deployment.</p>
<h2 id="attempting-to-keep-using-deploy-rs">Attempting to Keep Using deploy-rs</h2>
<p>The <code>deploy-rs</code> API is built for this, and it should be a simple change!</p>
<h3 id="in-the-server-repo">In the Server Repo</h3>
<p>I removed <code>paradigmatic-systems</code> from the flake inputs and instead defined an unprivileged deploy user:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-nix" data-lang="nix"><span style="display:flex;"><span>users<span style="color:#f92672">.</span>users<span style="color:#f92672">.</span>paradigmatic-deploy <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>  isSystemUser <span style="color:#f92672">=</span> <span style="color:#66d9ef">true</span>;
</span></span><span style="display:flex;"><span>  group <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;paradigmatic-deploy&#34;</span>;
</span></span><span style="display:flex;"><span>  createHome <span style="color:#f92672">=</span> <span style="color:#66d9ef">true</span>;
</span></span><span style="display:flex;"><span>  home <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;/var/lib/paradigmatic-deploy&#34;</span>;
</span></span><span style="display:flex;"><span>  shell <span style="color:#f92672">=</span> pkgs<span style="color:#f92672">.</span>bashInteractive;
</span></span><span style="display:flex;"><span>  openssh<span style="color:#f92672">.</span>authorizedKeys<span style="color:#f92672">.</span>keys <span style="color:#f92672">=</span> [
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;ssh-ed25519 AAAA...&#34;</span>
</span></span><span style="display:flex;"><span>  ];
</span></span><span style="display:flex;"><span>};
</span></span><span style="display:flex;"><span>users<span style="color:#f92672">.</span>groups<span style="color:#f92672">.</span>paradigmatic-deploy <span style="color:#f92672">=</span> { };
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>nix<span style="color:#f92672">.</span>settings<span style="color:#f92672">.</span>trusted-users <span style="color:#f92672">=</span> [ <span style="color:#e6db74">&#34;paradigmatic-deploy&#34;</span> ];
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># ensure the profile directory exists before the first deploy</span>
</span></span><span style="display:flex;"><span>systemd<span style="color:#f92672">.</span>tmpfiles<span style="color:#f92672">.</span>rules <span style="color:#f92672">=</span> [
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;d /nix/var/nix/profiles/per-user/paradigmatic-deploy 0755 paradigmatic-deploy paradigmatic-deploy -&#34;</span>
</span></span><span style="display:flex;"><span>];
</span></span></code></pre></div><p>Yes, I&rsquo;ve contradicted myself by making <code>paradigmatic-deploy</code> a trusted user. I&rsquo;ll come back to that later. But for now let&rsquo;s say &ldquo;we don&rsquo;t think they&rsquo;re going to upload malware, they just need guardrails from the other services&rdquo;.</p>
<p>Then I proxy the website to the actual user profile:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-nix" data-lang="nix"><span style="display:flex;"><span>services<span style="color:#f92672">.</span>nginx <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>  enable <span style="color:#f92672">=</span> <span style="color:#66d9ef">true</span>;
</span></span><span style="display:flex;"><span>  virtualHosts<span style="color:#f92672">.</span><span style="color:#e6db74">&#34;paradigmatic.systems&#34;</span> <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>    root <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;/nix/var/nix/profiles/per-user/paradigmatic-deploy/website&#34;</span>;
</span></span><span style="display:flex;"><span>  };
</span></span><span style="display:flex;"><span>};
</span></span></code></pre></div><p>In NixOS, a profile is a stable and mutable pointer into the (immutable) Nix store. It&rsquo;s a symlink managed as a series of numbered generations. For a desktop environment it might wrap your entire system closure, including your kernel, services, and every installed package. In this case it&rsquo;s just a single website!</p>
<h3 id="in-the-website-repo">In the Website Repo</h3>
<p>We add <code>deploy-rs</code> to the website flake inputs, and configure it to set the <code>website</code> profile:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-nix" data-lang="nix"><span style="display:flex;"><span>deploy<span style="color:#f92672">.</span>nodes<span style="color:#f92672">.</span>paradigmatic-systems <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>  hostname <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;my-node&#34;</span>;
</span></span><span style="display:flex;"><span>  profiles<span style="color:#f92672">.</span>website <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>    sshUser <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;paradigmatic-deploy&#34;</span>;
</span></span><span style="display:flex;"><span>    user <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;paradigmatic-deploy&#34;</span>;
</span></span><span style="display:flex;"><span>    path <span style="color:#f92672">=</span> deploy-rs<span style="color:#f92672">.</span>lib<span style="color:#f92672">.</span>x86_64-linux<span style="color:#f92672">.</span>activate<span style="color:#f92672">.</span>profile
</span></span><span style="display:flex;"><span>      self<span style="color:#f92672">.</span>packages<span style="color:#f92672">.</span>x86_64-linux<span style="color:#f92672">.</span>website;
</span></span><span style="display:flex;"><span>  };
</span></span><span style="display:flex;"><span>};
</span></span></code></pre></div><p><code>profiles.website</code> is the rendezvous point for the two flakes. The <code>path</code> attribute is what actually gets copied up. The <code>activate.profile</code> wrapper is an activation hook that <code>deploy-rs</code> uses to verify the deployment. It&rsquo;s also the villain of this story&hellip;</p>
<h2 id="the-snag">The Snag</h2>
<p>The above code worked beautifully from my NixOS machine. I was excited to demonstrate the system to my &ldquo;untrusted tenant&rdquo; and went to run the deployment from her macOS machine. My eyes darted across <code>deploy-rs.lib.x86_64-linux.activate.profile</code> and briefly questioned things before seeing a blaring <code>System Mismatch</code> error.</p>
<p>The activation wrapper and the host system require different architectures. To build it correctly I&rsquo;d need to pull in an entire Linux toolchain. <a href="https://nix.dev/tutorials/cross-compilation.html">Cross-compilation</a> is pretty robust, but how much do I want to beef up my flake for a static website that won&rsquo;t differ from one architecture to the next!?</p>
<h2 id="an-easy-escape-hatch">An Easy Escape Hatch</h2>
<p>One quick option offered by <code>deploy-rs</code> is the <code>remoteBuild = true</code> flag which pushes the derivation up and has the server build it on that end. Yes, it&rsquo;s that simple. However, my cheap-o server is sized for the services it runs; not for pulling in the whole build-time closure.</p>
<h2 id="switching-it-out-to-nix-copy">Switching it Out to nix-copy</h2>
<p>In the interest of moving forward with my life I opted to go under the hood to the same core Nix tools used by <code>deploy-rs</code>.
The server flake remained basically the same! In the website flake I defined a simple deploy app:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-nix" data-lang="nix"><span style="display:flex;"><span>  deploy-website <span style="color:#f92672">=</span> pkgs<span style="color:#f92672">.</span>writeShellApplication {
</span></span><span style="display:flex;"><span>    name <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;deploy-website&#34;</span>;
</span></span><span style="display:flex;"><span>    text <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;&#39;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      target=&#34;</span><span style="color:#ae81ff">&#39;&#39;$</span><span style="color:#e6db74">{DEPLOY_TARGET:-paradigmatic-deploy@my-node}&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      site=&#34;</span><span style="color:#e6db74">${</span>self<span style="color:#f92672">.</span>packages<span style="color:#f92672">.</span><span style="color:#e6db74">${</span>system<span style="color:#e6db74">}</span><span style="color:#f92672">.</span>website<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      nix copy --no-check-sigs --to &#34;ssh://$target&#34; &#34;$site&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      echo &#34;==&gt; Activating&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      ssh &#34;$target&#34; nix-env --profile /nix/var/nix/profiles/per-user/paradigmatic-deploy/website --set &#34;$site&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      echo &#34;==&gt; Deployed https://paradigmatic.systems&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    &#39;&#39;</span>;
</span></span><span style="display:flex;"><span>  };
</span></span></code></pre></div><p>All we&rsquo;re doing is copying up the package and making the profile switch. The <code>--no-check-sigs</code> flag is the counterpart of being in the <code>trusted-users</code> list. Let&rsquo;s talk about that briefly.</p>
<h2 id="the-threat-model">The Threat Model</h2>
<p>I&rsquo;ve given a fairly powerful capability to what I called an &ldquo;untrusted&rdquo; user, so I should probably address that a bit. Realistically, anyone who can write to the shared <code>/nix/store</code> is within my trust boundary.</p>
<p>Signing keys improve the situation with respect to accidents and shallow compromises. The trust is revocable and auditable, and composes into real CI architecture. But the tenant can still inject arbitrary store paths.</p>
<p>For handling a true scumbag tenant the design changes shape completely. They never get store write access whatsoever. They push up source and the server builds on its own terms. That, or they get a dedicated container or VM with its own store.</p>
<h2 id="conclusion">Conclusion</h2>
<p>I needed more granularity in my deployment, so I broke out a piece of my server config. Instead of being baked in, it&rsquo;s configured with a symlink that&rsquo;s switchable by a specific user.</p>
<p>To route around a mild inconvenience I dropped <code>deploy-rs</code> in favor of Nix primitives. I&rsquo;m using the same underlying manipulation of the nix store, so I still get atomicity, history, and garbage collection.</p>
<p>The main thing I lost is the &ldquo;magic rollback&rdquo; feature of deploy-rs where the system auto-reverts if SSH becomes unreachable. However, for this class of deployment (a static site swap) it&rsquo;s not a big risk.</p>
<p><code>deploy-rs</code> is a powerful tool for multi-node or multi-profile orchestration with interactive confirmation, and it&rsquo;s on my roadmap to work on some more interesting examples later!</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Self-Hosting with NixOS</title>
      <link>https://paradigmatic.systems/posts/self-hosting-nixos/</link>
      <pubDate>Sun, 12 Apr 2026 04:30:00 +0000</pubDate>
      
      <guid>https://paradigmatic.systems/posts/self-hosting-nixos/</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve been meaning to upgrade my home network based on some of the lessons I&amp;rsquo;ve learned deploying NixOS on a VPS. I&amp;rsquo;ve got a few of these little ProDesk machines that I got on eBay a while back. They&amp;rsquo;re great if you want some lightweight processes running 24/7. But they were still running Ubuntu! Oof! Time for an overhaul.&lt;/p&gt;
&lt;h2 id=&#34;1-deploy-over-network-with-deploy-rs&#34;&gt;1: Deploy Over Network with deploy-rs&lt;/h2&gt;
&lt;p&gt;Based on my &lt;a href=&#34;https://paradigmatic.systems/posts/setting-up-deploy-rs&#34;&gt;experience&lt;/a&gt; with a DigitalOcean VPS I knew I wanted to use the &lt;code&gt;deploy-rs&lt;/code&gt; model on a local node. Here&amp;rsquo;s the basic idea:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I&rsquo;ve been meaning to upgrade my home network based on some of the lessons I&rsquo;ve learned deploying NixOS on a VPS. I&rsquo;ve got a few of these little ProDesk machines that I got on eBay a while back. They&rsquo;re great if you want some lightweight processes running 24/7. But they were still running Ubuntu! Oof! Time for an overhaul.</p>
<h2 id="1-deploy-over-network-with-deploy-rs">1: Deploy Over Network with deploy-rs</h2>
<p>Based on my <a href="/posts/setting-up-deploy-rs">experience</a> with a DigitalOcean VPS I knew I wanted to use the <code>deploy-rs</code> model on a local node. Here&rsquo;s the basic idea:</p>
<ul>
<li>The node configuration lives in version control.</li>
<li>You edit it and build it on your workstation.</li>
<li>You push the complete build over the network onto the node.</li>
<li>Errors are caught with atomic rollbacks.</li>
</ul>
<p>That means I don&rsquo;t need physical access to the machine. It can live in the basement with the router. I don&rsquo;t even need to SSH in. Just edit the config and <code>deploy</code>. The process is very similar to the instructions in my other post:</p>
<ol>
<li>Install NixOS on the node</li>
<li>Modify the configuration:
<ul>
<li>Add your workstation SSH key to <code>users.root.openssh.authorizedKeys.keys</code></li>
<li>Enable Flakes</li>
</ul>
</li>
<li>Copy the contents of <code>/etc/nixos/</code> to your workstation and wrap them in a flake that specifies its hostname.</li>
<li>Add the <code>deploy-rs</code> configuration and then <code>deploy</code>.</li>
<li>Your node is unchanged, but now the configuration lives on your workstation and in version control.</li>
</ol>
<p>Just make sure your SSH keys are loaded with <code>ssh-add</code> - it&rsquo;s basic but this tripped me up as I thought it was a user configuration issue.</p>
<h2 id="2-define-a-dns-overlay-server-with-dnsmasq">2: Define a DNS Overlay Server with dnsmasq</h2>
<p>If you&rsquo;ve served custom domains over LAN before, you may have gone into your router settings and added custom DNS entries. This is something I wanted to avoid: I wanted to be able to add new domains purely in my node configuration.</p>
<p>So the first step was to go to a different place in the router settings. This will be different for everyone. For me, it was under Broadband Connection settings -&gt; &ldquo;Obtain IPv4 DNS Addresses Automatically&rdquo;. I adjusted this to point to the node&rsquo;s IP.</p>
<p>Next, configuring the node to act as a DNS server was easy as pie with the <code>dnsmasq</code> module:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-nix" data-lang="nix"><span style="display:flex;"><span>services<span style="color:#f92672">.</span>dnsmasq <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>  enable <span style="color:#f92672">=</span> <span style="color:#66d9ef">true</span>;
</span></span><span style="display:flex;"><span>  settings <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>    server <span style="color:#f92672">=</span> [
</span></span><span style="display:flex;"><span>      <span style="color:#e6db74">&#34;8.8.8.8&#34;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#e6db74">&#34;8.8.4.4&#34;</span>
</span></span><span style="display:flex;"><span>    ];
</span></span><span style="display:flex;"><span>    domain <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;home&#34;</span>;
</span></span><span style="display:flex;"><span>    local <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;/home/&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    address <span style="color:#f92672">=</span> [
</span></span><span style="display:flex;"><span>      <span style="color:#e6db74">&#34;/test.home/mynode&#34;</span>
</span></span><span style="display:flex;"><span>    ];
</span></span><span style="display:flex;"><span>  };
</span></span><span style="display:flex;"><span>};
</span></span></code></pre></div><p>I tried using a cute custom domain extension but couldn&rsquo;t get that to work. No big deal and I don&rsquo;t care enough to pull that thread: <code>.home</code> and <code>.lan</code> both worked.</p>
<p>Now since that domain is resolving to the same node, we need to add a virtual host. The endgame is to reverse proxy these domains to various services, but we&rsquo;ll start with a static response:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-nix" data-lang="nix"><span style="display:flex;"><span>services<span style="color:#f92672">.</span>nginx <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>  enable <span style="color:#f92672">=</span> <span style="color:#66d9ef">true</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  virtualHosts<span style="color:#f92672">.</span><span style="color:#e6db74">&#34;test.home&#34;</span> <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>    locations<span style="color:#f92672">.</span><span style="color:#e6db74">&#34;/&#34;</span> <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>      return <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;200 &#39;hello&#39;&#34;</span>;
</span></span><span style="display:flex;"><span>      extraConfig <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;&#39;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        add_header Content-Type text/plain;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      &#39;&#39;</span>;
</span></span><span style="display:flex;"><span>    };
</span></span><span style="display:flex;"><span>  };
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>If everything worked, you should be able to hit <code>http://test.home</code> from any device on your network, and get a nice little <code>hello</code>!</p>
<h2 id="3-configure-frigate-net-video-recorder">3: Configure Frigate (Net Video Recorder)</h2>
<p>If you&rsquo;ve followed to this point, then your system is a blank slate for adding new services under custom domains. I&rsquo;ll walk through this <a href="https://frigate.video/">Frigate</a> example since that&rsquo;s the main thing I was migrating! It was straightforward configuring the rtsp feed of my IP cameras. But with naive settings the CPU usage was very high. It took some fiddling to get the Intel hardware acceleration enabled and reduce some of the overhead. I&rsquo;ll annotate this fragment:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-nix" data-lang="nix"><span style="display:flex;"><span>services<span style="color:#f92672">.</span>frigate <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>  enable <span style="color:#f92672">=</span> <span style="color:#66d9ef">true</span>;
</span></span><span style="display:flex;"><span>  hostname <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;localhost&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  settings <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>    mqtt<span style="color:#f92672">.</span>enabled <span style="color:#f92672">=</span> <span style="color:#66d9ef">false</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">#This enables the Intel hardware acceleration</span>
</span></span><span style="display:flex;"><span>    ffmpeg <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>      hwaccel_args <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;preset-vaapi&#34;</span>;
</span></span><span style="display:flex;"><span>    };
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    cameras<span style="color:#f92672">.</span><span style="color:#e6db74">&#34;front_camera&#34;</span> <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>      ffmpeg<span style="color:#f92672">.</span>inputs <span style="color:#f92672">=</span> [{
</span></span><span style="display:flex;"><span>        path <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;rtsp://admin:pwd@192.168.1.54:554/h264Preview_01_main&#34;</span>;
</span></span><span style="display:flex;"><span>        roles <span style="color:#f92672">=</span> [ <span style="color:#e6db74">&#34;record&#34;</span> ];
</span></span><span style="display:flex;"><span>      }
</span></span><span style="display:flex;"><span>      <span style="color:#75715e"># This second input needs to be specified even though we&#39;re not</span>
</span></span><span style="display:flex;"><span>      <span style="color:#75715e"># doing detection, otherwise a high def feed is getting fed </span>
</span></span><span style="display:flex;"><span>      <span style="color:#75715e"># through to the dashboard! </span>
</span></span><span style="display:flex;"><span>      {
</span></span><span style="display:flex;"><span>        path <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;rtsp://admin:pwd@192.168.1.54:554/h264Preview_01_sub&#34;</span>;
</span></span><span style="display:flex;"><span>        roles <span style="color:#f92672">=</span> [ <span style="color:#e6db74">&#34;detect&#34;</span> ];
</span></span><span style="display:flex;"><span>      }
</span></span><span style="display:flex;"><span>      ];
</span></span><span style="display:flex;"><span>      detect<span style="color:#f92672">.</span>enabled <span style="color:#f92672">=</span> <span style="color:#66d9ef">false</span>;
</span></span><span style="display:flex;"><span>    };
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    record <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>      enabled <span style="color:#f92672">=</span> <span style="color:#66d9ef">true</span>;
</span></span><span style="display:flex;"><span>      retain <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>        days <span style="color:#f92672">=</span> <span style="color:#ae81ff">1</span>;
</span></span><span style="display:flex;"><span>        mode <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;all&#34;</span>;
</span></span><span style="display:flex;"><span>      };
</span></span><span style="display:flex;"><span>    };
</span></span><span style="display:flex;"><span>  };
</span></span><span style="display:flex;"><span>};
</span></span></code></pre></div><p>This keeps a trailing 24 hours of recording.</p>
<h3 id="intel-hardware-acceleration">Intel Hardware Acceleration</h3>
<p>This took a lot of trial and error, but got CPU usage down significantly (like, 75% to 2%)! Without knowing much about it, I will present without commentary.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-nix" data-lang="nix"><span style="display:flex;"><span>systemd<span style="color:#f92672">.</span>services<span style="color:#f92672">.</span>frigate <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>  environment<span style="color:#f92672">.</span>LIBVA_DRIVER_NAME <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;iHD&#34;</span>;
</span></span><span style="display:flex;"><span>  serviceConfig <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>    SupplementaryGroups <span style="color:#f92672">=</span> [ <span style="color:#e6db74">&#34;render&#34;</span> <span style="color:#e6db74">&#34;video&#34;</span> ];
</span></span><span style="display:flex;"><span>    DeviceAllow <span style="color:#f92672">=</span> [ <span style="color:#e6db74">&#34;/dev/dri/renderD128&#34;</span> ];
</span></span><span style="display:flex;"><span>    AmbientCapabilities <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;CAP_PERFMON&#34;</span>;
</span></span><span style="display:flex;"><span>  };
</span></span><span style="display:flex;"><span>};
</span></span><span style="display:flex;"><span>  
</span></span></code></pre></div><p>And in the <code>hardware-configuration.nix</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-nix" data-lang="nix"><span style="display:flex;"><span>hardware<span style="color:#f92672">.</span>opengl <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>  enable <span style="color:#f92672">=</span> <span style="color:#66d9ef">true</span>;
</span></span><span style="display:flex;"><span>  extraPackages <span style="color:#f92672">=</span> <span style="color:#66d9ef">with</span> pkgs; [
</span></span><span style="display:flex;"><span>    intel-media-driver
</span></span><span style="display:flex;"><span>    intel-vaapi-driver
</span></span><span style="display:flex;"><span>  ];
</span></span><span style="display:flex;"><span>};
</span></span></code></pre></div><h3 id="reverse-proxy-to-frigate-default-port">Reverse Proxy to Frigate Default Port</h3>
<p>Last but not least, we need to add the address in <code>dnsmasq.settings.address</code>, and the reverse proxy config:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-nix" data-lang="nix"><span style="display:flex;"><span>virtualHosts<span style="color:#f92672">.</span><span style="color:#e6db74">&#34;frigate.home&#34;</span> <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>  locations<span style="color:#f92672">.</span><span style="color:#e6db74">&#34;/&#34;</span> <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>    proxyPass <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;http://127.0.0.1:5000&#34;</span>;
</span></span><span style="display:flex;"><span>    proxyWebsockets <span style="color:#f92672">=</span> <span style="color:#66d9ef">true</span>; 
</span></span><span style="display:flex;"><span>  };
</span></span><span style="display:flex;"><span>};
</span></span></code></pre></div><p>The websocket part is important as Frigate uses them for live views.</p>
<p>That&rsquo;s it! Works like a charm.</p>
<h2 id="recap">Recap</h2>
<p>We&rsquo;re in a pretty good spot. We&rsquo;ve got:</p>
<ul>
<li>Access to the entire corpus of <code>nixpkgs</code> with modules for many incredible open-source tools.</li>
<li>Version-controlled configuration.</li>
<li>Network deployments with atomic rollback.</li>
</ul>
<p>That means we can host new services (like Jellyfin, Immich, NextCloud, Home Assistant, etc) with a few steps:</p>
<ol>
<li>Enable it: <code>services.jellyfin.enable = true;</code></li>
<li>Reverse proxy with <code>nginx</code>.</li>
<li>Add a DNS entry with <code>dnsmasq</code>.</li>
<li><code>deploy</code>.</li>
</ol>
<p>You have basically zero risk of bricking the machine with a failed experiment, or interfering at all with your existing services. If something goes awry you simply roll back the node and revert the config changes.</p>
<h2 id="next-steps">Next Steps?</h2>
<p>I reserve the right to work (or not) in any of these directions:</p>
<ul>
<li>
<p>Being an affirmed <a href="/posts/phoenix-in-nix">Elixir</a> enjoyer, I&rsquo;m tempted to make a LiveView dashboard. That would make it really easy to wrap Frigate and augment with whatever custom live logic I want.</p>
</li>
<li>
<p>I&rsquo;ve been wanting an excuse to try out Slack&rsquo;s <a href="https://github.com/slackhq/nebula">Nebula</a>. The idea there would be to make a network overlay connecting my VPS to the local node. Then I could (very carefully) access my services from public internet.</p>
</li>
<li>
<p>Once we&rsquo;ve got that, why not introduce some BEAM clustering so our LiveView backend is distributed? I have as many reasons to do that as I have not to! (It&rsquo;s zero.)</p>
</li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>NixOS Integration Tests To Become Even More Undeniable</title>
      <link>https://paradigmatic.systems/posts/integration-containers/</link>
      <pubDate>Sun, 15 Mar 2026 04:30:00 +0000</pubDate>
      
      <guid>https://paradigmatic.systems/posts/integration-containers/</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve mentioned the &lt;a href=&#34;https://paradigmatic.systems/posts/first-principles-infrastructure&#34;&gt;insane power&lt;/a&gt; of NixOS&amp;rsquo; QEMU-based integration tests.&lt;/p&gt;
&lt;details style=&#34;border: 2px solid #374151; border-radius: 8px; padding: 16px; background-color: #1f2937; margin: 16px 0;&#34;&gt;
&lt;summary style=&#34;cursor: pointer; font-weight: 600; color: #60a5fa;&#34;&gt;If you&#39;re wondering why you (yes, you!) should care at all about this random Linux distro, expand this carat for my summary.&lt;/summary&gt;
&lt;div style=&#34;margin-top: 12px;&#34;&gt;
For those not in the know, a **NixOS configuration** is a data structure that fully determines a Linux system, from the kernel to the users and the systemd processes. It&#39;s a highly reproducible artifact backed by the largest open-source package repo in existence. You can:
&lt;ul&gt;
&lt;li&gt;Put it in version control and use it for your daily driver to make hardware migrations painless&lt;/li&gt;
&lt;li&gt;Deploy to bare metal VPS with low-resource &lt;a href=&#34;https://paradigmatic.systems/posts/setting-up-deploy-rs&#34;&gt;&amp;ldquo;build-then-push&amp;rdquo; tooling&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Build Docker/OCI containers from it to appease corporate goons&lt;/li&gt;
&lt;li&gt;Run it in CI/CD pipelines&lt;/li&gt;
&lt;li&gt;Share it with teammates for identical dev environments&lt;/li&gt;
&lt;li&gt;Parameterize it to define a cluster of nodes&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/details&gt;
&lt;br/&gt;
&lt;p&gt;The integration test framework lets you define virtualized nodes with pass/fail conditions on the behavior of the components therein. Furthermore the nodes can be embedded within an arbitrary network topology to test the overall system-of-systems behavior. With the tests defined, they&amp;rsquo;re wrapped into the very notion of validity for your configuration(s).&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I&rsquo;ve mentioned the <a href="/posts/first-principles-infrastructure">insane power</a> of NixOS&rsquo; QEMU-based integration tests.</p>
<details style="border: 2px solid #374151; border-radius: 8px; padding: 16px; background-color: #1f2937; margin: 16px 0;">
<summary style="cursor: pointer; font-weight: 600; color: #60a5fa;">If you're wondering why you (yes, you!) should care at all about this random Linux distro, expand this carat for my summary.</summary>
<div style="margin-top: 12px;">
For those not in the know, a **NixOS configuration** is a data structure that fully determines a Linux system, from the kernel to the users and the systemd processes. It's a highly reproducible artifact backed by the largest open-source package repo in existence. You can:
<ul>
<li>Put it in version control and use it for your daily driver to make hardware migrations painless</li>
<li>Deploy to bare metal VPS with low-resource <a href="/posts/setting-up-deploy-rs">&ldquo;build-then-push&rdquo; tooling</a></li>
<li>Build Docker/OCI containers from it to appease corporate goons</li>
<li>Run it in CI/CD pipelines</li>
<li>Share it with teammates for identical dev environments</li>
<li>Parameterize it to define a cluster of nodes</li>
</ul>
</div>
</details>
<br/>
<p>The integration test framework lets you define virtualized nodes with pass/fail conditions on the behavior of the components therein. Furthermore the nodes can be embedded within an arbitrary network topology to test the overall system-of-systems behavior. With the tests defined, they&rsquo;re wrapped into the very notion of validity for your configuration(s).</p>
<p>This system is obviously the future of infrastructure. NixOS is doing things that are deep on the long-term roadmap for other system-of-system solutions. You could burn serious man-hours approximating this capability in <code>docker-compose</code> and end up nowhere near the raw reproducible bare-metal power.</p>
<p>However, in my own experiments, the virtualization layer quickly made my $300 dev laptop smoke at the ears for anything beyond 1 node. That&rsquo;s why I&rsquo;m excited that <a href="https://nixcademy.com/posts/faster-cheaper-nixos-integration-tests-with-containers/">Nixcademy has announced</a> a new feature. An unnamed corporate patron has supported development of namespace-based containerization backed by <code>systemd-nspawn</code>.</p>
<p>Here&rsquo;s an abbreviated example illustrating how I could bake this very post into my VPS deployment!</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-nix" data-lang="nix"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  description <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;NixOS configuration with test&#34;</span>;
</span></span><span style="display:flex;"><span>  inputs <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>    nixpkgs<span style="color:#f92672">.</span>url <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;github:NixOS/nixpkgs/nixos-unstable&#34;</span>;
</span></span><span style="display:flex;"><span>  };
</span></span><span style="display:flex;"><span>  outputs <span style="color:#f92672">=</span> { self<span style="color:#f92672">,</span> nixpkgs }: 
</span></span><span style="display:flex;"><span>  {
</span></span><span style="display:flex;"><span>    checks<span style="color:#f92672">.</span>x86_64-linux<span style="color:#f92672">.</span>test <span style="color:#f92672">=</span> <span style="color:#66d9ef">let</span>
</span></span><span style="display:flex;"><span>      pkgs <span style="color:#f92672">=</span> nixpkgs<span style="color:#f92672">.</span>legacyPackages<span style="color:#f92672">.</span>x86_64-linux;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">in</span> pkgs<span style="color:#f92672">.</span>testers<span style="color:#f92672">.</span>runNixOSTest {
</span></span><span style="display:flex;"><span>      
</span></span><span style="display:flex;"><span>      name <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;configuration-test&#34;</span>;
</span></span><span style="display:flex;"><span>      
</span></span><span style="display:flex;"><span>      nodes<span style="color:#f92672">.</span>server <span style="color:#f92672">=</span> { config<span style="color:#f92672">,</span> pkgs<span style="color:#f92672">,</span> paradigmatic<span style="color:#f92672">,</span> <span style="color:#f92672">...</span> }: {
</span></span><span style="display:flex;"><span>        services<span style="color:#f92672">.</span>nginx <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>          enable <span style="color:#f92672">=</span> <span style="color:#66d9ef">true</span>;
</span></span><span style="display:flex;"><span>          virtualHosts<span style="color:#f92672">.</span><span style="color:#e6db74">&#34;_&#34;</span> <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>            root <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;</span><span style="color:#e6db74">${</span>paradigmatic<span style="color:#f92672">.</span>packages<span style="color:#f92672">.</span><span style="color:#e6db74">${</span>system<span style="color:#e6db74">}</span><span style="color:#f92672">.</span>website<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>;
</span></span><span style="display:flex;"><span>          }
</span></span><span style="display:flex;"><span>        };
</span></span><span style="display:flex;"><span>      };
</span></span><span style="display:flex;"><span>      
</span></span><span style="display:flex;"><span>      nodes<span style="color:#f92672">.</span>client <span style="color:#f92672">=</span> { config<span style="color:#f92672">,</span> pkgs<span style="color:#f92672">,</span> <span style="color:#f92672">...</span> } : {};
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>      testScript <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;&#39;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        server.start()
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        client.start()
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        server.wait_for_unit(&#34;nginx.service&#34;)
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        client.wait_for_unit(&#34;multi-user.target&#34;)
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        client.succeed(&#34;curl http://server/posts/integration-containers 
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        | grep &#39;Hello from this very text block!&#39;&#34;)
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      &#39;&#39;</span>;
</span></span><span style="display:flex;"><span>    };
</span></span><span style="display:flex;"><span>  };
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Nice!</p>
<p><em>Want to chat about NixOS adoption? <a href="mailto:rorih@live.com">Email me</a></em></p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Nix: the Language, the Tool, the Package Repo, the OS</title>
      <link>https://paradigmatic.systems/posts/nix-nixpkgs-nixos/</link>
      <pubDate>Wed, 13 Aug 2025 04:30:00 +0000</pubDate>
      
      <guid>https://paradigmatic.systems/posts/nix-nixpkgs-nixos/</guid>
      <description>&lt;p&gt;There&amp;rsquo;s a lot going on in the Nix ecosystem. If you haven&amp;rsquo;t slogged through &lt;a href=&#34;https://edolstra.github.io/pubs/phd-thesis.pdf&#34;&gt;the thesis&lt;/a&gt; then it might seem downright opaque. This is my attempt at an easy explainer for newcomers.&lt;/p&gt;
&lt;h2 id=&#34;the-tool-and-the-language&#34;&gt;The Tool and the Language&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s bootstrap ourselves with some circular facts.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Nix is a &lt;em&gt;tool&lt;/em&gt; for building (or &amp;ldquo;packaging&amp;rdquo;) &lt;em&gt;software components&lt;/em&gt; using instructions expressed in the Nix &lt;em&gt;language&lt;/em&gt;.
The Nix language is even able to &lt;a href=&#34;https://github.com/NixOS/nix/blob/master/flake.nix&#34;&gt;package&lt;/a&gt; the Nix tool (which is mostly written in C++), thus closing the epistemological loop.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>There&rsquo;s a lot going on in the Nix ecosystem. If you haven&rsquo;t slogged through <a href="https://edolstra.github.io/pubs/phd-thesis.pdf">the thesis</a> then it might seem downright opaque. This is my attempt at an easy explainer for newcomers.</p>
<h2 id="the-tool-and-the-language">The Tool and the Language</h2>
<p>Let&rsquo;s bootstrap ourselves with some circular facts.</p>
<blockquote>
<p>Nix is a <em>tool</em> for building (or &ldquo;packaging&rdquo;) <em>software components</em> using instructions expressed in the Nix <em>language</em>.
The Nix language is even able to <a href="https://github.com/NixOS/nix/blob/master/flake.nix">package</a> the Nix tool (which is mostly written in C++), thus closing the epistemological loop.</p></blockquote>
<p>The language features first-class treatment of filesystem paths and URLs (to source code and tarballs, for example).
Nix <em>expressions</em> are evaluated in a hermetic environment with no path variables and no files. All outside references get copied into content-addressed folders in the <em>store</em>, and marked as build-time <em>dependencies</em> in any <em>derivation</em> that gets defined. Retained <em>runtime</em> dependencies are calculated by scanning the actual binary for store paths.</p>
<h2 id="the-build">The Build</h2>
<p>The component is the result of <em>realizing</em> the derivation. That means grabbing all the store path contents and combining them in the prescribed way. Imagine you have 3 components that are build-time dependencies:</p>
<ol>
<li>A compiler</li>
<li>Source code</li>
<li>A bash script that uses the compiler to build the source</li>
</ol>
<p>The derivation describes where to find these, and the (obvious) way that they fit together. The result is content-addressed with a hash based on all its dependencies, and placed in the store.</p>
<h2 id="nixpkgs">nixpkgs</h2>
<p>It turns out this scheme is highly flexible and repeatable.
<a href="https://github.com/NixOS/nixpkgs">nixpkgs</a> is a repo full of Nix expressions for over 120,000 packages. You can pull any one of them into an ephemeral shell to try it out, or install it system-wide if you&rsquo;re using:</p>
<h2 id="nixos">NixOS</h2>
<p>Finally, notice that &ldquo;<em>software components</em>&rdquo; could have a very broad definition. So broad, in fact, to include an entire OS with all of the installed programs and services. That&rsquo;s what NixOS is: A Linux distro defined entirely in Nix and built around the <code>/nix/store</code> path model instead of the more usual <a href="https://en.wikipedia.org/wiki/Filesystem_Hierarchy_Standard">FHS</a>.  I&rsquo;ve written <a href="/posts/first-principles-infrastructure">a bit</a> about how the Nix <em>module</em> system helps encapsulate complex system recipes into simple one-liners.</p>
<p>All my homies use NixOS. To paraphrase from <a href="https://joshblais.com/blog/nixos-is-the-endgame-of-distrohopping/">NixOS is the endgame of distrohopping </a>:</p>
<h3 id="no-more-configuration-drift---solved-">No more configuration drift - solved ✅</h3>
<h3 id="works-on-my-machine---solved-">“Works on my machine!” - solved ✅</h3>
<h3 id="dependency-hell---solved-">Dependency Hell - solved ✅</h3>
<h3 id="virtual-and-development-environments---solved-">Virtual and development environments - solved ✅</h3>
<h3 id="package-management---solved-">package management - solved ✅</h3>
<h2 id="ready-to-go-deeper">Ready to go deeper?</h2>
<p>To get started I highly recommend working through the <a href="https://nix.dev/tutorials/">tutorials at nix.dev</a>!</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>First Principles Infrastructure: Why NixOS Beats Containers and Clouds in 2025</title>
      <link>https://paradigmatic.systems/posts/first-principles-infrastructure/</link>
      <pubDate>Mon, 11 Aug 2025 14:30:00 +0000</pubDate>
      
      <guid>https://paradigmatic.systems/posts/first-principles-infrastructure/</guid>
      <description>&lt;p&gt;In the marketplace of developer skills, there&amp;rsquo;s a certain category that I&amp;rsquo;m long-term bearish on.  They don&amp;rsquo;t warrant investing free time trying to level up. Devs should limit their exposure to the minimum necessary to do their job. I&amp;rsquo;m talking about tools like Docker, Kubernetes, and the intricate managed hosting platforms of Azure and AWS. These are tools that help manage&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;social problems caused by scaling organizations to thousands of engineers&lt;/li&gt;
&lt;li&gt;existing complexity&lt;/li&gt;
&lt;li&gt;laziness or desire to throw money at a problem rather than solve it&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While they do solve real technical problems, I&amp;rsquo;ll argue here that there&amp;rsquo;s superior tooling for most use cases outside of big tech and legacy systems.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In the marketplace of developer skills, there&rsquo;s a certain category that I&rsquo;m long-term bearish on.  They don&rsquo;t warrant investing free time trying to level up. Devs should limit their exposure to the minimum necessary to do their job. I&rsquo;m talking about tools like Docker, Kubernetes, and the intricate managed hosting platforms of Azure and AWS. These are tools that help manage</p>
<ul>
<li>social problems caused by scaling organizations to thousands of engineers</li>
<li>existing complexity</li>
<li>laziness or desire to throw money at a problem rather than solve it</li>
</ul>
<p>While they do solve real technical problems, I&rsquo;ll argue here that there&rsquo;s superior tooling for most use cases outside of big tech and legacy systems.</p>
<h1 id="containerize-or-modularize">Containerize or Modularize?</h1>
<blockquote>
<p>We need deployment consistency and environment isolation, and containers solve those!</p></blockquote>
<p>The 2016 essay <a href="https://catern.com/docker.html">Docker Considered Harmful</a> explains how Linux primitives solve the same problems in a more robust way. One valid criticism of this is that Docker can get a junior developer productive more quickly without needing to learn about arcane system settings.</p>
<p>Fine, so we need an abstraction to work with.</p>
<p>Does the abstraction need to expose us to a bloated ecosystem prone to drift? What if it could evaluate to the precise, minimal, and correct system without introducing overhead? What if it&rsquo;s a <a href="https://xeiaso.net/talks/2024/nix-docker-build/">better Docker image builder than Docker&rsquo;s image builder</a>?</p>
<p>Enter Nix with its <a href="https://nix.dev/tutorials/module-system/index.html">module system</a>. Nix approaches the &ldquo;dependency hell&rdquo; problem from first principles. It requires a radically different mental model, which is itself a social organization problem. This model, by the way, is laid out best in Eelco Dolstra&rsquo;s <a href="https://edolstra.github.io/pubs/phd-thesis.pdf">2006 PHD Thesis (PDF)</a>.</p>
<p>When you think about</p>
<ul>
<li>polyglot system with multiple language ecosystems working together</li>
<li>co-existing packages with conflicting dependencies (i.e dfferent versions of python)</li>
</ul>
<p>do you get a headache thinking about all the setup? Well then you should probably swallow some Nix pills. No other tool can encapsulate cross-ecosystem practices and Linux expertise so cleanly. A simple configuration change like</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-nix" data-lang="nix"><span style="display:flex;"><span>services<span style="color:#f92672">.</span>postgres<span style="color:#f92672">.</span>enable <span style="color:#f92672">=</span> <span style="color:#66d9ef">true</span>;
</span></span></code></pre></div><p>is the tip of a carefully crafted reproducible iceberg. This line of code hides a host of system-level changes including</p>
<ul>
<li>User and groups setup with correct isolation and permissions</li>
<li>Directory structure with ownership and permissions</li>
<li>Initialization, default template databases</li>
<li>SystemD service with correct dependencies, start order, lifecycle and recovery
and much more!</li>
</ul>
<p>It&rsquo;s a massive setup process that would normally be done manually, maybe captured in brittle shell scripts, or containerized. But here it&rsquo;s a one-liner, and it&rsquo;s exposed for use as a declarative model that allows completely deterministic and atomic system upgrades!</p>
<p>So while containers provide language-agnostic deployment, Nix achieves the same universality at the package level while maintaining better reproducibility and lower overhead. You can even spit out a Docker image as an afterthought if that&rsquo;s what you really need.</p>
<h1 id="cluster-or-cluster">Cluster or Cluster*@!#?</h1>
<blockquote>
<p>We need service discovery, load balancing, and observability! Kubernetes provides these with a vast ecosystem of battle-tested tools, and its nice declarative operator pattern for nearly every infrastructure need.</p></blockquote>
<p>The infrastructure problem is solved by creating different problems: cognitive overhead, ecosystem lock-in, long development cycles, and significant cost markup. There are compelling reasons to look for a better alternative.</p>
<p>Well, the largest package repo in the world (Nixpkgs) has nice <a href="https://search.nixos.org/options?channel=25.05&amp;from=0&amp;size=50&amp;sort=relevance&amp;type=packages">modules</a> defined for over 20,000 world-class open-source tools. With a 3-line change to your system configuration, you&rsquo;re now building a robust set of tools:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-nix" data-lang="nix"><span style="display:flex;"><span>services <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>  nginx<span style="color:#f92672">.</span>enable <span style="color:#f92672">=</span> <span style="color:#66d9ef">true</span>;      <span style="color:#75715e">#Load balancing</span>
</span></span><span style="display:flex;"><span>  consul<span style="color:#f92672">.</span>enable <span style="color:#f92672">=</span> <span style="color:#66d9ef">true</span>;     <span style="color:#75715e">#Service discovery</span>
</span></span><span style="display:flex;"><span>  prometheus<span style="color:#f92672">.</span>enable <span style="color:#f92672">=</span> <span style="color:#66d9ef">true</span>; <span style="color:#75715e">#Observability</span>
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">#You&#39;ll want to configure these, of course.</span>
</span></span></code></pre></div><p>But that&rsquo;s not it. The <a href="https://nix.dev/manual/nix/2.28/command-ref/new-cli/nix3-flake.html#flake-format">Nix flake output schema</a> specifies <code>nixosConfigurations</code> as a key. Note the plural. So you might do something like:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-nix" data-lang="nix"><span style="display:flex;"><span>nixosConfigurations <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>  web <span style="color:#f92672">=</span> mkNode <span style="color:#e6db74">&#34;web&#34;</span> {
</span></span><span style="display:flex;"><span>    services<span style="color:#f92672">.</span>nginx<span style="color:#f92672">.</span>enable <span style="color:#f92672">=</span> <span style="color:#66d9ef">true</span>;
</span></span><span style="display:flex;"><span>    services<span style="color:#f92672">.</span>nginx<span style="color:#f92672">.</span>virtualHosts<span style="color:#f92672">.</span><span style="color:#e6db74">&#34;web&#34;</span> <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>      locations<span style="color:#f92672">.</span><span style="color:#e6db74">&#34;/api/&#34;</span> <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>      proxyPass <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;http://</span><span style="color:#e6db74">${</span>cluster<span style="color:#f92672">.</span>db<span style="color:#f92672">.</span>ip<span style="color:#e6db74">}</span><span style="color:#e6db74">:5000/&#34;</span>;
</span></span><span style="display:flex;"><span>    };
</span></span><span style="display:flex;"><span>    app<span style="color:#f92672">.</span>db_address <span style="color:#f92672">=</span> cluster<span style="color:#f92672">.</span>db<span style="color:#f92672">.</span>ip
</span></span><span style="display:flex;"><span>  };
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  db <span style="color:#f92672">=</span> mkNode <span style="color:#e6db74">&#34;db&#34;</span> {
</span></span><span style="display:flex;"><span>    services<span style="color:#f92672">.</span>postgresql<span style="color:#f92672">.</span>enable <span style="color:#f92672">=</span> <span style="color:#66d9ef">true</span>;
</span></span><span style="display:flex;"><span>    services<span style="color:#f92672">.</span>postgresql<span style="color:#f92672">.</span>enableTCPIP <span style="color:#f92672">=</span> <span style="color:#66d9ef">true</span>;
</span></span><span style="display:flex;"><span>    services<span style="color:#f92672">.</span>postgresql<span style="color:#f92672">.</span>authentication <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;&#39;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      host all all </span><span style="color:#e6db74">${</span>cluster<span style="color:#f92672">.</span>web<span style="color:#f92672">.</span>ip<span style="color:#e6db74">}</span><span style="color:#e6db74">/32 trust
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      &#39;&#39;</span>;
</span></span><span style="display:flex;"><span>    api<span style="color:#f92672">.</span>enable <span style="color:#f92672">=</span> <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>  };
</span></span><span style="display:flex;"><span>};
</span></span></code></pre></div><p>where the 2 nodes configurations make them aware of each other.</p>
<p>The same flake can even include</p>
<ul>
<li>integration tests using the insanely powerful <a href="https://nixcademy.com/posts/nixos-integration-tests/">integration test driver</a></li>
<li>deployment metadata with <a href="/posts/setting-up-deploy-rs">deploy-rs</a> to deploy the whole cluster with 1 command</li>
</ul>
<p>So the question then becomes, do you have a nest of messy legacy code and need to throw money at scaling it up? Or do you have the luxury of using the tools directly and scaling piecewise as needs arise?</p>
<h1 id="summary">Summary</h1>
<p>It&rsquo;s never going to be easy to retrofit legacy enterprise systems. But for designing new systems, the NixOS advantage is massive. Your entire system configuration, from kernel to application dependencies to infrastructure services, is a single, reproducible, version-controlled artifact. It allows for</p>
<ul>
<li>One deployment mechanism</li>
<li>Deterministic deployments</li>
<li>Exact parity between development and production</li>
</ul>
<p>and most importantly, a level of autonomy and sovereignty that you yield when your system is built on unstable abstractions.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Setting up deploy-rs for NixOS</title>
      <link>https://paradigmatic.systems/posts/setting-up-deploy-rs/</link>
      <pubDate>Thu, 30 Jan 2025 14:30:00 +0000</pubDate>
      
      <guid>https://paradigmatic.systems/posts/setting-up-deploy-rs/</guid>
      <description>&lt;p&gt;Our starting point here is &lt;a href=&#34;https://paradigmatic.systems/posts/provisioning-nixos-node-digital-ocean&#34;&gt;a node that is freshly infected with NixOS&lt;/a&gt;. For changing the system, we have 2 options.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;SSH in and edit the configuration files (or copy them over) and then run &lt;code&gt;nixos-rebuild switch&lt;/code&gt; which triggers the node to pull and build all the necessary items.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Check and build the configuration locally, and copy the entire closure across.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;At a glance there might not be an advantage to one or the other. But consider if you&amp;rsquo;re deploying custom content that isn&amp;rsquo;t part of nixOS builtin configuration. To use method (1) you would need to clone all your repos on the server to be able to rebuild. Then every update requires you to ssh in and pull.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Our starting point here is <a href="/posts/provisioning-nixos-node-digital-ocean">a node that is freshly infected with NixOS</a>. For changing the system, we have 2 options.</p>
<ol>
<li>
<p>SSH in and edit the configuration files (or copy them over) and then run <code>nixos-rebuild switch</code> which triggers the node to pull and build all the necessary items.</p>
</li>
<li>
<p>Check and build the configuration locally, and copy the entire closure across.</p>
</li>
</ol>
<p>At a glance there might not be an advantage to one or the other. But consider if you&rsquo;re deploying custom content that isn&rsquo;t part of nixOS builtin configuration. To use method (1) you would need to clone all your repos on the server to be able to rebuild. Then every update requires you to ssh in and pull.</p>
<p>That&rsquo;s why (2) is the much better option. We will use <a href="https://github.com/serokell/deploy-rs">deploy-rs</a> to implement it on DigitalOcean.</p>
<h3 id="1-enable-flakes-on-the-server">1) Enable flakes on the server</h3>
<p>If you followed the previous instructions you&rsquo;ve got a local folder full of configuration pulled from your node. Go ahead and turn that into a git repo before getting too far into this.</p>
<p>Adjust your <code>configuration.nix</code></p>
<pre tabindex="0"><code>{ pkgs, ... }: {
  imports = [
    ./hardware-configuration.nix
    ./networking.nix
  ];

  environment.systemPackages = with pkgs; [ vim ];

  boot.tmp.cleanOnBoot = true;
  zramSwap.enable = true;
  networking.hostName = &#34;your-hostname&#34;;
  networking.domain = &#34;&#34;;
  services.openssh.enable = true;
  users.users.root.openssh.authorizedKeys.keys = [&#39;&#39;your key here&#39;&#39; ];
  system.stateVersion = &#34;25.05&#34;;

  nix.settings.experimental-features = [ &#34;nix-command&#34; &#34;flakes&#34; ];
}
</code></pre><p>The main changes are to add the content of <code>host.nix</code> (personal preference but you can go ahead and delete that), and enable the flakes feature. Push the whole configuration folder back up and do a rebuild:</p>
<pre tabindex="0"><code>rsync -av --delete --exclude=&#39;.git&#39; ./ [node-name]:/etc/nixos/
ssh [node-name] &#34;sudo nixos-rebuild switch --flake /etc/nixos#default&#34;
</code></pre><h3 id="2-wrap-the-original-configuration-in-a-flake">2) Wrap the original configuration in a flake</h3>
<p>Now, working locally again, create a new <code>flake.nix</code>:</p>
<pre tabindex="0"><code>{
  description = &#34;NixOS configuration&#34;;

  inputs = {
    nixpkgs.url = &#34;github:nixos/nixpkgs/nixos-25.05&#34;;
    deploy-rs = {
      url = &#34;github:serokell/deploy-rs&#34;;
      inputs.nixpkgs.follows = &#34;nixpkgs&#34;;
    };
  };

  outputs = { self, nixpkgs, deploy-rs }: let
    system = &#34;x86_64-linux&#34;;
  in {
    nixosConfigurations.default = nixpkgs.lib.nixosSystem {
      inherit system;
      modules = [
        ./configuration.nix
      ];
    };

    deploy.nodes.default = {
      hostname = &#34;[node-name]&#34;;
      profiles.system = {
        sshUser = &#34;root&#34;;
        path = deploy-rs.lib.${system}.activate.nixos self.nixosConfigurations.default;
        user = &#34;root&#34;;
      };
    };
    checks = builtins.mapAttrs (system: deployLib: deployLib.deployChecks self.deploy) deploy-rs.lib;
  };
}
</code></pre><p>This flake:</p>
<ol>
<li>wraps the original system, importing it to <code>nixosConfigurations.default</code>.</li>
<li>describes the deployment target in <code>deploy.nodes.default</code>.</li>
</ol>
<h3 id="3-deploy-it">3) Deploy it</h3>
<p>You can run <code>deploy-rs</code> straight from github:</p>
<p><code>nix run github:serokell/deploy-rs .#default</code></p>
<p>or hit a <code>nix profile install github:serokell/deploy-rs</code> so that you can just use the <code>deploy</code> command.</p>
<p>This should succeed and leave your server right where you started (since the actual configuration is unchanged), but now you have a nice flake to work with locally.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Provisioning a NixOS node in DigitalOcean</title>
      <link>https://paradigmatic.systems/posts/provisioning-nixos-node-digital-ocean/</link>
      <pubDate>Tue, 28 Jan 2025 14:30:00 +0000</pubDate>
      
      <guid>https://paradigmatic.systems/posts/provisioning-nixos-node-digital-ocean/</guid>
      <description>&lt;p&gt;Here&amp;rsquo;s an easy way to get a NixOS node spun up using &lt;a href=&#34;https://github.com/elitak/nixos-infect&#34;&gt;nixos-infect&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;1-set-up-a-new-droplet&#34;&gt;1) Set up a new droplet&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Choose Ubuntu 22.04&lt;/li&gt;
&lt;li&gt;Add SSH keys for something you have a local IdentityFile for.&lt;/li&gt;
&lt;li&gt;Under &amp;ldquo;Advanced Options&amp;rdquo; -&amp;gt; &amp;ldquo;Add initialization scripts&amp;rdquo; paste&lt;/li&gt;
&lt;/ul&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;#cloud-config
write_files:
- path: /etc/nixos/host.nix
  permissions: &amp;#39;0644&amp;#39;
  content: |
    {pkgs, ...}:
    {
      environment.systemPackages = with pkgs; [ vim ];
    }
runcmd:
  - curl https://raw.githubusercontent.com/elitak/nixos-infect/master/nixos-infect | PROVIDER=digitalocean NIXOS_IMPORT=./host.nix NIX_CHANNEL=nixos-25.05 bash 2&amp;gt;&amp;amp;1 | tee /tmp/infect.log
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;2-ssh-in-and-copy-config-down&#34;&gt;2) SSH in and copy config down&lt;/h3&gt;
&lt;p&gt;Add the node&amp;rsquo;s SSH config to ~/.ssh/config&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Here&rsquo;s an easy way to get a NixOS node spun up using <a href="https://github.com/elitak/nixos-infect">nixos-infect</a>.</p>
<h3 id="1-set-up-a-new-droplet">1) Set up a new droplet</h3>
<ul>
<li>Choose Ubuntu 22.04</li>
<li>Add SSH keys for something you have a local IdentityFile for.</li>
<li>Under &ldquo;Advanced Options&rdquo; -&gt; &ldquo;Add initialization scripts&rdquo; paste</li>
</ul>
<pre tabindex="0"><code>#cloud-config
write_files:
- path: /etc/nixos/host.nix
  permissions: &#39;0644&#39;
  content: |
    {pkgs, ...}:
    {
      environment.systemPackages = with pkgs; [ vim ];
    }
runcmd:
  - curl https://raw.githubusercontent.com/elitak/nixos-infect/master/nixos-infect | PROVIDER=digitalocean NIXOS_IMPORT=./host.nix NIX_CHANNEL=nixos-25.05 bash 2&gt;&amp;1 | tee /tmp/infect.log
</code></pre><h3 id="2-ssh-in-and-copy-config-down">2) SSH in and copy config down</h3>
<p>Add the node&rsquo;s SSH config to ~/.ssh/config</p>
<pre tabindex="0"><code>Host [node-name]
        HostName [IP-address]
        User root
        IdentityFile ~/.ssh/[identity]
</code></pre><p>Now you should be able to <code>ssh [node-name]</code> and <code>cat /etc/nixos/configuration.nix</code> to see your new system.</p>
<p>Start a new directory for containing the node&rsquo;s configuration locally. Maybe call it <code>[node-name]-deployment</code>.</p>
<p>On the server, run
<code>scp [node-name]:/etc/nixos/* .</code>.</p>
<p>Now you&rsquo;ve got the remote configuration in hand. If you wanted to alter the node, the naive way to do it is edit the configuration, push it back over, and run <code>sudo nixos-rebuild switch</code>. But there&rsquo;s a better way to approach this that involves building the system locally. We&rsquo;ll continue in <a href="/posts/setting-up-deploy-rs">setting up deploy-rs</a>.</p>
]]></content:encoded>
    </item>
    
  </channel>
</rss>
