<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Home on Clan</title>
    <link>https://clan.lol/</link>
    <description>Recent content in Home on Clan</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language>
    <lastBuildDate>Sun, 01 Jan 2023 08:00:00 -0700</lastBuildDate><atom:link href="https://clan.lol/feed.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Data Mesher</title>
      <link>https://clan.lol/blog/data-mesher/</link>
      <pubDate>Mon, 30 Mar 2026 12:00:00 +0000</pubDate>
      
      <guid>https://clan.lol/blog/data-mesher/</guid>
      <description>&lt;p&gt;&lt;a href=&#34;https://nixos.org&#34;&gt;NixOS&lt;/a&gt; is great when it comes to &lt;em&gt;declarative state&lt;/em&gt;.
Instead of running commands to install packages, edit config files and so on, you declare what you want and &lt;code&gt;nixos-rebuild switch&lt;/code&gt; takes care of the rest.&lt;/p&gt;
&lt;p&gt;Clan takes this one step further, making it easy to define services which span multiple machines and ensuring the end result by running &lt;code&gt;clan machines update&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;So, what if we want to update a user&amp;rsquo;s &lt;code&gt;authorized_keys&lt;/code&gt; across a 10, 20 or 30-machine Clan?&lt;/p&gt;
&lt;p&gt;As you might have guessed, we first update our Clan config and then run &lt;code&gt;clan machines update&lt;/code&gt;.
This will build and deploy the new NixOS configuration across all 30 machines.&lt;/p&gt;
&lt;p&gt;Feels a little heavy though, doesn’t it?
Maybe we just wanted to give that person access for a short while?
When we’re finished, we have to update our Clan config and &lt;strong&gt;redeploy to every machine in our Clan&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;And what if a few of those machines are laptops, used in the field and possibly out of contact or turned off when we are ready to roll back that &lt;code&gt;authorized_keys&lt;/code&gt; change?
Unless you remember to deploy those machines when they come back online, they will remain out of sync and continue to allow access until the next deployment.&lt;/p&gt;
&lt;p&gt;That’s why we are building &lt;a href=&#34;https://git.clan.lol/clan/data-mesher&#34;&gt;Data Mesher&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;how-does-it-work&#34;&gt;How does it work?&lt;/h2&gt;
&lt;p&gt;Sign a file, give it to Data Mesher, and it will (eventually) replicate it to every other Data Mesher instance.
If a node is offline when the update is received, that isn’t a problem. The next time it connects to the cluster, it will catch up.&lt;/p&gt;
&lt;p&gt;There’s no &lt;a href=&#34;https://raft.github.io/&#34;&gt;RAFT&lt;/a&gt; here.
We don’t require a quorum when accepting writes.&lt;/p&gt;
&lt;p&gt;Networking is handled by &lt;a href=&#34;http://libp2p.io/&#34;&gt;libp2p&lt;/a&gt;, and we use a basic anti-entropy mechanism inspired by &lt;a href=&#34;https://github.com/hashicorp/memberlist&#34;&gt;memberlist&lt;/a&gt; to distribute an index of file signatures.
It’s definitely more verbose than it needs to be just now, but we are &lt;em&gt;tentatively&lt;/em&gt; adding complexity as we go along.&lt;/p&gt;
&lt;p&gt;And you can’t just put any old file into Data Mesher.
We require some configuration ahead of time, detailing each file name and a list of valid signers.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-toml&#34; data-lang=&#34;toml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;files&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;config.json&amp;#34;&lt;/span&gt; = [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# Base64 encoded ED25519 public keys&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;P6AE0lukf9/qmVglYrGPNYo5ZnpFrnqLeAzlCZF0lTk=&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;data.bin&amp;#34;&lt;/span&gt; = [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# Base64 encoded ED25519 public keys&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;ZasdhiAVJTa5b2qG8ynWvdHqALUxC6Eg8pdn6RVXuQE=&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;1ru2QQ1eWV7yDlyfTTDEml3xTiacASYn0KprzknN8Pc=&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This allows us to offload responsibility for managing the signature timestamp.
If the signature is valid and the timestamp is later than the node&amp;rsquo;s current timestamp, it is &lt;em&gt;accepted as the latest version and distributed accordingly&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;No more blessed nodes to ensure consistent timekeeping, as we had in V1 (yes, we’re already on V2). It&amp;rsquo;s up to the signers to ensure timestamps make sense.&lt;/p&gt;
&lt;p&gt;One last thing: &lt;strong&gt;no files over 10MiB&lt;/strong&gt;.
We aren’t building &lt;a href=&#34;https://syncthing.net/&#34;&gt;Syncthing&lt;/a&gt; or &lt;a href=&#34;https://ipfs.tech/&#34;&gt;IPFS&lt;/a&gt;.
Data Mesher is intended for targeted, small amounts of runtime state that need to be deployed resiliently within a reasonable time frame.&lt;/p&gt;
&lt;h2 id=&#34;what-can-it-be-used-for&#34;&gt;What can it be used for?&lt;/h2&gt;
&lt;p&gt;We are still fleshing that out, but so far we have &lt;a href=&#34;https://git.clan.lol/clan/clan-core/src/branch/main/clanServices/dm-dns&#34;&gt;dm-dns&lt;/a&gt;, a distributed DNS system which leverages &lt;a href=&#34;https://clan.lol/docs/25.11/guides/services/exports&#34;&gt;Service Exports&lt;/a&gt; to collect all the custom endpoints that a Clan wants to expose internally.
A zone file is then generated and injected into Data Mesher.
From there, systemd is configured to watch for changes to that zone file as they land in the local file system and reloads an instance of &lt;a href=&#34;https://www.nlnetlabs.nl/projects/unbound/about/&#34;&gt;unbound&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Other experiments include &lt;a href=&#34;https://git.clan.lol/clan/clan-community/src/branch/main/services/dm-pull-deploy&#34;&gt;dm-pull-deploy&lt;/a&gt;, an asynchronous deployment system which distributes flake references via Data Mesher.
Machines automatically run &lt;code&gt;nixos-rebuild&lt;/code&gt; when a new flake reference arrives.&lt;/p&gt;
&lt;p&gt;Most recently, &lt;a href=&#34;https://github.com/pinpox&#34;&gt;Pinpox&lt;/a&gt; hooked up an instance of &lt;a href=&#34;https://github.com/pinpox/opencrow&#34;&gt;OpenCrow&lt;/a&gt; (his own version of &lt;a href=&#34;https://github.com/openclaw&#34;&gt;OpenClaw&lt;/a&gt;) to Data Mesher, which let him change the wallpaper on his machines by chatting with the agent over &lt;a href=&#34;https://matrix.org/&#34;&gt;Matrix&lt;/a&gt;.
This experiment was tightly scoped, for &lt;em&gt;good reason&lt;/em&gt;.&lt;/p&gt;
&lt;video width=&#34;100%&#34; muted controls&gt;
  &lt;source
    src=&#34;https://static.clan.lol/videos/opencrow.webm&#34;
    type=&#34;video/webm&#34;
  /&gt;
  &lt;source
    src=&#34;https://static.clan.lol/videos/opencrow.mp4&#34;
    type=&#34;video/mp4&#34;
  /&gt;
  &lt;source
    src=&#34;https://static.clan.lol/videos/opencrow.ove&#34;
    type=&#34;video/ove&#34;
  /&gt;
&lt;/video&gt;

&lt;p&gt;But technically, there’s not much difference between changing a wallpaper and granting the agent access to the async deployment system, which would let it manage much more than just the wallpaper&amp;hellip;&lt;/p&gt;
&lt;h2 id=&#34;whats-next&#34;&gt;What’s Next?&lt;/h2&gt;
&lt;p&gt;Data Mesher is still experimental and will continue to evolve as we flesh out the various use cases.&lt;/p&gt;
&lt;p&gt;In the short term, we are working on &lt;a href=&#34;https://git.clan.lol/clan/data-mesher/pulls/310&#34;&gt;signed namespaces&lt;/a&gt; so we don’t have to specify every file ahead of time.
This will allow machines, for example, to publish self-signed information about themselves, such as their system closure, disk usage, and other basic metrics.&lt;/p&gt;
&lt;p&gt;Long-term, we need to see how much mileage we can get out of this file-based API.
Perhaps not everything will work well within this model. Or maybe the Linux kernel is on to something 🤷.&lt;/p&gt;
&lt;p&gt;We also encourage you, the user, to try it out and see what you can come up with, and be sure to drop anything interesting into the &lt;a href=&#34;https://git.clan.lol/clan/clan-community&#34;&gt;clan-community&lt;/a&gt; repository 🙏.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Debugging Offline Nix Builds</title>
      <link>https://clan.lol/blog/debugging-offline-nix-builds/</link>
      <pubDate>Thu, 29 Jan 2026 12:00:00 +0000</pubDate>
      
      <guid>https://clan.lol/blog/debugging-offline-nix-builds/</guid>
      <description>&lt;p&gt;I was trying to get our new container-based NixOS tests working.
