<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Nixicle on Haseeb Majid</title>
    <link>https://haseebmajid.dev/tags/nixicle/</link>
    <description>Haseeb Majid&#39;s blog</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en</language>
    <lastBuildDate>Wed, 31 Dec 2025 00:00:00 +0000</lastBuildDate><atom:link href="https://haseebmajid.dev/tags/nixicle/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>How to Setup a New PC With Lanzaboote, TPM Decryption, sops-nix, Impermanence and nixos-anywhere</title>
      <link>https://haseebmajid.dev/posts/2025-12-31-how-to-setup-a-new-pc-with-lanzaboote-tpm-decryption-sops-nix-impermanence-nixos-anywhere/</link>
      <pubDate>Wed, 31 Dec 2025 00:00:00 +0000</pubDate>
      <author>Haseeb Majid</author>
      <guid>https://haseebmajid.dev/posts/2025-12-31-how-to-setup-a-new-pc-with-lanzaboote-tpm-decryption-sops-nix-impermanence-nixos-anywhere/</guid>
      <description>&lt;h2 id=&#34;background&#34;&gt;Background&lt;/h2&gt;
&lt;details class=&#34;notice warning&#34; data-kind=&#34;warning&#34; open&gt;
  &lt;summary class=&#34;notice-title&#34;&gt;
    &lt;span class=&#34;notice-titlebar&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;notice-tab&#34;&gt;notes.md&lt;/span&gt;&lt;span class=&#34;notice-state&#34;&gt;WARNING&lt;/span&gt;&lt;/span&gt;
    &lt;svg class=&#34;notice-icon&#34; viewBox=&#34;0 0 24 24&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.7&#34; stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; aria-hidden=&#34;true&#34;&gt;&lt;path d=&#34;m12 3 10 18H2L12 3Z&#34;/&gt;&lt;path d=&#34;M12 9v5m0 3v1&#34;/&gt;&lt;/svg&gt;
    &lt;span class=&#34;notice-copy&#34;&gt;&lt;span class=&#34;notice-kind&#34;&gt;warning: &lt;/span&gt;&lt;span class=&#34;notice-heading&#34;&gt;Be Careful&lt;/span&gt;&lt;/span&gt;
    &lt;svg class=&#34;notice-chevron&#34; viewBox=&#34;0 0 24 24&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.7&#34; aria-hidden=&#34;true&#34;&gt;&lt;path d=&#34;m6 9 6 6 6-6&#34;/&gt;&lt;/svg&gt;
  &lt;/summary&gt;
  &lt;div class=&#34;notice-body&#34;&gt;&lt;p&gt;Make sure if you follow this guide you could lose your data. Make sure to back up whatever important data you have.
