<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <id>https://isabelroses.com</id>
    <title>isabel</title>
    <updated>2026-03-06T01:12:46.104Z</updated>
    <generator>https://github.com/jpmonette/feed</generator>
    <author>
        <name>isabel roses</name>
    </author>
    <link rel="alternate" href="https://isabelroses.com"/>
    <subtitle>the nix witch</subtitle>
    <icon>https://isabelroses.com/favicon.ico</icon>
    <rights>All rights reserved 2026</rights>
    <entry>
        <title type="html"><![CDATA[A NixOS PDS Hosting Guide]]></title>
        <id>https://isabelroses.com/blog/nix-pds-guide/</id>
        <link href="https://isabelroses.com/blog/nix-pds-guide/"/>
        <updated>2025-11-04T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[How to host your own personal data server using NixOS]]></summary>
        <content type="html"><![CDATA[<h2>Introduction</h2>
<p>Over the last number of days leading up to writing this guide I have been<br />
sharing my dotfiles left and right as a way to help people setting up their own<br />
personal data server, which I shall start to refer to as PDS going forwards.</p>
<h2>Understanding my assumptions</h2>
<p>Before we go any further I think it's best you understand some of the assumptions<br />
I have made when writing this guide.</p>
<ol>
<li>
<p>I am going to make a logical leap to assume you already are using NixOS as<br />
the host for your server. If you are not, I would highly recommend reading<br />
<a href="https://nixos.org/manual/nixos/stable/#sec-installation">the NixOS manual on installing<br />
NixOS</a>.</p>
</li>
<li>
<p>I will be keeping all nix code agnostic from flakes and classic nix.</p>
</li>
<li>
<p>We will only be dealing with the <a href="https://github.com/bluesky-social/pds">reference implementation of the PDS</a> and not<br />
alternative implementations such as <a href="https://github.com/haileyok/cocoon">cocoon</a> even if <a href="https://github.com/NixOS/nixpkgs/pull/458034">they are packaged in<br />
nixpkgs</a>.</p>
</li>
<li>
<p>I will be targeting my article towards unstable and the next upcoming stable<br />
release. If you are using the 25.05 release of NixOS, you will have to change<br />
any references of <code>bluesky-pds</code> to <code>pds</code> as the package was renamed on<br />
unstable and later releases.</p>
</li>
</ol>
<h2>Understanding your requirements</h2>
<p>Before even starting to set up your PDS we should first consider how many<br />
people we are going to host on the PDS. The <a href="https://github.com/bluesky-social/pds#self-hosting-a-pds">bluesky PDS<br />
documentation</a><br />
suggests that for 1-20 users 1GB of ram, 1 vCPU and 20GB of storage is<br />
sufficient. However, if you plan to host more users you should consider<br />
increasing these resources accordingly. I also personally consider a single<br />
user PDS to be a different entire world from a multi user PDS, so keep that in<br />
mind.</p>
<h2>Setting up the PDS</h2>
<p>As with any NixOS service the first step will be to set the <code>enable</code> option for<br />
the service we have chosen to <code>true</code>. This will look like such</p>
<pre><code>{
  services.bluesky-pds = {
    enable = true;
  };
}
</code></pre>
<h2>Configuring the PDS</h2>
<h3>overwhelming configuration</h3>
<p>At first the <a href="https://github.com/bluesky-social/atproto/blob/main/packages/pds/src/config/env.ts">list of environment<br />
variables</a><br />
can appear overwhelming. However, we are only going to need a few of them. Those being:</p>
<ul>
<li><code>PDS_PORT</code></li>
<li><code>PDS_HOSTNAME</code></li>
<li><code>PDS_ADMIN_EMAIL</code></li>
<li><code>PDS_JWT_SECRET</code></li>
<li><code>PDS_ADMIN_PASSWORD</code></li>
<li><code>PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX</code></li>
</ul>
<p>If you are going to be dealing with more than one user on your PDS you should<br />
also be aware of the following optional environment variables.</p>
<ul>
<li><code>PDS_EMAIL_SMTP_URL</code></li>
<li><code>PDS_EMAIL_FROM_ADDRESS</code></li>
</ul>
<p>So lets get started with the easier ones! I personally run the PDS on the port<br />
<code>3000</code> since I have it free, so that's what I'll use that in our example. Now<br />
we will need to get a domain name for our PDS for the sake of this example I<br />
will be using <code>example.com</code>. It is also 2025 so I'll assume that everyone has<br />
an email address that they can use for the admin of the PDS. So now let's put<br />
that all into practice by adding the settings to our nix configuration.</p>
<pre><code>{
  services.bluesky-pds = {
    enable = true;

    settings = {
      PDS_PORT = 3000;
      PDS_HOSTNAME = "example.com";
      PDS_ADMIN_EMAIL = "me@example.com";
    };
  };
}
</code></pre>
<h3>Secrets</h3>
<p>We are now faced with the time old question, "how do we deal with secrets in<br />
nix?". First off you should <strong>not</strong> put your secrets into plain nix code as this<br />
will place your secrets into the nix store, which everyone can see. So I would<br />
recommend either <a href="https://github.com/ryantm/agenix"><code>agenix</code></a> or<br />
<a href="https://github.com/Mic92/sops-nix"><code>sops-nix</code></a>. I shall not cover setting up<br />
either of these but I will explain how to generate the secrets and how to use<br />
both agenix and sops-nix here</p>
<p>To generate the <code>PDS_JWT_SECRET</code> and <code>PDS_ADMIN_PASSWORD</code>, you should open your<br />
preferred terminal and run</p>
<pre><code>openssl rand --hex 16
</code></pre>
<p>You <strong>must</strong> run this once for each secret.</p>
<p>And to generate the <code>PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX</code> secret you<br />
should run</p>
<pre><code>openssl ecparam --name secp256k1 --genkey --noout --outform DER | tail --bytes=+8 | head --bytes=32 | xxd --plain --cols 32
</code></pre>
<p>You should now place your newly generated secrets into your secrets manager.<br />
This should look something similar to</p>
<pre><code>PDS_JWT_SECRET=b2a99dc959f0509218cb64f46aec1d7b
PDS_ADMIN_PASSWORD=5114f716065307d0536fcfebc2044ced
PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX=78860ff08707c1219a890de116920c53507846d0cc702af9e7a5bba18cd6398c
</code></pre>
<p>With Agenix this may look something like</p>
<pre><code>{ config, ... }:
{
  age.secrets.pds = {
    file = ./pds.age; # replace with the path to your secret
    mode = "600";
    owner = "pds";
    group = "pds";
  };

  services.bluesky-pds = {
    enable = true;

    environmentFiles = [ config.age.secrets.pds.path ];

    settings = {
      PDS_PORT = 3000;
      PDS_HOSTNAME = "example.com";
      PDS_ADMIN_EMAIL = "me@example.com";
    };
  };
}
</code></pre>
<p>And sops-nix this will look like</p>
<pre><code>{ config, ... }:
{
  sops.secrets.pds = {
    owner = "pds";
    group = "pds";
  };

  services.bluesky-pds = {
    enable = true;

    environmentFiles = [ config.sops.secrets.pds.path ];

    settings = {
      PDS_PORT = 3000;
      PDS_HOSTNAME = "example.com";
      PDS_ADMIN_EMAIL = "me@example.com";
    };
  };
}
</code></pre>
<h3>The mailer</h3>
<p>If you're on a single user PDS you can skip this step, as long as you're willing<br />
to search for the <code>email_token</code> in <code>/var/lib/pds/accounts.sqlite</code>, when you<br />
first move your account, whenever you're trying to reset your password and so on.</p>
<p>I have set up both SMTP and <a href="https://resend.com/">resend</a> in my time hosting my<br />
PDS. Resend is by far the easier option if want your PDS to just work, or this<br />
is your first time dealing with the horrors of SMTP and emails.</p>
<p>In the case of resend append the following to your secrets, having replaced the<br />
placeholder details.</p>
<pre><code>PDS_EMAIL_SMTP_URL=smtps://resend:&lt;your-api-key-here&gt;@smtp.resend.com:465/
PDS_EMAIL_FROM_ADDRESS=noreply@example.com
</code></pre>
<p>In the case that you are daring enough to use SMTP and your own mail server. You<br />
should append the following to your secrets file, having replaced the placeholder data.<br />
Please note that you <strong>must</strong> <a href="https://en.wikipedia.org/wiki/Percent-encoding">percent encode</a> your username and password.</p>
<pre><code>PDS_EMAIL_SMTP_URL=smtps://username:password@smtp.example.com/
PDS_EMAIL_FROM_ADDRESS=noreply@example.com
</code></pre>
<h3>Additional fun variables</h3>
<p>There are still some more optional variables that you may want to consider<br />
using!</p>
<p>If you have multiple domains that would be good for using as handles you can<br />
use <code>PDS_SERVICE_HANDLE_DOMAINS</code> to do this. An example of this is<br />
<code>PDS_SERVICE_HANDLE_DOMAINS=.example.com,.catsky.social</code>.</p>
<p>Another useful option is <code>PDS_CRAWLERS</code>. I have shamelessly sourced my example<br />
of crawlers from <a href="https://compare.hose.cam">compare hoses</a>. So here is how it<br />
may look in nix code.</p>
<pre><code>PDS_CRAWLERS = lib.concatStringsSep "," [
  "https://bsky.network"
  "https://relay.cerulea.blue"
  "https://relay.fire.hose.cam"
  "https://relay2.fire.hose.cam"
  "https://relay3.fr.hose.cam"
  "https://relay.hayescmd.net"
  "https://relay.xero.systems"
  "https://relay.upcloud.world"
  "https://relay.feeds.blue"
  "https://atproto.africa"
];
</code></pre>
<h2>The web server</h2>
<p>For this part I shall be be providing both <a href="https://nginx.org/"><code>nginx</code></a> and<br />
<a href="https://caddyserver.com/"><code>caddy</code></a> examples. We will need to proxy the port we<br />
selected, in my case that is <code>3000</code> but we can do this in a smart way by<br />
accessing the port through the <code>config</code> attr. This will look like<br />
<code>config.services.bluesky-pds.settings.PDS_PORT</code>, from there we can apply this<br />
same premise to the domain. We must also proxy both our domain and all<br />
subdomains of our PDS's domain since subdomains are used for the handles of the<br />
PDS accounts.</p>
<p>In nginx this will look like</p>
<pre><code>{ config, ... }:
let
  pdsSettings = config.services.bluesky-pds.settings;
in
{
  sops.secrets.pds = {
    owner = "pds";
    group = "pds";
  };

  services = {
    bluesky-pds = {
      enable = true;

      environmentFiles = [ config.sops.secrets.pds.path ];

      settings = {
        PDS_PORT = 3000;
        PDS_HOSTNAME = "example.com";
        PDS_ADMIN_EMAIL = "me@example.com";
      };
    };

    nginx = {
      enable = true;

      virtualHosts.${pdsSettings.PDS_HOSTNAME} = {
        serverAliases = [ ".${pdsSettings.PDS_HOSTNAME}" ];

        locations."/" = {
          proxyPass = "http://127.0.0.1:${toString pdsSettings.PDS_PORT}";
          proxyWebsockets = true;
        };
      };
    };
  };
}
</code></pre>
<p>and in caddy it will look like</p>
<pre><code>{ config, ... }:
let
  pdsSettings = config.services.bluesky-pds.settings;
in
{
  sops.secrets.pds = {
    owner = "pds";
    group = "pds";
  };

  services = {
    bluesky-pds = {
      enable = true;

      environmentFiles = [ config.sops.secrets.pds.path ];

      settings = {
        PDS_PORT = 3000;
        PDS_HOSTNAME = "example.com";
        PDS_ADMIN_EMAIL = "me@example.com";
      };
    };

    caddy = {
      enable = true;

      virtualHosts.${pdsSettings.PDS_HOSTNAME} = {
        serverAliases = [ "*.${pdsSettings.PDS_HOSTNAME}" ];
        extraConfig = ''
          import common
          reverse_proxy http://127.0.0.1:${toString pdsSettings.PDS_PORT}
        '';
      };
    };
  };
}
</code></pre>
<h3>Age assurance</h3>
<p>We cannot be done just there; some of us are unfortunate enough to live in the<br />
UK under the online safety act. However, a lovely <a href="https://gist.github.com/mary-ext/6e27b24a83838202908808ad528b3318">gist on bluesky<br />
osa</a> has<br />
been provided to us by the lovely<br />
<a href="https://bsky.app/profile/did:plc:ia76kvnndjutgedggx2ibrem">mary</a>. So let us apply this to our nix code.</p>
<p>In nginx this will look like such</p>
<pre><code>{
  # ... same as before

  nginx = {
    enable = true;

    virtualHosts.${pdsSettings.PDS_HOSTNAME} = {
      serverAliases = [ ".${pdsSettings.PDS_HOSTNAME}" ];

      locations =
        let
          mkAgeAssured = state: {
            return = "200 '${builtins.toJSON state}'";
            extraConfig = ''
              add_header access-control-allow-headers "authorization,dpop,atproto-accept-labelers,atproto-proxy" always;
              add_header access-control-allow-origin "*" always;
              add_header X-Frame-Options SAMEORIGIN always;
              add_header X-Content-Type-Options nosniff;
              default_type application/json;
            '';
          };
        in
        {
          # i am of age but i don't want to prove it lol
          # https://gist.github.com/mary-ext/6e27b24a83838202908808ad528b3318
          "/xrpc/app.bsky.unspecced.getAgeAssuranceState" = mkAgeAssured {
            lastInitiatedAt = "2025-07-14T15:11:05.487Z";
            status = "assured";
          };
          "/xrpc/app.bsky.ageassurance.getConfig" = mkAgeAssured {
            regions = [ ];
          };
          "/xrpc/app.bsky.ageassurance.getState" = mkAgeAssured {
            state = {
              lastInitiatedAt = "2025-07-14T15:11:05.487Z";
              status = "assured";
              access = "full";
            };
            metadata = {
              accountCreatedAt = "2022-11-17T00:35:16.391Z";
            };
          };

          # pass everything else to the pds
          "/" = {
            proxyPass = "http://${cfg.host}:${toString cfg.port}";
            proxyWebsockets = true;
          };
        };
      };
    };
  };
}
</code></pre>
<p>And with caddy</p>
<pre><code>{
  # ... same as before

  caddy = {
    enable = true;

    virtualHosts.${pdsSettings.PDS_HOSTNAME} = {
      serverAliases = [ "*.${pdsSettings.PDS_HOSTNAME}" ];
      extraConfig = ''
        import common
        reverse_proxy http://127.0.0.1:${toString pdsSettings.PDS_PORT}

        handle /xrpc/app.bsky.unspecced.getAgeAssuranceState {
          header content-type "application/json"
          header access-control-allow-headers "authorization,dpop,atproto-accept-labelers,atproto-proxy"
          header access-control-allow-origin "*"
          respond `{"lastInitiatedAt":"2025-07-14T14:22:43.912Z","status":"assured"}` 200
        }

        handle /xrpc/app.bsky.ageassurance.getConfig {
          header content-type "application/json"
          header access-control-allow-headers "authorization,dpop,atproto-accept-labelers,atproto-proxy"
          header access-control-allow-origin "*"
          respond `{"regions":[]}` 200
        }
        handle /xrpc/app.bsky.ageassurance.getState {
          header content-type "application/json"
          header access-control-allow-headers "authorization,dpop,atproto-accept-labelers,atproto-proxy"
          header access-control-allow-origin "*"
          respond `{"state":{"lastInitiatedAt":"2025-07-14T14:22:43.912Z","status":"assured","access":"full"},"metadata":{"accountCreatedAt":"2022-11-17T00:35:16.391Z"}}` 200
        }
      '';
    };
  };
}
</code></pre>
<h2>Create a test account &amp; migration</h2>
<p>It is important that you now access the server and create a test repo. To do<br />
this you can use <code>pdsadmin</code> which is installed by default by the NixOS module.<br />
We <strong>really</strong> want to do this because there are possible issue that may arise when using a<br />
newly setup PDS, see <a href="https://blooym.dev/blog/setting-up-at-pds">setting up a pds by<br />
lyna</a>.</p>
<p>The below example will create an account for you with the email address and<br />
handle as follows, make sure you replace the placeholder with your own data.</p>
<pre><code>pdsadmin account create test@example.com test.example.com
</code></pre>
<p>When you have confirmed that everything is running well you can use<br />
<a href="https://pdsmoover.com/">pdsmoover</a> by the lovely <a href="https://bsky.app/profile/did:plc:rnpkyqnmsw4ipey6eotbdnnf">Bailey<br />
Townsend</a>. But to do<br />
so you will need an invite code from your PDS which you can generate with the<br />
following command</p>
<pre><code>pdsadmin create-invite-code
</code></pre>
<p>Now you can move your account over for which <a href="https://git.witchcraft.systems/scientific-witchery/pds-starter-pack#how-to-move-an-existing-account-to-our-pds">there are</a> <a href="https://tgirl.cloud/blog/migrating-pds/">many</a> <a href="https://blacksky.community/profile/did:plc:g7j6qok5us4hjqlwjxwrrkjm/post/3lw3hcuojck2u">guides</a>.</p>
<h2>Add PDS gatekeeper (optional)</h2>
<p><a href="https://tangled.org/@baileytownsend.dev/pds-gatekeeper">PDS gatekeeper</a> is a<br />
service, once again created by Bailey Townsend, that adds 2FA email and<br />
endpoint spam prevention, which is why I choose to employ it. In the future I<br />
intend to upstream my code packaging and modularizing to nixpkgs, but until<br />
then you will have to consume <a href="https://github.com/tgirlcloud/pkgs">tgirlpkgs</a>.<br />
Please follow the setup guide documented in their readme!</p>
<p>From there it is as simple as adding the following to your previous configuration.</p>
<pre><code>{ config, ... }:
let
  pdsSettings = config.services.bluesky-pds.settings;
in
{
  services.pds-gatekeeper = {
    enable = true;

    # assuming you're using nginx this will do all the stuff for you!
    setupNginx = true;

    settings = {
      # this should be different to the PDS's port
      GATEKEEPER_PORT = 3001;

      PDS_BASE_URL = "http://127.0.0.1:${toString pdsSettings.PDS_PORT}";
      GATEKEEPER_TRUST_PROXY = "true";

      # we need to share a lot of secrets between pds and gatekeeper
      # if you're using agenix make sure to swap the sops to age
      PDS_ENV_LOCATION = config.sops.secrets.pds.path;
    };
  };
}
</code></pre>
<h2>Conclusion</h2>
<p>You should now be all set! You have got your PDS running!</p>
<p>You should also consider giving me money on<br />
<a href="https://ko-fi.com/isabelroses">ko-fi</a> or <a href="https://github.com/sponsors/isabelroses">github<br />
sponsors</a> for writing this because I<br />
suck at writing and I spent a few hours on this.</p>
]]></content>
    </entry>
    <entry>
        <title type="html"><![CDATA[why I removed my self-healing URLs]]></title>
        <id>https://isabelroses.com/blog/why-i-removed-self-healing-urls/</id>
        <link href="https://isabelroses.com/blog/why-i-removed-self-healing-urls/"/>
        <updated>2025-07-07T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[the act of removing my self healing URLs]]></summary>
        <content type="html"><![CDATA[<p>In the past, I wrote about how I used <a href="https://isabelroses.com/blog/self-healing-urls/">self-healing URLs</a>. But while rewriting my website for the roughly the sixth time, a realization hit me: <em>who am I really building this for?</em></p>
<p>That question completely shifted how I approached my site. I had been optimizing for SEO, but the real purpose of my site is self-documentation and sharing what I learn with others who want to learn alongside me.</p>
<p>With that in mind, I decided to delete the self-healing URLs. I no longer wanted to cater to machines—I wanted to build for people. Self-healing URLs are inherently anti-user. How is a human supposed to remember which number blog post this is? Or worse, if it's just a random string?</p>
<p>Maybe this will prompt you to remove your self-healing URLs... or maybe it won't :D</p>
]]></content>
    </entry>
    <entry>
        <title type="html"><![CDATA[My custom lib.nixosSystem]]></title>
        <id>https://isabelroses.com/blog/custom-lib-nixossystem/</id>
        <link href="https://isabelroses.com/blog/custom-lib-nixossystem/"/>
        <updated>2025-02-07T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[How I came to write my own lib.nixosSystem]]></summary>
        <content type="html"><![CDATA[<h2>Introduction</h2>
<p>I've been using NixOS for a while now, and my biggest issue was that I have a<br />
lot of machines. Which leads to having a lot of different<br />
<a href="https://github.com/isabelroses/dotfiles/tree/main/systems">systems</a> on my<br />
<a href="https://github.com/isabelroses/dotfiles">flake</a> because of that hardware. The<br />
normal solution would be to use the module system, but a new issue arises when<br />
we add <a href="https://github.com/LnL7/nix-darwin">nix-darwin</a>. We suddenly start to<br />
fail<br />
eval over issues because some modules don't exist that are in the normal NixOS<br />
module system. So my new issue is that I can no longer use my small abstraction<br />
over<br />
<a href="https://github.com/NixOS/nixpkgs/blob/5223d4097bb2c9e89d133f61f898df611d5ea3ca/flake.nix#L57-L79"><code>lib.nixosSystem</code></a>.<br />
I would have to expand the abstraction to include<br />
<a href="https://github.com/LnL7/nix-darwin/blob/87131f51f8256952d1a306b5521cedc2dc61aa08/flake.nix#L21-L51"><code>lib.darwinSystem</code></a><br />
since I can no longer unconditionally import all modules I use if they don't<br />
exist in nix-darwin? But what if I don't want to do that? What if I want to<br />
write my own <code>lib.nixosSystem</code> or my own <code>lib.darwinSystem</code>? What if I call it<br />
<a href="https://github.com/isabelroses/dotfiles/blob/24588b2101c0fb2a8587df377a670e9e4cc47b42/parts/lib/builders.nix#L17"><code>mkSystem</code></a>.<br />
Well that's exactly what I did. And this article covers how I got to that point<br />
and how my custom “builder” later evolved into<br />
<a href="https://github.com/tgirlcloud/easy-hosts">easy-hosts</a>.</p>
<h2>The research</h2>
<p>To start we should read the <a href="https://nixos.org/manual/nixpkgs/stable/index.html#module-system-lib-evalModules">documentation for <code>lib.evalModules</code></a>.<br />
From which we find that there are 4 arguments, but 3 that we really care about.<br />
Those being <code>modules</code>, <code>specialArgs</code> and <code>class</code>. The <code>modules</code> argument is a<br />
list of modules, which are files, functions or attrsets that will be merged and<br />
then evaluated. The <code>specialArgs</code> argument is a attrset of arguments that are<br />
passed to the modules, but are not evaluated with the file structure in mind.<br />
The <code>class</code> argument is a nominal type that ensures that only compatible<br />
modules are imported. This is a very important argument, as it allows us to<br />
have different modules for different systems.</p>
<p>However, I'm not much a fan of reading documentation. So instead lets dig into<br />
the source code and pick it apart. To find out what we need to get our custom<br />
<code>lib.nixosSystem</code> working. To do this I identified 4 main files in the nixpkgs<br />
repository:</p>
<ul>
<li><a href="https://github.com/NixOS/nixpkgs/blob/5223d4097bb2c9e89d133f61f898df611d5ea3ca/flake.nix">flake.nix</a></li>
<li><a href="https://github.com/NixOS/nixpkgs/blob/5223d4097bb2c9e89d133f61f898df611d5ea3ca/nixos/lib/eval-config.nix">nixos/lib/eval-config.nix</a></li>
<li><a href="https://github.com/NixOS/nixpkgs/blob/5223d4097bb2c9e89d133f61f898df611d5ea3ca/nixos/lib/eval-config-minimal.nix">nixos/lib/eval-config-minimal.nix</a></li>
<li><a href="https://github.com/NixOS/nixpkgs/blob/5223d4097bb2c9e89d133f61f898df611d5ea3ca/lib/modules.nix">lib/modules.nix</a></li>
</ul>
<p>These files may seem somewhat arbitrary, but they are listed in the order they<br />
are called. The <code>flake.nix</code> file has our <code>lib.nixosSystem</code> function that calls<br />
our <code>nixos/lib/eval-config.nix</code> file which is a light wrapper around our final<br />
<code>lib/modules.nix</code>. So lets walk through those files in order and see what<br />
exactly they do.</p>
<h3><code>flake.nix</code></h3>
<p>This file contains our <code>lib.nixosSystem</code> function, which takes <code>args</code> as an<br />
argument. The<br />
<a href="https://github.com/NixOS/nixpkgs/blob/b78b5ce3b29c147e193633659a0ea9bf97f6c0c0/flake.nix#L44">documentation</a><br />
lists a set of known arguments being <code>modules</code>, <code>specialArgs</code> and<br />
<code>modulesLocation</code>, it also specifies some additional legacy arguments <code>system</code><br />
and <code>pkgs</code> (both of which are now redundant).</p>
<p>The <code>lib.nixosSystem</code> then imports the <code>nixos/lib/eval-config.nix</code> file whilst<br />
passing <code>lib</code>, and the remaining <code>args</code> to it. It also sets <code>system</code> to<br />
<code>null</code> as well as adding <code>nixpkgs.flake.source</code> to nixpkgs output derivation.</p>
<h3>nixos/lib/eval-config.nix</h3>
<p>This file immediately points us to the fact that it is a “light wrapper” around<br />
<code>lib.evalModules</code>. This file also has a large collection of arguments most of<br />
which will be the defaults. A good example of this is <code>baseModules</code> which<br />
defaults to a list of modules from the nixpkgs repo. The most important<br />
arguments from this file are <code>specialArgs</code>, <code>lib</code> and <code>modules</code>. For the<br />
most part these come from the prior <code>flake.nix</code> file.</p>
<p>As we read down the file we notice that there are two additional modules that<br />
are going to be added. These are the <code>pkgsModule</code> and the <code>modulesModule</code>.<br />
These appear to be pretty strange names at first, but the <code>pkgsModule</code> will<br />
set <code>nixpkgs.system</code> if <code>system</code> was not null, and will set<br />
<code>nixpkgs.pkgs</code> if <code>pkgs</code> is not null. The <code>modulesModule</code> will add<br />
<code>config._module.args</code> as an attrset of <code>noUserModules</code>, <code>baseModules</code>,<br />
<code>extraModules</code> and <code>modules</code>. So now we know some of the arguments that are<br />
given to <code>lib.evalModules</code> lets see what that does.</p>
<h3>nixos/lib/eval-config-minimal.nix</h3>
<p>This file is a small wrapper upon <code>lib.evalModules</code>, but it gives us a little<br />
bit of guidance on how to use the <code>class</code> argument. As well as showing us the<br />
default for <code>modulePath</code> which is going to be passed as a special arg.</p>
<h3>lib/modules.nix</h3>
<p>This file won't add much to our understanding of <code>lib.evalModules</code>, but it<br />
still is the definition of the function, so it's good to keep in mind if we have<br />
any issues later down the line.</p>
<h2>The implementation</h2>
<p>Now that we have our key inputs of <code>class</code>, <code>modules</code> and <code>specialArgs</code> we can<br />
start implementing our own <code>lib.nixosSystem</code>.</p>
<h3>Getting the basics</h3>
<p>Let us start in our very own <code>flake.nix</code> by writing the following code. This<br />
will give us a basic template to work with and you, the reader, knows how to<br />
start.</p>
<pre><code>{
  inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";

  outputs = inputs: let
    mkSystem = import ./mksystem.nix { inherit inputs; };
  in {
    nixosConfigurations = {
      mySystem = mkSystem { };
    };
  };
}
</code></pre>
<p>Now that we have a very bare bone <code>flake.nix</code> we can get started on the<br />
<code>mkSystem</code> function. Let's also create the <code>mksystem.nix</code> file. We are going to<br />
add some basic args that we know we are going to need later such as <code>modules</code>,<br />
<code>specialArgs</code> and <code>class</code>. And we are going to add some defaults to these args<br />
such that we don't get any errors if they are not provided.</p>
<pre><code>{
  inputs ? throw "No inputs provided",
  lib ? inputs.nixpkgs.lib,
  ...
}:
{
  modules ? [ ],
  specialArgs ? { },
  class ? "nixos",
}: lib.evalModules {
  inherit modules specialArgs class;
}
</code></pre>
<h3>Adding <code>modulesPath</code></h3>
<p>Now that we have our basic template down. Let's start by adding the<br />
<code>modulesPath</code>, most people probably recognize this from when they first<br />
installed nix and read their <code>hardware-configuration.nix</code> file and saw<br />
something along the lines of <code>modulesPath + /installer/scan/not-detected.nix</code>.<br />
That is why we are starting with this, so that our <code>hardware-configuration.nix</code><br />
will work.</p>
<p>The key to this is going to be including <code>modulesPath</code> in our <code>specialArgs</code>.<br />
This is because <code>specialArgs</code> should only be used with arguments that need to<br />
be evaluated when resolving module structure.</p>
<pre><code>{
  inputs,
  lib ? inputs.nixpkgs.lib,
  ...
}:
{
  modules ? [ ],
  specialArgs ? { },
  class ? "nixos",
}: let
  modulesPath = "${inputs.nixpkgs}/nixos/modules";
in
lib.evalModules {
  inherit modules class;

  # here we are merging the user provided specialArgs with the modulesPath
  specialArgs = { inherit modulesPath; } // specialArgs;
}
</code></pre>
<h3>So close and yet so far</h3>
<p>Only adding <code>modulePath</code> is a bit useless however, so we can't exactly replace<br />
our <code>lib.nixosSystem</code>'s yet. So let's work on that. We are going to start by<br />
importing <code>baseModules</code>, this will provide us with a base set of modules that<br />
provide abstractions over configuring your system. We can use the <code>modulePath</code><br />
and get the module list.</p>
<pre><code>{
  inputs,
  lib ? inputs.nixpkgs.lib,
  ...
}:
{
  modules ? [ ],
  specialArgs ? { },
  class ? "nixos",
}: let
  modulesPath = "${inputs.nixpkgs}/nixos/modules";

  baseModules = import "${modulesPath}/module-list.nix";
in
lib.evalModules {
  inherit class;

  specialArgs = { inherit modulesPath; } // specialArgs;

  modules = baseModules ++ modules;
}
</code></pre>
<h3>It actually works?</h3>
<p>We now have a <em>mostly</em> functional replacement. Depending on your configuration<br />
may actually work as it is now! To keep progressing we are going to have to go<br />
back to the <code>modulesModule</code> from <a href="#nixos/lib/eval-config.nix">earlier</a>. we need this such that some nixpkgs<br />
modules will work, one of these is the <a href="https://github.com/NixOS/nixpkgs/blob/48f79c1d5168ce8e9b21a790be523c9a8f60046c/nixos/modules/misc/documentation.nix#L1">documentation module</a><br />
which will be a hard module to ignore, when so many people use it.</p>
<p>To fix this we are going to introduce a new module which contains<br />
<code>config._module.args</code> and takes a set of attrs that will be passed to each<br />
module. I'm sure most of you recognize these when writing a module and adding<br />
something like <code>{ pkgs, config, ... }</code> to the top of a file. The<br />
<code>config._module.args</code> option should be used when trying to pass arguments to<br />
all modules, but should not be evaluated with file structure in mind.</p>
<pre><code>{
  inputs,
  lib ? inputs.nixpkgs.lib,
  ...
}:
{
  modules ? [ ],
  specialArgs ? { },
  class ? "nixos",
}: let
  modulesPath = "${inputs.nixpkgs}/nixos/modules";

  baseModules = import "${modulesPath}/module-list.nix";
in
lib.evalModules {
  inherit class;

  specialArgs = { inherit modulesPath; } // specialArgs;

  modules = baseModules ++ modules ++ [
    {
      config._module.args = {
        inherit baseModules modules;
      };
    }
  ];
}
</code></pre>
<h3>Adding some of our own modules</h3>
<p>Even better, now we have completely replaced <code>lib.nixosSystem</code> with our own<br />
<code>mkSystem</code> function. But let's be real. That's not enough for us. We should<br />
start abstracting some common themes between our systems. Some big examples of<br />
this are <code>networking.hostName</code> and <code>nixpkgs.hostPlatform</code>. And while were at it<br />
lets also re-add the <code>nixpkgs.flake.source</code> from the original <code>lib.nixoSystem</code>,<br />
as well as adding <code>inputs</code> as a special arg. As most people do this anyway,<br />
I think it's a safe assumption we should add it. For further reading about<br />
passing inputs to modules check <a href="https://blog.nobbz.dev/2022-12-12-getting-inputs-to-modules-in-a-flake/">nobbz's blog on getting inputs to flake modules</a>.</p>
<pre><code>{
  inputs,
  lib ? inputs.nixpkgs.lib,
  ...
}:
name:
{
  modules ? [ ],
  specialArgs ? { },
  class ? "nixos",
}: let
  modulesPath = "${inputs.nixpkgs}/nixos/modules";

  baseModules = import "${modulesPath}/module-list.nix";
in
lib.evalModules {
  inherit class;

  specialArgs = { inherit inputs modulesPath; } // specialArgs;

  modules = baseModules ++ modules ++ [
    {
      config. = {
        _module.args = {
          inherit baseModules modules;
        };

        networking.hostName = name;

        nixpkgs.flake.source = inputs.nixpkgs.outPath;
      };
    }
  ];
}
</code></pre>
<p>I just added <code>name</code> as an additional argument to our <code>mkSystem</code> function. This<br />
allows us to set the hostname of our system. The way I opted to write it allows<br />
for us to use <code>mapAttrs</code> on our <code>nixosConfigurations</code>. This will mean that we<br />
need to change how the original <code>flake.nix</code> works though.</p>
<pre><code>{
  inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";

  outputs = inputs: let
    mkSystem = import ./mksystem.nix { inherit inputs; };
  in {
    nixosConfigurations = builtins.mapAttrs mkSystem {
      mySystem = { };
    };
  };
}
</code></pre>
<p>Furthermore, notice how I lied about settings <code>nixpkgs.hostPlatform</code>. If your curious why, maybe you<br />
should read <a href="https://isabelroses.com/blog/im-not-mad-im-disapointed-10">my last blog post about it</a>. (Shameless plug)</p>
<h3>Darwin compatibility</h3>
<p>One of the main reasons I wanted to make this was to support<br />
<code>lib.darwinSystem</code>. So let's address that now.</p>
<p>To introduce Darwin support we are allowing users to set the <code>class</code><br />
argument to <code>darwin</code> from there we can determine what modules to import.<br />
As a result of this you may notice that Darwin has a different set of<br />
modules which introduced some new options to set for this system type. This<br />
includes <code>nixpkgs.source</code> and <code>darwinVersionSuffix</code> and <code>darwinRevision</code>. Some<br />
of these are for commands like <code>darwin-version</code>.</p>
<p>You may also notice that we had to add <code>system = eval.config.system.build.toplevel</code><br />
back into the final eval produced by our Darwin eval. This is required to swap<br />
to the configuration, otherwise it won't work at all. This is because the<br />
<code>system</code> output is used by <code>darwin-rebuild</code>, to identify the final build<br />
output.</p>
<pre><code>{
  inputs,
  lib ? inputs.nixpkgs.lib,
  ...
}:
name:
{
  modules ? [ ],
  specialArgs ? { },
  class ? "nixos",
}: let
  # this is new? what is it?
  # I'm glad you asked, this is a nice way of checking if we have our darwin and nixpkgs inputs
  nixpkgs = inputs.nixpkgs or (throw "No nixpkgs input found");
  darwin = inputs.darwin or inputs.nix-darwin or (throw "No nix-darwin input found");

  modulesPath = if class == "darwin" then "${darwin}/modules" else "${nixpkgs}/nixos/modules";

  baseModules = import "${modulesPath}/module-list.nix";

  eval = lib.evalModules {
    inherit class;

    specialArgs = { inherit inputs modulesPath; } // specialArgs;

    modules = baseModules ++ modules ++ [
      {
        config. = {
          _module.args = {
            inherit baseModules modules;
          };

          networking.hostName = name;

          nixpkgs.flake.source = nixpkgs.outPath;
        };
      }
    ] ++ lib.optionals (class == "darwin") [
      {
        config = {
          nixpkgs.source = nixpkgs.outPath;

          system = {
            checks.verifyNixPath = false;

            darwinVersionSuffix = ".${darwin.shortRev or darwin.dirtyShortRev or "dirty"}";
            darwinRevision = darwin.rev or darwin.dirtyRev or "dirty";
          };
        };
      }
    ];
  };
in
  if class == "darwin" then (eval // { system = eval.config.system.build.toplevel; }) else eval;
</code></pre>
<h3>The final touch</h3>
<p>The final and maybe the best bit is adding <code>inputs'</code>. For those who are unaware<br />
of flake-parts, you probably are not aware of the greatness that is <code>inputs'</code>.<br />
The diff below shows the advantage of using <code>inputs'</code> over <code>inputs</code> for<br />
accessing packages.</p>
<pre><code>- inputs.input-name.packages.${pkgs.stdenv.hostPlatform.system}.package-name
+ inputs'.input-name.packages.package-name
</code></pre>
<p>Is that not awesome? So how can we replicate that for ourselves?</p>
<p>What we will need to do is map over all inputs, and their outputs and select<br />
the output dependent on the host platform, if a system dependent output exists,<br />
otherwise it will leave it as is. We can achieve that with the following code:</p>
<pre><code>inputs' = lib.mapAttrs (_: lib.mapAttrs (_: v: v.${config.nixpkgs.hostPlatform} or v)) inputs;
</code></pre>
<p>Or if you are using flake-parts, you may prefer using the following code instead:</p>
<pre><code>withSystem config.nixpkgs.hostPlatform ({ inputs', ... }: { inherit inputs'; });
</code></pre>
<p>So let's add that to our <code>mkSystem</code> function.</p>
<pre><code>{
  inputs,
  lib ? inputs.nixpkgs.lib,
  ...
}:
name:
{
  modules ? [ ],
  specialArgs ? { },
  class ? "nixos",
}: let
  nixpkgs = inputs.nixpkgs or (throw "No nixpkgs input found");
  darwin = inputs.darwin or inputs.nix-darwin or (throw "No nix-darwin input found");

  modulesPath = if class == "darwin" then "${darwin}/modules" else "${nixpkgs}/nixos/modules";

  baseModules = import "${modulesPath}/module-list.nix";

  eval = lib.evalModules {
    inherit class;

    specialArgs = { inherit inputs modulesPath; } // specialArgs;

    modules = baseModules ++ modules ++ [
      ({ config, ... }: {
        config = {
          _module.args = {
            inherit baseModules modules;

            inputs' = lib.mapAttrs (_: lib.mapAttrs (_: v: v.${config.nixpkgs.hostPlatform} or v)) inputs;
          };

          networking.hostName = name;

          nixpkgs.flake.source = nixpkgs.outPath;
        };
      })
    ] ++ lib.optionals (class == "darwin") [
      {
        config = {
          nixpkgs.source = nixpkgs.outPath;

          system = {
            checks.verifyNixPath = false;

            darwinVersionSuffix = ".${darwin.shortRev or darwin.dirtyShortRev or "dirty"}";
            darwinRevision = darwin.rev or darwin.dirtyRev or "dirty";
          };
        };
      }
    ];
  };
in
  if class == "darwin" then
    (eval // { system = eval.config.system.build.toplevel; })
  else
    eval;
</code></pre>
<h2>Conclusion</h2>
<p>And that's it! We have a fully functional <code>mkSystem</code> function that can replace<br />
both <code>lib.nixosSystem</code> and <code>lib.darwinSystem</code>. This was quite the task, and<br />
although this blog post seems to reduce the quite simple. I've spent a lot of<br />
time on this, both when researching how to create the custom builder and<br />
writing and maintaining the latest rendition in the form of a flake module<br />
called <a href="https://github.com/tgirlcloud/easy-hosts">easy-hosts</a>. If you enjoyed<br />
this post, please consider donating on <a href="https://ko-fi.com/isabelroses">ko-fi</a><br />
or <a href="https://github.com/sponsors/isabelroses">github sponsors</a>.</p>
]]></content>
    </entry>
    <entry>
        <title type="html"><![CDATA[I'm not mad, I'm disappointed]]></title>
        <id>https://isabelroses.com/blog/im-not-mad-im-disappointed/</id>
        <link href="https://isabelroses.com/blog/im-not-mad-im-disappointed/"/>
        <updated>2025-01-09T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[A short rant about the state of your nix code]]></summary>
        <content type="html"><![CDATA[<h2>Introduction</h2>
<p>When I see another friend of mine start using nix my eyes glisten with the<br />
knowledge there is still hope for their lost soul yet. But then I observe their<br />
copy-pasted code and see all the bad practices everywhere. <em>Sighhhh</em>. Time to<br />
explain to you everything wrong about what you're doing in your nix code. Make<br />
sure to pay attention and make some notes because I don't want to explain it<br />
again.</p>
<h3><code>system</code> and <code>lib.nixosSystem</code></h3>
<p>Often I see people passing the <code>system</code> argument to <code>lib.nixosSystem</code>. However,<br />
you should not do this! This is because "legacy input" and will straight up be<br />
ignored because of your autogenerated <code>hardware-configuration.nix</code> file, which<br />
will set system for you. So to rectify this we can instead set<br />
<code>nixpkgs.hostPlatform</code> to the desired system.</p>
<pre><code>{
  inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";

  outputs = inputs: {
    nixosConfigurations = {
      # this host contains our `system` antipattern, which we want to avoid
      myBadHost = inputs.nixpkgs.lib.nixosSystem {
        system = "x86_64-linux";
        modules = [ ./hardware-configuration.nix ];
      };

      # this is our fixed host, notice how it uses `nixpkgs.hostPlatform`
      myGoodHost = inputs.nixpkgs.lib.nixosSystem {
        modules = [
          ./hardware-configuration.nix
          { nixpkgs.hostPlatform = "x86_64-linux"; }
        ];
      };
    };
  };
}
</code></pre>
<p>For further reading about this topic please consult<br />
<a href="https://github.com/NixOS/nixpkgs/blob/332e6030634ce7701496564d4c70ae8209919931/flake.nix#L54">nixpkgs@332e603/flake.nix#L54</a>.</p>
<p>To such I am usually asked "But I want to be able to track other nixpkgs<br />
revisions or inputs, then don't I have to? Or set a <code>specialArg</code> or something?".<br />
NO! Almost never do you want to use <code>specialArgs</code>. <code>specialArgs</code> are great but<br />
often overused for things that the module system was designed to do better. Now<br />
to answer the main question, you can still do that. Allow me to introduce you<br />
to <code>pkgs.stdenv.hostPlatform.system</code> and its alias <code>pkgs.system</code>. So now we can<br />
do something more like the following:</p>
<pre><code>{
  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
    # notice our new nixpkgs input that is pinned to a stable version
    nixpkgs-stable.url = "github:NixOS/nixpkgs/nixos-24.05";
  };

  #         v this here is destructuring the inputs, and the `@` will allow us to refer to `inputs` as a whole
  outputs = { nixpkgs, ... } @ inputs: {
    nixosConfigurations.mySystem = inputs.nixpkgs.lib.nixosSystem {
      # notice we are now we are now passing the inputs as a specialArg
      specialArgs = {
        inherit inputs;

        # do NOT add `system` as a specialArg
        system = "x86_64-linux";
      };

      modules = [
        # instead notice how we can use `pkgs.system` to refer to the system output from nixpkgs 24.05
        # and we didn't need to add `system` as a specialArg
        ({ pkgs, inputs, ... }: {
          environment.systemPackages = [
            inputs.nixpkgs-stable.legacyPackages.${pkgs.system}.nil
          ];

          # this didn't change from our previous example
          nixpkgs.hostPlatform = "x86_64-linux";
        })
      ];
    };
  };
}
</code></pre>
<p>Overall, <code>system</code> being an input to <code>lib.nixosSystem</code> was so irritating me so<br />
much that I made several bluesky posts complaining about it. And went on to<br />
make <a href="https://github.com/isabelroses/easy-hosts">easy-hosts</a> as an abstraction<br />
around making systems for your systems that will prevent you from making these<br />
mistakes, and will force you to use the module system more.</p>
<h3>system dependent overlays</h3>
<p>I sadly am not kidding. These are real. I have seen people do this.</p>
<p>First off, overlays should not be consumed with a system like<br />
<code>overlays.x86_64-linux</code> or <code>overlays.x86_64-linux.default</code>. Please stop<br />
yourself if you ever get here. Instead, overlays should be consumed like<br />
<code>source.overlays.name</code>, to improve upon this consider the following template:</p>
<pre><code>{
  # inputs if you need them

  outputs = inputs: {
    overlays = let
      pkgs = inputs.nixpkgs.legacyPackages.x86_64-linux;
    in {
      # these are all incorrect variations of the same code, that i have seen
      # or would be generate by some kind of code
      x86_64-linux = final: prev: {
        hello = pkgs.callPackage ./default.nix { };
      };

      # this is bad
      x86_64-linux.default = final: prev: {
        hello = pkgs.callPackage ./default.nix { };
      };

      # this is still bad
      default.x86_64-linux = final: prev: {
        hello = pkgs.callPackage ./default.nix { };
      };

      # notice in this example we removed any reference to `pkgs` and instead we use `prev`
      # this means that the overlay is not system dependent
      default = final: prev: {
        hello = prev.callPackage ./default.nix { };
      };
    };

    # any additional outputs go here
  };
}
</code></pre>
<p>Notice how here I used <code>prev</code> instead of <code>pkgs</code> or anything alike because we do<br />
not need to know what the system is here. And if you do wish to refer to the<br />
system recall how earlier I spoke of <code>pkgs.system</code> well <code>prev</code> like <code>pkgs</code> will<br />
also expose <code>system</code>, so you can use <code>prev.system</code>.</p>
<h3>Stop importing weirdly</h3>
<p>You do not need this needless import. This is a waste of nix eval time. Since<br />
users will place <code>inputs.&lt;source&gt;.nixosModules.default</code> in their flake<br />
<code>imports</code> it will load those files for them, so you don't need to import unless<br />
you need to pass args.</p>
<pre><code>{
  inputs = { };

  outputs = inputs: {
    nixosModules.default = import ./module.nix;
  };
}
</code></pre>
<p>Here is a more acceptable flake, where we only import because we are passing<br />
our inputs to our other module.</p>
<pre><code>{
  inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";

  outputs = inputs: {
    nixosModules = {
      noImport = ./module.nix;
      importWithArgs = import ./module.nix { inherit inputs; };
    };
  };
}
</code></pre>
<h3>Stop importing nixpkgs</h3>
<p>This one is somewhat of a subset of the previous problem, where people import<br />
needlessly. nixpkgs already provides <code>legacyPackages</code> which is a<br />
pre-constructed set of packages for you. So you don't need to import <code>nixpkgs</code>.</p>
<pre><code>{
  inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";

  outputs = { nixpkgs, ... }: let
    pkgs = import nixpkgs { };
    # or
    pkgs = import nixpkgs { system = "aarch64-linux"; };
  in {
    # imagine the rest of the file
  };
}
</code></pre>
<p>Importing <code>nixpkgs</code> without a system will use <code>builtins.currentSystem</code> which is<br />
impure, this means that it needs to use information that may not be<br />
reproducible like your system type. Instead, consider writing:</p>
<pre><code>{
  inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";

  outputs = { nixpkgs, ... }: let
    system = "aarch64-linux";
    pkgs = nixpkgs.legacyPackages.${system};
  in {
    packages.${system}.default = pkgs.callPackage ./default.nix {};
  };
}
</code></pre>
<p>You may also recall my earlier posts titled <a href="https://isabelroses.com/blog/experimenting-with-nix-7" target="_blank" rel="noopener">Experimenting with<br />
nix</a> and or <a href="https://isabelroses.com/blog/nix-shells-8">Dev<br />
dependencies</a>, where I refer to a<br />
function called <code>forAllSystems</code> to improve upon this pattern further. For the<br />
sake of completion I will bring one of those examples back and use it to<br />
generate the system based outputs of packages.</p>
<pre><code>{
  inputs.nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";

  outputs = { nixpkgs, ... }: let
    forAllSystems =
      function:
      nixpkgs.lib.genAttrs nixpkgs.lib.systems.flakeExposed (
        system: function nixpkgs.legacyPackages.${system}
      );
  in {
    packages = forAllSystems (pkgs: {
      default = pkgs.callPackage ./default.nix { };
    });
  };
}
</code></pre>
<p>You may however have noticed this won't work if you need to access unfree or<br />
broken packages. In which case I would recommend modifying the function to<br />
align with the following:</p>
<pre><code>forAllSystems =
  function:
  nixpkgs.lib.genAttrs nixpkgs.lib.systems.flakeExposed (
    system: function (
      import nixpkgs {
        inherit system;
        config.allowUnfree = true;
      }
    )
  );
</code></pre>
<p>The key difference in the above example is that we now are importing <code>nixpkgs</code><br />
and as such we now have access to <code>nixpkgs.config</code> and <code>nixpkgs.overlays</code> if<br />
you so wish to use those.</p>
<p>This brings me onto my final point,</p>
<h3>Stop using flake-utils</h3>
<p>flake-utils is often an extra dependency added for no good reason. The main<br />
purpose for using it more modernly is its overridable systems, they do this<br />
through an input from the <a href="https://github.com/nix-systems">nix-systems</a> GitHub<br />
org. But more typically no one uses it for that, in fact most people use<br />
flake-utils for its ability to create system dependent outputs. This is what<br />
leads to the issues with <code>system</code> dependent <code>overlays</code>. But recall, I've<br />
already covered all of what you need to make your own flake-utils, through the<br />
usage of the <code>forAllSystems</code> function.</p>
<p>However, some of you may still want those overridable systems. In which case<br />
please consider writing this flake like this instead:</p>
<pre><code>{
  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
    systems.url = "github:nix-systems/default";
  };

  outputs = { nixpkgs, systems, ... }: let
    forAllSystems =
      function:
      nixpkgs.lib.genAttrs (import systems) (
        system: function nixpkgs.legacyPackages.${system}
      );
  in {
    packages = forAllSystems (pkgs: {
      default = pkgs.callPackage ./default.nix { };
    });

    overlays.default = final: prev: {
      hello = prev.callPackage ./default.nix { };
    };
  };
}
</code></pre>
<h2>Conclusion</h2>
<p>I was never mad at you, I was just disappointed. But now you know better, so go<br />
forth and write better nix code.</p>
]]></content>
    </entry>
    <entry>
        <title type="html"><![CDATA[2024 Wrapped]]></title>
        <id>https://isabelroses.com/blog/2024-wrapped/</id>
        <link href="https://isabelroses.com/blog/2024-wrapped/"/>
        <updated>2024-12-29T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[like last year, but this year]]></summary>
        <content type="html"><![CDATA[<p>Welcome to my 2024 Wrapped! This is a recall of all that happened this year, and a look at what's to come in 2025.</p>
<h2>2024 in Review</h2>
<h3>New technologies</h3>
<ul>
<li><a href="https://wezfurlong.org/wezterm/">wezterm</a> - I started using this terminal emulator due to its multiplatform support and its depth of configurablity.</li>
<li><a href="https://ghostty.org/">ghostty</a> - I started using this one while it was still in beta, and its been my go-to terminal emulator on macOS since.</li>
<li><a href="https://lix.systems/">lix</a> - This is a fork of nix and I've been using it for its cli stability and improved performance.</li>
<li><a href="https://github.com/charmbracelet/freeze">freeze</a> - A lovely little tool for taking screenshots of code and sharing them with friends.</li>
</ul>
<p>Switching from WezTerm to Ghostty might seem odd, but the decision was clear: Ghostty’s native implementation offered unmatched speed and macOS integration, making it the superior choice for my needs.</p>
<h3>Languages</h3>
<p>This year I didn't learn any new languages, but rather I decided to focus on improving my existing skills in Go, Java and Rust. I found during that time that I really do prefer Rust, this is largly due to the developer support through rust-analyzer and cargo clippy.</p>
<h3>New Projects</h3>
<p>The year began with a significant project; rewriting my website in Go. The process was tedious, but the outcome was much preferable. However, I still had my reservations about Go's templating engine. While it’s good, I felt there was room for improvement.</p>
<p>And that is where Tera, a rust based templating engine like jinja2 and Django. I first encountered Tera through the <a href="https://github.com/catppuccin/whiskers">Catppuccin Whiskers</a> project. Tera’s extensablity convinced me to rewrite my site again, this time in Rust. The result exceeded my expectations, and Rust’s ecosystem further cemented my love for the language.</p>
<p>However, before the website was rewritten in rust I tried to create a new Go project <a href="https://github.com/isabelroses/izrss">izrss</a> which was a terminal RSS feed reader. It's not feature rich but it was created to look pretty due to my dissatisfaction with newsboat.</p>
<p>This year, I abstracted by neovim configuration incredibly through the use of <a href="https://github.com/catgardens/chainix">chainix</a>. I later decided to remove a significant amount of the nix abstraction through <a href="https://github.com/isabelroses/nvim">izvim</a>, where I made a package around the lua rather then using nix to generate the lua. During this year I also wrote the <a href="https://github.com/charm-and-friends/freeze.nvim">freeze.nvim</a> plugin, which I wrote a blog post about. You can find the jeorney here: <a href="https://isabelroses.com/blog/writing-a-neovim-plugin-6">neovim blog post</a>.</p>
<h2>My goals for 2025</h2>
<p>I would like to get some more work done on my Wayland compositor. I would also love to grow my social media presence, and maybe even write 3 blog posts, which would be my apology for not writing one in 6 months. And you all know me; rewrite my nix config once or twice again... per week.</p>
]]></content>
    </entry>
    <entry>
        <title type="html"><![CDATA[Dev dependencies]]></title>
        <id>https://isabelroses.com/blog/nix-shells/</id>
        <link href="https://isabelroses.com/blog/nix-shells/"/>
        <updated>2024-06-07T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Nix is truly an enlightening experience]]></summary>
        <content type="html"><![CDATA[<h2>Introduction</h2>
<p>Handling dependencies is hard to say the least. Issue after issue of missing and conflicting dependencies. And no good way to fix this util <s>Docker</s> Nix came along. This blog post will go over the start to end of getting a solid nix dev entiroment setup for all your projects.</p>
<h2>Getting started</h2>
<p>First we are going to create a basic file tree, such that you can understand how your system may look like.</p>
<pre><code>.
├── default.nix
├── shell.nix
├── flake.lock
└── flake.nix
</code></pre>
<p>At first when seeing this tree you may think why are we polluting our root directory with all these files. But in this is a good way to help us write less Nix!</p>
<p>Now, we're going to create a flake.nix file. Unlike some other files mentioned later in this post, it's quite agnostic about the language of your project. Since I won't be explaining this part in detail, I recommend you read my previous blog post <a href="https://isabelroses.com/blog/7" target="_blank" rel="noopener">experimenting with nix</a>.</p>
<pre><code>{
  inputs.nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";

  outputs =
    { nixpkgs, ... }:
    let
      forAllSystems =
        function:
        nixpkgs.lib.genAttrs nixpkgs.lib.systems.flakeExposed (
          system: function nixpkgs.legacyPackages.${system}
        );
    in
    {
      packages = forAllSystems (pkgs: {
        default = pkgs.callPackage ./default.nix { };
      });

      devShells = forAllSystems (pkgs: {
        default = pkgs.callPackage ./shell.nix { };
      });
    };
}
</code></pre>
<p>Now we have something more useable for our <code>flake.nix</code> file we can now start making our <code>default.nix</code> file and <code>shell.nix</code> files.</p>
<p>We are going to start with the <code>default.nix</code> file for a basic Rust project. We start with the <code>default.nix</code> file to identify the dependencies of our project, which is particularly easy to do with Nix since the build system is isolated.</p>
<pre><code>{
  lib,
  darwin,
  stdenv,
  openssl,
  pkg-config,
  rustPlatform,
}:
rustPlatform.buildRustPackage {
  pname = "kittysay"; # The name of the package
  version = "0.5.2"; # The version of the package

  # You can use lib here to make a more accurate source
  # this can be nice to reduce the amount of rebuilds
  # but thats out of scope for this post
  src = ./.; # The source of the package

  # The lock file of the package, this can be done in other ways
  # like cargoHash, we are not doing it in this case because this
  # is much simpler, especially if we have access to the lock file
  # in our source tree
  cargoLock.lockFile = ./Cargo.lock;

  # The runtime dependencies of the package
  buildInputs =
    [ openssl ]
    ++ lib.optionals stdenv.isDarwin (
      with darwin.apple_sdk.frameworks;
      [
        Security
        CoreFoundation
        SystemConfiguration
      ]
    );

  # programs and libraries used at build-time that, if they are a compiler or
  # similar tool, produce code to run at run-time—i.e. tools used to build the new derivation
  nativeBuildInputs = [ pkg-config ];

  meta = {
    license = lib.licenses.mit;
    mainProgram = "kittysay";
  };
}
</code></pre>
<p>You may have noticed <code>buildInputs</code> and <code>nativeBuildInputs</code>, which contain the dependencies of the project. At a basic level <code>buildInputs</code> are the dependencies that are needed at runtime whilst <code>nativeBuildInputs</code> are the dependencies that are only needed during the build process. So why does that matter?</p>
<p>Well these dependencies can be reused in our <code>shell.nix</code> file, we can do this like so:</p>
<pre><code>{
  clippy,
  rustfmt,
  callPackage,
  rust-analyzer,
}:
let
  mainPkg = callPackage ./default.nix { };
in
mainPkg.overrideAttrs (prev: {
  nativeBuildInputs = [
    # Additional Rust tooling
    clippy
    rustfmt
    rust-analyzer
  ] ++ (prev.nativeBuildInputs or [ ]);
})
</code></pre>
<p>In this example we are adding additional Rust tooling to our shell. This is because those inputs are not there in our dependencies that we have defined in our <code>default.nix</code> file.</p>
<h2>But what if I don't want to have <code>default.nix</code> file?</h2>
<p>Nix still has a solution for this — <code>pkgs.mkShell</code>.</p>
<pre><code>{
  clippy,
  mkShell,
  rustfmt,
  rust-analyzer,
}:
mkShell {
  packages = [
    clippy
    rustfmt
    rust-analyzer
  ];
}
</code></pre>
<p>What if you also require all the dependencies of another program? I've personally never needed this, but you can do it like so:</p>
<pre><code>{
  mkShell,
  rust-analyzer,
}:
mkShell {
  inputsFrom = [
    rust-analyzer
  ];
}
</code></pre>
<p>Now you have a shell with all the dependencies of <code>rust-analyzer</code> and any other dependencies you have defined in your <code>shell.nix</code> file.</p>
<h2>Some common misconceptions</h2>
<h3>Should I use <code>nativeBuildInputs</code> or <code>buildInputs</code>?</h3>
<p>Well in this case it doesn't matter! All packages that are put into the <code>mkShell</code> will be merged into one attribute, and will all be available to the end user. So really the best thing to do here is use <code>packages</code> since it's documented and therefore a good practice.</p>
<p>If you would like proof they get merged into the one attribute here is the <a href="https://github.com/NixOS/nixpkgs/blob/88130cec79267ea323ed7a31e60affbd9ca0cc3d/pkgs/build-support/mkshell/default.nix#L17-24">source</a>:</p>
<pre><code>mergeInputs = name:
  (attrs.${name} or [ ]) ++
  # 1. get all `{build,nativeBuild,...}Inputs` from the elements of `inputsFrom`
  # 2. since that is a list of lists, `flatten` that into a regular list
  # 3. filter out of the result everything that's in `inputsFrom` itself
  # this leaves actual dependencies of the derivations in `inputsFrom`, but never the derivations themselves
  (lib.subtractLists inputsFrom (lib.flatten (lib.catAttrs name inputsFrom)));
</code></pre>
<h3>How about the environment variables?</h3>
<p>I see a number of people using <code>shellHook</code>, but I'd argue that this is wrong — ideally we should use <code>env</code>. But not for any technical reason, since they fundamentally do the same thing and env is converted into a shellHook, but rather for readability.</p>
<ol>
<li>The worst way:</li>
</ol>
<pre><code>shellHook = ''
  export RUSTFLAGS="-lEGL -lwayland-client"
  export LD_LIBRARY_PATH=${"$LD_LIBRARY_PATH:${libglvnd}/lib";}
'';
</code></pre>
<ol>
<li>A bit better:</li>
</ol>
<pre><code>RUSTFLAGS = "-lEGL -lwayland-client";
LD_LIBRARY_PATH = lib.makeLibraryPath [ libglvnd ];
</code></pre>
<ol>
<li>The best way:</li>
</ol>
<pre><code>env = {
  RUSTFLAGS = "-lEGL -lwayland-client";
  LD_LIBRARY_PATH = lib.makeLibraryPath [ libglvnd ];
};
</code></pre>
<p>After reading all three of these example, I hope you understand why I personally prefer the third example. Since its clear that its exporting environment variables, and it's also clear what the environment variables are or will be. It should also be noted that as of release 24.05 the recommended manor of exporting environment variables in a shell is example 2, but uses mixes between example 1 and 2.</p>
<h3>But you didn't mention <code>pkgs.mkShellNoCC</code>?</h3>
<p>The difference between difference these two is that <code>mkShell</code> includes a C compiler in the shell environment, whilst <code>mkShellNoCC</code> does not. So in a situation where you know you won't need any C compiler or related technogies its better to use <code>pkgs.mkShellNoCC</code>.</p>
<h3>How about using my overlay?</h3>
<p>My personal favorite has to be <a href="https://github.com/oxalica/rust-overlay">oxalica/rust-overlay</a>, so that is what we are going to use in this example. I heavily advice mainly using the flake to get reproducible outputs.</p>
<pre><code>{
  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
    rust-overlay.url = "github:oxalica/rust-overlay";
  };

  outputs =
    { nixpkgs, rust-overlay, ... }:
    let
      forAllSystems =
        function:
        nixpkgs.lib.genAttrs nixpkgs.lib.systems.flakeExposed (
          system: let
            overlays = [ (import rust-overlay) ];
            pkgs = import nixpkgs { inherit system overlays; };
          in
            function pkgs;
        );
    in
    {
      devShells = forAllSystems (pkgs: {
        default = pkgs.mkShell {
          packages = [
            rust-bin.stable.latest.minimal
          ];
        };
      });
    };
}
</code></pre>
<p>But you didn't include a shell.nix that time? I know thats because I want to keep this as reproducible as possible and that beacomes difficult with overlays and staying backwards compatible. The below example shows how you could do this though its not exactly pretty.</p>
<pre><code>(import &lt;nixpkgs&gt; {
  overlays = [ (import (builtins.fetchTarball "https://github.com/oxalica/rust-overlay/archive/master.tar.gz")) ];
}).callPackage (
{ mkShell, rust-bin }:
mkShell {
  packages = [
    rust-bin.stable.latest.minimal
  ];
}) {}
</code></pre>
<h2>The cherry on top</h2>
<h3>direnv</h3>
<p><a href="https://github.com/nix-community/nix-direnv">nix-direnv</a> is a tool that allows you to have a <code>.envrc</code> file in your project that will automatically load the nix shell when you enter the directory. This is a great way to make sure that you are always in the correct environment.</p>
<p>The files from before will not change but now the <code>.envrc</code> file will be added to the project, and this will contain something like so:</p>
<pre><code>if has nix_direnv_version; then
  use flake
fi
</code></pre>
<h3>Templates</h3>
<p>Nix allows you to create reproducible templates for your project, so you only have to set these up one time and then you can reuse them for all your projects.</p>
<p>For example you can use my templates like so <code>nix flake init -t github:tgirlcloud/nix-templates#go</code> which will create a new go project in your current directory, or you can use <code>nix flake new -t github:tgirlcloud/nix-templates#rust cheese</code> which will create a new directory called <code>cheese</code> with the rust template defined <a href="https://github.com/tgirlcloud/nix-templates/tree/main/rust">here</a>.</p>
<p>Here we will create a quick example for a basic flake, but you can do this with littrally anything you want.</p>
<p>First lets define what our tree will look like as we did before:</p>
<pre><code>.
├── flake.nix
└── comfy
    ├── default.nix
    ├── shell.nix
    └── flake.nix
</code></pre>
<p>Now lets define our <code>flake.nix</code>:</p>
<pre><code>{
  outputs = _: {
    templates = {
      comfy = {
        path = ./comfy;
        description = "A comfy template";
      };
    };
  };
}
</code></pre>
<p>You may have notice that we are not taking any inputs and are only producing outputs and thats because we don't need a package set here. Then our <code>comfy/*</code> files will look like the ones we defined at the beginning of this post.</p>
<h2>Wrapping up</h2>
<p>Nix is a great tool for managing dependencies, and I hope this post has helped you understand how to use it in your projects. If you have any further questions feel free to <a href="mailto:isabel@isabelroses.com">email me</a> or join <a href="https://isabelroses.com/api/discord" target="_blank" rel="noopener">my discord server</a>. Thanks for reading!!! And If you really enjoyed the post please consider donating so I can keep doing this kind of thing on <a href="https://ko-fi.com/isabelroses">kofi</a> or <a href="https://github.com/sponsors/isabelroses">GitHub Sponsors</a>.</p>
]]></content>
    </entry>
    <entry>
        <title type="html"><![CDATA[Experimenting with Nix]]></title>
        <id>https://isabelroses.com/blog/experimenting-with-nix/</id>
        <link href="https://isabelroses.com/blog/experimenting-with-nix/"/>
        <updated>2024-05-25T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[A little bit of fun with Nix]]></summary>
        <content type="html"><![CDATA[<h2>Introduction</h2>
<p>My latest addiction is <a href="https://nixos.org">Nix</a>.</p>
<p>Those who have read my previous posts have probably noticed that I have made a few references to it here and there but nothing too deep, that is until now.</p>
<p>This article is going to detail some of the oddities and tips I have learned along the way. Just a fair warning, I am still learning and nix is ever-growing so some of these details may not be the same tomorrow or the next day. Also its a complete mess of hundreds of ideas that I have so prepare yourself.</p>
<h2>shell.nix the file I have everywhere</h2>
<p>I don't have <strong>any</strong> dev programs permanently installed on my system, I have a <code>shell.nix</code> file in every project that I work on. This file contains all the dependencies that I need to work on that project. This is a great way to keep your system clean and to keep your dependencies in check.</p>
<pre><code>{ pkgs ? import &lt;nixpkgs&gt; {} }:
pkgs.mkShell {
  nativeBuildInputs = with pkgs; [
    go
    gopls
  ];
}
</code></pre>
<h2>Easy things that are not so easy</h2>
<p>Recently I undertook the task of <a href="https://github.com/NixOS/nixpkgs/pull/314910">removing all unused occurrences of the <code>fetchFromGitHub</code> argument</a>, at the time this was about 64 items, though I only removed 57. But here is why, some files like zig for example contain a <code>generic.nix</code> file which has commonalities between all the zig files so to reduce the work here they import that file. And to do that they need to pass <code>fetchFromGitHub</code> otherwise, you will error out. This is unexpected since <a href="https://github.com/astro/deadnix"><code>deadnix</code></a> cannot detect these the import like that. Oh and for anyone curious what kind of monstrosity of a command made this easy:</p>
<pre><code>deadnix pkgs -o json |
  jq -n 'select(.results[].message == "Unused lambda pattern: fetchFromGitHub") | .file' |
  args -i nvim {}
</code></pre>
<h2>NURs</h2>
<p>I have never understood the point of NURs, they often ship outdated nixpkgs, and don't offer much more than putting the package on nixpkgs instead. I will be the first to admit I have my repo reminiscent of a NUR but it's not the same, that repo ships nightly packages because occasionally I want nightly packages for my programs and these should be consumed as an overlay.</p>
<h3>Pinning packages</h3>
<p>This feels somewhat of an extension to NURs since most of my packages are pins of specific working commits. You can achieve this with fetchers like <code>fetchFromGitHub</code> but my personal favorite is npins (and occasionally nvfetcher) since you can run git versions of a package. Then you can override the source with something reminiscent of this:</p>
<pre><code>_: prev: {
  catppuccin-gtk = prev.callPackage (args:
    (prev.catppuccin-gtk.override args).overrideAttrs (attrs: {
      src = pins.catppuccin-gtk;
    })
  ) {};
}
</code></pre>
<pre><code>fn expand_on_this_in_another_post() {
  todo!(); # yes this is a bad rust joke
}
</code></pre>
<h2>Lib</h2>
<p>The <code>lib</code> or library is a collection of expressions that are commonly used in nix because of this they are official and stable.</p>
<h3>forAllSystems</h3>
<p>This is not a part of the official <code>lib</code> but is featured in the official <a href="https://github.com/NixOS/templates/blob/c57ac1ea60ef97bdce2f13e12b849f0ca5eaffe9/go-hello/flake.nix#L20">nix templates repo</a> though maybe not in this exact format. What this does is provide an output for all of the system types that are in the list, this is important for packages that can work on multiple systems since it helps you prevent repeating the same code several times.</p>
<pre><code>forAllSystems =
  function:
  nixpkgs.lib.genAttrs [
    "x86_64-Linux"
    "aarch64-Linux"
    "x86_64-darwin"
    "aarch64-darwin"
  ] (system: function nixpkgs.legacyPackages.${system});
</code></pre>
<h3>lib. filesystem.packagesFromDirectoryRecursive</h3>
<p>One of my current favorites has to be <code>lib.filesystem.packagesFromDirectoryRecursive</code> or its alias <code>lib.packagesFromDirectoryRecursive</code>. This function allows you to generate an attrset of your packages from a given directory. Perhaps the best thing about this lib expression is how well-documented it is. Or perhaps it is flexible.<br />
I recently used this to generate a collection of packages you can find them on my GitHub repo <a href="https://github.com/isabelroses/beapkgs">isabelroses/beapkgs</a>. In this case, I needed to <code>callPackage</code> with <code>npins</code> such that all packages would have access to their source. And guess what this is double the learning opportunity since we can use the previously stated <code>forAllSystems</code>.</p>
<pre><code>packages = forAllSystems (
  pkgs:
  lib.packagesFromDirectoryRecursive {
    callPackage = lib.callPackageWith (pkgs // { pins = import ./npins; });
    directory = ./packages;
  });
</code></pre>
<h3>lib.trivial.pipe</h3>
<p>Similar to the concept of a pipe in bash, this function allows you to pipe the output of one function to the input of another. This is useful when you have a function that returns a value that you want to chain together several operations.</p>
<pre><code>lib.trivial.pipe 2 [
  (x: x + 2)  # 2 + 2 = 4
  (x: x * 2)  # 4 * 2 = 8
] # outputs 8
</code></pre>
<h2>Packaging</h2>
<h3>Tauri</h3>
<p>Recently I tried packaging some Tauri apps, what a big mistake. Anyone that tried packaging a <a href="https://tauri.app">tarui</a> app has probably seen this amazing error:</p>
<pre><code>chmod: changing permissions of '/nix/store/6scp0k430y2psl9i7zbiccv0687fk4hc-454xi372h27vn98mavqlxn5cf85x72ll-source/src-tauri': Operation not permitted
</code></pre>
<p>But it suddenly fixes itself if you package it for nixpkgs rather than as a flake??? This one I am beyond lost on, but my best advice is just to make a package for nixpkgs.</p>
<h3>My greatest enemy</h3>
<p><code>pngquant-bin</code> is truly my greatest enemy. In my upward battle to package <a href="https://github.com/lighttigerXIV/catppuccinifier"><code>catppuccinifier-gui</code></a>, it had a dependency on <code>pngquant-bin</code>. And the worst bit is that <code>pngquant-bin</code> runs an install script to download extra files which simply is not allowed in nix. In the end, I gave up and patched the binary release instead.</p>
<h2>The module system</h2>
<p>I wish I learned this much earlier. I abuse this now and it's worth every bit of it. It makes your system configurations a lot easier to understand and commonalities between your systems and users can be shared.</p>
<p>Let's say between all machines I want to have a user named <code>isabel</code>, I would set that in a file called <code>common.nix</code> and then import that file in all my system configurations.</p>
<p>The file tree might look something like this:</p>
<pre><code>.
├── hosts/
│   ├── host1.nix
│   └── host2.nix
└── users/
    └── isabel.nix
</code></pre>
<p>Then in the file <code>users/isabel.nix</code> file I would have something like this:</p>
<pre><code>{
  users.users.isabel = {
    isNormalUser = true;
    extraGroups = [ "wheel" "networkmanager" ];
  };
}
</code></pre>
<p>Then each host1, <code>hosts/host1.nix</code>, can contain something specific to that host, in this case, the hostname:</p>
<pre><code>{
  imports = [@users/isabel.nix];
  networking.hostName = "host1";
}
</code></pre>
<p>Whereas my other host <code>hosts/host2.nix</code>, might want to have a different hostname for example:</p>
<pre><code>{
  imports = [@users/isabel.nix];
  networking.hostName = "host2";
}
</code></pre>
<p>If you can see where this is going, you should understand that this means that the entire system is extensible. We can make changes to one host that don't affect another. And changes that apply across multiple hosts.</p>
<p>We can make this even better with options. You could do this with a tree that looks more like such:</p>
<pre><code>.
├── hosts/
│   ├── host1.nix
│   └── host2.nix
├── modules/
│   └── common.nix
└── users/
    └── isabel.nix
</code></pre>
<p>Our <code>modules/common.nix</code> may look something like this, where we are defining a new option for the hostname:</p>
<pre><code>{lib, config, ...}: {
  imports = [@users/isabel.nix]; # this file remains the same

  # in this case we are creating a new option under the `my` namespace
  options.my.hostname = lib.mkOption {
    type = types.str;
    default = "nixos";
  };

  # This might be a little confusing since it's set as
  # `options.my.hostname` but to use is calling `config.networking.hostName`
  config.networking.hostName = config.my.hostname;
}
</code></pre>
<p>Then each host would look almost identical to the other but with slightly differing values:</p>
<pre><code>{
  imports = [@modules/common.nix];
  config.my.hostname = "host1";
}
</code></pre>
<p>Changing the hostname per system like this is pretty trivial and not much of a real use case, but if you put your mind to it you can start to see how you might make a set of packages apply across 2 systems but not a 3rd or 4th.</p>
<h2>Conclusion</h2>
<p>Nix is super flexible and there's a lot of uses for it and ways you can use it. Some ways work better for some and not for others. I hope you found this article at least entertaining, if not that at least to have learned at least one thing.</p>
]]></content>
    </entry>
    <entry>
        <title type="html"><![CDATA[Writing a Neovim plugin]]></title>
        <id>https://isabelroses.com/blog/writing-a-neovim-plugin/</id>
        <link href="https://isabelroses.com/blog/writing-a-neovim-plugin/"/>
        <updated>2024-03-29T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[A story about writing my first Neovim plugin]]></summary>
        <content type="html"><![CDATA[<h2>Introduction</h2>
<p>I have been using Neovim for a while now, and I find it to be a great tool. However, I have never had the opportunity to write a plugin for it. Recently, I came across Charmbracelet's new app called <a href="https://github.com/charmbracelet/freeze">"Freeze"</a> which I liked, the app allows you to generate images of code snippits. This inspired me to integrate it into Neovim.</p>
<h2>The Idea</h2>
<p>The goal was to allow the user to select a range of text and then generate an image from that. If the user did not select an area of text then it would use the entire file.</p>
<h2>The Implementation</h2>
<p>To do this I created a set of allowed options the user could set when calling the <code>setup()</code> function for the plugin. The options would be identical to the arguments the cli command would take except in snake case. We could then use this data to generate a CLI command and call it with <code>vim.fn.system()</code>, and we could get the output and use that to give the user a log of what happened with <code>vim.notify()</code>.</p>
<p>I also wanted to ensure that the user's options would actually work, so I created a function that would check the options and ensure that they were correct. This was done by checking the options against a list of allowed options and ensuring they were the correct type. At this point I realized I was enforcing the user to use the command <code>freeze</code>, but I did not account for use cases other than mine, so this was added as an option.</p>
<h2>The struggle</h2>
<p>Neovim's documentation when it comes to old and new versions and what APIs are available is not the best. I found myself having to look at the source code of the plugin manager I was using to see what was available. Thankfully <a href="https://github.com/comfysage">@comfysage</a> managed to get to the bottom of it. The following snippet shows how we handle arrays and lists differently depending on Neovim versions.</p>
<pre><code>local function is_array(...)
  if vim.fn.has("nvim-0.10") == 1 then
    return vim.tbl_isarray(...)
  else
    return vim.tbl_islist(...)
  end
end
</code></pre>
<h2>The outcome</h2>
<p>The plugin was a success, and I was able to generate images from code snippets. I was also able to learn a lot about how to write a Neovim plugin and how to use the Neovim API. I was also able to learn a lot about how to use Vimscript and how to use the command line from within Neovim. This was a great experience, and I am thankful for it. See an example of the plugin below.</p>
<p>The plugin is available on GitHub under <a href="https://github.com/isabelroses/charm-freeze.nvim">isabelroses/charm-freeze.nvim</a>.</p>
<p><img src="@assets/posts/2024-03-29_freeze.webp" alt="Example of the plugin in action" /></p>
]]></content>
    </entry>
    <entry>
        <title type="html"><![CDATA[My Journey So Far]]></title>
        <id>https://isabelroses.com/blog/my-journey-so-far/</id>
        <link href="https://isabelroses.com/blog/my-journey-so-far/"/>
        <updated>2024-01-28T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[A in depth look at my linux and development journey so far]]></summary>
        <content type="html"><![CDATA[<h2>The prelude</h2>
<p>I started programming in Scratch when I was 10 or so years old, all of which I taught myself (this is a recurring theme). I didn't touch programming again until year 9 which is when I started my GCSEs. I took computer science as one of my options since I liked computers and I thought it would be fun. It was not. My class was the eldest year in this new school, we had no teachers at the time that actually did computer science, so we had a math teacher cobble together what he could for us. And later when we did get a teacher, we were given a booklet and told to complete it by the end of the year, it was awful.</p>
<p>Having completed my GCSEs, almost completely self-taught, I started my A-levels and I took computer science again. To my great surprise I had an not one but two amazing teachers, who were able to teach me a lot. I was able to learn a lot about java, and the deeper concepts behind programming and computer science.</p>
<h2>Diving right into the deep end</h2>
<p>I first started using Linux when I learned about self-hosting which came about from a friend of mine who was self-hosting a minecraft server. This prompted me to set up my own server in a virtual machine, all of this was brand new to me at the time and I found myself in the middle of a rush of information.</p>
<p>This all took place around, late February 2023. Following a tutorial on a now deleted YouTube video, probably not the best source. But despite that, I managed to get an Ubuntu virtual machine and bear in mind this is not even the server edition but the desktop version of Ubuntu which I chose.</p>
<p>Shortly after, I learned of <a href="https://github.com/catppuccin/catppuccin">Catppuccin</a>, an amazing soothing pastel theme with 4 major flavors. I decided to try out their userstyles, a way to apply the theme to a website, done through the means of a browser extension. And I found myself with a beautiful theme for YouTube, but it was riddled with bugs. So I decided to try and fix them, and I did but only for myself, having almost zero idea on how to use git let alone GitHub.</p>
<h2>I'm not drowning?</h2>
<p>In March 2023, I joined the Catppuccin discord server and was greeted by a friendly community, and one person was kind enough to walk me through the process of creating a pull request. But this was not with Git but rather the GitHub web interface where I would drag and drop, copy and paste all the changes I had made. And unexpectedly, I became the maintainer of the userstyles? I was not expecting this at all, but I was happy to take on the role.</p>
<p>Needless to say this forced me to get more in touch with my tools like Git and GitHub, and I was able to learn a lot from this experience. I was also able to learn a lot about CSS and how to use it to style a website. This was a great experience, and I am damn thankful for it.</p>
<h2>Trying to swim</h2>
<p>In late April to early May 2023, I felt ready for more which prompted me to try and learn more about Linux and how to use it. This lead me to uninstalling windows from my laptop and installing Ubuntu. This was a big step for me as I had never used Linux as my main operating system before and at first it felt like a swing and a miss. I shortly after realized that it was ubuntu just was not the right distro for me.</p>
<p>So prompted by others once more I decided to try installing Arch Linux. Having tested installing in a virtual machine several times I felt ready to install it on my laptop. And I did, and it was a success. I was able to install Arch Linux on my laptop, and I was able to use it as my main operating system.</p>
<p>At this point I created my <a href="https://github.com/isabelroses/dotfiles">dotfiles repo</a>, having learned it's a good idea to have the ability to easily install all my config files on a new machine. And I was able to learn a lot about how to use Git, and how to use the command line.</p>
<h2>Learning to swim</h2>
<p>In late May 2023, I was introduced to <a href="https://nixos.org">NixOS</a> and I loved the idea, but I think I was not ready for it. So I didn't commit until much later<br />
around late July 2023. I was able to install NixOS on my laptop, and I was able to use it as my main operating system. By this time I was much more proficient with the command line and all my other daily tools.</p>
<p>Around mid-July, I managed to become a member of staff for the <a href="https://github.com/catppuccin/userstyles">Catppuccin userstyles repo</a> which introduced me to a whole new set of skills. Like how to manage a repo, how to manage issues and pull requests, and how to manage a community. One of the strongest skills I learned from this is CI/CD, and how to use it to automate tasks, this was necessary for the userstyles repo due to the huge number of tasks that repetitive. Now almost all my repos have some amount of CI/CD.</p>
<h2>I really can swim</h2>
<p>As of the time of writing, I am still using NixOS as my main operating system and I am still learning a lot about it. I have also been able to learn a lot about programming and how to use Git and GitHub. I have also been able to learn a lot about how to manage a community and how to manage a repo. And i'm thankful for all the people who have helped me along the way. I hope to continue learning and improving my skills.</p>
]]></content>
    </entry>
    <entry>
        <title type="html"><![CDATA[2023 Wrapped]]></title>
        <id>https://isabelroses.com/blog/2023-wrapped/</id>
        <link href="https://isabelroses.com/blog/2023-wrapped/"/>
        <updated>2024-01-02T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[A look back at 2023, but with a fancy name]]></summary>
        <content type="html"><![CDATA[<p>During the course of 2023, I learned and did a lot of different things. And this post is here to summarize the most important things that I did and learned.</p>
<p>So to start off with, I started with using Git and GitHub <em>properly</em>, to be accurate I used to commit to GitHub rarely and doing so with the web upload by dragging and dropping files. I was also introduced to forking and pull requests thanks to the amazing community over at <a href="https://github.com/catppuccin/catppuccin">Catppuccin</a>.</p>
<p>From now on were going to swap to a list format as it will be easier to read, and break it down into different sections.</p>
<h3>New technologies</h3>
<ul>
<li><a href="https://neovim.io">Neovim</a>, the best editor ever. This now is my main editor, and I'm really happy with it. I'm still learning how to use it properly, but I'm getting there. In fact, I'm writing this post in Neovim right now. Prior to this I was using <a href="https://code.visualstudio.com/">VS Code</a> and from time to time I still do, but I find myself using Neovim more and more.</li>
<li><a href="https://nixos.org/">NixOS</a>, my main operating system, and it has made development so much easier.</li>
<li><a href="https://vikunja.io">Vikunja</a>, this functioned as my main to-do list manager, but I would like to start using <a href="https://github.com/isabelroses/bellado">bellado</a> as a way to interact with my Vikunja instance. The reason for this is I need to use my to-do list on the move more often then not. But I also love being in the command line and an easy way to do this would be amazing.</li>
</ul>
<h3>Self-hosted services</h3>
<p>I started getting into self-hosting services (all managed by my NixOS dotfiles) this year and I got really excited and ended up hosting more than I actually use, but I'm still happy with the outcome. However, some of the ones I use the most are:</p>
<ul>
<li><a href="https://forgejo.org/">forgejo</a>, it's a really cool Git service that is lightweight and easy to use. I use it for <em>most</em> my Git hosting needs.</li>
<li>My mail server, although this was a real pain to set up, I use this one of the most out of all my self-hosted services.</li>
<li><a href="https://wakatime.com">wakatime</a>, this is a really cool service that I use to track my coding time. I use this to see how much time I spend coding and what languages I use the most.</li>
<li><a href="https://vikunja.io">Vikunja</a> as mentioned above, this is my main to-do list manager. I use this to keep track of all my tasks and what I need to do.</li>
<li>And finally, <a href="https://github.com/dani-garcia/vaultwarden">vaultwarden</a>, an amazing open-source password manager, which saved me so many times.</li>
</ul>
<h3>New languages</h3>
<p>This year I learned a wide range of languages since I was in search for the prefect language for me. And I think I found it, but I'm still learning it. But the languages that I learned this year were:</p>
<ul>
<li><a href="https://golang.org/">Go</a>, this is my main language now, and I'm really happy with it.</li>
<li><a href="https://www.rust-lang.org/">Rust</a>, this is a really cool language, but I don't use it as much as I would like to, but I have made a few projects in it.</li>
<li><a href="https://en.wikipedia.org/wiki/C_(programming_language)">C</a>, this is a language that I didn't really like as much as I thought, but I still learned it and made a few small projects in it.</li>
</ul>
<h3>New Projects</h3>
<p>Some of the projects that I worked on during 2023 were:</p>
<ul>
<li><a href="https://github.com/catppuccin/userstyles">catppuccin/userstyles</a>, a collection of userstyles for various websites. This was my first <em>proper</em> project that I worked on, and I learned a lot from it. And It's still one of my favorite projects to work on, it currently has (at the time of writing) 51 styles and 29 people working on it. And I'm really proud of it, and I was happy to be able to work on it with such an amazing community.</li>
<li><a href="https://github.com/isabelroses/website">my website</a>, a cool website that I made in vue.js and now am in the process of remaking in go. I learned a lot from this project, and I'm really happy with how it turned out, but you can be my judge of that.</li>
<li><a href="https://github.com/isabelroses/bellado">bellado</a>, a command line to-do list manager. This is one of the hardest projects that I have worked on, and I'm really thankful to the Catppuccin community once again for all the help making this a much easier job, in particular <a href="https://github.com/nullishamy">@nullishamy</a>, she really was the best of help.</li>
<li><a href="https://github.com/isabelroses/dotfiles">my dotfiles</a>, my <a href="https://nixos.org/">NixOS</a> dotfiles. This is one of the hardest things that I've done all year, there was so much to learn and so much to do. I'm really happy that I completed this, and I am really happy to be using NixOS as my main operating system.</li>
<li><a href="https://github.com/isabelroses/rerefined">rerefined</a>, I don't often mention this one since I don't update it that much, but its purpose is to make a visual improvement to multiple websites.</li>
</ul>
]]></content>
    </entry>
    <entry>
        <title type="html"><![CDATA[My workflow]]></title>
        <id>https://isabelroses.com/blog/my-workflow/</id>
        <link href="https://isabelroses.com/blog/my-workflow/"/>
        <updated>2023-12-28T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Keeping on track with workflow]]></summary>
        <content type="html"><![CDATA[<p>In order to keep myself on track I wanted to document my workflow. So in order<br />
to do that this post will be kept up to date every 3 months or so with<br />
additional input and changes to how I work. Currently, I use NixOS and have my<br />
Neovim config managed through Nix, a big thanks to<br />
<a href="https://github.com/nekowinston">@nekowinson</a> for all their help.</p>
<p>To start right away with things that I wish to get better at:</p>
<ul>
<li>vim motions</li>
<li>touch typing</li>
<li>Go</li>
<li>Rust</li>
<li>Nix</li>
</ul>
<p>This list were created in order to improve my basic workflow to improve the<br />
generic speed that I work at. To start I barely use vim motions despite using<br />
Neovim as my main editor, and touch typing goes along well with this. And<br />
despite using NixOS I feel like I barely know anything about Nix.</p>
<p>In order to also properly manage myself I have two main tools<br />
<a href="https://github.com/isabelroses/bellado">bellado</a> and<br />
<a href="https://vikunja.io">Vikunja</a> having these two separate is really inconvenient<br />
but in order to use my to-do list well it needs to be accessible via the command<br />
line and through the web so whether I'm out and about or on my main work machine<br />
every will work well. One way I plan to improve this issue is to allow bellado<br />
to interact with my Vikunja instance.</p>
]]></content>
    </entry>
    <entry>
        <title type="html"><![CDATA[Self-Healing URLs]]></title>
        <id>https://isabelroses.com/blog/self-healing-urls/</id>
        <link href="https://isabelroses.com/blog/self-healing-urls/"/>
        <updated>2023-12-10T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Creating self-healing URLs within my Vue.js website]]></summary>
        <content type="html"><![CDATA[<p>I have been working on the creation of this <a href="https://github.com/isabelroses/website/commit/8c53b9f3576d98a2ebe71976a3f921a30e6ad052">version</a> of my website for a while and when I finally thought I was done, I was introduced to the concept of self-healing URLs.</p>
<p>Self-healing URLs are designed in a way that if a user was to type in a URL as<br />
long as a certain part of the URL is correct, the user will be redirected to the<br />
correct page. This is useful for when a user is trying to access a page that has<br />
been moved or deleted.</p>
<p>For example, if a user was to type in <a href="https://isabelroses.com/blog/gaoengioa-2">/blog/gaoengioa-2</a> they would be redirected to <a href="https://isabelroses.com/blog/self-healing-urls-2">/blog/self-healing-urls-2</a> as the only important part of the URL is the "2" in this case, which refers to the second blog post by ID.</p>
<p>To implement this I had to make a few changes to my code. The original way that<br />
the post data was being fetched was by using the slug of the post. This meant<br />
that if the slug was incorrect, the post would not be found and the user would<br />
be redirected to a 404 page. To fix this I had to change the way that the post<br />
was being fetched to use the ID of the post instead. This meant that if the slug<br />
was incorrect, the post would still be found and the user would be redirected to<br />
the correct page.</p>
<pre><code>// the old code
get post() {
    return meta.posts.find((post: any) =&gt; post.slug == this.$route.params.slug);
}

// the new code
get post() {
    // get the id from the slug
    const id = (this.$route.params.slug).toString().split("-").slice(-1)[0];
    // find the post using the id
    const post = meta.posts.find((post: any) =&gt; post.id == id);

    if (this.$route.params.slug != post?.slug) {
        // create the correct slug
        const slug = post?.slug + "-" + id;
        // redirect to the correct page
        this.$router.push({ name: "BlogPost", params: { slug: slug } });
    }

    return post;
}
</code></pre>
<p>Then all that was left was to ensure all links were using the new slug format.<br />
This was done by changing the way that the slug was being created. Instead of<br />
using the title of the post, the slug was created using the title and the ID of<br />
the post. This meant that the slug would always be unique and would always be<br />
the same for the same post.</p>
<h4>Inspiration</h4>
<p>The original idea for this post comes from: <a href="https://www.youtube.com/watch?v=a6lnfyES-LA">https://www.youtube.com/watch?v=a6lnfyES-LA</a></p>
]]></content>
    </entry>
</feed>