Should be simple, right? Run &lt;code&gt;nixos-rebuild&lt;/code&gt; inside a container, verify the configuration applied correctly, done.&lt;/p&gt;
&lt;p&gt;Instead, I watched Nix attempt to build what felt like half of nixpkgs from source…&lt;/p&gt;
&lt;h2 id=&#34;the-setup&#34;&gt;The Setup&lt;/h2&gt;
&lt;p&gt;We&amp;rsquo;ve been working on running NixOS tests inside systemd-nspawn containers rather than full QEMU VMs. It&amp;rsquo;s faster, uses fewer resources, and works in the Nix build sandbox. The test modifies a NixOS configuration and runs &lt;code&gt;nixos-rebuild switch&lt;/code&gt; to apply it.&lt;/p&gt;
&lt;p&gt;The problem? The container has no network access. It runs inside Nix&amp;rsquo;s build sandbox, which isolates network by design. We pre-populate the Nix store using &lt;code&gt;closureInfo&lt;/code&gt;, but if we miss even a single dependency, Nix can&amp;rsquo;t fetch it as a substitute. It has to build from source. And since dependencies have dependencies, missing one small package can cascade into rebuilding thousands.&lt;/p&gt;
&lt;p&gt;This isn&amp;rsquo;t the first time I&amp;rsquo;ve hit this. When working on &lt;a href=&#34;https://github.com/nix-community/nixos-anywhere&#34;&gt;nixos-anywhere&lt;/a&gt; and &lt;a href=&#34;https://github.com/nix-community/disko&#34;&gt;disko&lt;/a&gt;, I ran into similar issues with hermetic builds. The pattern is always the same: something is missing from the closure, but good luck figuring out what.&lt;/p&gt;
&lt;h2 id=&#34;the-usual-debugging-experience&#34;&gt;The Usual Debugging Experience&lt;/h2&gt;
&lt;p&gt;Normally when this happens, you stare at a wall of build output. Nix is compiling GCC. Why is it compiling GCC? You didn&amp;rsquo;t change anything related to GCC. You scroll back through thousands of lines trying to find the root cause. Maybe you give up and add more packages to your closure, hoping one of them fixes it.&lt;/p&gt;
&lt;p&gt;This time I wanted a better approach.&lt;/p&gt;
&lt;h2 id=&#34;the-key-insight-nix-build---dry-run&#34;&gt;The Key Insight: &lt;code&gt;nix build --dry-run&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;--dry-run&lt;/code&gt; flag is the hero here. It shows what Nix &lt;em&gt;would&lt;/em&gt; build or fetch without actually doing it:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-console&#34; data-lang=&#34;console&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ nix build --dry-run .#yourPackage
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The output has two sections:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Derivations to be built&lt;/strong&gt;: packages that must be built from source&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Paths to be fetched&lt;/strong&gt;: packages that would be downloaded from a binary cache&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In a properly configured hermetic environment, that second list should be empty. Any paths there are exactly what&amp;rsquo;s missing.&lt;/p&gt;
&lt;p&gt;But here&amp;rsquo;s the catch: &lt;code&gt;--dry-run&lt;/code&gt; needs network access to query the binary cache. And our test runs in a sandbox with no network.&lt;/p&gt;
&lt;h2 id=&#34;getting-network-access-into-the-sandbox&#34;&gt;Getting Network Access Into the Sandbox&lt;/h2&gt;
&lt;p&gt;For regular NixOS VM tests, this is straightforward. You build the test driver and run it outside the sandbox:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-console&#34; data-lang=&#34;console&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ nix build .#checks.x86_64-linux.yourTest.driver
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ ./result/bin/nixos-test-driver
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The interactive driver has network access, so you can run &lt;code&gt;nix build --dry-run&lt;/code&gt; inside the VM.&lt;/p&gt;
&lt;p&gt;For container tests, it&amp;rsquo;s more complicated. The container runs inside the Nix sandbox&amp;rsquo;s user namespace (needed for the &lt;code&gt;uid-range&lt;/code&gt; feature). We can&amp;rsquo;t just disable the sandbox because we need those namespaces.&lt;/p&gt;
&lt;p&gt;The solution? Inject network after the container starts.&lt;/p&gt;
&lt;h2 id=&#34;pausing-the-test&#34;&gt;Pausing the Test&lt;/h2&gt;
&lt;p&gt;The container test driver exposes a &lt;code&gt;wait_for_signal()&lt;/code&gt; helper that tests can call to pause and wait for SIGUSR1. Add it to your test script where you want to pause:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;start_all()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;machine&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;wait_for_unit(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;multi-user.target&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# Pause here to allow network injection&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;wait_for_signal()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# Continue with the test...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;When it pauses, the test driver prints a command you can run to inject network and continue:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;DEBUG MODE: Test paused, waiting for SIGUSR1...

To inject external network and continue test, run:
sudo /nix/store/...-python3/bin/python3 /nix/store/...-test-driver/.../inject_network.py &amp;lt;uuid&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We&amp;rsquo;ve packaged all of the network injection logic into an &lt;a href=&#34;https://git.clan.lol/clan/clan-core/src/commit/64f101cb733f2a50b38b8481da54f85afb2043ab/lib/test/container-test-driver/test_driver/inject_network.py&#34;&gt;&lt;code&gt;inject_network.py&lt;/code&gt;&lt;/a&gt; script. Just copy and run the command printed by the test driver. It finds the container, injects network, signals the test to continue, then waits for Ctrl-C to clean up.&lt;/p&gt;
&lt;h2 id=&#34;how-network-injection-works&#34;&gt;How Network Injection Works&lt;/h2&gt;
&lt;p&gt;The container test driver prints a UUID when it starts. The script uses that to find the container process and inject a network interface. Here&amp;rsquo;s how it works step by step.&lt;/p&gt;
&lt;p&gt;First, find the container process using the UUID:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-console&#34; data-lang=&#34;console&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ pgrep -af &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;abbc0409-94bb-4218-95b4-60b1fd13e4c2&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;276568 /bin/sh -c /nix/store/.../sleep 999999999 &amp;amp;&amp;amp; echo abbc0409-94bb-4218-95b4-60b1fd13e4c2
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Create a veth pair on the host:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-console&#34; data-lang=&#34;console&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ sudo ip link add veth-host type veth peer name veth-inject
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Move one end into the container&amp;rsquo;s network namespace:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-console&#34; data-lang=&#34;console&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ sudo ip link set veth-inject netns &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;/proc/276568/ns/net&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Configure the host side:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-console&#34; data-lang=&#34;console&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ sudo ip addr add 10.99.0.1/24 dev veth-host
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ sudo ip link set veth-host up
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now here&amp;rsquo;s the trick: we need to enter both the user namespace and network namespace to configure the container side. Just &lt;code&gt;--net&lt;/code&gt; alone fails with &amp;ldquo;Permission denied&amp;rdquo;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-console&#34; data-lang=&#34;console&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ sudo nsenter --user --net --target &lt;span style=&#34;color:#ae81ff&#34;&gt;276568&lt;/span&gt; -- ip addr add 10.99.0.2/24 dev veth-inject
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ sudo nsenter --user --net --target &lt;span style=&#34;color:#ae81ff&#34;&gt;276568&lt;/span&gt; -- ip link set veth-inject up
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ sudo nsenter --user --net --target &lt;span style=&#34;color:#ae81ff&#34;&gt;276568&lt;/span&gt; -- ip route add default via 10.99.0.1
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Don&amp;rsquo;t forget DNS! The container&amp;rsquo;s &lt;code&gt;/etc/resolv.conf&lt;/code&gt; is likely a symlink, so remove it first:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-console&#34; data-lang=&#34;console&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ sudo nsenter --user --mount --target &lt;span style=&#34;color:#ae81ff&#34;&gt;276568&lt;/span&gt; -- rm -f /etc/resolv.conf
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ sudo nsenter --user --mount --target &lt;span style=&#34;color:#ae81ff&#34;&gt;276568&lt;/span&gt; -- sh -c &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;echo &amp;#34;nameserver 8.8.8.8&amp;#34; &amp;gt; /etc/resolv.conf&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Enable NAT on the host:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-console&#34; data-lang=&#34;console&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ sudo sysctl -w net.ipv4.ip_forward&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ sudo iptables -t nat -A POSTROUTING -s 10.99.0.0/24 -j MASQUERADE
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Test connectivity:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-console&#34; data-lang=&#34;console&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ sudo nsenter --user --net --target &lt;span style=&#34;color:#ae81ff&#34;&gt;276568&lt;/span&gt; -- ping -c &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; 8.8.8.8
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&lt;/span&gt;PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;64 bytes from 8.8.8.8: icmp_seq=1 ttl=119 time=24.3 ms
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;inject_network.py&lt;/code&gt; script automates all of these steps. It also cleans up the veth pair and NAT rules when you press Ctrl-C.&lt;/p&gt;
&lt;h2 id=&#34;running-the-dry-run&#34;&gt;Running the Dry-Run&lt;/h2&gt;
&lt;p&gt;With network injected, SSH into the container and run the dry-run:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-console&#34; data-lang=&#34;console&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ ssh root@192.168.1.1 &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&amp;#39;&lt;/span&gt;cd /flake &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; nix build --dry-run &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;&lt;/span&gt;    .#nixosConfigurations.test-machine.config.system.build.toplevel&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Here&amp;rsquo;s what I got:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;these 9 derivations will be built:
  /nix/store/04r01g9s2ckfs4v64gx426r1ac3ssamy-etc-update-build-local-successful.drv
  /nix/store/fyi5gy25qcncs8zf9vxnlb54yah66fs5-stage-2-init.sh.drv
  /nix/store/gbnayc92q4q82jdrcnzfdpm2fw0acgfm-dry-activate.drv
  /nix/store/jf3sixnjh3v7x6fjs1rd0i9hqd9pzsjv-etc.drv
  /nix/store/lhvslpfnh3nr978m0c862qxsl3dnzymi-activate.drv
  /nix/store/hvxxm22ddlig6lvn8pifxa7kpb67acq6-builder.pl.drv
  /nix/store/pv0dybshsabgxf6n8wmdq64lgdimx4pf-perl-5.42.0-env.drv
  /nix/store/xsffzqw02ng2a4dryhxx2pwmndm7cymb-check-sshd-config.drv
  /nix/store/6639244qzcgn3bhvv2gq4kd4ybdvvgl7-nixos-system-update-machine-.drv

these 3 paths will be fetched (0.00 MiB download, 0.01 MiB unpacked):
  /nix/store/4w0spqkn44zlrys9y93x06h949dvcpj8-ensure-all-wrappers-paths-exist
  /nix/store/48zc854y65q0jvsa2na6liawgqvh69cq-make-shell-wrapper-hook
  /nix/store/r0ddi8vysis4rdlqjkv9jp68b8d41i4k-openssh-10.2p1-dev
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The 9 derivations are expected. That&amp;rsquo;s the new configuration we&amp;rsquo;re building. The 3 paths to be fetched are the problem.&lt;/p&gt;
&lt;h2 id=&#34;finding-the-packages&#34;&gt;Finding the Packages&lt;/h2&gt;
&lt;p&gt;Store paths have human-readable names, but we need the actual Nix expressions to add them to &lt;code&gt;closureInfo&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;For &lt;code&gt;make-shell-wrapper-hook&lt;/code&gt;, we can verify the package name:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-console&#34; data-lang=&#34;console&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ nix eval nixpkgs#makeShellWrapper --apply &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;x: x.name&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;#34;make-shell-wrapper-hook&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;For &lt;code&gt;-dev&lt;/code&gt; outputs like &lt;code&gt;openssh-10.2p1-dev&lt;/code&gt;, it&amp;rsquo;s usually the main package with &lt;code&gt;.dev&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-console&#34; data-lang=&#34;console&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ nix eval nixpkgs#openssh.dev --apply &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;x: x.name&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;#34;openssh-10.2p1&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;For NixOS-internal stuff like &lt;code&gt;ensure-all-wrappers-paths-exist&lt;/code&gt;, grep the nixpkgs source:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-console&#34; data-lang=&#34;console&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ grep -r &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;ensure-all-wrappers-paths-exist&amp;#34;&lt;/span&gt; nixos/modules/
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;nixos/modules/security/wrappers/default.nix:      pkgs.runCommand &amp;#34;ensure-all-wrappers-paths-exist&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This one is generated by the module system and should be part of the system toplevel closure. If it&amp;rsquo;s missing, it usually means the toplevel itself wasn&amp;rsquo;t properly included.&lt;/p&gt;
&lt;h2 id=&#34;the-fix&#34;&gt;The Fix&lt;/h2&gt;
&lt;p&gt;Add the missing packages to &lt;code&gt;closureInfo&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;closureInfo &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; pkgs&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;closureInfo {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  rootPaths &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# ... existing paths ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    pkgs&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;makeShellWrapper
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    pkgs&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;openssh&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;dev
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  ];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;After adding these, the test ran successfully. Only those 9 derivations got built, no mass rebuild. &amp;#x2705;&lt;/p&gt;
&lt;h2 id=&#34;a-note-on-drvpath&#34;&gt;A Note on &lt;code&gt;.drvPath&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;You might be tempted to include &lt;code&gt;pkg.drvPath&lt;/code&gt; to get the full build-time closure of a package. Be careful with this. It&amp;rsquo;s the nuclear option that can pull in a massive closure of build-time dependencies.&lt;/p&gt;
&lt;p&gt;Two problems:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Slower test startup&lt;/strong&gt;: Every path in the closure gets registered with the test&amp;rsquo;s Nix store database&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Unnecessary downloads&lt;/strong&gt;: You might download gigabytes of stuff you don&amp;rsquo;t need&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Only use &lt;code&gt;.drvPath&lt;/code&gt; for specific packages where you&amp;rsquo;ve verified it&amp;rsquo;s necessary:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;rootPaths &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  myPackage           &lt;span style=&#34;color:#75715e&#34;&gt;# Runtime closure, safe&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  pkgs&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;stdenv&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;drvPath &lt;span style=&#34;color:#75715e&#34;&gt;# Build-time closure, use sparingly&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;The key takeaway: &lt;code&gt;nix build --dry-run&lt;/code&gt; tells you exactly which paths are missing from your hermetic environment. The &amp;ldquo;paths to be fetched&amp;rdquo; list is your hit list.&lt;/p&gt;
&lt;p&gt;For VM tests, run the driver interactively. For container tests or other sandboxed builds, you&amp;rsquo;ll need to inject network access temporarily. The veth + nsenter approach works but requires some namespace gymnastics.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s not pretty, but it saves a lot of time, staring at build logs trying to figure out why Nix is mass-rebuilding dependencies.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Clan 2025 Wrap-Up: From Infrastructure to a New Computing Paradigm</title>
      <link>https://clan.lol/blog/2025-wrap-up/</link>
      <pubDate>Mon, 12 Jan 2026 00:00:00 +0000</pubDate>
      
      <guid>https://clan.lol/blog/2025-wrap-up/</guid>
      <description>&lt;h2 id=&#34;why-clan-exists&#34;&gt;Why Clan Exists&lt;/h2&gt;
&lt;p&gt;Clan&amp;rsquo;s core mission is to make &lt;a href=&#34;https://en.wikipedia.org/wiki/Digital_sovereignty&#34;&gt;digital sovereignty&lt;/a&gt; accessible to everyone. Clan is not a platform or a product; it is a free, open source framework that empowers people and groups to customize and manage machines within safe, secure, private networks that they alone control. Clan is built to be deeply composable (i.e. systems built from small, interoperable building blocks), reliably reproducible, and entirely self-sovereign.&lt;/p&gt;
&lt;p&gt;2025 delivered many forceful reminders of why this mission is more important and urgent than ever. From the rise of technocratic institutions, to increasingly invasive advertising, to the &lt;a href=&#34;https://doctorow.medium.com/https-pluralistic-net-2026-01-01-39c3-the-new-coalition-4a7a582ff5b7&#34;&gt;war on general-purpose computing&lt;/a&gt; still raging, we are inundated with technology designed to exploit, surveil, manipulate and extract from us. We won&amp;rsquo;t escape this mess through lobbying, interoperability or clever workarounds. It&amp;rsquo;s time for an exit, a full reset.&lt;/p&gt;
&lt;p&gt;In direct opposition to the capture-and-extract model that dominates the tech sector, Clan is designed to empower anyone to build a digital life that is completely their own. No dark patterns, no rent, no data tax, no black boxes, no backdoors, no copyrights or access controls. Just free, open source, composable infrastructure for truly personal computing – the foundation of a fully sovereign computing stack.&lt;/p&gt;
&lt;h2 id=&#34;2025-in-review&#34;&gt;2025 In Review&lt;/h2&gt;
&lt;p&gt;2025 culminated in the &lt;a href=&#34;https://git.clan.lol/clan/clan-core/src/branch/main/docs/site/releases/25-11.md&#34;&gt;announcement of our first stable release&lt;/a&gt;, marking a shift in mindset: Clan is no longer an experiment. It is stable infrastructure that people depend on. We were thrilled this year to see Clan growing beyond the realm of enthusiasts&amp;rsquo; homelabs and being used in production for business applications by corporate sysadmins. With this release comes a commitment to predictable behaviour, careful evolution, and responsibility toward users and contributors who are building on top of it.&lt;/p&gt;
&lt;p&gt;Read on for more of the exciting milestones we achieved in 2025, both strengthening the foundations of Clan and exploring the horizons of where it can take us in the future.&lt;/p&gt;
&lt;h2 id=&#34;networking--vpn-work-and-reliability-gains&#34;&gt;Networking / VPN work and reliability gains&lt;/h2&gt;
&lt;p&gt;Reliable networking is foundational for Clan. If machines can&amp;rsquo;t find each other, stay connected, or recover gracefully from failures, nothing else matters. Over the past year, much of our work has focused on making &lt;a href=&#34;https://en.wikipedia.org/wiki/Peer-to-peer&#34;&gt;peer-to-peer networking&lt;/a&gt; more robust, flexible, and predictable.&lt;/p&gt;
&lt;p&gt;One recurring lesson is that there is no single &amp;ldquo;best&amp;rdquo; VPN or &lt;a href=&#34;https://en.wikipedia.org/wiki/Overlay_network&#34;&gt;overlay network&lt;/a&gt;. Every solution comes with tradeoffs. Some rely on centralized controllers; others are fully peer-to-peer, but unreliable or difficult to operate. Some are fast but fragile, others are resilient but slow (see &lt;a href=&#34;https://vpnbench.clan.lol&#34;&gt;here&lt;/a&gt; for a detailed comparison). Instead of betting on one network to rule them all, we take a different approach.&lt;/p&gt;
&lt;p&gt;Clan introduces a networking abstraction that allows multiple network technologies to coexist. Rather than forcing users to choose upfront, Clan can automatically select the best available network for a given machine. Networks are treated as services that can be enabled declaratively, assigned priorities, and attached to machines through tags. The Clan CLI then transparently uses the most appropriate network when connecting to a machine.&lt;/p&gt;
&lt;p&gt;This abstraction has already led to significant gains in reliability. Clan commands now automatically pick up network configuration from the Clan &lt;a href=&#34;https://wiki.nixos.org/wiki/Flakes&#34;&gt;flake&lt;/a&gt; for most operations, removing the need for users to manually specify hosts, IPs, or connection methods. Sensitive connection details, such as &lt;a href=&#34;https://www.torproject.org/&#34;&gt;Tor&lt;/a&gt; hidden service addresses, are handled securely through Clan&amp;rsquo;s secret system and only decrypted when needed.&lt;/p&gt;
&lt;p&gt;Today, this enables admin-to-machine connectivity over networks such as Tor or the public internet, with support for on-demand networking services that start only when required. The result is a system that is more resilient to outages, avoids unnecessary exposure to the public web, and continues working even when individual network components fail.&lt;/p&gt;
&lt;p&gt;Looking ahead, this networking layer becomes even more important. Users can already add their own network services to Clan, and in the coming year, we plan to extend the model further by enhancing &lt;a href=&#34;https://en.wikipedia.org/wiki/Machine-to-machine&#34;&gt;machine-to-machine&lt;/a&gt; networking, supporting additional overlay networks, and unifying &lt;a href=&#34;https://en.wikipedia.org/wiki/User_space&#34;&gt;userspace networking&lt;/a&gt; across all providers. The goal is simple but ambitious: networking that &amp;ldquo;just works,&amp;rdquo; without hiding complexity behind a fragile abstraction, and without reintroducing centralized points of control.&lt;/p&gt;
&lt;h2 id=&#34;micro-vms&#34;&gt;Micro VMs&lt;/h2&gt;
&lt;p&gt;If Clan is about sovereign machines and networks, the next challenge is sovereign applications.&lt;/p&gt;
&lt;p&gt;Today&amp;rsquo;s proprietary platforms have set a very high bar for usability and security. Commercially hosted web and mobile apps are heavily &lt;a href=&#34;https://en.wikipedia.org/wiki/Sandbox_(computer_security)&#34;&gt;sandboxed&lt;/a&gt; by default, can run multiple instances, and are always pre-connected to the services they need. In contrast, much of the peer-to-peer software world is still made up of applications that are complicated to install and configure, have unclear security boundaries, rely on complex or unreliable &lt;a href=&#34;https://en.wikipedia.org/wiki/Client%E2%80%93server_model&#34;&gt;client-server protocols&lt;/a&gt; and are limited to a single instance. As powerful as a lot of this software is, it is often also unsafe, cumbersome, and limiting.&lt;/p&gt;
&lt;p&gt;To close this gap, we&amp;rsquo;ve been exploring &lt;a href=&#34;https://openmetal.io/resources/blog/microvms-scaling-out-over-scaling-up/&#34;&gt;micro VMs&lt;/a&gt; to make running peer-to-peer applications &lt;strong&gt;safer, more convenient and more flexible&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Safer:&lt;/strong&gt; Rather than the shared-kernel isolation of e.g. &lt;a href=&#34;https://en.wikipedia.org/wiki/Linux_namespaces&#34;&gt;Linux namespaces&lt;/a&gt;, micro VMs use &lt;a href=&#34;https://en.wikipedia.org/wiki/Hardware_virtualization&#34;&gt;hardware virtualization&lt;/a&gt; to maintain strong security boundaries between applications. This means software you run inside a micro VM environment has no connection to your primary system or any other programs you&amp;rsquo;re running, so any vulnerabilities stay contained.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;More convenient:&lt;/strong&gt; By running applications inside a micro VM, we can make sure software loads the same for everyone, regardless of what OS they&amp;rsquo;re using. &lt;a href=&#34;https://nixos.org/&#34;&gt;Nix&lt;/a&gt;&amp;rsquo;s caching and reproducible builds mean programs can launch almost as quickly as web apps, while remaining fully local and user-controlled. Best of all, Clans are ready-made peer-to-peer networks, so communication between them is already established and traffic never needs to touch hosted servers or the open web.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;More flexible:&lt;/strong&gt; Unlike traditional virtual machines, micro VMs are extremely lightweight and can start in a few hundred milliseconds. An everyday desktop or laptop can run a micro VM - or several - that operates in isolation, enabling a user to e.g. bypass compatibility issues, run multiple instances, experiment with different configurations or create an ad hoc environment for a specific purpose.&lt;/p&gt;
&lt;p&gt;A key requirement for making all of this practical is deep desktop integration, including &lt;a href=&#34;https://en.wikipedia.org/wiki/Hardware_acceleration&#34;&gt;graphics acceleration&lt;/a&gt;. We&amp;rsquo;ve been working on GPU-accelerated micro VMs using modern &lt;a href=&#34;https://pve.proxmox.com/wiki/Windows_VirtIO_Drivers&#34;&gt;virtio-gpu&lt;/a&gt; techniques, enabling &lt;a href=&#34;https://wayland.freedesktop.org/&#34;&gt;Wayland&lt;/a&gt;-based graphical applications to run inside micro VMs with near-native performance, without requiring dedicated GPUs or enterprise-only hardware features.&lt;/p&gt;
&lt;video width=&#34;100%&#34; loop muted controls&gt;
  &lt;source
    src=&#34;https://static.clan.lol/videos/munixvelooooren.webm&#34;
    type=&#34;video/webm&#34;
  /&gt;
  &lt;source
    src=&#34;https://static.clan.lol/videos/munixvelooooren.mp4&#34;
    type=&#34;video/mp4&#34;
  /&gt;
  &lt;source
    src=&#34;https://static.clan.lol/videos/munixvelooooren.ove&#34;
    type=&#34;video/ove&#34;
  /&gt;
&lt;/video&gt;

&lt;p&gt;Of course, isolation alone isn&amp;rsquo;t enough. Applications still need controlled ways to share data with users and with each other. To enable this, we&amp;rsquo;re integrating D-Bus–based &lt;a href=&#34;https://flatpak.github.io/xdg-desktop-portal/&#34;&gt;desktop portals&lt;/a&gt;, similar to those used by &lt;a href=&#34;https://flatpak.org/&#34;&gt;Flatpak&lt;/a&gt;, allowing applications to request access to files, cameras, or screens through explicit, user-mediated permissions. This preserves strong isolation while keeping applications genuinely useful.&lt;/p&gt;
&lt;p&gt;Together, Nix, micro VMs, GPU acceleration, desktop portals, and &lt;a href=&#34;https://tailscale.com/learn/understanding-mesh-vpns&#34;&gt;mesh VPNs&lt;/a&gt; form the basis of a local application platform that is secure by default, fast enough for daily use, and naturally compatible with peer-to-peer networking. As a result, even applications that were never designed for P2P use can be safely retrofitted to work in distributed, self-hosted environments. This work is still evolving, but it represents a critical step toward making community-owned, self-hosted software competitive with proprietary platforms, without sacrificing user experience or sovereignty.&lt;/p&gt;
&lt;p&gt;Looking ahead, the next step is to integrate micro VMs more deeply into Clan itself. This includes first-class support in the Clan CLI and, over time, exposing micro VM-based applications and services through the Clan GUI. Our goal is to make strong isolation, reproducibility, and secure application sharing feel like a natural part of managing a Clan, not a separate or specialized workflow.&lt;/p&gt;
&lt;p&gt;Our first explorations in this area have focused on deploying micro VMs within NixOS systems, but in the future we will extend this approach to make Clan itself portable: a complete operating environment in a replicable, shareable package, so a user can join a Clan even if they don&amp;rsquo;t have NixOS installed.&lt;/p&gt;
&lt;p&gt;We want to take this opportunity to express our gratitude to our friends at &lt;a href=&#34;https://www.qubes-os.org/&#34;&gt;Qubes OS&lt;/a&gt; for introducing us to the amazing &lt;a href=&#34;https://github.com/valpackett&#34;&gt;Val Packett&lt;/a&gt;, who has been driving this line of experimentation.&lt;/p&gt;
&lt;h2 id=&#34;clan-gui&#34;&gt;Clan GUI&lt;/h2&gt;
&lt;p&gt;Strong, safe defaults are a necessary foundation, but for many people, they are still not enough. Especially for those unfamiliar with NixOS, even a well-chosen default configuration leaves too much implicit knowledge unstated. Understanding how machines relate to each other, how services are composed, how secrets are managed, or how changes propagate across a network still requires expertise most users don&amp;rsquo;t have.&lt;/p&gt;
&lt;p&gt;To make Clan accessible beyond experienced Nix users, we needed a more legible way to understand and operate a Clan of machines. This is why we started building the &lt;a href=&#34;https://git.clan.lol/clan/clan-core/src/branch/main/pkgs/clan-app&#34;&gt;Clan GUI&lt;/a&gt;, &lt;a href=&#34;https://static.clan.lol/videos/nixcon-2025.mp4&#34;&gt;which we introduced at NixCon&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The GUI does not replace Nix or the Clan CLI; it builds on top of them. Where the CLI is precise and powerful, the GUI makes the system visible and approachable, especially for collaborators who shouldn&amp;rsquo;t need to touch configuration files to participate.&lt;/p&gt;
&lt;video width=&#34;100%&#34; loop controls&gt;
  &lt;source
    src=&#34;https://static.clan.lol/videos/clan-gui-demo.webm&#34;
    type=&#34;video/webm&#34;
  /&gt;
  &lt;source
    src=&#34;https://static.clan.lol/videos/clan-gui-demo.mp4&#34;
    type=&#34;video/mp4&#34;
  /&gt;
  &lt;source
    src=&#34;https://static.clan.lol/videos/clan-gui-demo.ove&#34;
    type=&#34;video/ove&#34;
  /&gt;
&lt;/video&gt;

&lt;p&gt;At its core, the GUI reflects Clan&amp;rsquo;s opinionated deployment framework. It focuses on the hardest parts of self-hosting: bootstrapping machines, managing secrets, wiring services across multiple systems, and understanding what is running where. A declarative secret management system allows services to opt into their own secrets before deployment, enabling automatic generation, secure storage, rotation, and testing, all exposed consistently through both CLI and GUI.&lt;/p&gt;
&lt;video width=&#34;100%&#34; loop controls&gt;
  &lt;source
    src=&#34;https://static.clan.lol/videos/monitoring-alert.webm&#34;
    type=&#34;video/webm&#34;
  /&gt;
  &lt;source
    src=&#34;https://static.clan.lol/videos/monitoring-alert.mp4&#34;
    type=&#34;video/mp4&#34;
  /&gt;
  &lt;source
    src=&#34;https://static.clan.lol/videos/monitoring-alert.ove&#34;
    type=&#34;video/ove&#34;
  /&gt;
&lt;/video&gt;

&lt;p&gt;On top of this sits client services: a multi-machine service layer that makes it possible to define shared infrastructure once and apply it across groups of machines using tags. Because this layer is &lt;a href=&#34;https://en.wikipedia.org/wiki/JSON&#34;&gt;JSON&lt;/a&gt;-compatible, services can be added, removed, or reconfigured through the GUI while remaining fully compatible with Nix-based workflows.&lt;/p&gt;
&lt;p&gt;The result is an interface that makes infrastructure easier to understand. Machines appear as part of a network, can be grouped and tagged, and have services applied visually. This makes Clan usable not only by the person who set it up, but also by families, teams, and collaborators.&lt;/p&gt;
&lt;p&gt;The Clan GUI is still in early development and today complements, rather than replaces, the CLI. But it already points toward the future: sovereign infrastructure that is not just powerful, but understandable - and maybe even fun.&lt;/p&gt;
&lt;figure&gt;&lt;img src=&#34;https://static.clan.lol/videos/25_06_clan-gui-screenshot.png&#34;&gt;
&lt;/figure&gt;
&lt;h2 id=&#34;secrets-and-computed-values-rethought&#34;&gt;&lt;strong&gt;Secrets and computed values, rethought&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;In 2025, &lt;a href=&#34;https://clan.lol/blog/vars/&#34;&gt;we replaced our initial secret management approach (&amp;ldquo;facts&amp;rdquo;) with vars&lt;/a&gt;, a declarative framework that allows services to define how secrets and other values are generated, shared, and rotated across machines. This removed much of the manual bootstrapping traditionally required when deploying infrastructure and made both CLI and GUI workflows more reliable. Looking ahead, we are continuing to move vars to the flake level, attaching secrets to services rather than individual machines to further improve scalability, usability, and cross-machine coordination.&lt;/p&gt;
&lt;h2 id=&#34;inventory-from-single-machines-to-fleets&#34;&gt;&lt;strong&gt;Inventory: from single machines to fleets&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;NixOS excels at configuring individual machines. Clan extends this paradigm to groups of machines by introducing an inventory and service layer abstraction. This makes it possible to define services, users, secrets, and relationships once and apply them consistently across many machines. It&amp;rsquo;s a critical shift from &amp;ldquo;machine configuration&amp;rdquo; to &amp;ldquo;infrastructure configuration,&amp;rdquo; and underpins everything from networking to future collaboration features.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;inventory&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;instances &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# One declaration enables VPN across all machines&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    zerotier &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      roles&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;controller&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;machines&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;server &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; { };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      roles&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;peer&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;tags &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; [ &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;all&amp;#34;&lt;/span&gt; ]; &lt;span style=&#34;color:#75715e&#34;&gt;# All machines join the network&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  };
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You can read more about the inventory &lt;a href=&#34;https://docs.clan.lol/main/guides/inventory/inventory/&#34;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;service-exports-and-composability&#34;&gt;&lt;strong&gt;Service exports and composability&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Clan services can now export values that other services can consume. This allows different parts of the system to wire themselves together automatically, for example, enabling VPN configuration to be reused by higher-level services without manual glue code. The result is a more composable system where services cooperate instead of being configured in isolation.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;perInstance &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; { mkExports&lt;span style=&#34;color:#f92672&#34;&gt;,&lt;/span&gt; machine&lt;span style=&#34;color:#f92672&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;...&lt;/span&gt; }: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    exports &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; mkExports {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      peer&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;hosts &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          plain &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; clanLib&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;getPublicValue {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            machine &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; machine&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;name;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            generator &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;mycelium&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            file &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;ip&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            flake &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; directory;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      ];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  };
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You can read more about how exports work &lt;a href=&#34;https://docs.clan.lol/main/guides/services/exports/&#34;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;macos-as-a-first-class-clan-member&#34;&gt;macOS as a first-class Clan member&lt;/h2&gt;
&lt;p&gt;In 2025, &lt;a href=&#34;https://clan.lol/blog/macos/&#34;&gt;Clan gained full support for managing macOS machines via nix-darwin&lt;/a&gt;. MacOS systems can now participate in the inventory, receive declarative configuration, manage secrets, and be deployed from — or deploy to — other Clan machines. This matters because real-world Clans are heterogeneous: families, teams, and communities rarely run Linux everywhere. Supporting macOS makes Clan viable in mixed environments and lowers the barrier to adoption.&lt;/p&gt;
&lt;h2 id=&#34;whats-next&#34;&gt;What&amp;rsquo;s next&lt;/h2&gt;
&lt;p&gt;In dry technical terms, we describe Clan as a peer-to-peer computer management framework. But this idea is deceptively radical: if anyone is free to create secure, autonomous networks, with self-hosted data and services, then we are also free to completely reimagine what it means to be online. The monolithic dead internet, the so-called &amp;ldquo;global public square&amp;rdquo;, could be replaced by a living web of individual spaces – some public, some private, all opt-in and entirely self-determined.&lt;/p&gt;
&lt;p&gt;However, this also presents a challenge: in a world where one person belongs to many Clans and connects to as many internets, how do they navigate? How do they keep track of where they are, where their people are, what is private and what is public?&lt;/p&gt;
&lt;p&gt;No matter how secure the technical foundation might be, it won&amp;rsquo;t accomplish anything unless it&amp;rsquo;s also usable. So we&amp;rsquo;ve started to experiment with different technologies that could weave these pieces together - to determine how multiple networks and applications could exist on the same machine, how those machines could find each other, offer services, exchange resources and more. Micro VMs and Clan GUI are the first, still ongoing, efforts in this direction. The following initiatives are still in early stages of research and prototyping, but we&amp;rsquo;re excited to share this first glimpse of what&amp;rsquo;s on the horizon.&lt;/p&gt;
&lt;h3 id=&#34;spaces&#34;&gt;Spaces&lt;/h3&gt;
&lt;p&gt;Spaces is a free and open source operating environment designed to make digital sovereignty the norm by building Clan in at the base level. The networked machines in a Clan represent a group of human beings and the connections between them; Spaces give those connections a tangible form. If your Clan is your village, then spaces are the rooms and common areas where you live, work, and gather together.&lt;/p&gt;
&lt;video width=&#34;100%&#34; controls&gt;
  &lt;source
    src=&#34;https://static.clan.lol/videos/create-space.webm&#34;
    type=&#34;video/webm&#34;
  /&gt;
  &lt;source
    src=&#34;https://static.clan.lol/videos/create-space.mp4&#34;
    type=&#34;video/mp4&#34;
  /&gt;
  &lt;source
    src=&#34;https://static.clan.lol/videos/create-space.ove&#34;
    type=&#34;video/ove&#34;
  /&gt;
&lt;/video&gt;

&lt;p&gt;&lt;strong&gt;Create and share spaces:&lt;/strong&gt; Customize spaces for work, family, gaming, art, life admin, or anything else you use your computer for. Each space can have its own look and feel, organization, tools, and access rules. One space might be completely private, cut off from the internet, so you can store sensitive files knowing there&amp;rsquo;s no way for anyone to get in. In another, you might add your family or work Clan. Or you could create a space that&amp;rsquo;s completely open and discoverable by anyone.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Multiplayer by default:&lt;/strong&gt; People in a shared space are connected at the OS level - no crashed website, censored platform or cloud outage can keep them apart.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Isolation by design:&lt;/strong&gt; A space is a self-contained environment, completely isolated by default, so what you do in one space won&amp;rsquo;t affect the security or functionality of another – even if they&amp;rsquo;re running on the same machine.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;No platform purgatory:&lt;/strong&gt; Essential tools such as messaging, video chat, shared files, collaborative docs, wallets, and more are built in as modular services and widgets, available to anyone who joins the space. No need for everyone to download anything or make an account anywhere – just get straight to doing whatever you do, together.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Own your OS:&lt;/strong&gt; Your computer, operating system, and online spaces are entirely your own. Create your own tools, apps and widgets, customize and share them with your Clan or with the whole world. No coding required - Spaces Playground will help you create whatever you dream up with a few text prompts.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s much more on the horizon - we&amp;rsquo;ll follow up with a dedicated post about Spaces in the coming weeks.&lt;/p&gt;
&lt;h3 id=&#34;clan-and-llms&#34;&gt;Clan and LLMs&lt;/h3&gt;
&lt;p&gt;We are deliberately not part of the current AI hype cycle. Calling large language models &amp;ldquo;AI&amp;rdquo; is, in our view, misleading at best. These systems are not intelligent in any meaningful sense, and framing them as such obscures both their limitations and the real risks of concentrating power in opaque, corporate-controlled systems. We are also wary of how LLMs are currently used to replace clear interfaces, obscure system behavior, or paper over poor design. Clan remains fundamentally &lt;strong&gt;declarative, inspectable, and deterministic by default&lt;/strong&gt;. Power users should always be able to reason about, audit, and reproduce their systems without a chat window in the way.&lt;/p&gt;
&lt;p&gt;That said, large language models &lt;em&gt;are&lt;/em&gt; a powerful piece of technology. Especially when combined with open source software, self-hosting, and strong isolation guarantees, they can become genuinely useful tools rather than extractive platforms.&lt;/p&gt;
&lt;p&gt;For Clan, the key condition is non-negotiable: &lt;strong&gt;LLMs must be able to be self-hosted and locally controlled&lt;/strong&gt;. Models that depend on centralized APIs, surveillance-driven business models, or proprietary platforms are fundamentally incompatible with digital sovereignty. But when models run locally, inside clearly defined boundaries, they can dramatically lower the barrier to using complex systems.&lt;/p&gt;
&lt;p&gt;In the short term, we&amp;rsquo;re exploring LLMs as an interface layer. One concrete experiment is controlling parts of the Clan UI and services through a locally hosted LLM. Instead of requiring users to learn new terminology or configuration patterns upfront, they can describe what they want in plain language and have the system translate that intent into concrete, inspectable actions.&lt;/p&gt;
&lt;video width=&#34;100%&#34; controls&gt;
  &lt;source
    src=&#34;https://static.clan.lol/videos/clan-client-prototype-compress.webm&#34;
    type=&#34;video/webm&#34;
  /&gt;
  &lt;source
    src=&#34;https://static.clan.lol/videos/clan-client-prototype-compress.mp4&#34;
    type=&#34;video/mp4&#34;
  /&gt;
  &lt;source
    src=&#34;https://static.clan.lol/videos/clan-client-prototype-compress.ove&#34;
    type=&#34;video/ove&#34;
  /&gt;
&lt;/video&gt;

&lt;p&gt;Used this way, LLMs don&amp;rsquo;t replace understanding, but help people to get started without being overwhelmed. A second already planned experiment is the use of LLMs within Spaces: locally hosted LLMs could also help facilitate interaction by acting as shared assistants that summarize activity, mediate coordination, surface relevant context, or help people navigate what is happening inside a space without turning it into a noisy or overwhelming environment.&lt;/p&gt;
&lt;p&gt;Long-term, we see a more interesting possibility. If Clan becomes the foundation for sovereign, self-hosted computing, then locally hosted and isolated LLMs could act as mediators between Clans. A Clan could describe the services it offers, the resources it is willing to share, or the conditions under which access is granted.&lt;/p&gt;
&lt;p&gt;These descriptions could be indexed, queried, and negotiated through LLMs, enabling discovery and coordination without relying on centralized platforms. This direction is inspired by projects such as &lt;a href=&#34;https://metagov.org/projects/koi-pond/project-details&#34;&gt;KOI&lt;/a&gt; and aligns closely with our vision of a decentralized, human-scale internet.&lt;/p&gt;
&lt;p&gt;In both cases, the principle remains the same: keeping LLMs local, inspectable, and isolated, we can use their strengths without importing the failures of the platforms that currently dominate this space.&lt;/p&gt;
&lt;h3 id=&#34;clanhub-a-home-for-community-services&#34;&gt;ClanHub: A Home for Community Services&lt;/h3&gt;
&lt;p&gt;As Clan has grown, so has the number of services built on top of it. What started as a small, tightly integrated set of core services has expanded to dozens of modules, many of which don&amp;rsquo;t require deep coupling with Clan&amp;rsquo;s core logic. While this growth is exciting, it also creates maintenance overhead that slows down core development.&lt;/p&gt;
&lt;p&gt;To address this, we&amp;rsquo;re introducing ClanHub: a shared home for open source services compatible with Clan that are developed and maintained by the wider community.&lt;/p&gt;
&lt;p&gt;ClanHub is intended as a place where contributors can add new services, iterate quickly, and establish best practices together, while relying on stable Clan core APIs. This allows the core team to focus on fundamentals like reliability, networking, and tooling, while giving community services more room to grow on their own terms.&lt;/p&gt;
&lt;p&gt;A great example of a contribution that fits naturally into ClanHub is monitoring. Recently, &lt;a href=&#34;https://git.clan.lol/clan/clan-core/pulls/5999&#34;&gt;Friedow contributed a new monitoring service&lt;/a&gt; that cleanly separates server and client roles, supports metrics, logs, dashboards, and alerting, and has already been tested in real-world Clan setups. This kind of well-scoped, production-proven service benefits from shared CI, clear ownership, and faster iteration, all of which ClanHub is designed to support.&lt;/p&gt;
&lt;p&gt;Over time, we plan to move many services that don&amp;rsquo;t require tight core integration into the community space, while keeping only a small, carefully curated set in Clan itself. ClanHub will provide shared CI, testing patterns, documentation, and visibility, making it easier to review contributions, ensure quality, and help new maintainers succeed.&lt;/p&gt;
&lt;p&gt;Most importantly, ClanHub is optional. No one is forced to use it, and nothing prevents experimentation elsewhere. But for contributors who want their services to be discoverable, well-supported, and compatible with Clan&amp;rsquo;s evolving ecosystem, ClanHub will be the natural place to collaborate.&lt;/p&gt;
&lt;p&gt;This is an important step toward a healthier division of responsibilities: a stable, focused core and a thriving, fast-moving ecosystem around it.&lt;/p&gt;
&lt;h3 id=&#34;clan-as-mass-scale-decentralized-infrastructure&#34;&gt;Clan as mass-scale decentralized infrastructure&lt;/h3&gt;
&lt;p&gt;Although we&amp;rsquo;ve so far focused on smaller scale networks, we have also been researching how Clan might add resilience and value to large-scale decentralized systems such as blockchains. Blockchains, which store more than $3 trillion worth of digital assets in the form of cryptocurrencies, are heavily dependent on centralized infrastructure such as cloud providers and hosted platforms. Now that we have a stable version of Clan, we can finally say to the crypto world: &amp;ldquo;Clan can fix this.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;We believe this area is worth exploring because it represents a strategic opportunity and proving ground for both Nix and Clan. It&amp;rsquo;s a highly adversarial environment, so if Clan can endure there, it can endure anywhere. Moreover, most blockchain infrastructure is already open source and Linux-based, so migration costs would be low, but the potential benefits are high.&lt;/p&gt;
&lt;p&gt;Blockchain decentralization is undermined at several layers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Infrastructure:&lt;/strong&gt; deploying and maintaining &lt;a href=&#34;https://www.coinbase.com/en-de/learn/crypto-glossary/what-is-a-node-in-cryptocurrency&#34;&gt;nodes&lt;/a&gt; is a technical burden that most individuals are unable or unwilling to take on. Nodes instead tend to consolidate around resource-rich enterprises who average users depend on, share private data with, and even pay for access.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Applications:&lt;/strong&gt; very little usable information can be stored or communicated over a blockchain. The vast majority of user experience and activity happens off-chain, where &lt;a href=&#34;https://en.wikipedia.org/wiki/Decentralized_application&#34;&gt;DApp&lt;/a&gt; developers have to bridge the gap with hosted interfaces or outsource to third-party platforms like &lt;a href=&#34;https://discord.com/&#34;&gt;Discord&lt;/a&gt;. This creates friction, restricts what DApps can do, and burdens maintainers with unnecessary liabilities.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Stack depth:&lt;/strong&gt; the core functionality of a blockchain is to sort transactions and maintain shared global states. It&amp;rsquo;s actually a very shallow stack, with the ordered logic of the protocol fundamentally disconnected from the chaotic system of human society.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We have considered a number of ways Clan could be used at each of these layers, including:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Communal hosting:&lt;/strong&gt; not all users need to run their infrastructure personally. Friends, families, and communities can create Clans to collectivize node resources. Useful configurations could then propagate between groups.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&#34;https://ethereum.org/en/dao/&#34;&gt;DAO&lt;/a&gt; desktop:&lt;/strong&gt; like showing up on the first day of work and being given a preconfigured laptop, DAO members could simply open a DAO-managed Clan to enter into a local, bespoke desktop environment, where things like privacy could be mandatory.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Off-chain smart contracts:&lt;/strong&gt; instead of relying on application-specific &lt;a href=&#34;https://help.coinbase.com/en/coinbase/getting-started/crypto-education/glossary/layer-2&#34;&gt;L2s&lt;/a&gt;, individuals could simply deploy ephemeral, user-specific private L2s for their personal transactions, executed securely over a Clan with verifiable end states.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;closing-thoughts&#34;&gt;Closing Thoughts&lt;/h2&gt;
&lt;p&gt;The examples discussed above, including blockchain and peer-to-peer coordination, are just one illustration of a broader pattern: many long-standing problems across different industries stem from the same underlying issues of centralization, opacity, and loss of user control. We see Clan as a foundation that can help address these issues far beyond any single domain, and we hope this post sparks curiosity and creativity about where Clan could be useful next, or what could be built on top of it.&lt;/p&gt;
&lt;p&gt;2025 marked an important transition for Clan: from experimentation toward production-grade infrastructure. With the first stable release, stronger defaults, and a growing focus on reliability, we&amp;rsquo;ve taken on a greater responsibility toward the people and communities who depend on this work. Over the past year, we also deepened our involvement in the Nix ecosystem, through community events, sponsorships, and direct contributions, including &lt;a href=&#34;https://github.com/lassulus&#34;&gt;Lassulus&lt;/a&gt; taking on the role of treasurer of the &lt;a href=&#34;https://nixos.org/community/teams/foundation-board/&#34;&gt;NixOS Foundation&lt;/a&gt;. These are signals of long-term commitment, not just to Clan, but to the ecosystem it builds upon.&lt;/p&gt;
&lt;p&gt;We are deeply grateful to everyone who contributed code, feedback, ideas, and critical perspectives throughout the year, as well as to our partners and sponsors, especially our friends at &lt;a href=&#34;https://golem.network/&#34;&gt;Golem&lt;/a&gt;, whose support has been instrumental in making this work possible.&lt;/p&gt;
&lt;p&gt;Clan is built in public, deliberately and collaboratively. If you&amp;rsquo;re curious, we invite you to try &lt;a href=&#34;https://docs.clan.lol/main/&#34;&gt;Clan&lt;/a&gt;, explore the &lt;a href=&#34;https://git.clan.lol/clan/clan-core&#34;&gt;repository&lt;/a&gt;, join the conversation on &lt;a href=&#34;https://matrix.to/#/#clan:clan.lol&#34;&gt;Matrix&lt;/a&gt;, give feedback, follow development, and share ideas about where Clan could help solve real problems. We believe sovereign computing is not a niche concern, but a prerequisite for a healthier digital future, and we&amp;rsquo;re excited to keep building toward it together.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Credits&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&amp;ldquo;Voxel art in Spaces UI by &lt;a href=&#34;https://madmaraca.art/&#34;&gt;Mari Zand&lt;/a&gt;, used with permission&amp;rdquo;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Towards a secure peer-to-peer app platform for Clan</title>
      <link>https://clan.lol/blog/towards-app-platform-vmtech/</link>
      <pubDate>Fri, 26 Sep 2025 06:30:00 -0333</pubDate>
      
      <guid>https://clan.lol/blog/towards-app-platform-vmtech/</guid>
      <description>&lt;p&gt;While most of the existing Clan framework is dedicated to machine and service management, there&amp;rsquo;s more on the horizon. Our mission is to make sure peer-to-peer, user-controlled, community software can beat Big Tech solutions. That&amp;rsquo;s why we&amp;rsquo;re working on platform fundamentals that would open the way for our FOSS stack to match the usability and convenience of proprietary platforms.&lt;/p&gt;
&lt;p&gt;Unfortunately, the FOSS world is still lagging behind commercial platforms in some important aspects:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Web and mobile apps are strongly sandboxed, so while they can get very aggressive in snooping on the data they &lt;em&gt;are&lt;/em&gt; allowed to access, the enforcement of the isolation model is very robust — and there is a model for &lt;em&gt;sharing&lt;/em&gt; data that makes the isolated applications actually useful..
&lt;ul&gt;
&lt;li&gt;Meanwhile in the FOSS world, it&amp;rsquo;s still extremely common to run software with full access to the user&amp;rsquo;s account. The only project that has built anything close to a similar platform for local software is Flatpak, which is still not perfect and its main repo has a very lax policy;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Centralized Web services can have &amp;ldquo;multiple instances&amp;rdquo; simply by switching accounts; self-hosting Web services is trivially multi-instance; even Android now provides a multi-instance facility..
&lt;ul&gt;
&lt;li&gt;Meanwhile local software often doesn&amp;rsquo;t have a global database, but when it does, it can be impossible to make it multi-instance without advanced knowledge;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Commercial apps come with their own always-online remote servers. Users don&amp;rsquo;t have to think about connecting the clients to the servers, it&amp;rsquo;s all pre-connected!
&lt;ul&gt;
&lt;li&gt;Meanwhile decentralized community software is stuck between various bad options. Supporting multiple commercial backends is tedious and defeats the point anyway. Self-hosting traditional web servers can get complex and unreliable, and exposes attack surface to the public Web. Direct peer-to-peer connections can be hard to set up and unreliable too, and typically don&amp;rsquo;t provide asynchronous communication.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So… What do we need to make it possible for communities to share apps install and load &lt;strong&gt;quickly&lt;/strong&gt;, already &lt;strong&gt;pre-connected&lt;/strong&gt; to network services; are isolated to a &lt;strong&gt;worry-free&lt;/strong&gt; level of security, and yet allow for enough &lt;strong&gt;sharing&lt;/strong&gt; via explicit permissions to make them useful?&lt;/p&gt;
&lt;p&gt;The first piece of the puzzle is, unsurprisingly, Nix. The entire Clan project is built on Nix, and the future app platform is no exception. Nix makes it possible to quickly fetch and run any software – thanks to caching, as long as we steer everyone towards using very few common versions of the nixpkgs tree, most downloads could be almost as fast as web app loads.&lt;/p&gt;
&lt;p&gt;Then we have to add a &lt;strong&gt;microVM hypervisor&lt;/strong&gt; with &lt;strong&gt;Wayland and GPU virtualization&lt;/strong&gt; and a side of &lt;strong&gt;D-Bus portals&lt;/strong&gt;… and we can finally get a glimpse of the future!&lt;/p&gt;
&lt;video width=&#34;100%&#34; loop muted autoplay&gt;
  &lt;source
    src=&#34;https://static.clan.lol/videos/munixfx.webm&#34;
    type=&#34;video/webm&#34;
  /&gt;
  &lt;source
    src=&#34;https://static.clan.lol/videos/munixfx.mp4&#34;
    type=&#34;video/mp4&#34;
  /&gt;
  &lt;source
    src=&#34;https://static.clan.lol/videos/munixfx.ove&#34;
    type=&#34;video/ove&#34;
  /&gt;
&lt;/video&gt;

&lt;h2 id=&#34;microvms&#34;&gt;microVMs&lt;/h2&gt;
&lt;p&gt;Secure isolation is essential for any modern app platform. Hardware-based virtualization is a lot more confidence-inspiring than shared-kernel isolation mechanisms like Linux namespaces. But it&amp;rsquo;s not &lt;em&gt;only&lt;/em&gt; a security measure. Running apps in VMs also improves environment consistency/reproducibility by ensuring everyone runs the same kernel — which can also give us portability, since it enables running on completely different host OSes as well.&lt;/p&gt;
&lt;p&gt;If your experience with virtualization on desktop has only been with booting entire Linux distros under something like VirtualBox, you might be very skeptical of the same technology being involved in launching applications all the time. But that&amp;rsquo;s not at all inherent to the use of KVM!&lt;/p&gt;
&lt;p&gt;Conventional VMs feel &amp;ldquo;heavy&amp;rdquo; —slow to launch, big RAM footprint, extra background CPU usage, fixed storage allocation, usually not very well integrated with the host desktop— only because their goal is to simulate a whole another computer within your existing computer. For app isolation, we don&amp;rsquo;t need that, so the whole stack can be vastly simplified and optimized for high performance and low overhead. The microVM idea was first popularized by AWS&amp;rsquo;s &lt;a href=&#34;https://firecracker-microvm.github.io/&#34;&gt;Firecracker&lt;/a&gt; on the server side, powering instantly-launching event/request handlers in Lambda. A microVM boots directly into the kernel (skipping firmware) and does not emulate any legacy PC hardware, which results in &lt;em&gt;very&lt;/em&gt; fast boot times, on the order of a couple hundred milliseconds.&lt;/p&gt;
&lt;p&gt;Now, has this been used on the client side already? Yes, most prominently by Asahi Linux, motivated by a technical restriction that was preventing Apple machines from playing legacy Windows games. That&amp;rsquo;s the &lt;a href=&#34;https://github.com/AsahiLinux/muvm&#34;&gt;muvm&lt;/a&gt; project, powered by &lt;a href=&#34;https://github.com/containers/libkrun&#34;&gt;libkrun&lt;/a&gt; – a Firecracker-like VMM provided as a dynamic library so that different frontends could be built. For our platform development, we have indeed adopted muvm (after submitting &lt;a href=&#34;https://github.com/AsahiLinux/muvm/pull/192&#34;&gt;some changes&lt;/a&gt; that make it more useful for us), combining it with namespace-based &lt;a href=&#34;https://github.com/containers/bubblewrap&#34;&gt;Bubblewrap&lt;/a&gt; to make a script that &lt;a href=&#34;https://git.clan.lol/clan/munix&#34;&gt;runs NixOS system closures&lt;/a&gt; in microVMs.&lt;/p&gt;
&lt;p&gt;…Wait, did someone mention playing games– like, highly GPU-demanding games? In a VM? Without a dedicated GPU?&lt;/p&gt;
&lt;h2 id=&#34;desktop-and-gpu-support&#34;&gt;Desktop and GPU support&lt;/h2&gt;
&lt;video width=&#34;100%&#34; loop muted autoplay&gt;
  &lt;source
    src=&#34;https://static.clan.lol/videos/munixvelooooren.webm&#34;
    type=&#34;video/webm&#34;
  /&gt;
  &lt;source
    src=&#34;https://static.clan.lol/videos/munixvelooooren.mp4&#34;
    type=&#34;video/mp4&#34;
  /&gt;
  &lt;source
    src=&#34;https://static.clan.lol/videos/munixvelooooren.ove&#34;
    type=&#34;video/ove&#34;
  /&gt;
&lt;/video&gt;

&lt;p&gt;In the Beginning (of virtio-gpu), there was the Framebuffer. An emulated computer monitor, a single rectangle representing the entire graphical output of the VM. Then there was &lt;a href=&#34;https://docs.mesa3d.org/drivers/virgl.html&#34;&gt;VirGL&lt;/a&gt;, a way to forward the OpenGL API across the VM boundary to make the host render on its GPU on behalf of the VM, so that 3D graphics could be displayed on the emulated monitor. It wasn&amp;rsquo;t super fast, it wasn&amp;rsquo;t compatible with the latest GL extensions, it wasn&amp;rsquo;t very secure, but it was something. With the advent of Vulkan, &lt;a href=&#34;https://docs.mesa3d.org/drivers/venus.html&#34;&gt;Venus&lt;/a&gt; was started as the Vulkan version of the same thing.&lt;/p&gt;
&lt;p&gt;Meanwhile, the Chrome OS team was working on adding support for Linux apps. While it was initially based on namespaces, they quickly started working on switching to hardware virtualization. The virtio-gpu device was extended to support arbitrary &amp;ldquo;cross-domain&amp;rdquo; protocols, making it possible —with some wrapping-unwrapping— to forward Unix domain sockets that pass certain types of file descriptors (shared memory and DMA-BUF) to the guest. (Well, initially it was a whole separate virtual device but let&amp;rsquo;s skip over that.) Google&amp;rsquo;s crosvm supports &lt;a href=&#34;https://crosvm.dev/book/devices/wayland.html&#34;&gt;connecting to the host Wayland socket&lt;/a&gt; to that facility, and the team wrote &lt;a href=&#34;https://chromium.googlesource.com/chromiumos/platform2/+/refs/heads/main/vm_tools/sommelier/README.md&#34;&gt;Sommelier&lt;/a&gt; as the guest-side proxy that exposes a normal Wayland socket to guest apps.&lt;/p&gt;
&lt;p&gt;The part of crosvm responsible for handling the virtio-gpu device was written as a reusable library called Rutabaga (now &lt;a href=&#34;https://github.com/magma-gpu/rutabaga_gfx&#34;&gt;living outside of the CrOS repos&lt;/a&gt;), and integrated into other VMMs such as good old Qemu. Sommelier was packaged by various Linux distros as well, and one enthusiast wrote &lt;a href=&#34;https://roscidus.com/blog/blog/2021/03/07/qubes-lite-with-kvm-and-wayland/&#34;&gt;an entire alternative to Sommelier&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Meanwhile, there was also a lot to improve in terms of accessing the GPU. As we&amp;rsquo;ve mentioned already, API forwarding solutions like VirGL/Venus leave a lot to be desired. PCIe passthrough requires a dedicated GPU, or SR-IOV support which GPU vendors have mostly restricted to enterprise models. However… of course a better way was possible! Rob Clark presented &lt;a href=&#34;https://indico.freedesktop.org/event/2/contributions/53/attachments/76/121/XDC2022_%20virtgpu%20drm%20native%20context.pdf&#34;&gt;DRM native contexts&lt;/a&gt; at XDC 2022: this approach essentially paravirtualizes the kernel-space GPU driver, letting the guest submit hardware-specific commands that the host would run in separate contexts (relying on the same separation as between programs on the host). That&amp;rsquo;s the approach that was picked up by the Asahi Linux project for gaming because of the amazing performance it allows for, but it&amp;rsquo;s also intended to be a stronger security boundary due to providing way less attack surface on the host (it&amp;rsquo;s all I/O management rather than implementing complex APIs).&lt;/p&gt;
&lt;p&gt;So, was it possible to take all of this technology and use it? Well… it required quite a bit of debugging and fixing everywhere – but that&amp;rsquo;s exactly why I joined! So far I&amp;rsquo;ve discovered that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the rutabaga_gfx integration in QEMU (which was the thing we tried to use initially) and other C consumers was broken with the latest versions due to &lt;a href=&#34;https://issuetracker.google.com/issues/440386997&#34;&gt;an &lt;code&gt;ifdef&lt;/code&gt; mistake&lt;/a&gt; (&lt;a href=&#34;https://github.com/magma-gpu/rutabaga_gfx/pull/9&#34;&gt;fixed&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;it&amp;rsquo;s not documented everywhere that &lt;a href=&#34;https://gitlab.com/qemu-project/qemu/-/issues/2574&#34;&gt;kernel &amp;gt;= 6.13 is required&lt;/a&gt; to be able to touch AMD GPU memory from KVM guests in any way&lt;/li&gt;
&lt;li&gt;Sommelier was &lt;a href=&#34;https://issuetracker.google.com/u/2/issues/441537635&#34;&gt;assuming Chromium OS kernel patches&lt;/a&gt; and misinterpreting &lt;code&gt;ioctl&lt;/code&gt; responses on regular mainline Linux&lt;/li&gt;
&lt;li&gt;libkrun&amp;rsquo;s internal version of rutabaga_gfx contained a tiny strange API modification incompatible with Sommelier/proxy-virtwl and didn&amp;rsquo;t handle memfd seals (&lt;a href=&#34;https://github.com/containers/libkrun/pull/407&#34;&gt;fixed&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;RADV (Radeon Vulkan driver in Mesa) only recognized PCI devices including for virtgpu, ignoring the &lt;code&gt;virtio-mmio&lt;/code&gt; setup used by libkrun (&lt;a href=&#34;https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/37281&#34;&gt;fixed&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And we&amp;rsquo;re continuing with more work in this area.&lt;/p&gt;
&lt;h2 id=&#34;d-bus--xdg-desktop-portals&#34;&gt;D-Bus / XDG Desktop Portals&lt;/h2&gt;
&lt;video width=&#34;100%&#34; loop muted autoplay&gt;
  &lt;source
    src=&#34;https://static.clan.lol/videos/munixportal.webm&#34;
    type=&#34;video/webm&#34;
  /&gt;
  &lt;source
    src=&#34;https://static.clan.lol/videos/munixportal.mp4&#34;
    type=&#34;video/mp4&#34;
  /&gt;
  &lt;source
    src=&#34;https://static.clan.lol/videos/munixportal.ove&#34;
    type=&#34;video/ove&#34;
  /&gt;
&lt;/video&gt;

&lt;p&gt;Application isolation is great, but completely isolated applications tend to have limited usefulness. That&amp;rsquo;s why we&amp;rsquo;re also integrating &lt;a href=&#34;https://flatpak.github.io/xdg-desktop-portal/&#34;&gt;desktop portals&lt;/a&gt; that Flatpak uses —at least the file-opening / document portal— into the microVM-based platform.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&#34;https://git.clan.lol/clan/sidebus&#34;&gt;sidebus&lt;/a&gt; project is inspired by &lt;a href=&#34;https://spectrum-os.org/&#34;&gt;Spectrum&lt;/a&gt;&amp;rsquo;s setup for using the document portal with virtiofs to dynamically expose chosen files to the guest, using vsock as the D-Bus transport. It is based on the &lt;a href=&#34;https://github.com/dbus2/busd&#34;&gt;busd&lt;/a&gt; broker library, and uses the portal frontend on the host for perfect integration with arbitrary desktop environments.&lt;/p&gt;
&lt;p&gt;With the switch to libkrun however, we are looking at the possibility of making the Camera and Screencast portals working, with full hardware acceleration – by switching to virtgpu cross-domain as the transport instead of vsock. Currently libkrun already has added some PipeWire support to its copy of rutabaga_gfx, however that&amp;rsquo;s fixed to one system-wide socket. How these portals work is that for every request they pass a new restricted PipeWire remote socket over D-Bus. So we&amp;rsquo;re looking to make rutabaga&amp;rsquo;s cross-domain sockets more generic, to be able to just pass through that whole chain of file descriptor passing.&lt;/p&gt;
&lt;p&gt;(And yes, lots of people are worried about PipeWire attack surface — it&amp;rsquo;s definitely possible to mitigate that with a proxy on the host that would only allow a small validated subset of the PipeWire protocol.)&lt;/p&gt;
&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;We&amp;rsquo;re looking to finally make a peer-to-peer community software platform that&amp;rsquo;s competitive with commercial ones in terms of security, usability and convenience.
If you want to try it out now, you can! Just follow the installation instructions on our &lt;a href=&#34;https://git.clan.lol/clan/munix&#34;&gt;munix project&lt;/a&gt;.
Note that it&amp;rsquo;s still actively being developed, so if you find any issues, please open up a bug report!&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>The Road Towards a NixOS UI</title>
      <link>https://clan.lol/blog/road-towards-nixos-ui/</link>
      <pubDate>Mon, 22 Sep 2025 10:08:10 +0200</pubDate>
      
      <guid>https://clan.lol/blog/road-towards-nixos-ui/</guid>
      <description>&lt;p&gt;We recently attended &lt;a href=&#34;https://2025.nixcon.org/&#34;&gt;NixCon 2025&lt;/a&gt; in Switzerland 🇨🇭 where we provided an &lt;strong&gt;early look&lt;/strong&gt; at our shiny new user interface for interacting with your Clan &amp;#x1f389;&amp;#x1f389;.&lt;/p&gt;
&lt;figure&gt;&lt;img src=&#34;https://clan.lol/blog/road-towards-nixos-ui/screenshot.png&#34;
alt=&#34;UI 2.0&#34;&gt;&lt;figcaption&gt;
    &lt;p&gt;UI 2.0&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Featuring &lt;strong&gt;new and improved workflows&lt;/strong&gt;, a &lt;strong&gt;fresh set of components&lt;/strong&gt; and a &lt;strong&gt;3D-rendered orthographic overview&lt;/strong&gt;, the latest version of the UI not only looks better, but it &lt;strong&gt;feels better&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Whilst it&amp;rsquo;s still a work in progress,
we&amp;rsquo;re excited to start sharing it with the community and start getting your feedback.&lt;/p&gt;
&lt;p&gt;If you came across our booth &amp;#x1f528;&amp;#x1f528;&amp;#x1f528; you might have already seen it in action.&lt;/p&gt;
&lt;figure&gt;&lt;img src=&#34;https://clan.lol/blog/road-towards-nixos-ui/booth-hammers.jpg&#34;
alt=&#34;Yes, those are real hammers&amp;hellip;&#34;&gt;&lt;figcaption&gt;
    &lt;p&gt;Yes, those are real hammers&amp;hellip;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;a href=&#34;https://kenji.page/&#34;&gt;@Kenji&lt;/a&gt; and &lt;a href=&#34;https://github.com/qubasa&#34;&gt;@Qubasa&lt;/a&gt; also gave a talk, &lt;em&gt;The Road Towards a NixOS UI&lt;/em&gt;, in which they gave a demo of the new UI in
action and explained how it was made possible thanks to the many building blocks we have been developing over the past
few years.&lt;/p&gt;
&lt;p&gt;For those who couldn&amp;rsquo;t make it to Rapperwsil, you can watch a recording of the talk below.&lt;/p&gt;
&lt;video width=&#34;100%&#34; controls poster=&#34;https://static.clan.lol/videos/nixcon-2025.jpg&#34;&gt;
  &lt;source
    src=&#34;https://static.clan.lol/videos/nixcon-2025.webm&#34;
    type=&#34;video/webm&#34;
  /&gt;
  &lt;source
    src=&#34;https://static.clan.lol/videos/nixcon-2025.mp4&#34;
    type=&#34;video/mp4&#34;
  /&gt;
  &lt;source
    src=&#34;https://static.clan.lol/videos/nixcon-2025.ove&#34;
    type=&#34;video/ove&#34;
  /&gt;
&lt;/video&gt;

&lt;p&gt;If you would like to try Clan for yourself, have a look at our &lt;a href=&#34;https://docs.clan.lol/&#34;&gt;docs&lt;/a&gt;.
To contribute, check out our &lt;a href=&#34;https://git.clan.lol/clan/clan-core&#34;&gt;gitea&lt;/a&gt;.
And if you need help or just want to chat, come say hi &amp;#x1f44b; in our &lt;a href=&#34;https://matrix.to/#/#clan:clan.lol&#34;&gt;matrix channel&lt;/a&gt;.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Why NixOS Modules Need Two JSON Schemas</title>
      <link>https://clan.lol/blog/json-schema-two/</link>
      <pubDate>Thu, 18 Sep 2025 09:11:39 +0200</pubDate>
      
      <guid>https://clan.lol/blog/json-schema-two/</guid>
      <description>&lt;h2 id=&#34;why-nixos-modules-need-two-json-schemas&#34;&gt;Why NixOS Modules Need Two JSON Schemas&lt;/h2&gt;
&lt;p&gt;In our previous blog posts, we explored a techinque that extracts interfaces from NixOS modules and converts them into JSON schemas. This enables building GUIs and APIs that can shallowly validate NixOS configurations without running Nix itself.
Our second post explored the broader challenge of maintaining type-safe interfaces across polyglot architectures - specifically how to share consistent data models between Nix, Python, and TypeScript in the Clan project&amp;rsquo;s tech stack.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://clan.lol/blog/json-schema-converter/&#34;&gt;Introducing the NixOS to JSON Schema Converter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://clan.lol/blog/interfaces/&#34;&gt;The Challenge of Polyglot Architectures&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;the-new-discovery&#34;&gt;The New Discovery&lt;/h2&gt;
&lt;p&gt;While implementing these JSON schema-based types/interfaces in production, we discovered a subtle but important issue: we actually need two different JSON schemas for each NixOS module, not just one.&lt;/p&gt;
&lt;p&gt;When using JSON Schema for type validation at build-time and runtime, the approach proved surprisingly stable for building our tech stack.
However, we noticed subtle usage differences that create unnecessary complexity.&lt;/p&gt;
&lt;p&gt;The issue stems from how the NixOS module system behaves as a transformation layer.
While one might expect that input configuration equals output configuration (since the module system is a fixed point),
there&amp;rsquo;s a notable difference that causes unnecessary type guarding when consuming or producing data.&lt;/p&gt;
&lt;p&gt;An example:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   options&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;foo &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; mkOption {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       type &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; types&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;anything;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       default &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;42&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;(Types in &lt;a href=&#34;https://cuelang.org/docs/tour/types/structs/&#34;&gt;CUE lang&lt;/a&gt; )&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;Schema&lt;/th&gt;
          &lt;th&gt;Purpose&lt;/th&gt;
          &lt;th&gt;Example&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;Write&lt;/td&gt;
          &lt;td&gt;What user/program provides&lt;/td&gt;
          &lt;td&gt;&lt;code&gt;foo?: _&lt;/code&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Read&lt;/td&gt;
          &lt;td&gt;What module system guarantees&lt;/td&gt;
          &lt;td&gt;&lt;code&gt;foo: _&lt;/code&gt;&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;This difference is inherent because the module system applies &lt;em&gt;defaults&lt;/em&gt;, &lt;em&gt;merges&lt;/em&gt;, does &lt;em&gt;coercions&lt;/em&gt;, and &lt;em&gt;normalization&lt;/em&gt;. That turns a potentially partial configuration into a total one.&lt;/p&gt;
&lt;p&gt;This subtle difference causes unnecessary type guards in our Python code currently.&lt;/p&gt;
&lt;p&gt;For example&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;foo: Foo &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; read_data()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# Unnecessary typeguard&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# Because foo cannot be be None in the `read`-schema&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; foo &lt;span style=&#34;color:#f92672&#34;&gt;is&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;not&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;None&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     &lt;span style=&#34;color:#75715e&#34;&gt;# Do something with foo&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     &lt;span style=&#34;color:#66d9ef&#34;&gt;pass&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The same goes on for the whole stack. I.e the Typescript frontend also suffers from the same problem.
As well as everything that calls the API&amp;rsquo;s in a typesafe manner.&lt;/p&gt;
&lt;p&gt;We use only the &lt;em&gt;write&lt;/em&gt; format for everything. which is safe because it is possibly a supertype of the &lt;em&gt;read&lt;/em&gt; format. Supertype here just means &amp;ldquo;more permissive&amp;rdquo;, it accepts more inputs than the read schema.&lt;/p&gt;
&lt;p&gt;I need to put more research into the question of which option types are strict supertypes, which would allow us to utilize that property of the system.&lt;/p&gt;
&lt;p&gt;This is a non-trivial question because it ties into how the module system’s types work and requires verifying every option type that exists or some property about option types.&lt;/p&gt;
&lt;p&gt;Another example: &lt;code&gt;lib.types.coercedTo A to B&lt;/code&gt;, which transforms the value into another value.
This can be represented as:&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;Schema&lt;/th&gt;
          &lt;th&gt;Purpose&lt;/th&gt;
          &lt;th&gt;Example&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;Write&lt;/td&gt;
          &lt;td&gt;What user/program provides&lt;/td&gt;
          &lt;td&gt;&lt;code&gt;foo?: A | B&lt;/code&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Read&lt;/td&gt;
          &lt;td&gt;What module system guarantees&lt;/td&gt;
          &lt;td&gt;&lt;code&gt;foo: B&lt;/code&gt;&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;In type-theory terms, this means the &lt;code&gt;read&lt;/code&gt; schema is a subtype of the &lt;code&gt;write&lt;/code&gt; schema. In practice, this gives us two big wins.&lt;/p&gt;
&lt;p&gt;We could either formally prove this for all types, or restrict our usage such that this is always true, so we gain the following:&lt;/p&gt;
&lt;h2 id=&#34;a-safe-round-tripping&#34;&gt;(a) Safe round-tripping&lt;/h2&gt;
&lt;p&gt;Any resolved configuration (read) is &lt;em&gt;also valid input&lt;/em&gt; (write).
That means we can:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;serialize the &lt;em&gt;read&lt;/em&gt; config,&lt;/li&gt;
&lt;li&gt;feed it back into the module system as input,&lt;/li&gt;
&lt;li&gt;and get the &lt;em&gt;same&lt;/em&gt; or &lt;em&gt;equivalent&lt;/em&gt; resolved config.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is valuable for reproducibility, migration tooling, and debugging.&lt;/p&gt;
&lt;p&gt;If &lt;code&gt;read&lt;/code&gt; were not a subtype of &lt;code&gt;write&lt;/code&gt;, round-tripping would break. We could resolve a config, but not be able to reapply it as input without errors.&lt;/p&gt;
&lt;h2 id=&#34;b-compatibility--stability-checks&#34;&gt;(b) Compatibility &amp;amp; stability checks&lt;/h2&gt;
&lt;p&gt;We can turn &lt;code&gt;read ⊑ write&lt;/code&gt; into a &lt;strong&gt;CI invariant&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If we change module options, we check whether the &lt;em&gt;new read schema&lt;/em&gt; is still a subtype of the &lt;em&gt;old write schema&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;If yes; no breaking change for existing configs.&lt;/li&gt;
&lt;li&gt;If no; flag it as breaking change.
That&amp;rsquo;s a strong formal guarantee that configuration evolution won&amp;rsquo;t silently invalidate existing setups. This is a possible future idea.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;what-if-not-a-subtype&#34;&gt;What if not a subtype?&lt;/h2&gt;
&lt;p&gt;If &lt;code&gt;read&lt;/code&gt; is &lt;em&gt;not&lt;/em&gt; a subtype of &lt;code&gt;write&lt;/code&gt;, then:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Developers are still safe, they always rely on &lt;code&gt;read&lt;/code&gt; or &lt;code&gt;write&lt;/code&gt; only.&lt;/li&gt;
&lt;li&gt;But we lose our important &lt;strong&gt;round-tripping&lt;/strong&gt;: some values you get from the system can&amp;rsquo;t be expressed as valid inputs.&lt;/li&gt;
&lt;li&gt;We also lose version compatibility checks: breaking schema evolution can&amp;rsquo;t be detected so easily anymore.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In practice, that would mean more ad-hoc checks and possibly more subtle breakages when integrating different stacks.&lt;/p&gt;
&lt;h2 id=&#34;practical-takeaway&#34;&gt;Practical takeaway&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;We should start exporting two schemas, to more closely represent the nature of the system:
&lt;ul&gt;
&lt;li&gt;Ensure that all consumers see consistent, reliable types, that closely represent the data.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Always use read schema for reading; For Python developers this means they can drop some type guards and possibly other noise.&lt;/li&gt;
&lt;li&gt;If we enforce &lt;code&gt;read ⊑ write&lt;/code&gt; we gain important ergonomics:
&lt;ul&gt;
&lt;li&gt;We can safely serialize/reserialize configs (roundtrip property).&lt;/li&gt;
&lt;li&gt;We get a cheap and strong compatibility invariant for free.&lt;/li&gt;
&lt;li&gt;We can trust read values as valid inputs everywhere.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;outlook&#34;&gt;Outlook&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;types.deferredModule&lt;/code&gt; breaks with the round-tripping property entirely.&lt;/p&gt;
&lt;p&gt;But in clan we use these in a couple of api-facing places. In the next post, I&amp;rsquo;ll dive into this case and show how I built a solution to restore those properties and make deferred modules api-usable.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Clan &#43; macOS</title>
      <link>https://clan.lol/blog/macos/</link>
      <pubDate>Tue, 09 Sep 2025 16:44:44 +0200</pubDate>
      
      <guid>https://clan.lol/blog/macos/</guid>
      <description>&lt;p&gt;Over the last year, we&amp;rsquo;ve been progressively adding macOS support to Clan. This began with sticking a M4 Mac mini in a basement to run all of our builds and test suite. In the beginning, we couldn&amp;rsquo;t manage this Mac using Clan so we created a repo separate to &lt;code&gt;clan-infra&lt;/code&gt; and used nix-darwin directly to configure the machine.&lt;/p&gt;
&lt;p&gt;After a while, we started to notice that the GUI and CLI would periodically stop working on macOS as breaking changes would accumulate as the majority of the team runs Linux. So we decided that it was time to actually put the Mac mini to work by configuring it to build the GUI, CLI and all the tests on every PR to ensure they work on macOS as well.&lt;/p&gt;
&lt;p&gt;At this point, the CLI ran and worked well on macOS allowing you to deploy to NixOS machines using the &lt;code&gt;clan machines install&lt;/code&gt; and &lt;code&gt;clan machines update&lt;/code&gt; commands.&lt;/p&gt;
&lt;p&gt;However, we wanted macOS machines to be first-class Clan members, with software, settings, and secrets managed declaratively like Linux, so we began adding nix-darwin support to Clan.&lt;/p&gt;
&lt;h2 id=&#34;what-is-nix-darwin&#34;&gt;What is nix-darwin&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/nix-darwin/nix-darwin&#34;&gt;nix-darwin&lt;/a&gt; is a framework that uses the module system from NixOS to manage macOS, allowing you to bring the reproducibility and declarative power of Nix to macOS.&lt;/p&gt;
&lt;p&gt;Clan automatically imports a core set of modules into all machines to provide features like remote deployment and vars. These modules were written specifically for NixOS, but are necessary to make Clan useful on macOS and other systems so we needed to extend them.&lt;/p&gt;
&lt;h2 id=&#34;multi-os-modules&#34;&gt;multi-OS modules&lt;/h2&gt;
&lt;p&gt;The NixOS module system has been made into a library and is now used in nix-darwin, Home Manager and other Nix-based projects. To keep a module from being loaded by the wrong module system, authors should set the &lt;code&gt;_class&lt;/code&gt; attribute to &lt;code&gt;nixos&lt;/code&gt; or &lt;code&gt;darwin&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;We could extend the core Clan modules to support macOS by duplicating all the code for nix-darwin, however this would lead to a lot of extra maintainence burden. So instead we decided to make modules that support both NixOS and nix-darwin.&lt;/p&gt;
&lt;p&gt;Previously, detecting which module system you were in required hacks like &lt;code&gt;options ? virtualisation&lt;/code&gt; which works due to the &lt;code&gt;virtualisation&lt;/code&gt; option tree existing only in NixOS and not nix-darwin:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{ options&lt;span style=&#34;color:#f92672&#34;&gt;,&lt;/span&gt; config&lt;span style=&#34;color:#f92672&#34;&gt;,&lt;/span&gt; lib&lt;span style=&#34;color:#f92672&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;...&lt;/span&gt; }:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  config &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; lib&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;optionalAttrs (options &lt;span style=&#34;color:#f92672&#34;&gt;?&lt;/span&gt; virtualisation) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# NixOS specific configuration&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This hack does not work inside of &lt;code&gt;imports&lt;/code&gt; meaning you are not able to do conditional imports:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{ options&lt;span style=&#34;color:#f92672&#34;&gt;,&lt;/span&gt; config&lt;span style=&#34;color:#f92672&#34;&gt;,&lt;/span&gt; lib&lt;span style=&#34;color:#f92672&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;...&lt;/span&gt; }:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  nixosModule &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; { &lt;span style=&#34;color:#f92672&#34;&gt;...&lt;/span&gt; }: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# NixOS specific configuration&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  darwinModule &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; { &lt;span style=&#34;color:#f92672&#34;&gt;...&lt;/span&gt; }: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# nix-darwin specific configuration&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;in&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  imports &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (lib&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;optionalAttrs (options &lt;span style=&#34;color:#f92672&#34;&gt;?&lt;/span&gt; virtualisation) nixosModule)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (lib&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;optionalAttrs (options &lt;span style=&#34;color:#f92672&#34;&gt;?&lt;/span&gt; launchd) darwinModule)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  ];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  config &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# shared configuration&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This led to me making my first ever PR to improve the module system, which added &lt;code&gt;_class&lt;/code&gt; to the module arguments allowing conditional imports based on the module system class:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{ _class&lt;span style=&#34;color:#f92672&#34;&gt;,&lt;/span&gt; options&lt;span style=&#34;color:#f92672&#34;&gt;,&lt;/span&gt; config&lt;span style=&#34;color:#f92672&#34;&gt;,&lt;/span&gt; lib&lt;span style=&#34;color:#f92672&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;...&lt;/span&gt; }:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  nixosModule &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; { &lt;span style=&#34;color:#f92672&#34;&gt;...&lt;/span&gt; }: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# NixOS specific configuration&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  darwinModule &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; { &lt;span style=&#34;color:#f92672&#34;&gt;...&lt;/span&gt; }: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# nix-darwin specific configuration&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;in&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  imports &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (lib&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;optionalAttrs (_class &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;nixos&amp;#34;&lt;/span&gt;) nixosModule)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (lib&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;optionalAttrs (_class &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;darwin&amp;#34;&lt;/span&gt;) darwinModule)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  ];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  config &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# shared configuration&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This means you can now write modules that support multiple module systems without needing to rely on hacks.&lt;/p&gt;
&lt;p&gt;Using this new feature, we have added vars and deployment support to nix-darwin machines managed by Clan.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;d like to try it out, you can check out our &lt;a href=&#34;https://docs.clan.lol/main/guides/macos/&#34;&gt;macOS guide&lt;/a&gt;.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Networking</title>
      <link>https://clan.lol/blog/networking/</link>
      <pubDate>Thu, 28 Aug 2025 00:15:00 +0200</pubDate>
      
      <guid>https://clan.lol/blog/networking/</guid>
      <description>&lt;p&gt;(For a more dry and technical explanation, check out the networking docs at &lt;a href=&#34;https://docs.clan.lol/main/guides/networking/networking/&#34;&gt;https://docs.clan.lol/main/guides/networking/networking/&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;Every now and then people ask me, what is the best overlay network/VPN? And each time I give them a long and sad answer: that all of them have different tradeoffs and there&amp;rsquo;s no one size that fits all.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s a quick idea of what I mean:&lt;/p&gt;
&lt;p&gt;(This table is subjective, but is here to make a point)&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;VPN&lt;/th&gt;
          &lt;th&gt;Pros&lt;/th&gt;
          &lt;th&gt;Cons&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;tailscale&lt;/td&gt;
          &lt;td&gt;works pretty well&lt;/td&gt;
          &lt;td&gt;single point of failure, controller needs to be online&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;tinc&lt;/td&gt;
          &lt;td&gt;no central server needed, local peer discovery&lt;/td&gt;
          &lt;td&gt;sometimes unreliable, questionable maintenance&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;zerotier&lt;/td&gt;
          &lt;td&gt;no public server needed, continues working if controller is down&lt;/td&gt;
          &lt;td&gt;BSL, single point of failure (controller), relying on 3rd party servers&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;tor&lt;/td&gt;
          &lt;td&gt;no central server needed, anonymous (probably)&lt;/td&gt;
          &lt;td&gt;no UDP, slow&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;mycelium&lt;/td&gt;
          &lt;td&gt;no central server needed&lt;/td&gt;
          &lt;td&gt;unreliable&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;yggdrasil&lt;/td&gt;
          &lt;td&gt;reliable (if setup right)&lt;/td&gt;
          &lt;td&gt;hard to setup, no local peer discovery&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;wireguard&lt;/td&gt;
          &lt;td&gt;fast, native kernel support&lt;/td&gt;
          &lt;td&gt;no auto peering, no TCP fallback&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;There are some more VPN solutions that I haven&amp;rsquo;t tried yet, but there is no ultimate solution and each comes with certain pros and cons. For this reason we have built a network abstraction into Clan that will choose the best network for all the actions you want to run on a remote machine.&lt;/p&gt;
&lt;h2 id=&#34;how-it-works&#34;&gt;How it works&lt;/h2&gt;
&lt;p&gt;First, you must enable a network-capable Clan Service. At the time of writing, there are only two (because I&amp;rsquo;ve been too lazy to add more): &lt;code&gt;tor&lt;/code&gt; and an &lt;code&gt;internet&lt;/code&gt; service. The &lt;code&gt;tor&lt;/code&gt; service connects through the Tor network to a hidden service running on the target machine. The &lt;code&gt;internet&lt;/code&gt; service requires manual configuration of IP addresses.&lt;/p&gt;
&lt;p&gt;Lets look at an example:&lt;/p&gt;
&lt;div class=&#34;highlight&#34; file=&#34;flake.nix&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  inventory&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;instances &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    tor &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      roles&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;server&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;tags&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;nixos &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {};
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This would enable the &lt;code&gt;tor&lt;/code&gt; service on all &lt;code&gt;nixos&lt;/code&gt; machines.&lt;/p&gt;
&lt;p&gt;A network is then exposed with a priority and an attribute set of peers into the global flake, which you can examine with the following command:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;clan &lt;span style=&#34;color:#66d9ef&#34;&gt;select&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;clan.exports.instances.*.networking.{priority,peers}&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The output should look something like this:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;{
  &amp;#34;blabla&amp;#34;: {}
  &amp;#34;tor&amp;#34;: {
    &amp;#34;priority&amp;#34;: 10,
    &amp;#34;peers&amp;#34;: {
      &amp;#34;my_cool_machine&amp;#34;: {
        SSHOptions&amp;#34;: [],
        &amp;#34;host&amp;#34;: {
          &amp;#34;var&amp;#34;: {
            &amp;#34;file&amp;#34;: &amp;#34;hostname&amp;#34;,
            &amp;#34;generator&amp;#34;: &amp;#34;tor_tor&amp;#34;,
            &amp;#34;machine&amp;#34;: &amp;#34;my_cool_machine&amp;#34;
          }
        },
        &amp;#34;name&amp;#34;: &amp;#34;my_cool_machine&amp;#34;
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You can see the default priority of &lt;code&gt;10&lt;/code&gt; and a list of peers corresponding to your machines.&lt;/p&gt;
&lt;p&gt;A &lt;code&gt;host&lt;/code&gt; entry can either be a plain value or a &lt;code&gt;var&lt;/code&gt; (as above) which gets decrypted on demand when used. We do this to prevent our Tor hidden service hostnames from being exposed publicly&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; If you install a machine after enabling the &lt;code&gt;tor&lt;/code&gt; service you will have to manually update the machine with &lt;code&gt;--target-host&lt;/code&gt; one last time, after which the &lt;code&gt;tor&lt;/code&gt; vars for that machine will to be generated and deployed.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;You can try out the new networking with:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;clan ssh my_cool_machine
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This should log you into the target machine via Tor (or something faster if you have another network configured). If you have no Tor daemon running on your system, it will start one for the duration of the connection (thats pretty rad).&lt;/p&gt;
&lt;p&gt;The JSON output from the &lt;code&gt;clan select&lt;/code&gt; command earlier is a bit annoying to type and the output a bit hard to read, so for this reason we built a nicer interface for interacting with networks:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;clan network list
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This prints a list of all the networks you have configured. For our test case it would look like this:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Network       Priority  Module      Running   Peers
---------------------------------------------------
tor           10        tor         No        my_cool_machine
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;(This is only cool with multiple networks).&lt;/p&gt;
&lt;p&gt;We can now have multiple networks configured and let the Clan cli decide on the best way to reach our machines via those networks from our admin machine.&lt;/p&gt;
&lt;h2 id=&#34;what-is-still-missing&#34;&gt;What is still missing?&lt;/h2&gt;
&lt;p&gt;Sadly, a lot. Here is a short list of the top of my head:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Currently only works for &lt;code&gt;admin -&amp;gt; machine&lt;/code&gt; connections. Ideally we want them for &lt;code&gt;machine &amp;lt;-&amp;gt; machine&lt;/code&gt; connections as well&lt;/li&gt;
&lt;li&gt;More network modules like mycelium and zerotier&lt;/li&gt;
&lt;li&gt;Userspace networking, like the Tor daemon with on-demand starting. We want to have the same behaviour for all kinds of networks.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There&amp;rsquo;s likely more features I can&amp;rsquo;t think of which are missing, so stay tuned for more networking related blogposts in the future :)&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>nix-unit</title>
      <link>https://clan.lol/blog/nix-unit/</link>
      <pubDate>Tue, 08 Jul 2025 08:00:00 +0000</pubDate>
      
      <guid>https://clan.lol/blog/nix-unit/</guid>
      <description>&lt;p&gt;When building robust Nix-based systems, testing your evaluation logic becomes crucial. Especially since the module system is flexible and can be used in many ways. One of the ways we test our Nix logic is &lt;code&gt;nix-unit&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&#34;what-is-nix-unit&#34;&gt;What is nix-unit?&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/nix-community/nix-unit&#34;&gt;nix-unit&lt;/a&gt; is a testing framework specifically designed for testing Nix expressions. It&amp;rsquo;s design makes it ideal to test pure functions, module system configurations and even more complex evaluation logic. It scales well and is fast.&lt;/p&gt;
&lt;p&gt;Some notable features:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Fast feedback loops&lt;/strong&gt;:
Tests run at evaluation time, not build time.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pure function testing&lt;/strong&gt;:
Perfect for testing Nix library functions.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Assertion Based Testing&lt;/strong&gt;:
The expression that is expected can be directly declared in Nix.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Diff Integration&lt;/strong&gt;
Diff integration on error failure makes spotting errors very quick.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Testing errors&lt;/strong&gt;:
Is able to tests and assert on evaluation errors.
It can test failures individually, even if the failure is caused by an evaluation error.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The above features are some of the reasons we use it to test our infrastructure on various granularities. Let us look into how a basic test structure would look like.&lt;/p&gt;
&lt;h3 id=&#34;basic-test-structure&#34;&gt;Basic Test Structure&lt;/h3&gt;
&lt;p&gt;A &lt;code&gt;nix-unit&lt;/code&gt; test is a Nix attribute set, that has an expression that should be tested (expr) and it&amp;rsquo;s expected outcome (expected). The name of the attribute set should be prefixed with &lt;code&gt;test&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  test_answer &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    expr &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; builtins&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;add &lt;span style=&#34;color:#ae81ff&#34;&gt;40&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    expected &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;42&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We can now also test error cases.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  test_error &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    expr &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;throw&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;10 instead of 5&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    expectedError&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;type &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;ThrownError&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    expectedError&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;msg &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\\&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;d+ instead of 5&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Notice how the error message even has regex support?&lt;/p&gt;
&lt;h3 id=&#34;grouping&#34;&gt;Grouping&lt;/h3&gt;
&lt;p&gt;The expressions can be directly declared in Nix and we can test nested attribute sets. This means grouping of hierarchical tests has a good UX.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  feature_1 &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    test_bar &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      expr &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;bar&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      expected &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;bar&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  feature_2 &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    test_foo &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      expr &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;foo&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      expected &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;foo&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    test_foo_set &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      expr &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; { x &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;foo&amp;#34;&lt;/span&gt;; };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      expected &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; { x &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;foo&amp;#34;&lt;/span&gt;; };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Here we test 2 different features, one which has 1 test and one feature which has 2 tests.&lt;/p&gt;
&lt;h3 id=&#34;interactive-debugging&#34;&gt;Interactive debugging&lt;/h3&gt;
&lt;p&gt;Since &lt;code&gt;nix-unit&lt;/code&gt; tests Nix expressions in an attribute set, we can use the default Nix repl to evaluate and inspect the tests Interactively.&lt;/p&gt;
&lt;p&gt;If we have our first test in a file called &lt;code&gt;answer.nix&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-shellSession&#34; data-lang=&#34;shellSession&#34;&gt;$ nix repl
Nix 2.29.1
Type :? for help.
nix-repl&amp;gt; test = import ./answer.nix {}

nix-repl&amp;gt; test
{
  test_answer = { ... };
}

nix-repl&amp;gt; tests.test_answer.expr
42
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;a href=&#34;https://github.com/nix-community/nix-unit&#34;&gt;nix-unit&lt;/a&gt; is fast, flexible and has good UX.&lt;/p&gt;
&lt;h2 id=&#34;integration-with-ci&#34;&gt;Integration with CI&lt;/h2&gt;
&lt;p&gt;While &lt;a href=&#34;https://github.com/nix-community/nix-unit&#34;&gt;nix-unit&lt;/a&gt; on its own is a valuable tool, a project might want to integrate such functionality into it&amp;rsquo;s CI pipeline.&lt;/p&gt;
&lt;p&gt;To make this seamless we need to evaluate Nix inside the Nix build sandbox. Or in other words &lt;code&gt;nix-unit&lt;/code&gt; should be wrapped inside a derivation.&lt;/p&gt;
&lt;p&gt;This has mainly two benefits:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;When we have a Nix based CI, we can build it anywhere leveraging Nix&amp;rsquo;s determinism.&lt;/li&gt;
&lt;li&gt;We can cache the derivation if there are no changes to the test, or the base logic. Meaning we don&amp;rsquo;t need to re-run the tests.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To make this work is non-trivial and when we originally started using &lt;code&gt;nix-unit&lt;/code&gt; we hooked everything up manually.&lt;/p&gt;
&lt;p&gt;But now &lt;a href=&#34;https://github.com/nix-community/nix-unit&#34;&gt;nix-unit&lt;/a&gt; exposes a &lt;a href=&#34;https://github.com/hercules-ci/flake-parts&#34;&gt;flake-parts&lt;/a&gt; module, which does this integration for you.&lt;/p&gt;
&lt;h2 id=&#34;integration-with-flake-parts&#34;&gt;Integration with flake-parts&lt;/h2&gt;
&lt;p&gt;Why would you want to integrate with &lt;a href=&#34;https://github.com/hercules-ci/flake-parts&#34;&gt;flake-parts&lt;/a&gt;?&lt;/p&gt;
&lt;p&gt;In addition to creating a wrapped derivation of &lt;code&gt;nix-unit&lt;/code&gt;, it allows to structure your flake in a more composable way.
The tests and logic can both be part of a module, meaning the tests live where the module lives.
In a larger codebase your tests are now more discoverable and the proximity to the logic makes them easier to maintain.&lt;/p&gt;
&lt;h2 id=&#34;you-can-do-it-too&#34;&gt;You can do it too&lt;/h2&gt;
&lt;p&gt;Here are the steps required, if you want to try it out yourself:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Have a flake with &lt;a href=&#34;https://github.com/hercules-ci/flake-parts&#34;&gt;flake-parts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Add &lt;code&gt;nix-unit.url = &amp;quot;github:nix-community/nix-unit&amp;quot;;&lt;/code&gt; to your inputs&lt;/li&gt;
&lt;li&gt;Create a &lt;code&gt;tests.nix&lt;/code&gt; file with the following content:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  flake&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;tests &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    test_answer &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      expr &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; builtins&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;add &lt;span style=&#34;color:#ae81ff&#34;&gt;40&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      expected &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;42&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;Add the test file and the flake-parts module to your imports:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;imports &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    inputs&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;nix-unit&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;modules&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;flake&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;default
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;./test.nix&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And now you can test your functions and logic to your hearts content!&lt;/p&gt;
&lt;p&gt;The test will be automatically added to the &lt;code&gt;checks&lt;/code&gt; attribute running the tests when you run &lt;code&gt;nix flake check&lt;/code&gt;.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Clan &#43; Yubikeys</title>
      <link>https://clan.lol/blog/clan-and-yubikeys/</link>
      <pubDate>Fri, 16 May 2025 08:00:00 +0000</pubDate>
      
      <guid>https://clan.lol/blog/clan-and-yubikeys/</guid>
      <description>&lt;p&gt;All I wanted was to test &lt;a href=&#34;https://git.clan.lol/clan/data-mesher&#34;&gt;Data Mesher&lt;/a&gt;&amp;hellip; &amp;#x1f643;&lt;/p&gt;
&lt;p&gt;I had done some local dev testing and created a &lt;a href=&#34;https://nixos.org&#34;&gt;NixOS&lt;/a&gt; module with some &lt;a href=&#34;https://wiki.nixos.org/wiki/NixOS_VM_tests&#34;&gt;VM tests&lt;/a&gt;.
&lt;a href=&#34;https://github.com/pinpox&#34;&gt;Pinpox&lt;/a&gt; had helped write the &lt;a href=&#34;https://docs.clan.lol/main/services/official/data-mesher/&#34;&gt;Clan module&lt;/a&gt; and added some more Clan-specific VM tests.
We were ready to start testing this in a real Clan.&lt;/p&gt;
&lt;p&gt;Until this point, I wasn&amp;rsquo;t running Clan. I had always meant to convert, but just never got around to
it. So this was as good a reason as any. I fired up the docs, opened up my config and got to work.&lt;/p&gt;
&lt;p&gt;But it wasn&amp;rsquo;t long before I hit a few problems.&lt;/p&gt;
&lt;h2 id=&#34;multiple-user-keys&#34;&gt;Multiple User Keys&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;m a long-time user of &lt;a href=&#34;https://github.com/getsops/sops&#34;&gt;SOPS&lt;/a&gt; via &lt;a href=&#34;https://github.com/Mic92/sops-nix&#34;&gt;sops-nix&lt;/a&gt; for managing secrets within &lt;a href=&#34;https://nixos.org&#34;&gt;NixOS&lt;/a&gt; configurations, and I like to use it in
combination with &lt;a href=&#34;https://www.yubico.com/&#34;&gt;Yubikeys&lt;/a&gt; via &lt;a href=&#34;https://github.com/FiloSottile/age&#34;&gt;AGE&lt;/a&gt; plugins. Clan can also use &lt;a href=&#34;https://github.com/getsops/sops&#34;&gt;SOPS&lt;/a&gt; for &lt;a href=&#34;https://docs.clan.lol/main/guides/secrets/&#34;&gt;secrets management&lt;/a&gt;, but it deviates in
one important way.&lt;/p&gt;
&lt;p&gt;Unlike with &lt;a href=&#34;https://github.com/getsops/sops&#34;&gt;SOPS&lt;/a&gt; where you configure keys in a &lt;code&gt;.sops.yaml&lt;/code&gt; file, Clan takes a more &lt;em&gt;active role&lt;/em&gt; in how
&lt;a href=&#34;https://github.com/getsops/sops&#34;&gt;SOPS&lt;/a&gt; keys are configured, introducing concepts like &lt;code&gt;users&lt;/code&gt;, &lt;code&gt;groups&lt;/code&gt;, and &lt;code&gt;machines&lt;/code&gt;. And at the time, the &lt;code&gt;user&lt;/code&gt; model
didn&amp;rsquo;t allow for &lt;em&gt;more than one encryption key&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Why is this important?&lt;/p&gt;
&lt;p&gt;Well, to protect against loss or failure of a &lt;a href=&#34;https://www.yubico.com/&#34;&gt;Yubikey&lt;/a&gt;, I &lt;em&gt;use more than one&lt;/em&gt;. And, just in case they&amp;rsquo;re all lost or
broken, I also use a vanilla age key backed up on paper or in a password manager. Ordinarily, this isn&amp;rsquo;t a problem with
&lt;a href=&#34;https://github.com/getsops/sops&#34;&gt;SOPS&lt;/a&gt;. But with Clan, it was a &lt;strong&gt;non-starter&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;So I &lt;a href=&#34;https://git.clan.lol/clan/clan-core/pulls/3230&#34;&gt;added support for it&lt;/a&gt;. &amp;#x2705;&lt;/p&gt;
&lt;h2 id=&#34;age-plugins&#34;&gt;Age Plugins&lt;/h2&gt;
&lt;p&gt;Having taken care of multiple user keys, I next found myself dealing with some issues related to &lt;a href=&#34;https://github.com/FiloSottile/age&#34;&gt;AGE&lt;/a&gt; plugins.&lt;/p&gt;
&lt;p&gt;The first was easy enough to fix, ensuring the Clan CLI was loading any necessary &lt;a href=&#34;https://github.com/FiloSottile/age&#34;&gt;AGE&lt;/a&gt; plugins when decrypting with &lt;a href=&#34;https://github.com/getsops/sops&#34;&gt;SOPS&lt;/a&gt;.
I added some &lt;a href=&#34;https://git.clan.lol/clan/clan-core/src/branch/main/pkgs/clan-cli/clan_cli/nix/allowed-packages.json&#34;&gt;packages to an allowlist&lt;/a&gt;,
tweaked the shell being used when &lt;a href=&#34;https://git.clan.lol/clan/clan-core/src/commit/13fa74b8cd663d5541bb735a1d7ba5b6d91cb450/pkgs/clan-cli/clan_cli/secrets/sops.py#L195-L210&#34;&gt;invoking SOPS&lt;/a&gt;
and added an option allowing a user to specify the plugins they require in their Clan:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  inputs&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;clan-core&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;url &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;https://git.clan.lol/clan/clan-core/archive/main.tar.gz&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  inputs&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;nixpkgs&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;follows &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;clan-core/nixpkgs&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  outputs &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    { self&lt;span style=&#34;color:#f92672&#34;&gt;,&lt;/span&gt; clan-core&lt;span style=&#34;color:#f92672&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;...&lt;/span&gt; }:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      clan &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; clan-core&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;clanLib&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;buildClan {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;inherit&lt;/span&gt; self;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        meta&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;name &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;myclan&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;# Add Yubikey and FIDO2 HMAC plugins&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;# Note: the plugins listed here must be available in nixpkgs.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        secrets&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;age&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;plugins &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;age-plugin-yubikey&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;age-plugin-fido2-hmac&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        ];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        machines &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#75715e&#34;&gt;# elided for brevity&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;in&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;inherit&lt;/span&gt; (clan) nixosConfigurations clanInternals;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#75715e&#34;&gt;# elided for brevity&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;recovering-recipients&#34;&gt;Recovering Recipients&lt;/h3&gt;
&lt;p&gt;The second issue required a bit more thought, as it turns out there is no uniform mechanism for recovering the
recipient (public key) from a secret key generated by an &lt;a href=&#34;https://github.com/FiloSottile/age&#34;&gt;AGE&lt;/a&gt; plugin.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s important we can do this because the Clan CLI implements a safety check when creating or updating a secret.
If the recipient associated with the currently configured &lt;a href=&#34;https://github.com/FiloSottile/age&#34;&gt;AGE&lt;/a&gt; private key is not in the list of recipients when
encrypting a secret, we &lt;em&gt;add that recipient to the list&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;This helps ensure you &lt;strong&gt;do not encrypt a secret which you cannot decrypt&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;With vanilla &lt;a href=&#34;https://github.com/FiloSottile/age&#34;&gt;AGE&lt;/a&gt; keys this isn&amp;rsquo;t a problem, since you can recover the recipient with a simple &lt;code&gt;age-keygen -y&lt;/code&gt;.
But for &lt;a href=&#34;https://github.com/FiloSottile/age&#34;&gt;AGE&lt;/a&gt; plugins, we ended up with a workaround in which a user needs to &lt;a href=&#34;https://docs.clan.lol/main/guides/vars/sops/age-plugins/#using-plugin-generated-keys&#34;&gt;prepend a comment with the recipient key&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For example, this is what your &lt;code&gt;~/.config/sops/age/keys.txt&lt;/code&gt; might look like:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;# public key: age1zdy49ek6z60q9r34vf5mmzkx6u43pr9haqdh5lqdg7fh5tpwlfwqea356l
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;AGE-PLUGIN-FIDO2-HMAC-1QQPQZRFR7ZZ2WCV...
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;For each private key in this file,
we look at the preceding line for a comment containing a recipient of the form &lt;code&gt;age1xxxx&lt;/code&gt;.
The rest of the line doesn&amp;rsquo;t really matter.
You can prefix it with &lt;code&gt;public key:&lt;/code&gt;, &lt;code&gt;recipient:&lt;/code&gt; or whatever else.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://git.clan.lol/clan/clan-core/pulls/3322&#34;&gt;AGE plugin support&lt;/a&gt;: &amp;#x2705;&lt;/p&gt;
&lt;h2 id=&#34;success&#34;&gt;Success?&lt;/h2&gt;
&lt;p&gt;By this point, I began porting my configuration over to a new repo I had set up for Clan.
I added an initial user key based on &lt;a href=&#34;https://github.com/olastor/age-plugin-fido2-hmac&#34;&gt;age-plugin-fido2-hmac&lt;/a&gt; and began fleshing out the configuration for my
main desktop machine.&lt;/p&gt;
&lt;p&gt;After a while, I remembered that I should add a couple more keys to my user, including my backup key.
No big deal, just a simple &lt;code&gt;clan secrets users add-key brian age1xxxxxxx&lt;/code&gt; right?&lt;/p&gt;
&lt;figure&gt;&lt;img src=&#34;https://clan.lol/blog/clan-and-yubikeys/pain.jpg&#34;
alt=&#34;It just keeps asking for my PIN&amp;hellip;&#34;&gt;&lt;figcaption&gt;
    &lt;p&gt;It just keeps asking for my PIN&amp;hellip;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&#34;vars--age-plugin-fido2-hmac--pain&#34;&gt;Vars + age-plugin-fido2-hmac = Pain&lt;/h2&gt;
&lt;p&gt;When adding a new user key, under the hood the Clan CLI is making a call to &lt;code&gt;sops updatekeys&lt;/code&gt;.
As part of the &lt;code&gt;updatekeys&lt;/code&gt; process, &lt;a href=&#34;https://github.com/getsops/sops&#34;&gt;sops&lt;/a&gt; needs to decrypt the underlying value to then encrypt it for the key we are
adding.
Nothing unusual so far.&lt;/p&gt;
&lt;p&gt;The sting in the tail comes when you understand how &lt;a href=&#34;https://clan.lol/blog/vars&#34;&gt;Vars&lt;/a&gt; organises secrets on the filesystem: &lt;strong&gt;one file per secret&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;This is in stark contrast to how I had been using &lt;a href=&#34;https://github.com/getsops/sops&#34;&gt;sops&lt;/a&gt; previously, where I typically grouped a number of secrets into
the same file on a per-host or a per-user basis.
But where&amp;rsquo;s the problem you might be asking?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&#34;https://github.com/olastor/age-plugin-fido2-hmac&#34;&gt;age-plugin-fido2-hmac&lt;/a&gt; has no form of PIN caching&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;For each decryption I have to &lt;em&gt;enter my PIN and touch my Yubikey&lt;/em&gt;.
And as you can imagine, any non-trivial setup is going to have more than a handful of secrets.
When you also consider this needs to be done for shared secrets as well as for each machine&amp;rsquo;s secrets &amp;#x1f622;&amp;hellip;&lt;/p&gt;
&lt;figure&gt;&lt;img src=&#34;https://media2.giphy.com/media/v1.Y2lkPTc5MGI3NjExM2c2am42ZTF6azBsNjJzbW5ldWFlNGZjcDdsczVqNWFhZm82NTNrZyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/7NoNw4pMNTvgc/giphy.gif&#34;
alt=&#34;Actual footage of clan secrets users add-key brian age1....&#34;&gt;&lt;figcaption&gt;
    &lt;p&gt;Actual footage of &lt;code&gt;clan secrets users add-key brian age1....&lt;/code&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;If I had stayed with &lt;a href=&#34;https://github.com/olastor/age-plugin-fido2-hmac&#34;&gt;age-plugin-fido2-hmac&lt;/a&gt;, adding a new user key would have been prohibitively expensive and a short
path to some form of repetitive strain injury.
I &lt;em&gt;could&lt;/em&gt; have used a key which didn&amp;rsquo;t require PIN entry, but I wasn&amp;rsquo;t comfortable with that.&lt;/p&gt;
&lt;p&gt;In the end, I moved to &lt;a href=&#34;https://github.com/str4d/age-plugin-yubikey&#34;&gt;age-plugin-yubikey&lt;/a&gt;, as it supports PIN caching and can also be configured to cache
presence checks (up to 15 seconds).
There is still an issue with &lt;a href=&#34;https://linux.die.net/man/8/pcscd&#34;&gt;pcscd&lt;/a&gt; aggressively &lt;a href=&#34;https://github.com/str4d/age-plugin-yubikey/issues/198&#34;&gt;powering off my yubikey&lt;/a&gt;
after 5 seconds and ruining the PIN caching, but for day-to-day usage with Clan it works well enough for now.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;PIN caching:&lt;/strong&gt; &amp;#x2705;&lt;/p&gt;
&lt;h2 id=&#34;terminal-multiplexing&#34;&gt;Terminal Multiplexing&lt;/h2&gt;
&lt;p&gt;By this point, having side-quested long enough, I was feeling like I was on the home straight.
But there was one last curveball, which at the time of writing, we still don&amp;rsquo;t have a good solution for: &lt;strong&gt;PIN entry
when deploying to multiple machines at once.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;When you run &lt;code&gt;clan machines update&lt;/code&gt;, the Clan CLI will perform the update process concurrently for each machine in
your configuration and blend their terminal outputs into one.
This becomes a problem when each one of them may ask you to enter a PIN to unlock your &lt;a href=&#34;https://www.yubico.com/&#34;&gt;Yubikey&lt;/a&gt;.&lt;/p&gt;
&lt;figure&gt;&lt;img src=&#34;https://clan.lol/blog/clan-and-yubikeys/update.gif&#34;
alt=&#34;concurrent deployment during clan machines update&#34;&gt;&lt;figcaption&gt;
    &lt;p&gt;concurrent deployment during &lt;code&gt;clan machines update&lt;/code&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Whilst annoying and suboptimal, this &lt;em&gt;isn&amp;rsquo;t necessarily a show-stopper&lt;/em&gt; for small Clans;
it just means you have to update machines individually.&lt;/p&gt;
&lt;p&gt;Longer-term, we&amp;rsquo;re still kicking around a few ideas.
For example, it would be nice
if &lt;a href=&#34;https://github.com/FiloSottile/age&#34;&gt;age&lt;/a&gt; plugins could be configured to ask for pin entry using something like &lt;code&gt;ssh-askpass&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Concurrent PIN entry&lt;/strong&gt;: &amp;#x274c;&lt;/p&gt;
&lt;h2 id=&#34;summary&#34;&gt;Summary&lt;/h2&gt;
&lt;p&gt;It took me longer than I would have hoped to &lt;em&gt;just run &lt;a href=&#34;https://git.clan.lol/clan/data-mesher&#34;&gt;Data Mesher&lt;/a&gt; in a Clan of my own&lt;/em&gt; &amp;#x1f602;,
but along the way there have been some much-needed improvements to Clan itself and, for me personally,
I&amp;rsquo;m left with a much better understanding of &lt;a href=&#34;https://github.com/FiloSottile/age&#34;&gt;age&lt;/a&gt;, &lt;a href=&#34;https://www.yubico.com/&#34;&gt;Yubikeys&lt;/a&gt; and &lt;a href=&#34;https://github.com/getsops/sops&#34;&gt;sops&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We will continue to look at ways of improving &lt;a href=&#34;https://www.yubico.com/&#34;&gt;Yubikey&lt;/a&gt; integration with Clan.&lt;/p&gt;
&lt;p&gt;And in the meantime, please bear in mind there are still a few rough edges &amp;#x1f64f;.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>How to Democratise DevOps</title>
      <link>https://clan.lol/blog/how-to-democratize/</link>
      <pubDate>Sun, 06 Apr 2025 21:25:04 +0700</pubDate>
      
      <guid>https://clan.lol/blog/how-to-democratize/</guid>
      <description>&lt;p&gt;Computers only became mainstream when interfaces borrowed from the analog world.
Suddenly, people weren’t staring at cryptic command lines, they had “desktops”,
“folders”, and “recycle bins”. Familiar metaphors turned, that digital complexity into something intuitive. They made digital complexity feel natural.&lt;/p&gt;
&lt;p&gt;Design makes a product understandable. It slices through complexity and making things click. But what if what you’re designing is buried deep - abstract, technical and invisible to almost everyone?&lt;/p&gt;
&lt;p&gt;That is the challenge we face at Clan. How do we design infrastructure and networking concepts in a visual application so that they are not only understandable but truly usable by anyone?&lt;/p&gt;
&lt;p&gt;&lt;em&gt;To develop a further sense of legacy interfaces and their calmness visit &lt;a href=&#34;http://toastytech.com/guis/index.html&#34;&gt;toastytech.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;figure&gt;&lt;img src=&#34;https://clan.lol/blog/how-to-democratize/clan-design-history-computing.png&#34;
alt=&#34;Macintosh in 1984 and a screenshot of the Note Pad application&#34;&gt;&lt;figcaption&gt;
    &lt;p&gt;Macintosh in 1984 and a screenshot of the Note Pad application&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;A strong metaphor unlocks a mental model, which is the blueprint of every truly intuitive user experience. Because here is the truth: Without it, even the best tools feel like black boxes.&lt;/p&gt;
&lt;p&gt;So where do we find ours? To design the how, we need to understand the why. Why are we building Clan? And why do we want to democratize networking through an application?&lt;/p&gt;
&lt;figure&gt;&lt;img src=&#34;https://clan.lol/blog/how-to-democratize/clan-design-neverending-ad.png&#34;
alt=&#34;The digital world has drifted apart&#34;&gt;&lt;figcaption&gt;
    &lt;p&gt;The digital world has drifted apart&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Because the digital world has drifted off course. Our devices have become closed boxes. Platforms addict and manipulate. Social media fractures societies and AI is blurring the line between real and fake by the day.&lt;/p&gt;
&lt;p&gt;The original promise of computing was a different one. When the machines entered our homes, they invited tinkering. You had to understand how they worked. You had to code. The barrier to entry wasn’t hidden, it was the whole point.&lt;/p&gt;
&lt;figure&gt;&lt;img src=&#34;https://clan.lol/blog/how-to-democratize/clan-design-tinkering.png&#34;
alt=&#34;The age of the internet&#34;&gt;&lt;figcaption&gt;
    &lt;p&gt;The age of the internet&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Then came the internet: A wave of openness where content flowed freely. Industries were disrupted and subcultures found one another. There was magic in that beginning.&lt;/p&gt;
&lt;p&gt;Today the internet is a never-ending ad. Everything is tracked. Everyone is categorized. The internet overwhelms. The global community is a dead-end, leaving people drained, disoriented, and empty. Idealized images, outrage, surveillance, burnout.&lt;/p&gt;
&lt;p&gt;So what do we do? We take back the computer! We stop being just users, and start being makers again. Not with likes, but with ideas. With uncertainty. With a bit of beautiful chaos.&lt;/p&gt;
&lt;p&gt;A quiet revolution. From the inside.&lt;/p&gt;
&lt;figure&gt;&lt;img src=&#34;https://clan.lol/blog/how-to-democratize/clan-design-history.png&#34;
alt=&#34;A quiet revolution&#34;&gt;&lt;figcaption&gt;
    &lt;p&gt;A quiet revolution&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;We reclaim the machine. We understand it. We use its building blocks to shape spaces of our own, digital enclaves outside the reach of the algorithmic hive mind.&lt;/p&gt;
&lt;p&gt;That’s why we’re building Clan: to make room for a better digital society.&lt;/p&gt;
&lt;figure&gt;&lt;img src=&#34;https://clan.lol/blog/how-to-democratize/clan-design-future.png&#34;
alt=&#34;A better digital society&#34;&gt;&lt;figcaption&gt;
    &lt;p&gt;A better digital society&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;And here’s the metaphor at the heart of our mission: Computers are like houses. You build them. Connect them. Neighborhoods form. Villages grow. Cities emerge. Societies take shape.&lt;/p&gt;
&lt;figure&gt;&lt;img src=&#34;https://clan.lol/blog/how-to-democratize/clan-design-house.png&#34;&gt;
&lt;/figure&gt;
&lt;figure&gt;&lt;img src=&#34;https://clan.lol/blog/how-to-democratize/clan-design-sim-city.png&#34;&gt;
&lt;/figure&gt;
&lt;p&gt;Inspired by games like &lt;a href=&#34;https://www.youtube.com/watch?v=jk7BkwksgX8&amp;amp;ab_channel=SergiuHellDragoonHQ&#34;&gt;SimCity&lt;/a&gt;, we use the metaphor of a digital city to represent networks and infrastructure. But this isn’t a simulation! It’s real, deployed architecture! It’s not just about designing, it’s about actually building, managing, and maintaining technical systems.&lt;/p&gt;
&lt;p&gt;At its core, our visual tool is a network simulation game with real technical infrastructure behind it. This is how we make complex systems understandable: playfully, visually, tangibly.&lt;/p&gt;
&lt;figure&gt;&lt;img src=&#34;https://clan.lol/blog/how-to-democratize/onboarding-step-not-yet-flashed.png&#34;
alt=&#34;Clan’s Darknet Builder with already created machines&#34;&gt;&lt;figcaption&gt;
    &lt;p&gt;Clan’s Darknet Builder with already created machines&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;&lt;img src=&#34;https://clan.lol/blog/how-to-democratize/onboarding-step.png&#34;
alt=&#34;A more advanced build of a network and its respective machines&#34;&gt;&lt;figcaption&gt;
    &lt;p&gt;A more advanced build of a network and its respective machines&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;&lt;img src=&#34;https://clan.lol/blog/how-to-democratize/sidepanel-a01.png&#34;
alt=&#34;Some details of Clan’s Darknet Builder UI&#34;&gt;&lt;figcaption&gt;
    &lt;p&gt;Some details of Clan’s Darknet Builder UI&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;It’s our way of rethinking networks and infrastructure and inviting everyone to become the architect of their own digital world!&lt;/p&gt;
&lt;p&gt;This is the Darknet Builder by Clan.&lt;/p&gt;
&lt;video width=&#34;100%&#34; loop muted autoplay&gt;
  &lt;source
    src=&#34;https://static.clan.lol/videos/clan-fast-timelapse.webm&#34;
    type=&#34;video/webm&#34;
  /&gt;
  &lt;source
    src=&#34;https://static.clan.lol/videos/clan-fast-timelapse.mp4&#34;
    type=&#34;video/mp4&#34;
  /&gt;
  &lt;source
    src=&#34;https://static.clan.lol/videos/clan-fast-timelapse.ove&#34;
    type=&#34;video/ove&#34;
  /&gt;
&lt;/video&gt;

&lt;p&gt;That’s how we make complexity understandable.
That’s how we democratise DevOps.&lt;/p&gt;
&lt;p&gt;Time to architect.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Vars: A framework for managing secrets and computed values</title>
      <link>https://clan.lol/blog/vars/</link>
      <pubDate>Mon, 24 Mar 2025 21:25:04 +0700</pubDate>
      
      <guid>https://clan.lol/blog/vars/</guid>
      <description>&lt;h2 id=&#34;the-problem&#34;&gt;The problem&lt;/h2&gt;
&lt;p&gt;Deploying NixOS machines is often not a one-click experience. Even when re-using existing NixOS configurations, there is usually some machine specific initialization overhead that needs to be dealt with manually. Machine IDs need to be generated, passwords set, and ssh key pairs generated, to name a few examples.&lt;/p&gt;
&lt;p&gt;None of that is very satisfying as an admin, and it is definitely a problem for clan, where our goal is to enable non-technical people to administer NixOS machines by automating away as much as possible.&lt;/p&gt;
&lt;h2 id=&#34;the-current-state&#34;&gt;The current state&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s go through the options a NixOS admin currently has.&lt;/p&gt;
&lt;p&gt;If secrets are entered or generated on the target machine directly, they become part of its state which means they are either gonna be lost upon re-deployment to new hardware, likely messing up authentication between hosts, or they need to be backed up and restored carefully. Both resembles overhead.&lt;/p&gt;
&lt;p&gt;To circumvent that problem, as of now, admins would use tools like sops-nix or agenix to store  and re-deploy secrets securely. But even this introduces manual initialization overhead, as secrets need to be generated manually, then copy and pasted between tools.&lt;/p&gt;
&lt;p&gt;No matter which of the above strategies is used, admins cannot simply enable a NixOS module that requires a secret without manual interaction. How a secret needs to be generated exactly, is not part of the NixOS module&amp;rsquo;s definition, so it cannot possibly be automated as of now.&lt;/p&gt;
&lt;h2 id=&#34;the-journey&#34;&gt;The journey&lt;/h2&gt;
&lt;p&gt;We wanted to solve this problem, which is why we developed a NixOS based framework for secrets and other computed values, that allows module maintainers to declare how such values need to be generated, instead of offloading that work to the admins.&lt;/p&gt;
&lt;p&gt;We have iterated on our solution several times already, going from imperative tooling, through our first module system based approach called &lt;code&gt;facts&lt;/code&gt;, to our current solution which we call &lt;code&gt;vars&lt;/code&gt; as in &amp;lsquo;variables&amp;rsquo;.&lt;/p&gt;
&lt;p&gt;During this journey we hit a lot of road bumps and learned quite a few things on what is needed to generate, store, manage and share secrets between machines seamlessly.&lt;/p&gt;
&lt;p&gt;The clan developers as well as some power users of our community have been using this framework already for several months now. While we are still working on some improvements, we think it is ready to be shared and tested more extensively by the community&lt;/p&gt;
&lt;h2 id=&#34;core-concepts-of-vars&#34;&gt;Core Concepts of &lt;code&gt;vars&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Before we jump into examples, here an overview about our core concepts&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Secret/Public&lt;/strong&gt;:&lt;br&gt;
Some generated values need to be stored encrypted, like for example ssh private keys, while others should be world readable, like public keys.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Generators&lt;/strong&gt;:&lt;br&gt;
&lt;code&gt;Vars&lt;/code&gt; (either secret or public) are computed by generators. Generators are scripts which take some input and produce files.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Stores&lt;/strong&gt;:&lt;br&gt;
Similar to how nix derivations produce files which are stored in the nix store, &lt;code&gt;vars&lt;/code&gt; generators produce files which are either stored in a public or secret store. A generic store interface allows swapping out the store. For example, users can choose to store their secrets either in sops-nix or password-store, or even bring their own store.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Prompts&lt;/strong&gt;:&lt;br&gt;
Some secrets require input from the user before they can be generated. If, for example, a password hash needs to be generated, the user has to enter the password first. As everything else, prompts are declarative, and different frontends, like CLIs or GUIs can be built in order to prompt the user.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sharing&lt;/strong&gt;:&lt;br&gt;
Some &lt;code&gt;vars&lt;/code&gt; need to be shared between machines. An admin might choose, for example, to re-use their github api key across several machines, instead of generating a new one for each. If the key changes, it should be changed on all machines simultaneously.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Dependencies&lt;/strong&gt;:&lt;br&gt;
&lt;code&gt;Vars&lt;/code&gt; can depend on each other, as in if one secret changes, others have to be updated as well, one simple example being a public key, that is derived from a private key. The dependency system ensures that secrets across the fleet remain in sync and also allows re-rolling the secrets easily without having to worry about forgetting to update something.&lt;/p&gt;
&lt;h2 id=&#34;example&#34;&gt;Example&lt;/h2&gt;
&lt;p&gt;In this example, a &lt;code&gt;vars&lt;/code&gt; generator is used to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;prompt the user for the password&lt;/li&gt;
&lt;li&gt;run the required &lt;code&gt;mkpasswd&lt;/code&gt; command to generate the hash&lt;/li&gt;
&lt;li&gt;store the hash in a file&lt;/li&gt;
&lt;li&gt;set &lt;code&gt;users.users.root.hashedPasswordFile&lt;/code&gt; to reference that file&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{config&lt;span style=&#34;color:#f92672&#34;&gt;,&lt;/span&gt; pkgs&lt;span style=&#34;color:#f92672&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;...&lt;/span&gt;}:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  vars &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; clan&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;core&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;vars;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;in&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# The vars definition&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  clan&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;core&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;vars&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;generators&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;root-password &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# Prompts the user for a password&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# (`password-input` being an arbitrary name)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    prompts&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;password-input&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;description &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;the root user&amp;#39;s password&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    prompts&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;password-input&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;type &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;hidden&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# Defines an output file for storing the hash and declare it as non-secret&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    files&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;password-hash&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;secret &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;false&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# Defines the logic for generating the hash&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    script &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;      cat $prompts/password-input | mkpasswd -m sha-512 &amp;gt; $out/password-hash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    &amp;#39;&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# Tools required by the script&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    runtimeInputs &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; [ pkgs&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;mkpasswd ];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# Sets the root password to the file containing the hash&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  users&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;users&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;root&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;hashedPasswordFile &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# Clan will make sure, this path exists at runtime&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    vars&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;generators&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;root-password&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;files&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;password-hash&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;path;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# Users need to be immutable, otherwise updating a password might be ignored&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  users&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;mutableUsers &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;false&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;As one can see, the &lt;code&gt;vars&lt;/code&gt; generators definition is part of the normal NixOS configuration.&lt;/p&gt;
&lt;p&gt;In this NixOS module, the root user&amp;rsquo;s &lt;code&gt;hashedPasswordFile&lt;/code&gt; is set to a, yet to be created path which will contain the generated password hash.&lt;/p&gt;
&lt;p&gt;A wrapper around &lt;code&gt;nixos-rebuild switch&lt;/code&gt; ensures that this path will be created before the machine is deployed.&lt;/p&gt;
&lt;h3 id=&#34;more-examples&#34;&gt;More Examples&lt;/h3&gt;
&lt;p&gt;To find more examples, just take a look at the &lt;a href=&#34;https://git.clan.lol/clan/clan-core/src/branch/main/clanModules&#34;&gt;existing clan modules&lt;/a&gt; of which several are using &lt;code&gt;vars&lt;/code&gt; already, for example the &lt;a href=&#34;https://git.clan.lol/clan/clan-core/src/commit/828eff528a5d3e996bf15a42a91e7712f6202bbe/clanModules/sshd/shared.nix&#34;&gt;sshd module&lt;/a&gt; which sets up a certificate authority to certify ssh host keys across all machines. Never have ssh based scripts stuck again because of having to type in &lt;code&gt;yes&lt;/code&gt; to trust the host.&lt;/p&gt;
&lt;h2 id=&#34;future-work&#34;&gt;Future work&lt;/h2&gt;
&lt;h3 id=&#34;service-oriented-design&#34;&gt;Service oriented design&lt;/h3&gt;
&lt;p&gt;As seen in the example above, &lt;code&gt;vars&lt;/code&gt; are currently defined inside a machine&amp;rsquo;s NixOS configuration. While this design choice is nice in terms of compatibility, some interactions become more complex, like defining global &lt;code&gt;vars&lt;/code&gt; across all machines. In the worst case, several machines would have to be evaluated in order to update &lt;code&gt;vars&lt;/code&gt; reliably across the infrastructure.&lt;/p&gt;
&lt;p&gt;We want to add &lt;code&gt;vars&lt;/code&gt; support to the clan inventory which improves the UX and performance to manage infrastructure overarching settings.&lt;/p&gt;
&lt;h3 id=&#34;upstreaming-the-framework&#34;&gt;Upstreaming the framework&lt;/h3&gt;
&lt;p&gt;We believe &lt;code&gt;vars&lt;/code&gt; can be quite beneficial for any NixOS user and therefore want to upstream as much of it as possible. In fact, a first &lt;a href=&#34;https://github.com/NixOS/nixpkgs/pull/370444&#34;&gt;draft PR&lt;/a&gt; has been opened by @lassulus a while ago.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>VPN Benchmarks Part 1</title>
      <link>https://clan.lol/blog/vpn-benchmark-part-1/</link>
      <pubDate>Fri, 07 Mar 2025 00:08:10 +0200</pubDate>
      
      <guid>https://clan.lol/blog/vpn-benchmark-part-1/</guid>
      <description>&lt;p&gt;Which VPN technology really stands out? How efficiently does it scale, and how does it perform under poor network conditions? These are the questions that arise when faced with the endless array of VPN options—yet hard data remains scarce. Often, if you ask around, you&amp;rsquo;ll hear someone say, &amp;ldquo;I tried $VPN and it works perfectly for me,&amp;rdquo; while another retorts, &amp;ldquo;But it kept crashing for me during $vacation.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Well, I say enough with this hearsay! It’s time to move beyond anecdotal evidence and start benchmarking. Let&amp;rsquo;s establish a fully automated, reproducible testing approach so that we can clearly track improvements over time.&lt;/p&gt;
&lt;p&gt;At least, that’s our goal. I’ve begun implementing the &lt;a href=&#34;https://git.clan.lol/Qubasa/vpn-benchmark&#34;&gt;vpn-benchmark&lt;/a&gt; using the Clan Python API (which isn’t stable right now) in combination with the cloud API abstraction &lt;a href=&#34;https://opentofu.org/&#34;&gt;opentofu&lt;/a&gt; - the open source equivalent of Terraform. With this setup, a user simply needs to execute:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  nix shell git+https://git.clan.lol/Qubasa/vpn-benchmark.git#vpn-bench --refresh
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To get a shell that includes our package, &lt;code&gt;vpb&lt;/code&gt;. Within this shell, you can run the command &lt;code&gt;vpb create&lt;/code&gt; to automatically launch Hetzner Cloud machines for benchmarking. These machines are provisioned with a generated SSH key. The tool accomplishes this by defining a user_data section within our opentofu script, which contains a YAML configuration string. This format is standardized across cloud vendors and documented at &lt;a href=&#34;https://cloudinit.readthedocs.io/en/latest/reference/yaml_examples/set_passwords.html&#34;&gt;cloudinit.readthedocs.io&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;However, there was one issue. When logging into a Hetzner machine for the first time, a prompt appears asking the user to change the root password. Normally, adding an SSH key to the machine disables this prompt—but due to a bug, the prompt persists when the SSH key is deployed through the user_data section. Fortunately, the user_data configuration also allows us to change user passwords, which effectively resolves this problem.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;display:grid;&#34;&gt;&lt;code class=&#34;language-terraform&#34; data-lang=&#34;terraform&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;resource&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;hcloud_server&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;servers&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;for_each&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; { &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;server&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;in&lt;/span&gt; var.&lt;span style=&#34;color:#a6e22e&#34;&gt;servers&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;server&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;name&lt;/span&gt; =&amp;gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;server&lt;/span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;name&lt;/span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; each.&lt;span style=&#34;color:#a6e22e&#34;&gt;value&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;name&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;server_type&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; each.&lt;span style=&#34;color:#a6e22e&#34;&gt;value&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;server_type&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;image&lt;/span&gt;       &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; var.&lt;span style=&#34;color:#a6e22e&#34;&gt;os_image&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;location&lt;/span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; each.&lt;span style=&#34;color:#a6e22e&#34;&gt;value&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;location&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;  # This is the cloudinit config
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;user_data&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;lt;-EOF&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    #cloud-config
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    ssh_authorized_keys:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;      - ${join(&amp;#34;\n  - &amp;#34;, var.ssh_pubkeys)}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#3c3d38&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    ssh_pwauth: false
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    chpasswd:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;      expire: false
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;      users:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#3c3d38&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;      - {name: root, password: Sahb7pied8, type: text}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  &lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;EOF&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Nextup a user can execute &lt;code&gt;vpb install&lt;/code&gt; this is where the Clan magic happens. We create a temporary Clan inside the &lt;code&gt;~/.local/share/vpn_bench/clan&lt;/code&gt; folder, and then create a dynamic amount of machines inside that Clan.&lt;/p&gt;
&lt;p&gt;With the power of &lt;a href=&#34;https://github.com/nix-community/nixos-anywhere&#34;&gt;nixos-anywhere&lt;/a&gt;, we can transform any pre-installed Linux OS into our own NixOS-based system. In addition, &lt;a href=&#34;https://github.com/nix-community/nixos-facter&#34;&gt;nixos-facter&lt;/a&gt; generates a hardware report to ensure that all necessary drivers are included—without any user interaction. If you&amp;rsquo;re interested in the code, check out the install.py file in our repository: &lt;a href=&#34;https://git.clan.lol/Qubasa/vpn-benchmark/src/branch/main/pkgs/vpn-bench/vpn_bench/install.py&#34;&gt;install.py&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Once the infrastructure is in place, executing &lt;code&gt;vpb bench&lt;/code&gt; initiates a suite of benchmarks across all supported VPN technologies. For more targeted testing, users can specify a particular VPN with &lt;code&gt;vpb bench --vpn &amp;lt;name&amp;gt;&lt;/code&gt;. This command triggers a &lt;code&gt;clan machine update&lt;/code&gt; process behind the scenes, leveraging Clan&amp;rsquo;s Inventory system to dynamically configure each machine in the test network.&lt;/p&gt;
&lt;p&gt;Clan&amp;rsquo;s Inventory system is the secret sauce that makes this all possible. It provides a programmatic interface to define which services and Nix modules should be enabled on each node in our distributed benchmark environment. Because the Inventory is fully JSON-serializable, our Python code can easily manipulate configurations on the fly, enabling us to test multiple VPN setups with varying parameters in a single automated run. This approach ensures consistent, reproducible results that can be tracked over time, finally giving us objective data to evaluate VPN performance claims.&lt;/p&gt;
&lt;p&gt;To then visualize the data a user can execute &lt;code&gt;vpb bench&lt;/code&gt; this will build a static website with Nix and serve it over http once compiled.&lt;/p&gt;
&lt;figure&gt;&lt;img src=&#34;https://clan.lol/blog/vpn-benchmark-part-1/web_plots.png&#34;&gt;
&lt;/figure&gt;
&lt;p&gt;The current testing methodology is fairly basic, only using &lt;code&gt;iperf3&lt;/code&gt; to benchmark throughput and packetloss. However in the upcoming weeks we plan on extending this, to test things like packet delay, packet reordering, node failures and of course more VPNs.&lt;/p&gt;
&lt;p&gt;One thing one has to look out for is the fact that cloud VMs could share a network card with a noisy neigbhour generating lots of traffic and thus skewing the results. But that case should be fairly obvious to spot when looking at the ground truth benchmarks that are not using any kind of VPN.&lt;/p&gt;
&lt;p&gt;An interesting finding from our preliminary tests revealed that UDP throughput between VMs was significantly lower than TCP throughput in the baseline tests. This counterintuitive result likely stems from Hetzner&amp;rsquo;s UDP traffic throttling within their NAT infrastructure. One  approach to circumvent this limitation could be implementing tests using the QUIC protocol, which might be whitelisted due to being a webstandard.&lt;/p&gt;
&lt;p&gt;For now that&amp;rsquo;s how far the project is, please note that it&amp;rsquo;s not yet ready to be used.&lt;/p&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>nix-select</title>
      <link>https://clan.lol/blog/nix-select/</link>
      <pubDate>Mon, 17 Feb 2025 00:08:10 +0200</pubDate>
      
      <guid>https://clan.lol/blog/nix-select/</guid>
      <description>&lt;p&gt;Find it at: &lt;a href=&#34;https://git.clan.lol/clan/select&#34;&gt;https://git.clan.lol/clan/select&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Do you find accessing values from lists cumbersome? Is &lt;code&gt;builtins.elemAt&lt;/code&gt; bringing you down? What about filtering nested dictionaries for specific keys?&lt;/p&gt;
&lt;p&gt;For this reason, we hacked together an unmatched selection experience. It is written in pure Nix, without dependencies, to make your life 100% more &amp;ldquo;selectful.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;To use &lt;code&gt;nix-select&lt;/code&gt; with the &lt;code&gt;clan&lt;/code&gt; CLI, just run &lt;code&gt;clan select&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&#34;prominent-features-include&#34;&gt;Prominent features include:&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Globs: &lt;code&gt;*&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;{a,b}&lt;/code&gt; (set-based selection)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.0&lt;/code&gt; (index-based access)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;example-using-the-clan-cli&#34;&gt;Example using the Clan CLI:&lt;/h2&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;clan &lt;span style=&#34;color:#66d9ef&#34;&gt;select&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;nixosConfigurations.*.config.clan.core.vars.generators.*.files.*.{path,secret}&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This will print all variables defined in your Clan.&lt;/p&gt;
&lt;p&gt;Here’s the equivalent non-selectful expression:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;builtins&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;mapAttrs (
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  _: v0:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  builtins&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;mapAttrs (
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    _: v1:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (builtins&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;mapAttrs (
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      _: v3:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      builtins&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;intersectAttrs {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        path &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; { };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        secret &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; { };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      } v3
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ) v1&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;files)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  ) v0&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;config&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;clan&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;core&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;vars&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;generators
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;) nixosConfigurations
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;clan select&lt;/code&gt; also uses a transparent caching technique, so running the same command again will be significantly faster.&lt;/p&gt;
&lt;p&gt;There are many more operators and constructs supported in &lt;code&gt;nix-select&lt;/code&gt;, all of which are documented in the &lt;a href=&#34;https://git.clan.lol/clan/select&#34;&gt;README&lt;/a&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;usage-without-clan&#34;&gt;Usage Without Clan&lt;/h2&gt;
&lt;p&gt;You can also import our 100% pure Nix library and use it directly in your project today!&lt;/p&gt;
&lt;p&gt;To use it inside any &lt;code&gt;nix repl&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;nix-repl&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; select &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (builtins&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;getFlake &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;github:clan-lol/select&amp;#34;&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;lib&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;select
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;With this, the &lt;code&gt;select&lt;/code&gt; function can be used by simply passing it a query string and an attribute set or list:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;nix-repl&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; select &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;*.config.networking.hostName&amp;#34;&lt;/span&gt; nixosConfigurations
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The query language is intentionally designed to be compatible with the Nix grammar, so it might be possible to extend the Nix expression language to improve ease of use.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;This project was developed during &lt;a href=&#34;https://thaigersprint.org/&#34;&gt;Thaigersprint 2025&lt;/a&gt;.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Providing Type-safe interfaces between NixOS and other languages</title>
      <link>https://clan.lol/blog/interfaces/</link>
      <pubDate>Wed, 11 Sep 2024 09:08:10 +0200</pubDate>
      
      <guid>https://clan.lol/blog/interfaces/</guid>
      <description>&lt;p&gt;When building a consumer-facing project on top of NixOS, one crucial question arises:
How can we provide type-safe interfaces within a polyglot software stack?&lt;/p&gt;
&lt;p&gt;This blogpost discusses one method for creating type-safe interfaces in a software stack by using JSON-schema to maintain consistent models across layers of an application.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Within the &lt;a href=&#34;https://clan.lol&#34;&gt;clan&lt;/a&gt; project, we explored one possible solution to this challenge. Our tech stack is composed of three main components:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Nix: Handles the core business logic.&lt;/li&gt;
&lt;li&gt;Python: Acts as a thin wrapper, exposing the business logic through a convenient to use CLI and API.&lt;/li&gt;
&lt;li&gt;TypeScript: Manages the presentation and GUI layer, communicating with Python via an API.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This architecture is a product of our guiding principles: We aim to encapsulate as much business logic as possible in pure Nix, ensuring that anyone familiar with Nix can utilize it.&lt;/p&gt;
&lt;h3 id=&#34;the-challenge-of-polyglot-architectures&#34;&gt;The Challenge of Polyglot Architectures&lt;/h3&gt;
&lt;p&gt;Throughout the lifecycle of an application, architectural models, relationships, and fields are typically refined and improved over time.
By this, I refer to constructs such as classes, structs, or enums.&lt;/p&gt;
&lt;p&gt;These elements are often required across multiple layers of the application
and must remain consistent to avoid discrepancies.
Logically, maintaining these models in a single location is crucial to prevent discrepancies and
eliminate a common source of errors — interface inconsistencies between software components.&lt;/p&gt;
&lt;p&gt;This approach can save significant time during development cycles,
particularly in dynamically typed environments like Nix and Python,
where errors are often caught through extensive unit testing or at runtime when issues arise.&lt;/p&gt;
&lt;p&gt;The Nix language presents a significant challenge due to its untyped and dynamic nature. Combined with NixOS, a constantly evolving collection of modules, it becomes incredibly difficult to build stable interfaces. As we develop more complex applications, a crucial question emerges: &amp;ldquo;How can models (such as classes or structs) be shared between multiple foreign languages and Nix?&amp;rdquo;&lt;/p&gt;
&lt;p&gt;One potential solution is to define the model once in a chosen language and then generate the necessary code for all other languages. This approach ensures consistency and reduces the likelihood of errors caused by manual translations between languages.&lt;/p&gt;
&lt;p&gt;Well-defined, statically typed models would provide build-time checks for correctness and prevent many issues and regressions that could have been avoided with robust interfaces.&lt;/p&gt;
&lt;p&gt;Building on the earlier blog post about the &lt;a href=&#34;https://docs.clan.lol/blog/2024/05/25/jsonschema-converter/&#34;&gt;NixOS modules to JSON-schema converter&lt;/a&gt;, a further exploration could involve using JSON-schema as an intermediate format. While not explicitly mentioned in the blog post, the JSON-schema converter operates solely on the interface declaration. It can also populate example values and other metadata that may become important later.&lt;/p&gt;
&lt;p&gt;In our case, we decided to use NixOS module interface declarations as the source of truth, as all our models are Nix-first citizens. We will use JSON-schema as an interoperable format that can further be utilized to generate Python classes and TypeScript types.&lt;/p&gt;
&lt;p&gt;For example, the desired Python code output could be a &lt;code&gt;TypedDict&lt;/code&gt; or a &lt;code&gt;dataclass&lt;/code&gt;. Since our input data might contain Nix attribute names that are invalid identifiers in Python, and vice versa, it is preferable to choose dataclasses. This allows us to store more metadata about the mapping relationships within the field properties.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{lib&lt;span style=&#34;color:#f92672&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;...&lt;/span&gt;}:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  types &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; lib&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;types;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  option &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; t: lib&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;mkOption {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    type &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; t;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;in&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  options &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    submodule &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; option (types&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;submodule {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      options &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        string &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; option types&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;str;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        list-str &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; option (types&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;listOf types&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;str);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        attrs-str &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; option (types&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;attrsOf types&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;str);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;With the following nix code this can be converted into python&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# Import clan-core flake&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  clan-core &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; builtins&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;getFlake &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;git+https://git.clan.lol/clan/clan-core&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  pkgs &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; clan-core&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;inputs&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;nixpkgs {};
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# Step 1: Convert NixOS module expression to JSON schema&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  serialize &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; expr: builtins&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;toFile &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;in.json&amp;#34;&lt;/span&gt; (builtins&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;toJSON expr);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  schema &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; serialize ((clan-core&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;lib&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;jsonschema {})&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;parseModule &lt;span style=&#34;color:#e6db74&#34;&gt;./in.nix&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# classgenerator&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;inherit&lt;/span&gt; (clan-core&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;packages&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;x86_64-linux) classgen;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;in&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;inherit&lt;/span&gt; schema;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  python-classes &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; pkgs&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;runCommand &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;py-cls&amp;#34;&lt;/span&gt; {}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;classgen&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;/bin/classgen &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;schema&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt; $out
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  &amp;#39;&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now execute the following:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-shellSession&#34; data-lang=&#34;shellSession&#34;&gt;nix build -f convert.nix python-classes
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The final Python code ensures that the Python component is always in sync with the Nix code.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;@dataclass&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Submodule&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    string: str
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    attrs_str: dict[str, str] &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; field(default_factory &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; dict, metadata &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;alias&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;attrs-str&amp;#34;&lt;/span&gt;})
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    list_str: list[str] &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; field(default_factory &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; list, metadata &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;alias&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;list-str&amp;#34;&lt;/span&gt;})
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;@dataclass&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Module&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    submodule: Submodule
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;p&gt;However, this approach comes with some constraints for both the interface itself and the tools surrounding it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;All types used in the interface must be JSON-serializable (e.g., Number, String, AttrSet, List, etc.).&lt;/li&gt;
&lt;li&gt;We identified certain constraints that work best for dataclasses, while also enhancing the final user experience:
&lt;ul&gt;
&lt;li&gt;Top-level options should specify a default value or be nullable.&lt;/li&gt;
&lt;li&gt;Ideally, option identifiers should use names that don&amp;rsquo;t require a &amp;ldquo;field-alias,&amp;rdquo; although this might not always be feasible.&lt;/li&gt;
&lt;li&gt;Neutral values for lists or dictionaries, such as an empty list or empty dictionary, must be supported.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The Python generator adds default constructors for dictionary and list types because the absence of a value would violate our type constraints.&lt;/p&gt;
&lt;p&gt;It is also important to note that we control both the JSON schema converter and the class generator, which is crucial. This control allows us to limit their scope to a subset of JSON schema features and ensure interoperability between the two generators.&lt;/p&gt;
&lt;p&gt;Another consideration is serialization and deserialization. In Python, Pydantic is typically a great choice, as it also offers &lt;a href=&#34;https://docs.pydantic.dev/latest/concepts/serialization/#custom-serializers&#34;&gt;custom serializers&lt;/a&gt;. However, when working with NixOS modules, we chose not to emit unset or null values because they create merge conflicts in the underlying NixOS modules. We also wanted to use field-aliases for names that are invalid identifiers in Python or TypeScript and wanted validation to catch errors early (in the deserializer) between our frontend and Nix, allowing us to present well-formatted errors instead of Nix evaluation error stack traces. Nevertheless, we ultimately did not use Pydantic because it did not meet our needs.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&#34;catching-errors&#34;&gt;Catching errors&lt;/h3&gt;
&lt;p&gt;Interface violations or regressions can be detected during the development cycle at build time.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Submodule(string&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;gt; Argument of type &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Literal[1]&amp;#34;&lt;/span&gt; cannot be assigned to parameter &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;string&amp;#34;&lt;/span&gt; of type &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;str&amp;#34;&lt;/span&gt; in &lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;__init__&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Literal[1]&amp;#34;&lt;/span&gt; is incompatible with &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;str&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Since all our layers communicate through JSON interfaces, any potential runtime type errors are caught in Python during deserialization before they can trigger any Nix stack traces. This allows for errors to be neatly formatted for the consumer.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;data &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;submodule&amp;#34;&lt;/span&gt;: { &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;string&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;  } }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;checked(Model, data)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; Traceback (most recent call last):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; Expected string, got &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h3 id=&#34;future&#34;&gt;Future&lt;/h3&gt;
&lt;p&gt;By adopting this approach, we aim to provide a stable and secure interface for polyglot software stacks built on top of Nixpkgs,
ultimately enhancing the reliability and maintainability of complex applications.&lt;/p&gt;
&lt;p&gt;Additionally, we will improve the tooling and develop a library, making this methodology applicable to other projects as well.&lt;/p&gt;
&lt;h3 id=&#34;links&#34;&gt;Links&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://docs.clan.lol/blog/2024/05/25/jsonschema-converter/&#34;&gt;https://docs.clan.lol/blog/2024/05/25/jsonschema-converter/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
</description>
    </item>
    
    <item>
      <title>Introducing NixOS Facter</title>
      <link>https://clan.lol/blog/nixos-facter/</link>
      <pubDate>Fri, 19 Jul 2024 09:08:10 +0200</pubDate>
      
      <guid>https://clan.lol/blog/nixos-facter/</guid>
      <description>&lt;p&gt;If you&amp;rsquo;ve ever installed &lt;a href=&#34;https://nixos.org&#34; title=&#34;Declarative builds and deployments&#34;&gt;NixOS&lt;/a&gt;, you&amp;rsquo;ll be familiar with a little Perl script called &lt;a href=&#34;https://github.com/NixOS/nixpkgs/blob/dac9cdf8c930c0af98a63cbfe8005546ba0125fb/nixos/modules/installer/tools/nixos-generate-config.pl&#34;&gt;nixos-generate-config&lt;/a&gt;. Unsurprisingly, it generates a couple of NixOS modules based on available hardware, mounted filesystems, configured swap, etc.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s a critical component of the install process, aiming to ensure you have a good starting point for your NixOS system, with necessary or recommended kernel modules, file system mounts, networking config and much more.&lt;/p&gt;
&lt;p&gt;As solutions go, it&amp;rsquo;s a solid one. It has helped many users take their first steps into this rabbit hole we call NixOS. However, it does suffer from one fundamental limitation.&lt;/p&gt;
&lt;h2 id=&#34;static-generation&#34;&gt;Static Generation&lt;/h2&gt;
&lt;p&gt;When a user generates a &lt;code&gt;hardware-configuration.nix&lt;/code&gt; with &lt;code&gt;nixos-generate-config&lt;/code&gt;, it makes choices based on the current state of the world as it sees it. By its very nature, then, it cannot account for changes in NixOS over time.&lt;/p&gt;
&lt;p&gt;A recommended configuration option today might be different two NixOS releases from now.&lt;/p&gt;
&lt;p&gt;To account for this, you could always run &lt;code&gt;nixos-generate-config&lt;/code&gt; again. But that requires a working system, which may have broken due to the historical choices made last time, or worst-case, requiring you to fire up the installer again.&lt;/p&gt;
&lt;h2 id=&#34;a-layer-of-indirection&#34;&gt;A Layer of Indirection&lt;/h2&gt;
&lt;p&gt;What if, instead of generating some Nix code, we first describe the current hardware in an intermediate format? This hardware report would be &lt;em&gt;&amp;lsquo;pure&amp;rsquo;&lt;/em&gt;, devoid of any reference to NixOS, and intended as a stable, longer-term representation of the system.&lt;/p&gt;
&lt;p&gt;From here, we can create a series of NixOS modules designed to examine the report&amp;rsquo;s contents and make the same kinds of decisions that &lt;code&gt;nixos-generate-config&lt;/code&gt; does. The critical difference is that as NixOS evolves, so can these modules, and with a full hardware report available we can make more interesting config choices about things such as GPUs and other devices.&lt;/p&gt;
&lt;p&gt;In a perfect world, we should not need to regenerate the underlying report as long as there are no hardware changes. We can take this one step further.&lt;/p&gt;
&lt;p&gt;Provided that certain sensitive information, such as serial numbers and MAC addresses, is filtered out, there is no reason why these hardware reports could not be shared after they are generated for things like EC2 instance types, specific laptop models, and so on, much like &lt;a href=&#34;https://github.com/NixOS/nixos-hardware&#34;&gt;NixOS Hardware&lt;/a&gt; currently shares Nix configs.&lt;/p&gt;
&lt;h2 id=&#34;introducing-nixos-facter&#34;&gt;Introducing NixOS Facter&lt;/h2&gt;
&lt;p&gt;Still in its early stages, &lt;a href=&#34;https://github.com/numtide/nixos-facter&#34;&gt;NixOS Facter&lt;/a&gt; is intended to do what I&amp;rsquo;ve described above.&lt;/p&gt;
&lt;p&gt;A user can generate a JSON-based hardware report using a (eventually static) Go program: &lt;code&gt;nixos-facter -o facter.json&lt;/code&gt;. From there, they can include this report in their NixOS config and make use of our &lt;a href=&#34;https://github.com/numtide/nixos-facter-modules&#34;&gt;NixOS modules&lt;/a&gt; as follows:&lt;/p&gt;
&lt;h4 id=&#34;flakenix&#34;&gt;flake.nix&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;```nix
{
    inputs = {
        nixpkgs.url = &amp;quot;github:nixos/nixpkgs/nixos-unstable&amp;quot;;
        nixos-facter-modules.url = &amp;quot;github:numtide/nixos-facter-modules&amp;quot;;
    };

    outputs = inputs @ {
        nixpkgs,
        ...
    }: {
        nixosConfigurations.basic = nixpkgs.lib.nixosSystem {
            modules = [
                inputs.nixos-facter-modules.nixosModules.facter
                { config.facter.reportPath = ./facter.json; }
                # ...
            ];
        };
    };
}
```
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&#34;without-flakes&#34;&gt;without flakes&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;```nix
# configuration.nix
{
    imports = [
      &amp;quot;${(builtins.fetchTarball {
        url = &amp;quot;https://github.com/numtide/nixos-facter-modules/&amp;quot;;
      })}/modules/nixos/facter.nix&amp;quot;
    ];

    config.facter.reportPath = ./facter.json;
}
```
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That&amp;rsquo;s it.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;We assume that users will rely on &lt;a href=&#34;https://github.com/nix-community/disko&#34;&gt;disko&lt;/a&gt;, so we have not implemented file system configuration yet (it&amp;rsquo;s on the roadmap).
In the meantime, if you don&amp;rsquo;t use disko you have to specify that part of the configuration yourself or take it from &lt;code&gt;nixos-generate-config&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;early-days&#34;&gt;Early Days&lt;/h2&gt;
&lt;p&gt;Please be aware that &lt;a href=&#34;https://github.com/numtide/nixos-facter&#34;&gt;NixOS Facter&lt;/a&gt; is still in early development and is still subject to significant changes especially the output json format as we flesh things out. Our initial goal is to reach feature parity with &lt;a href=&#34;https://github.com/NixOS/nixpkgs/blob/dac9cdf8c930c0af98a63cbfe8005546ba0125fb/nixos/modules/installer/tools/nixos-generate-config.pl&#34;&gt;nixos-generate-config&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;From there, we want to continue building our NixOS modules, opening things up to the community, and beginning to capture shared hardware configurations for providers such as Hetzner, etc.&lt;/p&gt;
&lt;p&gt;Over the coming weeks, we will also build up documentation and examples to make it easier to play with. For now, please be patient.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Side note: if you are wondering why the repo is in the &lt;a href=&#34;https://numtide.com&#34;&gt;Numtide&lt;/a&gt; org, we started partnering with Clan! Both companies are looking to make self-hosting easier and we&amp;rsquo;re excited to be working together on this. Expect more tools and features to come!&lt;/p&gt;
&lt;/blockquote&gt;
</description>
    </item>
    
    <item>
      <title>Declarative Backups and Restore</title>
      <link>https://clan.lol/blog/declarative-backups-and-restore/</link>
      <pubDate>Mon, 24 Jun 2024 09:08:15 +0200</pubDate>
      
      <guid>https://clan.lol/blog/declarative-backups-and-restore/</guid>
      <description>&lt;p&gt;Our goal with &lt;a href=&#34;https://clan.lol/&#34;&gt;Clan&lt;/a&gt; is to give users control over their data.
However, with great power comes great responsibility, and owning your data means you also need to take care of backups yourself.&lt;/p&gt;
&lt;p&gt;In our experience, setting up automatic backups is often a tedious process as it requires custom integration of the backup software and
the services that produce the state. More important than the backup is the restore.
Restores are often not well tested or documented, and if not working correctly, they can render the backup useless.&lt;/p&gt;
&lt;p&gt;In Clan, we want to make backup and restore a first-class citizen.
Every service should describe what state it produces and how it can be backed up and restored.&lt;/p&gt;
&lt;p&gt;In this article, we will discuss how our backup interface in Clan works.
The interface allows different backup software to be used interchangeably and
allows module authors to define custom backup and restore logic for their services.&lt;/p&gt;
&lt;h2 id=&#34;first-comes-the-state&#34;&gt;First Comes the State&lt;/h2&gt;
&lt;p&gt;Our services are built from Clan modules. Clan modules are essentially &lt;a href=&#34;https://wiki.nixos.org/wiki/NixOS_modules&#34;&gt;NixOS modules&lt;/a&gt;, the basic configuration components of NixOS.
However, we have enhanced them with additional features provided by Clan and restricted certain option types to enable configuration through a &lt;a href=&#34;https://docs.clan.lol/blog/2024/05/25/jsonschema-converter/&#34;&gt;graphical interface&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In a simple case, this can be just a bunch of directories, such as what we define for our &lt;a href=&#34;https://www.zerotier.com/&#34;&gt;ZeroTier&lt;/a&gt; VPN service.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  clan&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;core&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;state&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;zerotier&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;folders &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;  [ &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;/var/lib/zerotier-one&amp;#34;&lt;/span&gt; ];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;For other systems, we need more complex backup and restore logic.
For each state, we can provide custom command hooks for backing up and restoring.&lt;/p&gt;
&lt;p&gt;In our PostgreSQL module, for example, we define &lt;code&gt;preBackupCommand&lt;/code&gt; and &lt;code&gt;postRestoreCommand&lt;/code&gt; to use &lt;code&gt;pg_dump&lt;/code&gt; and &lt;code&gt;pg_restore&lt;/code&gt; to backup and restore individual databases:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;preBackupCommand &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  # ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  runuser -u postgres -- pg_dump &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;compression&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt; --dbname=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;db&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;name&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt; -Fc -c &amp;gt; &amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;current&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;.tmp&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  # ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;postRestoreCommand &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  # ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  runuser -u postgres -- dropdb &amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;db&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;name&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  runuser -u postgres -- pg_restore -C -d postgres &amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;current&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  # ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;then-the-backup&#34;&gt;Then the Backup&lt;/h2&gt;
&lt;p&gt;Our CLI unifies the different backup providers in one &lt;a href=&#34;https://docs.clan.lol/main/guides/backups/backup-intro/&#34;&gt;interface&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As of now, we support backups using &lt;a href=&#34;https://www.borgbackup.org/&#34;&gt;BorgBackup&lt;/a&gt; and
a backup module called &amp;ldquo;localbackup&amp;rdquo; based on &lt;a href=&#34;https://rsnapshot.org/&#34;&gt;rsnapshot&lt;/a&gt;, optimized for backup on locally attached storage media.&lt;/p&gt;
&lt;p&gt;To use different backup software, a module needs to set the options provided by our backup interface.
The following Nix code is a toy example that uses the &lt;code&gt;tar&lt;/code&gt; program to perform backup and restore to illustrate how the backup interface works:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  clan&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;core&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;backups&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;providers&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;tar &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    list &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;      echo /var/lib/system-back.tar
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    &amp;#39;&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    create &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      uniqueFolders &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; lib&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;unique (
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        lib&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;flatten (lib&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;mapAttrsToList (_name: state: state&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;folders) config&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;clan&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;core&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;state)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;      # FIXME: a proper implementation should also run `state.preBackupCommand` of each state
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;      if [ -f /var/lib/system-back.tar ]; then
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;        tar -uvpf /var/lib/system-back.tar &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;builtins&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;toString uniqueFolders&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;      else
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;        tar -cvpf /var/lib/system-back.tar &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;builtins&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;toString uniqueFolders&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;      fi
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    &amp;#39;&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    restore &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;      IFS=&amp;#39;:&amp;#39; read -ra FOLDER &amp;lt;&amp;lt;&amp;lt; &amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;&amp;#39;&amp;#39;$&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;FOLDERS&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;      echo &amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;FOLDER[&lt;span style=&#34;color:#f92672&#34;&gt;@&lt;/span&gt;]&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34; &amp;gt; /run/folders-to-restore.txt
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;      tar -xvpf /var/lib/system-back.tar -C / -T /run/folders-to-restore.txt
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    &amp;#39;&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  };
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;For better real-world implementations, check out the implementations for &lt;a href=&#34;https://git.clan.lol/clan/clan-core/src/branch/main/clanModules/borgbackup/default.nix&#34;&gt;BorgBackup&lt;/a&gt;
and &lt;a href=&#34;https://git.clan.lol/clan/clan-core/src/branch/main/clanModules/localbackup/default.nix&#34;&gt;localbackup&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;what-it-looks-like-to-the-end-user&#34;&gt;What It Looks Like to the End User&lt;/h2&gt;
&lt;p&gt;After following the guide for configuring a &lt;a href=&#34;https://docs.clan.lol/main/guides/backups/backup-intro/&#34;&gt;backup&lt;/a&gt;,
users can use the CLI to create backups, list them, and restore them.&lt;/p&gt;
&lt;p&gt;Backups can be created through the CLI like this:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;clan backups create web01
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;BorgBackup will also create backups itself every day by default.&lt;/p&gt;
&lt;p&gt;Completed backups can be listed like this:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;clan backups list web01
...
web01::u366395@u366395.your-storagebox.de:/./borgbackup::web01-web01-2024-06-18T01:00:00
web03::u366395@u366395.your-storagebox.de:/./borgbackup::web01-web01-2024-06-19T01:00:00
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;One cool feature of our backup system is that it is aware of individual services/applications.
Let&amp;rsquo;s say we want to restore the state of our &lt;a href=&#34;https://matrix.org/&#34;&gt;Matrix&lt;/a&gt; chat server; we can just specify it like this:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;clan backups restore --service matrix-synapse web01 borgbackup web03::u366395@u366395.your-storagebox.de:/./borgbackup::web01-web01-2024-06-19T01:00:00
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In this case, it will first stop the matrix-synapse systemd service, then delete the &lt;a href=&#34;https://www.postgresql.org/&#34;&gt;PostgreSQL&lt;/a&gt; database, restore the database from the backup, and then start the matrix-synapse service again.&lt;/p&gt;
&lt;h2 id=&#34;future-work&#34;&gt;Future work&lt;/h2&gt;
&lt;p&gt;As of now we implemented our backup and restore for a handful of services and we expect to refine the interface as we test the interface for more applications.&lt;/p&gt;
&lt;p&gt;Currently, our backup implementation backs up filesystem state from running services.
This can lead to inconsistencies if applications change the state while the backup is running.
In the future, we hope to make backups more atomic by backing up a filesystem snapshot instead of normal directories.
This, however, requires the use of modern filesystems that support these features.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Introducing the NixOS to JSON Schema Converter</title>
      <link>https://clan.lol/blog/json-schema-converter/</link>
      <pubDate>Sat, 25 May 2024 09:08:10 +0200</pubDate>
      
      <guid>https://clan.lol/blog/json-schema-converter/</guid>
      <description>&lt;h2 id=&#34;overview&#34;&gt;Overview&lt;/h2&gt;
&lt;p&gt;We’ve developed a new library designed to extract interfaces from NixOS modules and convert them into JSON schemas, paving the way for effortless GUI generation. This blog post outlines the motivations behind this development, demonstrates the capabilities of the library, and guides you through leveraging it to create GUIs seamlessly.&lt;/p&gt;
&lt;h2 id=&#34;motivation&#34;&gt;Motivation&lt;/h2&gt;
&lt;p&gt;In recent months, our team has been exploring various graphical user interfaces (GUIs) to streamline NixOS machine configuration. While our opinionated Clan modules simplify NixOS configurations, there&amp;rsquo;s a need to configure these modules from diverse frontends, such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Command-line interfaces (CLIs)&lt;/li&gt;
&lt;li&gt;Web-based UIs&lt;/li&gt;
&lt;li&gt;Desktop applications&lt;/li&gt;
&lt;li&gt;Mobile applications&lt;/li&gt;
&lt;li&gt;Large Language Models (LLMs)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Given this need, a universal format like JSON is a natural choice. It is already possible as of now, to import json based NixOS configurations, as illustrated below:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;configuration.json&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{ &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;networking&amp;#34;&lt;/span&gt;: { &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;hostName&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;my-machine&amp;#34;&lt;/span&gt; } }
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This configuration can be then imported inside a classic NixOS config:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{config&lt;span style=&#34;color:#f92672&#34;&gt;,&lt;/span&gt; lib&lt;span style=&#34;color:#f92672&#34;&gt;,&lt;/span&gt; pkgs&lt;span style=&#34;color:#f92672&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;...&lt;/span&gt;}: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  imports &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (lib&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;importJSON &lt;span style=&#34;color:#e6db74&#34;&gt;./configuration.json&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  ];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This straightforward approach allows us to build a frontend that generates JSON, enabling the configuration of NixOS machines. But, two critical questions arise:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;How does the frontend learn about existing configuration options?&lt;/li&gt;
&lt;li&gt;How can it verify user input without running Nix?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Introducing &lt;a href=&#34;https://json-schema.org/&#34;&gt;JSON schema&lt;/a&gt;, a widely supported standard that defines interfaces in JSON and validates input against them.&lt;/p&gt;
&lt;p&gt;Example schema for &lt;code&gt;networking.hostName&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;type&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;object&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;properties&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;networking&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;type&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;object&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;properties&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;hostName&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;type&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;string&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;pattern&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;^$|^[a-z0-9]([a-z0-9_-]{0,61}[a-z0-9])?$&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;client-side-input-validation&#34;&gt;Client-Side Input Validation&lt;/h2&gt;
&lt;p&gt;Validating input against JSON schemas is both efficient and well-supported across numerous programming languages. Using JSON schema validators, you can accurately check configurations like our &lt;code&gt;configuration.json&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Validation example:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ nix-shell -p check-jsonschema
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ jsonschema -o pretty ./schema.json -i ./configuration.json
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;===[&lt;/span&gt;SUCCESS&lt;span style=&#34;color:#f92672&#34;&gt;]===(&lt;/span&gt;./configuration.json&lt;span style=&#34;color:#f92672&#34;&gt;)===&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In case of invalid input, schema validators provide explicit error messages:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ echo &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;{ &amp;#34;networking&amp;#34;: { &amp;#34;hostName&amp;#34;: &amp;#34;my/machine&amp;#34; } }&amp;#39;&lt;/span&gt; &amp;gt; configuration.json
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ jsonschema -o pretty ./schema.json -i ./configuration.json
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;===[&lt;/span&gt;ValidationError&lt;span style=&#34;color:#f92672&#34;&gt;]===(&lt;/span&gt;./configuration.json&lt;span style=&#34;color:#f92672&#34;&gt;)===&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;my/machine&amp;#39;&lt;/span&gt; does not match &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;^$|^[a-z0-9]([a-z0-9_-]{0,61}[a-z0-9])?$&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Failed validating &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;pattern&amp;#39;&lt;/span&gt; in schema&lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;properties&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;][&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;networking&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;][&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;properties&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;][&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;hostName&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;pattern&amp;#39;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;^$|^[a-z0-9]([a-z0-9_-]{0,61}[a-z0-9])?$&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;type&amp;#39;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;string&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;On instance&lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;networking&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;][&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;hostName&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;my/machine&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;automatic-gui-generation&#34;&gt;Automatic GUI Generation&lt;/h2&gt;
&lt;p&gt;Certain libraries facilitate straightforward GUI generation from JSON schemas. For instance, the &lt;a href=&#34;https://rjsf-team.github.io/react-jsonschema-form/&#34;&gt;react-jsonschema-form playground&lt;/a&gt; auto-generates a form for any given schema.&lt;/p&gt;
&lt;h2 id=&#34;nixos-module-to-json-schema-converter&#34;&gt;NixOS Module to JSON Schema Converter&lt;/h2&gt;
&lt;p&gt;To enable the development of responsive frontends, our library allows the extraction of interfaces from NixOS modules to JSON schemas. Open-sourced for community collaboration, this library supports building sophisticated user interfaces for NixOS.&lt;/p&gt;
&lt;p&gt;Here’s a preview of our library&amp;rsquo;s functions exposed through the &lt;a href=&#34;https://git.clan.lol/clan/clan-core&#34;&gt;clan-core&lt;/a&gt; flake:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;lib.jsonschema.parseModule&lt;/code&gt; - Generates a schema for a NixOS module.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;lib.jsonschema.parseOption&lt;/code&gt; - Generates a schema for a single NixOS option.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;lib.jsonschema.parseOptions&lt;/code&gt; - Generates a schema from an attrset of NixOS options.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Example:
&lt;code&gt;module.nix&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{lib&lt;span style=&#34;color:#f92672&#34;&gt;,&lt;/span&gt; config&lt;span style=&#34;color:#f92672&#34;&gt;,&lt;/span&gt; pkgs&lt;span style=&#34;color:#f92672&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;...&lt;/span&gt;}: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# a simple service with two options&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  options&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;services&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;example-web-service &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    enable &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; lib&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;mkEnableOption &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Example web service&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    port &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; lib&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;mkOption {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      type &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; lib&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;types&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;int;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      description &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Port used to serve the content&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Converted, using the &lt;code&gt;parseModule&lt;/code&gt; function:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ cd clan-core
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ nix eval --json --impure --expr &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;(import ./lib/jsonschema {}).parseModule ./module.nix&amp;#39;&lt;/span&gt; | jq | head
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;properties&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;services&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;properties&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;example-web-service&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;properties&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;enable&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;              &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;default&amp;#34;&lt;/span&gt;: false,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;              &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;description&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Whether to enable Example web service.&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;              &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;examples&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;...
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This utility can also generate interfaces for existing NixOS modules or options.&lt;/p&gt;
&lt;h2 id=&#34;gui-for-nginx-in-under-a-minute&#34;&gt;GUI for NGINX in Under a Minute&lt;/h2&gt;
&lt;p&gt;Creating a prototype GUI for the NGINX module using our library and &lt;a href=&#34;https://rjsf-team.github.io/react-jsonschema-form/&#34;&gt;react-jsonschema-form playground&lt;/a&gt; can be done quickly:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Export all NGINX options into a JSON schema using a Nix expression:&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# export.nix&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  pkgs &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;lt;nixpkgs&amp;gt;&lt;/span&gt; {};
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  clan-core &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; builtins&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;getFlake &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;git+https://git.clan.lol/clan/clan-core&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  options &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (pkgs&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;nixos {})&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;options&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;services&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;nginx;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;in&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  clan-core&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;lib&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;jsonschema&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;parseOption options
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol start=&#34;2&#34;&gt;
&lt;li&gt;Write the schema into a file:&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ nix eval --json -f ./export.nix | jq &amp;gt; nginx.json
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol start=&#34;3&#34;&gt;
&lt;li&gt;Open the &lt;a href=&#34;https://rjsf-team.github.io/react-jsonschema-form/&#34;&gt;react-jsonschema-form playground&lt;/a&gt;, select &lt;code&gt;Blank&lt;/code&gt; and paste the &lt;code&gt;nginx.json&lt;/code&gt; contents.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This provides a quick look at a potential GUI (screenshot is cropped).&lt;/p&gt;
&lt;figure&gt;&lt;img src=&#34;https://clan.lol/blog/json-schema-converter/nginx-gui.jpg&#34;
alt=&#34;Potential Nginx Gui&#34;&gt;&lt;figcaption&gt;
    &lt;p&gt;Potential Nginx Gui&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&#34;limitations&#34;&gt;Limitations&lt;/h2&gt;
&lt;h3 id=&#34;laziness&#34;&gt;Laziness&lt;/h3&gt;
&lt;p&gt;JSON schema mandates the declaration of all required fields upfront, which might be configured implicitly or remain unused. For instance, &lt;code&gt;services.nginx.virtualHosts.&amp;lt;name&amp;gt;.sslCertificate&lt;/code&gt; must be specified even if SSL isn’t enabled.&lt;/p&gt;
&lt;h3 id=&#34;limited-types&#34;&gt;Limited Types&lt;/h3&gt;
&lt;p&gt;Certain NixOS module types, like &lt;code&gt;types.functionTo&lt;/code&gt; and &lt;code&gt;types.package&lt;/code&gt;, do not map straightforwardly to JSON. For full compatibility, adjustments to NixOS modules might be necessary, such as substituting &lt;code&gt;listOf package&lt;/code&gt; with &lt;code&gt;listOf str&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&#34;parsing-nixos-modules&#34;&gt;Parsing NixOS Modules&lt;/h3&gt;
&lt;p&gt;Currently, our converter relies on the &lt;code&gt;options&lt;/code&gt; attribute of evaluated NixOS modules, extracting information from the &lt;code&gt;type.name&lt;/code&gt; attribute, which is suboptimal. Enhanced introspection capabilities within the NixOS module system would be beneficial.&lt;/p&gt;
&lt;h2 id=&#34;future-prospects&#34;&gt;Future Prospects&lt;/h2&gt;
&lt;p&gt;We hope these experiments inspire the community, encourage contributions and further development in this space. Share your ideas and contributions through our issue tracker or matrix channel!&lt;/p&gt;
&lt;h2 id=&#34;links&#34;&gt;Links&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://discourse.nixos.org/t/introducing-the-nixos-to-json-schema-converter/45948&#34;&gt;Comments on NixOS Discourse&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://git.clan.lol/clan/clan-core/src/branch/main/lib/jsonschema&#34;&gt;Source Code of the JSON Schema Library&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://git.clan.lol/clan/clan-core/issues&#34;&gt;Our Issue Tracker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://matrix.to/#/#clan:clan.lol&#34;&gt;Our Matrix Channel&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://rjsf-team.github.io/react-jsonschema-form/&#34;&gt;react-jsonschema-form Playground&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>New documentation site and weekly new meetup</title>
      <link>https://clan.lol/blog/new-docs/</link>
      <pubDate>Tue, 16 Apr 2024 09:08:10 +0200</pubDate>
      
      <guid>https://clan.lol/blog/new-docs/</guid>
      <description>&lt;p&gt;Last week, we added a new documentation hub for clan at &lt;a href=&#34;https://docs.clan.lol&#34;&gt;docs.clan.lol&lt;/a&gt;.
We now have weekly office hours where people are invited to hangout and ask questions.
They are every Wednesday 15:30 UTC (17:30 CEST) in our &lt;a href=&#34;https://jitsi.lassul.us/clan.lol&#34;&gt;jitsi&lt;/a&gt;.
Otherwise drop by in our &lt;a href=&#34;https://matrix.to/#/#clan:clan.lol&#34;&gt;matrix channel&lt;/a&gt;.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Introducing Clan: Full-Stack Computing Redefined</title>
      <link>https://clan.lol/blog/introduction-clan/</link>
      <pubDate>Tue, 19 Mar 2024 09:08:10 +0200</pubDate>
      
      <guid>https://clan.lol/blog/introduction-clan/</guid>
      <description>&lt;p&gt;In a digital age where users are guided increasingly toward submission and dependence, Clan reclaims computing and networking from the ground up.&lt;/p&gt;
&lt;p&gt;Clan enables users to build any system from a git repository, automate secret handling, and join devices in a secure darknet. This control extends beyond applications to communication protocols and the operating system itself, putting you fully in charge of your own digital environment.&lt;/p&gt;
&lt;h2 id=&#34;why-were-building-clan&#34;&gt;Why We&amp;rsquo;re Building Clan&lt;/h2&gt;
&lt;p&gt;Our mission is simple: to restore fun, freedom, and functionality to computing as an open source project. We believe in building tools that empower users, foster innovation, and challenge the limitations imposed by outdated paradigms. Clan, in its essence, is an open source endeavor; it&amp;rsquo;s our contribution to a future where technology serves humanity, not the other way around.&lt;/p&gt;
&lt;h2 id=&#34;how-clan-changes-the-game&#34;&gt;How Clan Changes the Game&lt;/h2&gt;
&lt;p&gt;Clan embodies a new philosophy in system, application, and network design. It enables seamless, secure communication across devices, simplifies software distribution and updates, and offers both public and private network configurations. Here are some of the ways it accomplishes this:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Nix as a Foundation:&lt;/strong&gt; Imagine a safety net for your computer&amp;rsquo;s operating system, one that lets you make changes or updates without the fear of causing a crash or losing data. Nix simplifies the complexities of system design, ensuring that updates are safe and systems are more reliable.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Simplified System Deployment:&lt;/strong&gt; Building and managing a computer system, from the operating system to the software you use, often feels like putting together a complex puzzle. With Clan, the puzzle pieces are replaced by a set of building blocks. Leveraging the power of Nix and Clan&amp;rsquo;s innovative toolkit, anyone from tech-savvy administrators to everyday users can create and maintain what we call &amp;ldquo;full-stack systems&amp;rdquo; (everything your computer needs to run smoothly).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A Leap in Connectivity:&lt;/strong&gt; Imagine if you could create private, secure pathways between your devices, bypassing the noisy and often insecure internet. Clan makes this possible through something called &amp;ldquo;overlay networks.&amp;rdquo; These networks are like private tunnels, allowing your devices to talk to each other securely and directly. With Clan&amp;rsquo;s built-in overlay networks and automatically configured services, connecting your devices becomes seamless, secure, and hassle-free.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Security Through Separation:&lt;/strong&gt; Clan employs sandboxing and virtual machines, a technology that runs code in isolated environments - so even if you explore new Clans, your system remains protected from potential threats.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Reliable:&lt;/strong&gt; With Clan, your data and services are preserved for the long haul. We focus on self-hosted backups and integration with the &lt;a href=&#34;https://de.wikipedia.org/wiki/Fediverse&#34;&gt;Fediverse&lt;/a&gt;, a network of interconnected, independent online communities, so your digital life remains uninterrupted and under your control.&lt;/p&gt;
&lt;h2 id=&#34;a-glimpse-at-clans-features&#34;&gt;A Glimpse at Clan&amp;rsquo;s Features&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Social Scaling:&lt;/strong&gt; Choose between creating a private sanctuary for your closest contacts, a dynamic space for a self-contained community, or embracing the open web with public Clans anyone can join.&lt;/p&gt;
&lt;video width=&#34;100%&#34; loop muted autoplay&gt;
  &lt;source
    src=&#34;https://static.clan.lol/videos/show_join.webm&#34;
    type=&#34;video/webm&#34;
  /&gt;
  &lt;source
    src=&#34;https://static.clan.lol/videos/show_join.mp4&#34;
    type=&#34;video/mp4&#34;
  /&gt;
  &lt;source
    src=&#34;https://static.clan.lol/videos/show_join.ove&#34;
    type=&#34;video/ove&#34;
  /&gt;
&lt;/video&gt;

&lt;p&gt;&lt;strong&gt;Seamless VM Integration:&lt;/strong&gt; Applications running in virtual machines can appear and behave as if they&amp;rsquo;re part of your main operating system — a blend of power and simplicity.&lt;/p&gt;
&lt;video width=&#34;100%&#34; loop muted autoplay&gt;
  &lt;source
    src=&#34;https://static.clan.lol/videos/show_run.webm&#34;
    type=&#34;video/webm&#34;
  /&gt;
  &lt;source
    src=&#34;https://static.clan.lol/videos/show_run.mp4&#34;
    type=&#34;video/mp4&#34;
  /&gt;
  &lt;source
    src=&#34;https://static.clan.lol/videos/show_run.ove&#34;
    type=&#34;video/ove&#34;
  /&gt;
&lt;/video&gt;

&lt;p&gt;&lt;strong&gt;Robust Backup Management:&lt;/strong&gt; Keep your data safe &lt;em&gt;forever&lt;/em&gt; - never worry about cloud services disappearing in 10 years.&lt;/p&gt;
&lt;div id=&#34;backups&#34;&gt;&lt;/div&gt;
&lt;script&gt;
    AsciinemaPlayer.create(&#39;https://static.clan.lol/videos/backups.cast&#39;, document.getElementById(&#39;backups&#39;), {autoplay:true, loop:true});
&lt;/script&gt;

&lt;p&gt;&lt;strong&gt;Intuitive Secret Management:&lt;/strong&gt; Clan simplifies digital security by automating the creation and management of encryption keys and passwords for your services.&lt;/p&gt;
&lt;div id=&#34;secrets&#34;&gt;&lt;/div&gt;
&lt;script&gt;
    AsciinemaPlayer.create(&#39;https://static.clan.lol/videos/secrets.cast&#39;, document.getElementById(&#39;secrets&#39;), {autoplay:true, loop:true});
&lt;/script&gt;

&lt;p&gt;&lt;strong&gt;Remote Install:&lt;/strong&gt; Set up and manage Clan systems anywhere in the world with just a QR scan or SSH access, making remote installations as easy as snapping a photo or sharing a link.&lt;/p&gt;
&lt;div id=&#34;nixos-install&#34;&gt;&lt;/div&gt;
&lt;script&gt;
    AsciinemaPlayer.create(&#39;https://static.clan.lol/videos/nixos-install.cast&#39;, document.getElementById(&#39;nixos-install&#39;), {autoplay:true, loop:true});
&lt;/script&gt;

&lt;h2 id=&#34;who-stands-to-benefit&#34;&gt;Who Stands to Benefit?&lt;/h2&gt;
&lt;p&gt;Clan is for anyone and everyone who believes in the power of open source technology to connect, empower, and protect. From system administrators to less tech-savvy individuals, small business owners to privacy-conscious users, Clan offers something for everyone — a way to reclaim control and redefine how we interact with technology.&lt;/p&gt;
&lt;h2 id=&#34;join-the-revolution&#34;&gt;Join the Revolution&lt;/h2&gt;
&lt;p&gt;Ready to control your digital world? Clan is more than a tool—it&amp;rsquo;s a movement. Secure your data, manage your systems easily, or connect with others how you like. Start with Clan for a better digital future.&lt;/p&gt;
&lt;p&gt;Connect with us on our &lt;a href=&#34;https://matrix.to/#/#clan:clan.lol&#34;&gt;Matrix channel at clan.lol&lt;/a&gt; or through our IRC bridges (coming soon).&lt;/p&gt;
&lt;p&gt;Want to see the code? Check it out &lt;a href=&#34;https://git.clan.lol/clan/clan-core&#34;&gt;on our Gitea&lt;/a&gt; or &lt;a href=&#34;https://github.com/clan-lol/clan-core&#34;&gt;on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Or follow our &lt;a href=&#34;https://clan.lol/feed.xml&#34;&gt;RSS feed&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;Join us and be part of changing technology for the better, together.&lt;/p&gt;
</description>
    </item>
    
  </channel>
</rss>