Or do what I did and test this on a new device where it doesn&amp;rsquo;t matter if something goes wrong.&lt;/p&gt;</description><content:encoded><![CDATA[<h2 id="background">Background</h2>
<details class="notice warning" data-kind="warning" open>
  <summary class="notice-title">
    <span class="notice-titlebar" aria-hidden="true"><span class="notice-tab">notes.md</span><span class="notice-state">WARNING</span></span>
    <svg class="notice-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m12 3 10 18H2L12 3Z"/><path d="M12 9v5m0 3v1"/></svg>
    <span class="notice-copy"><span class="notice-kind">warning: </span><span class="notice-heading">Be Careful</span></span>
    <svg class="notice-chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" aria-hidden="true"><path d="m6 9 6 6 6-6"/></svg>
  </summary>
  <div class="notice-body"><p>Make sure if you follow this guide you could lose your data. Make sure to back up whatever important data you have.
Or do what I did and test this on a new device where it doesn&rsquo;t matter if something goes wrong.</p>
<p>But don&rsquo;t try to do this on a machine that has important data, or you need to use day to day. Though I was able to
set up a new laptop in about 20 mins once I got everything working. It can be fiddly.</p>
</div>
  <div class="notice-status" aria-hidden="true"><b>WARNING</b><span>notes.md</span><span>read only</span></div>
</details>

<p>Since finishing the second round of development on my game/web app banter bus (currently not deployed anywhere since I took down
my k8s cluster), I have been looking for something else to sink my teeth into.</p>
<p>I decided I was going to sell my custom-built PC and then use the money to get a Framework desktop.
So I thought I might as well go with something simpler that would work just as well for development.</p>
<p>As part of this, I decided to finally tackle some of the tasks I wanted to complete with my Nix configuration. Mainly
setting up <a href="https://wiki.nixos.org/wiki/Impermanence">impermanence</a>, which means files we don&rsquo;t persist will get deleted
across reboots, forcing us to specify more of our machine in config/nix code. I haven&rsquo;t done this yet with home-manager,
but will at some point do that as well, though that will take much more effort and also be more dangerous.</p>
<p>Whilst doing this, I decided that I would also like to set up Secure Boot with <a href="https://github.com/nix-community/lanzaboote">Lanzaboote</a>
and LUKS but using TPM decryption, vs needing to type in a LUKS password then another password to login.</p>
<p>I set up my new device using nixos-anywhere, a great tool when you have a well-defined nix config to build from, which
can build and install NixOS on a device if it can SSH to it. My favourite tool for installing NixOS whenever I need to
(probably more often than I&rsquo;d like to admit). Whilst this all worked great and eventually I managed to get my setup
working, I am writing this blog post on my new Framework Desktop. I wondered if we could cut down the number of manual
steps, originally inspired by <sup id="fnref:1"><a href="https://haseebmajid.dev/posts/2025-12-31-how-to-setup-a-new-pc-with-lanzaboote-tpm-decryption-sops-nix-impermanence-nixos-anywhere/#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>.</p>
<p>However, I hit some issues with all of the tools above playing nice. Mostly sops-nix with impermanence, getting
the SSH keys on the machine so I could use my secrets. In this increasingly long blog post, I will show you how I got it
set up. I won&rsquo;t go into lots of detail about each specific tool. I will assume you are somewhat familiar with them.
Each could have their own blog post (or maybe YouTube video; I should start creating videos again, I think).</p>
<p>I will show you the final config I managed, with minimal manual effort needed. This should work on any Framework
device (especially the Secure Boot part).</p>
<h2 id="config">Config</h2>
<p>Now I&rsquo;ll quickly show you how I have set up my Nix config for the various tools above. These configurations are interdependent: impermanence creates the persist structure, sops-nix needs persisted SSH keys for decryption, and Lanzaboote needs persisted signing keys for Secure Boot. Link to my <a href="https://gitlab.com/hmajid2301/nixicle/-/tree/7ffe47fb27c804383d5c53405e918f7b4749bfaf">config</a> (at the point I published this article).</p>
<details class="notice info" data-kind="info" open>
  <summary class="notice-title">
    <span class="notice-titlebar" aria-hidden="true"><span class="notice-tab">notes.md</span><span class="notice-state">INFO</span></span>
    <svg class="notice-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="M12 11v6m0-11v1"/></svg>
    <span class="notice-copy"><span class="notice-kind">info: </span><span class="notice-heading">One Disk</span></span>
    <svg class="notice-chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" aria-hidden="true"><path d="m6 9 6 6 6-6"/></svg>
  </summary>
  <div class="notice-body">My setup also assumes you are just setting up a single disk, i.e., one NVMe.</div>
  <div class="notice-status" aria-hidden="true"><b>INFO</b><span>notes.md</span><span>read only</span></div>
</details>

<h3 id="sops-nix">sops-nix</h3>
<p>When we have secrets that we don&rsquo;t want to keep in plaintext, we can use sops to encrypt the data with our age/SSH keys.
Either host key for NixOS secrets or our age key for home-manager related secrets. For example, for defining the user&rsquo;s
password, which in our secrets.yaml file looks like <code>user_password: ENC[AES256_GCM,data:wSbEwtgPzM1FLYAb3rMCXneXJr2xM4w4lydvwA+DDP1i4DNPKQvC7VUKe00wIp3rDhjLTept/WA8uIGEJsPaf30/iYc2txdDWVvjDL81UnXtIfFoXKsYWQ7vrftShMJckByMiD5uZoYkSA==,iv:0YpQ5RL5CSjfS6jfpZArore25jn42uW3IRr8xLK/798=,tag:oVyxq1uIsKfV/HQWum6LQA==,type:str]</code>. But this can be decrypted by our Nix config and stored at <code>/run/secrets</code> (and other folders next to it).</p>
<p>This means when we do a nixos-anywhere install, we need to send it relevant SSH keys/age keys so that it can decrypt
our secrets such that we can log in. Otherwise, there will be no secret and we won&rsquo;t be able to log in to our machine.</p>
<p>In my <code>hosts/framework/default.nix</code> config I have:</p>
<div class="lab-neovim" data-code-lines="10" data-code-filename="snippet.nix" data-code-lang="nix">
  <div class="lab-code-tab">
    <span class="lab-code-filename" title="snippet.nix">snippet.nix</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">sops</span><span class="o">.</span><span class="n">secrets</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">user_password</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">sopsFile</span> <span class="o">=</span> <span class="sr">./secrets.yaml</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">neededForUsers</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">user</span><span class="o">.</span><span class="n">passwordSecretFile</span> <span class="o">=</span> <span class="n">config</span><span class="o">.</span><span class="n">sops</span><span class="o">.</span><span class="n">secrets</span><span class="o">.</span><span class="n">user_password</span><span class="o">.</span><span class="n">path</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div>
  <div class="lab-code-bar">
    <b class="lab-code-lang">nix</b>
    <span class="lab-code-lines">10 lines</span>
    <span class="lab-code-copy"></span>
  </div>
  <div class="lab-code-command" role="status">"snippet.nix" 10L</div>
</div>
<p>And then when defining users, we can do something like this in <code>modules/nixos/user/default.nix</code>, where we can define the hash of the password above:</p>
<div class="lab-neovim" data-code-lines="6" data-code-filename="snippet.nix" data-code-lang="nix">
  <div class="lab-code-tab">
    <span class="lab-code-filename" title="snippet.nix">snippet.nix</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">users</span><span class="o">.</span><span class="n">mutableUsers</span> <span class="o">=</span> <span class="no">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">users</span><span class="o">.</span><span class="n">users</span><span class="o">.</span><span class="si">${</span><span class="n">cfg</span><span class="o">.</span><span class="n">name</span><span class="si">}</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">hashedPasswordFile</span> <span class="o">=</span> <span class="n">cfg</span><span class="o">.</span><span class="n">passwordSecretFile</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div>
  <div class="lab-code-bar">
    <b class="lab-code-lang">nix</b>
    <span class="lab-code-lines">6 lines</span>
    <span class="lab-code-copy"></span>
  </div>
  <div class="lab-code-command" role="status">"snippet.nix" 6L</div>
</div>
<p>And finally my <code>modules/nixos/security/sops/default.nix</code> config:</p>
<p>I reference this file in the persist, which I&rsquo;m not sure I need to do:</p>
<div class="lab-neovim" data-code-lines="5" data-code-filename="snippet.nix" data-code-lang="nix">
  <div class="lab-code-tab">
    <span class="lab-code-filename" title="snippet.nix">snippet.nix</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">sops</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">age</span><span class="o">.</span><span class="n">sshKeyPaths</span> <span class="o">=</span> <span class="p">[</span> <span class="s2">&#34;/persist/etc/ssh/ssh_host_ed25519_key&#34;</span> <span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div>
  <div class="lab-code-bar">
    <b class="lab-code-lang">nix</b>
    <span class="lab-code-lines">5 lines</span>
    <span class="lab-code-copy"></span>
  </div>
  <div class="lab-code-command" role="status">"snippet.nix" 5L</div>
</div>
<h3 id="lanzaboote-secure-boot">Lanzaboote (Secure Boot)</h3>
<p>We will use Lanzaboote so we can enable Secure Boot.</p>
<blockquote>
<p>Secure Boot usually refers to a platform firmware capability to verify the boot components and ensure that only your own operating system is allowed to boot. - NixOS Wiki <sup id="fnref:2"><a href="https://haseebmajid.dev/posts/2025-12-31-how-to-setup-a-new-pc-with-lanzaboote-tpm-decryption-sops-nix-impermanence-nixos-anywhere/#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup></p>
</blockquote>
<p>Here are the relevant parts of my Nix config <code>modules/nixos/system/boot/default.nix</code> for Secure Boot.
We want to persist the /etc/secureboot folder, which is where our signing keys will be auto-generated by Lanzaboote
on our first boot. We also need to disable systemd-boot if we are using Lanzaboote.</p>
<p>The combination of auto-generate keys and auto-enroll means fewer manual steps we need to take post-install to enable
Secure Boot. However, the first time I did this, I did set this up manually following the Lanzaboote getting started guide.
Do whichever you prefer; it&rsquo;s not like it&rsquo;s many steps anyway.</p>
<div class="lab-neovim" data-code-lines="26" data-code-filename="snippet.nix" data-code-lang="nix">
  <div class="lab-code-tab">
    <span class="lab-code-filename" title="snippet.nix">snippet.nix</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">boot</span><span class="o">.</span><span class="n">lanzaboote</span> <span class="o">=</span> <span class="n">mkIf</span> <span class="n">cfg</span><span class="o">.</span><span class="n">secureBoot</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">pkiBundle</span> <span class="o">=</span> <span class="s2">&#34;/etc/secureboot&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">autoGenerateKeys</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">autoEnrollKeys</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">autoReboot</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">boot</span><span class="o">.</span><span class="n">systemd-boot</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">enable</span> <span class="o">=</span> <span class="no">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">configurationLimit</span> <span class="o">=</span> <span class="mi">20</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">editor</span> <span class="o">=</span> <span class="no">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">environment</span><span class="o">.</span><span class="n">persistence</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;/persist&#34;</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">directories</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;/etc/secureboot&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div>
  <div class="lab-code-bar">
    <b class="lab-code-lang">nix</b>
    <span class="lab-code-lines">26 lines</span>
    <span class="lab-code-copy"></span>
  </div>
  <div class="lab-code-command" role="status">"snippet.nix" 26L</div>
</div>
<h3 id="impermanence">Impermanence</h3>
<p>My <a href="https://gitlab.com/hmajid2301/nixicle/-/blob/7ffe47fb27c804383d5c53405e918f7b4749bfaf/modules/nixos/system/impermanence/default.nix#L1">impermanence module</a> is longish,
so I won&rsquo;t go into lots of details. But we use btrfs to be able to roll back to a clean state, then copy files we specify
in our <code>persist</code> btrfs subvolume.</p>
<h4 id="disko">disko</h4>
<p>I use disko with nixos-anywhere to be able to partition my disks without lots of manual commands. Here is <a href="https://gitlab.com/hmajid2301/nixicle/-/blob/7ffe47fb27c804383d5c53405e918f7b4749bfaf/hosts/framework/disks.nix">my disko config</a>.
You can see the subvolumes we create for our btrfs setup, i.e., <code>/persist</code> for persisting files between reboots.</p>
<p>Here is where I define my LUKS settings as well, so that our data will be encrypted at rest (full-disk encryption).</p>
<h3 id="pcr-15">PCR 15</h3>
<details class="notice warning" data-kind="warning" open>
  <summary class="notice-title">
    <span class="notice-titlebar" aria-hidden="true"><span class="notice-tab">notes.md</span><span class="notice-state">WARNING</span></span>
    <svg class="notice-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m12 3 10 18H2L12 3Z"/><path d="M12 9v5m0 3v1"/></svg>
    <span class="notice-copy"><span class="notice-kind">warning: </span><span class="notice-heading">TPM</span></span>
    <svg class="notice-chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" aria-hidden="true"><path d="m6 9 6 6 6-6"/></svg>
  </summary>
  <div class="notice-body"><p>Take what I say here with a grain of salt; this is all based on my understanding.
This is from reading articles, and a bit of asking Claude to explain/draw stuff to make it more visual.</p>
<p>I have linked a few articles/videos that I found useful.</p>
</div>
  <div class="notice-status" aria-hidden="true"><b>WARNING</b><span>notes.md</span><span>read only</span></div>
</details>

<p>Eventually, we will want to enroll TPM to decrypt LUKS so that it is decrypted automatically but will only work with our
TPM chip soldered onto our motherboard. Part of this will be specifying the PCRs. I don&rsquo;t know loads about how it works.</p>
<p><a href="https://wiki.archlinux.org/title/Trusted_Platform_Module#Accessing_PCR_registers">PCR</a>, Platform Configuration Registers.</p>
<blockquote>
<p>Platform Configuration Registers (PCR) allow binding of the encryption of secrets to specific software versions and system state via hashes, so that the enrolled key is only accessible (may be &ldquo;unsealed&rdquo;) if specific trusted software and/or configuration is used. - Arch Wiki</p>
</blockquote>
<p>We check the previous registers before we give access to the data inside the TPM, i.e., our LUKS password stored inside
the TPM. There is a really good post <sup id="fnref:3"><a href="https://haseebmajid.dev/posts/2025-12-31-how-to-setup-a-new-pc-with-lanzaboote-tpm-decryption-sops-nix-impermanence-nixos-anywhere/#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup> about how TPM decryption might not be secure enough, though I don&rsquo;t think we need
to worry about PCR 9 <sup id="fnref:4"><a href="https://haseebmajid.dev/posts/2025-12-31-how-to-setup-a-new-pc-with-lanzaboote-tpm-decryption-sops-nix-impermanence-nixos-anywhere/#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup> (due to checksums on initrd, from what I understand anyway).</p>
<p>I may do a longer post about how this all works when I learn more myself. But if we don&rsquo;t check PCR 15, someone could
just copy the metadata and pretend they have our disk, assuming they have physical access to our PC. The TPM
is not checking the PCR 15 value, so therefore would just spit out our LUKS password, which they could then use
on our original disk. Now, of course, this all depends on your threat model, but I thought it&rsquo;d be fun to fix this issue,
at least on my laptop.</p>
<p>So I copied the <a href="https://forge.lel.lol/patrick/nix-config/src/commit/ab2cb2b4d554040ce208fc60624fe729a9d5e32b/modules/ensure-pcr.nix">module</a>.
Until the expected option is configured, it won&rsquo;t do anything; we won&rsquo;t know this until the system has been built.
So it&rsquo;ll be one more manual step we need to take, but perhaps worth it to make my system more secure.</p>
<div class="lab-neovim" data-code-lines="6" data-code-filename="snippet.nix" data-code-lang="nix">
  <div class="lab-code-tab">
    <span class="lab-code-filename" title="snippet.nix">snippet.nix</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">security</span><span class="o">.</span><span class="n">nixicle</span><span class="o">.</span><span class="n">pcr-verification</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># expectedPcr15 = &#34;caf33e79c645b65849256238a11fa68ae197e5cb89730c463c1cdf1d9128376f&#34;;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div>
  <div class="lab-code-bar">
    <b class="lab-code-lang">nix</b>
    <span class="lab-code-lines">6 lines</span>
    <span class="lab-code-copy"></span>
  </div>
  <div class="lab-code-command" role="status">"snippet.nix" 6L</div>
</div>
<h2 id="steps">Steps</h2>
<p>Now onto how I deploy NixOS onto a new system/device with all the above playing nice and reducing the number of
manual steps we need to take. Normally, I had Claude create some scripts I can use with <code>go-task</code>, but for the sake
of this blog post, we will do it step by step, i.e., normally I would do <code>task install:secure</code>. Where we could then
follow the script using <a href="https://github.com/charmbracelet/gum">charm&rsquo;s gum</a> to make nice interactive scripts.
This includes info like the username, IP to SSH onto.</p>
<p>Anyway, make sure the device can be SSH&rsquo;d onto; usually you can just use a live media USB. I build my own with nixos-generators.
But any live media should be fine; use NixOS if you don&rsquo;t have anything set up. I do something like this:
<code>mkdir -p ~/.ssh &amp;&amp; curl https://github.com/hmajid2301.keys &gt; ~/.ssh/authorized_keys</code>, so that I can SSH onto the live
media. Also, take note of the IP address of the machine; you can find this by running <code>ip addr</code>.</p>
<p>For example, on my local network it might be <code>192.168.1.71</code>.</p>
<h3 id="install">Install</h3>
<p>Enter our encryption password to a file:</p>
<div class="lab-neovim" data-code-lines="2" data-code-filename="snippet.sh" data-code-lang="bash">
  <div class="lab-code-tab">
    <span class="lab-code-filename" title="snippet.sh">snippet.sh</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nv">MY_PASS</span><span class="o">=</span><span class="k">$(</span>mktemp<span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;YOUR_LUKS_PASSWORD&#34;</span> &gt; <span class="nv">$MY_PASS</span></span></span></code></pre></div>
  <div class="lab-code-bar">
    <b class="lab-code-lang">bash</b>
    <span class="lab-code-lines">2 lines</span>
    <span class="lab-code-copy"></span>
  </div>
  <div class="lab-code-command" role="status">"snippet.sh" 2L</div>
</div>
<p>Get SSH keys ready for copying onto the new machine; this is so that sops can decrypt the various secrets. Either you
can copy the existing SSH keys and replace them after, or you can generate new ones and update your sops config and sops
files with these new keys. The latter is the more secure option, but I went with the first one as I was feeling lazy
(security through inconvenience is still security, right? Right?):</p>
<blockquote>
<p>Note: If this is your first installation and you don&rsquo;t have existing SSH keys at <code>/persist/etc/ssh/</code>, you can either generate temporary keys or skip this step and regenerate your secrets after installation with the new host keys that will be created.</p>
</blockquote>
<div class="lab-neovim" data-code-lines="7" data-code-filename="snippet.sh" data-code-lang="bash">
  <div class="lab-code-tab">
    <span class="lab-code-filename" title="snippet.sh">snippet.sh</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nv">SSH_KEYS</span><span class="o">=</span><span class="k">$(</span>mktemp -d<span class="k">)</span>
</span></span><span class="line"><span class="cl">mkdir -p <span class="s2">&#34;</span><span class="nv">$SSH_KEYS</span><span class="s2">/persist/etc/ssh&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">sudo cp /persist/etc/ssh/ssh_host_ed25519_key <span class="s2">&#34;</span><span class="nv">$SSH_KEYS</span><span class="s2">/persist/etc/ssh/&#34;</span>
</span></span><span class="line"><span class="cl">sudo cp /persist/etc/ssh/ssh_host_ed25519_key.pub <span class="s2">&#34;</span><span class="nv">$SSH_KEYS</span><span class="s2">/persist/etc/ssh/&#34;</span>
</span></span><span class="line"><span class="cl">sudo cp /persist/etc/ssh/ssh_host_rsa_key <span class="s2">&#34;</span><span class="nv">$SSH_KEYS</span><span class="s2">/persist/etc/ssh/&#34;</span> 2&gt;/dev/null
</span></span><span class="line"><span class="cl">sudo cp /persist/etc/ssh/ssh_host_rsa_key.pub <span class="s2">&#34;</span><span class="nv">$SSH_KEYS</span><span class="s2">/persist/etc/ssh/&#34;</span> 2&gt;/dev/null</span></span></code></pre></div>
  <div class="lab-code-bar">
    <b class="lab-code-lang">bash</b>
    <span class="lab-code-lines">7 lines</span>
    <span class="lab-code-copy"></span>
  </div>
  <div class="lab-code-command" role="status">"snippet.sh" 7L</div>
</div>
<p>Update permissions on the SSH keys folder:</p>
<blockquote>
<p>Note: This step is crucial for sops-nix to work with impermanence. If you skip this, you&rsquo;ll need to re-encrypt your secrets with new host keys.</p>
</blockquote>
<div class="lab-neovim" data-code-lines="2" data-code-filename="snippet.sh" data-code-lang="bash">
  <div class="lab-code-tab">
    <span class="lab-code-filename" title="snippet.sh">snippet.sh</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo chmod <span class="m">600</span> <span class="s2">&#34;</span><span class="nv">$SSH_KEYS</span><span class="s2">/persist/etc/ssh&#34;</span>/ssh_host_*_key 2&gt;/dev/null
</span></span><span class="line"><span class="cl">sudo chown -R <span class="k">$(</span>id -u<span class="k">)</span>:<span class="k">$(</span>id -g<span class="k">)</span> <span class="s2">&#34;</span><span class="nv">$SSH_KEYS</span><span class="s2">&#34;</span></span></span></code></pre></div>
  <div class="lab-code-bar">
    <b class="lab-code-lang">bash</b>
    <span class="lab-code-lines">2 lines</span>
    <span class="lab-code-copy"></span>
  </div>
  <div class="lab-code-command" role="status">"snippet.sh" 2L</div>
</div>
<p>Then finally we can start the install process:</p>
<blockquote>
<p>Note: Update the username@host as needed, nixos is the default user for the NixOS ISO.</p>
</blockquote>
<div class="lab-neovim" data-code-lines="6" data-code-filename="snippet.sh" data-code-lang="bash">
  <div class="lab-code-tab">
    <span class="lab-code-filename" title="snippet.sh">snippet.sh</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">nixos-anywhere <span class="se">\
</span></span></span><span class="line"><span class="cl">--flake <span class="s2">&#34;.#framework&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">--disk-encryption-keys /tmp/disk-encryption.key <span class="s2">&#34;</span><span class="nv">$MY_PASS</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">--extra-files <span class="s2">&#34;</span><span class="nv">$SSH_KEYS</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">--build-on-remote <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="s2">&#34;nixos@192.168.1.71&#34;</span></span></span></code></pre></div>
  <div class="lab-code-bar">
    <b class="lab-code-lang">bash</b>
    <span class="lab-code-lines">6 lines</span>
    <span class="lab-code-copy"></span>
  </div>
  <div class="lab-code-command" role="status">"snippet.sh" 6L</div>
</div>
<h3 id="post-install">Post Install</h3>
<p>After the command is done, your PC will reboot. At this point, you can start to get it ready for Secure Boot if you want.
For a Framework laptop to enable Secure Boot, first we need to erase the Secure Boot settings.</p>
<p>It may differ on your device:</p>
<div class="lab-neovim" data-code-lines="8" data-code-filename="snippet.txt" data-code-lang="text">
  <div class="lab-code-tab">
    <span class="lab-code-filename" title="snippet.txt">snippet.txt</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Framework-specific: Enter Setup Mode
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">On Framework you can enter the setup mode like this:
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    Select &#34;Administer Secure Boot&#34;
</span></span><span class="line"><span class="cl">    Select &#34;Erase all Secure Boot Settings&#34;
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">When you are done, press F10 to save and exit.</span></span></code></pre></div>
  <div class="lab-code-bar">
    <b class="lab-code-lang">text</b>
    <span class="lab-code-lines">8 lines</span>
    <span class="lab-code-copy"></span>
  </div>
  <div class="lab-code-command" role="status">"snippet.txt" 8L</div>
</div>
<p>Then enter your password for LUKS so it can decrypt your drive and boot like normal. You will see your PC reboot
due to the auto-reboot we set above with Lanzaboote. That&rsquo;s fine; it&rsquo;s all very normal.
If your computer starts speaking Latin or emitting smoke, that&rsquo;s NOT normal. Please consult a priest or your local fire department.</p>
<p>On my Framework device, it then told me it&rsquo;s going to enroll my keys, and I didn&rsquo;t interrupt the process.
Then I logged into my device and enabled TPM decryption:</p>
<blockquote>
<p>Note: Replace <code>/dev/nvme0n1p2</code> with your actual LUKS partition. Check your disko config to confirm the correct partition path.</p>
</blockquote>
<div class="lab-neovim" data-code-lines="6" data-code-filename="snippet.sh" data-code-lang="bash">
  <div class="lab-code-tab">
    <span class="lab-code-filename" title="snippet.sh">snippet.sh</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># The --tpm2-pcrs=15:sha256=0000... uses all zeros as a placeholder that will be updated after first boot</span>
</span></span><span class="line"><span class="cl">sudo systemd-cryptenroll /dev/nvme0n1p2 <span class="se">\
</span></span></span><span class="line"><span class="cl">    --wipe-slot<span class="o">=</span>tpm2 <span class="se">\
</span></span></span><span class="line"><span class="cl">    --tpm2-device<span class="o">=</span>auto <span class="se">\
</span></span></span><span class="line"><span class="cl">    --tpm2-pcrs<span class="o">=</span>0+2+7 <span class="se">\
</span></span></span><span class="line"><span class="cl">    --tpm2-pcrs<span class="o">=</span>15:sha256<span class="o">=</span><span class="m">0000000000000000000000000000000000000000000000000000000000000000</span></span></span></code></pre></div>
  <div class="lab-code-bar">
    <b class="lab-code-lang">bash</b>
    <span class="lab-code-lines">6 lines</span>
    <span class="lab-code-copy"></span>
  </div>
  <div class="lab-code-command" role="status">"snippet.sh" 6L</div>
</div>
<p>Check and make sure it all looks normal; you should see files signed (except perhaps one old one):</p>
<div class="lab-neovim" data-code-lines="10" data-code-filename="snippet.sh" data-code-lang="bash">
  <div class="lab-code-tab">
    <span class="lab-code-filename" title="snippet.sh">snippet.sh</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">❯ sudo -E sbctl verify
</span></span><span class="line"><span class="cl"><span class="o">[</span>sudo<span class="o">]</span> password <span class="k">for</span> haseeb:
</span></span><span class="line"><span class="cl">Verifying file database and EFI images in /boot...
</span></span><span class="line"><span class="cl">✓ /boot/EFI/BOOT/BOOTX64.EFI is signed
</span></span><span class="line"><span class="cl">✓ /boot/EFI/Linux/nixos-generation-40-7ik2vgrngo25ml7c2wb43uy52pt7ahpovfrrxczp445y76u56dlq.efi is signed
</span></span><span class="line"><span class="cl">✗ /boot/EFI/nixos/kernel-6.18.2-otn6nn3tkudhh5xpj5736u2q3h4kjzojd6fkg4rqzdf5l5c7gxuq.efi is not signed
</span></span><span class="line"><span class="cl">✓ /boot/EFI/systemd/systemd-bootx64.efi is signed
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Verify Secure Boot is working</span>
</span></span><span class="line"><span class="cl">sudo bootctl status</span></span></code></pre></div>
  <div class="lab-code-bar">
    <b class="lab-code-lang">bash</b>
    <span class="lab-code-lines">10 lines</span>
    <span class="lab-code-copy"></span>
  </div>
  <div class="lab-code-command" role="status">"snippet.sh" 10L</div>
</div>
<p>Now we can enable Secure Boot; again, this may vary on your device.</p>
<div class="lab-neovim" data-code-lines="6" data-code-filename="snippet.txt" data-code-lang="text">
  <div class="lab-code-tab">
    <span class="lab-code-filename" title="snippet.txt">snippet.txt</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">On Framework you need to manually enable Secure Boot:
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    Select &#34;Administer Secure Boot&#34;
</span></span><span class="line"><span class="cl">    Enable &#34;Enforce Secure Boot&#34;
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">When you are done, press F10 to save and exit.</span></span></code></pre></div>
  <div class="lab-code-bar">
    <b class="lab-code-lang">text</b>
    <span class="lab-code-lines">6 lines</span>
    <span class="lab-code-copy"></span>
  </div>
  <div class="lab-code-command" role="status">"snippet.txt" 6L</div>
</div>
<p>Finally, after logging in again, we can run the following to get the actual PCR 15 hash value:</p>
<div class="lab-neovim" data-code-lines="2" data-code-filename="snippet.sh" data-code-lang="bash">
  <div class="lab-code-tab">
    <span class="lab-code-filename" title="snippet.sh">snippet.sh</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">❯ systemd-analyze pcrs <span class="m">15</span> --json<span class="o">=</span>short
</span></span><span class="line"><span class="cl"><span class="o">[{</span><span class="s2">&#34;nr&#34;</span>:15,<span class="s2">&#34;name&#34;</span>:<span class="s2">&#34;system-identity&#34;</span>,<span class="s2">&#34;sha256&#34;</span>:<span class="s2">&#34;0000000000000000000000000000000000000000000000000000000000000000&#34;</span><span class="o">}]</span></span></span></code></pre></div>
  <div class="lab-code-bar">
    <b class="lab-code-lang">bash</b>
    <span class="lab-code-lines">2 lines</span>
    <span class="lab-code-copy"></span>
  </div>
  <div class="lab-code-command" role="status">"snippet.sh" 2L</div>
</div>
<p>Take the sha256 value from this output and update your config&rsquo;s <code>security.nixicle.pcr-verification.expectedPcr15</code> field with the actual hash, then run:</p>
<div class="lab-neovim" data-code-lines="1" data-code-filename="snippet.sh" data-code-lang="bash">
  <div class="lab-code-tab">
    <span class="lab-code-filename" title="snippet.sh">snippet.sh</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo nixos-rebuild switch --flake .#framework</span></span></code></pre></div>
  <div class="lab-code-bar">
    <b class="lab-code-lang">bash</b>
    <span class="lab-code-lines">1 line</span>
    <span class="lab-code-copy"></span>
  </div>
  <div class="lab-code-command" role="status">"snippet.sh" 1L</div>
</div>
<p>Then, of course, go about setting up everything else. If you copied existing SSH keys during installation,
you may want to rotate them for better security by generating new host keys and re-encrypting your sops secrets with the new keys.</p>
<p>That&rsquo;s it! Hopefully, you found that useful! I appreciate it was very long-winded, but that&rsquo;s how I was able to do a mostly
unattended install with few manual steps after getting the process started.
If you made it this far, you deserve a cookie 🍪 (or at least a functioning NixOS installation).</p>
<h2 id="appendix">Appendix</h2>
<p>I had to do a bunch of research whilst working on this; here are some useful links I used:</p>
<ul>
<li>A really interesting post about how you can break TPM encryption with Secure Boot: <a href="https://oddlama.org/blog/bypassing-disk-encryption-with-tpm2-unlock/#crude-implementation-of-pcr15-verification">https://oddlama.org/blog/bypassing-disk-encryption-with-tpm2-unlock/#crude-implementation-of-pcr15-verification</a></li>
<li>Lanzaboote getting started guide: <a href="https://github.com/nix-community/lanzaboote/blob/master/docs/getting-started/enable-secure-boot.md">https://github.com/nix-community/lanzaboote/blob/master/docs/getting-started/enable-secure-boot.md</a></li>
<li>Idea for copying SSH keys: <a href="https://github.com/nix-community/nixos-anywhere/issues/604">https://github.com/nix-community/nixos-anywhere/issues/604</a></li>
<li>nixos-anywhere SSH keys copy: <a href="https://github.com/nix-community/nixos-anywhere/blob/main/docs/howtos/secrets.md#example-decrypting-an-openssh-host-key-with-pass">https://github.com/nix-community/nixos-anywhere/blob/main/docs/howtos/secrets.md#example-decrypting-an-openssh-host-key-with-pass</a></li>
<li>Discourse post with same issue as mine: <a href="https://discourse.nixos.org/t/impermanence-sops-nix-nixos-anywhere-lead-to-missing-hashedpasswordfile-s/66472">https://discourse.nixos.org/t/impermanence-sops-nix-nixos-anywhere-lead-to-missing-hashedpasswordfile-s/66472</a></li>
<li>Another discourse post with a similar issue: <a href="https://discourse.nixos.org/t/nixos-anywhere-failing-to-deploy-secrets/68392">https://discourse.nixos.org/t/nixos-anywhere-failing-to-deploy-secrets/68392</a></li>
</ul>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="https://ryanseipp.com/posts/nixos-automated-deployment/">https://ryanseipp.com/posts/nixos-automated-deployment/</a>&#160;<a href="https://haseebmajid.dev/posts/2025-12-31-how-to-setup-a-new-pc-with-lanzaboote-tpm-decryption-sops-nix-impermanence-nixos-anywhere/#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p><a href="https://wiki.nixos.org/wiki/Secure_Boot">https://wiki.nixos.org/wiki/Secure_Boot</a>&#160;<a href="https://haseebmajid.dev/posts/2025-12-31-how-to-setup-a-new-pc-with-lanzaboote-tpm-decryption-sops-nix-impermanence-nixos-anywhere/#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p><a href="https://oddlama.org/blog/bypassing-disk-encryption-with-tpm2-unlock/#crude-implementation-of-pcr15-verification">https://oddlama.org/blog/bypassing-disk-encryption-with-tpm2-unlock/#crude-implementation-of-pcr15-verification</a>&#160;<a href="https://haseebmajid.dev/posts/2025-12-31-how-to-setup-a-new-pc-with-lanzaboote-tpm-decryption-sops-nix-impermanence-nixos-anywhere/#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p><a href="https://www.youtube.com/watch?v=hXblxnDS6eU">https://www.youtube.com/watch?v=hXblxnDS6eU</a>&#160;<a href="https://haseebmajid.dev/posts/2025-12-31-how-to-setup-a-new-pc-with-lanzaboote-tpm-decryption-sops-nix-impermanence-nixos-anywhere/#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    
    <item>
      <title>How to Use Tailscale GitLab CI to Deploy to Our Nix Home lab</title>
      <link>https://haseebmajid.dev/posts/2025-07-10-how-to-use-tailscale-gitlab-ci-to-deploy-to-our-nix-homelab/</link>
      <pubDate>Thu, 10 Jul 2025 00:00:00 +0000</pubDate>
      <author>Haseeb Majid</author>
      <guid>https://haseebmajid.dev/posts/2025-07-10-how-to-use-tailscale-gitlab-ci-to-deploy-to-our-nix-homelab/</guid>
      <description>&lt;h2 id=&#34;background&#34;&gt;Background&lt;/h2&gt;
&lt;p&gt;So I have a small home lab, where I have a few random things running, like a GitLab CI runner, a media server etc.
All my home lab servers (3 of them) are running NixOS and are defined within my Nix flake what services they run.&lt;/p&gt;
&lt;p&gt;For example: &lt;a href=&#34;https://gitlab.com/hmajid2301/nixicle/-/blob/main/systems/x86_64-linux/ms01/default.nix?ref_type=heads&#34;&gt;https://gitlab.com/hmajid2301/nixicle/-/blob/main/systems/x86_64-linux/ms01/default.nix?ref_type=heads&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;They are set up using &lt;a href=&#34;https://haseebmajid.dev/posts/2024-05-02-part-5b-installing-our-nix-configuration-as-part-of-your-workflow&#34;&gt;nixos-anywhere like most of my other services&lt;/a&gt;. Then using deploy-rs we can ssh onto our home lab servers and deploy nix config, i.e. updating packages.&lt;/p&gt;</description><content:encoded><![CDATA[<h2 id="background">Background</h2>
<p>So I have a small home lab, where I have a few random things running, like a GitLab CI runner, a media server etc.
All my home lab servers (3 of them) are running NixOS and are defined within my Nix flake what services they run.</p>
<p>For example: <a href="https://gitlab.com/hmajid2301/nixicle/-/blob/main/systems/x86_64-linux/ms01/default.nix?ref_type=heads">https://gitlab.com/hmajid2301/nixicle/-/blob/main/systems/x86_64-linux/ms01/default.nix?ref_type=heads</a></p>
<p>They are set up using <a href="https://haseebmajid.dev/posts/2024-05-02-part-5b-installing-our-nix-configuration-as-part-of-your-workflow">nixos-anywhere like most of my other services</a>. Then using deploy-rs we can ssh onto our home lab servers and deploy nix config, i.e. updating packages.</p>
<p>I wanted a more automated way to do, i.e. anytime nixicle repo changes, try to deploy those changes to my home lab on CI.</p>
<h2 id="tailscale">Tailscale</h2>
<p>I connect to my home lab using Tailscale a very cool tool, think like a VPN, it uses WireGuard behind the scenes.
With some extra stuff on top to provide a really nice user experience. Highly recommend if you are looking to get into homelabbing.</p>
<p>So first, our CI runner would need to be able to connect to Tailscale to be able to deploy our new config onto the home lab
servers.</p>
<p>I will assume you have already set up your Tailscale account for the next part. I may create a tutorial later on how
you can do it. Better to be honest, there is a ton of decent literature available already, including videos.</p>
<p>First, let&rsquo;s create an auth key. Go to: <a href="https://login.tailscale.com/admin/settings/keys">https://login.tailscale.com/admin/settings/keys</a></p>
<p><img
        loading="eager"
        fetchpriority="high"
        decoding="async" src="https://haseebmajid.dev/posts/2025-07-10-how-to-use-tailscale-gitlab-ci-to-deploy-to-our-nix-homelab/images/auth_key_hu_b5c29e5dc335bc93.webp" srcset="https://haseebmajid.dev/posts/2025-07-10-how-to-use-tailscale-gitlab-ci-to-deploy-to-our-nix-homelab/images/auth_key_hu_b5c29e5dc335bc93.webp 549w" sizes="(max-width: 800px) calc(100vw - 40px), 760px" data-zoom-src="https://haseebmajid.dev/posts/2025-07-10-how-to-use-tailscale-gitlab-ci-to-deploy-to-our-nix-homelab/images/auth_key.png"
        width="549" height="986"
        alt="Auth Key"
        
      /></p>
<p>We want to enable Ephemeral, so the device gets removed from our list, else every time CI runs we will get a new
device in our list of machines. Then, in my case, I need to also check the pre-approve, so the admin doesn&rsquo;t need to
manually approve it, slightly defeating the purpose of CI.</p>
<p>You can also optionally create a tag for your machines using this auth key. In my case, I wanted to tag it with CI.
Which we can then use for ACL rules etc.</p>
<h2 id="gitlab-ci">Gitlab CI</h2>
<p>After creating the key copy the value, you won&rsquo;t be able to see it again, so keep that in mind. Add it to your CI
variables, you can check the masked option, so the auth key isn&rsquo;t accidentally printed in CI.</p>
<p>(You can access this using the left side bar; setting &gt; CI/CD &gt; Variables)</p>
<p><img
        loading="lazy"
        fetchpriority="auto"
        decoding="async" src="https://haseebmajid.dev/posts/2025-07-10-how-to-use-tailscale-gitlab-ci-to-deploy-to-our-nix-homelab/images/ci_vars_hu_acc8be95c9afda50.webp" srcset="https://haseebmajid.dev/posts/2025-07-10-how-to-use-tailscale-gitlab-ci-to-deploy-to-our-nix-homelab/images/ci_vars_hu_acc8be95c9afda50.webp 720w, https://haseebmajid.dev/posts/2025-07-10-how-to-use-tailscale-gitlab-ci-to-deploy-to-our-nix-homelab/images/ci_vars_hu_cbcc9e1a28901337.webp 1313w" sizes="(max-width: 800px) calc(100vw - 40px), 760px" data-zoom-src="https://haseebmajid.dev/posts/2025-07-10-how-to-use-tailscale-gitlab-ci-to-deploy-to-our-nix-homelab/images/ci_vars.png"
        width="1313" height="760"
        alt="Tailscale CI Vars"
        
      /></p>
<p><img
        loading="lazy"
        fetchpriority="auto"
        decoding="async" src="https://haseebmajid.dev/posts/2025-07-10-how-to-use-tailscale-gitlab-ci-to-deploy-to-our-nix-homelab/images/ci_var_hu_407560b406766f7a.webp" srcset="https://haseebmajid.dev/posts/2025-07-10-how-to-use-tailscale-gitlab-ci-to-deploy-to-our-nix-homelab/images/ci_var_hu_407560b406766f7a.webp 394w" sizes="(max-width: 800px) calc(100vw - 40px), 760px" data-zoom-src="https://haseebmajid.dev/posts/2025-07-10-how-to-use-tailscale-gitlab-ci-to-deploy-to-our-nix-homelab/images/ci_var.png"
        width="394" height="861"
        alt="Tailscale CI Var"
        
      /></p>
<p>Then our CI file may look something like this <code>.gitlab-ci.yml</code></p>
<div class="lab-neovim" data-code-lines="28" data-code-filename="snippet.yaml" data-code-lang="yaml">
  <div class="lab-code-tab">
    <span class="lab-code-filename" title="snippet.yaml">snippet.yaml</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">nixos/nix</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">stages</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">deploy</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">tailscale</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">deploy</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">rules</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">if</span><span class="p">:</span><span class="w"> </span><span class="l">$CI_PIPELINE_SOURCE == &#34;merge_request_event&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">mkdir -p /etc/nix/ /etc/ssh/</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">echo &#34;experimental-features = nix-command flakes&#34; &gt;&gt; /etc/nix/nix.conf</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">nix-env -iA nixpkgs.docker nixpkgs.go-task nixpkgs.flyctl nixpkgs.tailscale nixpkgs.jq</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">ssh-keygen -t ed25519 -N &#34;&#34; -f ~/.ssh/id_ed25519</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">ssh-keygen -t ed25519 -N &#34;&#34; -f /etc/ssh/ssh_host_ed25519_key</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">mkdir -p ~/.ssh</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">chmod 700 ~/.ssh</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">echo &#34;Host *&#34; &gt;&gt; ~/.ssh/config</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">echo &#34;  StrictHostKeyChecking no&#34; &gt;&gt; ~/.ssh/config</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">echo &#34;  UserKnownHostsFile /dev/null&#34; &gt;&gt; ~/.ssh/config</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">export SSH_ASKPASS_REQUIRE=never</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">eval $(ssh-agent -s)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">ssh-add ~/.ssh/id_ed25519 2&gt;/dev/null</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">tailscaled --state=&#34;mem:&#34; --statedir=/var/lib/tailscale &amp;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">tailscale up --authkey=${TAILSCALE_AUTHKEY} --hostname=&#34;gitlab-$(cat /etc/hostname)&#34; --accept-routes --ssh</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">nix profile install github:serokell/deploy-rs</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">deploy &#34;.#ms01&#34; --hostname ms01 --ssh-user nixos --skip-checks --confirm-timeout &#34;600&#34; --remote-build</span></span></span></code></pre></div>
  <div class="lab-code-bar">
    <b class="lab-code-lang">yaml</b>
    <span class="lab-code-lines">28 lines</span>
    <span class="lab-code-copy"></span>
  </div>
  <div class="lab-code-command" role="status">"snippet.yaml" 28L</div>
</div>
<p>Let&rsquo;s break this file down</p>
<p>First, we want to create some SSH keys so that you can SSH on the CI runner onto the home lab server.</p>
<p>The next part we start up the Tailscale daemon in the background, and then we authenticate with Tailscale so our
runner can now connect to our home lab servers</p>
<p>Then, finally, we install deploy-rs and use it to SSH to our machines and deploy to it.</p>
<h2 id="tailscale-ssh">Tailscale SSH</h2>
<p><img
        loading="lazy"
        fetchpriority="auto"
        decoding="async" src="https://haseebmajid.dev/posts/2025-07-10-how-to-use-tailscale-gitlab-ci-to-deploy-to-our-nix-homelab/images/ssh_hu_3adf946502a7d30f.webp" srcset="https://haseebmajid.dev/posts/2025-07-10-how-to-use-tailscale-gitlab-ci-to-deploy-to-our-nix-homelab/images/ssh_hu_3adf946502a7d30f.webp 230w" sizes="(max-width: 800px) calc(100vw - 40px), 760px" data-zoom-src="https://haseebmajid.dev/posts/2025-07-10-how-to-use-tailscale-gitlab-ci-to-deploy-to-our-nix-homelab/images/ssh.png"
        width="230" height="122"
        alt="SSH"
        
      /></p>
<p>I also had to enable Tailscale ssh on the host devices so we could SSH from the runner without needing to include
an SSH private key and adding that to the allowed SSH list on each server. Which we can do like so:</p>
<p><code>tailscale set --ssh</code></p>
<p>This lets Tailscale take over SSH for us, users can still SSH like normal, but you can also SSH like normal as well.
But just make sure you are aware of the security implications before you turn it on.</p>
<p>Then I set up on ACLs like so:</p>
<div class="lab-neovim" data-code-lines="35" data-code-filename="snippet.json" data-code-lang="json">
  <div class="lab-code-tab">
    <span class="lab-code-filename" title="snippet.json">snippet.json</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl">	<span class="s2">&#34;tagOwners&#34;</span><span class="err">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nt">&#34;tag:ci&#34;</span><span class="p">:</span>           <span class="p">[],</span>
</span></span><span class="line"><span class="cl">		<span class="nt">&#34;tag:homelab&#34;</span><span class="p">:</span>      <span class="p">[],</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span><span class="err">,</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="c1">// Define access control lists for users, groups, autogroups, tags,
</span></span></span><span class="line"><span class="cl">	<span class="c1">// Tailscale IP addresses, and subnet ranges.
</span></span></span><span class="line"><span class="cl">	<span class="s2">&#34;acls&#34;</span><span class="err">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">		<span class="c1">// Allow all connections.
</span></span></span><span class="line"><span class="cl">		<span class="c1">// Comment this section out if you want to define specific restrictions.
</span></span></span><span class="line"><span class="cl">		<span class="p">{</span><span class="nt">&#34;action&#34;</span><span class="p">:</span> <span class="s2">&#34;accept&#34;</span><span class="p">,</span> <span class="nt">&#34;src&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;tag:ci&#34;</span><span class="p">],</span> <span class="nt">&#34;dst&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;tag:homelab:*&#34;</span><span class="p">]},</span>
</span></span><span class="line"><span class="cl">		<span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="nt">&#34;action&#34;</span><span class="p">:</span> <span class="s2">&#34;accept&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">			<span class="nt">&#34;src&#34;</span><span class="p">:</span>    <span class="p">[</span><span class="s2">&#34;tag:homelab&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">			<span class="nt">&#34;dst&#34;</span><span class="p">:</span>    <span class="p">[</span><span class="s2">&#34;tag:homelab:*&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">		<span class="p">},</span>
</span></span><span class="line"><span class="cl">	<span class="p">]</span><span class="err">,</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="c1">// Define users and devices that can use Tailscale SSH.
</span></span></span><span class="line"><span class="cl">	<span class="s2">&#34;ssh&#34;</span><span class="err">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">		<span class="c1">// Allow all users to SSH into their own devices in check mode.
</span></span></span><span class="line"><span class="cl">		<span class="c1">// Comment this section out if you want to define specific restrictions.
</span></span></span><span class="line"><span class="cl">		<span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="nt">&#34;action&#34;</span><span class="p">:</span> <span class="s2">&#34;accept&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">			<span class="nt">&#34;src&#34;</span><span class="p">:</span>    <span class="p">[</span><span class="s2">&#34;autogroup:member&#34;</span><span class="p">,</span> <span class="s2">&#34;tag:ci&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">			<span class="nt">&#34;dst&#34;</span><span class="p">:</span>    <span class="p">[</span><span class="s2">&#34;tag:homelab&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">			<span class="nt">&#34;users&#34;</span><span class="p">:</span>  <span class="p">[</span><span class="s2">&#34;autogroup:nonroot&#34;</span><span class="p">,</span> <span class="s2">&#34;root&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">		<span class="p">},</span>
</span></span><span class="line"><span class="cl">		<span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="nt">&#34;action&#34;</span><span class="p">:</span> <span class="s2">&#34;accept&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">			<span class="nt">&#34;src&#34;</span><span class="p">:</span>    <span class="p">[</span><span class="s2">&#34;tag:ci&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">			<span class="nt">&#34;dst&#34;</span><span class="p">:</span>    <span class="p">[</span><span class="s2">&#34;tag:homelab&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">			<span class="nt">&#34;users&#34;</span><span class="p">:</span>  <span class="p">[</span><span class="s2">&#34;nixos&#34;</span><span class="p">,</span> <span class="s2">&#34;root&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">		<span class="p">},</span>
</span></span><span class="line"><span class="cl">	<span class="p">]</span><span class="err">,</span></span></span></code></pre></div>
  <div class="lab-code-bar">
    <b class="lab-code-lang">json</b>
    <span class="lab-code-lines">35 lines</span>
    <span class="lab-code-copy"></span>
  </div>
  <div class="lab-code-command" role="status">"snippet.json" 35L</div>
</div>
<p>I am no expert on ACL rules in Tailscale, but I think this allows the CI runner to SSH onto the home lab servers.
Which I, of course, have already tagged with home lab.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>How I Setup Tailwindcss LSP With Neovim &amp; Nix (With DaisyUI)</title>
      <link>https://haseebmajid.dev/posts/2025-05-06-how-i-setup-tailwindcss-lsp-with-neovim-nix-with-daisyui-/</link>
      <pubDate>Tue, 06 May 2025 00:00:00 +0000</pubDate>
      <author>Haseeb Majid</author>
      <guid>https://haseebmajid.dev/posts/2025-05-06-how-i-setup-tailwindcss-lsp-with-neovim-nix-with-daisyui-/</guid>
      <description>&lt;p&gt;Recently, I have been building an app (&lt;a href=&#34;https://voxicle.app&#34;&gt;https://voxicle.app&lt;/a&gt; #ShamelessPlug), with tailwind and DaisyUI. I have been
having issues getting the tailwind LSP to work nicely in Neovim, and only recently managed to make it work.
So in this article, I will go over how I set up, assuming you are using Nix as a package manager.&lt;/p&gt;
&lt;h2 id=&#34;why-nix&#34;&gt;Why Nix?&lt;/h2&gt;
&lt;p&gt;In my project, it is a go web app, so all the dependencies in the project are managed as go modules by my &lt;code&gt;go.mod&lt;/code&gt; file.
Then everything else the developer needs is set up as a dev shell. Things like the standalone tailwind CLI,
I am using a nix flake which already has DaisyUI built in so it can also generate those types i.e. &lt;code&gt;btn&lt;/code&gt; in the final
styles.css (or whatever you called it). The flake is here: &lt;a href=&#34;https://github.com/aabccd021/tailwindcss-daisyui-nix&#34;&gt;https://github.com/aabccd021/tailwindcss-daisyui-nix&lt;/a&gt;&lt;/p&gt;</description><content:encoded><![CDATA[<p>Recently, I have been building an app (<a href="https://voxicle.app">https://voxicle.app</a> #ShamelessPlug), with tailwind and DaisyUI. I have been
having issues getting the tailwind LSP to work nicely in Neovim, and only recently managed to make it work.
So in this article, I will go over how I set up, assuming you are using Nix as a package manager.</p>
<h2 id="why-nix">Why Nix?</h2>
<p>In my project, it is a go web app, so all the dependencies in the project are managed as go modules by my <code>go.mod</code> file.
Then everything else the developer needs is set up as a dev shell. Things like the standalone tailwind CLI,
I am using a nix flake which already has DaisyUI built in so it can also generate those types i.e. <code>btn</code> in the final
styles.css (or whatever you called it). The flake is here: <a href="https://github.com/aabccd021/tailwindcss-daisyui-nix">https://github.com/aabccd021/tailwindcss-daisyui-nix</a></p>
<p>I didn&rsquo;t want to have multiple package managers, using <code>npm</code> or something else.</p>
<p>You can learn more about this here: <a href="https://www.youtube.com/watch?v=bdGfn_ihHOk">https://www.youtube.com/watch?v=bdGfn_ihHOk</a></p>
<h2 id="setup">Setup</h2>
<p>Assuming we have enabled the tailwind LSP in our Neovim config, I am using NixCats, and it looks it like this (using nvim-lspconfig):</p>
<div class="lab-neovim" data-code-lines="4" data-code-filename="snippet.lua" data-code-lang="lua">
  <div class="lab-code-tab">
    <span class="lab-code-filename" title="snippet.lua">snippet.lua</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-lua" data-lang="lua"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;tailwindcss&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">lsp</span> <span class="o">=</span> <span class="p">{},</span>
</span></span><span class="line"><span class="cl"><span class="p">},</span></span></span></code></pre></div>
  <div class="lab-code-bar">
    <b class="lab-code-lang">lua</b>
    <span class="lab-code-lines">4 lines</span>
    <span class="lab-code-copy"></span>
  </div>
  <div class="lab-code-command" role="status">"snippet.lua" 4L</div>
</div>
<p>You can find my full config here:</p>
<ul>
<li><a href="https://gitlab.com/hmajid2301/nixicle/-/blob/8c959ca5bd6436595592d0cedf932787ba86e359/modules/home/cli/editors/neovim/lua/myLuaConf/LSPs/init.lua#L199">https://gitlab.com/hmajid2301/nixicle/-/blob/8c959ca5bd6436595592d0cedf932787ba86e359/modules/home/cli/editors/neovim/lua/myLuaConf/LSPs/init.lua#L199</a></li>
<li><a href="https://gitlab.com/hmajid2301/nixicle/-/blob/MAJ-311/modules/home/cli/editors/neovim/default.nix#L48">https://gitlab.com/hmajid2301/nixicle/-/blob/MAJ-311/modules/home/cli/editors/neovim/default.nix#L48</a></li>
</ul>
<p>So first I added this flake as an input</p>
<div class="lab-neovim" data-code-lines="2" data-code-filename="snippet.nix" data-code-lang="nix">
  <div class="lab-code-tab">
    <span class="lab-code-filename" title="snippet.nix">snippet.nix</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="n">tailwindcss_daisy</span><span class="o">.</span><span class="n">url</span> <span class="o">=</span> <span class="s2">&#34;github:aabccd021/tailwindcss-daisyui-nix/lsp&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="n">tailwindcss_daisy</span><span class="o">.</span><span class="n">inputs</span><span class="o">.</span><span class="n">nixpkgs</span><span class="o">.</span><span class="n">follows</span> <span class="o">=</span> <span class="s2">&#34;nixpkgs&#34;</span><span class="p">;</span></span></span></code></pre></div>
  <div class="lab-code-bar">
    <b class="lab-code-lang">nix</b>
    <span class="lab-code-lines">2 lines</span>
    <span class="lab-code-copy"></span>
  </div>
  <div class="lab-code-command" role="status">"snippet.nix" 2L</div>
</div>
<p>Then install the following packages:</p>
<div class="lab-neovim" data-code-lines="2" data-code-filename="snippet.nix" data-code-lang="nix">
  <div class="lab-code-tab">
    <span class="lab-code-filename" title="snippet.nix">snippet.nix</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="n">tailwindcss-language-server</span>
</span></span><span class="line"><span class="cl"><span class="n">tailwindcss_daisy</span><span class="o">.</span><span class="n">packages</span><span class="o">.</span><span class="si">${</span><span class="n">system</span><span class="si">}</span><span class="o">.</span><span class="n">default</span></span></span></code></pre></div>
  <div class="lab-code-bar">
    <b class="lab-code-lang">nix</b>
    <span class="lab-code-lines">2 lines</span>
    <span class="lab-code-copy"></span>
  </div>
  <div class="lab-code-command" role="status">"snippet.nix" 2L</div>
</div>
<p>The <code>tailwindcss-language-server</code> being the LSP server for tailwind. Normally, this is installed in my dot files repo.
(I linked above). But this one is an overlay from the flake. So this one also has DaisyUI in its NODE_PATH.
Then it also auto-complete classes from DaisyUI.</p>
<p>Read this issue: <a href="https://github.com/aabccd021/tailwindcss-daisyui-nix/issues/1">https://github.com/aabccd021/tailwindcss-daisyui-nix/issues/1</a></p>
<p>Finally, to get the tailwind LSP to work, we need to create an empty <code>tailwind.config.js</code>. Else, the tailwind LSP does
not seem to start correctly. Even though the project is configured using the CSS file (probably will be fixed)</p>
<p>That&rsquo;s it! It took me a while to figure it out, but this seems to work pretty nicely.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>How to Fix Firefox Rendering Emoji Font (NixOS)</title>
      <link>https://haseebmajid.dev/posts/2025-01-20-how-to-fix-firefox-rendering-emoji-font-black-bold-numbers-issue/</link>
      <pubDate>Mon, 20 Jan 2025 00:00:00 +0000</pubDate>
      <author>Haseeb Majid</author>
      <guid>https://haseebmajid.dev/posts/2025-01-20-how-to-fix-firefox-rendering-emoji-font-black-bold-numbers-issue/</guid>
      <description>&lt;p&gt;I was some issues with the ways some fonts were rendering in Nix/NixOS machines. It seemed to specifically effect
a few apps, mainly Firefox and only certain webpages. Sometimes numbers would render like below:&lt;/p&gt;
&lt;p&gt;&lt;img
        loading=&#34;eager&#34;
        fetchpriority=&#34;high&#34;
        decoding=&#34;async&#34; src=&#34;https://haseebmajid.dev/posts/2025-01-20-how-to-fix-firefox-rendering-emoji-font-black-bold-numbers-issue/images/bad_hu_feb498f6439c732.webp&#34; srcset=&#34;https://haseebmajid.dev/posts/2025-01-20-how-to-fix-firefox-rendering-emoji-font-black-bold-numbers-issue/images/bad_hu_feb498f6439c732.webp 586w&#34; sizes=&#34;(max-width: 800px) calc(100vw - 40px), 760px&#34; data-zoom-src=&#34;https://haseebmajid.dev/posts/2025-01-20-how-to-fix-firefox-rendering-emoji-font-black-bold-numbers-issue/images/bad.png&#34;
        width=&#34;586&#34; height=&#34;160&#34;
        alt=&#34;Bad Rendering&#34;
        
      /&gt;&lt;/p&gt;
&lt;h2 id=&#34;debugging-steps&#34;&gt;Debugging Steps&lt;/h2&gt;
&lt;p&gt;This occurred off and on over the course of a few weeks. My font setup was primarily done using
&lt;a href=&#34;https://github.com/danth/stylix&#34;&gt;stylix&lt;/a&gt; and a bit of NixOS option config.&lt;/p&gt;</description><content:encoded><![CDATA[<p>I was some issues with the ways some fonts were rendering in Nix/NixOS machines. It seemed to specifically effect
a few apps, mainly Firefox and only certain webpages. Sometimes numbers would render like below:</p>
<p><img
        loading="eager"
        fetchpriority="high"
        decoding="async" src="https://haseebmajid.dev/posts/2025-01-20-how-to-fix-firefox-rendering-emoji-font-black-bold-numbers-issue/images/bad_hu_feb498f6439c732.webp" srcset="https://haseebmajid.dev/posts/2025-01-20-how-to-fix-firefox-rendering-emoji-font-black-bold-numbers-issue/images/bad_hu_feb498f6439c732.webp 586w" sizes="(max-width: 800px) calc(100vw - 40px), 760px" data-zoom-src="https://haseebmajid.dev/posts/2025-01-20-how-to-fix-firefox-rendering-emoji-font-black-bold-numbers-issue/images/bad.png"
        width="586" height="160"
        alt="Bad Rendering"
        
      /></p>
<h2 id="debugging-steps">Debugging Steps</h2>
<p>This occurred off and on over the course of a few weeks. My font setup was primarily done using
<a href="https://github.com/danth/stylix">stylix</a> and a bit of NixOS option config.</p>
<p>I noticed this only seemed to happen when the CSS on the page include a font-family like so &ldquo;monospace&rdquo; i.e.
the monospace font in quotes. But without quotes, the font would render fine.</p>
<p>I made a few posts to ask for help, as I was confused, why this was happening, and found a similar issue,
you can find here: <a href="https://old.reddit.com/r/NixOS/comments/1fha5hl/firefox_font_problem/">https://old.reddit.com/r/NixOS/comments/1fha5hl/firefox_font_problem/</a>.</p>
<p>Anyway, I didn&rsquo;t get any reply, so I decided to use fc-match to see what font my system was returning.</p>
<div class="lab-neovim" data-code-lines="7" data-code-filename="snippet.sh" data-code-lang="bash">
  <div class="lab-code-tab">
    <span class="lab-code-filename" title="snippet.sh">snippet.sh</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">blog on  2025-01-20-how-to-fix-firefox-rendering-emoji-font-black-bold-numbers-issue <span class="o">[</span><span class="nv">$?</span><span class="o">]</span> via 🐹 v1.22.10 via ❄  impure <span class="o">(</span>nix-shell-env<span class="o">)</span> took 19s
</span></span><span class="line"><span class="cl">❯ fc-match monospace
</span></span><span class="line"><span class="cl">MonoLisa-Regular.ttf: <span class="s2">&#34;MonoLisa&#34;</span> <span class="s2">&#34;Regular&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">blog on  2025-01-20-how-to-fix-firefox-rendering-emoji-font-black-bold-numbers-issue <span class="o">[</span><span class="nv">$?</span><span class="o">]</span> via 🐹 v1.22.10 via ❄  impure <span class="o">(</span>nix-shell-env<span class="o">)</span>
</span></span><span class="line"><span class="cl">❯ fc-match <span class="s1">&#39;&#34;monospace&#34;&#39;</span>
</span></span><span class="line"><span class="cl">NotoSans<span class="o">[</span>wdth,wght<span class="o">]</span>.ttf: <span class="s2">&#34;Noto Sans&#34;</span> <span class="s2">&#34;Regular&#34;</span></span></span></code></pre></div>
  <div class="lab-code-bar">
    <b class="lab-code-lang">bash</b>
    <span class="lab-code-lines">7 lines</span>
    <span class="lab-code-copy"></span>
  </div>
  <div class="lab-code-command" role="status">"snippet.sh" 7L</div>
</div>
<p>I thought I&rsquo;d found the smoking gun, ohh it was trying to use the San font to render it. I asked someone to share their
working config from an Ubuntu machine and theirs also seemed to be similar to mine.</p>
<div class="lab-neovim" data-code-lines="5" data-code-filename="snippet.sh" data-code-lang="bash">
  <div class="lab-code-tab">
    <span class="lab-code-filename" title="snippet.sh">snippet.sh</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">╰─ fc-match <span class="s2">&#34;monospace&#34;</span>
</span></span><span class="line"><span class="cl">DejaVuSansMono.ttf: <span class="s2">&#34;DejaVu Sans Mono&#34;</span> <span class="s2">&#34;Book&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">╰─ fc-match <span class="s1">&#39;&#34;monospace&#34;&#39;</span>
</span></span><span class="line"><span class="cl">DejaVuSans.ttf: <span class="s2">&#34;DejaVu Sans&#34;</span> <span class="s2">&#34;Book&#34;</span></span></span></code></pre></div>
  <div class="lab-code-bar">
    <b class="lab-code-lang">bash</b>
    <span class="lab-code-lines">5 lines</span>
    <span class="lab-code-copy"></span>
  </div>
  <div class="lab-code-command" role="status">"snippet.sh" 5L</div>
</div>
<p>Then whilst looking up stuff specific to how Firefox renders fonts, I realised in the DevTools there is a page for
fonts, it&rsquo;s just a bit hidden. I mean, it makes sense, of course there is.</p>
<p><img
        loading="lazy"
        fetchpriority="auto"
        decoding="async" src="https://haseebmajid.dev/posts/2025-01-20-how-to-fix-firefox-rendering-emoji-font-black-bold-numbers-issue/images/fonts_hu_42db743e31ab4e7f.webp" srcset="https://haseebmajid.dev/posts/2025-01-20-how-to-fix-firefox-rendering-emoji-font-black-bold-numbers-issue/images/fonts_hu_42db743e31ab4e7f.webp 346w" sizes="(max-width: 800px) calc(100vw - 40px), 760px" data-zoom-src="https://haseebmajid.dev/posts/2025-01-20-how-to-fix-firefox-rendering-emoji-font-black-bold-numbers-issue/images/fonts.png"
        width="346" height="354"
        alt="Font Panel Firefox"
        
      /></p>
<p>So looking at this I realised it wasn&rsquo;t bold text it was rendering, but it was falling back and rendering the numbers
as emojis.</p>
<p>I then had a closer look at my font config in Nix and realised it wasn&rsquo;t doing what I thought it was doing.</p>
<div class="lab-neovim" data-code-lines="22" data-code-filename="snippet.xml" data-code-lang="xml">
  <div class="lab-code-tab">
    <span class="lab-code-filename" title="snippet.xml">snippet.xml</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="cp">&lt;?xml version=&#34;1.0&#34;?&gt;</span>
</span></span><span class="line"><span class="cl"><span class="cp">&lt;!DOCTYPE fontconfig SYSTEM &#34;fonts.dtd&#34;&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;fontconfig&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;alias</span> <span class="na">binding=</span><span class="s">&#34;weak&#34;</span><span class="nt">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&lt;family&gt;</span>monospace<span class="nt">&lt;/family&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&lt;prefer&gt;</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&lt;family&gt;</span>emoji<span class="nt">&lt;/family&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&lt;/prefer&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;/alias&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;alias</span> <span class="na">binding=</span><span class="s">&#34;weak&#34;</span><span class="nt">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&lt;family&gt;</span>sans-serif<span class="nt">&lt;/family&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&lt;prefer&gt;</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&lt;family&gt;</span>emoji<span class="nt">&lt;/family&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&lt;/prefer&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;/alias&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;alias</span> <span class="na">binding=</span><span class="s">&#34;weak&#34;</span><span class="nt">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&lt;family&gt;</span>serif<span class="nt">&lt;/family&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&lt;prefer&gt;</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&lt;family&gt;</span>emoji<span class="nt">&lt;/family&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&lt;/prefer&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;/alias&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/fontconfig&gt;</span></span></span></code></pre></div>
  <div class="lab-code-bar">
    <b class="lab-code-lang">xml</b>
    <span class="lab-code-lines">22 lines</span>
    <span class="lab-code-copy"></span>
  </div>
  <div class="lab-code-command" role="status">"snippet.xml" 22L</div>
</div>
<p>What I had done by copying and pasting some config I didn&rsquo;t fully understand (tut tut tut) is if the character was
available in the emoji font use that instead, i.e. the numbers or certain symbols like TM.
So after deleting that config and reloading Firefox, it was rendering properly as expected.</p>
<p>That&rsquo;s it! As expected, remember, to make sure you understand what your code is doing. I thought it was allowing emojis
to be rendered with the monospace font, not replacing it or preferring it.</p>
<p>Relevant commit resolving the above issue: <a href="https://gitlab.com/hmajid2301/nixicle/-/commit/3168db8df339d79e838c98f8675eb21a23452d4e">https://gitlab.com/hmajid2301/nixicle/-/commit/3168db8df339d79e838c98f8675eb21a23452d4e</a></p>
]]></content:encoded>
    </item>
    
    <item>
      <title>How to Wrap NixGL Around Package in Home Manager</title>
      <link>https://haseebmajid.dev/posts/2024-10-15-how-to-wrap-nixgl-around-package-in-home-manager/</link>
      <pubDate>Tue, 15 Oct 2024 00:00:00 +0000</pubDate>
      <author>Haseeb Majid</author>
      <guid>https://haseebmajid.dev/posts/2024-10-15-how-to-wrap-nixgl-around-package-in-home-manager/</guid>
      <description>&lt;p&gt;I use Nix mainly with home manager on my Ubuntu laptop, and for the most part, it works fine. However, some apps installed
using Nix, need to use &lt;a href=&#34;https://github.com/nix-community/nixGL&#34;&gt;nixGL&lt;/a&gt;. A wrapper tool for OpenGL, allowing Nix installed
tooling to use the system&amp;rsquo;s OpenGL and Vulkan APIs. Some apps including kitty and Firefox (mainly for Google Meet).&lt;/p&gt;
&lt;p&gt;There is currently a &lt;a href=&#34;https://github.com/nix-community/home-manager/pull/5355&#34;&gt;branch&lt;/a&gt; in home-manager we can pull
into our config, which provides a convenient way to wrap these apps in nixGL rather than needing to specify say
&lt;code&gt;nixGLIntel kitty&lt;/code&gt; in our terminal or say Hyprland config (key bindings).&lt;/p&gt;</description><content:encoded><![CDATA[<p>I use Nix mainly with home manager on my Ubuntu laptop, and for the most part, it works fine. However, some apps installed
using Nix, need to use <a href="https://github.com/nix-community/nixGL">nixGL</a>. A wrapper tool for OpenGL, allowing Nix installed
tooling to use the system&rsquo;s OpenGL and Vulkan APIs. Some apps including kitty and Firefox (mainly for Google Meet).</p>
<p>There is currently a <a href="https://github.com/nix-community/home-manager/pull/5355">branch</a> in home-manager we can pull
into our config, which provides a convenient way to wrap these apps in nixGL rather than needing to specify say
<code>nixGLIntel kitty</code> in our terminal or say Hyprland config (key bindings).</p>
<p>First, let&rsquo;s import the relevant code from the branch/PR:</p>
<div class="lab-neovim" data-code-lines="7" data-code-filename="snippet.nix" data-code-lang="nix">
  <div class="lab-code-tab">
    <span class="lab-code-filename" title="snippet.nix">snippet.nix</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"> <span class="n">imports</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># TODO: remove when https://github.com/nix-community/home-manager/pull/5355 gets merged:</span>
</span></span><span class="line"><span class="cl">    <span class="p">(</span><span class="nb">builtins</span><span class="o">.</span><span class="n">fetchurl</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">url</span> <span class="o">=</span> <span class="s2">&#34;https://raw.githubusercontent.com/Smona/home-manager/nixgl-compat/modules/misc/nixgl.nix&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">sha256</span> <span class="o">=</span> <span class="s2">&#34;01dkfr9wq3ib5hlyq9zq662mp0jl42fw3f6gd2qgdf8l8ia78j7i&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">})</span>
</span></span><span class="line"><span class="cl">  <span class="p">];</span></span></span></code></pre></div>
  <div class="lab-code-bar">
    <b class="lab-code-lang">nix</b>
    <span class="lab-code-lines">7 lines</span>
    <span class="lab-code-copy"></span>
  </div>
  <div class="lab-code-command" role="status">"snippet.nix" 7L</div>
</div>
<p>Then, simply, we can do something like this:</p>
<div class="lab-neovim" data-code-lines="4" data-code-filename="snippet.nix" data-code-lang="nix">
  <div class="lab-code-tab">
    <span class="lab-code-filename" title="snippet.nix">snippet.nix</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="n">programs</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">kitty</span><span class="o">.</span><span class="n">package</span> <span class="o">=</span> <span class="n">config</span><span class="o">.</span><span class="n">lib</span><span class="o">.</span><span class="n">nixGL</span><span class="o">.</span><span class="n">wrap</span> <span class="n">pkgs</span><span class="o">.</span><span class="n">kitty</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">firefox</span><span class="o">.</span><span class="n">package</span> <span class="o">=</span> <span class="n">config</span><span class="o">.</span><span class="n">lib</span><span class="o">.</span><span class="n">nixGL</span><span class="o">.</span><span class="n">wrap</span> <span class="n">pkgs</span><span class="o">.</span><span class="n">firefox</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div>
  <div class="lab-code-bar">
    <b class="lab-code-lang">nix</b>
    <span class="lab-code-lines">4 lines</span>
    <span class="lab-code-copy"></span>
  </div>
  <div class="lab-code-command" role="status">"snippet.nix" 4L</div>
</div>
<p>Then we should be able to use the apps like normal, such as using our app launcher like rofi.
That&rsquo;s It!</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/hmajid2301/nixicle/-/commit/512df880d52908a232ad48f7a043c4cbdd0264bc">Commit with nixGL wrap in my own config</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>How to Set up Cloudflare Tunnels With Traefik (In NixOS)</title>
      <link>https://haseebmajid.dev/posts/2024-09-30-how-to-setup-cloudflare-tunnels-to-point-to-service-behind-traefik/</link>
      <pubDate>Mon, 30 Sep 2024 00:00:00 +0000</pubDate>
      <author>Haseeb Majid</author>
      <guid>https://haseebmajid.dev/posts/2024-09-30-how-to-setup-cloudflare-tunnels-to-point-to-service-behind-traefik/</guid>
      <description>&lt;p&gt;Recently, I was setting up Navidrome, kind of like a self-hosted Spotify, in my home lab and I wanted to set it up to use
proxy auth in Authentik. But to do this I needed to use with a reverse proxy, i.e. Traefik. In this article
I will show you how to you can point your Cloudflare tunnel to Traefik and have that forward the request to the service.&lt;/p&gt;
&lt;h2 id=&#34;prerequisite&#34;&gt;Prerequisite&lt;/h2&gt;
&lt;p&gt;In this article, I assume you are already familiar with Cloudflare Tunnels and Traefik.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Recently, I was setting up Navidrome, kind of like a self-hosted Spotify, in my home lab and I wanted to set it up to use
proxy auth in Authentik. But to do this I needed to use with a reverse proxy, i.e. Traefik. In this article
I will show you how to you can point your Cloudflare tunnel to Traefik and have that forward the request to the service.</p>
<h2 id="prerequisite">Prerequisite</h2>
<p>In this article, I assume you are already familiar with Cloudflare Tunnels and Traefik.</p>
<h2 id="background">Background</h2>
<p>Cloudflare tunnels are an easy way to allow access to apps running on a server, i.e. home lab. Where you only want
expose, say, a few services. When a client connects, they connect to Cloudflare and then Cloudflare makes a secure
connection to your server. Which is running the <code>cloudflared</code> daemon.</p>
<p>Where my Cloudflare nix config looks something like this <sup id="fnref:1"><a href="https://haseebmajid.dev/posts/2024-09-30-how-to-setup-cloudflare-tunnels-to-point-to-service-behind-traefik/#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>:</p>
<div class="lab-neovim" data-code-lines="32" data-code-filename="snippet.nix" data-code-lang="nix">
  <div class="lab-code-tab">
    <span class="lab-code-filename" title="snippet.nix">snippet.nix</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">config</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">lib</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="p">}:</span>
</span></span><span class="line"><span class="cl"><span class="k">with</span> <span class="n">lib</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">with</span> <span class="n">lib</span><span class="o">.</span><span class="n">nixicle</span><span class="p">;</span> <span class="k">let</span>
</span></span><span class="line"><span class="cl">  <span class="n">cfg</span> <span class="o">=</span> <span class="n">config</span><span class="o">.</span><span class="n">services</span><span class="o">.</span><span class="n">nixicle</span><span class="o">.</span><span class="n">cloudflared</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">in</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">options</span><span class="o">.</span><span class="n">services</span><span class="o">.</span><span class="n">nixicle</span><span class="o">.</span><span class="n">cloudflared</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">enable</span> <span class="o">=</span> <span class="n">mkEnableOption</span> <span class="s2">&#34;Enable The cloudflared (tunnel) service&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">config</span> <span class="o">=</span> <span class="n">mkIf</span> <span class="n">cfg</span><span class="o">.</span><span class="n">enable</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">sops</span><span class="o">.</span><span class="n">secrets</span><span class="o">.</span><span class="n">cloudflared</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">sopsFile</span> <span class="o">=</span> <span class="sr">../secrets.yaml</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">owner</span> <span class="o">=</span> <span class="s2">&#34;cloudflared&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">services</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">cloudflared</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">tunnels</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="s2">&#34;ec0b6af0-a823-4616-a08b-b871fd2c7f58&#34;</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">credentialsFile</span> <span class="o">=</span> <span class="n">config</span><span class="o">.</span><span class="n">sops</span><span class="o">.</span><span class="n">secrets</span><span class="o">.</span><span class="n">cloudflared</span><span class="o">.</span><span class="n">path</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="n">default</span> <span class="o">=</span> <span class="s2">&#34;http_status:404&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">          <span class="p">};</span>
</span></span><span class="line"><span class="cl">        <span class="p">};</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div>
  <div class="lab-code-bar">
    <b class="lab-code-lang">nix</b>
    <span class="lab-code-lines">32 lines</span>
    <span class="lab-code-copy"></span>
  </div>
  <div class="lab-code-command" role="status">"snippet.nix" 32L</div>
</div>
<p>The other apps I have set up in Cloudflare tunnels point directly to the service itself. This is the first one
I needed to point to Traefik, and then Traefik would forward the request to Navidrome.</p>
<p>Where my Traefik setup looks something <sup id="fnref:2"><a href="https://haseebmajid.dev/posts/2024-09-30-how-to-setup-cloudflare-tunnels-to-point-to-service-behind-traefik/#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>:</p>
<div class="lab-neovim" data-code-lines="50" data-code-filename="snippet.nix" data-code-lang="nix">
  <div class="lab-code-tab">
    <span class="lab-code-filename" title="snippet.nix">snippet.nix</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">config</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">lib</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="p">}:</span>
</span></span><span class="line"><span class="cl"><span class="k">with</span> <span class="n">lib</span><span class="p">;</span> <span class="k">let</span>
</span></span><span class="line"><span class="cl">  <span class="n">cfg</span> <span class="o">=</span> <span class="n">config</span><span class="o">.</span><span class="n">services</span><span class="o">.</span><span class="n">nixicle</span><span class="o">.</span><span class="n">traefik</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">in</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">options</span><span class="o">.</span><span class="n">services</span><span class="o">.</span><span class="n">nixicle</span><span class="o">.</span><span class="n">traefik</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">enable</span> <span class="o">=</span> <span class="n">mkEnableOption</span> <span class="s2">&#34;Enable traefik&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">config</span> <span class="o">=</span> <span class="n">mkIf</span> <span class="n">cfg</span><span class="o">.</span><span class="n">enable</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">services</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">traefik</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">staticConfigOptions</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="c1"># removed config to simplify</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">          <span class="n">entryPoints</span><span class="o">.</span><span class="n">web</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">address</span> <span class="o">=</span> <span class="s2">&#34;0.0.0.0:80&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="n">http</span><span class="o">.</span><span class="n">redirections</span><span class="o">.</span><span class="n">entryPoint</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">              <span class="n">to</span> <span class="o">=</span> <span class="s2">&#34;websecure&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">              <span class="n">scheme</span> <span class="o">=</span> <span class="s2">&#34;https&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">              <span class="n">permanent</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="p">};</span>
</span></span><span class="line"><span class="cl">          <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">          <span class="n">entryPoints</span><span class="o">.</span><span class="n">websecure</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">address</span> <span class="o">=</span> <span class="s2">&#34;0.0.0.0:443&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="n">http</span><span class="o">.</span><span class="n">tls</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">              <span class="n">certResolver</span> <span class="o">=</span> <span class="s2">&#34;letsencrypt&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">              <span class="n">domains</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">                <span class="p">{</span>
</span></span><span class="line"><span class="cl">                  <span class="n">main</span> <span class="o">=</span> <span class="s2">&#34;homelab.haseebmajid.dev&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                  <span class="n">sans</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;*.homelab.haseebmajid.dev&#34;</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">                <span class="p">}</span>
</span></span><span class="line"><span class="cl">                <span class="p">{</span>
</span></span><span class="line"><span class="cl">                  <span class="n">main</span> <span class="o">=</span> <span class="s2">&#34;haseebmajid.dev&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                  <span class="n">sans</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;*.haseebmajid.dev&#34;</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">                <span class="p">}</span>
</span></span><span class="line"><span class="cl">              <span class="p">];</span>
</span></span><span class="line"><span class="cl">            <span class="p">};</span>
</span></span><span class="line"><span class="cl">          <span class="p">};</span>
</span></span><span class="line"><span class="cl">        <span class="p">};</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div>
  <div class="lab-code-bar">
    <b class="lab-code-lang">nix</b>
    <span class="lab-code-lines">50 lines</span>
    <span class="lab-code-copy"></span>
  </div>
  <div class="lab-code-command" role="status">"snippet.nix" 50L</div>
