<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Luka builds things</title>
  <subtitle>Join me as I learn about software, self-hosting and all things DIY</subtitle>
  <link href="https://luka.korosec.cc/feed.xml" rel="self"/>
  <link href="https://luka.korosec.cc/blog/"/>
  <updated>2026-07-17T00:00:00.000Z</updated>
  <id>https://luka.korosec.cc/</id>
  <author>
    <name>Luka Korošec</name>
  </author>
  <entry>
    <title>My consultancy stack</title>
    <link href="https://luka.korosec.cc/posts/2026/07/my-consultancy-stack/"/>
    <published>2026-07-17T00:00:00.000Z</published>
    <updated>2026-07-17T00:00:00.000Z</updated>
    <id>https://luka.korosec.cc/posts/2026/07/my-consultancy-stack/</id>
    <content type="html">&lt;p&gt;If you don&#39;t know yet, I&#39;ve started my own IT consultancy!
Yay!&lt;/p&gt;
&lt;p&gt;The most interesting part, from a builder&#39;s perspective of course, was thinking about what new services I&#39;ll likely need to spin up in my homelab.&lt;/p&gt;
&lt;p&gt;Let&#39;s take a look at a few areas we need to cover in our one-man-consultancy.&lt;/p&gt;
&lt;h2&gt;Tracking opportunities and getting clients&lt;/h2&gt;
&lt;p&gt;Before you start working for a client, you first have to find them and then negotiate with them and convince them you&#39;re the one to help solve their problem, whatever that might be.
Since you&#39;re likely not talking with just one client, you&#39;ll want to track this process for each client separately.
So what you&#39;ll need is a &lt;a href=&quot;https://en.wikipedia.org/wiki/Customer_relationship_management&quot;&gt;CRM&lt;/a&gt; system.&lt;/p&gt;
&lt;p&gt;I&#39;ve looked at a few and the one I&#39;ve decided to self-host is &lt;a href=&quot;https://www.espocrm.com/&quot;&gt;EspoCRM&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You can self-host it on your own server for free, it integrates with your email and supports OIDC authentication!&lt;/p&gt;
&lt;p&gt;I like its simplicity and ease of use.&lt;/p&gt;
&lt;p&gt;One thing I dislike about it is that its built-in calendar and meeting tracking system does not expose or integrate with CalDAV natively.
Which brings us to the next thing you&#39;ll need.&lt;/p&gt;
&lt;h2&gt;Calendar&lt;/h2&gt;
&lt;p&gt;You&#39;ll have lots and lots of meetings with clients.
So having a calendar is essential.&lt;/p&gt;
&lt;p&gt;I used to use NextCloud for that, but recently I switched over to &lt;a href=&quot;https://github.com/lennart-k/rustical&quot;&gt;Rustical&lt;/a&gt;, so this is now my calendar and contacts server.&lt;/p&gt;
&lt;p&gt;I use &lt;a href=&quot;https://www.thunderbird.net/&quot;&gt;Thunderbird&lt;/a&gt; for interacting with my calendar on my laptop and on the mobile I use &lt;a href=&quot;https://f-droid.org/packages/ws.xsoh.etar/&quot;&gt;Etar&lt;/a&gt; via &lt;a href=&quot;https://www.davx5.com/&quot;&gt;DAVx5&lt;/a&gt;.
All of this works great.&lt;/p&gt;
&lt;p&gt;Bonus point, Rustical works great with OIDC.&lt;/p&gt;
&lt;h2&gt;Task management&lt;/h2&gt;
&lt;p&gt;Even before opening your consultancy you&#39;ll have a lot of tasks to do.
For example, you&#39;ll need to actually register your consultancy, open a bank account, manage your insurance and so on.
You&#39;ll want to track these things!&lt;/p&gt;
&lt;p&gt;The winner in this category for me was &lt;a href=&quot;https://vikunja.io/&quot;&gt;Vikunja&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This one was easy to choose because it&#39;s a mature service and ticks all the boxes for me.
The most important being OIDC authentication.&lt;/p&gt;
&lt;p&gt;I like that it&#39;s not bloated and is very snappy.&lt;/p&gt;
&lt;p&gt;So far, I have no complaints about this one.&lt;/p&gt;
&lt;h2&gt;Time tracking&lt;/h2&gt;
&lt;p&gt;Doing a project for a paying client means they would (generally) like to know where your time has gone, so you&#39;ll need to have a way of showing that to the client.
Most of the projects are also paid by-the-hour, so knowing how many hours a project took is important.&lt;/p&gt;
&lt;p&gt;For this &lt;a href=&quot;https://www.kimai.org/en/&quot;&gt;Kimai&lt;/a&gt; was chosen.&lt;/p&gt;
&lt;p&gt;It&#39;s a very mature project and it has been great so far.
There are also a lot of community developed apps like &lt;a href=&quot;https://github.com/Foraum-GmbH/kimai-clock&quot;&gt;kimai-clock&lt;/a&gt; (macOS menu bar app).&lt;/p&gt;
&lt;p&gt;Feels polished and has many knobs you can turn to get it tuned just right.&lt;/p&gt;
&lt;p&gt;One thing that almost turned me towards other solutions was a lack of OIDC login.
Luckily for me it does support LDAP login, which is what my OIDC provider (Authelia) uses as the user store.&lt;/p&gt;
&lt;h2&gt;Invoicing&lt;/h2&gt;
&lt;p&gt;While Kimai can also do invoicing, this is something that I decided not to self-host, due to future regulatory requirements.&lt;/p&gt;
&lt;p&gt;For this I&#39;m using a local service &lt;a href=&quot;https://www.cebelca.biz/&quot;&gt;Cebelca&lt;/a&gt;.
It&#39;s used by a lot of Slovenian entrepreneurs and is a kind of go-to for invoicing.&lt;/p&gt;
&lt;p&gt;It&#39;s free for up to 15 invoices per month and is just a joy to use.&lt;/p&gt;
&lt;p&gt;I love its simple interface.&lt;/p&gt;
&lt;p&gt;If I had to critique something, I&#39;d point out that the invoice customization with a logo could be a bit better, since the size of the logo you upload (in pixels) is the size you&#39;ll see on the PDF.&lt;/p&gt;
&lt;h2&gt;Development environment&lt;/h2&gt;
&lt;p&gt;At first I thought I&#39;ll have to buy a beefy MacBook Pro for me to develop on, but since it&#39;s not cheap, and I already have a beefy server, I decided I&#39;ll just use the server for development and I bought a MacBook Air instead.&lt;/p&gt;
&lt;p&gt;I have Incus running on the server (see &lt;a href=&quot;/posts/2025/09/declarative-incus-on-nixos-with-sso/&quot;&gt;Declarative Incus on NixOS with SSO&lt;/a&gt;), so my current workflow is that whenever I get a new client, I spin up a new VM just for that client and work on their projects in that VM exclusively.&lt;/p&gt;
&lt;p&gt;So each morning I SSH into the VM of the client whose project I&#39;ll work on.
&lt;a href=&quot;https://zellij.dev/&quot;&gt;Zellij&lt;/a&gt; (a terminal multiplexer) auto-attaches and I&#39;m presented with the exact state that I left yesterday on.
I usually run Emacs in one tab, then I have multiple other tabs open for running shell commands, following logs and driving Claude Code.
All of this works just as it would on my laptop!&lt;/p&gt;
&lt;p&gt;Even testing out web apps is not an issue.
SSH supports port forwarding, so opening &lt;code&gt;localhost:1234&lt;/code&gt; is forwarded to the VM to the same (or a different) port, allowing inspection and quick iteration.&lt;/p&gt;
&lt;p&gt;There are a few benefits to this VM development approach:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;no cross contamination&lt;/strong&gt;: git credentials, SSH keys, API keys; all of these are scoped per client, so I just can&#39;t accidentally commit with a wrong identity&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;persistent environment&lt;/strong&gt;: you can update/restart your Mac without closing your editor or stopping a long running test or a compilation&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;declarative&lt;/strong&gt;: each VM has its own NixOS configuration, with client-specific setup&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;not a macOS&lt;/strong&gt;: macOS is great, but sometimes it just adds friction, e.g. ARM vs x86 docker containers&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;close to deployment environment&lt;/strong&gt;: it&#39;s a Linux VM and you can make it as close as you want to the actual target deployment environment for the service&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What about cons?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;file access&lt;/strong&gt;: if you want to send something to the client (e.g. a parsed CSV), you first have to &lt;code&gt;rsync&lt;/code&gt; it to your laptop&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;you need the server&lt;/strong&gt;: if your server isn&#39;t online, you can&#39;t work&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;no offline work&lt;/strong&gt;: if you&#39;re not online, you can&#39;t work either&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;no clipboard&lt;/strong&gt;: copying text is a bit more involved than it would be on your laptop&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Try it out, it&#39;s been great for me so far.&lt;/p&gt;
&lt;h2&gt;Email and website&lt;/h2&gt;
&lt;p&gt;This is something that I already had before starting a consultancy and I think it&#39;s a must-have.&lt;/p&gt;
&lt;p&gt;Running a website is simple, you can just chuck an HTML file and point Caddy to it.&lt;/p&gt;
&lt;p&gt;As for emails, they are quite important, and I&#39;ve read all sorts of horror stories about self-hosting this on residential ISPs that I&#39;m letting &lt;a href=&quot;https://www.migadu.com/&quot;&gt;Migadu&lt;/a&gt; take care of that one for me.
I do use Thunderbird for this as well on my Mac and I bought &lt;a href=&quot;https://email.faircode.eu/&quot;&gt;Fairmail&lt;/a&gt; for my phone.
Recommend both!&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Running a consultancy is not easy, but having great open-source projects that you can rely on, makes it a bit easier.&lt;/p&gt;
&lt;p&gt;Looking back at the list, it&#39;s apparent how much OIDC drove my choices.&lt;/p&gt;
&lt;p&gt;If you&#39;re running your own consultancy on a different set of tools, I&#39;d love to hear about it, feel free to &lt;a href=&quot;mailto:luka@korosec.cc?subject=My consultancy stack&quot;&gt;email me&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Turn your server into a family game streaming hub</title>
    <link href="https://luka.korosec.cc/posts/2025/11/turn-your-server-into-a-family-game-streaming-hub/"/>
    <published>2025-11-04T00:00:00.000Z</published>
    <updated>2025-11-04T00:00:00.000Z</updated>
    <id>https://luka.korosec.cc/posts/2025/11/turn-your-server-into-a-family-game-streaming-hub/</id>
    <content type="html">&lt;p&gt;If you&#39;ve read my &lt;a href=&quot;/posts/2025/09/my-hardware/&quot;&gt;My hardware&lt;/a&gt; post, you&#39;ve seen that I had a gaming PC at some point.
