Files
blog.html/posts/cool-nix-shit/index.html
2026-05-20 17:03:37 +01:00

391 lines
20 KiB
HTML

<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="generator" content="Norgolith" />
<meta name="description" content="Nix is surprisingly versatile" />
<meta name="author" content="Adumh00man" />
<meta name="keywords" content="nix, linux, blog" />
<meta property="og:title" content="Cool Shit You Can Do With Nix - Voidarc" />
<meta property="og:type" content="website" />
<meta property="og:url" content="https:&#x2F;&#x2F;blog.voidarc.co.uk&#x2F;posts&#x2F;cool-nix-shit&#x2F;" />
<meta property="og:description" content="Nix is surprisingly versatile" />
<meta property="og:site_name" content="voidarc" />
<meta property="og:locale" content="en-US" />
<link rel="canonical" href="https:&#x2F;&#x2F;blog.voidarc.co.uk&#x2F;posts&#x2F;cool-nix-shit&#x2F;" />
<meta name="robots" content="index, follow" />
<link rel="stylesheet" href="https://blog.voidarc.co.uk/assets/css/prism-sweetie.min.css" />
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js"></script>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/autoloader/prism-autoloader.min.js"></script>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/line-numbers/prism-line-numbers.min.js"></script>
<script defer src="https://cdn.jsdelivr.net/npm/@alpinejs/collapse@3.x.x/dist/cdn.min.js"></script>
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
<script>
document.addEventListener("alpine:init", () => {
Alpine.data("menu", () => ({
currentPage: window.location.pathname,
openMobile: false,
toggleMobile() {
this.openMobile = !this.openMobile;
}
}));
Alpine.data("toc", () => ({
open: false,
toggle() {
this.open = !this.open;
document.querySelector("#toc-toggle-icon").classList.toggle("rotate-90");
}
}));
Alpine.data("theme", () => ({
// Defaults to dark theme
current: "dark",
init() {
const storedTheme = localStorage.getItem("theme");
if (storedTheme === "dark") {
this.current = "dark";
} else if (storedTheme === "light") {
this.current = "light";
} else if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
this.current = "dark";
}
localStorage.setItem("theme", this.current);
if (this.current === "dark") {
document.documentElement.classList.add("dark");
}
},
toggle() {
this.current = this.current === "dark" ? "light" : "dark";
document.documentElement.classList.toggle("dark", this.current === "dark");
localStorage.setItem("theme", this.current);
}
}));
});
</script>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/mermaid/11.9.0/mermaid.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/tabler-icons/3.28.1/tabler-icons.min.css">
<link rel="stylesheet" href="https://blog.voidarc.co.uk/assets/css/styles.min.css" />
<link rel="icon" href=&#x2F;assets&#x2F;norgolith.svg />
<title>Cool Shit You Can Do With Nix - Voidarc</title>
</head>
<body>
<div class="transition-colors duration-150 ease-linear">
<header class="relative shadow-sm">
<nav x-data="menu" class="container mx-auto px-4 md:px-0 font-mono">
<div class="flex items-center justify-between h-16">
<!-- Logo and dark mode -->
<div x-data="theme" class="flex items-center shrink-0 space-x-2">
<a href="https://blog.voidarc.co.uk/" class="flex items-center space-x-2">
<!-- <img src="https:&#x2F;&#x2F;blog.voidarc.co.uk/assets/norgolith.svg" alt="Norgolith Logo" class="h-8 w-8"> -->
<span class="text-lg md:text-xl font-bold text-magenta">Voidarc</span>
</a>
<button
@click="toggle()"
type="button"
class="rounded-lg flex items-center"
:aria-label="current === 'dark' ? 'Switch to light mode' : 'Switch to dark mode'"
>
<i
:class="current === 'dark' ? 'ti-sun hover:text-yellow' : 'ti-moon hover:text-blue'"
class="ti text-xl md:text-2xl text-grey transition-colors duration-200 ease-in-out"
></i>
</button>
</div>
<!-- Desktop Menu -->
<div class="hidden md:flex md:items-center md:space-x-8">
<a
:class="{ 'text-magenta!': currentPage.startsWith('&#x2F;posts') }"
class="text-text-alt hover:text-blue px-3 py-2 rounded-md text-sm font-medium"
href="https://blog.voidarc.co.uk/posts"
>Posts</a>
<a
:class="{ 'text-magenta!': currentPage.startsWith('&#x2F;categories') }"
class="text-text-alt hover:text-blue px-3 py-2 rounded-md text-sm font-medium"
href="https://blog.voidarc.co.uk/categories"
>Tags</a>
</div>
<!-- Mobile Menu Button -->
<div class="md:hidden">
<button @click="toggleMobile" type="button" class="inline-flex items-center justify-center p-2 rounded-md text-dark-grey hover:text-text-alt focus:outline-none" aria-controls="mobile-menu" aria-expanded="false">
<span class="sr-only">Open main menu</span>
<!-- Hamburger Icon -->
<svg x-show="!openMobile" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"/>
</svg>
<!-- Close Icon -->
<svg x-show="openMobile" class="h-5 w-5 text-red" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
</svg>
</button>
</div>
</div>
<!-- Mobile Menu -->
<div
x-show="openMobile"
x-transition:enter="transition ease-out duration-300"
x-transition:enter-start="opacity-0"
x-transition:enter-end="opacity-100"
x-transition:leave="transition ease-in duration-300"
x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0"
@click.outside="openMobile = false"
class="md:hidden"
id="mobile-menu"
>
<div class="flex flex-row items-center justify-between pt-2 pb-4">
<a
:class="{ 'text-blue!': currentPage.startsWith('&#x2F;posts') }"
class="text-text-alt hover:text-blue px-3 py-2 rounded-md text-sm font-medium"
href="https://blog.voidarc.co.uk/posts"
>Posts</a>
<a
:class="{ 'text-blue!': currentPage.startsWith('&#x2F;categories') }"
class="text-text-alt hover:text-blue px-3 py-2 rounded-md text-sm font-medium"
href="https://blog.voidarc.co.uk/categories"
>Tags</a>
</div>
</div>
</nav>
</header>
<main class="container mx-auto min-h-screen pt-8 px-4 md:px-0">
<div>
<h1>Cool shit you can do with nix</h1>
</div>
<div class="flex flex-col md:flex-row">
<div class="flex flex-col md:flex-row flex-1 justify-between">
<div class="flex flex-col space-y-2">
<span class="text-grey text-sm tabular-nums text-opacity-70 dark:text-opacity-100 space-x-1">
<time datetime="2026-03-28T19:03:27+00:00">March 28, 2026</time>
</span>
</div>
<div class="flex flex-wrap items-center max-w-[50%]">
<span class="font-semibold mr-px">Tags:&nbsp;</span>
<a
class="w-fit bg-grey hover:bg-magenta text-base hover:text-base! no-underline! hover:no-underline! text-sm mr-2 last:mr-0 px-1 rounded-sm"
href="https://blog.voidarc.co.uk/categories/nix"
>
Nix
</a>
<a
class="w-fit bg-grey hover:bg-magenta text-base hover:text-base! no-underline! hover:no-underline! text-sm mr-2 last:mr-0 px-1 rounded-sm"
href="https://blog.voidarc.co.uk/categories/linux"
>
Linux
</a>
<a
class="w-fit bg-grey hover:bg-magenta text-base hover:text-base! no-underline! hover:no-underline! text-sm mr-2 last:mr-0 px-1 rounded-sm"
href="https://blog.voidarc.co.uk/categories/blog"
>
Blog
</a>
</div>
</div>
</div>
<div x-data="toc" class="mt-4">
<button @click="toggle()" type="button" class="inline-flex items-center">
<span class="sr-only">Close Table of Contents</span>
<h3 class="mt-0! mr-1">
Table of Contents
</h3>
<span
id="toc-toggle-icon"
class="transition-all duration-300 ease-in-out text-2xl hover:text-blue ti ti-arrow-badge-right-filled"
></span>
</button>
<nav
x-transition:enter="transition ease-out duration-300"
x-transition:enter-start="opacity-0"
x-transition:enter-end="opacity-100"
x-transition:leave="transition ease-in duration-300"
x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0"
x-show="open"
class="toc"
>
<ol><li><a href="#Nix-is-hard">Nix is hard</a><ol><li><a href="#Starting-out">Starting out</a><ol><li><a href="#Flakes">Flakes</a><ol><li><a href="#Inputs">Inputs</a></li><li><a href="#Outputs">Outputs</a></li></ol></li></ol></li></ol></li></ol>
</nav>
</div>
<div id="content" class="mt-12 break-keep pt-10 border-t border-t-base-alt dark:border-t-[#39394b]">
<h1 id="Nix-is-hard" >Nix is hard</h1> <p >Nix is supposed to be a solve-all to the "it runs on my machine" problem. And it is. If it didn't do that, then there would be no point in it existing. But, the syntax is a pain in the ass, and the documentation is half baked at best. For reference, everything I talk about here is coming from my <a href="https://git.voidarc.co.uk/voidarc/nixos" >nixos config repo</a>, so if you need some context, then everything in there should be functional enough, at least to serve as some kind of guide.</p><blockquote class="note" >I am not a professional. If you want to get your info from someone that knows what they're talking about, look on youtube, idiot. </blockquote><h2 id="Starting-out" >Starting out</h2> <p >This whole guide is coming from the perspective of someone who has never used nix outside of nixos. I decided to switch one day and had to learn as I went (<a href="https://blog.voidarc.co.uk/posts/nix" ><em>If only there was a post about that</em></a>). Of course, at the beginning, my config was very simple. I basically tried to replicate my arch config, which I already managed the dotfiles for through git. I managed, but my main issue was that my nixos config had to be edited by the root user, because it was in the <code>/etc/nixos</code> directory. This is the default location, which makes sense, because most linux systems need to be oriented toward having multiple users, and you don't want every person on the system to be able to change the underlying programs. However, this is irritating, because you have to manage a git repo as the root user, which <strong>sucks</strong>. There are some workarounds, mostly involving symlinks, that, if broken, will brick your system, so they aren't recommended. The real way you're supposed to do it is with flakes.</p><h3 id="Flakes" >Flakes</h3> <p >Flakes aren't real. They can't hurt you. But, they can let you manage your config outside of the <code>/etc</code> directory.</p><pre ><code class="language-nix">{
description = &quot;My example flake&quot;;
inputs = {
nixpkgs.url = &quot;github:nixos&#x2F;nixpkgs?ref=nixos-unstable&quot;;
};
outputs = { self, nixpkgs }: {
packages.x86_64-linux = {
default = self.packages.x86_64-linux.hello;
hello = nixpkgs.legacyPackages.x86_64-linux.hello;
};
};
}
</code></pre><p >This is the default flake that you get upon running <code>nix flake init</code> in a project. What you'll immediately notice is that there are only 2 sections: inputs and outputs (there are more but idk how to use them, except the description). These are not as explanatory as you might first expect, so I tend to think about it like this:</p><ul><li >Inputs </li> <ul><li >External links / projects </li><li >Immutable (for the most part) </li></ul><li >Outputs </li> <ul><li >Generated / built code </li><li >Changable </li><li >Where you would put anything of interest </li></ul></ul><p >I am aware this clears up nothing, so let me explain slightly further.</p><h4 id="Inputs" >Inputs</h4> <p >Without inputs, you basically can't build anything outside of a hello world with no dependancies. They are the main reason that flakes were made in the first place. This is because every input is stored in the lock file as a hash of the git repo it is a part of. I went more in depth in the article I linked to at the start, but for now I'll give you some examples.</p><p >The main input that you would need to know about is the <code>nixpkgs</code> input, which provides a fixed version of the nixpkgs git repo that you can use to access whatever packages you need. This can be unstable, which, as the name suggests, is newer and less supported, a fixed hash, like <code>nixpkgs.url = "github:nixos/nixpkgs?ref=f5da6d7f24b8565882487ce7f45c2a7d9d8afdeb";</code>, or just a branch, like the one in my config: <code>nixpkgs.url = "github:nixos/nixpkgs/nixos-25.11";</code>. All have different use cases, which I may or may not get to.</p><p >Another common form of input is a repo input. Nixpkgs uses this kind, so it follows the same rules.</p><pre ><code class="language-nix">omnisearch = {
url = &quot;git+https:&#x2F;&#x2F;git.voidarc.co.uk&#x2F;voidarc&#x2F;omnisearch&quot;;
inputs.nixpkgs.follows = &quot;nixpkgs&quot;;
};
</code></pre><p >The url is slightly different because the repo isn't hosted on github, but the <code>github:</code> from before is just a shortcut that expands into <code>git+https://github.com/</code> when you build the flake. Notice that there is another line in the input. This tells nix to use the version of nixpkgs that the whole system is running on, the one in this repo's flake.lock, instead of the one in the called flake's repo. This saves storage and prevents you from installing needless copies of apps that are basically the same. Using this syntax is best practice unless there is a specific version of a dependancy that the app needs.</p><p >The last kind of input you're probably going to see is the local input. These come in the form of local paths, relative to the current flake. These are used when you have submodules that aren't large enough to deserve a whole repo, or modules that you need to reload on the fly.</p><pre ><code class="language-nix">chataigne.url = &quot;.&#x2F;modules&#x2F;chataigne&quot;;
nvim-wrapped = {
url = &quot;git+file:&#x2F;&#x2F;&#x2F;home&#x2F;user01&#x2F;.dotfiles&#x2F;.config&#x2F;nvim&quot;;
inputs.nixpkgs.follows = &quot;nixpkgs&quot;;
};
</code></pre><p >The first input here is just a relative path, with a specific set of dependancies, hence it doesn't follow nixpkgs like the other. The second input is still a local module, but it isn't relative. The flake is located in a path above the system flake, that I want to call it from, so I needed to use a workaround and use the repo input type with a <code>file://</code> prefix. This lets me test my nvim config without pushing to git beforehand, which is useful when I've only added one dependancy and don't want to make a whole commit for 3 lines of code. (don't worry, we'll get back to why it's called nvim-wrapped later)</p><h4 id="Outputs" >Outputs</h4> <p >Outputs, as I said, are what's produced when the flake is run. There are different types of these, too, so let's start simple.</p><p >Before you can even think about compiling an app, you have to allow the outputs access to the inputs. This can be seen as the function call at the beginning of the output section:</p><pre ><code class="language-nix">outputs =
{ self, nixpkgs }:
{
# Whatever app
};
</code></pre><p >However, this only allows the outputs to access nixpkgs, which is ok for the first type of output: The humble binary.</p><p >Binaries are defined by system. This allows you to have different build instructions for x64 and arm systems in the same flake, but with modern programming languages that isn't much of an issue. As an example, this flake output would provide fastfetch</p><pre ><code class="language-nix">packages.${system}.default = pkgs.fastfetch;
</code></pre><p >Simple, right? the <code>nixpkgs</code> input provides <code>pkgs</code> as a callable parameter, so that would be all you need. This is on display with the example flake from earlier, providing the <code>hello</code> package for x86_64-linux. This can be added to, by providing a devshell with this package, although that requires slightly more boilerplate.</p><pre ><code class="language-nix">devShells.${system}.default = pkgs.mkShell {
# Packages you want available in your shell
buildInputs = [
pkgs.fastfetch
];
# Environmental variables or shell hooks that you want to run
# when the shell starts
shellHook = &#x27;&#x27;
export SOME_VAR=foo
echo &quot;Shell started&quot;
&#x27;&#x27;;
};
</code></pre><p >Of course, a whole shell for fastfetch is a solved issue, with <code>nix shell</code> and everything, but for explanatory purposes that's all you would need to read flakes, and understand what you would need to call in order to get the right package from a repo. There are obviously building tools, but I have no idea how those work. This is supposed to be from a fully nixos user perspective, and I don't tend to write too much code.</p><p >Finally, there's the main thing that you would need to manage your system, <code>pkgs.lib.nixosSystem</code>. Of course, you could use the function directly, but that's boring, so I made a function to make a system instead:</p><pre ><code class="language-nix"> outputs =
{
self,
nixpkgs,
...
}@inputs:
let
mkSystem =
extraModules:
nixpkgs.lib.nixosSystem {
inherit system;
specialArgs = { inherit inputs; };
modules = [
common
hardwareConfig
]
++ extraModules;
};
in
{
nixosConfigurations = {
mobile02 = mkSystem [ .&#x2F;configs&#x2F;configuration-laptop.nix ];
};
</code></pre><p >There are a few things to note here. This is from my system flake, I've just omitted my pc config, because you only really need one to see what's going on. Starting from the top, there is an extra <code>@inputs</code> after the output function. This allows for the <code>inherit inputs</code> inside the mkSystem function, so that you can dynamically access any input from whatever module you're calling for your system.</p>
</div>
</main>
<footer class="mt-8 py-4 px-6 w-full font-mono">
<div
class="flex flex-col md:flex-row justify-between md:items-center font-medium text-xs md:text-sm text-text-alt space-y-4 md:space-y-0"
>
<span
>Copyright &copy; 2026
<a
href="https:&#x2F;&#x2F;git.voidarc.co.uk&#x2F;voidarc"
class="text-blue hover:underline">Adumh00man</a
>.
<br class="md:hidden" /> Licensed under MIT.
</span>
<div class="flex flex-inline">
<div class="mr-4 md:mr-6 lg:mr-8 last:mr-0">
<a
href="https:&#x2F;&#x2F;git.voidarc.co.uk&#x2F;voidarc"
class="hover:text-blue"
>
<span>Git</span>
</a>
</div>
<div class="mr-4 md:mr-6 lg:mr-8 last:mr-0">
<a
href="https:&#x2F;&#x2F;blog.voidarc.co.uk&#x2F;rss.xml"
class="hover:text-blue"
>
<span>Rss</span>
</a>
</div>
</div>
</div>
</footer>
</div>
</body>
</html>