</div>
<p>Things to note about my setup (now shown here) but Traefik is set up to be able to provision certificates from let&rsquo;s encrypt
using Cloudflare DNS challenge (see full config linked above).</p>
<h2 id="setup">Setup</h2>
<p><img
        loading="eager"
        fetchpriority="high"
        decoding="async" src="https://haseebmajid.dev/posts/2024-09-30-how-to-setup-cloudflare-tunnels-to-point-to-service-behind-traefik/images/cloudflare-tunnels_hu_2729dd3c190c12a7.webp" srcset="https://haseebmajid.dev/posts/2024-09-30-how-to-setup-cloudflare-tunnels-to-point-to-service-behind-traefik/images/cloudflare-tunnels_hu_2729dd3c190c12a7.webp 720w, https://haseebmajid.dev/posts/2024-09-30-how-to-setup-cloudflare-tunnels-to-point-to-service-behind-traefik/images/cloudflare-tunnels_hu_fc222c2b28fbc70c.webp 1440w" sizes="(max-width: 800px) calc(100vw - 40px), 760px" data-zoom-src="https://haseebmajid.dev/posts/2024-09-30-how-to-setup-cloudflare-tunnels-to-point-to-service-behind-traefik/images/cloudflare-tunnels.png"
        width="1622" height="1008"
        alt="cloudflare-tunnels.png"
        
      /></p>