I cannibalized its graphics card for my server to run speech-to-text models on it for my Home Assistant, which means no more gaming for me.&lt;/p&gt;
&lt;p&gt;Or does it?&lt;/p&gt;
&lt;h2&gt;VM gaming&lt;/h2&gt;
&lt;p&gt;For a while, before I ran the STT model on it, I created a gaming VM, passed through the graphic card and gamed on it.
It worked fine!&lt;/p&gt;
&lt;p&gt;The server was under my desk, so I could just connect the output of the GPU to my screen, plug in (and pass through) my keyboard, mouse and headphones and off I went.&lt;/p&gt;
&lt;p&gt;Still, not everything is great with VM gaming:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;it&#39;s a hassle to turn on the VM, I had to SSH into the server to turn it on&lt;/li&gt;
&lt;li&gt;GPU can only be used by the VM, you can&#39;t share it with other processes on the server&lt;/li&gt;
&lt;li&gt;you have to be smart about disk space management, you either go with thinly provisioned storage (speed penalty), or possibly over-provision&lt;/li&gt;
&lt;li&gt;you can&#39;t share a VM for gaming with multiple people practically&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So while it worked ok for my use case, I yearned for more.&lt;/p&gt;
&lt;p&gt;That&#39;s when I discovered container gaming.&lt;/p&gt;
&lt;h2&gt;Container gaming&lt;/h2&gt;
&lt;p&gt;While trying to set up headless LXC container with Incus (see my &lt;a href=&quot;/posts/2025/09/declarative-incus-on-nixos-with-sso/&quot;&gt;post about setting up Incus with SSO&lt;/a&gt;), I came across &lt;a href=&quot;https://games-on-whales.github.io/&quot;&gt;Games on Whales&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Their tagline is &amp;quot;Stream multiple virtual desktops and games running in Docker!&amp;quot;, and that is exactly what it does.&lt;/p&gt;
&lt;p&gt;On your server, you run their container called &lt;code&gt;wolf&lt;/code&gt; which itself runs a Sunshine-like application.
You then pair it with your Moonlight client running on your laptop or TV and once that is done, you can start up various applications or desktops.&lt;/p&gt;
&lt;p&gt;They have a good default selection:&lt;/p&gt;
&lt;img src=&quot;/assets/images/008-moonlight-apps.png&quot; alt=&quot;A screenshot of the moonlight client featuring applications like Steam, RetroArch, Firefox, Kodi, XFCE (Desktop), ...&quot;&gt;
&lt;p&gt;You can add your own applications/containers later if you wish, I have yet to do so.&lt;/p&gt;
&lt;p&gt;Moonlight passes through your input devices (keyboard+mouse/gamepad) and streams back the application GUI.&lt;/p&gt;
&lt;h3&gt;Sunshine + Moonlight&lt;/h3&gt;
&lt;p&gt;If you haven&#39;t heard about either of them, they are desktop streaming protocols, not that different from VNC or RDP, but made for low-latency high-quality remote gaming or desktop streaming.&lt;/p&gt;
&lt;p&gt;Sunshine is the &#39;server&#39; part, or something that you install on the PC you want to &lt;strong&gt;stream from&lt;/strong&gt;, while the Moonlight client is installed on your laptop or TV, where you want to &lt;strong&gt;stream to&lt;/strong&gt;.&lt;/p&gt;
&lt;h3&gt;How it works under the hood&lt;/h3&gt;
&lt;p&gt;I won&#39;t go into too much detail, but the gist of it is that it relies heavily on containers.&lt;/p&gt;
&lt;p&gt;So the &#39;main&#39; container (&lt;code&gt;wolf&lt;/code&gt;) acts as a Sunshine server, allowing Moonlight clients to pair with it.
It&#39;s not a real Sunshine server though, it just talks the talk.&lt;/p&gt;
&lt;p&gt;Currently you pair it by opening a URL &lt;code&gt;wolf&lt;/code&gt; gives you in its container logs (something like https://your.host/pin/#123D3240715B5C74), which leads to an input form where you can input the PIN that was given to you by the Moonlight client.&lt;/p&gt;
&lt;p&gt;Once paired, you can view the applications in Moonlight.
When starting an application, &lt;code&gt;wolf&lt;/code&gt; starts a container related to that application, mounts your data and your graphics card and starts the stream.&lt;/p&gt;
&lt;p&gt;For example if you selected the &lt;code&gt;Steam&lt;/code&gt; app, &lt;code&gt;wolf&lt;/code&gt; will:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;start &lt;code&gt;SteamApp_1234567890&lt;/code&gt; container, where the number is the internal ID of your user&lt;/li&gt;
&lt;li&gt;create a folder &lt;code&gt;1234567890&lt;/code&gt; (on your server) where it will store all your user app data&lt;/li&gt;
&lt;li&gt;mount a sub-folder into the container, e.g. &lt;code&gt;1234567890/Steam&lt;/code&gt; will be mounted into the container at &lt;code&gt;~/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;mount your graphics card so that it will be used by Steam to render games&lt;/li&gt;
&lt;li&gt;set up audio (via I think another separate container?)&lt;/li&gt;
&lt;li&gt;map through your input devices&lt;/li&gt;
&lt;li&gt;probably more stuff that I&#39;m not aware (yet)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The result is amazing. It works great, the latency is very low, I was getting 60 FPS of crisp images and the sound was of high quality.&lt;/p&gt;
&lt;p&gt;To give you some concrete numbers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;On my laptop over 5GHz WiFi: steady 60 FPS, 6ms network latency, 4ms decoding latency&lt;/li&gt;
&lt;li&gt;On my NVidia Shield TV over gigabit cable: 1920x1080 at steady 60 FPS, 1ms network latency, 1.5ms decoding latency&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Not all is roses though&lt;/strong&gt;.&lt;/p&gt;
&lt;h3&gt;Security Considerations&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;One critical caveat&lt;/strong&gt;: For &lt;code&gt;wolf&lt;/code&gt; to spawn new containers, you must give it access to the Docker/Podman socket.&lt;/p&gt;
&lt;p&gt;This essentially gives &lt;code&gt;wolf&lt;/code&gt; root-level control over your entire container infrastructure, meaning it can start, stop, and access any container on your system.&lt;/p&gt;
&lt;p&gt;How significant this risk is depends on your setup. I run 95% of my services on bare metal, so not in containers, and the other 5% are stateless services (&lt;a href=&quot;https://gotenberg.dev/&quot;&gt;Gotenberg&lt;/a&gt; and &lt;a href=&quot;https://tika.apache.org/&quot;&gt;Apache Tika&lt;/a&gt;) and &lt;a href=&quot;https://github.com/meeb/tubesync&quot;&gt;TubeSync&lt;/a&gt;. So nothing really private.&lt;/p&gt;
&lt;p&gt;You have been warned!&lt;/p&gt;
&lt;h2&gt;Setting it all up&lt;/h2&gt;
&lt;p&gt;For this part I&#39;m just going to share my NixOS module as it is.
It contains some of my specifics, but you&#39;ll get the gist of it and you&#39;ll be able pick and choose the parts that are of interest for you.&lt;/p&gt;
&lt;p&gt;I followed the &amp;quot;Nvidia (Manual)&amp;quot; method from &lt;a href=&quot;https://games-on-whales.github.io/wolf/stable/user/quickstart.html&quot;&gt;their guide&lt;/a&gt; since I have an NVidia GeForce 1070 GPU.&lt;/p&gt;
&lt;p&gt;Here is the module:&lt;/p&gt;
&lt;pre class=&quot;language-nix&quot;&gt;&lt;code class=&quot;language-nix&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  config&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  lib&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  pkgs&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;with&lt;/span&gt; lib&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;# This config itself&lt;/span&gt;
  cfg &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; config&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;my&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;services&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;wolf&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;# Name the container&lt;/span&gt;
  containerName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;wolf&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;# A reference of ports that are use by wolf&lt;/span&gt;
  ports &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    https &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;47984&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    http &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;47989&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    control &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;47999&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    rtsp &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;48010&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    video &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;48100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    audio &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;48200&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;# A shortcut&lt;/span&gt;
  backend &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; config&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;virtualisation&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;oci&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;containers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;backend&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;# Name typo-less reference&lt;/span&gt;
  nvidiaVolumeName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;nvidia-driver-vol&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;my&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;services&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;wolf &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;with&lt;/span&gt; lib&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;types&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    enable &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; mkEnableOption &lt;span class=&quot;token string&quot;&gt;&quot;wolf&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;# Where we&#39;ll persist our user data and config&lt;/span&gt;
    dataDir &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; mkOption &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      type &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      default &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/var/my/wolf&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;# On which address to listen on&lt;/span&gt;
    httpAddress &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; mkOption &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      type &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; str&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      default &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;127.0.0.1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    
    httpPort &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; mkOption &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      type &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; int&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      default &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; ports&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;http&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;# On which domain it will be accessible&lt;/span&gt;
    domain &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; mkOption &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      type &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; str&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      default &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;wolf.&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token antiquotation important&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;my&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;domain&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# Resolves to something like `wolf.example.com`&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  config &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; mkIf cfg&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;enable &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# Enable NVIDIA Container Toolkit if GPU support is requested&lt;/span&gt;
    hardware&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;nvidia&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;container&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;toolkit&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;enable &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;# Needed to get working virtual input devices&lt;/span&gt;
    boot&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;kernelModules &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
      &lt;span class=&quot;token string&quot;&gt;&quot;uinput&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;# The `virtualisation.oci-containers.containers.&quot;${containerName}&quot; ` option below creates this systemd service&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# We add a preStart script to it, to setup nvidia volume and do some cleanup&lt;/span&gt;
    systemd&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;services&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token antiquotation important&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;backend&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;-&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token antiquotation important&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;containerName&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      preStart &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;&#39;
        # Setup the nvidia volume
        &lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token antiquotation important&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;pkgs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;curl&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;/bin/curl https://raw.githubusercontent.com/games-on-whales/gow/master/images/nvidia-driver/Dockerfile | &lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token antiquotation important&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;backend&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt; build -t gow/nvidia-driver:latest -f - --build-arg NV_VERSION=$(cat /sys/module/nvidia/version) .
        &lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token antiquotation important&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;backend&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt; create --rm --mount source=&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token antiquotation important&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;nvidiaVolumeName&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;,destination=/usr/nvidia gow/nvidia-driver:latest sh

        # Stop/remove games-on-whales containers if they are running
        ids=$(&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token antiquotation important&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;backend&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt; ps --all --format &#39; &#39; | grep &#39;games-on-whales&#39; | &lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token antiquotation important&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;pkgs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;busybox&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;/bin/awk &#39;{print $1}&#39;)
        if [ -n &quot;$ids&quot; ]; then
          &lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token antiquotation important&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;backend&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt; stop $ids || true
          &lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token antiquotation important&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;backend&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt; rm $ids || true
        fi
      &#39;&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;# Define the main `wolf` container&lt;/span&gt;
    virtualisation&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;oci&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;containers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;containers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token antiquotation important&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;containerName&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      autoStart &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      image &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ghcr.io/games-on-whales/wolf:stable&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      volumes &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token antiquotation important&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;cfg&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dataDir&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;:/etc/wolf:rw&quot;&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&quot;/run/&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token antiquotation important&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;backend&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;/&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token antiquotation important&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;backend&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;.sock:/var/run/docker.sock:rw&quot;&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&quot;/dev/:/dev/:rw&quot;&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&quot;/run/udev:/run/udev:rw&quot;&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token antiquotation important&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;nvidiaVolumeName&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;:/usr/nvidia:rw&quot;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      devices &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&quot;/dev/dri&quot;&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&quot;/dev/uinput&quot;&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&quot;/dev/uhid&quot;&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&quot;/dev/nvidia-uvm&quot;&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&quot;/dev/nvidia-uvm-tools&quot;&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&quot;/dev/nvidia-caps/nvidia-cap1&quot;&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&quot;/dev/nvidia-caps/nvidia-cap2&quot;&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&quot;/dev/nvidiactl&quot;&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&quot;/dev/nvidia0&quot;&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&quot;/dev/nvidia-modeset&quot;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      ports &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token antiquotation important&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toString&lt;/span&gt; ports&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;https&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;:47984/tcp&quot;&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token antiquotation important&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toString&lt;/span&gt; cfg&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;httpPort&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;:47989/tcp&quot;&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token antiquotation important&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toString&lt;/span&gt; ports&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;control&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;:47999/udp&quot;&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token antiquotation important&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toString&lt;/span&gt; ports&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;rtsp&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;:48010/tcp&quot;&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token antiquotation important&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toString&lt;/span&gt; ports&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;video&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;:48100/udp&quot;&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token antiquotation important&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toString&lt;/span&gt; ports&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;audio&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;:48200/udp&quot;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      extraOptions &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&quot;--pull=newer&quot;&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&quot;--device-cgroup-rule&quot;&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&quot;c 13:* rmw&quot;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      environment &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        NVIDIA_DRIVER_VOLUME_NAME &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; nvidiaVolumeName&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;# Open up the ports on the VM&lt;/span&gt;
    networking&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;firewall &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      allowedTCPPorts &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
        ports&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;https
        cfg&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;httpPort
        ports&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;rtsp
      &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      allowedUDPPorts &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
        ports&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;control
        ports&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;video
        ports&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;audio
      &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    services&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;udev&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;extraRules &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;&#39;
      # Allows Wolf to acces /dev/uinput
      KERNEL==&quot;uinput&quot;, SUBSYSTEM==&quot;misc&quot;, MODE=&quot;0660&quot;, GROUP=&quot;input&quot;, OPTIONS+=&quot;static_node=uinput&quot;

      # Allows Wolf to access /dev/uhid
      KERNEL==&quot;uhid&quot;, TAG+=&quot;uaccess&quot;

      # Move virtual keyboard and mouse into a different seat
      SUBSYSTEMS==&quot;input&quot;, ATTRS{id/vendor}==&quot;ab00&quot;, MODE=&quot;0660&quot;, GROUP=&quot;input&quot;, ENV{ID_SEAT}=&quot;seat9&quot;

      # Joypads
      SUBSYSTEMS==&quot;input&quot;, ATTRS{name}==&quot;Wolf X-Box One (virtual) pad&quot;, MODE=&quot;0660&quot;, GROUP=&quot;input&quot;
      SUBSYSTEMS==&quot;input&quot;, ATTRS{name}==&quot;Wolf PS5 (virtual) pad&quot;, MODE=&quot;0660&quot;, GROUP=&quot;input&quot;
      SUBSYSTEMS==&quot;input&quot;, ATTRS{name}==&quot;Wolf gamepad (virtual) motion sensors&quot;, MODE=&quot;0660&quot;, GROUP=&quot;input&quot;
      SUBSYSTEMS==&quot;input&quot;, ATTRS{name}==&quot;Wolf Nintendo (virtual) pad&quot;, MODE=&quot;0660&quot;, GROUP=&quot;input&quot;
    &#39;&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    systemd&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;tmpfiles&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;rules &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;# Create he data dir if it does not exist&lt;/span&gt;
      &lt;span class=&quot;token string&quot;&gt;&quot;d &lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token antiquotation important&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;cfg&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dataDir&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt; 750 root root&quot;&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;# create a small file that indicates to borgmatic that this folder should not be backed up&lt;/span&gt;
      &lt;span class=&quot;token string&quot;&gt;&quot;f &lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token antiquotation important&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;cfg&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dataDir&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;/&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token antiquotation important&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;config&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;my&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;services&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;borgmatic&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;noBackupFilename&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt; 755 root root&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;# I use ephemeral root partition, so we indicate that this data is to be persisted across reboots&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# This maps to some Impermanence option&lt;/span&gt;
    my&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;persisted&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;directories &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
      cfg&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dataDir
    &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;# Make the web part accessible&lt;/span&gt;
    my&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;services &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      proxy&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;reverseProxies &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          from &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; cfg&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;domain&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
          to &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token antiquotation important&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;cfg&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;httpAddress&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;:&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token antiquotation important&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toString&lt;/span&gt; cfg&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;httpPort&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;# Only allow accessing this internally, from LAN&lt;/span&gt;
      authelia &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        accessRules &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            domain &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; cfg&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;domain&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            whenInternal &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            policy &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;two_factor&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I&#39;m using the &lt;code&gt;podman&lt;/code&gt; backend on my server, but it should work the same for &lt;code&gt;docker&lt;/code&gt;.
You can restart the container (and all of its associated containers) with &lt;code&gt;systemctl restart podman-wolf&lt;/code&gt; (or &lt;code&gt;systemctl restart docker-wolf&lt;/code&gt; if you&#39;re using the &lt;code&gt;docker&lt;/code&gt; backend).&lt;/p&gt;
&lt;h2&gt;A few tips and tricks&lt;/h2&gt;
&lt;h3&gt;Sharing user folders between multiple clients&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;wolf&lt;/code&gt; treats each paired client as a separate user by default, which means each client gets its own home folder in the containers.
If you have multiple devices, it is possible to share the home folder by editing the config file by hand a bit.&lt;/p&gt;
&lt;p&gt;Pair the second client to get the entries and then open up the config file at &lt;code&gt;/var/my/wolf/cfg/config.toml&lt;/code&gt;.
Find the &lt;code&gt;[[paired_clients]]&lt;/code&gt; entries and make sure that both of your clients have the same &lt;code&gt;app_state_folder&lt;/code&gt; ID by using the ID of your first client (that likely already has some data in that folder).&lt;/p&gt;
&lt;p&gt;I restarted the service for good measure.&lt;/p&gt;
&lt;h3&gt;Mounting additional folders into the containers&lt;/h3&gt;
&lt;p&gt;If you have some ROMs (or other files) on your server, that you want to make available to your RetroArch container (and others), you can do that by creating a volume mapping.&lt;/p&gt;
&lt;p&gt;Edit the &lt;code&gt;/var/my/wolf/cfg/config.toml&lt;/code&gt; file.
Find &lt;code&gt;[[apps]]&lt;/code&gt; with the &lt;code&gt;title&lt;/code&gt; of the application you want to add the mapping to (e.g. &lt;code&gt;title = &#39;RetroArch&#39;&lt;/code&gt;) and edit &lt;code&gt;mounts&lt;/code&gt; array to add your volume (it&#39;s in the &lt;code&gt;[apps.runner]&lt;/code&gt; section).&lt;/p&gt;
&lt;p&gt;Here is how I mounted my downloads folder from the server (&lt;code&gt;/mnt/pool/download/&lt;/code&gt;) to the container at &lt;code&gt;/download&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;language-toml&quot;&gt;&lt;code class=&quot;language-toml&quot;&gt;&lt;span class=&quot;token key property&quot;&gt;mounts&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;/mnt/pool/download/:/download:rw&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now I can easily download mods or other things on the server and have the downloads show up in the containers.&lt;/p&gt;
&lt;p&gt;Don&#39;t forget to restart!&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I&#39;ve been using this setup for a few weeks now and have found it to be reliable and performant.&lt;/p&gt;
&lt;p&gt;I&#39;m using it on my laptop and on my NVidia Shield which is connected to the TV.
I can pass through gamepads and keyboard/mouse.&lt;/p&gt;
&lt;p&gt;For me, performance- and usability-wise it&#39;s on par with Parsec, which has been my go-to service for streaming in the past, but without any external services or accounts.
It&#39;s all self-hosted.&lt;/p&gt;
&lt;p&gt;Since I&#39;m the only one in my household that games at this moment, I sadly can&#39;t comment on how well the multi-user aspect works.
I&#39;ll let you know when my kids start gaming.
Hopefully the GPU prices will have come down by then and we&#39;ll be able to pool our money and replace my aging GeForce 1070.&lt;/p&gt;
&lt;p&gt;I hope this post has been valuable and let me know if you run into any issues (email is on my homepage)!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>A kid-friendly DIY jukebox</title>
    <link href="https://luka.korosec.cc/posts/2025/10/a-kid-friendly-diy-jukebox/"/>
    <published>2025-10-14T00:00:00.000Z</published>
    <updated>2025-10-14T00:00:00.000Z</updated>
    <id>https://luka.korosec.cc/posts/2025/10/a-kid-friendly-diy-jukebox/</id>
    <content type="html">&lt;p&gt;I grew up in the music CD era, which meant I could take a look at our family CD collection, pick what I wanted to listen to, pop it into a CD player and voila, music!
The collection was not that big, it was music that my parents liked, but at least I had &lt;strong&gt;agency&lt;/strong&gt;.
I could listen to music without involving anyone else from a very young age.&lt;/p&gt;
&lt;p&gt;Today, most kids aren&#39;t that lucky.
All they can do is ask their parents to play something for them on their phone/TV.
Or what is even worse, they have their own phone/tablet with YouTube.
Additionally, they are tracked, have to be online and have to interact with screens.&lt;/p&gt;
&lt;p&gt;The solution?&lt;/p&gt;
&lt;p&gt;Build an offline screen-free jukebox together with your kids.
It&#39;s fun, you&#39;ll learn something and the kids will love using it.&lt;/p&gt;
&lt;h2&gt;Who is this guide for&lt;/h2&gt;
&lt;p&gt;I think this DIY project is quite approachable, that said, what is easy for me might be hard for you and vice versa.
Also, I have quite a few years of Linux experience, I&#39;ve completed projects with Raspberry Pis before and I have soldered a few PCBs in my time.
So I generally knew what I needed to do going in.&lt;/p&gt;
&lt;p&gt;You will need to know how to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;find the IP of a machine on your network&lt;/li&gt;
&lt;li&gt;SSH into that machine&lt;/li&gt;
&lt;li&gt;run (copy/pasted) commands on that machine&lt;/li&gt;
&lt;li&gt;do some light soldering&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As always, if you need help with anything feel free to &lt;a href=&quot;mailto:luka@korosec.cc?subject=Building my own PhonieBox and need some help&quot;&gt;email me&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;What kind of jukebox&lt;/h2&gt;
&lt;p&gt;A &lt;a href=&quot;https://phoniebox.de/index-en.html&quot;&gt;PhonieBox&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;It&#39;s a free, open-source, DIY jukebox aimed at kids.
Everything you need to build it can be found at their &lt;a href=&quot;https://github.com/MiczFlor/RPi-Jukebox-RFID&quot;&gt;GitHub repository&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Basically you use a Raspberry Pi, attach a speaker and a card reader to it.
Then you install PhonieBox software on it, copy over the music files and link them with RFID cards.
The kids can then tap the cards to the card reader and music starts playing.
Simple as that!&lt;/p&gt;
&lt;p&gt;Let&#39;s build it.&lt;/p&gt;
&lt;h2&gt;What you&#39;ll need&lt;/h2&gt;
&lt;p&gt;Now I&#39;ve had a few of these things lying at home already.
I just had to buy the card reader, RFID cards and some jumper cables totaling 18,47€.&lt;/p&gt;
&lt;img src=&quot;/assets/images/007-jukebox-parts.jpg&quot; alt=&quot;Raspberry Pi 1, JBL speaker, card reader, SD card, USB and AUX cables, jumper cables, SD card adapter, MicroSD card&quot;&gt;
&lt;p&gt;I&#39;ve bought them from az-delivery.de:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a &lt;a href=&quot;https://www.az-delivery.de/en/products/rfid-set&quot;&gt;card reader&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;to read the cards&lt;/li&gt;
&lt;li&gt;6,49€&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.az-delivery.de/en/products/3er-set-40-stk-jumper-wire-m2m-f2m-f2f&quot;&gt;jumper cables&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;to connect the reader to the Pi&lt;/li&gt;
&lt;li&gt;6,99€&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;some &lt;a href=&quot;https://www.az-delivery.de/en/products/rfid-karten-13-56-mhz-weiss&quot;&gt;RFID cards&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;to associate them with songs/folders/playlists/actions&lt;/li&gt;
&lt;li&gt;4,99€&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What you probably already have:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Raspberry Pi 1
&lt;ul&gt;
&lt;li&gt;the brains of the operation&lt;/li&gt;
&lt;li&gt;any Pi works, PhonieBox folks suggest either a 2 or a 3&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;an SD card (or MicroSD + SD Adapter)
&lt;ul&gt;
&lt;li&gt;it&#39;s where we&#39;ll install the OS and put the files&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;An old JBL speaker box
&lt;ul&gt;
&lt;li&gt;on mine, the battery is dead, but that is okay, since it will be powered by the Pi via MicroUSB&lt;/li&gt;
&lt;li&gt;has an AUX input&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;AUX-to-AUX cable
&lt;ul&gt;
&lt;li&gt;to connect the Pi and the JBL&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;2 x USB-to-MicroUSB cable
&lt;ul&gt;
&lt;li&gt;one for powering the Pi and another for powering the JBL&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;USB Power Adapter
&lt;ul&gt;
&lt;li&gt;provides power to the Pi&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;a nice box for the whole thing&lt;/li&gt;
&lt;li&gt;card sleeves and some paper for marking the cards&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As for the tools:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a soldering iron&lt;/li&gt;
&lt;li&gt;some tape&lt;/li&gt;
&lt;li&gt;a marker pen&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;The hardware assembly&lt;/h2&gt;
&lt;p&gt;Solder the pin headers to the card reader and attach the jumper cables.
Note the colors of the jumper cables, as we&#39;ll connect it to the Pi next.&lt;/p&gt;
&lt;img class=&quot;image-medium&quot; src=&quot;/assets/images/007-assembled-card-reader.jpg&quot; alt=&quot;Card reader with soldered pin headers and attached jumper cables&quot;&gt;
&lt;p&gt;It&#39;s a bit fiddly to connect the cables to the Pi.
Here is how the end result looks like.&lt;/p&gt;
&lt;div class=&quot;image-grid&quot;&gt;
    &lt;img src=&quot;/assets/images/007-card-reader-pi-left.jpg&quot; alt=&quot;Jumper cables from the card reader attaching to the Pi from the left&quot;&gt;
    &lt;img src=&quot;/assets/images/007-card-reader-pi-right.jpg&quot; alt=&quot;Jumper cables from the card reader attaching to the Pi from the right&quot;&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; If you have different version of the Pi, please adjust the jumper cables connections on the Pi side according to your &lt;a href=&quot;https://pinout.xyz/&quot;&gt;pinout diagram&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;That was the hard part.&lt;/p&gt;
&lt;p&gt;Next, just connect the JBL speaker with AUX and USB cable to the Pi, to get sound and power.&lt;/p&gt;
&lt;p&gt;Here is how it looks like assembled, lots of wires:&lt;/p&gt;
&lt;img class=&quot;image-medium&quot; src=&quot;/assets/images/007-assembled-jukebox.jpg&quot; alt=&quot;Raspberry Pi is connected with the JBL speakers, card reader, SD card and is ready to be plugged into the wall&quot;&gt;
&lt;h2&gt;Installing the OS + PhonieBox software&lt;/h2&gt;
&lt;p&gt;Here, I would direct you to the &lt;a href=&quot;https://github.com/MiczFlor/RPi-Jukebox-RFID/blob/future3/main/documentation/builders/installation.md&quot;&gt;official PhonieBox installation docs&lt;/a&gt;.
It guides you through formatting the SD card, setting up SSH access and so on.
There are some specifics, so read the fine print as well.&lt;/p&gt;
&lt;p&gt;Your installation choices will vary based on your Pi version and network setup.
I have not set up any WiFi or WiFi AP, since the Pi 1 does not have any WiFi capabilities built-in.
Instead, I&#39;m using a good old Ethernet cable to connect to the Pi when I need to.&lt;/p&gt;
&lt;p&gt;On the Raspberry Pi 1 the installation took a long time, I think around an hour or so.
If you&#39;ve botched something during installation, just delete the &lt;code&gt;~/RPi-Jukebox-RFID&lt;/code&gt; folder and try again.
Worked for me.&lt;/p&gt;
&lt;h2&gt;Uploading your music&lt;/h2&gt;
&lt;p&gt;When I want to add some music to the jukebox, I take it out of its box and connect it with an Ethernet cable.
Then I can copy the files over with &lt;code&gt;rsync&lt;/code&gt; and manage it via its WebUI accessible on its LAN IP.&lt;/p&gt;
&lt;p&gt;Here is the command to copy over everything in &lt;code&gt;episodes&lt;/code&gt; folder to the &lt;code&gt;zverinice&lt;/code&gt; folder on the Pi:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# replace &amp;lt;username&gt; and &amp;lt;IP&gt; with your username and IP of the jukebox&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;rsync&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-avz&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;--progress&lt;/span&gt; episodes/ &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;username&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;@&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;IP&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;:~/RPi-Jukebox-RFID/shared/audiofolders/zverinice/&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I had a bit of trouble finding where to upload the files to.
You can create music folders at &lt;code&gt;~/RPi-Jukebox-RFID/shared/audiofolders/&lt;/code&gt; and playlists go to &lt;code&gt;~/RPi-Jukebox-RFID/shared/playlists/&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;It&#39;s always a fun family activity when we add new files to the jukebox.
My wife is responsible for putting the cards into sleeves together with the name and a drawing of the music/episode and sealing them, then my kid is responsible for booping the cards on the card reader, so that I can pair it to the correct music/episode on the PhonieBox WebUI.&lt;/p&gt;
&lt;p&gt;When using the WebUI on an under-powered device like the Raspberry Pi 1, give it some time and be patient.
This can be especially visible when listing the files in the &lt;code&gt;audiofolders&lt;/code&gt; folder.
The first load takes time, but after that, subsequent requests are much faster.&lt;/p&gt;
&lt;p&gt;The playback on the Pi 1 is instant, so overall I don&#39;t regret using the old Pi.&lt;/p&gt;
&lt;h2&gt;Putting it into a nice box&lt;/h2&gt;
&lt;p&gt;When we were building it this year in August, I didn&#39;t know if this is something that my kid is going to use or not, so we just took a simple box and put everything in there.
I&#39;ve then asked my kid to paint it as he wants, all I did was add the indication of where to press the cards.&lt;/p&gt;
&lt;p&gt;Here is the final result:&lt;/p&gt;
&lt;div class=&quot;image-grid&quot;&gt;
    &lt;img src=&quot;/assets/images/007-painting-the-box.jpg&quot; alt=&quot;A kid is using watercolors to paint the box&quot;&gt;
    &lt;img src=&quot;/assets/images/007-cards-sample.jpg&quot; alt=&quot;A few RFID cards we created for our jukebox&quot;&gt;
    &lt;img src=&quot;/assets/images/007-final-open-box.jpg&quot; alt=&quot;An open box with the finished product stuffed in it&quot;&gt;
    &lt;img src=&quot;/assets/images/007-final-closed-box.jpg&quot; alt=&quot;A closed box, with the JBL speaker attached&quot;&gt;
&lt;/div&gt;
&lt;h2&gt;Next steps&lt;/h2&gt;
&lt;p&gt;You&#39;ll notice that our version of the PhonieBox has no buttons.
Not all PhonieBoxes are built the same.
Check out the Calendar section on the &lt;a href=&quot;https://phoniebox.de/index-en.html&quot;&gt;project page&lt;/a&gt;.
There is a huge variety of them.&lt;/p&gt;
&lt;p&gt;I first wanted to build a super simple version, to see if it works, how well it works, how much usage it will get and so on.&lt;/p&gt;
&lt;p&gt;That said, our version is not so bad either.
The JBL speaker has built-in buttons for volume control; though I was sad that the play/pause button is not getting picked up on the Pi.
Still, nothing an extra RFID card can&#39;t fix.
We also have cards for Previous and Next song, Shuffle, Stop and so on, but they are rarely used.&lt;/p&gt;
&lt;p&gt;I also bought a rotary encoder for volume control and I have some mechanical keyboard switches lying around that I can use for buttons, so that will probably happen once I decide to create a better box for it.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;There are lots of nicer jukeboxes out there, but this one is completely ours!
We built it and we&#39;re proud of it.&lt;/p&gt;
&lt;p&gt;It works completely offline, does not track us, has no ads and we have 100% control over what we can or can&#39;t listen to on it.
Building it was so much fun and took us less than one afternoon.
My kid was very engaged in the process and helped me throughout.&lt;/p&gt;
&lt;p&gt;Thanks to the great &lt;a href=&quot;https://phoniebox.de/index-en.html&quot;&gt;PhonieBox&lt;/a&gt; software that makes this possible.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Declarative Incus on NixOS with SSO</title>
    <link href="https://luka.korosec.cc/posts/2025/09/declarative-incus-on-nixos-with-sso/"/>
    <published>2025-09-29T00:00:00.000Z</published>
    <updated>2025-09-29T00:00:00.000Z</updated>
    <id>https://luka.korosec.cc/posts/2025/09/declarative-incus-on-nixos-with-sso/</id>
    <content type="html">&lt;p&gt;I&#39;ve started my HomeLabbing journey with &lt;a href=&quot;https://www.proxmox.com/&quot;&gt;Proxmox&lt;/a&gt; quite a few years ago, thinking I would have many VMs and LXC containers doing so many things.
In the end I&#39;ve ditched Proxmox for NixOS, I&#39;m using one server and many baremetal systemd services. I run 75 of those and only 3 containers.
I&#39;ve yet to use LXC containers.&lt;/p&gt;
&lt;p&gt;But lately I&#39;ve been itching for some gaming and I&#39;ve got nothing to game on!
I&#39;ve previously used QEMU and Virsh to setup a VM with GPU passthrough on my server and it was working nicely.
I was able to connect a monitor the GPU and sit down to game.&lt;/p&gt;
&lt;p&gt;But since then another kid came into the picture and I lost that space and had to move to another room.
I haven&#39;t really had time to game so that was fine.
And I started to use my GPU for my Home Assistant&#39;s Speech-To-Text functionality via &lt;code&gt;wyoming-piper&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;So now I can&#39;t dedicate the full GPU to my VM anymore, and neither can I just connect the monitor to the server, since it&#39;s now in another room.&lt;/p&gt;
&lt;p&gt;I did a bit of searching and found out that if you have an LXC container, it can share the GPU with other processes or even other LXC containers!&lt;/p&gt;
&lt;p&gt;Then I found &lt;a href=&quot;https://www.reddit.com/r/Proxmox/comments/oj6ai5/guide_lxc_gpu_accelerated_gaming_desktop_without/&quot;&gt;this guide&lt;/a&gt; and decided to implement it.&lt;/p&gt;
&lt;p&gt;But since I have no Proxmox, I had to look for alternatives, and that is how I found out about &lt;a href=&quot;https://linuxcontainers.org/incus/&quot;&gt;Incus&lt;/a&gt;.
So that is what I&#39;ll be showing you how to set it up on your NixOS machine with SSO login via Authelia.&lt;/p&gt;
&lt;p&gt;Phew, what an intro. &lt;strong&gt;Let&#39;s get started!&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;Prerequisites&lt;/h2&gt;
&lt;p&gt;This guide assumes you already have Caddy and Authelia configured.&lt;/p&gt;
&lt;h2&gt;My Incus NixOS module&lt;/h2&gt;
&lt;p&gt;This post will be a bit different in the sense that I will just provide you with the NixOS module that I use.
I will try to comment it extensively, so that you know what values to put where for your setup.&lt;/p&gt;
&lt;p&gt;What you don&#39;t see in this module is how I set up Caddy and Authelia, both of which this module uses.
If you run into issues, I&#39;ll be happy to help you out if you reach out (my email is on the homepage).&lt;/p&gt;
&lt;p&gt;You might need the following pages to help you if you get stuck, they sure helped me tremendously:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://wiki.nixos.org/wiki/Incus&quot;&gt;Incus Wiki&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://linuxcontainers.org/incus/docs/main/server_config/&quot;&gt;Incus configuration reference&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://linuxcontainers.org/incus/docs/main/reference/manpages/incus/&quot;&gt;Incus CLI manpage&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://discuss.linuxcontainers.org/t/configure-incus-ui-with-sso/19926&quot;&gt;Another guide to set up Incus with SSO&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.authelia.com/integration/openid-connect/clients/incus/&quot;&gt;Authelia guide to setup Incus OIDC client&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Keep in mind that whoever can log into Incus can do everything.&lt;/strong&gt;
They do not yet have a concept of users or permissions.
A bit of a shame to be really honest.&lt;/p&gt;
&lt;p&gt;Anyway, here is the whole thing:&lt;/p&gt;
&lt;pre class=&quot;language-nix&quot;&gt;&lt;code class=&quot;language-nix&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  config&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  lib&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# This uses a modified standard lib that includes a `my` field for my-setup-specific values.&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;with&lt;/span&gt; lib&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;# For this config, for easier reference&lt;/span&gt;
  cfg &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; config&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;my&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;services&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;incus&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;# Module option `dataDir` is the root dir, so we expand it a bit to get the storage pool dir&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;# Down the line I might need more than one&lt;/span&gt;
  defaultStoragePoolDir &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token antiquotation important&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;cfg&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dataDir&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;/storage-pools/default&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;# The OIDC Client ID I will use. You should make this a bit longer, and a bit more random than this example&lt;/span&gt;
  oidcClientId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;incus&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;# Here we define the available options consumers of this module can set&lt;/span&gt;
  options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;my&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;services&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;incus &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;with&lt;/span&gt; lib&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;types&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    enable &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; mkEnableOption &lt;span class=&quot;token string&quot;&gt;&quot;incus&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;# I place all my data in `/var/my`, so incus gets a place there too&lt;/span&gt;
    dataDir &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; mkOption &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      type &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      default &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/var/my/incus&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;# The LISTEN address for the UI&lt;/span&gt;
    uiAddress &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; mkOption &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      type &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; str&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      default &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;127.0.0.1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;# And its corresponding port&lt;/span&gt;
    uiPort &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; mkOption &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      type &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; int&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      default &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;17171&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;# The domain name to use&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# Don&#39;t forget to create this domain name on your router or wherever you create those&lt;/span&gt;
    domain &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; mkOption &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      type &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; str&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      default &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;incus.&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token antiquotation important&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;my&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;domain&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# expands to something like `incus.example.com`&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;# The network Incus will use for containers&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# We&#39;ll create a bridge, so a `br` is in the name&lt;/span&gt;
    networkName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; mkOption &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      type &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; str&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      default &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;incusbr0&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;# Apply config only if the module is enabled&lt;/span&gt;
  config &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; mkIf cfg&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;enable &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    virtualisation&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;incus &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;# Enable Incus&lt;/span&gt;
      enable &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;# Enable the UI&lt;/span&gt;
      ui&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;enable &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;# Specify the initial settings&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;# Mind that if you change these imperatively via the `incus` command, they will not be overwritten by this declarative configuration.&lt;/span&gt;
      preseed &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt;
          poolName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;default&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          config &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;# Where the UI will listen on&lt;/span&gt;
            &lt;span class=&quot;token string&quot;&gt;&quot;core.https_address&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token antiquotation important&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;cfg&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;uiAddress&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;:&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token antiquotation important&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toString&lt;/span&gt; cfg&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;uiPort&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;# OIDC config&lt;/span&gt;
            &lt;span class=&quot;token string&quot;&gt;&quot;oidc.issuer&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token antiquotation important&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;config&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;my&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;services&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;authelia&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;domain&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# This resolves to something like `https://authelia.example.com`&lt;/span&gt;
            &lt;span class=&quot;token string&quot;&gt;&quot;oidc.client.id&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; oidcClientId&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token string&quot;&gt;&quot;oidc.audience&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token antiquotation important&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;cfg&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;domain&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;# I went the SSO route, since I use that everywhere else as well&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;# This disables certificate login&lt;/span&gt;
            &lt;span class=&quot;token string&quot;&gt;&quot;user.ui.sso_only&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;true&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
          networks &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
              &lt;span class=&quot;token comment&quot;&gt;# VMs and containers will have IPs in the 10.10.10.X network&lt;/span&gt;
              config &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token string&quot;&gt;&quot;ipv4.address&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;10.10.10.1/24&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token string&quot;&gt;&quot;ipv4.nat&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;true&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
              &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
              &lt;span class=&quot;token comment&quot;&gt;# Name the network&lt;/span&gt;
              name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; cfg&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;networkName&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
              &lt;span class=&quot;token comment&quot;&gt;# Specify its type&lt;/span&gt;
              type &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;bridge&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
          &lt;span class=&quot;token comment&quot;&gt;# The profiles are the default settings for containers&lt;/span&gt;
          profiles &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
              &lt;span class=&quot;token comment&quot;&gt;# Name this profile&lt;/span&gt;
              name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;default&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
              devices &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token comment&quot;&gt;# The network that will get attached to our container, that will use our bridge&lt;/span&gt;
                eth0 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                  name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;eth0&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                  network &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; cfg&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;networkName&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                  type &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;nic&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token comment&quot;&gt;# Create a disk mounted at root, using the &#39;default&#39; pool (created below)&lt;/span&gt;
                root &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                  path &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                  pool &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; poolName&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                  type &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;disk&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
              &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
          &lt;span class=&quot;token comment&quot;&gt;# Define the data pool, with the &#39;dir&#39; driver, meaning it&#39;s just a directory at &#39;config.source&#39;&lt;/span&gt;
          storage_pools &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
              config&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;source &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; defaultStoragePoolDir&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
              driver &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;dir&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
              name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; poolName&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;# Add my user to these groups so that it can use and configure Incus&lt;/span&gt;
    my&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;user &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      extraGroups &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&quot;incus&quot;&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&quot;incus-admin&quot;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;# As per the Wiki, Incus only works with nftables, so we enable that&lt;/span&gt;
    networking&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;nftables&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;enable &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# And set the bridge to be trusted to allow traffic on this interface&lt;/span&gt;
    networking&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;firewall&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;trustedInterfaces &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; cfg&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;networkName &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;# Create the folder if it does not exist&lt;/span&gt;
    systemd&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;tmpfiles&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;rules &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
      &lt;span class=&quot;token string&quot;&gt;&quot;d &lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token antiquotation important&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;defaultStoragePoolDir&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt; 750 root root&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;# This one uses the Impermanence module, skip it if you don&#39;t use it&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# Set the data and config directories to persist across reboots&lt;/span&gt;
    my&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;persisted&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;directories &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
      cfg&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dataDir
      &lt;span class=&quot;token string&quot;&gt;&quot;/var/lib/incus&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;# Make it accessible&lt;/span&gt;
    my&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;services &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      proxy&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;reverseProxies &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          from &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; cfg&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;domain&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
          extraConfig &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;&#39;
            reverse_proxy https://&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token antiquotation important&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;cfg&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;uiAddress&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;:&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token antiquotation important&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toString&lt;/span&gt; cfg&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;uiPort&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt; {
              transport http {
                tls_insecure_skip_verify
              }
            }
          &#39;&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;# Above translates into this Caddy virtual host config&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;# services.caddy.virtualHosts.&quot;${cfg.domain}&quot; ={&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;#  extraConfig = &#39;&#39;&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;#    forward_auth ${config.my.services.authelia.domain} {&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;#      uri /api/authz/forward-auth&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;#      copy_headers Remote-User Remote-Groups Remote-Name Remote-Email&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;#    }&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;#&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;#    reverse_proxy https://${cfg.uiAddress}:${toString cfg.uiPort} {&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;#      transport http {&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;#        tls_insecure_skip_verify&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;#      }&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;#    }&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;#  &#39;&#39;;&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;#  hostName = cfg.domain;&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;#  useACMEHost = my.domain;&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;#}&lt;/span&gt;

      &lt;span class=&quot;token comment&quot;&gt;# This sets up Incus in Authelia&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;# Again this uses my module&lt;/span&gt;
      authelia &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;# This one is merged into `config.services.authelia.instances.default.settings.access_control.rules`&lt;/span&gt;
        accessRules &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            domain &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; cfg&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;domain&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            subject &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
              &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;user:luka&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            whenInternal &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            policy &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;two_factor&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
          &lt;span class=&quot;token comment&quot;&gt;# becomes&lt;/span&gt;
          &lt;span class=&quot;token comment&quot;&gt;#{&lt;/span&gt;
          &lt;span class=&quot;token comment&quot;&gt;#  domain = cfg.domain;&lt;/span&gt;
          &lt;span class=&quot;token comment&quot;&gt;#  networks = &quot;internal&quot;; # I only want Incus to be accessible on my internal networks (my LAN, LAB, VPN, etc)&lt;/span&gt;
          &lt;span class=&quot;token comment&quot;&gt;#  policy = &quot;two_factor&quot;;&lt;/span&gt;
          &lt;span class=&quot;token comment&quot;&gt;#  subject = [ [ &quot;user:luka&quot; ] ];&lt;/span&gt;
          &lt;span class=&quot;token comment&quot;&gt;#}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;# This one is simply mapped&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;# config.services.authelia.instances.default.settings.identity_providers.oidc.clients = cfg.oidcClients;&lt;/span&gt;
        oidcClients &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            client_id &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; oidcClientId&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            client_name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Incus&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            public &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            authorization_policy &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;two_factor&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            require_pkce &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            pkce_challenge_method &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            redirect_uris &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
              &lt;span class=&quot;token string&quot;&gt;&quot;https://&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token antiquotation important&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;cfg&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;domain&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;/oidc/callback&quot;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            audience &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
              &lt;span class=&quot;token string&quot;&gt;&quot;https://&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token antiquotation important&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;cfg&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;domain&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&quot;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            scopes &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
              &lt;span class=&quot;token string&quot;&gt;&quot;openid&quot;&lt;/span&gt;
              &lt;span class=&quot;token string&quot;&gt;&quot;offline_access&quot;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            response_types &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
              &lt;span class=&quot;token string&quot;&gt;&quot;code&quot;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            grant_types &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
              &lt;span class=&quot;token string&quot;&gt;&quot;authorization_code&quot;&lt;/span&gt;
              &lt;span class=&quot;token string&quot;&gt;&quot;refresh_token&quot;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            access_token_signed_response_alg &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;RS256&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            userinfo_signed_response_alg &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;none&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            token_endpoint_auth_method &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;none&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And there we go!
Don&#39;t forget to &lt;code&gt;git add modules/incus.nix&lt;/code&gt; and &lt;code&gt;my.services.incus.enable = true;&lt;/code&gt; in your hosts configuration and save.
Just one &lt;code&gt;nixos-rebuild&lt;/code&gt; later and you&#39;re good to go!&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;So I have Incus running now and I was able to experiment with it already.
But I still have not yet set up a headless gaming LXC container.
Perhaps in another blog post.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Setting up your own VPN server</title>
    <link href="https://luka.korosec.cc/posts/2025/09/setting-up-your-own-vpn-server/"/>
    <published>2025-09-28T00:00:00.000Z</published>
    <updated>2025-09-28T00:00:00.000Z</updated>
    <id>https://luka.korosec.cc/posts/2025/09/setting-up-your-own-vpn-server/</id>
    <content type="html">&lt;p&gt;At work, we&#39;ve had to come up with a way to access devices hiding behind firewalls at a few different locations.
This is a great fit for a VPN.&lt;/p&gt;
&lt;p&gt;Let&#39;s explore how to set up a manageable private VPN in the public cloud.
We&#39;ll leverage podman and systemd to manage a container.&lt;/p&gt;
&lt;h2&gt;The plan&lt;/h2&gt;
&lt;p&gt;We&#39;ll use &lt;a href=&quot;https://www.wireguard.com/&quot;&gt;WireGuard&lt;/a&gt; as our VPN solution, it&#39;s fast, simple, modern and secure, and manage it with &lt;a href=&quot;https://github.com/wg-easy/wg-easy&quot;&gt;wg-easy&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://caddyserver.com/&quot;&gt;Caddy&lt;/a&gt; will provide TLS certificates to secure our frontend.&lt;/p&gt;
&lt;p&gt;You&#39;ll also need a domain.&lt;/p&gt;
&lt;h2&gt;Setting up the VM&lt;/h2&gt;
&lt;p&gt;You can use any public cloud provider you want.
You don&#39;t need a powerful VM for a VPN, so a cheap one will do just fine.&lt;/p&gt;
&lt;p&gt;This guide focuses on Debian-based systems (specifically Ubuntu), as that&#39;s what we used.
So if you want to follow along select that.&lt;/p&gt;
&lt;p&gt;Once the VM is running, note its IP and update your DNS records to point to the machine.
Add the following DNS entries to wherever you have those:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;an A record e.g. &lt;code&gt;vpn.example.com&lt;/code&gt; with your VMs IP&lt;/li&gt;
&lt;li&gt;a CNAME record for wg-easy frontend, e.g. &lt;code&gt;wg-easy.example.com&lt;/code&gt; or &lt;code&gt;wg-easy.vpn.example&lt;/code&gt;, in either case it should point to &lt;code&gt;vpn.example.com&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;you could also skip this second domain if all you&#39;ll host on this VM is the &lt;code&gt;wg-easy&lt;/code&gt; frontend, if you do, remember to use &lt;code&gt;vpn.example.com&lt;/code&gt; in your Caddyfile later.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Some cloud providers set up the SSH access keys for you, but if they don&#39;t, open a web console, edit &lt;code&gt;~/.ssh/authorized_keys&lt;/code&gt; and paste in your SSH key.
Now you can use regular SSH to connect to the machine.&lt;/p&gt;
&lt;p&gt;First make sure SSH server does not allow password login, by inspecting the config at &lt;code&gt;/etc/ssh/ssh_config&lt;/code&gt; and setting &lt;code&gt;PasswordAuthentication no&lt;/code&gt; if its not set already.&lt;/p&gt;
&lt;p&gt;Then you should update it:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;&lt;span class=&quot;token function&quot;&gt;apt&lt;/span&gt; update
&lt;span class=&quot;token function&quot;&gt;apt&lt;/span&gt; upgrade&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And install the dependencies we&#39;ll need:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;&lt;span class=&quot;token function&quot;&gt;apt&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-y&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;podman&lt;/span&gt; caddy&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Firewall setup&lt;/h3&gt;
&lt;p&gt;You&#39;ll need to allow certain ports:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Protocol&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Port&lt;/th&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SSH&lt;/td&gt;
&lt;td&gt;TCP&lt;/td&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;td&gt;All IPv4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTTP&lt;/td&gt;
&lt;td&gt;TCP&lt;/td&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;td&gt;All IPv4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTTPS&lt;/td&gt;
&lt;td&gt;TCP&lt;/td&gt;
&lt;td&gt;443&lt;/td&gt;
&lt;td&gt;All IPv4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WireGuard&lt;/td&gt;
&lt;td&gt;UDP&lt;/td&gt;
&lt;td&gt;51820&lt;/td&gt;
&lt;td&gt;All IPv4&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;Installing wg-easy&lt;/h3&gt;
&lt;p&gt;Usually I would advocate for installing it as a pure systemd unit with a binary run as a dynamic/service user, but &lt;code&gt;wg-easy&lt;/code&gt; does not have that option.
They only support deploying via a container.&lt;/p&gt;
&lt;p&gt;So we&#39;ll follow their &lt;a href=&quot;https://wg-easy.github.io/wg-easy/latest/examples/tutorials/podman-nft/&quot;&gt;guide&lt;/a&gt; here and modify it a bit for our needs.&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Create folders for our files&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;mkdir&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-p&lt;/span&gt; /etc/containers/systemd/wg-easy
&lt;span class=&quot;token function&quot;&gt;mkdir&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-p&lt;/span&gt; /etc/containers/volumes/wg-easy&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We modified the container file a bit to drop the IPv6 stuff, since some of our ISPs don&#39;t have that yet (&lt;em&gt;I know!&lt;/em&gt;).
So we also don&#39;t need the network.&lt;/p&gt;
&lt;p&gt;As per the guide above, we&#39;ll define our container using a &lt;code&gt;.container&lt;/code&gt; file.
This is a feature of Podman&#39;s quadlet generator, which allows &lt;code&gt;systemd&lt;/code&gt; to manage containers directly from a simple definition file.
Its structure is the same as it is for regular &lt;code&gt;systemd&lt;/code&gt; unit files.&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# /etc/containers/systemd/wg-easy/wg-easy.container&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;Container&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token assign-left variable&quot;&gt;ContainerName&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;wg-easy
&lt;span class=&quot;token assign-left variable&quot;&gt;Image&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;ghcr.io/wg-easy/wg-easy:15
&lt;span class=&quot;token assign-left variable&quot;&gt;AutoUpdate&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;registry

&lt;span class=&quot;token assign-left variable&quot;&gt;Volume&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;/etc/containers/volumes/wg-easy:/etc/wireguard:Z
&lt;span class=&quot;token assign-left variable&quot;&gt;PublishPort&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;51820&lt;/span&gt;:51820/udp
&lt;span class=&quot;token assign-left variable&quot;&gt;PublishPort&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;51821&lt;/span&gt;:51821/tcp

&lt;span class=&quot;token comment&quot;&gt;# Disable IPv6, as some ISPs don&#39;t have full support yet.&lt;/span&gt;
&lt;span class=&quot;token assign-left variable&quot;&gt;Environment&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;DISABLE_IPV6&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;true

&lt;span class=&quot;token assign-left variable&quot;&gt;AddCapability&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;NET_ADMIN
&lt;span class=&quot;token assign-left variable&quot;&gt;AddCapability&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;SYS_MODULE
&lt;span class=&quot;token assign-left variable&quot;&gt;AddCapability&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;NET_RAW
&lt;span class=&quot;token assign-left variable&quot;&gt;Sysctl&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;net.ipv4.ip_forward&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
&lt;span class=&quot;token assign-left variable&quot;&gt;Sysctl&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;net.ipv4.conf.all.src_valid_mark&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;Install&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token assign-left variable&quot;&gt;WantedBy&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;default.target&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We indicate that we want additional modules loaded with the following file:&lt;/p&gt;
&lt;pre class=&quot;language-conf&quot;&gt;&lt;code class=&quot;language-conf&quot;&gt;# /etc/modules-load.d/wg-easy.conf
wireguard
nft_masq&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can load these modules with either a reboot of the VM or by running &lt;code&gt;systemctl restart systemd-modules-load.service&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Then we can start the service:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;systemctl daemon-reload
systemctl start wg-easy&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Verify that the service is running with &lt;code&gt;systemctl status wg-easy&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Setup Caddy&lt;/h3&gt;
&lt;p&gt;For setting up Caddy all we need is a Caddyfile.&lt;/p&gt;
&lt;pre class=&quot;language-Caddyfile&quot;&gt;&lt;code class=&quot;language-Caddyfile&quot;&gt;# /etc/caddy/Caddyfile
wg-easy.vpn.example.com {
	reverse_proxy localhost:51821
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then reload the service and check that all is good:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;systemctl reload caddy
systemctl status caddy&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can also inspect the logs and check that it got the TLS certificates with &lt;code&gt;journalctl -eu caddy&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Verify we can access &lt;code&gt;wg-easy&lt;/code&gt; by going to &lt;code&gt;https://wg-easy.vpn.example.com&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I &lt;strong&gt;strongly&lt;/strong&gt; urge you to set up 2FA on the &lt;code&gt;wg-easy&lt;/code&gt; frontend, especially if you&#39;ll be exposing it on the WWW.&lt;/p&gt;
&lt;h3&gt;Configure WireGuard via &lt;code&gt;wg-easy&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;The configuration will depend on your specific needs.
This setup is highly flexible, allowing you to connect single devices or entire networks.&lt;/p&gt;
&lt;p&gt;But there is one last step that you should do according to the linked guide and that is to update the hooks.
You can find these in the Administrator &amp;gt; Admin panel &amp;gt; Hooks.&lt;/p&gt;
&lt;p&gt;PostUp hook:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;nft &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; table inet wg_table&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
nft &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; chain inet wg_table prerouting &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;type&lt;/span&gt; nat hook prerouting priority &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
nft &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; chain inet wg_table postrouting &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;type&lt;/span&gt; nat hook postrouting priority &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
nft &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; rule inet wg_table postrouting &lt;span class=&quot;token function&quot;&gt;ip&lt;/span&gt; saddr  oifname  masquerade&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
nft &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; chain inet wg_table input &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;type&lt;/span&gt; filter hook input priority &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; policy accept &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
nft &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; rule inet wg_table input udp dport  accept&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
nft &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; rule inet wg_table input tcp dport  accept&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
nft &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; chain inet wg_table forward &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;type&lt;/span&gt; filter hook forward priority &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; policy accept &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
nft &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; rule inet wg_table forward iifname &lt;span class=&quot;token string&quot;&gt;&quot;wg0&quot;&lt;/span&gt; accept&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
nft &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; rule inet wg_table forward oifname &lt;span class=&quot;token string&quot;&gt;&quot;wg0&quot;&lt;/span&gt; accept&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This configuration enables the WireGuard server to:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Accept VPN connections&lt;/li&gt;
&lt;li&gt;Route client traffic to the internet (NAT/masquerade)&lt;/li&gt;
&lt;li&gt;Allow forwarding between the VPN and other networks&lt;/li&gt;
&lt;li&gt;Accept management interface connections&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;PostDown hook:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;nft delete table inet wg_table&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, go to Admin panel &amp;gt; General and make sure that the Host and the Port are correct.&lt;/p&gt;
&lt;p&gt;I&#39;ve set the &lt;code&gt;AllowedIPs&lt;/code&gt; to &lt;code&gt;10.99.0.0/16&lt;/code&gt;.
This is the global default setting that will get set for all clients.
It tells the clients what traffic to route into the VPN.
You would set this to &lt;code&gt;0.0.0.0/0&lt;/code&gt;, if you&#39;d like to route &lt;em&gt;all&lt;/em&gt; traffic through the VPN.&lt;/p&gt;
&lt;p&gt;I&#39;ve used same IP in Admin panel &amp;gt; Interface &amp;gt; Change CIDR &amp;gt; IPv4.
This defines the WireGuard subnet.&lt;/p&gt;
&lt;p&gt;Now you can add your &lt;strong&gt;first client&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Click the &lt;em&gt;New&lt;/em&gt; button on the dashboard, give client a name and an (option) expiration date.
If you open up the client by clicking on its name you can see that an IP from the WireGuard network has been allocated to it.
I will change this one to &lt;code&gt;10.99.1.1&lt;/code&gt; for my scheme.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;AllowedIPs&lt;/code&gt; is here if we want to override the default from the Admin panel.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;ServerAllowedIPs&lt;/code&gt; needs to be filled out if we want the server to route traffic to this client (or the network connected to it).
I foresee that I will want to route more than just the client IP to this client (for its network), so I will input &lt;code&gt;10.99.1.0/24&lt;/code&gt; here.&lt;/p&gt;
&lt;p&gt;When you&#39;re done, &lt;code&gt;wg-easy&lt;/code&gt; makes it simple to share the client config to each client with either a QR code, a file or a link.&lt;/p&gt;
&lt;h2&gt;Testing the connection&lt;/h2&gt;
&lt;p&gt;Downloading the config file for my client, I import it into my WireGuard client installed on my machine and turn it on.
If everything works, you will see that a handshake has happened and on the &lt;code&gt;wg-easy&lt;/code&gt; dashboard you will see that some data has started flowing through the VPN.&lt;/p&gt;
&lt;h2&gt;Next steps&lt;/h2&gt;
&lt;h3&gt;Security hardening&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;use a separate non-root user for the service&lt;/li&gt;
&lt;li&gt;restrict the firewall even further (e.g. to allow connections only from specific IPs)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Ad blocking&lt;/h3&gt;
&lt;p&gt;You can add some DNS-based Ad blocking, like the excelent &lt;a href=&quot;https://pi-hole.net/&quot;&gt;Pi-hole&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Monitoring and Alerting&lt;/h3&gt;
&lt;p&gt;You might want to set up monitoring and alerting to see how much traffic, bandwidth, CPU or RAM you&#39;re using.&lt;/p&gt;
&lt;h3&gt;Automate the provisioning process&lt;/h3&gt;
&lt;p&gt;At work we wrapped this process up in a script to make it painless to deploy this setup to another machine, if needed.
If you&#39;ll add more than just a few services, then going with Ansible or Terraform might be a better fit.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;We saw how easy it is to set up your own and private VPN server, that you can easily share with your friends and family.
It runs on a cheap public cloud VM that is cheaper than leading hosted VPN solutions.&lt;/p&gt;
&lt;p&gt;Try it out and let me know how well it works for you&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>My hardware</title>
    <link href="https://luka.korosec.cc/posts/2025/09/my-hardware/"/>
    <published>2025-09-19T00:00:00.000Z</published>
    <updated>2025-09-19T00:00:00.000Z</updated>
    <id>https://luka.korosec.cc/posts/2025/09/my-hardware/</id>
    <content type="html">&lt;p&gt;I have lots of little machines!
This post will describe them and what I use them for.
Let&#39;s go!&lt;/p&gt;
&lt;h2&gt;The Server&lt;/h2&gt;
&lt;p&gt;This is my crown jewel custom-built box.&lt;/p&gt;
&lt;p&gt;It runs most of my services; it is my cloud (&lt;a href=&quot;https://nextcloud.com/&quot;&gt;NextCloud&lt;/a&gt;), password manager (&lt;a href=&quot;https://github.com/dani-garcia/vaultwarden&quot;&gt;Vaultwarden&lt;/a&gt;), git forge (&lt;a href=&quot;https://forgejo.org/&quot;&gt;Forgejo&lt;/a&gt;), media server (the *arrs and &lt;a href=&quot;https://www.plex.tv/&quot;&gt;Plex&lt;/a&gt;), and lots and lots more.&lt;/p&gt;
&lt;p&gt;Here are the specs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CPU: AMD EPYC 7402P 24-Core Processor
&lt;ul&gt;
&lt;li&gt;Usage varies, usually under 10%, but &lt;a href=&quot;https://nixos.org/&quot;&gt;NixOS&lt;/a&gt; rebuilds, Plex transcoding, and running local LLMs stress it quite a lot&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;RAM: 128GB DDR4 running at 3200MHz
&lt;ul&gt;
&lt;li&gt;All 8 memory slots are populated with 16GB sticks&lt;/li&gt;
&lt;li&gt;Currently using ~10GB with 115GB available, but this varies as I play with local LLMs&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;GPU: NVIDIA GeForce GTX 1070 8GB VRAM
&lt;ul&gt;
&lt;li&gt;Currently using ~3GB for STT for my &lt;a href=&quot;https://www.home-assistant.io/&quot;&gt;HomeAssistant&lt;/a&gt; Voice Control&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;DELL Perc H310 SAS Raid Controller
&lt;ul&gt;
&lt;li&gt;For connecting all the storage devices&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Network:
&lt;ul&gt;
&lt;li&gt;2x Intel I210 Gigabit Ethernet controllers&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It&#39;s a beefy machine, but if I were to buy it again, I would go for the 16 core EPYC.
I only chose the 24 core one because it was just 20eur more expensive compared to the 16 core one.
But it does draw more power, which results in more heat generated, which sucks in the summer.&lt;/p&gt;
&lt;p&gt;Anyway, I love running heavy loads on it and watching all the cores light up.
Nix updates and rebuilds sometimes rebuild a lot of packages, and this machine handles it like a champ.&lt;/p&gt;
&lt;p&gt;What is great about having the server motherboard is having IPMI!
I never saw the appeal of connecting to the machine and having the ability to change BIOS settings remotely, for example,
but after some failed updates that locked me out of SSH, I was able to connect the machine via a web console and restore connectivity,
it was great!
I recommend getting a motherboard with IPMI now.&lt;/p&gt;
&lt;p&gt;Another great thing is having two NICs.
I can assign one to a VM or something else.&lt;/p&gt;
&lt;h3&gt;Storage&lt;/h3&gt;
&lt;p&gt;I have two BTRFS pools, one for OS and services and another for media.
Both are set to RAID1, so I generally buy two drives if I want to expand my storage.&lt;/p&gt;
&lt;p&gt;I&#39;ve had one drive fail and I was able to replace it with no data loss.
I have scheduled data scrubs and they have at times detected and corrected issues.
It&#39;s also quite fast, especially if you split the spinning drives and SSDs.&lt;/p&gt;
&lt;p&gt;All in all, I recommend using &lt;a href=&quot;https://btrfs.readthedocs.io/en/latest/&quot;&gt;BTRFS&lt;/a&gt;.&lt;/p&gt;
&lt;h4&gt;Root pool&lt;/h4&gt;
&lt;p&gt;My root pool is a mishmash of old drives that I cannibalized from other/older systems, e.g. my defunct gaming PC.&lt;/p&gt;
&lt;p&gt;Devices:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;NVMe SSD: WDC WDS500G2B0C (500GB)&lt;/li&gt;
&lt;li&gt;SATA SSDs:
&lt;ul&gt;
&lt;li&gt;Crucial CT512MX100SSD1 (512GB)&lt;/li&gt;
&lt;li&gt;Samsung 870 EVO (1TB)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This gives me ~1TB of usable storage.&lt;/p&gt;
&lt;p&gt;So far it has performed quite well and no devices have died so far.&lt;/p&gt;
&lt;h4&gt;Media pool&lt;/h4&gt;
&lt;p&gt;Here is the place for my big files and (local) backups.&lt;/p&gt;
&lt;p&gt;Media pool Hard Drives:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;2x Seagate ST12000NM0127 (12TB each)&lt;/li&gt;
&lt;li&gt;2x Seagate ST8000NE001 (8TB each)&lt;/li&gt;
&lt;li&gt;2x WD WD60EFRX/EFAX (6TB each)&lt;/li&gt;
&lt;li&gt;1x WD WD30EFRX (3TB)&lt;/li&gt;
&lt;li&gt;1x WD WD20NMVW (2TB external, for backups)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here I&#39;ve lost one 3TB drive to failure in the last 5 years.
I was able to replace it and the BTRFS pool carried on with no data loss.&lt;/p&gt;
&lt;h2&gt;PC-Engines APU2 board&lt;/h2&gt;
&lt;p&gt;I have a &lt;a href=&quot;https://www.pcengines.ch/apu2.htm&quot;&gt;PC-engines APU2 board&lt;/a&gt;.&lt;/p&gt;
&lt;img src=&quot;/assets/images/004-apu2.jpg&quot; alt=&quot;My APU2 board in its cabinet, don&#39;t mind the messy cables&quot;&gt;
&lt;p&gt;Currently its serving as my main router with &lt;a href=&quot;https://opnsense.org/&quot;&gt;OPNsense&lt;/a&gt;.
I manage my DHCP, DNS, VPNs, VLANs and other network related things on it.&lt;/p&gt;
&lt;p&gt;So far it&#39;s been great, though the CPU usage frequently reaches 100% and it&#39;s also running quite hot.&lt;/p&gt;
&lt;h2&gt;Linksys EA7500&lt;/h2&gt;
&lt;p&gt;This one serves as my main home Wi-Fi AP running &lt;a href=&quot;https://openwrt.org/&quot;&gt;OpenWrt&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It has been with me for the longest time, even before I started my HomeLab journey and I only needed it for WiFi.&lt;/p&gt;
&lt;h2&gt;NVidia Shield&lt;/h2&gt;
&lt;p&gt;This is a relatively new addition, I bought it used to replace the apps of my smart LG TV.
The TV Plex app was slow and I was also afraid I would start to get ads on the TV at some point via a sneaky WebOS update.&lt;/p&gt;
&lt;p&gt;With the addition of the &lt;a href=&quot;https://www.nvidia.com/en-us/shield/&quot;&gt;NVIDIA Shield&lt;/a&gt;, I was able to disconnect the TV from the network and now I only use it as a TV screen.&lt;/p&gt;
&lt;h2&gt;Raspberry Pi 1&lt;/h2&gt;
&lt;p&gt;Just recently I used it to create a &lt;a href=&quot;https://github.com/MiczFlor/RPi-Jukebox-RFID&quot;&gt;Phoniebox&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I should write a guide on how to build it yourself.
It was super easy, literally took me minutes.&lt;/p&gt;
&lt;p&gt;Since it does not have a Wi-Fi, I can&#39;t actually check the load its under (I could connect to it via Ethernet cable, but I&#39;m lazy and can&#39;t be bothered right now).
Interacting with it with the RFID cards is working great and feels snappy.&lt;/p&gt;
&lt;p&gt;Overall, it&#39;s working great for such an underpowered device.&lt;/p&gt;
&lt;h2&gt;Dell Wyze 5070&lt;/h2&gt;
&lt;p&gt;This &lt;a href=&quot;https://www.parkytowers.me.uk/thin/wyse/5070/&quot;&gt;little machine&lt;/a&gt; is currently running my HomeAssistant instance.
It has 4GB of RAM.&lt;/p&gt;
&lt;p&gt;The instance is working great, it loads fast and the devices it controls are very responsive.
It is currently connected to 18 smart home devices, most of which are lights and switches.&lt;/p&gt;
&lt;p&gt;Load on the CPU rarely reaches 10%, its usually under 4%, so there is a lot of room for growth.
It currently consumes around 1.3GB of RAM.&lt;/p&gt;
&lt;h2&gt;The closet&lt;/h2&gt;
&lt;p&gt;I also have a few other machines that are not in use right now.&lt;/p&gt;
&lt;h3&gt;Raspberry Pi 5&lt;/h3&gt;
&lt;p&gt;This was an impulse buy, I admit.
I think I was going to put some retro gaming OS on it, then I was gonna turn it into a monitoring solution for my HomeLab and now it&#39;s sitting and gathering dust.&lt;/p&gt;
&lt;p&gt;We&#39;ll see what use I find for it down the road.&lt;/p&gt;
&lt;h3&gt;My old gaming rig&lt;/h3&gt;
&lt;p&gt;This one is based around a &lt;a href=&quot;https://motherboarddb.com/motherboards/733/z170/&quot;&gt;Asus Z170 Pro Gaming&lt;/a&gt; motherboard.&lt;/p&gt;
&lt;p&gt;I think it has an i5 processor and 16GB of RAM.
At some point I used it as a temporary server replacement, when my main server motherboard died.
It was a painless switch, but I remember the slowness and I think the network wasn&#39;t the most stable.&lt;/p&gt;
&lt;p&gt;Anyway, it&#39;s gathering dust now as well. I keep intending on selling it, but just never get around to it.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Caching adventures with Caddy and Nix</title>
    <link href="https://luka.korosec.cc/posts/2025/08/caching-adventures-with-caddy-and-nix/"/>
    <published>2025-08-08T00:00:00.000Z</published>
    <updated>2025-08-08T00:00:00.000Z</updated>
    <id>https://luka.korosec.cc/posts/2025/08/caching-adventures-with-caddy-and-nix/</id>
    <content type="html">&lt;p&gt;So if you&#39;ve followed along my &lt;a href=&quot;/posts/2025/07/create-a-static-blog-with-nix/&quot;&gt;Create a static blog with Nix&lt;/a&gt; blog post you might have noticed an issue when serving the files.&lt;/p&gt;
&lt;p&gt;When you update a blog post and deploy it to the server, you check it on your phone to see the new changes, but they are just not showing!
You have to do a hard-refresh for them to show up.
You investigate and notice that the server is always returning &lt;code&gt;304 Not Modified&lt;/code&gt; for all static files.
You try creating a new blog post, just to test it out, and you get &lt;code&gt;200&lt;/code&gt; response with the content of the post just fine.
You refresh the page, you get a &lt;code&gt;304&lt;/code&gt; which is also fine - you think caching is working.
You then update the blog post and expect a &lt;code&gt;200&lt;/code&gt; response with the content, but you still get a &lt;code&gt;304 Not Modified&lt;/code&gt; response with no content.
Browser then displays the old blog post.
Weird.
Something is not working as it should.&lt;/p&gt;
&lt;p&gt;Let&#39;s investigate.&lt;/p&gt;
&lt;h2&gt;How browser caching works&lt;/h2&gt;
&lt;p&gt;Say we&#39;re serving a static file &lt;code&gt;/index.html&lt;/code&gt;.
When you first navigate to this page, browser makes a request for that file and displays it.
It also saves it to its own cache so that it does not have to fetch it everytime you open that page, saving you time and bandwidth.&lt;/p&gt;
&lt;p&gt;But if the file changes on the server, how does browser usually display the new content?&lt;/p&gt;
&lt;p&gt;When a browser navigates to a page for the second time (or any subsequent visit), it includes an &lt;code&gt;If-Modified-Since&lt;/code&gt; header in its request. This header contains the &lt;code&gt;Last-Modified&lt;/code&gt; timestamp that the server provided during the initial request.&lt;/p&gt;
&lt;p&gt;Upon receiving this request, the server compares the header timestamp with the file&#39;s actual modification time. If the file has been modified since the indicated time, the server responds with the complete file. If no changes have occurred, it returns a &lt;code&gt;304 Not Modified&lt;/code&gt; status code.&lt;/p&gt;
&lt;p&gt;When browser receives the full file, it displays the content, updates its cache, and records the new timestamp for future requests. If it receives a &lt;code&gt;304&lt;/code&gt; response, it simply displays the cached version of the page.&lt;/p&gt;
&lt;p&gt;That works great, &lt;em&gt;if the server has proper file modification timestamps on the files it serves&lt;/em&gt;. &lt;strong&gt;That is not the case for files in the nix store.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;Nix&#39;s peculiarities strike again&lt;/h2&gt;
&lt;p&gt;When Nix adds a file to the &lt;code&gt;/nix/store&lt;/code&gt;, it strips away the file modification times.&lt;/p&gt;
&lt;p&gt;Let&#39;s take a look at a file in the store:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;❯ &lt;span class=&quot;token function&quot;&gt;ls&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-l&lt;/span&gt; /nix/store/50n81h7r3mp2v8ybjc7qrjx5igrcghmc-just-1.42.3/bin/just
-r-xr-xr-x &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; root nixbld &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;.7M Jan  &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;  &lt;span class=&quot;token number&quot;&gt;1970&lt;/span&gt; /nix/store/50n81h7r3mp2v8ybjc7qrjx5igrcghmc-just-1.42.3/bin/just*

❯ &lt;span class=&quot;token function&quot;&gt;stat&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;--format&lt;/span&gt; %y /nix/store/50n81h7r3mp2v8ybjc7qrjx5igrcghmc-just-1.42.3/bin/just
&lt;span class=&quot;token number&quot;&gt;1970&lt;/span&gt;-01-01 01:00:01.000000000 +0100

❯ &lt;span class=&quot;token function&quot;&gt;stat&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;--format&lt;/span&gt; %Y /nix/store/50n81h7r3mp2v8ybjc7qrjx5igrcghmc-just-1.42.3/bin/just
&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;

❯ &lt;span class=&quot;token function&quot;&gt;stat&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;--help&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; rg &lt;span class=&quot;token string&quot;&gt;&quot;(format=|modif)&quot;&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-c&lt;/span&gt;  &lt;span class=&quot;token parameter variable&quot;&gt;--format&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;FORMAT   use the specified FORMAT instead of the default&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  %y   &lt;span class=&quot;token function&quot;&gt;time&lt;/span&gt; of last data modification, human-readable
  %Y   &lt;span class=&quot;token function&quot;&gt;time&lt;/span&gt; of last data modification, seconds since Epoch&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can see that the modification time is one second after the Unix epoch.&lt;/p&gt;
&lt;p&gt;This is clearly an issue for our Caddy server. When the &lt;code&gt;If-Modified-Since&lt;/code&gt; time is always &lt;em&gt;after&lt;/em&gt; the file&#39;s modified time, Caddy will consistently return a &lt;code&gt;304 Not Modified&lt;/code&gt; response for requests containing the &lt;code&gt;If-Modified-Since&lt;/code&gt; header.&lt;/p&gt;
&lt;p&gt;This behavior is actually correct - Caddy is operating according to specification.&lt;/p&gt;
&lt;h2&gt;So what can we do?&lt;/h2&gt;
&lt;p&gt;Well, if browser does not have the last modification time, it won&#39;t send the &lt;code&gt;If-Modified-Since&lt;/code&gt; header and Caddy will return the whole file always.
Which does fix our issue of updates not being seen by people, but also wastes bandwidth and increases load times.
Not cool.&lt;/p&gt;
&lt;p&gt;When searching for this I encountered &lt;a href=&quot;https://en.wikipedia.org/wiki/HTTP_ETag&quot;&gt;ETags&lt;/a&gt;.
ETag stands for Entity Tag.
You can think of them as file hashes.
If a file changes, so does its hash.&lt;/p&gt;
&lt;p&gt;Browsers can use this for cache control via the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/ETag&quot;&gt;ETag header&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In my tests I found that Caddy would not generate etags for my files.
After some searching I confirmed it when I found this PR: &lt;a href=&quot;https://github.com/caddyserver/caddy/pull/5550&quot;&gt;fileserver: Don&#39;t set Etag if mtime is 0 or 1&lt;/a&gt;.
Remember when we inspected the modification times in the nix store?
Yeah, its 1.&lt;/p&gt;
&lt;p&gt;So Caddy won&#39;t do it automatically, but can we somehow force it?
We can!&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;file_server&lt;/code&gt; module accepts &lt;a href=&quot;https://caddyserver.com/docs/json/apps/http/servers/routes/handle/file_server/etag_file_extensions/&quot;&gt;&lt;code&gt;etag_file_extensions&lt;/code&gt;&lt;/a&gt;, an array of extensions from which ETags will be read by Caddy.
So for each file we&#39;ll just have to create an ETag file.&lt;/p&gt;
&lt;p&gt;Alright, so we have a plan!&lt;/p&gt;
&lt;h2&gt;Implementation time&lt;/h2&gt;
&lt;p&gt;Let&#39;s first generate the ETag files.&lt;/p&gt;
&lt;h3&gt;Generating ETag files&lt;/h3&gt;
&lt;p&gt;We add some additional build inputs (&lt;code&gt;fd&lt;/code&gt; and &lt;code&gt;coreutils&lt;/code&gt; for &lt;code&gt;md5sum&lt;/code&gt; and &lt;code&gt;cut&lt;/code&gt;) and generate the files as part of our &lt;code&gt;installPhase&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;diff --git a/flake.nix b/flake.nix
index d8c6552..d1b602f 100644
&lt;span class=&quot;token coord&quot;&gt;--- a/flake.nix&lt;/span&gt;
&lt;span class=&quot;token coord&quot;&gt;+++ b/flake.nix&lt;/span&gt;
&lt;span class=&quot;token coord&quot;&gt;@@ -30,12 +30,22 @@&lt;/span&gt;
&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;          pname = &quot;my-static-site&quot;;
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;          version = &quot;1.0.0&quot;;
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;          src = ./.;
&lt;/span&gt;&lt;span class=&quot;token deleted-sign deleted&quot;&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;          buildInputs = [ eleventy ];
&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;          buildInputs = with pkgs; [
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;            eleventy
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;            fd
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;            coreutils
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;          ];
&lt;/span&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;          buildPhase = &quot;eleventy&quot;;
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;          installPhase = &#39;&#39;
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;            mkdir -p $out/
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;            echo $out
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;            cp -r _site/* $out/
&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;            # Generate .etag files for cache validation
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;            for file in $(fd --type f . &quot;$out&quot;); do
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;              hash=$(md5sum &quot;$file&quot; | cut -d&quot; &quot; -f1)
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;              echo &quot;&#92;&quot;$hash&#92;&quot;&quot; &gt; &quot;$file.etag&quot;
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;            done;
&lt;/span&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;          &#39;&#39;;
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;        };
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;      in
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you wan&#39;t you can swap out the &lt;code&gt;md5sum&lt;/code&gt; for something faster/slower, in the end it just has to be uniqu enough.&lt;/p&gt;
&lt;p&gt;Alright lets test it out with &lt;code&gt;nix build&lt;/code&gt; and inspect the results:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;❯ nix build
warning: Git tree &lt;span class=&quot;token string&quot;&gt;&#39;[...]/create-a-static-blog-with-nix&#39;&lt;/span&gt; is dirty

❯ tree ./result
./result
├── index.html
└── index.html.etag

&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; directory, &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt; files

❯ &lt;span class=&quot;token function&quot;&gt;cat&lt;/span&gt; ./result/index.html.etag
&lt;span class=&quot;token string&quot;&gt;&quot;5273e1f62e43bab55701f318492d7cad&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; The contents of the &lt;code&gt;.etag&lt;/code&gt; file &lt;strong&gt;have to be quoted&lt;/strong&gt; for this to work.
I pulled my hair out until the kind folks at the Caddy &lt;a href=&quot;https://github.com/caddyserver/caddy/issues/7096#issuecomment-3015209236&quot;&gt;helped me solve the issue&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Configuring Caddy&lt;/h3&gt;
&lt;p&gt;Now that we have the ETag files, lets tell Caddy to use them.&lt;/p&gt;
&lt;p&gt;So we need to remove the &lt;code&gt;Last-Modified&lt;/code&gt; header, to force browser to use ETags and specify the file extension Caddy should use to get the ETags for our files.&lt;/p&gt;
&lt;pre class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;diff --git a/nixos-configuration/my-static-site.nix b/nixos-configuration/my-static-site.nix
index 82737c4..650898a 100644
&lt;span class=&quot;token coord&quot;&gt;--- a/nixos-configuration/my-static-site.nix&lt;/span&gt;
&lt;span class=&quot;token coord&quot;&gt;+++ b/nixos-configuration/my-static-site.nix&lt;/span&gt;
@@ -7,7 +7,12 @@ in
&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;    enable = true;
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;    # Needs `http://` prefix so that it does not try to request TLS certificates and redirect to 443
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;    virtualHosts.&quot;http://${site-url}&quot;.extraConfig = &#39;&#39;
&lt;/span&gt;&lt;span class=&quot;token deleted-sign deleted&quot;&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;      file_server
&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;      header {
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;        -Last-Modified
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;      }
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;      file_server {
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;          etag_file_extensions .etag
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;      }
&lt;/span&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;      root * ${inputs.our-site.packages.&quot;${pkgs.system}&quot;.default}
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;      encode gzip
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;    &#39;&#39;;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now is a good time to commit this: &lt;a href=&quot;https://git.kalu.blue/tech-blog/create-a-static-blog-with-nix/commit/c7a4d2fe0c67d33d53454dbbfcbf204c39b79799&quot;&gt;Use ETags instead of Last-Modified for caching&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;ETag browser caching process&lt;/h2&gt;
&lt;p&gt;With ETags properly configured, browser caching process follows this sequence:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Initial Request:&lt;/strong&gt;
The server retrieves the ETag from the &lt;code&gt;index.html.etag&lt;/code&gt; file and includes it in the response headers alongside the &lt;code&gt;index.html&lt;/code&gt; content. Browser receives both the file and its corresponding ETag value, caching them together.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Subsequent Requests:&lt;/strong&gt;
When browser requests the same file again, it automatically includes an &lt;code&gt;If-None-Match&lt;/code&gt; header containing the previously cached ETag value. The server compares this ETag against the current value stored in &lt;code&gt;index.html.etag&lt;/code&gt;. If they match, the server responds with a &lt;code&gt;304 Not Modified&lt;/code&gt; status, allowing browser to use its cached version. If the ETags differ, the server sends the updated file with the new ETag.&lt;/p&gt;
&lt;h2&gt;That&#39;s it, we fixed it!&lt;/h2&gt;
&lt;p&gt;This was one of those frustrating debugging sessions where everything &lt;em&gt;seemed&lt;/em&gt; to be working correctly - Caddy was following HTTP specifications to the letter, browsers were caching as expected, and yet we couldn&#39;t see updates.
The real culprit turned out to be Nix&#39;s timestamp normalization, a detail of how the Nix store works.&lt;/p&gt;
&lt;p&gt;What made me miss this issue in the first place is that it only manifested for me after making some updates to my posts.
But with some persistence we found a clean solution using ETags that, in my opinion, works better than modification times anyway.&lt;/p&gt;
&lt;p&gt;Now our static site updates show up immediately for users, without sacrificing caching performance.
Problem solved!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Create a static blog with Nix</title>
    <link href="https://luka.korosec.cc/posts/2025/07/create-a-static-blog-with-nix/"/>
    <published>2025-07-02T00:00:00.000Z</published>
    <updated>2025-09-19T00:00:00.000Z</updated>
    <id>https://luka.korosec.cc/posts/2025/07/create-a-static-blog-with-nix/</id>
    <content type="html">&lt;p&gt;This blog has been written with &lt;a href=&quot;https://www.11ty.dev/&quot;&gt;Eleventy&lt;/a&gt;.
It handles the static site generation part, like converting markdown to HTML, routes and so on.
Classic static site generator stuff.
Their slogan is &amp;quot;Eleventy is a simpler static site generator&amp;quot; and so far I think it lives up to it.&lt;/p&gt;
&lt;p&gt;What I don&#39;t like about it is that you need to install Node.js on your machine to get it to work.
I seriously considered using &lt;a href=&quot;https://www.getzola.org/&quot;&gt;Zola&lt;/a&gt; instead for this reason alone.
In the end I decided for Eleventy because it&#39;s more mature and has a bigger community.
The documentation is also more complete I feel.&lt;/p&gt;
&lt;p&gt;If you want to skip ahead and take a look at the resulting repository, you can find it at &lt;a href=&quot;https://git.kalu.blue/tech-blog/create-a-static-blog-with-nix&quot;&gt;tech-blog/create-a-static-blog-with-nix&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Also, there is also a continuation of this post in &lt;a href=&quot;/posts/2025/08/caching-adventures-with-caddy-and-nix/&quot;&gt;Caching adventures with Caddy and Nix&lt;/a&gt;.
It&#39;s where we investigate the (tricky) caching issues with this setup and fix them.&lt;/p&gt;
&lt;h2&gt;Bootstrapping the project&lt;/h2&gt;
&lt;p&gt;So, given that I decided to go with Eleventy, that means we need to have Node.js installed.
I don&#39;t want to have it installed system-wide just for Eleventy to work, so I decided to reach for the power of Nix.&lt;/p&gt;
&lt;p&gt;To be fair, I would&#39;ve used Nix even if I went with some other static site generator, but using something like Eleventy means I get to learn how to use Node packages with Nix.&lt;/p&gt;
&lt;p&gt;First step is creating a git repository (&lt;code&gt;git init&lt;/code&gt;) and a new flake (&lt;code&gt;nix flake init&lt;/code&gt;).
I like to use &lt;a href=&quot;https://github.com/numtide/flake-utils&quot;&gt;flake-utils&lt;/a&gt;, so I add that to the inputs and adjust the flake to use it.
Run &lt;code&gt;nix flake update&lt;/code&gt; to get the &lt;code&gt;flake.lock&lt;/code&gt; file and then let&#39;s do an initial commit (&lt;code&gt;git add . &amp;amp;&amp;amp; git commit -m &amp;quot;Initial commit&amp;quot;&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Now we have a good starting point.&lt;/p&gt;
&lt;p&gt;Commit: &lt;a href=&quot;https://git.kalu.blue/tech-blog/create-a-static-blog-with-nix/commit/4b931208b6781e05058b2eb36caf3f2da2836604&quot;&gt;Initial commit&lt;/a&gt;&lt;/p&gt;
&lt;pre class=&quot;language-nix&quot;&gt;&lt;code class=&quot;language-nix&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  inputs &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    nixpkgs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;github:NixOS/nixpkgs/nixpkgs-unstable&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    flake&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;utils&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;github:numtide/flake-utils&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  outputs &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      self&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      nixpkgs&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      flake&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;utils&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;@&lt;/span&gt;inputs&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    flake&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;utils&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lib&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;eachDefaultSystem &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      system&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt;
        pkgs &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; nixpkgs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;legacyPackages&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token antiquotation important&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;system&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        devShells&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;default &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; pkgs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;mkShell &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          buildInputs &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
          shellHook &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Development shell&lt;/h3&gt;
&lt;p&gt;Next step I always do is add a development shell.&lt;/p&gt;
&lt;p&gt;If you&#39;re not familiar with that, &lt;a href=&quot;https://nixos.wiki/wiki/Development_environment_with_nix-shell&quot;&gt;here is a nice write up&lt;/a&gt; (I will use &lt;code&gt;nix develop&lt;/code&gt;).
But the short summary is that it provides a sort of virtual environment with the tools you need to interact with the project.
It is super simple for other people to use and I think it is way better than using docker/podman for sharing development environments.&lt;/p&gt;
&lt;p&gt;I &lt;em&gt;just&lt;/em&gt; can&#39;t live without &lt;a href=&quot;https://just.systems/&quot;&gt;just&lt;/a&gt;, the task runner.
I always use it to run tasks in the project, but not everyone has it installed.&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# This command will output the path of the just command if you have it installed&lt;/span&gt;
❯ &lt;span class=&quot;token function&quot;&gt;which&lt;/span&gt; just
/Users/luka/.nix-profile/bin/just&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can see I already have it installed system-wide, but I always try to include all project dependencies in my development shell, just in case anyone else wants to interact with the project.&lt;/p&gt;
&lt;p&gt;Let&#39;s modify the &lt;code&gt;flake.nix&lt;/code&gt; to add it.&lt;/p&gt;
&lt;pre class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;diff --git a/flake.nix b/flake.nix
index 58dadad..fffb25c 100644
&lt;span class=&quot;token coord&quot;&gt;--- a/flake.nix&lt;/span&gt;
&lt;span class=&quot;token coord&quot;&gt;+++ b/flake.nix&lt;/span&gt;
&lt;span class=&quot;token coord&quot;&gt;@@ -18,7 +18,9 @@&lt;/span&gt;
&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;      in
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;      {
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;        devShells.default = pkgs.mkShell {
&lt;/span&gt;&lt;span class=&quot;token deleted-sign deleted&quot;&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;          buildInputs = [];
&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;          buildInputs = with pkgs; [
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;            just
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;          ];
&lt;/span&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;          shellHook = &quot;&quot;;
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;        };
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;      }
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, we have to activate the development shell.
We can do that by running &lt;code&gt;nix develop&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;❯ nix develop
&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
❯ &lt;span class=&quot;token function&quot;&gt;which&lt;/span&gt; just
/nix/store/w0s0f89zcpwl9ipygxiihba0j6fgwnyq-just-1.40.0/bin/just
❯ &lt;span class=&quot;token comment&quot;&gt;#press CTRL+D to exit this development shell or run exit command&lt;/span&gt;
&lt;span class=&quot;token builtin class-name&quot;&gt;exit&lt;/span&gt;

❯ &lt;span class=&quot;token function&quot;&gt;which&lt;/span&gt; just
/Users/luka/.nix-profile/bin/just&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see above, we get a &lt;code&gt;just&lt;/code&gt; binary, that is exactly defined by the nixpkgs commit our &lt;code&gt;flake.lock&lt;/code&gt; is pinned to.
When you update the flake (&lt;code&gt;nix flake update&lt;/code&gt;) your project binaries will also update.
In my case the binary provided by the system and the one provided by the development shell one are the same, but that will not always hold.&lt;/p&gt;
&lt;p&gt;One thing you might&#39;ve noticed if you&#39;re using &lt;code&gt;zsh&lt;/code&gt; is that the new shell is pure bash.
Use &lt;code&gt;nix develop -c $SHELL&lt;/code&gt; to retain your shell instead.&lt;/p&gt;
&lt;p&gt;Anyway, now, when you want to work on your blog/project, you need to &lt;code&gt;cd&lt;/code&gt; into the project root and run &lt;code&gt;nix develop&lt;/code&gt;.
If you&#39;re using the excellent &lt;a href=&quot;https://direnv.net/&quot;&gt;direnv&lt;/a&gt;, you can also paste &lt;code&gt;use flake&lt;/code&gt; in your &lt;code&gt;.envrc&lt;/code&gt;, run &lt;code&gt;direnv allow&lt;/code&gt; and now whenever you &lt;code&gt;cd&lt;/code&gt; into this repo, you&#39;ll get dropped into the development shell automatically.&lt;/p&gt;
&lt;p&gt;Now let&#39;s run &lt;code&gt;just --init&lt;/code&gt; to create a &lt;code&gt;justfile&lt;/code&gt; and edit to something like:&lt;/p&gt;
&lt;pre class=&quot;language-justfile&quot;&gt;&lt;code class=&quot;language-justfile&quot;&gt;# The default command to run when ran with just &#39;just&#39;
[group(&#39;General&#39;)]
default: help

# Print the available commands
[group(&#39;General&#39;)]
help:
    @just --list

# Update project dependencies
[group(&#39;General&#39;)]
update:
    nix flake update&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now when you run &lt;code&gt;just&lt;/code&gt; you get a nice list of available recipes you can choose from.&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;❯ just
Available recipes:
    &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;General&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    default &lt;span class=&quot;token comment&quot;&gt;# The default command to run when ran with just &#39;just&#39;&lt;/span&gt;
    &lt;span class=&quot;token builtin class-name&quot;&gt;help&lt;/span&gt;    &lt;span class=&quot;token comment&quot;&gt;# Print the available commands&lt;/span&gt;
    update  &lt;span class=&quot;token comment&quot;&gt;# Update project dependencies&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I think it&#39;s time we commit these changes and work on getting Eleventy into our development shell.&lt;/p&gt;
&lt;p&gt;Commit: &lt;a href=&quot;https://git.kalu.blue/tech-blog/create-a-static-blog-with-nix/commit/3ff6d210f0160ecf66fb8c1446132a173454847b&quot;&gt;Add just and justfile&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Add Eleventy&lt;/h2&gt;
&lt;p&gt;Now here comes the tricky part.
We need to add Eleventy to our project dependencies.&lt;/p&gt;
&lt;p&gt;As mentioned earlier, I don&#39;t really want to install Node.js to my machine, so let&#39;s see if we can find Eleventy in Nixpkgs repository and use that instead.
Searching &lt;a href=&quot;https://search.nixos.org/packages?query=eleventy&quot;&gt;nixpkgs&lt;/a&gt; for the Eleventy package, we find nothing (as of 2025-06-22 at least).
What now?&lt;/p&gt;
&lt;p&gt;Well, we can package it ourselves and add it to our repository as a dependency that way.
This being Nix, it took me quite a while to figure out how to do all of this.
But I persevered, because that is what I do and I like doing things the hard way I guess.
Anyway after a few hours of searching the web and scouring the nixpkgs source code here is what I came up with.&lt;/p&gt;
&lt;p&gt;I found the &lt;code&gt;pkgs.buildNpmPackage&lt;/code&gt; function (&lt;a href=&quot;https://github.com/NixOS/nixpkgs/blob/master/pkgs/build-support/node/build-npm-package/default.nix&quot;&gt;source&lt;/a&gt;).
It seems like a perfect candidate for us to use.
To build the NPM package it needs its source code, so let&#39;s get that first.&lt;/p&gt;
&lt;p&gt;You do that by adding it as an input to our flake. All inputs are git repositories and Nix manages them for us.&lt;/p&gt;
&lt;pre class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;diff --git a/flake.nix b/flake.nix
index fffb25c..18aea66 100644
&lt;span class=&quot;token coord&quot;&gt;--- a/flake.nix&lt;/span&gt;
&lt;span class=&quot;token coord&quot;&gt;+++ b/flake.nix&lt;/span&gt;
&lt;span class=&quot;token coord&quot;&gt;@@ -2,6 +2,11 @@&lt;/span&gt;
&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;  inputs = {
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;    nixpkgs.url = &quot;github:NixOS/nixpkgs/nixpkgs-unstable&quot;;
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;    flake-utils.url = &quot;github:numtide/flake-utils&quot;;
&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;    eleventy-src = {
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;      url = &quot;github:11ty/eleventy&quot;;
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;      flake = false;
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;    };
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;  };
&lt;/span&gt;
&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;  outputs =
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since the Eleventy repository does not contain a flake, you need to set &lt;code&gt;flake = false;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Now let&#39;s pass that to &lt;code&gt;buildNpmPackage&lt;/code&gt; to build it for us.
Let&#39;s create a &lt;code&gt;pkgs&lt;/code&gt; folder and place a &lt;code&gt;11ty-eleventy.nix&lt;/code&gt; file there with the following contents:&lt;/p&gt;
&lt;pre class=&quot;language-nix&quot;&gt;&lt;code class=&quot;language-nix&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# We&#39;ll pass in the pkgs and `src`&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; pkgs&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; src &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
pkgs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;buildNpmPackage &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;# Package name&lt;/span&gt;
  pname &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;eleventy&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  version &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;1.0.0&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  src &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; src&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  npmDepsHash &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; pkgs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lib&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;fakeHash&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# We&#39;ll replace this soon&lt;/span&gt;
  dontNpmBuild &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  meta &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;with&lt;/span&gt; pkgs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lib&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    description &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;A simpler static site generator&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    homepage &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://www.11ty.dev/&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    license &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; licenses&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;mit&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We set &lt;code&gt;dontNpmBuild&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt; because the &lt;code&gt;package.json&lt;/code&gt; in the Eleventy repository does not contain a &lt;code&gt;build&lt;/code&gt; script.&lt;/p&gt;
&lt;p&gt;And use it in our &lt;code&gt;flake.nix&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;diff --git a/flake.nix b/flake.nix
index 18aea66..58dd8c7 100644
&lt;span class=&quot;token coord&quot;&gt;--- a/flake.nix&lt;/span&gt;
&lt;span class=&quot;token coord&quot;&gt;+++ b/flake.nix&lt;/span&gt;
&lt;span class=&quot;token coord&quot;&gt;@@ -20,6 +20,10 @@&lt;/span&gt;
&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;      system:
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;      let
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;        pkgs = nixpkgs.legacyPackages.${system};
&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;        eleventy = import ./pkgs/11ty-eleventy.nix {
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;          inherit pkgs;
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;          src = inputs.eleventy-src;
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;        };
&lt;/span&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;      in
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;      {
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;        devShells.default = pkgs.mkShell {
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Make sure that you stage the changes with git (&lt;code&gt;git add .&lt;/code&gt;), otherwise you&#39;ll get an error that &lt;code&gt;[...]source/pkgs/pkgs/11ty-eleventy.nix&#39; does not exist&lt;/code&gt; when Nix tries to use it.&lt;/p&gt;
&lt;p&gt;Cool, now we have everything set up, all we need is to expose it in our outputs.&lt;/p&gt;
&lt;pre class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;diff --git a/flake.nix b/flake.nix
index 58dd8c7..e45810f 100644
&lt;span class=&quot;token coord&quot;&gt;--- a/flake.nix&lt;/span&gt;
&lt;span class=&quot;token coord&quot;&gt;+++ b/flake.nix&lt;/span&gt;
&lt;span class=&quot;token coord&quot;&gt;@@ -26,6 +26,7 @@&lt;/span&gt;
&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;        };
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;      in
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;      {
&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;        packages.eleventy = eleventy;
&lt;/span&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;        devShells.default = pkgs.mkShell {
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;          buildInputs = with pkgs; [
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;            just
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;nix flake show&lt;/code&gt; command shows us the outputs of our flake.
I&#39;ve edited it for brevity a bit, but yours should look similar.
We can see that we have one devShell (&lt;code&gt;default&lt;/code&gt;) and one package (&lt;code&gt;eleventy&lt;/code&gt;).&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;❯ nix flake show
warning: Git tree &lt;span class=&quot;token string&quot;&gt;&#39;[...]/create-a-static-blog-with-nix&#39;&lt;/span&gt; is dirty
git+file://&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;/create-a-static-blog-with-nix
├───devShells
│   ├───aarch64-darwin
│   │   └───default: development environment &lt;span class=&quot;token string&quot;&gt;&#39;nix-shell&#39;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
└───packages
    ├───aarch64-darwin
    │   └───eleventy: package &lt;span class=&quot;token string&quot;&gt;&#39;eleventy-1.0.0&#39;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now let&#39;s try and build it.&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;❯ nix build &lt;span class=&quot;token builtin class-name&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;#eleventy&lt;/span&gt;
warning: Git tree &lt;span class=&quot;token string&quot;&gt;&#39;[...]/create-a-static-blog-with-nix&#39;&lt;/span&gt; is dirty
error: &lt;span class=&quot;token builtin class-name&quot;&gt;hash&lt;/span&gt; mismatch &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; fixed-output derivation &lt;span class=&quot;token string&quot;&gt;&#39;/nix/store/kx4k4fh0hh7v0apnxc9xrnca6g71nwrh-eleventy-1.0.0-npm-deps.drv&#39;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt;
         specified: sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
            got:    sha256-LGdCM1gjt3hRn7BiIlbA4e2HOiQ6e/qkAtWp0Qwn+PE&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
error: &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; dependencies of derivation &lt;span class=&quot;token string&quot;&gt;&#39;/nix/store/cbc08ym28sgzz123gjf06q0k26j2sxc1-eleventy-1.0.0.drv&#39;&lt;/span&gt; failed to build&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you&#39;ve followed along this is what you should see, just the hashes will be different.
We expected this error to happen, since we used a &lt;code&gt;fakeHash&lt;/code&gt;.
If we replace the fake hash with the correct one the package should build.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;IMPORTANT: You should use the hash that you got, don&#39;t copy mine.&lt;/strong&gt;
As you&#39;ll get another hash mismatch.&lt;/p&gt;
&lt;pre class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;diff --git a/pkgs/11ty-eleventy.nix b/pkgs/11ty-eleventy.nix
index c0692bf..c6adbfa 100644
&lt;span class=&quot;token coord&quot;&gt;--- a/pkgs/11ty-eleventy.nix&lt;/span&gt;
&lt;span class=&quot;token coord&quot;&gt;+++ b/pkgs/11ty-eleventy.nix&lt;/span&gt;
@@ -5,7 +5,7 @@ pkgs.buildNpmPackage {
&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;  pname = &quot;eleventy&quot;;
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;  version = &quot;1.0.0&quot;;
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;  src = src;
&lt;/span&gt;&lt;span class=&quot;token deleted-sign deleted&quot;&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;  npmDepsHash = pkgs.lib.fakeHash; # We&#39;ll replace this soon
&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;  npmDepsHash = &quot;sha256-LGdCM1gjt3hRn7BiIlbA4e2HOiQ6e/qkAtWp0Qwn+PE=&quot;;
&lt;/span&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;  dontNpmBuild = true;
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;  meta = with pkgs.lib; {
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;    description = &quot;A simpler static site generator&quot;;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Running &lt;code&gt;nix build .#eleventy&lt;/code&gt; now completes successfully and gives us a &lt;code&gt;result&lt;/code&gt; file in the root of our repo.
If we take a look at what it points to, we can see it points at &lt;code&gt;/nix/store/[...]-eleventy-1.0.0&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;❯ readlink result
/nix/store/hqa8c57nbl7rlimi1fa611r64lp0mp4w-eleventy-1.0.0&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And taking a deeper look at the &lt;code&gt;result&lt;/code&gt;, we can see that it contains a binary (&lt;code&gt;result/bin/eleventy&lt;/code&gt;) and &lt;code&gt;node_modules&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;❯ tree result &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;head&lt;/span&gt;
result
├── bin
│   └── eleventy
└── lib
    └── node_modules
        └── @11ty
            └── eleventy
                ├── cmd.cjs
                ├── CODE_OF_CONDUCT.md
                ├── LICENSE&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let&#39;s try executing that binary:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;❯ ./result/bin/eleventy &lt;span class=&quot;token parameter variable&quot;&gt;--version&lt;/span&gt;
&lt;span class=&quot;token number&quot;&gt;3.1&lt;/span&gt;.2-beta.2

❯ ./result/bin/eleventy &lt;span class=&quot;token parameter variable&quot;&gt;--help&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;head&lt;/span&gt;
Usage: eleventy
       eleventy &lt;span class=&quot;token parameter variable&quot;&gt;--input&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;. &lt;span class=&quot;token parameter variable&quot;&gt;--output&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;./_site
       eleventy &lt;span class=&quot;token parameter variable&quot;&gt;--serve&lt;/span&gt;

Arguments:

     &lt;span class=&quot;token parameter variable&quot;&gt;--version&lt;/span&gt;

     &lt;span class=&quot;token parameter variable&quot;&gt;--input&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;.
       Input template files &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;default: &lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Works like a charm! What a great moment.&lt;/p&gt;
&lt;p&gt;To get that binary available in our development shell, we need to add the derivation (what Nix calls packages) as a build input.
Nix will then add the &lt;code&gt;bin&lt;/code&gt; folder of that derivation to our path.&lt;/p&gt;
&lt;pre class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;diff --git a/flake.nix b/flake.nix
index e45810f..29adafc 100644
&lt;span class=&quot;token coord&quot;&gt;--- a/flake.nix&lt;/span&gt;
&lt;span class=&quot;token coord&quot;&gt;+++ b/flake.nix&lt;/span&gt;
&lt;span class=&quot;token coord&quot;&gt;@@ -30,6 +30,7 @@&lt;/span&gt;
&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;        devShells.default = pkgs.mkShell {
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;          buildInputs = with pkgs; [
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;            just
&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;            eleventy
&lt;/span&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;          ];
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;          shellHook = &quot;&quot;;
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;        };
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let&#39;s enter the shell and try it out:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;❯ nix develop

❯ eleventy &lt;span class=&quot;token parameter variable&quot;&gt;--version&lt;/span&gt;
&lt;span class=&quot;token number&quot;&gt;3.1&lt;/span&gt;.2-beta.2

❯ &lt;span class=&quot;token function&quot;&gt;which&lt;/span&gt; eleventy
/nix/store/hqa8c57nbl7rlimi1fa611r64lp0mp4w-eleventy-1.0.0/bin/eleventy&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can see it&#39;s exactly the same Nix store path as the one we got when we ran &lt;code&gt;nix build .#eleventy&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;We could call it quits at this point, but I don&#39;t like that the version we specified in the package does not match the actual eleventy version.
Another thing that bothers me is that when the eleventy repository updates (and we run &lt;code&gt;nix flake update&lt;/code&gt; to get that update), we&#39;ll have to manually update the hash to the new one.&lt;/p&gt;
&lt;p&gt;Ugh, manual work! I would much rather invest a few more hours into this right now, then ever have to worry about manual work.&lt;/p&gt;
&lt;p&gt;Relevant XKCD as always: &lt;a href=&quot;https://xkcd.com/1205/&quot;&gt;Is it worth the time?&lt;/a&gt;.
In my case I think the answer would be a &lt;em&gt;no&lt;/em&gt;, but hopefully for you it might be a &lt;em&gt;yes&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Anyway, I had to figure out how this hash is calculated.
Turns out there is a package called &lt;code&gt;prefetch-npm-deps&lt;/code&gt; that given a &lt;code&gt;package-lock.json&lt;/code&gt; file outputs the desired hash.
So let&#39;s use it to calculate the hash.&lt;/p&gt;
&lt;p&gt;Let&#39;s create a new justfile recipe called &lt;code&gt;update-eleventy&lt;/code&gt; and let&#39;s not forget to add the binaries we use in the recipe to our development shell.&lt;/p&gt;
&lt;pre class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;diff --git a/flake.nix b/flake.nix
index 29adafc..dd6cc6d 100644
&lt;span class=&quot;token coord&quot;&gt;--- a/flake.nix&lt;/span&gt;
&lt;span class=&quot;token coord&quot;&gt;+++ b/flake.nix&lt;/span&gt;
&lt;span class=&quot;token coord&quot;&gt;@@ -31,6 +31,9 @@&lt;/span&gt;
&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;          buildInputs = with pkgs; [
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;            just
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;            eleventy
&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;            prefetch-npm-deps
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;            jq
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;            curlMinimal
&lt;/span&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;          ];
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;          shellHook = &quot;&quot;;
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;        };
&lt;/span&gt;diff --git a/justfile b/justfile
index 6776929..c7450cb 100644
&lt;span class=&quot;token coord&quot;&gt;--- a/justfile&lt;/span&gt;
&lt;span class=&quot;token coord&quot;&gt;+++ b/justfile&lt;/span&gt;
@@ -11,3 +11,13 @@ help:
&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;[group(&#39;General&#39;)]
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;update:
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;    nix flake update
&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;# Update packages sha256 and version
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;update-eleventy:
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;    #!/bin/bash
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;    pkgs=$(pwd)/pkgs
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;    cd $(mktemp -d)
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;    curl -f https://raw.githubusercontent.com/11ty/eleventy/refs/heads/main/package-lock.json -o package-lock.json
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;    prefetch-npm-deps package-lock.json &gt; $pkgs/11ty-eleventy.sha256
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;    cat package-lock.json | jq --raw-output &quot;.version&quot; &gt; $pkgs/11ty-eleventy.version
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let me explain the &lt;code&gt;update-eleventy&lt;/code&gt; recipe, line by line:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;#!/bin/bash&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;this tells Just to interpret this recipe as a bash script&lt;/li&gt;
&lt;li&gt;this is significant, since otherwise Just executes each line in its own separate shell, a &lt;code&gt;cd&lt;/code&gt; would have no effect for example&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pkgs=$(pwd)/pkgs&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;set a &lt;code&gt;pkgs&lt;/code&gt; variable to &lt;code&gt;$(pwd)/pkgs&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pwd&lt;/code&gt; stands for &#39;print working directory&#39;, which outputs the absolute path of the current directory&lt;/li&gt;
&lt;li&gt;we&#39;ll need this variable to copy the files back to our repository&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cd $(mktemp -d)&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;mktemp -d&lt;/code&gt; creates a temporary directory and outputs its path&lt;/li&gt;
&lt;li&gt;so we &lt;code&gt;cd&lt;/code&gt; into a temporary directory that we created&lt;/li&gt;
&lt;li&gt;we do this, so that we don&#39;t have to clean-up the files, the operating system will do that for us at some point&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;curl -f https://[...]/package-lock.json -o package-lock.json&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;get the &lt;code&gt;package-lock.json&lt;/code&gt; file from the GitHub repository of Eleventy and save it to &lt;code&gt;package-lock.json&lt;/code&gt; file in the current (temporary) directory&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;prefetch-npm-deps package-lock.json &amp;gt; $pkgs/11ty-eleventy.sha256&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;calculate the hash with of the &lt;code&gt;package-lock.json&lt;/code&gt; file with &lt;code&gt;prefetch-npm-deps&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;and save it (&lt;code&gt;&amp;gt;&lt;/code&gt;) to &lt;code&gt;$pkgs/11ty-eleventy.sha256&lt;/code&gt; file in our repository&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cat package-lock.json | jq --raw-output &amp;quot;.version&amp;quot; &amp;gt; $pkgs/11ty-eleventy.version&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;print out the &lt;code&gt;package-lock.json&lt;/code&gt; file (&lt;code&gt;cat&lt;/code&gt;) and pass it along (&lt;code&gt;|&lt;/code&gt;) to &lt;code&gt;jq&lt;/code&gt; which extracts the version (&lt;code&gt;&#39;.version&#39;&lt;/code&gt;) and saves it (&lt;code&gt;&amp;gt;&lt;/code&gt;) to &lt;code&gt;$pkgs/11ty-eleventy.version&lt;/code&gt; file in our repository&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To get the new dependencies in your development shell, make sure you leave (&lt;code&gt;CTRL+D&lt;/code&gt;) and re-enter the development shell (&lt;code&gt;nix develop&lt;/code&gt;).
Now we can run &lt;code&gt;just update-eleventy&lt;/code&gt;.
You&#39;ll see that two new files appeared in the &lt;code&gt;pkgs&lt;/code&gt; folder, a &lt;code&gt;11ty-eleventy.sha256&lt;/code&gt; and a &lt;code&gt;11ty-eleventy.version&lt;/code&gt;.
If you open them, you&#39;ll see what you expect to see from the filename, a hash and a version.&lt;/p&gt;
&lt;p&gt;Let&#39;s use these two new files in our &lt;code&gt;pkgs/11ty-eleventy.nix&lt;/code&gt; file.
I used a suffix of &lt;code&gt;main&lt;/code&gt; for the version, since we&#39;re using that branch of the Eleventy repo.
A better approach would be to use a tagged release instead.&lt;/p&gt;
&lt;pre class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;diff --git a/pkgs/11ty-eleventy.nix b/pkgs/11ty-eleventy.nix
index c6adbfa..240d557 100644
&lt;span class=&quot;token coord&quot;&gt;--- a/pkgs/11ty-eleventy.nix&lt;/span&gt;
&lt;span class=&quot;token coord&quot;&gt;+++ b/pkgs/11ty-eleventy.nix&lt;/span&gt;
&lt;span class=&quot;token coord&quot;&gt;@@ -3,9 +3,9 @@&lt;/span&gt;
&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;pkgs.buildNpmPackage {
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;  # Package name
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;  pname = &quot;eleventy&quot;;
&lt;/span&gt;&lt;span class=&quot;token deleted-sign deleted&quot;&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;  version = &quot;1.0.0&quot;;
&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;  version = (builtins.readFile ./11ty-eleventy.version) + &quot;main&quot;;
&lt;/span&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;  src = src;
&lt;/span&gt;&lt;span class=&quot;token deleted-sign deleted&quot;&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;  npmDepsHash = &quot;sha256-LGdCM1gjt3hRn7BiIlbA4e2HOiQ6e/qkAtWp0Qwn+PE=&quot;;
&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;  npmDepsHash = builtins.readFile ./11ty-eleventy.sha256;
&lt;/span&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;  dontNpmBuild = true;
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;  meta = with pkgs.lib; {
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;    description = &quot;A simpler static site generator&quot;;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And after adding both new files to our git (&lt;code&gt;git add pkgs&lt;/code&gt;), we can try and build &lt;code&gt;eleventy&lt;/code&gt; again with &lt;code&gt;nix build .#eleventy&lt;/code&gt;.
&lt;strong&gt;Success!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Taking a look at the nix store, we can see it matches the version reported by &lt;code&gt;eleventy&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;❯ readlink result
/nix/store/j7kr145ws70in0sbj91yn2ba0m64kmih-eleventy-3.1.2-beta.2-main

❯ result/bin/eleventy &lt;span class=&quot;token parameter variable&quot;&gt;--version&lt;/span&gt;
&lt;span class=&quot;token number&quot;&gt;3.1&lt;/span&gt;.2-beta.2&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One thing to note here is that if you only run &lt;code&gt;just update-eleventy&lt;/code&gt; and don&#39;t run &lt;code&gt;just update&lt;/code&gt; (&lt;code&gt;nix flake update&lt;/code&gt; alias), the hash will at some point go out of sync, since &lt;code&gt;flake.lock&lt;/code&gt; will be frozen in time and so will the &lt;code&gt;src&lt;/code&gt; that we&#39;re passing into &lt;code&gt;pkgs/11ty-eleventy.nix&lt;/code&gt;, but the &lt;code&gt;package-lock.json&lt;/code&gt; that we&#39;re fetching will not be.
You&#39;ll get a hash mismatch and you&#39;ll have to update the &lt;code&gt;flake.lock&lt;/code&gt; file with &lt;code&gt;just update&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;So let&#39;s fix that and make sure both are run at the same time.&lt;/p&gt;
&lt;pre class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;diff --git a/justfile b/justfile
index c7450cb..8de6672 100644
&lt;span class=&quot;token coord&quot;&gt;--- a/justfile&lt;/span&gt;
&lt;span class=&quot;token coord&quot;&gt;+++ b/justfile&lt;/span&gt;
@@ -9,11 +9,11 @@ help:

&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;# Update project dependencies
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;[group(&#39;General&#39;)]
&lt;/span&gt;&lt;span class=&quot;token deleted-sign deleted&quot;&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;update:
&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;update: _update-eleventy
&lt;/span&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;    nix flake update
&lt;/span&gt;
&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;# Update packages sha256 and version
&lt;/span&gt;&lt;span class=&quot;token deleted-sign deleted&quot;&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;update-eleventy:
&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;_update-eleventy:
&lt;/span&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;    #!/bin/bash
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;    pkgs=$(pwd)/pkgs
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;    cd $(mktemp -d)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So now whenever you run &lt;code&gt;just update&lt;/code&gt;, it will also run &lt;code&gt;_update-eleventy&lt;/code&gt;, thus they will never be out of sync.
I&#39;ve also made the &lt;code&gt;update-eleventy&lt;/code&gt; recipe a private one by prepending &lt;code&gt;_&lt;/code&gt; in front of it.&lt;/p&gt;
&lt;p&gt;One last thing before we &lt;code&gt;git commit&lt;/code&gt;, let&#39;s ignore the &lt;code&gt;result&lt;/code&gt; in our &lt;code&gt;.gitignore&lt;/code&gt; file (&lt;code&gt;echo &amp;quot;result&amp;quot; &amp;gt; .gitignore&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Commit: &lt;a href=&quot;https://git.kalu.blue/tech-blog/create-a-static-blog-with-nix/commit/b0d1441acf3da692da6f8334b878ee9a275c648f&quot;&gt;Add @11ty/eleventy package to dev shell&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Phew! That was quite some work we put in. Now let&#39;s build our static site.&lt;/p&gt;
&lt;h2&gt;Build with Eleventy&lt;/h2&gt;
&lt;p&gt;Creating an Eleventy generated static site is pretty straightforward, they really do live up to their slogan.&lt;/p&gt;
&lt;p&gt;Let&#39;s create an &lt;code&gt;index.html&lt;/code&gt; file in our repo root.&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token doctype&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;!&lt;/span&gt;&lt;span class=&quot;token doctype-tag&quot;&gt;DOCTYPE&lt;/span&gt; &lt;span class=&quot;token name&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;html&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;head&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;title&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Hello World&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;title&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;head&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;body&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;h1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Hello, World!&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;h1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;body&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;html&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That should do it.&lt;/p&gt;
&lt;p&gt;Now let&#39;s enter our development shell and run &lt;code&gt;eleventy&lt;/code&gt; to build the static site.&lt;/p&gt;
&lt;p&gt;(I also gitignored &lt;code&gt;_site&lt;/code&gt; at this point &lt;code&gt;echo &amp;quot;_site&amp;quot; &amp;gt;&amp;gt; .gitignore&lt;/code&gt;).&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;❯ eleventy
&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;11ty&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; Writing ./_site/index.html from ./index.html &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;liquid&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;11ty&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; Wrote &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;file&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.04&lt;/span&gt; seconds &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;v3.1.2-beta.2&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Looks good! Let&#39;s also try if live development works.&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;❯ eleventy &lt;span class=&quot;token parameter variable&quot;&gt;--serve&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;11ty&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; Writing ./_site/index.html from ./index.html &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;liquid&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;11ty&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; Wrote &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;file&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.05&lt;/span&gt; seconds &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;v3.1.2-beta.2&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;11ty&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; Watching…
&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;11ty&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; Server at http://localhost:8080/
&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;11ty&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; File changed: ./index.html
&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;11ty&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; Writing ./_site/index.html from ./index.html &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;liquid&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;11ty&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; Wrote &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;file&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.01&lt;/span&gt; seconds &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;v3.1.2-beta.2&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;11ty&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; Watching…&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I edited the &lt;code&gt;index.html&lt;/code&gt; and the page updated by itself, awesome!&lt;/p&gt;
&lt;p&gt;I think we&#39;re done here. &lt;code&gt;git commit&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Commit: &lt;a href=&quot;https://git.kalu.blue/tech-blog/create-a-static-blog-with-nix/commit/3a556717bf434e80fc57aa36aabbef10b86fd6fe&quot;&gt;Add index.html&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Now let&#39;s build the site, with Nix.&lt;/p&gt;
&lt;h2&gt;Build with Nix&lt;/h2&gt;
&lt;p&gt;You might be wondering why build with Nix, didn&#39;t we just build with Eleventy?
Can&#39;t I just take the files in &lt;code&gt;_site&lt;/code&gt;, copy them to some hosting site and serve them?&lt;/p&gt;
&lt;p&gt;Of course.
That would be perfectly fine and I won&#39;t hold it against you if you want to stop here and do that.
Congrats on your new static site!&lt;/p&gt;
&lt;p&gt;But if you have NixOS as your server and you&#39;re like me and dislike manual work, then continue reading and I will show you how Nix wants you to do this.
And I promise it will be easier this time!&lt;/p&gt;
&lt;p&gt;Alrighty!&lt;/p&gt;
&lt;pre class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;diff --git a/flake.nix b/flake.nix
index dd6cc6d..b31a478 100644
&lt;span class=&quot;token coord&quot;&gt;--- a/flake.nix&lt;/span&gt;
&lt;span class=&quot;token coord&quot;&gt;+++ b/flake.nix&lt;/span&gt;
&lt;span class=&quot;token coord&quot;&gt;@@ -24,9 +24,24 @@&lt;/span&gt;
&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;          inherit pkgs;
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;          src = inputs.eleventy-src;
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;        };
&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;        # The static site
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;        site = pkgs.stdenv.mkDerivation {
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;          pname = &quot;my-static-site&quot;;
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;          version = &quot;1.0.0&quot;;
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;          src = ./.;
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;          buildInputs = [ eleventy ];
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;          buildPhase = &quot;eleventy&quot;;
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;          installPhase = &#39;&#39;
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;            mkdir -p $out/
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;            cp -r _site/* $out/
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;          &#39;&#39;;
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;        };
&lt;/span&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;      in
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;      {
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;        packages.eleventy = eleventy;
&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;        packages.site = site;
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;        packages.default = site;
&lt;/span&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;        devShells.default = pkgs.mkShell {
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;          buildInputs = with pkgs; [
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;            just
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There is a couple of things going on here.&lt;/p&gt;
&lt;p&gt;First, we created a new derivation/package by calling &lt;code&gt;mkDerivation&lt;/code&gt; and assigned to a variable called &lt;code&gt;site&lt;/code&gt;.
In Nix-land almost everything is a derivation, you get used to it.&lt;/p&gt;
&lt;p&gt;We set a few things for this derivation:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;pname&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;package name&lt;/li&gt;
&lt;li&gt;you&#39;ll probably set this to the name of your static site&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;version&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;a version number, if anyone uses your package, they might want to know about major version changes, etc&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;src&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;this is what the derivation will consider as its source files, in our case its the current directory (our repo root)&lt;/li&gt;
&lt;li&gt;any changes to the source files (even unrelated to the static site, like &lt;code&gt;.gitignore&lt;/code&gt;) will change the derivation&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;buildInputs&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;these are similar to the build inputs that we have for our development environment&lt;/li&gt;
&lt;li&gt;since in our build and install phases we only use &lt;code&gt;eleventy&lt;/code&gt; and since &lt;code&gt;mkdir&lt;/code&gt; and &lt;code&gt;cp&lt;/code&gt; are already part of the &lt;code&gt;stdenv&lt;/code&gt;, we only have to specify &lt;code&gt;eleventy&lt;/code&gt; here&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;buildPhase&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;Nix builds the package in phases&lt;/li&gt;
&lt;li&gt;in this phase we should produce the built files somehow, in our case that is running the &lt;code&gt;eleventy&lt;/code&gt; binary and producing the &lt;code&gt;_site&lt;/code&gt; folder&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;installPhase&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;in this phase Nix expects us to create the &lt;code&gt;$out&lt;/code&gt; folder and copy the files that we want to be part of the package there&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$out&lt;/code&gt; variable resolves to the Nix store path that the package will occupy&lt;/li&gt;
&lt;li&gt;in our case, we want the files in &lt;code&gt;_site&lt;/code&gt; to be part of the package, so we copy them&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And then we make the package available as an output of our flake.
Let&#39;s take a look.&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;❯ nix flake show
&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
git+file://&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;/create-a-static-blog-with-nix
├───devShells
│   ├───aarch64-darwin
│   │   └───default: development environment &lt;span class=&quot;token string&quot;&gt;&#39;nix-shell&#39;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
└───packages
    ├───aarch64-darwin
    │   ├───default: package &lt;span class=&quot;token string&quot;&gt;&#39;my-static-site-1.0.0&#39;&lt;/span&gt;
    │   ├───eleventy: package &lt;span class=&quot;token string&quot;&gt;&#39;eleventy-3.1.2-beta.2-main&#39;&lt;/span&gt;
    │   └───site: package &lt;span class=&quot;token string&quot;&gt;&#39;my-static-site-1.0.0&#39;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We&#39;ve made it available under two different names &lt;code&gt;site&lt;/code&gt; and &lt;code&gt;default&lt;/code&gt;.
The &lt;code&gt;default&lt;/code&gt; package is a bit special in the sense that you can build it simply with &lt;code&gt;nix build&lt;/code&gt;.
Let&#39;s try it!&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;❯ nix build
❯ readlink result
/nix/store/25m04s0xmfz2cwlbj1nq7iw9fas9h3jy-my-static-site-1.0.0
❯ &lt;span class=&quot;token function&quot;&gt;cat&lt;/span&gt; result/index.html
&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;DOCTYPE html&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;html&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;head&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;title&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;Hello World&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;/title&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;/head&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And there you have it!
You&#39;ve built the site with Nix!
That wasn&#39;t so bad, was it?&lt;/p&gt;
&lt;p&gt;Let&#39;s &lt;code&gt;git commit&lt;/code&gt; and continue on to deploying the site.&lt;/p&gt;
&lt;p&gt;Commit: &lt;a href=&quot;https://git.kalu.blue/tech-blog/create-a-static-blog-with-nix/commit/09a424d95636112c8be9ec89b0af380b43574bdd&quot;&gt;Build the site with nix&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Deploy!&lt;/h2&gt;
&lt;p&gt;Now we can deploy it! Woo!&lt;/p&gt;
&lt;p&gt;So what we&#39;ll do is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;create a new NixOS configuration that can run as a VM&lt;/li&gt;
&lt;li&gt;in the VM, start a &lt;a href=&quot;https://caddyserver.com/&quot;&gt;Caddy&lt;/a&gt; web server&lt;/li&gt;
&lt;li&gt;configure Caddy to serve our static site&lt;/li&gt;
&lt;li&gt;add a test user to the VM, so we can log in and test that everything works&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In this section I will just present the end result, as there are many ways to achieve the same thing and a lot of it will depend on your existing/desired setup.&lt;/p&gt;
&lt;p&gt;Before we proceed, we&#39;ll need to push the static site repository to a remote.
Instructions on how to do this will again vary depending on the forge you&#39;re using.
I use a self-hosted git forge (Forgejo), so I:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;created an empty repository there&lt;/li&gt;
&lt;li&gt;&lt;code&gt;git remote add origin git@git.kalu.blue:tech-blog/create-a-static-blog-with-nix.git&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;git push -u origin main&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For convenience I created a sub-directory &lt;code&gt;nixos-configuration&lt;/code&gt; in our static site repository to contain the NixOS configuration, but you should create a new repository to contain the configuration for your NixOS server.&lt;/p&gt;
&lt;p&gt;Here are the files that I added to that directory:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;❯ tree nixos-configuration
nixos-configuration
├── flake.lock
├── flake.nix
├── justfile
└── my-static-site.nix

&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; directory, &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt; files&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here is the &lt;code&gt;flake.nix&lt;/code&gt;. &lt;code&gt;flake.lock&lt;/code&gt; will be created for you once you run some nix command (or if you want to mirror my example 100%, you can copy it from the post repository).&lt;/p&gt;
&lt;p&gt;I created a separate module file for our static site, just to demonstrate how to pass the flake &lt;code&gt;inputs&lt;/code&gt; to modules as an argument (see &lt;code&gt;specialArgs&lt;/code&gt; below).&lt;/p&gt;
&lt;p&gt;There is also one (inline) module that handles things needed to run this configuration as a VM.
Keep in mind the configuration as-is only works on &lt;code&gt;x86_64-linux&lt;/code&gt; systems.&lt;/p&gt;
&lt;pre class=&quot;language-nix&quot;&gt;&lt;code class=&quot;language-nix&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  inputs &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    nixpkgs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;github:NixOS/nixpkgs/nixpkgs-unstable&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    our&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;site &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;git+ssh://git@git.kalu.blue/tech-blog/create-a-static-blog-with-nix.git?ref=main&amp;amp;shallow=1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      inputs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;nixpkgs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;follows &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;nixpkgs&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# Will use the same Nixpkgs as the NixOS system&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  outputs &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; self&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; nixpkgs&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;@&lt;/span&gt;inputs&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      nixosConfigurations&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;my&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;server&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; nixpkgs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lib&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;nixosSystem &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        specialArgs &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;token comment&quot;&gt;# These will be passed as arguments to the modules&lt;/span&gt;
          &lt;span class=&quot;token keyword&quot;&gt;inherit&lt;/span&gt; inputs&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        modules &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
          &lt;span class=&quot;token comment&quot;&gt;# Essential for test VM&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            virtualisation&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;vmVariant &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
              virtualisation &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                memorySize &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2048&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                cores &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token comment&quot;&gt;# Disable graphics to avoid gtk error&lt;/span&gt;
                graphics &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
              &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

            &lt;span class=&quot;token comment&quot;&gt;# Create a user, so we can login and test&lt;/span&gt;
            users&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;users&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;test &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
              isNormalUser &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
              &lt;span class=&quot;token comment&quot;&gt;# Never use this, use `hashedPassword` instead&lt;/span&gt;
              initialPassword &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; nixpkgs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lib&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;mkForce &lt;span class=&quot;token string&quot;&gt;&quot;123123&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
              &lt;span class=&quot;token comment&quot;&gt;# So we can use `sudo` mainly for `sudo shutdown now` and to be able to debug `caddy`&lt;/span&gt;
              group &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;wheel&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
          &lt;span class=&quot;token comment&quot;&gt;# Some configuration&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            nixpkgs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;hostPlatform &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;x86_64-linux&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            system&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;stateVersion &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;25.11&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
          &lt;span class=&quot;token url&quot;&gt;./my-static-site.nix&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then the module &lt;code&gt;my-static-site.nix&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;We have to enable the &lt;code&gt;caddy&lt;/code&gt; service and configure one virtual host (&lt;code&gt;example.com&lt;/code&gt;) with some configuration that will set it to serve files from our static site package.
We also add some gzip compression.&lt;/p&gt;
&lt;p&gt;As a bonus, I also included a &lt;code&gt;curl-site&lt;/code&gt; shell alias, which you can use in the VM to view our static site main page.
I think it also nicely demonstrates how simple it is to use one variable to configure two vastly different systems (web server and shell aliases).&lt;/p&gt;
&lt;pre class=&quot;language-nix&quot;&gt;&lt;code class=&quot;language-nix&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; inputs&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; pkgs&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt;
  site&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;example.com&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  services&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;caddy &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    enable &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# Needs `http://` prefix so that it does not try to request TLS certificates and redirect to 443&lt;/span&gt;
    virtualHosts&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;http://&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token antiquotation important&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;site&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;extraConfig &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;&#39;
      file_server
      root * &lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token antiquotation important&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;inputs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;our&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;site&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;packages&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token antiquotation important&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;pkgs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;system&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;default&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
      encode gzip
    &#39;&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  environment&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;shellAliases &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# Test our site&lt;/span&gt;
    curl&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;site &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;curl -H &#92;&quot;Host: &lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token antiquotation important&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;site&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&#92;&quot; localhost&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This configuration will start Caddy and set it serve our static site.
It will only be available on &lt;code&gt;localhost&lt;/code&gt;, since we have not opened any firewall ports (e.g. &lt;code&gt;80&lt;/code&gt; and &lt;code&gt;443&lt;/code&gt;).
But it was enough for me to start the VM and verify that everything works as intended.&lt;/p&gt;
&lt;p&gt;I&#39;ve also added a &lt;code&gt;just vm&lt;/code&gt; recipe (runs &lt;code&gt;nix run &amp;quot;.#nixosConfigurations.my-server-name.config.system.build.vm&amp;quot;&lt;/code&gt;).
I think its really neat how simple it is to start a full-blown virtual machine with Nix.&lt;/p&gt;
&lt;p&gt;Anyway enter the &lt;code&gt;nixos-configuration&lt;/code&gt; folder and run &lt;code&gt;just vm&lt;/code&gt;.
It will build and start the virtual machine and after a while a login screen will be presented.
We have configured a user &lt;code&gt;test&lt;/code&gt; with a plain-text password &lt;code&gt;123123&lt;/code&gt;.
Use that to log in.&lt;/p&gt;
&lt;p&gt;Now we can inspect the system:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;systemctl status caddy&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;we can see its running&lt;/li&gt;
&lt;li&gt;its using config located at &lt;code&gt;/etc/caddy/caddy_config&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cat /etc/caddy/caddy_config&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;taking a peek at the config file reveals the root path that our web server is serving file from&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cat /nix/store/cr5wkgbijfhhp0dy1qm2k30wx8j83bzh-my-static-site-1.0.0/index.html&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;yep, this is the &lt;code&gt;index.html&lt;/code&gt; that we wrote&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;curl-site&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;yep, this is the &lt;code&gt;index.html&lt;/code&gt; file we expected to be served from our web server&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;❯ &lt;span class=&quot;token builtin class-name&quot;&gt;cd&lt;/span&gt; nixos-configuration
❯ just vm
nix run &lt;span class=&quot;token string&quot;&gt;&quot;.#nixosConfigurations.my-server-name.config.system.build.vm&quot;&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&amp;lt;&amp;lt;&lt;/span&gt; Welcome to NixOS &lt;span class=&quot;token number&quot;&gt;25.11&lt;/span&gt;.20250618.5395fb3 &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;x86_64&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; - ttyS0 &lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;

Run &lt;span class=&quot;token string&quot;&gt;&#39;nixos-help&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; the NixOS manual.

nixos login: &lt;span class=&quot;token builtin class-name&quot;&gt;test&lt;/span&gt;
Password:

&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;test@nixos:~&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;$ systemctl status caddy
● caddy.service - Caddy
     Loaded: loaded &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;/etc/systemd/system/caddy.service&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; enabled&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; preset: ignored&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    Drop-In: /nix/store/bg47546cl8k10w06wg3i002frn2c422f-system-units/caddy.service.d
             └─overrides.conf
     Active: active &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;running&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; since Sun &lt;span class=&quot;token number&quot;&gt;2025&lt;/span&gt;-06-22 &lt;span class=&quot;token number&quot;&gt;18&lt;/span&gt;:06:27 UTC&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; 1min 31s ago
 Invocation: 82109b58487c4df48a9d15ba2a958d7e
       Docs: https://caddyserver.com/docs/
   Main PID: &lt;span class=&quot;token number&quot;&gt;873&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;caddy&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
         IP: 0B in, 0B out
         IO: 40K read, 8K written
      Tasks: &lt;span class=&quot;token number&quot;&gt;8&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;limit: &lt;span class=&quot;token number&quot;&gt;2343&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
     Memory: &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt;.6M &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;peak: 33M&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        CPU: 992ms
     CGroup: /system.slice/caddy.service
             └─873 /nix/store/9820hkh6vc96hjmbqpi9xzfjdrgsw17i-caddy-2.10.0/bin/caddy run &lt;span class=&quot;token parameter variable&quot;&gt;--config&lt;/span&gt; /etc/caddy/caddy_config &lt;span class=&quot;token parameter variable&quot;&gt;--adapter&lt;/span&gt; ca&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;

Jun &lt;span class=&quot;token number&quot;&gt;22&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;18&lt;/span&gt;:06:25 nixos systemd&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;: Starting Caddy&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.
Jun &lt;span class=&quot;token number&quot;&gt;22&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;18&lt;/span&gt;:06:27 nixos caddy&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;873&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;: &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;level&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;info&quot;&lt;/span&gt;,&lt;span class=&quot;token string&quot;&gt;&quot;ts&quot;&lt;/span&gt;:1750615587.5026293,&lt;span class=&quot;token string&quot;&gt;&quot;msg&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;maxprocs: Leaving GOMAXPROCS=2: CPU quota undefined&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
Jun &lt;span class=&quot;token number&quot;&gt;22&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;18&lt;/span&gt;:06:27 nixos caddy&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;873&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;: &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;level&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;info&quot;&lt;/span&gt;,&lt;span class=&quot;token string&quot;&gt;&quot;ts&quot;&lt;/span&gt;:1750615587.5131006,&lt;span class=&quot;token string&quot;&gt;&quot;msg&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;GOMEMLIMIT is updated&quot;&lt;/span&gt;,&lt;span class=&quot;token string&quot;&gt;&quot;package&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;github.com/KimMachin&gt;
Jun 22 18:06:27 nixos caddy[873]: {&quot;&lt;/span&gt;level&lt;span class=&quot;token string&quot;&gt;&quot;:&quot;&lt;/span&gt;info&lt;span class=&quot;token string&quot;&gt;&quot;,&quot;&lt;/span&gt;ts&lt;span class=&quot;token string&quot;&gt;&quot;:1750615587.5194187,&quot;&lt;/span&gt;msg&lt;span class=&quot;token string&quot;&gt;&quot;:&quot;&lt;/span&gt;using config from &lt;span class=&quot;token function&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;,&quot;&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;:&quot;&lt;/span&gt;/etc/caddy/caddy_confi&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
Jun &lt;span class=&quot;token number&quot;&gt;22&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;18&lt;/span&gt;:06:27 nixos caddy&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;873&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;: &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;level&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;info&quot;&lt;/span&gt;,&lt;span class=&quot;token string&quot;&gt;&quot;ts&quot;&lt;/span&gt;:1750615587.5768142,&lt;span class=&quot;token string&quot;&gt;&quot;msg&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;adapted config to JSON&quot;&lt;/span&gt;,&lt;span class=&quot;token string&quot;&gt;&quot;adapter&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;caddyfile&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
Jun &lt;span class=&quot;token number&quot;&gt;22&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;18&lt;/span&gt;:06:27 nixos caddy&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;873&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;: &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;level&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;info&quot;&lt;/span&gt;,&lt;span class=&quot;token string&quot;&gt;&quot;ts&quot;&lt;/span&gt;:1750615587.6890154,&lt;span class=&quot;token string&quot;&gt;&quot;msg&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;serving initial configuration&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
Jun &lt;span class=&quot;token number&quot;&gt;22&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;18&lt;/span&gt;:06:27 nixos systemd&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;: Started Caddy.

&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;test@nixos:~&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;$ &lt;span class=&quot;token function&quot;&gt;cat&lt;/span&gt; /etc/caddy/caddy_config
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        log &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                level ERROR
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

http://example.com &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        log &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                output &lt;span class=&quot;token function&quot;&gt;file&lt;/span&gt; /var/log/caddy/access-http:__example.com.log
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

        file_server
        root * /nix/store/cr5wkgbijfhhp0dy1qm2k30wx8j83bzh-my-static-site-1.0.0
        encode &lt;span class=&quot;token function&quot;&gt;gzip&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;test@nixos:~&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;$ &lt;span class=&quot;token function&quot;&gt;cat&lt;/span&gt; /nix/store/cr5wkgbijfhhp0dy1qm2k30wx8j83bzh-my-static-site-1.0.0/index.html
&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;DOCTYPE html&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;html&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;head&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;title&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;Hello World&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;/title&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;/head&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;body&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;h&lt;span class=&quot;token operator&quot;&gt;&lt;span class=&quot;token file-descriptor important&quot;&gt;1&lt;/span&gt;&gt;&lt;/span&gt;Hello, World&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;/h&lt;span class=&quot;token operator&quot;&gt;&lt;span class=&quot;token file-descriptor important&quot;&gt;1&lt;/span&gt;&gt;&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;p&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;Eleventy rocks&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;/p&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;/body&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;/html&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;test@nixos:~&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;$ curl-site
&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;DOCTYPE html&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;html&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;head&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;title&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;Hello World&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;/title&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;/head&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;body&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;h&lt;span class=&quot;token operator&quot;&gt;&lt;span class=&quot;token file-descriptor important&quot;&gt;1&lt;/span&gt;&gt;&lt;/span&gt;Hello, World&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;/h&lt;span class=&quot;token operator&quot;&gt;&lt;span class=&quot;token file-descriptor important&quot;&gt;1&lt;/span&gt;&gt;&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;p&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;Eleventy rocks&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;/p&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;/body&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;/html&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;test@nixos:~&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;shutdown&lt;/span&gt; now&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Everything is working as expected. Hooray!&lt;/p&gt;
&lt;p&gt;Commit: &lt;a href=&quot;https://git.kalu.blue/tech-blog/create-a-static-blog-with-nix/commit/f2f8efdc1d57ede1c93edf85378c3bfc2476aa81&quot;&gt;Add an example nixos config using this site&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;We&#39;ve successfully created a fully reproducible static blog setup using Nix and Eleventy. What started as avoiding a simple Node.js installation turned into a powerful development workflow that demonstrates the strength of Nix&#39;s approach to package management and deployment.&lt;/p&gt;
&lt;p&gt;While this approach required more upfront investment than a traditional Node.js setup, we now have a system that &amp;quot;just works&amp;quot; across different machines and time. No more &amp;quot;works on my machine&amp;quot; problems, no dependency conflicts, and deployments are as simple as pointing to a git commit.&lt;/p&gt;
&lt;h2&gt;Next steps&lt;/h2&gt;
&lt;p&gt;Here are a few things you might want to do next:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;As you expand the site, you should move the static site source files to the folder &lt;code&gt;src&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Instead of using the main branch of Eleventy, you should switch to a tagged release.
For me, living on the edge feels fine, for you it might not.&lt;/li&gt;
&lt;li&gt;You&#39;ll probably want to expose the site to the world wide web.
That would mean at the very least opening the ports &lt;code&gt;80&lt;/code&gt; and &lt;code&gt;443&lt;/code&gt;, setting up DNS records to point to the machine and getting a valid TLS certificate (remove the &lt;code&gt;http://&lt;/code&gt; prefix from the module).
If you&#39;re running the machine at home, you might also need to do some port forwarding.&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry>
    <title>Hello world!</title>
    <link href="https://luka.korosec.cc/posts/2025/06/hello-world/"/>
    <published>2025-06-18T00:00:00.000Z</published>
    <updated>2025-06-19T00:00:00.000Z</updated>
    <id>https://luka.korosec.cc/posts/2025/06/hello-world/</id>
    <content type="html">&lt;p&gt;So this is the very first post on this blog. Woo!&lt;/p&gt;
&lt;p&gt;I&#39;ve been thinking about starting a blog for a while now, there just wasn&#39;t ever a right time. Anyway, here we are, &lt;strong&gt;let&#39;s do this!&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;What I&#39;ll write about&lt;/h2&gt;
&lt;p&gt;I always have a few projects that I&#39;d like to start working on and a few that are in progress.&lt;/p&gt;
&lt;p&gt;Right now I&#39;m waiting to get some time to start working on an eInk-display-based image frame and weather dashboard.
I&#39;ve been eyeing the &lt;a href=&quot;https://shop.pimoroni.com/products/inky-impression-7-3?variant=55186435277179&quot;&gt;Inky Impression 13.3&amp;quot;&lt;/a&gt; one.
Still haven&#39;t bought it since I also have a Guitar Hero guitar at home that is waiting for me to hook up and play with, and that one already cost me ~100€.
So I&#39;m a bit hesitant when it comes to new purchases right now.&lt;/p&gt;
&lt;p&gt;I also have a &lt;a href=&quot;https://www.dell.com/en-us/shop/wyse-endpoints-and-software/wyse-5070-thin-client/spd/wyse-5070-thin-client&quot;&gt;Dell Wyse 5070 Thin Client&lt;/a&gt; lying around.
I installed Home Assistant on it and even that wasn&#39;t as straightforward as I thought it was going to be.
Apparently you have to live-boot Ubuntu from a USB stick and then do the installation to the main drive from there.
I didn&#39;t RTFM well enough on the first run and ended up with it installed to the USB stick.
Took a while to figure out why it wasn&#39;t booting properly once I took the USB stick out.&lt;/p&gt;
&lt;p&gt;Installing Home Assistant was one thing, then I wanted to set up VLAN in my home network.
I thought it was going to be a piece of cake and it probably would&#39;ve been if I had just one router to set up, but I have an OpenWRT set up as a dumb AP connected to an OPNsense router acting as the main router, so I have to set up VLANs on both ends...
I&#39;ve had some luck, but it&#39;s not easy to play around and test configuration when my wife watches TV on the same network.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Anyway&lt;/em&gt;, what I&#39;m trying to say is that I have a lot of projects going on and I&#39;d like to share them with the world.
Maybe you&#39;ll learn something by reading about my pain, or maybe, even better, I&#39;ll learn something when you write to me about a better way of doing a certain thing.&lt;/p&gt;
&lt;p&gt;A few examples of things I&#39;d like to write about at some point:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;my homelab, what I host there and how I built it&lt;/li&gt;
&lt;li&gt;terminal and shell, not enough people know about CTRL + R for example&lt;/li&gt;
&lt;li&gt;Nix/NixOS/Nixpkgs/home-manager tips and tricks&lt;/li&gt;
&lt;li&gt;home automation projects, there is always something extra to tinker with&lt;/li&gt;
&lt;li&gt;soldering projects, like the air quality sensor I assembled&lt;/li&gt;
&lt;li&gt;Emacs and how I configure it&lt;/li&gt;
&lt;li&gt;my home network and DNS setup, how to block ads network-wide&lt;/li&gt;
&lt;li&gt;how to fix gadgets when they break - like the remote scroll wheel&lt;/li&gt;
&lt;li&gt;and so much more...&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;First post down, hopefully a lot more to follow.
&lt;strong&gt;Here&#39;s to a healthy posting schedule!&lt;/strong&gt;&lt;/p&gt;
</content>
  </entry>
</feed>