<p>We can see here this is how I configured my tunnel, first I point to traefik on port 443, i.e. https. As Traefik
I have set up always expects traffic on 443. Also notice the <code>origin server name</code> is <code>navidrome.haseebmajid.dev</code>.
This will get passed to Traefik.</p>
<p>Or in my Nix config I have defined it like so:</p>
<div class="lab-neovim" data-code-lines="19" data-code-filename="snippet.nix" data-code-lang="nix">
  <div class="lab-code-tab">
    <span class="lab-code-filename" title="snippet.nix">snippet.nix</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl"><span class="n">services</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">cloudflared</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">tunnels</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;ec0b6af0-a823-4616-a08b-b871fd2c7f58&#34;</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">ingress</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="s2">&#34;navidrome.haseebmajid.dev&#34;</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">service</span> <span class="o">=</span> <span class="s2">&#34;https://localhost&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="n">originRequest</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">              <span class="n">originServerName</span> <span class="o">=</span> <span class="s2">&#34;navidrome.haseebmajid.dev&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="p">};</span>
</span></span><span class="line"><span class="cl">          <span class="p">};</span>
</span></span><span class="line"><span class="cl">        <span class="p">};</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div>
  <div class="lab-code-bar">
    <b class="lab-code-lang">nix</b>
    <span class="lab-code-lines">19 lines</span>
    <span class="lab-code-copy"></span>
  </div>
  <div class="lab-code-command" role="status">"snippet.nix" 19L</div>
</div>
<p>Finally, the last bit to connect the two bits together, the Traefik config:</p>
<div class="lab-neovim" data-code-lines="25" data-code-filename="snippet.nix" data-code-lang="nix">
  <div class="lab-code-tab">
    <span class="lab-code-filename" title="snippet.nix">snippet.nix</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">traefik</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">dynamicConfigOptions</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">http</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">services</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="n">navidrome</span><span class="o">.</span><span class="n">loadBalancer</span><span class="o">.</span><span class="n">servers</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">            <span class="p">{</span>
</span></span><span class="line"><span class="cl">              <span class="n">url</span> <span class="o">=</span> <span class="s2">&#34;http://localhost:4533&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">          <span class="p">];</span>
</span></span><span class="line"><span class="cl">        <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">routers</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="n">navidrome</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">entryPoints</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;websecure&#34;</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">            <span class="n">rule</span> <span class="o">=</span> <span class="s2">&#34;Host(`navidrome.haseebmajid.dev`)&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="n">service</span> <span class="o">=</span> <span class="s2">&#34;navidrome&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="n">tls</span><span class="o">.</span><span class="n">certResolver</span> <span class="o">=</span> <span class="s2">&#34;letsencrypt&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="n">middlewares</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;authentik&#34;</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">          <span class="p">};</span>
</span></span><span class="line"><span class="cl">        <span class="p">};</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div>
  <div class="lab-code-bar">
    <b class="lab-code-lang">nix</b>
    <span class="lab-code-lines">25 lines</span>
    <span class="lab-code-copy"></span>
  </div>
  <div class="lab-code-command" role="status">"snippet.nix" 25L</div>
</div>
<p>Pointing to our local service <code>http://localhost:4533</code> where Navidrome is running.
We also tell it that if we get a request for host <code>navidrome.haseebmajid.dev</code> it should forward it to that
localhost service. We also tell it to provision a certificate using letsencrypt. Though you may not need this
as Cloudflare does it for you.</p>
<p>However, Cloudflare will only do it on direct subdomains, i.e. it won&rsquo;t work on <code>navidrome.homelab.haseebmajid.dev</code>.
Because it&rsquo;s a subdomain of a subdomain.</p>
<p>Then tell it to use the <code>authentik</code> middleware, which you can find out how to set up
<a href="https://haseebmajid.dev/posts/2024-09-06-how-to-setup-authentik-forward-auth-with-traefik-on-nixos/">here</a>. This tells Traefik to forward
the request to Authentik, so we can correctly authenticate the user.</p>
<p>That&rsquo;s it, we should be able to access our service Navidrome when it&rsquo;s all deployed. The request is going from
Client → Cloudflare →Traefik → Navidrome.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="https://gitlab.com/hmajid2301/nixicle/-/blob/39654a157bc95d02ad24b4fd9bb2379d3173f53b/modules/nixos/services/cloudflared/default.nix">https://gitlab.com/hmajid2301/nixicle/-/blob/39654a157bc95d02ad24b4fd9bb2379d3173f53b/modules/nixos/services/cloudflared/default.nix</a>&#160;<a href="https://haseebmajid.dev/posts/2024-09-30-how-to-setup-cloudflare-tunnels-to-point-to-service-behind-traefik/#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p><a href="https://gitlab.com/hmajid2301/nixicle/-/blob/39654a157bc95d02ad24b4fd9bb2379d3173f53b/modules/nixos/services/traefik/default.nix">https://gitlab.com/hmajid2301/nixicle/-/blob/39654a157bc95d02ad24b4fd9bb2379d3173f53b/modules/nixos/services/traefik/default.nix</a>&#160;<a href="https://haseebmajid.dev/posts/2024-09-30-how-to-setup-cloudflare-tunnels-to-point-to-service-behind-traefik/#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    
    <item>
      <title>How to Set up Authentik Forward Auth With Traefik on NixOS</title>
      <link>https://haseebmajid.dev/posts/2024-09-06-how-to-setup-authentik-forward-auth-with-traefik-on-nixos/</link>
      <pubDate>Fri, 06 Sep 2024 00:00:00 +0000</pubDate>
      <author>Haseeb Majid</author>
      <guid>https://haseebmajid.dev/posts/2024-09-06-how-to-setup-authentik-forward-auth-with-traefik-on-nixos/</guid>
      <description>&lt;p&gt;In this post, I will show you how you can set up forward auth for a single host in Authentik, with Traefik as our reverse
proxy on NixOS. This is particularly useful way to protect apps that don&amp;rsquo;t have any built in auth.&lt;/p&gt;
&lt;p&gt;Authentik is a great app which can handle authentication for almost all of our home lab. So we only need to log in
with Authentik, to log in to any of our apps. Rather than needing separate passwords and usernames for each app.&lt;/p&gt;</description><content:encoded><![CDATA[<p>In this post, I will show you how you can set up forward auth for a single host in Authentik, with Traefik as our reverse
proxy on NixOS. This is particularly useful way to protect apps that don&rsquo;t have any built in auth.</p>
<p>Authentik is a great app which can handle authentication for almost all of our home lab. So we only need to log in
with Authentik, to log in to any of our apps. Rather than needing separate passwords and usernames for each app.</p>
<p>You may see an error like this, when trying to access the app:</p>
<p><img
        loading="eager"
        fetchpriority="high"
        decoding="async" src="https://haseebmajid.dev/posts/2024-09-06-how-to-setup-authentik-forward-auth-with-traefik-on-nixos/images/404_hu_c93fa57383cef823.webp" srcset="https://haseebmajid.dev/posts/2024-09-06-how-to-setup-authentik-forward-auth-with-traefik-on-nixos/images/404_hu_c93fa57383cef823.webp 720w, https://haseebmajid.dev/posts/2024-09-06-how-to-setup-authentik-forward-auth-with-traefik-on-nixos/images/404_hu_b30a11b4f34f6ed3.webp 787w" sizes="(max-width: 800px) calc(100vw - 40px), 760px" data-zoom-src="https://haseebmajid.dev/posts/2024-09-06-how-to-setup-authentik-forward-auth-with-traefik-on-nixos/images/404.png"
        width="787" height="445"
        alt="404"
        
      /></p>
<p>or you may see this</p>
<p><img
        loading="lazy"
        fetchpriority="auto"
        decoding="async" src="https://haseebmajid.dev/posts/2024-09-06-how-to-setup-authentik-forward-auth-with-traefik-on-nixos/images/not-found_hu_3ba734649af241fa.webp" srcset="https://haseebmajid.dev/posts/2024-09-06-how-to-setup-authentik-forward-auth-with-traefik-on-nixos/images/not-found_hu_3ba734649af241fa.webp 362w" sizes="(max-width: 800px) calc(100vw - 40px), 760px" data-zoom-src="https://haseebmajid.dev/posts/2024-09-06-how-to-setup-authentik-forward-auth-with-traefik-on-nixos/images/not-found.png"
        width="362" height="196"
        alt="404"
        
      /></p>
<h2 id="background">Background</h2>
<p>As per is the case with everything I do, rather than sticking to my home lab in K3S. I decided to move some of it to
NixOS. The main reason for this being, it felt like there was a lot of boilerplate, sometimes 4 files just to deploy
an app. I felt like I was getting more frustrated and not really spending any time setting up my home lab.</p>
<p>So hence I decided to have a look at how I could do it with NixOS itself. I would lose out on some of the HA features, i.e.
if a node goes down, pods would get moved to another node. But for now, it would be good enough.</p>
<p>One thing that took me a while to figure out was how to set up forward auth like I had working with my Kubernetes
cluster. Such as protecting apps from the &ldquo;arr&rdquo; stack, such as Sonarr.</p>
<p>As seems to be the norm for these blog posts it took me longer than I would care to admit, so let me show you how
I did it.</p>
<h2 id="getting-started">Getting started</h2>
<p>For this, I will use the embedded outpost we have with Authentik and you are already using Traefik as your reverse proxy.</p>
<h3 id="authentik">Authentik</h3>
<p>To set up Authentik, I used <a href="https://github.com/nix-community/authentik-nix">authentik-nix</a>. Where my config looks
something like this:</p>
<div class="lab-neovim" data-code-lines="15" data-code-filename="snippet.nix" data-code-lang="nix">
  <div class="lab-code-tab">
    <span class="lab-code-filename" title="snippet.nix">snippet.nix</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">services</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">authentik</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">environmentFile</span> <span class="o">=</span> <span class="n">config</span><span class="o">.</span><span class="n">sops</span><span class="o">.</span><span class="n">secrets</span><span class="o">.</span><span class="n">authenik_env</span><span class="o">.</span><span class="n">path</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">settings</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="n">email</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="c1"># email settings</span>
</span></span><span class="line"><span class="cl">          <span class="p">};</span>
</span></span><span class="line"><span class="cl">          <span class="n">disable_startup_analytics</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">          <span class="n">avatars</span> <span class="o">=</span> <span class="s2">&#34;initials&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">};</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div>
  <div class="lab-code-bar">
    <b class="lab-code-lang">nix</b>
    <span class="lab-code-lines">15 lines</span>
    <span class="lab-code-copy"></span>
  </div>
  <div class="lab-code-command" role="status">"snippet.nix" 15L</div>
</div>
<p>Where my authentik_env config in sops looks like:</p>
<div class="lab-neovim" data-code-lines="3" data-code-filename="snippet.txt" data-code-lang="text">
  <div class="lab-code-tab">
    <span class="lab-code-filename" title="snippet.txt">snippet.txt</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">authenik_env: |
</span></span><span class="line"><span class="cl">    AUTHENTIK_SECRET_KEY=
</span></span><span class="line"><span class="cl">    AUTHENTIK_EMAIL__PASSWORD=</span></span></code></pre></div>
  <div class="lab-code-bar">
    <b class="lab-code-lang">text</b>
    <span class="lab-code-lines">3 lines</span>
    <span class="lab-code-copy"></span>
  </div>
  <div class="lab-code-command" role="status">"snippet.txt" 3L</div>
</div>
<p>Then the key bit I was missing the relevant Traefik config:</p>
<ul>
<li>Define a new middleware</li>
<li>Define a new service</li>
<li>Define a new route to take us to the Authentik embedded outpost</li>
</ul>
<div class="lab-neovim" data-code-lines="48" data-code-filename="snippet.nix" data-code-lang="nix">
  <div class="lab-code-tab">
    <span class="lab-code-filename" title="snippet.nix">snippet.nix</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="n">services</span><span class="o">.</span><span class="n">traefik</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">dynamicConfigOptions</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">http</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">middlewares</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="n">authentik</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">forwardAuth</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">              <span class="n">tls</span><span class="o">.</span><span class="n">insecureSkipVerify</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">              <span class="n">address</span> <span class="o">=</span> <span class="s2">&#34;https://localhost:9443/outpost.goauthentik.io/auth/traefik&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">              <span class="n">trustForwardHeader</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">              <span class="n">authResponseHeaders</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;X-authentik-username&#34;</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;X-authentik-groups&#34;</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;X-authentik-email&#34;</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;X-authentik-name&#34;</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;X-authentik-uid&#34;</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;X-authentik-jwt&#34;</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;X-authentik-meta-jwks&#34;</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;X-authentik-meta-outpost&#34;</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;X-authentik-meta-provider&#34;</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;X-authentik-meta-app&#34;</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;X-authentik-meta-version&#34;</span>
</span></span><span class="line"><span class="cl">              <span class="p">];</span>
</span></span><span class="line"><span class="cl">            <span class="p">};</span>
</span></span><span class="line"><span class="cl">          <span class="p">};</span>
</span></span><span class="line"><span class="cl">        <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">services</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="n">auth</span><span class="o">.</span><span class="n">loadBalancer</span><span class="o">.</span><span class="n">servers</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">            <span class="p">{</span>
</span></span><span class="line"><span class="cl">              <span class="n">url</span> <span class="o">=</span> <span class="s2">&#34;http://localhost:9000&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">          <span class="p">];</span>
</span></span><span class="line"><span class="cl">        <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">routers</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="n">auth</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">entryPoints</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;websecure&#34;</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">            <span class="n">rule</span> <span class="o">=</span> <span class="s2">&#34;Host(`authentik.haseebmajid.dev`) || HostRegexp(`{subdomain:[a-z0-9]+}.haseebmajid.com`) &amp;&amp; PathPrefix(`/outpost.goauthentik.io/`)&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="n">service</span> <span class="o">=</span> <span class="s2">&#34;auth&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="n">tls</span><span class="o">.</span><span class="n">certResolver</span> <span class="o">=</span> <span class="s2">&#34;letsencrypt&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">          <span class="p">};</span>
</span></span><span class="line"><span class="cl">        <span class="p">};</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl"> <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div>
  <div class="lab-code-bar">
    <b class="lab-code-lang">nix</b>
    <span class="lab-code-lines">48 lines</span>
    <span class="lab-code-copy"></span>
  </div>
  <div class="lab-code-command" role="status">"snippet.nix" 48L</div>
</div>
<p>The main bit I was missing was the address I needed to use the port <code>9443</code> to redirect to the embed outpost.
Then creating the relevant auth redirect:</p>
<div class="lab-neovim" data-code-lines="6" data-code-filename="snippet.nix" data-code-lang="nix">
  <div class="lab-code-tab">
    <span class="lab-code-filename" title="snippet.nix">snippet.nix</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl">  <span class="n">auth</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">entryPoints</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;websecure&#34;</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="n">rule</span> <span class="o">=</span> <span class="s2">&#34;Host(`authentik.haseebmajid.dev`) || HostRegexp(`{subdomain:[a-z0-9]+}.haseebmajid.com`) &amp;&amp; PathPrefix(`/outpost.goauthentik.io/`)&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">service</span> <span class="o">=</span> <span class="s2">&#34;auth&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">tls</span><span class="o">.</span><span class="n">certResolver</span> <span class="o">=</span> <span class="s2">&#34;letsencrypt&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span></span></span></code></pre></div>
  <div class="lab-code-bar">
    <b class="lab-code-lang">nix</b>
    <span class="lab-code-lines">6 lines</span>
    <span class="lab-code-copy"></span>
  </div>
  <div class="lab-code-command" role="status">"snippet.nix" 6L</div>
</div>
<p>Where you should adjust <code>authentik.haseebmajid.dev</code> to match the domain you are using to access authentik on.
Then you can simply follow the <a href="https://docs.goauthentik.io/integrations/services/sonarr/">docs</a> and should be
able to repeat this for all apps you want to set this forward auth for.</p>
<p>Finally, in our Sonarr Traefik config, we would like to add our middleware that we just defined above:</p>
<div class="lab-neovim" data-code-lines="7" data-code-filename="snippet.nix" data-code-lang="nix">
  <div class="lab-code-tab">
    <span class="lab-code-filename" title="snippet.nix">snippet.nix</span>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl">  <span class="n">sonarr</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">entryPoints</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;websecure&#34;</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="n">rule</span> <span class="o">=</span> <span class="s2">&#34;Host(`sonarr.bare.homelab.haseebmajid.dev`)&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">service</span> <span class="o">=</span> <span class="s2">&#34;sonarr&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">tls</span><span class="o">.</span><span class="n">certResolver</span> <span class="o">=</span> <span class="s2">&#34;letsencrypt&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">middlewares</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;authentik&#34;</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span></span></span></code></pre></div>
  <div class="lab-code-bar">
    <b class="lab-code-lang">nix</b>
    <span class="lab-code-lines">7 lines</span>
    <span class="lab-code-copy"></span>
  </div>
  <div class="lab-code-command" role="status">"snippet.nix" 7L</div>
</div>
<p>That&rsquo;s about it! The main thing I was missing was that <code>https://localhost:9443/outpost.goauthentik.io/auth/traefik</code>
especially the port. After I added that everything worked like a dream.</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li>My home lab Nix config
<ul>
<li><a href="https://gitlab.com/hmajid2301/nixicle/-/blob/58ba59fee49b2533f1e047acae7fc06d6192667d/modules/nixos/services/arr/default.nix#L143">Sonarr Traefik config</a></li>
<li><a href="https://gitlab.com/hmajid2301/nixicle/-/blob/58ba59fee49b2533f1e047acae7fc06d6192667d/modules/nixos/services/authentik/default.nix#L55">Authentik config</a></li>
</ul>
</li>
</ul>
]]></content:encoded>
    </item>
    
  </channel>
</rss>
