<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>nix-book</title><id>https://saylesss88.github.io/atom.xml</id><updated>2026-08-01T00:00:00+00:00</updated><author><name>saylesss88</name></author><link href="https://saylesss88.github.io/atom.xml" rel="self"/><link href="https://saylesss88.github.io/" rel="alternate"/><subtitle>Description</subtitle><entry><title>Understanding Nix Functions</title><id>https://saylesss88.github.io/Understanding_Nix_Functions_2.html</id><updated>2026-08-01T00:00:00+00:00</updated><author><name>saylesss87@proton.me (saylesss88)</name></author><link href="https://saylesss88.github.io/Understanding_Nix_Functions_2.html" rel="alternate"/><content type="html">&lt;h1&gt;Chapter 2&lt;/h1&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Table of Contents&lt;/summary&gt;
&lt;!-- toc --&gt;
&lt;/details&gt;
&lt;!-- &lt;img src=&quot;https://saylesss88.github.io/images/nixLogo.png&quot; width=&quot;400&quot; height=&quot;300&quot;&gt; --&gt;
&lt;p&gt;&lt;img src=&quot;https://saylesss88.github.io/images/trees2.cleaned.png&quot; alt=&quot;trees2&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Understanding Nix Functions&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Functions&lt;/strong&gt; are the building blocks of Nix, appearing everywhere in Nix
expressions and configurations. Mastering them is essential for writing
effective Nix code and understanding tools like NixOS and Home Manager. This
chapter explores how Nix functions work, focusing on their &lt;strong&gt;single-argument
nature&lt;/strong&gt;, &lt;strong&gt;currying&lt;/strong&gt;, &lt;strong&gt;partial application&lt;/strong&gt;, and their role in &lt;strong&gt;modules&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;What are Nix Functions?&lt;/h2&gt;
&lt;p&gt;A &lt;strong&gt;Nix Function&lt;/strong&gt; is a rule that takes an input (called an &lt;strong&gt;argument&lt;/strong&gt;) and
produces an &lt;strong&gt;output&lt;/strong&gt; based on that input. Unlike many programming languages,
Nix functions are designed to take exactly one argument at a time. This unique
approach, combined with a technique called currying, allows Nix to simulate
multi-argument functions in a flexible and reusable way.&lt;/p&gt;
&lt;h2&gt;Builtins&lt;/h2&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Nix Builtin Functions (Click to Expand)&lt;/summary&gt;
&lt;p&gt;The Nix expression evaluator has a bunch of functions and constants built in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;toString e&lt;/code&gt;: (Convert the expression &lt;code&gt;e&lt;/code&gt; to a string)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;import path&lt;/code&gt;: (Load, parse and return the Nix expression in the file &lt;code&gt;path&lt;/code&gt;)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;throw x&lt;/code&gt;: (Throw an error message &lt;code&gt;x&lt;/code&gt;. Usually stops evaluation)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;map f list&lt;/code&gt;: (Apply the function &lt;code&gt;f&lt;/code&gt; to each element in the &lt;code&gt;list&lt;/code&gt;)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://nix.dev/manual/nix/2.18/language/builtins&quot;&gt;Built-in Functions&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://nix.dev/manual/nix/2.26/language/operators&quot;&gt;Nix Operators&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/details&gt;
&lt;h2&gt;Lambdas&lt;/h2&gt;
&lt;p&gt;Nix functions are anonymous (lambdas) (e.g., &lt;code&gt;x: x + 2&lt;/code&gt;), and technically take a
single parameter. However, that single parameter is very often an attribute set,
allowing you to effectively pass multiple named inputs by destructuring (e.g.,
&lt;code&gt;{ arg1, arg2 }: arg1 + arg2&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Type the parameter name, followed by a colon, and finally the body of the
function.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;nix-repl&amp;gt; param: param * 2
&amp;lt;&amp;lt;lambda @ &amp;lt;&amp;lt;string&amp;gt;&amp;gt;:1:1&amp;gt;&amp;gt;

nix-repl&amp;gt; (param: param * 2) 2
4
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The above example shows that everything in Nix returns a value. When you call a
function directly (without first assigning the function itself to a variable),
the result of that call is immediately evaluated and displayed/used.&lt;/p&gt;
&lt;p&gt;In order to make our function reusable and be able to pass different values at
different times we have to assign our function to a variable:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;nix-repl&amp;gt; twoTimes = param: param * 2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, we can reference our function by it’s name and pass our required parameter:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;nix-repl&amp;gt; twoTimes
«lambda @ «string»:1:2»
nix-repl&amp;gt; twoTimes 2
4
nix-repl&amp;gt; twoTimes 4
8
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We defined a function &lt;code&gt;param: param * 2&lt;/code&gt; takes one parameter &lt;code&gt;param&lt;/code&gt;, and
returns &lt;code&gt;param * 2&lt;/code&gt;. We then assigned this function to the variable &lt;code&gt;twoTimes&lt;/code&gt;.
Lastly, we called the function with a few different arguments showing it’s
reusability.&lt;/p&gt;
&lt;h2&gt;Understanding Function Structure: The Role of the Colon&lt;/h2&gt;
&lt;p&gt;The colon (&lt;code&gt;:&lt;/code&gt;) acts as a clear separator within a function definition:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Left of the Colon:&lt;/strong&gt; This is the function’s &lt;strong&gt;argument&lt;/strong&gt;. It’s a placeholder
name for a value that will be provided when the function is called.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Right of the Colon:&lt;/strong&gt; This is the &lt;strong&gt;function body&lt;/strong&gt;. It’s the expression
that will be evaluated when the function is invoked.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Think of function arguments as naming values that aren’t known in advance.&lt;/strong&gt;
These names are placeholders that get filled with specific values when the
function is used.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;greet = personName: &quot;Hello, ${personName}!&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Here, &lt;code&gt;personName&lt;/code&gt; is the &lt;strong&gt;argument&lt;/strong&gt; (the placeholder).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;&quot;Hello, ${personName}!&quot;&lt;/code&gt;, is the &lt;strong&gt;function body&lt;/strong&gt; (which uses the
placeholder to create the greeting).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When you call the function, (click to see Output):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;greet &quot;Anonymous&quot;
~ &quot;Hello, Anonymous!&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The value &lt;code&gt;&quot;Anonymous&quot;&lt;/code&gt; is substituted for the &lt;code&gt;personName&lt;/code&gt; placeholder within
the function body.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;This structure is the foundation of all Nix functions, whether simple or
complex.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Single-Argument Functions: The Basics&lt;/h3&gt;
&lt;p&gt;The simplest form of a Nix function takes a single argument. In Nix, function
definitions like &lt;code&gt;x: x + 1&lt;/code&gt; or &lt;code&gt;personName: &quot;Hello, ${personName}!&quot;;&lt;/code&gt; are
&lt;strong&gt;anonymous lambda functions&lt;/strong&gt;. They exist as values until they are assigned to
a variable.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Click to see Output:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# This is an anonymous lambda function value:
# x: x + 1
inc = x: x + 1;          # here we assigned our lambda to a variable `inc`
inc 5
~ 6
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;x&lt;/code&gt; is the argument.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;x + 1&lt;/code&gt; is the function body.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This straightforward design makes single-argument functions easy to understand
and use. But what if you need a function that seems to take multiple arguments?
That’s where &lt;strong&gt;currying&lt;/strong&gt; comes in.&lt;/p&gt;
&lt;h3&gt;Simulating Multiple Arguments: Currying&lt;/h3&gt;
&lt;p&gt;To create functions that appear to take multiple arguments, Nix uses currying.
This involves nesting single-argument functions, where each function takes one
argument and returns another function that takes the next argument, and so on.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;nix-repl&amp;gt; multiply = x: (y: x*y)
nix-repl&amp;gt; multiply
«lambda»
nix-repl&amp;gt; multiply 4
«lambda»
nix-repl&amp;gt; (mul 4) 5
20
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We defined a function that takes the parameter &lt;code&gt;x&lt;/code&gt;, the body returns another
function. This other function takes a parameter &lt;code&gt;y&lt;/code&gt; and returns &lt;code&gt;x*y&lt;/code&gt;.
Therefore, calling &lt;code&gt;multiply 4&lt;/code&gt; returns a function like: &lt;code&gt;x: 4*y&lt;/code&gt;. In turn, we
call the returned function with &lt;code&gt;5&lt;/code&gt;, and get the expected result.&lt;/p&gt;
&lt;h4&gt;Currying example 2&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# concat is equivalent to:
# concat = x: (y: x + y);
concat = x: y: x + y;
concat 6 6    # Evaluates to 12
12
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here, &lt;code&gt;concat&lt;/code&gt; is actually &lt;strong&gt;two nested functions&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;The &lt;strong&gt;first function&lt;/strong&gt; takes &lt;code&gt;x&lt;/code&gt; and returns another function.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The &lt;strong&gt;second function&lt;/strong&gt; takes &lt;code&gt;y&lt;/code&gt; and performs &lt;code&gt;x + y&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Nix interprets the colons (&lt;code&gt;:&lt;/code&gt;) as separators for this chain of single-argument
functions.&lt;/p&gt;
&lt;p&gt;Here’s how it works step by step:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;When you call &lt;code&gt;concat 6&lt;/code&gt;, the outer function binds &lt;code&gt;x&lt;/code&gt; to &lt;code&gt;6&lt;/code&gt; and returns a
new function: &lt;code&gt;y: 6 + y&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;When you call that function with &lt;code&gt;6&lt;/code&gt; (i.e., &lt;code&gt;concat 6 6&lt;/code&gt;), it computes
&lt;code&gt;6 + 6&lt;/code&gt;, resulting in &lt;code&gt;12&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This chaining is why Nix functions are so powerful—it allows you to build
flexible, reusable functions.&lt;/p&gt;
&lt;p&gt;Currying is a powerful feature in Nix that enables you to partially apply
arguments to functions, leading to increased reusability. This behavior is a
direct consequence of Nix functions being “first-class citizens” (a concept
we’ll delve into later), and it proves invaluable for decomposing intricate
logic into a series of smaller, more focused functions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Key Insight&lt;/strong&gt;: Every colon in a function definition separates a &lt;strong&gt;single
argument&lt;/strong&gt; from its &lt;strong&gt;function body&lt;/strong&gt;, even if that body is another function
definition.&lt;/p&gt;
&lt;h4&gt;Greeting Example&lt;/h4&gt;
&lt;p&gt;Let’s explore currying with a more relatable example in the &lt;code&gt;nix repl&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;nix repl
nix-repl&amp;gt; greeting = prefix: name: &quot;${prefix}, ${name}!&quot;;

nix-repl&amp;gt; greeting &quot;Hello&quot;
&amp;lt;&amp;lt;lambda @ &amp;lt;&amp;lt;string&amp;gt;&amp;gt;:1:10&amp;gt;&amp;gt; # partial application returns a lambda

nix-repl&amp;gt; greeting &quot;Hello&quot; &quot;Alice&quot;
&quot;Hello, Alice!&quot;         # providing both arguments returns the expected result
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This function is a chain of two single-argument functions:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;The outer function takes &lt;code&gt;prefix&lt;/code&gt; (e.g. &lt;code&gt;&quot;Hello&quot;&lt;/code&gt;) and returns a function
that expects &lt;code&gt;name&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The inner function takes &lt;code&gt;name&lt;/code&gt; (e.g. &lt;code&gt;&quot;Alice&quot;&lt;/code&gt;) and combines it with
&lt;code&gt;prefix&lt;/code&gt; to produce the final string.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Thanks to &lt;strong&gt;lexical scope&lt;/strong&gt; (where inner functions can access variables from
outer functions), the inner function “remembers” the &lt;code&gt;prefix&lt;/code&gt; value.&lt;/p&gt;
&lt;h4&gt;Partial Application: Using Functions Incrementally&lt;/h4&gt;
&lt;p&gt;Because of &lt;strong&gt;currying&lt;/strong&gt;, you can apply arguments to a Nix function one at a
time. This is called &lt;em&gt;partial application&lt;/em&gt;. When you provide only some of the
expected arguments, you get a new function that “remembers” the provided
arguments and waits for the rest.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!EXAMPLE]&lt;/p&gt;
&lt;p&gt;Using our &lt;code&gt;greeting&lt;/code&gt; function again:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;nix repl
nix-repl&amp;gt; greeting = prefix: name: &quot;${prefix}, ${name}!&quot;;
nix-repl&amp;gt; helloGreeting = greeting &quot;Hello&quot;;
nix-repl&amp;gt; helloGreeting &quot;Alice&quot;
&quot;Hello, Alice&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;helloGreeting&lt;/code&gt; is now a new function. It has already received the &lt;code&gt;prefix&lt;/code&gt;
argument (&lt;code&gt;&quot;Hello&quot;&lt;/code&gt;), when we provide the second argument we get
&lt;code&gt;&quot;Hello, Alice!&quot;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Benefits of Partial Application:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Partial application provides significant benefits by enabling you to derive
specialized functions from more general ones through the process of fixing
certain parameters. Additionally, it serves as a powerful tool for adapting
existing functions to fit the precise argument requirements of higher-order
functions like &lt;code&gt;map&lt;/code&gt; and &lt;code&gt;filter&lt;/code&gt;.&lt;/p&gt;
&lt;h4&gt;Nix Functions being “first class citizens”&lt;/h4&gt;
&lt;p&gt;In the context of Nix, the phrase “Nix treats functions as first-class citizens”
means that functions in Nix are treated as values, just like numbers, strings,
or lists. They can be manipulated, passed around, and used in the same flexible
ways as other data types. This concept comes from functional programming and has
specific implications in Nix.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What It Means in Nix&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Functions Can Be &lt;strong&gt;Assigned to Variables&lt;/strong&gt;:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;You can store a function in a variable, just like you would store a number or
string.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;[!EXAMPLE]&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;greet = name: &quot;Hello, ${name}!&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Here, greet is a variable that holds a function.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;Functions Can Be &lt;strong&gt;Passed as Arguments&lt;/strong&gt;:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;You can pass a function to another function as an argument, allowing for
higher-order functions (functions that operate on other functions).&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;[!EXAMPLE]&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;applyTwice = f: x: f (f x);
inc = x: x + 1;
applyTwice inc 5 # Output: 7 (increments 5 twice: 5 → 6 → 7)
~ 7
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Here, applyTwice takes a function &lt;code&gt;f&lt;/code&gt; (in this case, &lt;code&gt;inc&lt;/code&gt;) and applies it to
&lt;code&gt;x&lt;/code&gt; twice.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;Functions Can Be &lt;strong&gt;Returned from Functions&lt;/strong&gt;:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;Functions can produce other functions as their output, which is key to
currying in Nix.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;[!EXAMPLE]&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;greeting = prefix: name: &quot;${prefix}, ${name}!&quot;;
helloGreeting = greeting &quot;Hello&quot;;  # Returns a function
helloGreeting &quot;Alice&quot;  # Output: &quot;Hello, Alice!&quot;
~ &quot;Hello, Alice!&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;The greeting function returns another function when partially applied with
prefix.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;Functions &lt;strong&gt;Are Values in Expressions&lt;/strong&gt;:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;Functions can be used anywhere a value is expected, such as in attribute sets
or lists.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;[!EXAMPLE]&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;myFuncs = {
  add = x: y: x + y;
  multiply = x: y: x * y;
};
myFuncs.add 3 4  # Output: 7
~ 7
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Here, functions are stored as values in an attribute set.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;To try this in the &lt;code&gt;repl&lt;/code&gt; just remove the semi-colon (&lt;code&gt;;&lt;/code&gt;)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Why This Matters in Nix&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;This functional approach is fundamental to Nix’s unique build system. In Nix,
&lt;strong&gt;package builds (called derivations)&lt;/strong&gt; are essentially functions. They take
specific &lt;strong&gt;inputs&lt;/strong&gt; (source code, dependencies, build scripts) and
deterministically produce &lt;strong&gt;outputs&lt;/strong&gt; (a built package).&lt;/p&gt;
&lt;p&gt;This design ensures &lt;strong&gt;atomicity&lt;/strong&gt;: if a build does not succeed completely and
perfectly, it produces no output at all. This prevents situations common in
other package managers where partial updates or corrupted builds can leave your
system in an inconsistent or broken state.&lt;/p&gt;
&lt;p&gt;Many NixOS and Home Manager modules are functions, and their first-class status
means they can be combined, reused, or passed to other parts of the
configuration system.&lt;/p&gt;
&lt;p&gt;Now that we understand the “first-class” nature of Nix Functions let’s see how
they fit into NixOS and Home Manager modules.&lt;/p&gt;
&lt;h4&gt;The Function Nature of NixOS and Home Manager Modules&lt;/h4&gt;
&lt;p&gt;It’s crucial to understand that most NixOS and Home Manager modules are
fundamentally &lt;strong&gt;functions&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;These module functions typically accept a single argument: &lt;strong&gt;an attribute set&lt;/strong&gt;
(remember this, it’s important to understand).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;A practical NixOS module example for Thunar with plugins:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# thunar.nix
{pkgs, ...}: {
  programs = {
    thunar = {
      enable = true;
      plugins = with pkgs.xfce; [
        thunar-archive-plugin
        thunar-volman
      ];
    };
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;To use this module I would need to import it into my &lt;code&gt;configuration.nix&lt;/code&gt; or
equivalent, shown here for completeness.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# configuration.nix
# ... snip ...
imports = [ ../nixos/thunar.nix ];
# ... snip ...
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;This is actually a pretty good example of &lt;code&gt;with&lt;/code&gt; making it a bit harder to
reason where the plugins are from. You might instinctively try to trace a path
like &lt;code&gt;programs.thunar.plugins.pkgs.xfce&lt;/code&gt; because you saw &lt;code&gt;pkgs.xfce&lt;/code&gt; in the
&lt;code&gt;with&lt;/code&gt; statement. But that’s now how &lt;code&gt;with&lt;/code&gt; works. The &lt;code&gt;pkgs.xfce&lt;/code&gt; path exists
&lt;em&gt;outside&lt;/em&gt; the &lt;code&gt;plugins&lt;/code&gt; list, defining the source of the items, not their
nested structure within the list.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;To follow best practices you could write the above plugins section as:&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;plugins = [
  pkgs.xfce.thunar-archive-plugin
  pkgs.xfce.thunar-volman
];
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Now it’s clear that each plugin comes directly from &lt;code&gt;pkgs&lt;/code&gt; and each will
resolve to a derivation.
&lt;ul&gt;
&lt;li&gt;To be clear either way is fine, especially in such a small self contained
module. If it were in a single file &lt;code&gt;configuration.nix&lt;/code&gt; it would be a bit
more confusing to trace. Explicitness is your friend with Nix and
maintaining reproducability. &lt;code&gt;with&lt;/code&gt; isn’t always bad but should be avoided
at the top of a file for example to bring &lt;code&gt;nixpkgs&lt;/code&gt; into scope, use &lt;code&gt;let&lt;/code&gt;
instead.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The entire module definition is a function that takes one argument (an attribute
set):&lt;code&gt;{ pkgs, ... }&lt;/code&gt;. When this module is included in your configuration, the
NixOS module system calls this function with a specific attribute set. This
attribute set contains the available packages (&lt;code&gt;pkgs&lt;/code&gt;), and other relevant
information. The module then uses these values to define parts of your system.&lt;/p&gt;
&lt;h3&gt;Understanding passing and getting back arguments&lt;/h3&gt;
&lt;p&gt;For this example we will build the Hello derivation from the Nix Pills series.&lt;/p&gt;
&lt;p&gt;Create an &lt;code&gt;autotools.nix&lt;/code&gt; with the following contents:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;pkgs: attrs: let
  defaultAttrs = {
    builder = &quot;${pkgs.bash}/bin/bash&quot;;
    args = [./builder.sh];
    baseInputs = with pkgs; [
      gnutar
      gzip
      gnumake
      gcc
      coreutils
      gawk
      gnused
      gnugrep
      binutils.bintools
    ];
    buildInputs = [];
    system = builtins.currentSystem;
  };
in
  derivation (defaultAttrs // attrs)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let’s create the hello derivation:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;let
  pkgs = import &amp;lt;nixpkgs&amp;gt; {};
  mkDerivation = import ./autotools.nix pkgs;
in
  mkDerivation {
    name = &quot;hello&quot;;
    src = ./hello-2.12.1.tar.gz;
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;You can get the tarball
&lt;a href=&quot;https://ftp.gnu.org/gnu/hello/hello-2.12.1.tar.gz&quot;&gt;here&lt;/a&gt;, place it in the
same directory as &lt;code&gt;autotools.nix&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And finally the &lt;code&gt;builder.sh&lt;/code&gt; that &lt;code&gt;autotools.nix&lt;/code&gt; declares for the &lt;code&gt;args&lt;/code&gt;
attribute:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#!/bin/bash
set -e
unset PATH
for p in $buildInputs $baseInputs; do
    export PATH=$p/bin${PATH:+:}$PATH
done

tar -xf $src

for d in *; do
    if [ -d &quot;$d&quot; ]; then
        cd &quot;$d&quot;
        break
    fi
done

./configure --prefix=$out
make
make install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When you write:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;mkDerivation = import ./autotools.nix pkgs;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;import ./autotools.nix&lt;/code&gt;: This evaluates the &lt;code&gt;autotools.nix&lt;/code&gt; file. Because it
starts with &lt;code&gt;pkgs: attrs: ...&lt;/code&gt;, it means that &lt;code&gt;autotools.nix&lt;/code&gt; evaluates to a
function that expects one argument named &lt;code&gt;pkgs&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;... pkgs&lt;/code&gt;: We are immediately calling that function (the one returned by
&lt;code&gt;import ./autotools.nix&lt;/code&gt;) and passing it our &lt;code&gt;pkgs&lt;/code&gt; variable (which is the
result of &lt;code&gt;import &amp;lt;nixpkgs&amp;gt; {}&lt;/code&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;This illustrates the concept of Currying in Nix&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;The function defined in &lt;code&gt;autotools.nix&lt;/code&gt; (&lt;code&gt;pkgs: attrs: ...&lt;/code&gt;) is a curried
function. It’s a function that, when given its first argument (&lt;code&gt;pkgs&lt;/code&gt;), returns
another function (which then expects &lt;code&gt;attrs&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;The result of import &lt;code&gt;./autotools.nix pkgs&lt;/code&gt; is that second, inner function:
&lt;code&gt;attrs: derivation (defaultAttrs // attrs)&lt;/code&gt;. This inner function is then bound
to the &lt;code&gt;mkDerivation&lt;/code&gt; variable, making it ready to be called with just the
specific attributes for your package (like &lt;code&gt;name&lt;/code&gt; and &lt;code&gt;src&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Understanding the &lt;code&gt;attrs&lt;/code&gt; Argument&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Now let’s focus on the second argument of our &lt;code&gt;autotools.nix&lt;/code&gt; function: &lt;code&gt;attrs&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Recall the full function signature in &lt;code&gt;autotools.nix&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;pkgs: attrs: let
  # ... defaultAttrs definition ...
in
  derivation (defaultAttrs // attrs)
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;What &lt;code&gt;attrs&lt;/code&gt; Represents:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Once &lt;code&gt;autotools.nix&lt;/code&gt; has received its &lt;code&gt;pkgs&lt;/code&gt; argument (and returned the inner
function), this inner function is waiting for its final argument, which we
call &lt;code&gt;attrs&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;attrs&lt;/code&gt; is simply an attribute set (a key-value map in Nix). It’s designed to
receive all the specific properties of the individual package you want to
build using this helper.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;How &lt;code&gt;attrs&lt;/code&gt; is Used:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Look at the final line of &lt;code&gt;autotools.nix&lt;/code&gt;:
&lt;code&gt;derivation (defaultAttrs // attrs)&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The &lt;code&gt;//&lt;/code&gt; operator in Nix performs an attribute set merge. It takes all
attributes from &lt;code&gt;defaultAttrs&lt;/code&gt; and combines them with all attributes from
&lt;code&gt;attrs&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Crucially, if an attribute exists in both &lt;code&gt;defaultAttrs&lt;/code&gt; and &lt;code&gt;attrs&lt;/code&gt;, the
value from &lt;code&gt;attrs&lt;/code&gt; (the second operand) takes precedence and overrides the
default value.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;Applying attrs in the hello Derivation:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;In the &lt;code&gt;hello&lt;/code&gt; derivation, we call &lt;code&gt;mkDerivation&lt;/code&gt; like this:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;        mkDerivation {
          name = &quot;hello&quot;;
          src = ./hello-2.12.1.tar.gz;
        }
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The attribute set &lt;code&gt;{ name = &quot;hello&quot;; src = ./hello-2.12.1.tar.gz; }&lt;/code&gt; is what
gets passed as the &lt;code&gt;attrs&lt;/code&gt; argument to the &lt;code&gt;mkDerivation&lt;/code&gt; function (which,
remember, is the inner function returned by &lt;code&gt;autotools.nix&lt;/code&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;When derivation &lt;code&gt;(defaultAttrs // attrs)&lt;/code&gt; is evaluated for “hello”, the &lt;code&gt;name&lt;/code&gt;
and &lt;code&gt;src&lt;/code&gt; provided in the &lt;code&gt;attrs&lt;/code&gt; set will be merged with all the
&lt;code&gt;defaultAttrs&lt;/code&gt; (like &lt;code&gt;builder&lt;/code&gt;, &lt;code&gt;args&lt;/code&gt;, &lt;code&gt;baseInputs&lt;/code&gt;, etc.).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In summary:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The &lt;code&gt;pkgs&lt;/code&gt; argument configures the general environment and available tools for
the builder.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The &lt;code&gt;attrs&lt;/code&gt; argument is where you provide the unique details for each specific
package you intend to build using this &lt;code&gt;autotools.nix&lt;/code&gt; helper. It allows you
to specify things like the package’s name, source code, version, and any
custom build flags, while still benefiting from all the sensible defaults
provided by &lt;code&gt;autotools.nix&lt;/code&gt;. This separation makes &lt;code&gt;autotools.nix&lt;/code&gt; a reusable
and flexible “template” for creating derivations.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Conclusion&lt;/h4&gt;
&lt;p&gt;Having explored the fundamental nature of functions in Nix, we can now see this
concept applies to more complex areas like NixOS configuration and derivations.
In the next chapter,
&lt;a href=&quot;https://saylesss88.github.io/NixOS_Modules_Explained_3.html&quot;&gt;NixOS Modules Explained&lt;/a&gt;.
We will learn about NixOS Modules which are themselves functions most of the
time.&lt;/p&gt;
&lt;h4&gt;Resources&lt;/h4&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Resources (Click to Expand) &lt;/summary&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://nix.dev/tutorials/nix-language.html&quot;&gt;nix.dev Nix Lang Basics&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://nixos.org/guides/nix-pills/05-functions-and-imports.html&quot;&gt;nix pills Functions and Imports&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://zero-to-nix.com/concepts/nix-language/&quot;&gt;zero-to-nix Nix Lang&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://nixcloud.io/tour/?id=functions%2Fintroduction&quot;&gt;A tour of Nix “Functions”&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://learnxinyminutes.com/nix/&quot;&gt;learn Nix in y minutes&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://noogle.dev/&quot;&gt;noogle function library&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/details&gt;
</content></entry><entry><title>Hardening NixOS</title><id>https://saylesss88.github.io/nix/hardening_NixOS.html</id><updated>2026-06-17T00:00:00+00:00</updated><author><name>saylesss87@proton.me (saylesss88)</name></author><link href="https://saylesss88.github.io/nix/hardening_NixOS.html" rel="alternate"/><content type="html">&lt;h1&gt;Hardening NixOS&lt;/h1&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Table of Contents&lt;/summary&gt;
&lt;!-- toc --&gt;
&lt;/details&gt;
&lt;p&gt;&lt;img src=&quot;https://saylesss88.github.io/../images/guy_fawks.png&quot; alt=&quot;guy fawks hacker&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Securing your NixOS system begins with a philosophy of minimalism, explicit
configuration, and proactive control. As desktop Linux attracts more novice
users, it has become an increasingly valuable target for attackers. This makes
it crucial to adopt security best practices early to protect your desktop from
common attack vectors and to avoid configuration mistakes that could expose
vulnerabilities.&lt;/p&gt;
&lt;blockquote class=&quot;markdown-alert-warning&quot;&gt;
&lt;p&gt;I am not a security expert. This guide presents various options for hardening
NixOS, but it is your responsibility to evaluate whether each
adjustment suits your specific needs and environment. Security hardening and
process isolation can introduce stability challenges, compatibility issues, or
unexpected behavior. Additionally, these protections often come with
performance tradeoffs. Always conduct thorough research, there are no plug and
play one size fits all security solutions.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;That said, I typically write about what I’m implementing myself to deepen
understanding and share what works for me. &lt;code&gt;--Source&lt;/code&gt; means the proceeding
paragraph came from &lt;code&gt;--Source&lt;/code&gt;, you can often click to check for yourself. If
you use some common sense with a bit of caution you could end up with a more
secure NixOS system that fits your needs.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Much of this guide draws inspiration or recommendations from the well-known
&lt;a href=&quot;https://madaidans-insecurities.github.io/guides/linux-hardening.html&quot;&gt;Linux Hardening Guide&lt;/a&gt;
by Madaidan’s Insecurities. Madaidan’s work is widely regarded in technical
and security circles as one of the most comprehensive and rigorously
researched sources on practical Linux security, frequently cited for its depth
and actionable advice. For example, much of the original basis for hardening
for &lt;a href=&quot;https://github.com/cynicsketch/nix-mineral&quot;&gt;nix-mineral&lt;/a&gt; came from this
guide as well. This can be a starting point but shouldn’t be blindly followed
either, always do your own research, things change frequently. Madaidan is
also a contributor to both
&lt;a href=&quot;https://www.kicksecure.com/wiki/Contributors&quot;&gt;Kicksecure&lt;/a&gt; and
&lt;a href=&quot;https://www.whonix.org/wiki/Contributors&quot;&gt;Whonix&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;For an article with apposing perspectives, see
&lt;a href=&quot;https://chyrp.cgps.ch/en/debunking-madaidans-insecurities/&quot;&gt;debunking-madaidans-insecurities&lt;/a&gt;.
We can learn from both and hopefully find something in between that is closer to
the truth.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;❗ &lt;strong&gt;Note on SELinux and AppArmor&lt;/strong&gt;: While NixOS can provide a high degree of
security through its immutable and declarative nature, it’s important to
understand the limitations regarding Mandatory Access Control (MAC)
frameworks. Neither SELinux nor AppArmor are fully supported or widely used in
the NixOS ecosystem. You can do a lot to secure NixOS but if anonymity and
isolation are paramount, I recommend booting into a
&lt;a href=&quot;https://tails.net/&quot;&gt;Tails USB stick&lt;/a&gt;. Or using
&lt;a href=&quot;https://www.whonix.org/&quot;&gt;Whonix&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;☝️ The unique file structure of NixOS, particularly the immutable &lt;code&gt;/nix/store&lt;/code&gt;,
makes it difficult to implement and manage the file-labeling mechanisms that
these frameworks rely on. There are ongoing community efforts to improve
support, but as of now, they are considered experimental and not a standard part
of a typical NixOS configuration. For an immutable distro that implements
SELinux by default at a system level as well as many other hardening techniques,
see &lt;a href=&quot;https://secureblue.dev/&quot;&gt;Fedora secureblue&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Containers and VMs are beyond the scope of this chapter but can also enhance
security and sandboxing if configured correctly. See
&lt;a href=&quot;https://saylesss88.github.io/nix/kvm.html&quot;&gt;Running NixOS in a VM&lt;/a&gt; for more
details on running NixOS in a Secureblue VM for additional security.&lt;/p&gt;
&lt;p&gt;It’s crucial to &lt;strong&gt;document every change&lt;/strong&gt; you make. By creating smaller,
feature-complete commits, each with a descriptive message, you’re building a
clear history. This approach makes it far simpler to revert a breaking change
and quickly identify what went wrong. Over time, this discipline allows you to
create security-focused checklists and ensure all angles are covered, building a
more robust and secure system.&lt;/p&gt;
&lt;p&gt;Don’t rely on single solutions or products, develop processes and defense in
depth. Think ahead and fail securely so that a single failure doesn’t mean total
insecurity.&lt;/p&gt;
&lt;p&gt;Attackers often monitor the latest Linux CVEs (Common Vulnerabilities and
Exposures) and check if and when specific distributions like NixOS have
implemented fixes. The unstable branch will receive the security patches and
fixes faster than stable which is another thing to keep in mind.&lt;/p&gt;
&lt;p&gt;Check out the
&lt;a href=&quot;https://saylesss88.github.io/nix/index.html&quot;&gt;Hardening NixOS Baseline Hardening README&lt;/a&gt;
for baseline hardening recommendations and best practices.&lt;/p&gt;
&lt;p&gt;There is something to be said about the window manager you use. GNOME, KDE
Plasma, and Sway secure privileged Wayland protocols like screencopy. This means
that on environments outside of GNOME, KDE, and Sway, applications can access
screen content of the entire desktop. This implicitly includes the content of
other applications. It’s primarily for this reason that Silverblue, Kinoite,
Sericea, and COSMIC images are recommended. &lt;del&gt;COSMIC has plans to fix this.&lt;/del&gt;
–&lt;a href=&quot;https://secureblue.dev/images&quot;&gt;secureblue Images&lt;/a&gt;&lt;/p&gt;
&lt;blockquote class=&quot;markdown-alert-important&quot;&gt;
&lt;p&gt;This is a little misleading, hyprland takes a different approach that also
works. Disabling wlroots portal does not block screencopy for all apps, but
only with sandboxed clients. Unsandboxed apps (like &lt;code&gt;grim&lt;/code&gt;) can still access
ext-image-copy-capture directly on Sway without going through the portal.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://wiki.hypr.land/0.50.0/Configuring/Permissions/&quot;&gt;Hyprland Permissions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For example, to disable Xwayland for sway on home-manager you would add:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;wayland.windowManager.sway = {
  enable = true;
  extraConfig = &apos;&apos;
    xwayland disable
  &apos;&apos;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;You may get an error saying you’re only able to disable xwayland at boot,
restart your system and you’ll be all set.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can explicitly disable &lt;code&gt;xdg-desktop-portal-wlr&lt;/code&gt; with systemd in your
&lt;code&gt;configuration.nix&lt;/code&gt; like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# configuration.nix
systemd.user.services.&quot;xdg-desktop-portal-wlr&quot; = {
  enable = false;  # Masks/stops the wlr service
};
xdg.portal.wlr.enable = false;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Common Attack Vectors for Linux&lt;/h2&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Common Attack Vectors in Linux &lt;/summary&gt;
&lt;p&gt;&lt;strong&gt;Privilege escalation&lt;/strong&gt;: The unauthorized act of gaining elevated permissions
rather than legitimate, controlled privilege use. It’s a very common tactic that
threat actors use to take over a system, steal data, delete files, and more.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Processes to protect against Privilege escalation&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Adopt the principle of least privilege, only giving users the permissions that
they require to perform their duties.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Harden your system: Minimize the attack surface, use strong passwords, and
follow best practices.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Monitor relevant sources such as the
&lt;a href=&quot;https://www.strongdm.com/nist-compliance&quot;&gt;NIST National Vulnerability Database&lt;/a&gt;,
&lt;a href=&quot;https://github.com/NixOS/nix/security/advisories&quot;&gt;NixOS Security Advisories&lt;/a&gt;,
and
&lt;a href=&quot;https://discourse.nixos.org/c/announcements/security/56&quot;&gt;NixOS Discourse Security&lt;/a&gt;
So you’ll know the latest CVEs and vulnerabilities in Linux and NixOS.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;While not made for NixOS the
&lt;a href=&quot;https://github.com/peass-ng/PEASS-ng/tree/master/linPEAS&quot;&gt;linPEAS Privilege Escalation Awesome Script&lt;/a&gt;
gives you some useful info such as active capabilities and potential risks.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Remove unnecessary SUID binaries to reduce the attack surface.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;strong&gt;Use after Free/Double free&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Use-After-Free (UAF)&lt;/strong&gt; is a type of software vulnerability that occurs in
memory unsafe languages (C C++) when a program continues to use a memory
location after it has been freed or deallocated.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Double free&lt;/strong&gt;: is a flaw where a program frees the same memory block twice
using &lt;code&gt;free()&lt;/code&gt; or &lt;code&gt;delete&lt;/code&gt;, leading to undefined behavior and potential
exploitation.&lt;/p&gt;
&lt;p&gt;Mitigation techniques include the use of hardened allocators such as
&lt;code&gt;hardened_malloc&lt;/code&gt;, which improve memory management to detect and prevent UAF and
double-free bugs. Recent versions of &lt;code&gt;glibc&lt;/code&gt; also incorporate built-in checks to
catch double frees.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;strong&gt;Unauthorized Access&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;Unauthorized access is the entry or use of your system, networks, or data by
individuals without permission. It’s a common way for adversaries to exfiltrate
data, execute malicious code, and cause damage.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Protections against Unauthorized Access&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Strong Passwords, MFA, and robust Secrets management. In 2025, 22% of breaches
involved stolen credentials overall; in basic web app attacks, 88% used stolen
credentials.
–&lt;a href=&quot;https://www.strongdm.com/blog/data-breach-statistics&quot;&gt;StrongDM data-breach-statistics&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Close unused ports with a Firewall&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Encrypt data in transit and at rest&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Watch your Logs, and deploy intrusion detection systems such as AIDE.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://cwe.mitre.org/data/definitions/89.html&quot;&gt;SQL Injection CWE&lt;/a&gt;, SQL
injection is the most common critical web application vulnerability.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://owasp.org/www-community/attacks/xss/&quot;&gt;Cross Site Scripting (XSS)&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;strong&gt;Misconfiguration&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;With many new users trying NixOS, misconfiguration is common and an easy way
for an attacker to gain control over your system.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;It is recommended to start slowly and try to ensure that you understand your
configuration. Avoid copy-pasting config files that you don’t understand yet.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;strong&gt;Zero Day Exploits&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;The term “Zero-Day” refers to a security vulnerability or flaw that is unknown
to the software developers or security teams, meaning they have had zero days to
create a patch or fix for it. This term is often associated with concepts such
as Vulnerabilities, Exploits, and Threats, and it’s important to distinguish
among them:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;A &lt;strong&gt;Zero-Day Vulnerability&lt;/strong&gt; is a previously undiscovered security weakness or
flaw in software that malicious actors can exploit.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A &lt;strong&gt;Zero-Day Exploit&lt;/strong&gt; describes the specific method or technique attackers
use to take advantage of that vulnerability to compromise a system.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A &lt;strong&gt;Zero-Day Attack&lt;/strong&gt; happens when malicious actors launch an attack using a
zero-day exploit before the software vendor has had a chance to patch or fix
the vulnerability.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.google.com/spreadsheets/d/1lkNJ0uQwbeC1ZTRrxdtuPLCIl7mlUreoKfSIgajnSyY/view?gid=0#gid=0&quot;&gt;Project Zero’s 0day spreadsheet&lt;/a&gt;.
You’ll see that a majority of zero-days are Memory Corruption bugs.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.zero-day.cz/database/&quot;&gt;Zero-Day tracking project&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.zerodayinitiative.com/advisories/published/&quot;&gt; Trend Micro’s zero day inituative&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/details&gt;
&lt;hr /&gt;
&lt;h2&gt;Minimal Installation with LUKS&lt;/h2&gt;
&lt;p&gt;Begin with NixOS’s minimal installation image. This gives you a base system with
only essential tools and no extras that could introduce vulnerabilities.&lt;/p&gt;
&lt;p&gt;NixOS’s declarative model makes auditing the installed packages and services
easy, do so regularly.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Manual Encrypted Install Following the Manual&lt;/h2&gt;
&lt;p&gt;Encryption is the process of using an algorithm to scramble plaintext data into
ciphertext, making it unreadable except to a person who has the key to decrypt
it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Data at rest&lt;/strong&gt; is data in storage, such as a computer’s or a servers hard
disk.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Data at rest encryption&lt;/strong&gt; (typically hard disk encryption), secures the
documents, directories, and files behind an encryption key. Encrypting your data
at rest prevents data leakage, physical theft, unauthorized access, and more as
long as the key management scheme isn’t compromised.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://channels.nixos.org/nixos-25.05/latest-nixos-minimal-x86_64-linux.iso&quot;&gt;Minimal ISO Download (64-bit Intel/AMD)&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://nixos.org/manual/nixos/stable/#sec-installation&quot;&gt;NixOS Manual Installation&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://wiki.nixos.org/wiki/Full_Disk_Encryption&quot;&gt;NixOS Wiki Full Disk Encryption&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The
&lt;a href=&quot;https://www.nsa.gov/Press-Room/Press-Releases-Statements/Press-Release-View/Article/3498776/post-quantum-cryptography-cisa-nist-and-nsa-recommend-how-to-prepare-now/&quot;&gt;NSA, CISA, and NIST warn&lt;/a&gt;
that nation-state actors are likely stockpiling encrypted data now, preparing
for a future when quantum computers could break today’s most widely used
encryption algorithms. Sensitive data with long-term secrecy needs is
especially at risk.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.nsa.gov/Press-Room/News-Highlights/Article/Article/3630145/cybersecurity-speaker-series-preparing-for-post-quantum/&quot;&gt;NSA/CSS Preparing for Post-Quantum&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;This is a wake-up call to use the strongest encryption available today and to
plan early for post-quantum security.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.nist.gov/news-events/news/2024/08/nist-releases-first-3-finalized-post-quantum-encryption-standards&quot;&gt;NIST First 3 Post-Quantum Encryption Standards&lt;/a&gt;
Organizations and individuals should prepare to migrate cryptographic systems
to these new standards as soon as practical.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;They chose
&lt;a href=&quot;https://www.nist.gov/news-events/news/2022/07/nist-announces-first-four-quantum-resistant-cryptographic-algorithms&quot;&gt;Four Quantum-Resistant Cryptographic Algorithms&lt;/a&gt;
warning that public-key cryptography is especially vulnerable and widely used
to protect digital information.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;Guided Encrypted BTRFS Subvol install using disko&lt;/h2&gt;
&lt;p&gt;Use LUKS encryption to protect your data at rest, the following guide is a
minimal disko encrypted installation:
&lt;a href=&quot;https://saylesss88.github.io/installation/enc/enc_install.html&quot;&gt;Encrypted Install&lt;/a&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Installing Software&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://determinate.systems/blog/nixpkgs-cooldown/&quot;&gt;Nixpkgs cooldowns&lt;/a&gt;, this
is a great addition after all of the AUR compromises. “&lt;a href=&quot;https://flakehub.com/flake/DeterminateSystems/nixpkgs-weekly?view=usage&quot;&gt;nixpkgs-weekly&lt;/a&gt; still updates once a
week, but deliberately introduces a seven-day “cooldown” period, updating to a
new revision of Nixpkgs released by upstream only after seven days have elapsed.
This introduces a buffer period where major vulnerabilities or attacks can be
identified prior to it arriving in our users’ hands.“&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Checkout &lt;a href=&quot;https://cooldowns.dev/&quot;&gt;cooldowns.dev&lt;/a&gt; for more info on cooldowns.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;The 2025 Edgescan study examined full-stack applications and found that
one-third contained critical or severe vulnerabilities, putting them at risk.
Over 45% of large enterprises leave unresolved vulnerabilities for more than a
year. This shows the necessity of containing your apps in sandboxes when
possible.
–&lt;a href=&quot;https://www.edgescan.com/stats-report/&quot;&gt;edgescan Vulnerability Report&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote class=&quot;markdown-alert-caution&quot;&gt;
&lt;p&gt;⚠️ For system security it is strongly advised to not install
&lt;a href=&quot;https://en.wikipedia.org/wiki/Proprietary_software&quot;&gt;proprietary&lt;/a&gt;,
&lt;a href=&quot;https://www.gnu.org/proprietary/proprietary.html&quot;&gt;non-freedom&lt;/a&gt; software.
Instead, use of
&lt;a href=&quot;https://www.fsf.org/about/what-is-free-software&quot;&gt;Free Software&lt;/a&gt; is
&lt;a href=&quot;https://www.gnu.org/philosophy/shouldbefree.html&quot;&gt;recommended&lt;/a&gt; –Kicksecure&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.gnu.org/proprietary/proprietary.html&quot;&gt;Proprietary Software is Often Malware&lt;/a&gt;
NOTE: While I respect the importance of software freedom, I choose to focus on
practical, technical solutions rather than engage with the ideological tone
often present in related advocacy.
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.kicksecure.com/wiki/Miscellaneous_Threats_to_User_Freedom&quot;&gt;User Freedom Threats&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.gnu.org/proprietary/proprietary-back-doors.html&quot;&gt;Proprietary Back Doors&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.eff.org/deeplinks/2015/02/who-really-owns-your-drones&quot;&gt;EFF Back Doors&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# configuration.nix
nixpkgs.config.allowUnfree = false;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To explicitly disable it for flakes:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# ...snip...
pkgs = import nixpkgs {
  system = &quot;x86_64-linux&quot;;
  config = {
    allowUnfree = false;
  };
};
# ...snip...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Most users don’t fully understand that running any software without sandboxing
gives it unrestricted access to their user data and system resources. There is a
widespread lack of awareness that Linux apps generally run with the full
permissions of the user. It’s easy to overlook the fact that “trusted source”
doesn’t mean “safe to run uncontained”. –summarized from kicksecure docs&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pre-Install Recommendations&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://nixos.org/community/teams/security/&quot;&gt;NixOS Security&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When installing software, first check
&lt;a href=&quot;https://search.nixos.org/packages&quot;&gt;search.nixos&lt;/a&gt;, and follow the &lt;code&gt;Homepage&lt;/code&gt;
link to ensure that said package is maintained.&lt;/p&gt;
&lt;p&gt;For example, when I search for &lt;code&gt;doas&lt;/code&gt;, and go to the
&lt;a href=&quot;https://github.com/Duncaen/OpenDoas&quot;&gt;Homepage&lt;/a&gt; link, I can see that the most
recent commit was made 3 years ago. For certain software this might not be an
issue but &lt;code&gt;doas&lt;/code&gt; isn’t one of them.&lt;/p&gt;
&lt;p&gt;Looking at the &lt;code&gt;sudo-rs&lt;/code&gt;
&lt;a href=&quot;https://github.com/trifectatechfoundation/sudo-rs&quot;&gt;Homepage&lt;/a&gt; I can see that it
was updated yesterday (11-19-25) and might be a better alternative. It’s
maintained and written in a memory safe language.&lt;/p&gt;
&lt;p&gt;For critical apps like &lt;code&gt;sudo&lt;/code&gt;, you should also check for vulnerabilities in said
software. If you did so for &lt;code&gt;sudo-rs&lt;/code&gt;, you’d see
&lt;a href=&quot;https://nvd.nist.gov/vuln/detail/CVE-2025-64170&quot;&gt;CVE-2025-64170&lt;/a&gt; and see that
it’s been patched. You can then look at the
&lt;a href=&quot;https://github.com/NixOS/nixpkgs/blob/master/pkgs/by-name/su/sudo-rs/package.nix&quot;&gt;sudo-rs package.nix&lt;/a&gt;
to ensure that the versions match. (As of 11-20-25 they match).&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;strong&gt;nixpkgs-unstable Security Overview&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;nixpkgs-unstable&lt;/code&gt; tracks the master branch of the Nixpkgs repo and is
constantly updated.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;This branch gets security updates faster, patching vulnerabilities faster.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Since it’s a rolling-release, packages are less thoroughly tested. This
increases the risk of new, undiscovered bugs or regressions. Some of which
could have security implications.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The packages are generally the most recent upstream versions, which is
important for security-sensitive software like browsers and kernels, as old
versions may have publicly known, unpatched vulnerabilities.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;As the name states, &lt;code&gt;nixpkgs-unstable&lt;/code&gt; is less stable and an update is more
likely to cause your system to fail to build due to breaking changes in Nix
expressions.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I personally use unstable for everything, but I don’t mind having to fix
issues that arise.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;strong&gt;Stable (e.g., &lt;code&gt;nixos-24.05&lt;/code&gt;) Security Overview&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Stable Nixpkgs channels correspond to point release (e.g., released every 6
months) and are supported for a limited period (typically one month past the
next release).&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Stable channels generally only receive conservative bug and security fixes.
Major version bumps for features are typically avoided to maintain “stability
against deliberate changes”, which means you won’t get the latest upstream
features or general bug fixes.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;While critical security updates are backported quickly, updates for less
critical packages may be slower or not happen at all if they require a
significant refactoring or version bump.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Stable channels are generally more stable, meaning updates are less likely to
introduce breaking changes to your configuration or system environment.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Many packages will be older versions. If a critical security vulnerability
requires a major upstream version update (which is often avoided in a stable
channel), the maintainers must backport the patch, a process which can
introduce its own set of risks and delays.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;strong&gt;What should you use?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The primary security trade-off is between &lt;strong&gt;patching speed for known
vulnerabilities&lt;/strong&gt; and &lt;strong&gt;stability/exposure to new bugs&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Choose &lt;code&gt;unstable&lt;/code&gt; if you prioritize getting the latest security fixes
(especially for end-user apps like browsers) as soon as they are available
upstream, accepting a higher risk of non-security-related system breakage or
new, undiscovered bugs.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Choose &lt;code&gt;stable&lt;/code&gt; if you prioritize system predictability and stability, relying
on dedicated backports for critical vulnerabilities, while accepting that
non-critical security and bug fixes will be delayed or absent until the next
major release.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A common hybrid approach is to use the &lt;code&gt;stable&lt;/code&gt; channel as the base for the OS
and selectively pin specific packages from &lt;code&gt;unstable&lt;/code&gt; to ensure they receive
rapid security updates.&lt;/p&gt;
&lt;p&gt;With flakes it’s easy to add both &lt;code&gt;stable&lt;/code&gt; and &lt;code&gt;unstable&lt;/code&gt; as flake inputs and
access each with some simple logic.&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Click to Expand Flake example using both stable &amp; unstable &lt;/summary&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{
  description = &quot;NixOS configuration with two or more channels&quot;;

 inputs = {
    nixpkgs.url = &quot;github:NixOS/nixpkgs/nixos-25.05&quot;;
    nixpkgs-unstable.url = &quot;github:NixOS/nixpkgs/nixos-unstable&quot;;
  };

  outputs =
    { nixpkgs, nixpkgs-unstable, ... }:
    {
      nixosConfigurations.&quot;your-host&quot; = nixpkgs.lib.nixosSystem {
        modules = [
          {
            nixpkgs.overlays = [
              (final: prev: {
                unstable = nixpkgs-unstable.legacyPackages.${prev.system};
                # use this variant if unfree packages are needed:
                # unstable = import nixpkgs-unstable {
                #   inherit prev;
                #   system = prev.system;
                #   config.allowUnfree = true;
                # };
              })
            ];
          }
          ./configuration.nix
        ];
      };
    };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;This is also how you enable unfree packages for flakes rather than in your
&lt;code&gt;configuration.nix&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now you can specify which packages are to be installed with which channel like
so:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# configuration.nix
{ pkgs, ... }:
{
  environment.systemPackages = [
    pkgs.firefox
    pkgs.unstable.helix
  ];
  # ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;
&lt;hr /&gt;
&lt;h2&gt;Users and SUID Binaries&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Replacing sudo with run0&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote class=&quot;markdown-alert-note&quot;&gt;
&lt;p&gt;The point here is to avoid using the setuid binary (&lt;code&gt;sudo&lt;/code&gt;), &lt;code&gt;run0&lt;/code&gt; is a
wrapper over &lt;code&gt;systemd-run&lt;/code&gt; which speaks over Inter-process Communication
Mechanisms (IPC) to PID1 which is considered safer than running a setuid
binary. We separate our daily user from administration tasks and authenticate
through our admin account. This reduces the attack surface by removing sudo as
well as reduces the risk of local privilege escalation.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;IPC&lt;/strong&gt; is the mechanism that allows processes to communicate. There are two
methods of IPC, shared memory and message passing. An OS can implement both.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;PID 1&lt;/strong&gt; is the first userspace process the kernel starts (the init system),
which becomes the ancestor and reaper of all other processes; because it runs
as root, is always present, and controls the system lifecycle, any bugs or
design issues in PID 1 have outsized security impact and can translate into
system-wide compromise or denial of service.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;details&gt;
&lt;summary&gt; Click to Expand SUID and run0 resources &lt;/summary&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://mastodon.social/@pid_eins/112353324518585654&quot;&gt;run0 explained by Lennart&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Setuid&quot;&gt;setuid Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Using &lt;code&gt;run0&lt;/code&gt; removes of these classes of
&lt;a href=&quot;https://ruderich.org/simon/notes/su-sudo-from-root-tty-hijacking&quot;&gt;attacks&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The following lists some of the downsides
&lt;a href=&quot;https://www.kicksecure.com/wiki/Dev/secureblue&quot;&gt;kicksecure vs secureblue&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/details&gt;
&lt;p&gt;&lt;code&gt;run0&lt;/code&gt; is not a SUID, it asks the service manager to invoke a command or shell
under the target user’s UID. The target command is invoked in an isolated exec
context, freshly forked off PID1 without inheriting any context from the client.&lt;/p&gt;
&lt;p&gt;The core danger of &lt;strong&gt;setuid&lt;/strong&gt; (Set User ID) lies in its ability to allow a
low-privilege user to execute a program with the &lt;strong&gt;permissions of the file’s
owner&lt;/strong&gt;, which is most often the powerful &lt;strong&gt;root user&lt;/strong&gt;.&lt;/p&gt;
&lt;h3&gt;💥 The Danger of setuid&lt;/h3&gt;
&lt;p&gt;For granting limited, controlled privilege escalation to apps, the primary
choices are broadly between traditional &lt;strong&gt;setuid/setgid permissions&lt;/strong&gt; and more
modern &lt;strong&gt;Linux capabilities&lt;/strong&gt;. &lt;a href=&quot;https://saylesss88.github.io/nix/hardening_NixOS.html#capabilities&quot;&gt;Jump to Capabilities&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.cbtnuggets.com/blog/technology/system-admin/linux-file-permissions-understanding-setuid-setgid-and-the-sticky-bit&quot;&gt;Understanding setuid/setgid&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Use the following command to find all SUID binaries:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo find / -perm -4000 -type f -ls 2&amp;gt;/dev/null
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;setuid&lt;/code&gt; permission is dangerous because it creates a privilege escalation
pathway that can be exploited for malicious purposes.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Temporary Root Access: When a file has the setuid bit set and is owned by
&lt;code&gt;root&lt;/code&gt;, any user who executes that program instantly and temporarily gains the
full power of the root user while the program runs.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If a setuid program (such as &lt;code&gt;passwd&lt;/code&gt;, or &lt;code&gt;sudo&lt;/code&gt;) contain a security flaw,
such as a buffer overflow (Common in C) or improper input validation, an
attacker can exploit the flaw.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Since the program is running with root privileges, the attacker can execute
shell code or commands with root access, completely compromising the entire
system.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Normally the root user (UID 0) gets unrestricted access to almost everything on
the entire system.&lt;/p&gt;
&lt;p&gt;I rebuild/update way too often to completely separate the accounts and allow no
admin tasks for my daily user. That may be a better option for servers, etc.&lt;/p&gt;
&lt;p&gt;Create an admin user for administrative tasks and remove your daily user from
the &lt;code&gt;wheel&lt;/code&gt; group, and disable the &lt;code&gt;sudo&lt;/code&gt;, &lt;code&gt;su&lt;/code&gt;, and &lt;code&gt;pkexec&lt;/code&gt; SUIDs:&lt;/p&gt;
&lt;p&gt;(Edited: 2026-02-01): Changed from disabling the &lt;code&gt;setuid&lt;/code&gt; bits to disabling the
wrapper entirely. Caught by &lt;code&gt;SuperSandro2000&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{ config, pkgs, lib }:
{
users.users.admin = {
    isNormalUser = true;
    description  = &quot;System administrator&quot;;
    extraGroups  = [ &quot;wheel&quot; ];   # wheel = sudo
    # run `mkpasswd --method=yescrypt` and replace &quot;changeme&quot; w/ the result
    initialHashedPassword = &quot;changeme&quot;;           # change with `passwd admin` later
    openssh.authorizedKeys.keys = [
      # (optional) paste your SSH public key here
      # &quot;ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI...&quot;
    ];
  };
    users.groups.admin = {};
    users.mutableUsers = false;

  # --------------------------------------------------------------------
  # 2. Existing daily user – remove from wheel, keep everything else
  # --------------------------------------------------------------------
  users.users.daily = {
    isNormalUser = true;
    description  = &quot;Daily driver account&quot;;
    extraGroups  = lib.mkForce [ &quot;networkmanager&quot; &quot;audio&quot; &quot;video&quot; ]; # keep useful groups
    initialHashedPassword = &quot;changeme&quot;;
    # Remove `wheel` by *not* listing it (mkForce overrides any default)
  };
  users.groups.daily = {};

security = {
# alias sudo = &apos;run0&apos;
run0.enableSudoAlias = true;
polkit.enable = true;
# Disable sudo
sudo.enable = false;
wrappers = {
    su.enable = lib.mkForce false;
    sudoedit.enable = lib.mkForce false;
    sg.enable = lib.mkForce false;
    fusermount.enable = lib.mkForce false;
    fusermount3.enable = lib.mkForce false;
    pkexec.setuid = lib.mkForce false;
    newgrp.setuid = lib.mkForce false;
    newgidmap.setuid = lib.mkForce false;
    newuidmap.setuid = lib.mkForce false;
    # `mount` Needed for `fileSystems.options`
    # mount.enable = lib.mkForce false;
    # Optional: if you disable mount, disable umount as well
    # umount.enable = lib.mkForce false;
};
# Or hyprlock, required for swaylock to accept your password
pam.services.swaylock = {
  text = &apos;&apos;
    auth include login
    account include login
    password include login
    session include login
  &apos;&apos;;
  };
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;security.wrappers...&lt;/code&gt; removes the setuid bit making the commands unusable
removing the SUID vulnerabilities for &lt;code&gt;su&lt;/code&gt; and &lt;code&gt;pkexec&lt;/code&gt;. You can find the other
SUID wrappers in &lt;code&gt;/run/wrappers/bin/&lt;/code&gt;, such as &lt;code&gt;fusermount&lt;/code&gt; and more.&lt;/p&gt;
&lt;p&gt;SUID’s that can be disabled:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;umount&lt;/code&gt;: Allows unprivileged users to unmount devices listed in your fstab.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;mount&lt;/code&gt;: Same as above but for mounting. It is recommended to set
&lt;code&gt;fileSystems.&quot;/boot&quot;.options = [ &quot;fmask=0077&quot; &quot;dmask=0077&quot; ];&lt;/code&gt; this won’t work
without &lt;code&gt;mount&lt;/code&gt;s setuid.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;sg&lt;/code&gt;: Executes a command as a different group.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;mtr-packet&lt;/code&gt;: Used by mtr to create network sockets.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;fusermount&lt;/code&gt;, &lt;code&gt;fusermount3&lt;/code&gt;: Allows unprivileged users to mount FUSE
filesystems. Can be disabled if you don’t use FUSE (e.g., Appimages, etc.)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;newuidmap&lt;/code&gt;, &lt;code&gt;newgidmap&lt;/code&gt;: Used for user namespace creation (Often used for
unprivileged containers). (Disable if you don’t use unprivileged
containers/namespaces)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;Never Disable:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;unix_chkpwd&lt;/code&gt;: This is a core PAM helper to securely check user passwords
against the root-readable &lt;code&gt;/etc/shadow&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Check again which SUID binaries are active:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo find / -perm -4000 -type f -ls 2&amp;gt;/dev/null
# Example
-rwsr-xr-x  root  root  /run/wrappers/bin/fusermount
   ^-- This &apos;s&apos; means setuid bit is set
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ls -la /run/wrappers/bin/
# Or
find /run/wrappers -perm -4000 -ls
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Only enable the wrappers you actually use!&lt;/strong&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;You will have to use &lt;code&gt;run0&lt;/code&gt; to authenticate your daily user, for example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;run0 nixos-rebuild switch --flake .
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since &lt;code&gt;run0&lt;/code&gt; doesn’t cache results and &lt;code&gt;nixos-rebuild&lt;/code&gt; calls on Polkit 3 times,
so on every rebuild, you will be asked for your password 3 times which isn’t
ideal. I found the following workaround that will only ask for your password
once.&lt;/p&gt;
&lt;p&gt;Add the following to your &lt;code&gt;configuration.nix&lt;/code&gt;, replacing &lt;code&gt;user-name&lt;/code&gt; with your
username:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt; security.polkit.extraConfig = &apos;&apos;
     polkit.addRule(function(action, subject) {
       if (subject.user == &quot;user-name&quot;) {
         if (action.id.indexOf(&quot;org.nixos&quot;) == 0) {
           polkit.log(&quot;Caching admin authentication for single NixOS operation&quot;);
           return polkit.Result.AUTH_ADMIN_KEEP;
         }
       }
     });
   &apos;&apos;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create a zsh function for easy access:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# zsh.nix
#...snip...
initContent = &apos;&apos;
  fr() {
    run0 nixos-rebuild switch --flake &quot;/home/$USER/flake#&quot;$(hostname)
  }
&apos;&apos;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Needless to say, this is less secure but much more convenient than entering your
password 3 times on every single rebuild.&lt;/p&gt;
&lt;p&gt;Without the &lt;code&gt;pam&lt;/code&gt; settings for swaylock, it won’t accept your password to log
back in.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;run0 Usage Example&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;When you are in a privileged shell, &lt;code&gt;run0&lt;/code&gt; changes the color of the background
to red to remind you of this.&lt;/p&gt;
&lt;p&gt;Example creating a user:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;run0&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;adduser admin&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;usermod -aG wheel admin&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;passwd admin&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;exit&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;reboot&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This is just an example, since we manage our users declaratively the user
created would be discarded on the next rebuild because of the
&lt;code&gt;users.mutableUsers = false;&lt;/code&gt; setting. You could of course change this to &lt;code&gt;true&lt;/code&gt;
to manage your users imperatively but I don’t recommend it.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Capabilities&lt;/h3&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Click to expand capabilities examples &lt;/summary&gt;
&lt;p&gt;One way to help get rid of setuid binaries is to replace them with capabilities.
I personally only remove the SUID bit and don’t try to replace with capabilities
as of now. You can still use the commands from &lt;code&gt;security.wrappers&lt;/code&gt; such as
&lt;code&gt;run0 su -&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Capabilities provide a subset of what is available to root to a process. This
breaks up root privileges into smaller units that can independently grant access
to processes. This reduces the full set of privileges, decreasing the risk of
exploitation.&lt;/p&gt;
&lt;p&gt;(This is just an example):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{
  # a setuid root program
  doas =
    { setuid = true;
      owner = &quot;root&quot;;
      group = &quot;root&quot;;
      source = &quot;${pkgs.doas}/bin/doas&quot;;
    };

  # a setgid program
  locate =
    { setgid = true;
      owner = &quot;root&quot;;
      group = &quot;mlocate&quot;;
      source = &quot;${pkgs.locate}/bin/locate&quot;;
    };

  # a program with the CAP_NET_RAW capability
  ping =
    { owner = &quot;root&quot;;
      group = &quot;root&quot;;
      capabilities = &quot;cap_net_raw+ep&quot;;
      source = &quot;${pkgs.iputils.out}/bin/ping&quot;;
    };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;List the highest capability number for your kernel with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cat /proc/sys/kernel/cap_last_cap
# Output:
40
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;List available Linux capabilities:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;capsh --print
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;List processes:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ps
# Example Output
PID    TTY     TIME   CMD
8063   pts/1    02     zsh
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cat /proc/8063/status | grep Cap
# Output
CapInh: 0000000800000000
CapPrm: 0000000000000000
CapEff: 0000000000000000
CapBnd: 000001ffffffffff
CapAmb: 0000000000000000
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;capsh --decode=000001ffffffffff
# Output
0x000001ffffffffff=cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_linux_immutable,cap_net_bind_service,cap_net_broadcast,cap_net_admin,cap_net_raw,cap_ipc_lock,cap_ipc_owner,cap_sys_module,cap_sys_rawio,cap_sys_chroot,cap_sys_ptrace,cap_sys_pacct,cap_sys_admin,cap_sys_boot,cap_sys_nice,cap_sys_resource,cap_sys_time,cap_sys_tty_config,cap_mknod,cap_lease,cap_audit_write,cap_audit_control,cap_setfcap,cap_mac_override,cap_mac_admin,cap_syslog,cap_wake_alarm,cap_block_suspend,cap_audit_read,cap_perfmon,cap_bpf,cap_checkpoint_restore
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;cap_net_raw&lt;/code&gt;: Allows the program to use raw and unbuffered network sockets,
which is what &lt;code&gt;ping&lt;/code&gt; and &lt;code&gt;mtr-packet&lt;/code&gt; need to send ICMP packets.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;cap_sys_admin&lt;/code&gt;: Grants a variety of system administration operations, including
the ability to perform FUSE mounts. This is a powerful capability, but it’s
still more restrictive than full root SUID.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;+ep&lt;/code&gt;: This is crucial. It stands for:
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;e&lt;/code&gt; (Effective): The set of capabilities actually used by the process when
running.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;p&lt;/code&gt; (Permitted): The set of capabilities that can be enabled by the process.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By using this approach, you are following the security principle of least
privilege, significantly reducing the attack surface compared to traditional
SUID binaries.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://search.nixos.org/options?channel=unstable&amp;amp;show=security.wrappers&amp;amp;query=security.wrappers&quot;&gt;security.wrappers&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://linux-audit.com/kernel/capabilities/linux-capabilities-101/&quot;&gt;Linux Audit capabilities 101&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.kicksecure.com/wiki/Dev/secureblue#capabilities&quot;&gt;Kicksecure’s take on capabilities&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://man7.org/linux/man-pages/man7/capabilities.7.html&quot;&gt;capabilities(7)&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.redhat.com/en/documentation/red_hat_enterprise_linux_atomic_host/7/html/container_security_guide/linux_capabilities_and_seccomp&quot;&gt;capabilities and seccomp&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/details&gt;
&lt;hr /&gt;
&lt;h2&gt;Impermanence&lt;/h2&gt;
&lt;p&gt;Impermanence, especially when using a &lt;code&gt;tmpfs&lt;/code&gt; as the root filesystem, provides
several significant security benefits. The core principle is that impermanence
defeats persistence, a fundamental goal for any attacker.&lt;/p&gt;
&lt;p&gt;When you use a root-as-tmpfs setup on NixOS, the boot process loads the entire
operating system from the read-only Nix store into a &lt;code&gt;tmpfs&lt;/code&gt; in RAM. The mutable
directories, such as &lt;code&gt;/etc&lt;/code&gt; and &lt;code&gt;/var&lt;/code&gt;, are then created on this RAM disk. When
the system is shut down, the &lt;code&gt;tmpfs&lt;/code&gt; is wiped, leaving the on-disk storage
untouched and secure.&lt;/p&gt;
&lt;p&gt;This means you get a fresh, secure boot every time, making it much harder for an
attacker to maintain a presence on your system.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://grahamc.com/blog/erase-your-darlings/&quot;&gt;Erase your Darlings (ZFS)&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://saylesss88.github.io/installation/enc/encrypted_impermanence.html&quot;&gt;Encrypted BTRFS Impermanence Guide&lt;/a&gt;
Only follow this guide if you also followed the encrypted disko install,
impermanence is designed to be destructive and needs to match your config
exactly.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Replace timesyncd with a chron job that enables Network Time Security (NTS)&lt;/h2&gt;
&lt;p&gt;This is implementing the GrapheneOS/secureblue NTS chrony settings to NixOS:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{ config
, ...
}:
{
  services.chrony = {
    enable = true;
    enableNTS = true;
    servers = [
        &quot;server time.cloudflare.com iburst nts&quot;
        &quot;server ntppool1.time.nl iburst nts&quot;
        &quot;server nts.netnod.se iburst nts&quot;
        &quot;server ptbtime1.ptb.de iburst nts&quot;
        &quot;server time.dfm.dk iburst nts&quot;
        &quot;server time.cifelli.xyz iburst nts&quot;
     ];
    # havent worked out the kinks yet
  #  extraConfig = &apos;&apos;
  #      minsources 3
  #      authselectmode require

  #      # EF
  #      dscp 46

  #      driftfile /var/lib/chrony/drift
  #      dumpdir /var/lib/chrony
  #      ntsdumpdir /var/lib/chrony

  #      leapseclist /usr/share/zoneinfo/leap-seconds.list
  #      makestep 1.0 3

  #      rtconutc

  #      cmdport 0

  #      noclientlog
  #  &apos;&apos;;
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ensure NTS is being used with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo chronyc -N authdata
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;Secure Boot&lt;/h2&gt;
&lt;!-- ![Virus](../images/virus1.png) --&gt;
&lt;p&gt;Enable a UEFI password or Administrator password where it requires
authentication in order to access the UEFI/BIOS.&lt;/p&gt;
&lt;p&gt;Secure Boot helps ensure only signed, trusted kernels and bootloaders are
executed at startup.&lt;/p&gt;
&lt;p&gt;Useful Resources:&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Click to Expand Secure Boot Resources &lt;/summary&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://0pointer.net/blog/authenticated-boot-and-disk-encryption-on-linux.html&quot;&gt;The Strange State of Authenticated Boot and Encryption&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://wiki.nixos.org/wiki/Secure_Boot&quot;&gt;NixOS Wiki Secure Boot&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/nix-community/lanzaboote&quot;&gt;lanzaboote repo&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/details&gt;
&lt;p&gt;Practical Lanzaboote Secure Boot setup for NixOS:
&lt;a href=&quot;https://saylesss88.github.io/installation/enc/lanzaboote.html&quot;&gt;Guide:Secure Boot on NixOS with Lanzaboote&lt;/a&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;The Kernel&lt;/h3&gt;
&lt;p&gt;The Kernel Self Protection Project:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://kspp.github.io/Recommended_Settings&quot;&gt;KSPP Recommended_Settings&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Given the kernel’s central role, it’s a frequent target for malicious actors,
making robust hardening essential.&lt;/p&gt;
&lt;p&gt;NixOS provides a &lt;code&gt;hardened&lt;/code&gt; profile that applies a set of security-focused
kernel and system configurations.&lt;/p&gt;
&lt;p&gt;For flakes, you could do something like the following in your
&lt;code&gt;configuration.nix&lt;/code&gt; or equivalent to import &lt;code&gt;hardened.nix&lt;/code&gt; and enable
&lt;code&gt;profiles.hardened&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# configuration.nix
{ pkgs, inputs, ... }: let
   modulesPath = &quot;${inputs.nixpkgs}/nixos/modules&quot;;

in {
  imports = [ &quot;${modulesPath}/profiles/hardened.nix&quot; ];

}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;There is a proposal to remove it completely that has gained ground, the
following thread discusses why:
&lt;a href=&quot;https://discourse.nixos.org/t/proposal-to-deprecate-the-hardened-profile/63081&quot;&gt;Discourse Thread&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/NixOS/nixpkgs/pull/383438&quot;&gt;PR #383438&lt;/a&gt; Proposed removal
PR.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Check
&lt;a href=&quot;https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/profiles/hardened.nix&quot;&gt;hardened.nix&lt;/a&gt;
to see exactly what adding it enables to avoid duplicates and conflicts moving
on. I included this for completeness, the choice is yours if you want to use
it or not.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Choosing your Kernel&lt;/h2&gt;
&lt;p&gt;See which kernel you’re currently using with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# show the kernel release
uname -r
# show kernel version, hostname, and architecture
uname -a
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Show the configuration of your current kernel:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;zcat /proc/config.gz
# ...snip...
#
# Compression
#
CONFIG_CRYPTO_DEFLATE=m
CONFIG_CRYPTO_LZO=y
CONFIG_CRYPTO_842=m
CONFIG_CRYPTO_LZ4=m
CONFIG_CRYPTO_LZ4HC=m
CONFIG_CRYPTO_ZSTD=y
# end of Compression
# ...snip...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;a href=&quot;https://nixos.org/manual/nixos/stable/#sec-kernel-config&quot;&gt;NixOS Manual&lt;/a&gt;
states that the default Linux kernel configuration should be fine for most
users.&lt;/p&gt;
&lt;p&gt;The Linux kernel is typically released under two forms: stable and long-term
support (LTS). Choosing either has consequences, do your research.
&lt;a href=&quot;https://madaidans-insecurities.github.io/guides/linux-hardening.html#stable-vs-lts&quot;&gt;Stable vs. LTS kernels&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.kernel.org/category/releases.html&quot;&gt;The Linux Kernel Archives Active kernel releases&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;OR&lt;/strong&gt;, you can choose the hardened kernel for a kernel that prioritizes
security over everything else.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;The Hardened Kernel&lt;/h3&gt;
&lt;blockquote class=&quot;markdown-alert-note&quot;&gt;
&lt;p&gt;Expect breakage when using the hardened kernel. &lt;code&gt;linux-hardened&lt;/code&gt; completely
disables
&lt;a href=&quot;https://secureblue.dev/articles/userns&quot;&gt;unprivileged user namespaces&lt;/a&gt;, which
are required for Flatpak, chromium-based browsers, and more.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The &lt;code&gt;linuxPackages_latest_hardened&lt;/code&gt; attribute has been deprecated. If you want
to use a hardened kernel, it is now recommended to use &lt;code&gt;linux_hardened&lt;/code&gt;, which
is aliased to &lt;code&gt;linux_default.kernel&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;You can find the latest available hardened kernel packages by searching
&lt;a href=&quot;https://github.com/NixOS/nixpkgs/blob/master/pkgs/top-level/linux-kernels.nix&quot;&gt;pkgs/top-level/linux-kernels.nix&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It is recommended to use &lt;code&gt;linux_hardened&lt;/code&gt; without specifying a version, such as:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;boot.kernelPackages = pkgs.linuxPackages_hardened;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;linux_hardened&lt;/code&gt; is aliased to the &lt;code&gt;linux_default.kernel&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Note that this not only replaces the kernel, but also packages that are specific
to the kernel version, such as NVIDIA video drivers. This also removes your
ability to use the &lt;code&gt;.extend&lt;/code&gt; kernel attribute, they are only available to
&lt;em&gt;kernel package sets&lt;/em&gt; (e.g., &lt;code&gt;linuxPackages_hardened&lt;/code&gt;)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If you decide to use this, read further before rebuilding.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can inspect
&lt;a href=&quot;https://github.com/NixOS/nixpkgs/blob/master/pkgs/os-specific/linux/kernel/hardened/patches.json&quot;&gt;nixpkgs/pkgs/os-specific/linux/kernel/hardened/patches.json&lt;/a&gt;
to see the metadata of the patches that are applied. You can then follow the
links in the &lt;code&gt;.json&lt;/code&gt; file to see the patch diffs.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;sysctl&lt;/h3&gt;
&lt;p&gt;A tool for checking the security hardening options of the Linux kernel:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;environment.systemPackages = [ pkgs.kernel-hardening-checker ];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;sysctl&lt;/code&gt; is a tool that allows you to view or modify kernel settings and
enable/disable different features.&lt;/p&gt;
&lt;p&gt;Check all &lt;code&gt;sysctl&lt;/code&gt; parameters against the &lt;code&gt;kernel-hardening-checker&lt;/code&gt;
recommendations:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo sysctl -a &amp;gt; params.txt
kernel-hardening-checker -l /proc/cmdline -c /proc/config.gz -s ./params.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Check the value of a specific parameter:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo sysctl -a | grep &quot;kernel.kptr_restrict&quot;
# Output:
kernel.kptr_restrict = 2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Check Active Linux Security Modules:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cat /sys/kernel/security/lsm
# Output:
File: /sys/kernel/security/lsm
capability,landlock,yama,bpf,apparmor
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Check Kernel Configuration Options:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;zcat /proc/config.gz | grep CONFIG_SECURITY_SELINUX
zcat /proc/config.gz | grep CONFIG_HARDENED_USERCOPY
zcat /proc/config.gz | grep CONFIG_STACKPROTECTOR
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since it is difficult to see exactly what enabling the hardened_kernel does.
Before rebuilding, you could do something like this to see exactly what is
added:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo sysctl -a &amp;gt; before.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And after the rebuild:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo sysctl -a &amp;gt; after.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And finally run a &lt;code&gt;diff&lt;/code&gt; on them:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;diff before.txt after.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can also diff against &lt;code&gt;after.txt&lt;/code&gt; for future changes to avoid duplicates,
this seems easier to me than trying to parse through the patches.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Kernel Security Settings&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;security = {
      protectKernelImage = true;
      lockKernelModules = false; # this breaks iptables, wireguard, and virtd

      # force-enable the Page Table Isolation (PTI) Linux kernel feature
      forcePageTableIsolation = true;

      # User namespaces are required for sandboxing.
      # this means you cannot set `&quot;user.max_user_namespaces&quot; = 0;` in sysctl
      allowUserNamespaces = true;

      # Disable unprivileged user namespaces, unless containers are enabled
      unprivilegedUsernsClone = config.virtualisation.containers.enable;
      allowSimultaneousMultithreading = true;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;Further Hardening with sysctl&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;sysctl&lt;/code&gt; hardening settings further reinforce kernel-level protections. The
hardened kernel includes security patches and stricter defaults, but it doesn’t
cover all runtime tunables. Refer to the above commands to get a diff of the
changes.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://nixos.org/manual/nixos/stable/options#opt-boot.kernel.sysctl&quot;&gt;boot.kernel.sysctl&lt;/a&gt;:
Runtime parameters of the Linux kernel, as set by sysctl(8). Note that the
sysctl parameters names must be enclosed in quotes. Values may be a string,
integer, boolean, or null.&lt;/p&gt;
&lt;p&gt;Check what each setting does &lt;a href=&quot;https://sysctl-explorer.net/&quot;&gt;sysctl-explorer&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Refer to
&lt;a href=&quot;https://madaidans-insecurities.github.io/guides/linux-hardening.html#sysctl-kernel&quot;&gt;madadaidans-insecurities#sysctl-kernel&lt;/a&gt;
for the following settings and their explainations.&lt;/p&gt;
&lt;p&gt;Also see the
&lt;a href=&quot;https://kspp.github.io/Recommended_Settings#sysctls&quot;&gt;Kernel Self Protection Projects sysctls&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;  boot.kernel.sysctl = {
    &quot;fs.suid_dumpable&quot; = 0;
    # prevent pointer leaks
    &quot;kernel.kptr_restrict&quot; = 2;
    # restrict kernel log to CAP_SYSLOG capability
    &quot;kernel.dmesg_restrict&quot; = 1;
    # Note: certian container runtimes or browser sandboxes might rely on the following
    # restrict eBPF to the CAP_BPF capability
    &quot;kernel.unprivileged_bpf_disabled&quot; = 1;
    # should be enabled along with bpf above
    # &quot;net.core.bpf_jit_harden&quot; = 2;
    # restrict loading TTY line disciplines to the CAP_SYS_MODULE
    &quot;dev.tty.ldisk_autoload&quot; = 0;
    # prevent exploit of use-after-free flaws
    &quot;vm.unprivileged_userfaultfd&quot; = 0;
    # kexec is used to boot another kernel during runtime and can be abused
    &quot;kernel.kexec_load_disabled&quot; = 1;
    # Kernel self-protection
    # SysRq exposes a lot of potentially dangerous debugging functionality to unprivileged users
    # 4 makes it so a user can only use the secure attention key. A value of 0 would disable completely
    &quot;kernel.sysrq&quot; = 4;
    # disable unprivileged user namespaces, Note: Docker, NH, and other apps may need this
    # &quot;kernel.unprivileged_userns_clone&quot; = 0; # Set to 1 because it makes NH and other programs fail
    # This should be set to 0 if you don&apos;t rely on flatpak, NH, Docker, etc.
    &quot;kernel.unprivileged_userns_clone&quot; = 1;
    # restrict all usage of performance events to the CAP_PERFMON capability
    &quot;kernel.perf_event_paranoid&quot; = 3;

    # Network
    # protect against SYN flood attacks (denial of service attack)
    &quot;net.ipv4.tcp_syncookies&quot; = 1;
    # protection against TIME-WAIT assassination
    &quot;net.ipv4.tcp_rfc1337&quot; = 1;
    # enable source validation of packets received (prevents IP spoofing)
    &quot;net.ipv4.conf.default.rp_filter&quot; = 1;
    &quot;net.ipv4.conf.all.rp_filter&quot; = 1;

    &quot;net.ipv4.conf.all.accept_redirects&quot; = 0;
    &quot;net.ipv4.conf.default.accept_redirects&quot; = 0;
    &quot;net.ipv4.conf.all.secure_redirects&quot; = 0;
    &quot;net.ipv4.conf.default.secure_redirects&quot; = 0;
    # Protect against IP spoofing
    &quot;net.ipv6.conf.all.accept_redirects&quot; = 0;
    &quot;net.ipv6.conf.default.accept_redirects&quot; = 0;
    &quot;net.ipv4.conf.all.send_redirects&quot; = 0;
    &quot;net.ipv4.conf.default.send_redirects&quot; = 0;

    # prevent man-in-the-middle attacks
    &quot;net.ipv4.icmp_echo_ignore_all&quot; = 1;

    # ignore ICMP request, helps avoid Smurf attacks
    &quot;net.ipv4.conf.all.forwarding&quot; = 0;
    &quot;net.ipv4.conf.default.accept_source_route&quot; = 0;
    &quot;net.ipv4.conf.all.accept_source_route&quot; = 0;
    &quot;net.ipv6.conf.all.accept_source_route&quot; = 0;
    &quot;net.ipv6.conf.default.accept_source_route&quot; = 0;
    # Reverse path filtering causes the kernel to do source validation of
    &quot;net.ipv6.conf.all.forwarding&quot; = 0;
    &quot;net.ipv6.conf.all.accept_ra&quot; = 0;
    &quot;net.ipv6.conf.default.accept_ra&quot; = 0;

    ## TCP hardening
    # Prevent bogus ICMP errors from filling up logs.
    &quot;net.ipv4.icmp_ignore_bogus_error_responses&quot; = 1;

    # Userspace
    # restrict usage of ptrace
    &quot;kernel.yama.ptrace_scope&quot; = 2;

    # ASLR memory protection (64-bit systems)
    &quot;vm.mmap_rnd_bits&quot; = 32;
    &quot;vm.mmap_rnd_compat_bits&quot; = 16;

    # only permit symlinks to be followed when outside of a world-writable sticky directory
    &quot;fs.protected_symlinks&quot; = 1;
    &quot;fs.protected_hardlinks&quot; = 1;
    # Prevent creating files in potentially attacker-controlled environments
    &quot;fs.protected_fifos&quot; = 2;
    &quot;fs.protected_regular&quot; = 2;

    # Randomize memory
    &quot;kernel.randomize_va_space&quot; = 2;
    # Exec Shield (Stack protection)
    &quot;kernel.exec-shield&quot; = 1;

    ## TCP optimization
    # TCP Fast Open is a TCP extension that reduces network latency by packing
    # data in the sender’s initial TCP SYN. Setting 3 = enable TCP Fast Open for
    # both incoming and outgoing connections:
    &quot;net.ipv4.tcp_fastopen&quot; = 3;
    # Bufferbloat mitigations + slight improvement in throughput &amp;amp; latency
    &quot;net.ipv4.tcp_congestion_control&quot; = &quot;bbr&quot;;
    &quot;net.core.default_qdisc&quot; = &quot;cake&quot;;
  };
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;❗ Note: The above settings are fairly aggressive and can break common
programs, read the comment warnings.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;Hardening Boot Parameters&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;boot.kernelParams&lt;/code&gt; can be used to set additional kernel command line arguments
at boot time. It can only be used for built-in modules.&lt;/p&gt;
&lt;p&gt;You can find the following settings in the
&lt;a href=&quot;https://madaidans-insecurities.github.io/guides/linux-hardening.html#boot-parameters&quot;&gt;Boot parameters section&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# boot.nix
      boot.kernelParams = [
        # make it harder to influence slab cache layout
        &quot;slab_nomerge&quot;
        # enables zeroing of memory during allocation and free time
        # helps mitigate use-after-free vulnerabilaties
        &quot;init_on_alloc=1&quot;
        &quot;init_on_free=1&quot;
        # randomizes page allocator freelist, improving security by
        # making page allocations less predictable
        &quot;page_alloc.shuffel=1&quot;
        # enables Kernel Page Table Isolation, which mitigates Meltdown and
        # prevents some KASLR bypasses
        &quot;pti=on&quot;
        # randomizes the kernel stack offset on each syscall
        # making attacks that rely on a deterministic stack layout difficult
        &quot;randomize_kstack_offset=on&quot;
        # disables vsyscalls, they&apos;ve been replaced with vDSO
        &quot;vsyscall=none&quot;
        # disables debugfs, which exposes sensitive info about the kernel
        &quot;debugfs=off&quot;
        # certain exploits cause an &quot;oops&quot;, this makes the kernel panic if an &quot;oops&quot; occurs
        &quot;oops=panic&quot;
        # only alows kernel modules that have been signed with a valid key to be loaded
        # making it harder to load malicious kernel modules
        # can make VirtualBox or Nvidia drivers unusable
        &quot;module.sig_enforce=1&quot;
        # prevents user space code excalation
        &quot;lockdown=confidentiality&quot;
        # &quot;rd.udev.log_level=3&quot;
        # &quot;udev.log_priority=3&quot;
      ];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is a thoughtful start to hardening boot parameters, there are more
recommendations in the guide.&lt;/p&gt;
&lt;p&gt;Kernel modules for hardware devices are generally loaded automatically by
&lt;code&gt;udev&lt;/code&gt;. You can force a module to be loaded via &lt;code&gt;boot.kernelModules&lt;/code&gt;.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;strong&gt;Hardening Modprobe&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You can use both &lt;code&gt;extraModprobeConfig&lt;/code&gt; &amp;amp; &lt;code&gt;blacklistedKernelModules&lt;/code&gt; to disable
different features. If you prefer, you can place these in the next section as
well.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;boot.extraModprobeConfig = &apos;&apos;
     # firewire and thunderbolt
    install firewire-core /bin/false
    install firewire_core /bin/false
    install firewire-ohci /bin/false
    install firewire_ohci /bin/false
    install firewire_sbp2 /bin/false
    install firewire-sbp2 /bin/false
    install firewire-net /bin/false
    install thunderbolt /bin/false
    install ohci1394 /bin/false
    install sbp2 /bin/false
    install dv1394 /bin/false
    install raw1394 /bin/false
    install video1394 /bin/false
&apos;&apos;;
# OR
#boot.blacklistedKernelModules = [
#  &quot;firewire-core&quot;
#  # ... snip ...
#];
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;strong&gt;Blacklisting Kernel Parameters&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Blacklisting unused kernel modules reduces the attack surface.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://nixos.org/manual/nixos/stable/options#opt-boot.blacklistedKernelModules&quot;&gt;boot.blacklistedKernelModules&lt;/a&gt;:
List of names of kernel modules that should not be loaded automatically by the
hardware probing code.&lt;/p&gt;
&lt;p&gt;You can find the following settings in the
&lt;a href=&quot;https://madaidans-insecurities.github.io/guides/linux-hardening.html#kasr-kernel-modules&quot;&gt;Blacklisting Kernel Modules Section&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;      boot.blacklistedKernelModules = [
        # Obscure networking protocols
        &quot;dccp&quot;   # Datagram Congestion Control Protocol
        &quot;sctp&quot;  # Stream Control Transmission Protocol
        &quot;rds&quot;  # Reliable Datagram Sockets
        &quot;tipc&quot;  # Transparent Inter-Process Communication
        &quot;n-hdlc&quot; # High-level Data Link Control
        &quot;ax25&quot;  # Amateur X.25
        &quot;netrom&quot;  # NetRom
        &quot;x25&quot;     # X.25
        &quot;rose&quot;
        &quot;decnet&quot;
        &quot;econet&quot;
        &quot;af_802154&quot;  # IEEE 802.15.4
        &quot;ipx&quot;  # Internetwork Packet Exchange
        &quot;appletalk&quot;
        &quot;psnap&quot;  # SubnetworkAccess Protocol
        &quot;p8023&quot;  # Novell raw IEE 802.3
        &quot;p8022&quot;  # IEE 802.3
        &quot;can&quot;   # Controller Area Network
        &quot;atm&quot;
        # Various rare filesystems
        &quot;cramfs&quot;
        &quot;freevxfs&quot;
        &quot;jffs2&quot;
        &quot;hfs&quot;
        &quot;hfsplus&quot;
        &quot;udf&quot;

        # &quot;squashfs&quot;  # compressed read-only file system used for Live CDs
        # &quot;cifs&quot;  # cmb (Common Internet File System)
        # &quot;nfs&quot;  # Network File System
        # &quot;nfsv3&quot;
        # &quot;nfsv4&quot;
        # &quot;ksmbd&quot;  # SMB3 Kernel Server
        # &quot;gfs2&quot;  # Global File System 2
        # vivid driver is only useful for testing purposes and has been the
        # cause of privilege escalation vulnerabilities
        # &quot;vivid&quot;
      ];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As with the &lt;code&gt;kernelParameters&lt;/code&gt; above, there are more suggestions in the guide, I
have used the above parameters along with the commented out ones and had no
issues.&lt;/p&gt;
&lt;p&gt;Also see
&lt;a href=&quot;https://github.com/secureblue/secureblue/blob/live/files/system/etc/modprobe.d/blacklist.conf&quot;&gt;SecureBlue’s blacklist.conf&lt;/a&gt;
for more ideas.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Hardened Memory Allocator&lt;/h2&gt;
&lt;blockquote class=&quot;markdown-alert-note&quot;&gt;
&lt;p&gt;There is a performance cost to enabling a hardened memory allocator, and
some apps will not work without a workaround such as Firefox, Thunderbird,
Torbrowser, LibreWolf, and ZenBrowser to name a few.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;With memory corruption bugs being the leading zero day category, it’s clearly
something that you should be concerned with.&lt;/p&gt;
&lt;p&gt;The grapheneOS &lt;code&gt;hardened_malloc&lt;/code&gt; is available for NixOS in two variants, add
either to your &lt;code&gt;configuration.nix&lt;/code&gt; or equivalent to apply them:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;environment.memoryAllocator.provider = &quot;graphene-hardened&quot;;&lt;/code&gt;: This is the
default configuration template that has all normal optional security features
enabled. It’s aggressive, you can expect app breakage and a performance cost.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;environment.memoryAllocator.provider = &quot;graphene-hardened-light&quot;;&lt;/code&gt;: The
light template disables the slap quarantines, write after free check, slot
randomization and raises the guard slab interval from 1 to 8 but leaves
zero-on-free and slab canaries enabled. This version has solid performance
and is still far more secure than the standard allocator.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;code&gt;libhardened_malloc.so&lt;/code&gt; is typically installed to
&lt;code&gt;/usr/local/lib/libhardened_malloc.so&lt;/code&gt; and referenced from &lt;code&gt;/etc/ld.so.preload&lt;/code&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://nixos.org/manual/nixos/stable/options#opt-environment.memoryAllocator.provider&quot;&gt;NixOS Manual memoryAllocator&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/GrapheneOS/hardened_malloc?tab=readme-ov-file#traditional-linux-based-operating-systems&quot;&gt;GrapheneOS hardened_malloc&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/secureblue/secureblue/issues/193#issuecomment-1953323680&quot;&gt;GrapheneOS/secureblue discussion on hardened_malloc issues&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://man7.org/linux/man-pages/man8/ld.so.8.html&quot;&gt;ld.so man page&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.synacktiv.com/en/publications/exploring-grapheneos-secure-allocator-hardened-malloc&quot;&gt;Exploring hardened_malloc&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;Hardening Systemd&lt;/h2&gt;
&lt;!-- ![Hacker](../images/hacker.png) --&gt;
&lt;p&gt;&lt;code&gt;systemd&lt;/code&gt; is the core “init system” and service manager that controls how
services, daemons, and basic system processes are started, stopped and
supervised on modern Linux distributions, including NixOS. It provides a suite
of basic building blocks for a Linux system as well as a system and service
manager that runs as &lt;code&gt;PID 1&lt;/code&gt; and starts the rest of the system.&lt;/p&gt;
&lt;p&gt;Stage 1 (initrd) is now based on systemd by default, the old scripted
implimentation is deprecated.&lt;/p&gt;
&lt;p&gt;Because it launches and supervises almost all system services, hardening systemd
means raising the baseline security of your entire system.&lt;/p&gt;
&lt;p&gt;Disable coredumps:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;systemd.coredump.enable = false;
# ➡️ Sets the kernel&apos;s resource limit (ulimit -c 0)
  security.pam.loginLimits = [
    {
      domain = &quot;*&quot;; # Applies to all users/sessions
      type = &quot;-&quot;; # Set both soft and hard limits
      item = &quot;core&quot;; # The soft/hard limit item
      value = &quot;0&quot;;   # Core dumps size is limited to 0 (effectively disabled)
    }
  ];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Disabling coredumps helps save space and improves security/privacy because when
a program fails, a coredump contains an exact copy of a programs running memory
at the time of the crash. This can inadvertently expose sensitive data.&lt;/p&gt;
&lt;p&gt;If a program is handling private information when it crashes, the core dump file
could contain:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Passwords&lt;/strong&gt;: Stored in memory before being sent or hashed.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Encryption Keys&lt;/strong&gt;: Used for securing network connections.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Personal Info&lt;/strong&gt;: Chat messages, website forms, etc.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It can give a minor performance upgrade and does reduce the attack surface. If a
malicious program were to gain access to your system, one of the first things it
might look for are core dump files to extract sensitive data. By disabling them,
you eliminate this potential source of information leakage.&lt;/p&gt;
&lt;p&gt;&lt;del&gt;&lt;code&gt;dbus-broker&lt;/code&gt; is generally considered more secure and robust but isn’t the
default as of yet.&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;dbus-broker&lt;/code&gt; is now the default, it’s faster and more reliable.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;  users.groups.netdev = {};
  services = {
    dbus.implementation = &quot;broker&quot;;
    logrotate.enable = true;
    journald = {
      storage = &quot;volatile&quot;; # Store logs in memory
      upload.enable = false; # Disable remote log upload (the default)
      extraConfig = &apos;&apos;
        SystemMaxUse=500M
        SystemMaxFileSize=50M
      &apos;&apos;;
    };
  };
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;dbus-broker&lt;/code&gt; is more resilient to resource exhaustion attacks and integrates
better with Linux security features.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://dvdhrm.github.io/rethinking-the-dbus-message-bus/&quot;&gt;Rethinking-the-dbus-message-bus&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Setting &lt;code&gt;storage = &quot;volatile&quot;&lt;/code&gt; tells journald to keep log data only in memory.
There is a tradeoff though, If you need long-term auditing or troubleshooting
after a reboot, this will not preserve system logs.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;upload.enable&lt;/code&gt; is for forwarding log messages to remote servers, setting this
to false prevents accidental leaks of potentially sensitive or internal system
information.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Enabling &lt;code&gt;logrotate&lt;/code&gt; prevents your disk from filling with excessive
&lt;strong&gt;legacy/service&lt;/strong&gt; log files. These are the classic plain-text logs.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Systemd uses &lt;code&gt;journald&lt;/code&gt; which stores logs in a binary format&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can check the security status with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;systemd-analyze security
# or for a detailed view of individual services security posture
systemd-analyze security NetworkManager
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Optionally disable vulnerable services to reduce the attack surface, obviously
don’t disable what you need, or change your habits:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;services = {
    # mDNS/DNS-SD
    avahi.enable = false;
    # Geoclue (location services)
    geoclue2.enable = false;
    udisks2.enable = false;
    accounts-daemon.enable = false;
  };
  # Only needed for WWAN/3G/4G modems, otherwise it runs `mmcli` unnecessarily
  networking.modemmanager.enable = false;
  # Bluetooth has a long history of vulnerabilities
  hardware.bluetooth.enable = false;
  # Prefer manual upgrades on a hardened system
  system.autoUpgrade.enable = false;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Further reading on systemd:&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Click to Expand Systemd Resources &lt;/summary&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://systemd.io/&quot;&gt;systemd.io&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://0pointer.de/blog/projects/systemd.html&quot;&gt;Rethinking PID 1&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://0pointer.de/blog/projects/the-biggest-myths.html&quot;&gt;Biggest Myths about Systemd&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/details&gt;
&lt;p&gt;The following is a repo containing many of the Systemd hardening settings in
NixOS format:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/wallago/nix-system-services-hardened&quot;&gt;nix-system-services-hardened&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;For example, to harden bluetooth you could add the following to your
&lt;code&gt;configuration.nix&lt;/code&gt; or equivalent:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;systemd.services = {
      bluetooth.serviceConfig = {
      ProtectKernelTunables = lib.mkDefault true;
      ProtectKernelModules = lib.mkDefault true;
      ProtectKernelLogs = lib.mkDefault true;
      ProtectHostname = true;
      ProtectControlGroups = true;
      ProtectProc = &quot;invisible&quot;;
      SystemCallFilter = [
        &quot;~@obsolete&quot;
        &quot;~@cpu-emulation&quot;
        &quot;~@swap&quot;
        &quot;~@reboot&quot;
        &quot;~@mount&quot;
      ];
      SystemCallArchitectures = &quot;native&quot;;
    };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see from above, you typically use the &lt;code&gt;serviceConfig&lt;/code&gt; attribute to
harden settings for systemd services.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;systemd-analyze security bluetooth
→ Overall exposure level for bluetooth.service: 3.3 OK 🙂
&lt;/code&gt;&lt;/pre&gt;
&lt;details&gt;
&lt;summary&gt; Click to expand `systemd.nix` example implementing many of the recommendations &lt;/summary&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{lib, ...}: {
  systemd.services = {
    # &quot;home-manager-jr&quot;.after = [&quot;network-online.target&quot;];
    # &quot;home-manager-jr&quot;.wantedBy = [&quot;multi-user.target&quot;];
    &quot;user@&quot;.serviceConfig = {
      ProtectSystem = &quot;strict&quot;;
      ProtectClock = true;
      ProtectHostname = true;
      ProtectKernelTunables = true;
      ProtectKernelModules = true;
      ProtectKernelLogs = true;
      ProtectProc = &quot;invisible&quot;;
      PrivateTmp = true;
      PrivateNetwork = true;
      MemoryDenyWriteExecute = false;
      RestrictAddressFamilies = [
        &quot;AF_UNIX&quot;
        &quot;AF_NETLINK&quot;
        &quot;AF_BLUETOOTH&quot;
      ];
      RestrictNamespaces = true;
      RestrictRealtime = true;
      RestrictSUIDSGID = true;
      SystemCallFilter = [
        &quot;~@keyring&quot;
        &quot;~@swap&quot;
        &quot;~@debug&quot;
        &quot;~@module&quot;
        &quot;~@obsolete&quot;
        &quot;~@cpu-emulation&quot;
      ];
      SystemCallArchitectures = &quot;native&quot;;
    };
    acpid.serviceConfig = {
      ProtectSystem = &quot;full&quot;;
      ProtectHome = true;
      RestrictAddressFamilies = [&quot;AF_INET&quot; &quot;AF_INET6&quot;];
      SystemCallFilter = &quot;~@clock @cpu-emulation @debug @module @mount @raw-io @reboot @swap&quot;;
      ProtectKernelTunables = true;
      ProtectKernelModules = true;
    };

    auditd.serviceConfig = {
      NoNewPrivileges = true;
      ProtectSystem = &quot;full&quot;;
      ProtectHome = true;
      ProtectHostname = true;
      ProtectKernelTunables = true;
      ProtectKernelModules = true;
      ProtectControlGroups = true;
      ProtectProc = &quot;invisible&quot;;
      ProtectClock = true;
      PrivateTmp = true;
      PrivateNetwork = true;
      PrivateMounts = true;
      PrivateDevices = true;
      RestrictNamespaces = true;
      RestrictRealtime = true;
      RestrictSUIDSGID = true;
      RestrictAddressFamilies = [
        &quot;~AF_INET6&quot;
        &quot;~AF_INET&quot;
        &quot;~AF_PACKET&quot;
      ];
      MemoryDenyWriteExecute = true;
      LockPersonality = true;
      SystemCallFilter = [
        &quot;~@clock&quot;
        &quot;~@module&quot;
        &quot;~@mount&quot;
        &quot;~@swap&quot;
        &quot;~@obsolete&quot;
        &quot;~@cpu-emulation&quot;
      ];
      SystemCallArchitectures = &quot;native&quot;;
      CapabilityBoundingSet = [
        &quot;~CAP_CHOWN&quot;
        &quot;~CAP_FSETID&quot;
        &quot;~CAP_SETFCAP&quot;
      ];
    };

    cups.serviceConfig = {
      NoNewPrivileges = true;
      ProtectSystem = &quot;full&quot;;
      ProtectHome = true;
      ProtectKernelModules = true;
      ProtectKernelTunables = true;
      ProtectKernelLogs = true;
      ProtectControlGroups = true;
      ProtectHostname = true;
      ProtectClock = true;
      ProtectProc = &quot;invisible&quot;;
      RestrictRealtime = true;
      RestrictNamespaces = true;
      RestrictSUIDSGID = true;
      RestrictAddressFamilies = [
        &quot;AF_UNIX&quot;
        &quot;AF_NETLINK&quot;
        &quot;AF_INET&quot;
        &quot;AF_INET6&quot;
        &quot;AF_PACKET&quot;
      ];

      MemoryDenyWriteExecute = true;
      SystemCallFilter = [
        &quot;~@clock&quot;
        &quot;~@reboot&quot;
        &quot;~@debug&quot;
        &quot;~@module&quot;
        &quot;~@swap&quot;
        &quot;~@obsolete&quot;
        &quot;~@cpu-emulation&quot;
      ];
      SystemCallArchitectures = &quot;native&quot;;
      LockPersonality = true;
    };

    NetworkManager.serviceConfig = {
      NoNewPrivileges = true;
      ProtectHome = true;
      ProtectKernelModules = true;
      ProtectKernelLogs = true;
      ProtectControlGroups = true;
      ProtectClock = true;
      ProtectHostname = true;
      ProtectProc = &quot;invisible&quot;;
      PrivateTmp = true;
      RestrictRealtime = true;
      RestrictAddressFamilies = [
        &quot;AF_UNIX&quot;
        &quot;AF_NETLINK&quot;
        &quot;AF_INET&quot;
        &quot;AF_INET6&quot;
        &quot;AF_PACKET&quot;
      ];
      RestrictNamespaces = true;
      RestrictSUIDSGID = true;
      MemoryDenyWriteExecute = true;
      SystemCallFilter = [
        &quot;~@mount&quot;
        &quot;~@module&quot;
        &quot;~@swap&quot;
        &quot;~@obsolete&quot;
        &quot;~@cpu-emulation&quot;
        &quot;ptrace&quot;
      ];
      SystemCallArchitectures = &quot;native&quot;;
      LockPersonality = true;
    };

    wpa_supplicant.serviceConfig = {
      NoNewPrivileges = true;
      ProtectSystem = &quot;strict&quot;;
      ProtectHome = true;
      ProtectKernelModules = true;
      ProtectKernelLogs = true;
      ProtectControlGroups = true;
      ProtectClock = true;
      ProtectHostname = true;
      ProtectProc = &quot;invisible&quot;;
      PrivateTmp = true;
      PrivateMounts = true;
      RestrictRealtime = true;
      RestrictAddressFamilies = [
        &quot;AF_UNIX&quot;
        &quot;AF_NETLINK&quot;
        &quot;AF_INET&quot;
        &quot;AF_INET6&quot;
        &quot;AF_PACKET&quot;
      ];
      RestrictNamespaces = true;
      RestrictSUIDSGID = true;
      MemoryDenyWriteExecute = true;
      SystemCallFilter = [
        &quot;~@mount&quot;
        &quot;~@raw-io&quot;
        &quot;~@privileged&quot;
        &quot;~@keyring&quot;
        &quot;~@reboot&quot;
        &quot;~@module&quot;
        &quot;~@swap&quot;
        &quot;~@resources&quot;
        &quot;~@obsolete&quot;
        &quot;~@cpu-emulation&quot;
        &quot;ptrace&quot;
      ];
      SystemCallArchitectures = &quot;native&quot;;
      LockPersonality = true;
      CapabilityBoundingSet = &quot;CAP_NET_ADMIN CAP_NET_RAW&quot;;
    };

    dbus.serviceConfig = {
      NoNewPrivileges = true;
      ProtectSystem = &quot;stric&quot;;
      ProtectControlGroups = true;
      ProtectHome = true;
      ProtectHostname = true;
      ProtectKernelTunables = true;
      ProtectKernelModules = true;
      ProtectKernelLogs = true;
      PrivateMounts = true;
      PrivateDevices = true;
      PrivateTmp = true;
      RestrictSUIDSGID = true;
      RestrictRealtime = true;
      RestrictAddressFamilies = [
        &quot;AF_UNIX&quot;
      ];
      RestrictNamespaces = true;
      SystemCallErrorNumber = &quot;EPERM&quot;;
      SystemCallArchitectures = &quot;native&quot;;
      SystemCallFilter = [
        &quot;~@obsolete&quot;
        &quot;~@resources&quot;
        &quot;~@debug&quot;
        &quot;~@mount&quot;
        &quot;~@reboot&quot;
        &quot;~@swap&quot;
        &quot;~@cpu-emulation&quot;
      ];
      LockPersonality = true;
      IPAddressDeny = [&quot;0.0.0.0/0&quot; &quot;::/0&quot;];
      MemoryDenyWriteExecute = true;
      DevicePolicy = &quot;closed&quot;;
      UMask = 0077;
    };

    nscd.serviceConfig = {
      ProtectClock = true;
      ProtectHostname = true;
      ProtectKernelTunables = true;
      ProtectKernelModules = true;
      ProtectKernelLogs = true;
      ProtectControlGroups = true;
      ProtectProc = &quot;invisible&quot;;
      RestrictNamespaces = true;
      RestrictRealtime = true;
      MemoryDenyWriteExecute = true;
      LockPersonality = true;
      SystemCallFilter = [
        &quot;~@mount&quot;
        &quot;~@swap&quot;
        &quot;~@clock&quot;
        &quot;~@obsolete&quot;
        &quot;~@cpu-emulation&quot;
      ];
      SystemCallArchitectures = &quot;native&quot;;
      CapabilityBoundingSet = [
        &quot;~CAP_CHOWN&quot;
        &quot;~CAP_FSETID&quot;
        &quot;~CAP_SETFCAP&quot;
      ];
    };
    bluetooth.serviceConfig = {
      ProtectKernelTunables = lib.mkDefault true;
      ProtectKernelModules = lib.mkDefault true;
      ProtectKernelLogs = lib.mkDefault true;
      ProtectHostname = true;
      ProtectControlGroups = true;
      ProtectProc = &quot;invisible&quot;;
      SystemCallFilter = [
        &quot;~@obsolete&quot;
        &quot;~@cpu-emulation&quot;
        &quot;~@swap&quot;
        &quot;~@reboot&quot;
        &quot;~@mount&quot;
      ];
      SystemCallArchitectures = &quot;native&quot;;
    };
    systemd-rfkill.serviceConfig = {
      ProtectSystem = &quot;strict&quot;;
      ProtectHome = true;
      ProtectKernelTunables = true;
      ProtectKernelModules = true;
      ProtectControlGroups = true;
      ProtectClock = true;
      ProtectProc = &quot;invisible&quot;;
      ProcSubset = &quot;pid&quot;;
      PrivateTmp = true;
      MemoryDenyWriteExecute = true;
      NoNewPrivileges = true;
      LockPersonality = true;
      RestrictRealtime = true;
      SystemCallArchitectures = &quot;native&quot;;
      UMask = &quot;0077&quot;;
      IPAddressDeny = &quot;any&quot;;
    };
    systemd-machined.serviceConfig = {
      NoNewPrivileges = true;
      ProtectSystem = &quot;strict&quot;;
      ProtectHome = true;
      ProtectClock = true;
      ProtectHostname = true;
      ProtectKernelTunables = true;
      ProtectKernelModules = true;
      ProtectKernelLogs = true;
      ProtectProc = &quot;invisible&quot;;
      PrivateTmp = true;
      PrivateMounts = true;
      PrivateUsers = true;
      PrivateNetwork = true;
      RestrictNamespaces = true;
      RestrictRealtime = true;
      RestrictSUIDSGID = true;
      RestrictAddressFamilies = [&quot;AF_UNIX&quot;];
      MemoryDenyWriteExecute = true;
      SystemCallArchitectures = &quot;native&quot;;
    };
    systemd-udevd.serviceConfig = {
      NoNewPrivileges = true;
      ProtectSystem = &quot;strict&quot;;
      ProtectHome = true;
      ProtectKernelLogs = true;
      ProtectControlGroups = true;
      ProtectClock = true;
      ProtectProc = &quot;invisible&quot;;
      RestrictNamespaces = true;
      CapabilityBoundingSet = &quot;~CAP_SYS_PTRACE ~CAP_SYS_PACCT&quot;;
    };
    nix-daemon.serviceConfig = {
      NoNewPrivileges = true;
      ProtectControlGroups = true;
      ProtectKernelModules = true;
      PrivateMounts = true;
      PrivateTmp = true;
      PrivateDevices = true;
      RestrictSUIDSGID = true;
      RestrictRealtime = true;
      RestrictNamespaces = [&quot;~cgroup&quot;];
      RestrictAddressFamilies = [
        &quot;AF_UNIX&quot;
        &quot;AF_NETLINK&quot;
        &quot;AF_INET6&quot;
        &quot;AF_INET&quot;
      ];
      CapabilityBoundingSet = [
        &quot;~CAP_SYS_CHROOT&quot;
        &quot;~CAP_BPF&quot;
        &quot;~CAP_AUDIT_WRITE&quot;
        &quot;~CAP_AUDIT_CONTROL&quot;
        &quot;~CAP_AUDIT_READ&quot;
        &quot;~CAP_SYS_PTRACE&quot;
        &quot;~CAP_SYS_NICE&quot;
        &quot;~CAP_SYS_RESOURCE&quot;
        &quot;~CAP_SYS_RAWIO&quot;
        &quot;~CAP_SYS_TIME&quot;
        &quot;~CAP_SYS_PACCT&quot;
        &quot;~CAP_LINUX_IMMUTABLE&quot;
        &quot;~CAP_IPC_LOCK&quot;
        &quot;~CAP_WAKE_ALARM&quot;
        &quot;~CAP_SYS_TTY_CONFIG&quot;
        &quot;~CAP_SYS_BOOT&quot;
        &quot;~CAP_LEASE&quot;
        &quot;~CAP_BLOCK_SUSPEND&quot;
        &quot;~CAP_MAC_ADMIN&quot;
        &quot;~CAP_MAC_OVERRIDE&quot;
      ];
      SystemCallErrorNumber = &quot;EPERM&quot;;
      SystemCallArchitectures = &quot;native&quot;;
      SystemCallFilter = [
        &quot;~@resources&quot;
        &quot;~@module&quot;
        &quot;~@obsolete&quot;
        &quot;~@debug&quot;
        &quot;~@reboot&quot;
        &quot;~@swap&quot;
        &quot;~@cpu-emulation&quot;
        &quot;~@clock&quot;
        &quot;~@raw-io&quot;
      ];
      LockPersonality = true;
      MemoryDenyWriteExecute = false;
      DevicePolicy = &quot;closed&quot;;
      UMask = 0077;
    };
    systemd-journald.serviceConfig = {
      NoNewPrivileges = true;
      ProtectProc = &quot;invisible&quot;;
      ProtectHostname = true;
      PrivateMounts = true;
    };
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;
&lt;hr /&gt;
&lt;h2&gt;Lynis and other tools&lt;/h2&gt;
&lt;p&gt;Lynis is a security auditing tool for systems based on UNIX like Linux, macOS,
BSD, and others.–&lt;a href=&quot;https://github.com/CISOfy/lynis&quot;&gt;lynis repo&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;chkrootkit&lt;/code&gt; was removed as it is unmaintained and archived upstream.&lt;/p&gt;
&lt;p&gt;Installation:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;environment.systemPackages = [
pkgs.lynis
pkgs.clamav
pkgs.aide
 ];
&lt;/code&gt;&lt;/pre&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Click to Expand AIDE Example &lt;/summary&gt;
&lt;p&gt;AIDE is an intrusion detection system (IDS) that will notify us whenever it
detects that a potential intrusion has occurred. When a system is compromised,
attackers typically will try to change file permissions and escalate to the root
user account and start to modify system files, AIDE can detect this.&lt;/p&gt;
&lt;p&gt;To set up AIDE on your system follow these steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create the &lt;code&gt;aide.conf&lt;/code&gt;:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo mkdir -p /var/lib/aide &amp;amp;&amp;amp; cd /var/lib/aide/
sudo hx aide.conf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add the following content to &lt;code&gt;/var/lib/aide/aide.conf&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;# aide.conf
# Example configuration file for AIDE.

@@define DBDIR /var/lib/aide

# The location of the database to be read.
database_in=file:@@{DBDIR}/aide.db.gz

# The location of the database to be written.
#database_out=sql:host:port:database:login_name:passwd:table
#database_out=file:aide.db.new
database_out=file:@@{DBDIR}/aide.db.new.gz

# Whether to gzip the output to database
gzip_dbout=yes

log_level=info

report_url=file:/var/log/aide/aide.log
report_url=stdout
#report_url=stderr
#NOT IMPLEMENTED report_url=mailto:root@foo.com
#NOT IMPLEMENTED report_url=syslog:LOG_AUTH

# These are the default rules.
#
#p:      permissions
#i:      inode:
#n:      number of links
#u:      user
#g:      group
#s:      size
#b:      block count
#m:      mtime
#a:      atime
#c:      ctime
#S:      check for growing size
#md5:    md5 checksum
#sha1:   sha1 checksum
#rmd160: rmd160 checksum
#tiger:  tiger checksum
#haval:  haval checksum
#gost:   gost checksum
#crc32:  crc32 checksum
#R:      p+i+n+u+g+s+m+c+md5
#L:      p+i+n+u+g
#E:      Empty group
#&amp;gt;:      Growing logfile p+u+g+i+n+S

# You can create custom rules like this.

NORMAL = R+b+sha512

DIR = p+i+n+u+g

# Next decide what directories/files you want in the database.

/boot   NORMAL
/bin    NORMAL
/sbin   NORMAL
/lib    NORMAL
/opt    NORMAL
/usr    NORMAL
/root   NORMAL

# Check only permissions, inode, user and group for /etc, but
# cover some important files closely.
/etc    p+i+u+g
!/etc/mtab
/etc/exports  NORMAL
/etc/fstab    NORMAL
/etc/passwd   NORMAL
/etc/group    NORMAL
/etc/gshadow  NORMAL
/etc/shadow   NORMAL

/var/log   p+n+u+g

# With AIDE&apos;s default verbosity level of 5, these would give lots of
# warnings upon tree traversal. It might change with future version.
#
#=/lost\+found    DIR
#=/home           DIR
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create the logfile:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo mkdir -p /var/log/aide
sudo touch /var/log/aide/aide.log
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;Generate the initial database, this will store the checksums of all files
that it’s configured to monitor. Take note of the location of the new
database, mine was &lt;code&gt;/etc/aide.db.new&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo aide --config /var/lib/aide/aide.conf --init
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;Move the new database and remove the &lt;code&gt;.new&lt;/code&gt;:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo mv /var/lib/aide/aide.db.new.gz /var/lib/aide/aide.db.gz
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ls /var/lib/aide/
aide.conf   aide.db.gz
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;Check with AIDE:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo aide --check --config /var/lib/aide/aide.conf
Start timestamp: 2025-09-05 09:50:07 -0400 (AIDE 0.19.2)
AIDE found NO differences between database and filesystem. Looks okay!!
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;5&quot;&gt;
&lt;li&gt;Whenever you make changes to system files, or especially after running a
system update or installing new tools, you have to rescan all files to update
their checksums in the AIDE database:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo aide --update --config /var/lib/aide/aide.conf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Unfortunately, AIDE doesn’t automatically replace the old database so you have
to rename the new one again:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo mv /var/lib/aide/aide.db.new.gz /var/lib/aide/aide.db.gz
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And finally check again:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo aide --check --config /var/lib/aide/aide.conf
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://linux.die.net/man/1/aide&quot;&gt;aide(1) man page&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/details&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Click to Expand clamav.nix Example &lt;/summary&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{pkgs, ...}: {
  environment.systemPackages = with pkgs; [
    clamav
  ];
  services.clamav = {
    # Enable clamd daemon
    daemon.enable = true;
    updater.enable = true;
    updater.frequency = 12; # Number of database checks per day
    scanner = {
      enable = true;
      # 4:00 AM
      interval = &quot;*-*-* 04:00:00&quot;;
      scanDirectories = [
        &quot;/home&quot;
        &quot;/var/lib&quot;
        &quot;/tmp&quot;
        &quot;/etc&quot;
        &quot;/var/tmp&quot;
      ];
    };
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;
&lt;p&gt;Lynis Usage:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo lynis show commands
# Output:
Commands:
lynis audit
lynis configure
lynis generate
lynis show
lynis update
lynis upload-only

sudo lynis audit system
# Output:
  Lynis security scan details:

  Hardening index : 79 [###############     ]
  Tests performed : 234
  Plugins enabled : 0

  Components:
  - Firewall               [V]
  - Malware scanner        [V]

  Scan mode:
  Normal [V]  Forensics [ ]  Integration [ ]  Pentest [ ]

  Lynis modules:
  - Compliance status      [?]
  - Security audit         [V]
  - Vulnerability scan     [V]
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The “Lynis hardening index” is an overall impression on how well a system is
hardened. However, this is just an indicator on measures taken - not a
percentage of how safe a system might be. A score over 75 typically indicates
a system with more than average safety measures implemented.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Lynis will give you more recommendations for securing your system as well.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you use &lt;code&gt;clamscan&lt;/code&gt;, create the following log file:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo touch /var/log/clamscan.log
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Example cron job for &lt;code&gt;clamav&lt;/code&gt; &amp;amp; &lt;code&gt;aide&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{pkgs, ...}: {
  services.cron = {
    enable = true;
    # messages.enable = true;
    systemCronJobs = [
      # Every day at 2:00 AM, run clamscan as root and append output to a log file
      &quot;0 2 * * * root ${pkgs.clamav}/bin/clamscan -r /home &amp;gt;&amp;gt; /var/log/clamscan.log&quot;
      &quot;0 11 * * * ${pkgs.aide}/bin/aide --check --config /var/lib/aide/aide.conf&quot;
    ];
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ClamAV usage:&lt;/p&gt;
&lt;p&gt;You can run &lt;code&gt;clamav&lt;/code&gt; manually with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Recursive Scan:
sudo clamscan -r ~/home
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote class=&quot;markdown-alert-note&quot;&gt;
&lt;p&gt;You only need either the individual &lt;code&gt;pkgs.clamav&lt;/code&gt; with the cron job &lt;strong&gt;OR&lt;/strong&gt; the
&lt;code&gt;clamd-daemon&lt;/code&gt; module. &lt;code&gt;clamdscan&lt;/code&gt; is for software integration and
uses a different user that doesn’t have permission to scan your files. You can
use &lt;code&gt;clamdscan --fdpass /path/to/scan&lt;/code&gt; to pass the necessary file permissions.
&lt;code&gt;clamdscan&lt;/code&gt; runs in the background, you can watch it with &lt;code&gt;top&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Securing SSH&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Security information&lt;/strong&gt;: Changing SSH configuration settings can
significantly impact the security of your system(s). It is crucial to have a
solid understanding of what you are doing before making any adjustments. Avoid
blindly copying and pasting examples, including those from this Wiki page,
without conducting a thorough analysis. Failure to do so may compromise the
security of your system(s) and lead to potential vulnerabilities. Take the
time to comprehend the implications of your actions and ensure that any
changes made are done thoughtfully and with care. –NixOS Wiki&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote class=&quot;markdown-alert-note&quot;&gt;
&lt;p&gt;Choose one, either &lt;code&gt;ssh-agent&lt;/code&gt; or &lt;code&gt;gpg-agent&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;Use normal SSH keys generated with &lt;code&gt;ssh-keygen&lt;/code&gt;, this is recommended unless
you have a good reason for not using it.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;OR&lt;/strong&gt;&lt;/p&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;Use a GPG key with &lt;code&gt;gpg-agent&lt;/code&gt; (which acts as your SSH agent). Complex, and
harder to understand in my opinion.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;My setup caused conflicts when enabling &lt;code&gt;programs.ssh.startAgent&lt;/code&gt; so I chose
&lt;code&gt;gpg-agent&lt;/code&gt; personally.&lt;/p&gt;
&lt;p&gt;There are situations where you are required to use one or the other like for
headless CI/CD environments, &lt;code&gt;ssh-keygen&lt;/code&gt; is required.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://saylesss88.github.io/nix/gpg-agent.html&quot;&gt;Click Here for GnuPG and gpg-agent chapter&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Further reading:&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Click to Expand Resourses on OpenSSH &lt;/summary&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://wiki.archlinux.org/title/OpenSSH&quot;&gt;Arch Wiki OpenSSH&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://wiki.gentoo.org/wiki/GnuPG&quot;&gt;Gentoo GnuPG&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://rgoulter.com/blog/posts/programming/2022-06-10-a-visual-explanation-of-gpg-subkeys.html&quot;&gt;A Visual Explanation of GPG Subkeys&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://blog.stribik.technology/2015/01/04/secure-secure-shell.html&quot;&gt;Secure Secure Shell&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/details&gt;
&lt;hr /&gt;
&lt;h2&gt;Key generation&lt;/h2&gt;
&lt;h3&gt;ssh-keygen&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;ed25519&lt;/code&gt; algorithm is significantly faster and more secure when compared to
&lt;code&gt;RSA&lt;/code&gt;. You can also specify the key derivation function (KDF) rounds to
strengthen protection even more.&lt;/p&gt;
&lt;p&gt;For example, to generate a strong key for GitHub:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ssh-keygen -t ed25519 -a 32 -f ~/.ssh/id_ed25519_github_$(date +%Y-%m-%d) -C &quot;SSH Key for GitHub&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;-t&lt;/code&gt; is for type&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;-a 32&lt;/code&gt; sets the number of KDF rounds. The standard is usually good enough,
adding extra rounds can make it harder to brute-force.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;-f&lt;/code&gt; is for filename&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;OpenSSH Server&lt;/h3&gt;
&lt;p&gt;First of all, if you don’t use SSH don’t enable it in the first place. If you do
use SSH, it’s important to understand what that opens you up to.&lt;/p&gt;
&lt;p&gt;The following are some recommendations from Mozilla on OpenSSH:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://infosec.mozilla.org/guidelines/openssh.html&quot;&gt;Mozilla OpenSSH guidelines&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The following OpenSSH setup is based on the above guidelines with strong
algorithms, and best practices: (EDITED: 10-07-25 to follow best-practices on
post-quantum crypto)&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{config, ...}: {
  config = {
    services = {
      fail2ban = {
        enable = true;
        maxretry = 5;
        bantime = &quot;1h&quot;;
        # ignoreIP = [
        # &quot;172.16.0.0/12&quot;
        # &quot;192.168.0.0/16&quot;
        # &quot;2601:881:8100:8de0:31e6:ac52:b5be:462a&quot;
        # &quot;matrix.org&quot;
        # &quot;app.element.io&quot; # don&apos;t ratelimit matrix users
        # ];

        bantime-increment = {
          enable = true; # Enable increment of bantime after each violation
          multipliers = &quot;1 2 4 8 16 32 64 128 256&quot;;
          maxtime = &quot;168h&quot;; # Do not ban for more than 1 week
          overalljails = true; # Calculate the bantime based on all the violations
        };
      };
      openssh = {
        enable = true;
        settings = {
          PasswordAuthentication = false;
          PermitEmptyPasswords = false;
          PermitTunnel = false;
          UseDns = false;
          KbdInteractiveAuthentication = false;
          X11Forwarding = config.services.xserver.enable;
          MaxAuthTries = 3;
          MaxSessions = 2;
          ClientAliveInterval = 300;
          ClientAliveCountMax = 0;
          AllowUsers = [&quot;your-user&quot;];
          TCPKeepAlive = false;
          AllowTcpForwarding = false;
          AllowAgentForwarding = false;
          LogLevel = &quot;VERBOSE&quot;;
          PermitRootLogin = &quot;no&quot;;
          KexAlgorithms = [
            # Post-Quantum: https://www.openssh.org/pq.html
            &quot;mlkem768x25519-sha256&quot;
            &quot;sntrup761x25519-sha512&quot;
            &quot;curve25519-sha256@libssh.org&quot;
            &quot;ecdh-sha2-nistp521&quot;
            &quot;ecdh-sha2-nistp384&quot;
            &quot;ecdh-sha2-nistp256&quot;
            &quot;diffie-hellman-group-exchange-sha256&quot;
          ];
          Ciphers = [
            &quot;aes256-gcm@openssh.com&quot;
            &quot;aes128-gcm@openssh.com&quot;
            # stream cipher alternative to aes256, proven to be resilient
            # Very fast on basically anything
            &quot;chacha20-poly1305@openssh.com&quot;
            # industry standard, fast if you have AES-NI hardware
            &quot;aes256-ctr&quot;
            &quot;aes192-ctr&quot;
            &quot;aes128-ctr&quot;
          ];
          Macs = [
            # Combines the SHA-512 hash func with a secret key to create a MAC
            &quot;hmac-sha2-512-etm@openssh.com&quot;
            &quot;hmac-sha2-256-etm@openssh.com&quot;
            &quot;umac-128-etm@openssh.com&quot;
            &quot;hmac-sha2-512&quot;
            &quot;hmac-sha2-256&quot;
            &quot;umac-128@openssh.com&quot;
          ];
        };
        # These keys will be generated for you
        hostKeys = [
          {
            path = &quot;/etc/ssh/ssh_host_ed25519_key&quot;;
            type = &quot;ed25519&quot;;
          }
        ];
      };
    };
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;TCP port 22 (ssh) is opened automatically if the SSH daemon is enabled
(&lt;code&gt;services.openssh.enable = true;&lt;/code&gt;)&lt;/p&gt;
&lt;p&gt;Much of the SSH hardening settings came from
&lt;a href=&quot;https://ryanseipp.com/post/nixos-secure-ssh/&quot;&gt;ryanseipp’s secure-ssh Guide&lt;/a&gt;
with some additions of my own.&lt;/p&gt;
&lt;p&gt;Fail2Ban is an intrusion prevention software framework. It’s designed to prevent
brute-force attacks by scanning log files for suspicious activity, such as
repeated failed login attempts.&lt;/p&gt;
&lt;p&gt;As of 26.05, &lt;a href=&quot;https://reaction.ppom.me/&quot;&gt;reaction&lt;/a&gt; was added to nixpkgs.
“A daemon that scans program outputs for repeated patterns, and takes action.”
It labels itself as a more modern alternative to fail2ban, implemented in Rust.
The initial rust commit was authored May 20, 2024 and it has not received an
external security audit yet. Worth checking out, probably not a primary
recommendation yet.&lt;/p&gt;
&lt;p&gt;OpenSSH is the primary tool for secure remote access for NixOS. Enabling it
activates the OpenSSH server on the system, allowing incoming SSH connections.&lt;/p&gt;
&lt;p&gt;The above configuration is a robust setup for securing an SSH server by:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Preventing brute-force attacks with Fail2Ban&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Eliminating password authentication in favor of more secure SSH keys&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Restricting user access and preventing root login&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Disabling potentially risky forwarding features (tunnel, TCP, agent)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Enforce the use of strong, modern cryptographic algorithms for all SSH
communications.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Enhanced logging for better auditing.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Further Reading:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.openssh.com/&quot;&gt;OpenSSH&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.digitalocean.com/community/tutorials/how-fail2ban-works-to-protect-services-on-a-linux-server&quot;&gt;DigitalOcean how fail2ban works&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;Encrypted Secrets&lt;/h2&gt;
&lt;p&gt;Never store secrets in plain text in repositories. Use something like
&lt;a href=&quot;https://github.com/Mic92/sops-nix&quot;&gt;sops-nix&lt;/a&gt;, which lets you keep encrypted
secrets under version control declaratively.&lt;/p&gt;
&lt;p&gt;Another option is &lt;a href=&quot;https://github.com/ryantm/agenix&quot;&gt;agenix&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://wiki.nixos.org/wiki/Agenix&quot;&gt;NixOS Wiki Agenix&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Sops-nix Guide&lt;/h3&gt;
&lt;p&gt;Protect your secrets, the following guide is on setting up Sops on NixOS:
&lt;a href=&quot;https://saylesss88.github.io/installation/enc/sops-nix.html&quot;&gt;Sops Encrypted Secrets&lt;/a&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Auditd&lt;/h2&gt;
&lt;p&gt;To enable the Linux Audit Daemon (&lt;code&gt;auditd&lt;/code&gt;) and define a very basic rule set,
you can use the following NixOS configuration. This example demonstrates how to
log every program execution (&lt;code&gt;execve&lt;/code&gt;) on a 64-bit architecture.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# modules/security/auditd-minimal.nix (or directly in configuration.nix)
{
  # start as early in the boot process as possible
  boot.kernelParams = [&quot;audit=1&quot;];
  security.auditd.enable = true;
  security.audit.enable = true;
  security.audit.rules = [
    # Log all program executions on 64-bit architecture
    &quot;-a exit,always -F arch=b64 -S execve&quot;
  ];
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;audit=1&lt;/code&gt; Enables auditing at the kernel level very early in the boot process.
Without this, some events could be missed.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;security.auditd.enable = true;&lt;/code&gt; Ensures the &lt;code&gt;auditd&lt;/code&gt; userspace daemon is
started.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;While often enabled together, &lt;code&gt;security.audit.enable&lt;/code&gt; specifically refers to
enabling the NixOS module for audit rules generation.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;execve&lt;/code&gt; (program executions)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;This is just a basic configuration, there is much more that can be tracked.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;USB Port Protection&lt;/h2&gt;
&lt;p&gt;It’s important to protect your USB ports to prevent BadUSB attacks, data
exfiltration, unauthorized device access, malware injection, etc.&lt;/p&gt;
&lt;p&gt;To get a list of your connected USB devices you can use &lt;code&gt;lsusb&lt;/code&gt; from the
&lt;code&gt;usbutils&lt;/code&gt; package.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;lsusb
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To list the devices recognized by USBGuard, run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo usbguard list-devices
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://mynixos.com/options/services.usbguard&quot;&gt;MyNixOS services.usbguard&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Change &lt;code&gt;your-user&lt;/code&gt; to your username:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# usbguard.nix
{
  config,
  pkgs,
  lib,
  ...
}: let
  inherit (lib) mkIf;
  cfg = config.custom.security.usbguard;
in {
  options.custom.security.usbguard = {
    enable = lib.mkEnableOption &quot;usbguard&quot;;
  };

  config = mkIf cfg.enable {
    services.usbguard = {
      enable = true;
      IPCAllowedUsers = [&quot;root&quot; &quot;your-user&quot;];
    # presentDevicePolicy refers to how to treat USB devices that are already connected when the daemon starts
      presentDevicePolicy = &quot;allow&quot;;
      rules = &apos;&apos;
        # allow `only` devices with mass storage interfaces (USB Mass Storage)
        allow with-interface equals { 08:*:* }
        # allow mice and keyboards
        # allow with-interface equals { 03:*:* }

        # Reject devices with suspicious combination of interfaces
        reject with-interface all-of { 08:*:* 03:00:* }
        reject with-interface all-of { 08:*:* 03:01:* }
        reject with-interface all-of { 08:*:* e0:*:* }
        reject with-interface all-of { 08:*:* 02:*:* }
      &apos;&apos;;
    };

    environment.systemPackages = [pkgs.usbguard];
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The above settings can be found in
&lt;a href=&quot;https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/7/html/security_guide/sec-using-usbguard&quot;&gt;RedHat UsbGuard&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The only &lt;code&gt;allow&lt;/code&gt; rule is for devices with &lt;strong&gt;only&lt;/strong&gt; mass storage interfaces
(&lt;code&gt;08:*:*&lt;/code&gt;) i.e., USB Mass storage devices, devices like keyboards and mice
(which use interface class &lt;code&gt;03:*:*&lt;/code&gt;) implicitly &lt;strong&gt;not allowed&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;reject&lt;/code&gt; rules reject devices with a suspicious combination of interfaces. A
USB drive that implements a keyboard or a network interface is very suspicious,
these &lt;code&gt;reject&lt;/code&gt; rules prevent that.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;presentDevicePolicy = &quot;allow&quot;;&lt;/code&gt; allows any device that is present at daemon
start up even if they’re not explicitly allowed. However, newly plugged in
devices must match an &lt;code&gt;allow&lt;/code&gt; rule or get denied implicitly.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;presentDevicePolicy&lt;/code&gt; should be one of: # one of &lt;code&gt;&quot;apply-policy&quot;&lt;/code&gt;(default,
evaluate the rule set for every present device), &lt;code&gt;&quot;block&quot;&lt;/code&gt;, &lt;code&gt;&quot;reject&quot;&lt;/code&gt;, &lt;code&gt;&quot;keep&quot;&lt;/code&gt;
(keep whatever state the device is currently in), or &lt;code&gt;&quot;allow&quot;&lt;/code&gt;, which is used in
the example.&lt;/p&gt;
&lt;p&gt;There is also the
&lt;a href=&quot;https://github.com/Cropi/usbguard-notifier&quot;&gt;usbguard-notifier&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;And enable it with the following in your &lt;code&gt;configuration.nix&lt;/code&gt; or equivalent:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# configuration.nix
imports = [
    ./usbguard.nix
];
custom.security.usbguard.enable = true;
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;❗ If you are ever unsure about a setting that you want to harden and think
that it could possibly break your system you can always use a specialisation
reversing the action and choose it’s generation at boot up. For example, to
force-reverse the above settings you could:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# configuration.nix
specialisation.no-usbguard.configuration = {
    services.usbguard.enable = lib.mkForce false;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;This is a situation where I recommend this, it’s easy to lock yourself out
of your keyboard, mouse, etc. when trying to configure this.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;Further Reading:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.ninjaone.com/it-hub/endpoint-security/what-is-badusb/&quot;&gt;NinjaOne BadUSB&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://usbguard.github.io/&quot;&gt;USBGuard&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.cyberciti.biz/security/how-to-protect-linux-against-rogue-usb-devices-using-usbguard/&quot;&gt;NixCraft USBGuard&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;Doas over sudo (Warning Doas is unmaintained)&lt;/h2&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Click to Expand Unmaintained Doas example &lt;/summary&gt;
&lt;blockquote class=&quot;markdown-alert-note&quot;&gt;
&lt;p&gt;I have moved to &lt;code&gt;run0&lt;/code&gt; for authentication which is included by default with
systemd. It’s actually a symlink to the existing &lt;code&gt;systemd-run&lt;/code&gt; tool. It
behaves like a secure &lt;code&gt;sudo&lt;/code&gt; alternative: it spawns a transient service under
PID 1 for privilege escalation, without relying on SUID (set user ID)
binaries.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote class=&quot;markdown-alert-warning&quot;&gt;
&lt;p&gt;the Nixpkgs version of &lt;code&gt;doas&lt;/code&gt;,&lt;a href=&quot;https://github.com/Duncaen/OpenDoas&quot;&gt;OpenDoas&lt;/a&gt;
is unmaintained and hasn’t been updated in 3 to 4 years. If you don’t like
&lt;code&gt;run0&lt;/code&gt;, consider using &lt;code&gt;sudo-rs&lt;/code&gt;. I’m leaving this here for now, may remove it
in the future to not promote using unmaintained software, you’ve been warned.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://mastodon.social/@pid_eins/112353324518585654&quot;&gt;Why run0&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;SUID = “Set User ID”: When a binary has the SUID bit set, it runs with the
privileges of the file’s owner (often root). There is a long history of
vulnerabilities with SUID binaries.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For a more minimalist version of &lt;code&gt;sudo&lt;/code&gt; with a smaller codebase and attack
surface, consider &lt;code&gt;doas&lt;/code&gt;. Replace &lt;code&gt;userName&lt;/code&gt; with your username:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# doas.nix
{
  lib,
  config,
  pkgs, # Add pkgs if you need to access user information
  ...
}: let
  cfg = config.custom.security.doas;
in {
  options.custom.security.doas = {
    enable = lib.mkEnableOption &quot;doas&quot;;
  };

  config = lib.mkIf cfg.enable {
    # Disable sudo
    security.sudo.enable = false;

    # Enable and configure `doas`.
    security.doas = {
      enable = true;
      extraRules = [
        {
          # Grant doas access specifically to your user
          users = [&quot;userName&quot;]; # &amp;lt;--- Only give access to your user
          # persist = true; # Convenient but less secure
          # noPass = true;    # Convenient but even less secure
          keepEnv = true; # Often necessary
          # Optional: You can also specify which commands they can run, e.g.:
          # cmd = &quot;ALL&quot;; # Allows running all commands (default if not specified)
          # cmd = &quot;/run/current-system/sw/bin/nixos-rebuild&quot;; # Only allow specific command
        }
      ];
    };

    # Add an alias to the shell for backward-compat and convenience.
    environment.shellAliases = {
      sudo = &quot;doas&quot;;
    };
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You would then import this into your &lt;code&gt;configuration.nix&lt;/code&gt; and enable/disable it
with the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# configuration.nix

imports = [
    ./doas.nix
];

custom.security.doas.enable = true;
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;[!NOTE]: Many people opt for the less secure &lt;code&gt;groups = [&quot;wheel&quot;];&lt;/code&gt; in the
above configuration instead of &lt;code&gt;users = [&quot;userName&quot;];&lt;/code&gt; to give wider access,
the choice is yours.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/details&gt;
&lt;hr /&gt;
&lt;h2&gt;Firejail&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;❗️ Critics such as madaidan say that Firejail worsens security by acting as a
privilege escalation hole. Firejail requires the executable to be setuid,
meaning it runs with root privileges.This is risky because any vulnerability
in Firejail can lead to privilege escalation. This combined with many
convenience features and complicated command line flags leads to a large
attack surface.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;I haven’t personally tried
&lt;a href=&quot;https://github.com/Naxdy/nix-bwrapper&quot;&gt;nix-bwrapper&lt;/a&gt; myself yet, but it’s
another sandboxing option that looks interesting. Bubblewrap is known for
having a more minimal design and smaller attack surface.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Also see: &lt;a href=&quot;https://saylesss88.github.io/nix/hardening_NixOS.html#flatpak&quot;&gt;Flatpak section&lt;/a&gt; for another option for sandboxing.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://sr.ht/~fgaz/nix-bubblewrap/&quot;&gt;nix-bubblewrap&lt;/a&gt; is another option.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://wiki.nixos.org/wiki/Firejail&quot;&gt;NixOS Wiki Firejail&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://wiki.archlinux.org/title/Firejail&quot;&gt;Arch Wiki Firejail&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote class=&quot;markdown-alert-warning&quot;&gt;
&lt;p&gt;Running untrusted code is never safe, sandboxing cannot change this.
–Arch Wiki&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# firejail.nix
{
  pkgs,
  lib,
  ...
}: {
  programs.firejail = {
    enable = true;
    wrappedBinaries = {
      # Sandbox a web browser
      librewolf = {
        executable = &quot;${lib.getBin pkgs.librewolf}/bin/librewolf&quot;;
        profile = &quot;${pkgs.firejail}/etc/firejail/librewolf.profile&quot;;
      };
      # Sandbox a file manager
      thunar = {
        executable = &quot;${lib.getBin pkgs.xfce.thunar}/bin/thunar&quot;;
        profile = &quot;${pkgs.firejail}/etc/firejail/thunar.profile&quot;;
      };
      # Sandbox a document viewer
      zathura = {
        executable = &quot;${lib.getBin pkgs.zathura}/bin/zathura&quot;;
        profile = &quot;${pkgs.firejail}/etc/firejail/zathura.profile&quot;;
      };
    };
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;wrappedBinaries&lt;/code&gt; is a list of applications you want to run inside a sandbox.
Only the apps in the &lt;code&gt;wrappedBinaries&lt;/code&gt; attribute set will be automatically
firejailed when launched the usual way.&lt;/p&gt;
&lt;p&gt;Other apps may be started manually using &lt;code&gt;firejail &amp;lt;app&amp;gt;&lt;/code&gt;, or added to
&lt;code&gt;wrappedBinaries&lt;/code&gt; if you want automatic sandboxing, just make sure the profile
exists.&lt;/p&gt;
&lt;p&gt;To inspect which profiles are available, after rebuilding go to &lt;code&gt;/nix/store/&lt;/code&gt;, I
used Yazi to search for &lt;code&gt;/firejail&lt;/code&gt; and followed it to &lt;code&gt;firejail/etc&lt;/code&gt;, where the
profiles are.&lt;/p&gt;
&lt;p&gt;There are many flags and options available with firejail, I suggest checking out
&lt;code&gt;man firejail&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;There are comments explaining what’s going on in:
&lt;a href=&quot;https://github.com/NixOS/nixpkgs/blob/master/pkgs/by-name/fi/firejail/package.nix&quot;&gt;firejail/package.nix&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Firejail is a SUID program that reduces the risk of security breaches by
restricting the running environment of untrusted applications using
&lt;a href=&quot;https://lwn.net/Articles/531114/&quot;&gt;Linux namespaces&lt;/a&gt; and
&lt;a href=&quot;https://l3net.wordpress.com/2015/04/13/firejail-seccomp-guide/&quot;&gt;seccomp-bpf&lt;/a&gt;–&lt;a href=&quot;https://firejail.wordpress.com/&quot;&gt;Firejail Security Sandbox&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;It provides sandboxing and access restriction per application, much like what
AppArmor/SELinux does at a kernel level. However, it’s not as secure or
comprehensive as kernel-enforced MAC systems (AppArmor/SELinux), since it’s a
userspace tool and can potentially be bypassed by privilege escalation exploits.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Flatpak&lt;/h2&gt;
&lt;blockquote class=&quot;markdown-alert-note&quot;&gt;
&lt;p&gt;You cannot effectively use Firejail with Flatpak apps because of how
their sandboxing technologies operate. Flatpak also won’t work with the
hardened kernel because they require unprivileged user namespaces which the
hardened kernel completely disables.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.flatpak.org/en/latest/sandbox-permissions.html#permissions-guidelines&quot;&gt;Flatpak permissions &amp;amp; What they Do&lt;/a&gt;
Reference this while setting permissions with Flatseal, many apps come with
more permissions than they need to function effectively breaking the sandbox.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.flatpak.org/en/latest/sandbox-permissions.html#portals&quot;&gt;Portals&lt;/a&gt;
provide mediated, user-controlled access to host resources outside the
sandbox, so apps don’t need broad blanket permissions.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Apps that don’t have a flatpak equivalent can be further hardened with
bubblewrap independently but bubblewrap is not needed on Flatpak apps.&lt;/p&gt;
&lt;p&gt;Because of this limited native MAC (Mandatory Access Control) support on NixOS,
using Flatpak is often a good approach to get sandboxing and isolation for many
GUI apps.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Flatpak bundles runtimes and sandbox mechanisms that provide app isolation
independently of the host system’s AppArmor or SELinux infrastructure. This
can improve security and containment for GUI applications running on NixOS
despite the system lacking full native MAC coverage.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Flatpak apps benefit from sandboxing through bubblewrap, which isolate apps
and restrict access to user/home and system resources.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Add Flatpak with the FlatHub repository for all users:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;services.flatpak.enable = true;
  systemd.services.flatpak-repo = {
    wantedBy = [ &quot;multi-user.target&quot; ];
    path = [ pkgs.flatpak ];
    script = &apos;&apos;
      flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
      # Only apps that are verified
      # flatpak remote-add --if-not-exists --subset=verified flathub-verified https://flathub.org/repo/flathub.flatpakrepo
    &apos;&apos;;
  };
xdg = {
  portal = {
    enable = true;
    extraPortals = [ pkgs.xdg-desktop-portal-gtk ];
    config.common.default = [ &quot;gtk&quot; ];
  };
};
# Disables screencopy
systemd.user.services.&quot;xdg-desktop-portal-wlr&quot; = {
  enable = false;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ declarative-flatpak &lt;/summary&gt;
&lt;ol&gt;
&lt;li&gt;Add the flake input to your &lt;code&gt;flake.nix&lt;/code&gt;:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;inputs = {
  flatpaks.url = &quot;github:in-a-dil-emma/declarative-flatpak/latest&quot;;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;The following is a NixOS module that installs Firefox &amp;amp; Bitwarden:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# flatpak.nix
{
  pkgs,
  inputs,
  ...
}: {
  imports = [
    inputs.flatpaks.nixosModules.default
  ];
  services.flatpak = {
    enable = true;
    remotes = {
      &quot;flathub&quot; = &quot;https://dl.flathub.org/repo/flathub.flatpakrepo&quot;;
      # &quot;flathub-beta&quot; = &quot;https://dl.flathub.org/beta-repo/flathub-beta.flatpakrepo&quot;;
    };
    packages = [
      &quot;flathub:app/org.mozilla.firefox//stable&quot;
      &quot;flathub:app/com.bitwarden.desktop//stable&quot;
      # &quot;flathub-beta:app/org.kde.kdenlive/x86_64/stable&quot;
      # &quot;:${./foobar.flatpak}&quot;
      &quot;flathub:/root/testflatpak.flatpakref&quot;
    ];
    overrides = {
      # note: &quot;global&quot; is a flatpak thing
      # if you ever ran &quot;flatpak override&quot; without specifying a ref you will know
      &quot;global&quot;.Context = {
        filesystems = [
          &quot;home&quot;
        ];
        sockets = [
          &quot;!wayland&quot;
          &quot;!fallback-x11&quot;
        ];
      };
      &quot;org.mozilla.Firefox&quot; = {
        Environment = {
          &quot;MOZ_ENABLE_WAYLAND&quot; = 1;
        };
        Context.sockets = [
          &quot;!wayland&quot;
          &quot;!fallback-x11&quot;
          # &quot;x11&quot;
        ];
      };
    };
  };
  xdg.portal = {
    enable = true;
    extraPortals = [
      pkgs.xdg-desktop-portal-gtk
    ];
    config = {
      common.default = [&quot;gtk&quot;];
    };
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote class=&quot;markdown-alert-note&quot;&gt;
&lt;p&gt;I got the above configuration to build successfully, one of the hardening
steps I took isn’t allowing either app to launch. I’ll update once I find
which setting it is exactly. (01-13-26)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/details&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.flathub.org/docs/for-users/verification&quot;&gt;Flathub Verified Apps&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://secureblue.dev/articles/flatpak&quot;&gt;Flatpak the good the bad the ugly&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Then you can either find apps through &lt;a href=&quot;https://flathub.org/en&quot;&gt;FlatHub&lt;/a&gt; or on
the cmdline with &lt;code&gt;flatpak search &amp;lt;app&amp;gt;&lt;/code&gt;. Flatpak is best used for GUI apps, some
CLI apps can be installed through it but not all.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;There is also &lt;a href=&quot;https://github.com/gmodena/nix-flatpak&quot;&gt;nix-flatpak&lt;/a&gt;, which
enables you to manage your flatpaks declaratively.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://flathub.org/en/apps/com.github.tchx84.Flatseal&quot;&gt;Flatseal&lt;/a&gt; is GUI
utility that enables you to review and modify permissions from your Flatpak
apps. Many apps by default come with smart-card support, X11 &amp;amp; Wayland
support, and more, disabling unnecessary permissions is recommended.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://flathub.org/en/apps/io.github.flattool.Warehouse&quot;&gt;Warehouse&lt;/a&gt; provides
a simple UI to control complex Flatpak options, no cmdline required.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I have heard that it is not recommended to use Flatpak browsers because in order
for flatpak to work it has to disable some of the built-in browser sandboxing
which can reduce security. I haven’t found any examples of Flatpak browsers
being exploited but it’s something to keep in mind.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;SeLinux/AppArmor MAC (Mandatory Access Control)&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;AppArmor&lt;/strong&gt; is available on NixOS, but is still in a somewhat experimental and
evolving state. There are only a few profiles that have been adapted to NixOS,
see here
&lt;a href=&quot;https://discourse.nixos.org/t/apparmor-default-profiles/16780&quot;&gt;Discourse on default-profiles&lt;/a&gt;
Which guides you here
&lt;a href=&quot;https://github.com/NixOS/nixpkgs/blob/2acaef7a85356329f750819a0e7c3bb4a98c13fe/nixos/modules/security/apparmor/includes.nix&quot;&gt;apparmor/includes.nix&lt;/a&gt;
where you can see some of the abstractions and tunables to follow progress.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;SELinux&lt;/strong&gt;: Experimental, not fully integrated, recent progress for
advanced/curious users; expect rough edges and manual intervention if you want
to try it. Most find SELinux more complex to configure and maintain than
AppArmor.&lt;/p&gt;
&lt;p&gt;This isn’t meant to be a comprehensive guide, more to get people thinking about
security on NixOS.&lt;/p&gt;
&lt;p&gt;See the following guide on hardening networking:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://saylesss88.github.io/nix/hardening_networking.html&quot;&gt;Hardening Networking&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;Resources&lt;/h2&gt;
&lt;h3&gt;Advanced Hardening with &lt;code&gt;nix-mineral&lt;/code&gt; (Community Project)&lt;/h3&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Click to Expand section on `nix-mineral` &lt;/summary&gt;
&lt;p&gt;For users seeking a more comprehensive and opinionated approach to system
hardening beyond the built-in &lt;code&gt;hardened&lt;/code&gt; profile, the community project
&lt;a href=&quot;https://github.com/cynicsketch/nix-mineral&quot;&gt;&lt;code&gt;nix-mineral&lt;/code&gt;&lt;/a&gt; offers a declarative
NixOS module.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;nix-mineral&lt;/code&gt; aims to apply a wide array of security configurations, focusing on
tweaking kernel parameters, system settings, and file permissions to reduce the
attack surface.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Community Project Status:&lt;/strong&gt; &lt;code&gt;nix-mineral&lt;/code&gt; is a community-maintained project
and is not officially part of the Nixpkgs repository or NixOS documentation.
Its development status is explicitly stated as “Alpha software,” meaning it
may introduce stability issues or unexpected behavior.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For detailed information on &lt;code&gt;nix-mineral&lt;/code&gt;’s capabilities and current status,
refer directly to its
&lt;a href=&quot;https://github.com/cynicsketch/nix-mineral&quot;&gt;GitHub repository&lt;/a&gt;.&lt;/p&gt;
&lt;/details&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Click to Expand Resources &lt;/summary&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://hedgedoc.grimmauld.de/s/hWcvJEniW#&quot;&gt;AppArmor and apparmor.d on NixOS&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://tristanxr.com/post/selinux-on-nixos/&quot;&gt;SELinux on NixOS&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://xeiaso.net/blog/paranoid-nixos-2021-07-18/&quot;&gt;Paranoid NixOS&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://wiki.nixos.org/wiki/Security&quot;&gt;NixOS Wiki Security&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://nixos.org/manual/nixos/unstable/index.html#sec-luks-file-systems&quot;&gt;Luks Encrypted File Systems&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://discourse.nixos.org/t/a-modern-and-secure-desktop-setup/41154&quot;&gt;Discourse A Modern and Secure Desktop&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://notashelf.dev/posts/insecurities-remedies-i&quot;&gt;notashelf NixOS Security 1 Systemd&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://ryanseipp.com/post/hardening-nixos/&quot;&gt;ryanseipp hardening-nixos&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://madaidans-insecurities.github.io/guides/linux-hardening.html&quot;&gt;madaidans Linux Hardening Guide&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://cybersecuritynews.com/hardening-linux-servers&quot;&gt;Hardening-Linux-Servers&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://linux-audit.com/linux-server-hardening-most-important-steps-to-secure-systems/&quot;&gt;linux-audit Linux Server hardening best practices&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://linux-audit.com/linux-security-guide-extended-version/&quot;&gt;linux-audit Linux security guide extended&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://wiki.archlinux.org/title/Security&quot;&gt;Arch Wiki Security&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://wiki.gentoo.org/wiki/Security_Handbook/Concepts&quot;&gt;Gentoo Security_Handbook Concepts&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://secureblue.dev/faq&quot;&gt;secureblue FAQ&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.kicksecure.com/wiki/Documentation&quot;&gt;Excellent Kicksecure Docs&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/decalage2/awesome-security-hardening&quot;&gt;Awesome-Security-Hardening List&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://factorable.net/faq.html&quot;&gt;factorable.net (study of RSA and DSA crypto keys) FAQ&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://blog.cr.yp.to/20140205-entropy.html&quot;&gt;The cr.yp.to blog Entropy&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://delroth.net/posts/nixos-security-wishlist/&quot;&gt;NixOS Security wishlist&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://beej.us/guide/bgipc/html/&quot;&gt;Beejus IPC guide&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.geeksforgeeks.org/operating-systems/inter-process-communication-ipc/&quot;&gt;GeeksforGeeks IPC&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;neal.codes vulnerability scan script:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix-shell -p grype sbomnix --run &apos;
  sbomnix /run/current-system --csv /dev/null --spdx /dev/null --cdx sbom.cdx.json;
  grype sbom.cdx.json
&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/nealfennimore/nixos-stig-anduril&quot;&gt;neal.codes nixos-stig-anduril&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.suse.com/c/linux-hardeningthe-complete-guide-to-securing-your-systems/&quot;&gt;Suse Linux Hardening Guide&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Government Resources 1st 6 come from gentoo’s Security_Handbook)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.cyber.gov.au/sites/default/files/2023-03/Information%20Security%20Manual%20-%20%28March%202023%29.pdf&quot;&gt;The Austrailian Cyber Security Centre’s Informational Security Manual (ISM)&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.protectivesecurity.gov.au/policies&quot;&gt;The Australian Government’s Protective Security Policy Framework (PSPF)&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.cyber.gov.au/protect-yourself&quot;&gt;The Australian Cyber Security Centre’s Protect Yourself page&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.gov.uk/government/publications/security-policy-framework/hmg-security-policy-framework&quot;&gt;The UK Government’s Security Policy Framework (SPF)&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.gov.uk/government/publications/information-security-policy-framework&quot;&gt;The UK Government’s Information Security Policy Framework (ISF)&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.nist.gov/cybersecurity&quot;&gt;The US National Institute of Standards and Technology’s Cybersecurity page&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://stigviewer.com/stigs/anduril_nixos&quot;&gt;NixOS STIG&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;STIGs are configuration standards developed by the Defense Information Systems
Agency (DISA) to secure systems and software for the U.S. Department of
Defense (DoD). They are considered a highly authoritative source for system
hardening.There are recommendations for hardening all kinds of software in the
&lt;a href=&quot;https://stigviewer.com/stigs&quot;&gt;Stig Viewer&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.cisecurity.org/cis-benchmarks&quot;&gt;CIS Benchmarks&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/nsacyber&quot;&gt;NSA Cybersecurity Directorate&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/alam00000/bentopdf&quot;&gt;bentopdf&lt;/a&gt;: looks interesting, haven’t
checked it out yet though.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://media.defense.gov/2023/Sep/08/2003296489/-1/-1/0/WHITFIELD%20DIFFIE_CRYPTOLOGIC%20ICONOCLAST.PDF&quot;&gt;Non-Secret Encryption a “SECRET” no longer&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.nsa.gov/Cybersecurity/Post-Quantum-Cybersecurity-Resources/&quot;&gt;Post-Quantum Cybersecurity Resources&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/details&gt;
</content></entry><entry><title>Top-Level Attributes</title><id>https://saylesss88.github.io/Understanding_Top-Level_Attributes_5.html</id><updated>2026-05-31T00:00:00+00:00</updated><author><name>saylesss87@proton.me (saylesss88)</name></author><link href="https://saylesss88.github.io/Understanding_Top-Level_Attributes_5.html" rel="alternate"/><content type="html">&lt;h1&gt;Chapter 5&lt;/h1&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Click to Expand Table of Contents&lt;/summary&gt;
&lt;!-- toc --&gt;
&lt;/details&gt;
&lt;!-- ![coding1](images/coding1.png) --&gt;
&lt;img src=&quot;https://saylesss88.github.io/images/gruv9.png&quot; width=&quot;800&quot; height=&quot;600&quot;&gt;
&lt;h2&gt;Understanding Top-Level Attributes in NixOS Modules&lt;/h2&gt;
&lt;p&gt;This explanation is based on insights from Infinisil, a prominent figure in the
Nix community, to help clarify the concept of top-level attributes within NixOS
modules.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!NOTE] “top-level attributes” here refers to the attributes at the top level
of a module file (imports, options, config), not to be confused with
&lt;code&gt;system.build.toplevel&lt;/code&gt;, which is the final system derivation everything
builds toward.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;To understand why top-level module attributes matter, it helps to first
understand what they’re ultimately building toward: &lt;code&gt;system.build.toplevel&lt;/code&gt;, the
final derivation that represents your entire NixOS system.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;The Core of a NixOS System: &lt;code&gt;system.build.toplevel&lt;/code&gt;&lt;/h3&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ `system.build.toplevel` Explained (Click to Expand) &lt;/summary&gt;
&lt;p&gt;In a NixOS system, everything is built from a single “system derivation.” The
command &lt;code&gt;nix-build &apos;&amp;lt;nixpkgs/nixos&amp;gt;&apos; -A system&lt;/code&gt; initiates this build process.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;-A system&lt;/code&gt; part tells Nix to focus on the &lt;code&gt;system&lt;/code&gt; attribute defined in the
&lt;code&gt;&apos;&amp;lt;nixpkgs/nixos&amp;gt;&apos;&lt;/code&gt; file (which is essentially &lt;code&gt;./default.nix&lt;/code&gt; within the
Nixpkgs repository).&lt;/p&gt;
&lt;p&gt;This &lt;code&gt;system&lt;/code&gt; attribute is specifically the NixOS option &lt;code&gt;system.build.toplevel&lt;/code&gt;
. Think of &lt;code&gt;system.build.toplevel&lt;/code&gt; as the &lt;strong&gt;very top of the configuration
hierarchy&lt;/strong&gt; for your entire NixOS system. Almost every setting you configure
eventually influences this top-level derivation, often through a series of
intermediate steps.&lt;/p&gt;
&lt;/details&gt;
&lt;h3&gt;How Options Relate: A Chain of Influence&lt;/h3&gt;
&lt;p&gt;Options in NixOS are not isolated; they often build upon each other.&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;Example: Nginx Option Chain (Click to Expand)&lt;/summary&gt;
&lt;p&gt;Here’s an example of how a high-level option can lead down to a low-level system
configuration:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You enable Nginx with &lt;code&gt;services.nginx.enable = true;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;This setting influences the lower-level option &lt;code&gt;systemd.services.nginx&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Which, in turn, affects the even lower-level option
&lt;code&gt;systemd.units.&quot;nginx.service&quot;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Ultimately, this leads to the creation of a systemd unit file within
&lt;code&gt;environment.etc.&quot;systemd/system&quot;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Finally, this unit file ends up as &lt;code&gt;result/etc/systemd/system/nginx.service&lt;/code&gt;
within the final &lt;code&gt;system.build.toplevel&lt;/code&gt; derivation.&lt;/li&gt;
&lt;/ul&gt;
&lt;/details&gt;
&lt;h3&gt;The NixOS Module System: Evaluating Options&lt;/h3&gt;
&lt;p&gt;So, how do these options get processed and turned into the final system
configuration? That’s the job of the &lt;strong&gt;NixOS module system&lt;/strong&gt;, located in the
&lt;code&gt;./lib&lt;/code&gt; directory of Nixpkgs (specifically in &lt;code&gt;modules.nix&lt;/code&gt;, &lt;code&gt;options.nix&lt;/code&gt;, and
&lt;code&gt;types.nix&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Interestingly, the module system isn’t exclusive to NixOS; you can use it to
manage option sets in your own Nix projects.&lt;/p&gt;
&lt;p&gt;Here’s a simplified example of using the module system outside of NixOS:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;let
  systemModule = { lib, config, ... }: {
    options.toplevel = lib.mkOption {
      type = lib.types.str;
    };

    options.enableFoo = lib.mkOption {
      type = lib.types.bool;
      default = false;
    };

    config.toplevel = &apos;&apos;
      Is foo enabled? ${lib.boolToString config.enableFoo}
    &apos;&apos;;
  };

  userModule = {
    enableFoo = true;
  };

in (import &amp;lt;nixpkgs/lib&amp;gt;).evalModules {
  modules = [ systemModule userModule ];
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;You can evaluate the &lt;code&gt;config.toplevel&lt;/code&gt; option from this example using:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix-instantiate --eval file.nix -A config.toplevel
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h3&gt;How the Module System Works: A Simplified Overview&lt;/h3&gt;
&lt;p&gt;The module system processes a set of “modules” through these general steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Importing Modules&lt;/strong&gt;: It recursively finds and includes all modules
specified in &lt;code&gt;imports = [ ... ];&lt;/code&gt; statements.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Declaring Options&lt;/strong&gt;: It collects all option declarations defined using
&lt;code&gt;options = { ... };&lt;/code&gt; from all the modules and merges them. If the same option
is declared in multiple modules, the module system handles this (details
omitted for simplicity).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Defining Option Values&lt;/strong&gt;: For each declared option, it gathers all the
value assignments (defined using &lt;code&gt;config = { ... };&lt;/code&gt; or directly at the top
level if no &lt;code&gt;options&lt;/code&gt; or &lt;code&gt;config&lt;/code&gt; are present) from all modules and merges
them according to the option’s defined type.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;[!NOTE] Option evaluation is lazy, meaning an option’s value is only computed
when it’s actually needed. It can also depend on the values of other options.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;strong&gt;Top-Level Attributes in a Module: &lt;code&gt;imports&lt;/code&gt;, &lt;code&gt;options&lt;/code&gt;, and &lt;code&gt;config&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Within a NixOS module (the files that define parts of your system configuration)
, the attributes defined directly at the top level of the module’s function have
specific meanings:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;imports&lt;/code&gt;: This attribute is a list of other module files to include. Their
options and configurations will also be part of the evaluation.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;options&lt;/code&gt;: This attribute is where you declare new configuration options. You
define their type, default value, description, etc., using functions like
&lt;code&gt;lib.mkOption&lt;/code&gt; or &lt;code&gt;lib.mkEnableOption&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;config&lt;/code&gt;: This attribute is where you assign values to the options that have
been declared (either in the current module or in imported modules).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;The Rule: Move Non-Option Attributes Under &lt;code&gt;config&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If you define either an &lt;code&gt;options&lt;/code&gt; or a &lt;code&gt;config&lt;/code&gt; attribute at the top level of
your module, any other attributes that are not option declarations must be moved
inside the config attribute.&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Examples of Correct and Incorrect Usage (Click to Expand)&lt;/summary&gt;
&lt;p&gt;Let’s look at an example of what not to do:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{ pkgs, lib, config, ... }:
{
imports = [];

# Defining an option at the top level

options.mine.desktop.enable = lib.mkEnableOption &quot;desktop settings&quot;;

# This will cause an error because &apos;environment&apos; and &apos;appstream&apos;

# are not &apos;options&apos; and &apos;config&apos; is also present at the top level.

environment.systemPackages =
lib.mkIf config.appstream.enable [ pkgs.git ];

appstream.enable = true;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will result in the error:
&lt;code&gt;error: Module has an unsupported attribute &apos;appstream&apos; This is caused by introducing a top-level &apos;config&apos; or &apos;options&apos; attribute. Add configuration attributes immediately on the top level instead, or move all of them into the explicit &apos;config&apos; attribute&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Correct Way&lt;/strong&gt;: Using the &lt;code&gt;config&lt;/code&gt; Attribute&lt;/p&gt;
&lt;p&gt;To fix the previous example, you need to move the value assignments for
&lt;code&gt;environment.systemPackages&lt;/code&gt; and &lt;code&gt;appstream.enable&lt;/code&gt; inside the config attribute:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{ pkgs, lib, config, ... }:
{
imports = [];

# Defining an option at the top level

options.mine.desktop.enable = lib.mkEnableOption &quot;desktop settings&quot;;

config = {
environment.systemPackages =
lib.mkIf config.appstream.enable [ pkgs.git ];

    appstream.enable = true;

};
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, Nix knows that you are declaring an option (&lt;code&gt;options.mine.desktop.enable&lt;/code&gt;)
and then setting values for other options (&lt;code&gt;environment.systemPackages&lt;/code&gt;,
&lt;code&gt;appstream.enable&lt;/code&gt;) within the &lt;code&gt;config&lt;/code&gt; block.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Implicit &lt;code&gt;config&lt;/code&gt;: When &lt;code&gt;options&lt;/code&gt; is Absent&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If your module does not define either &lt;code&gt;options&lt;/code&gt; or &lt;code&gt;config&lt;/code&gt; at the top level,
then any attributes you define directly at the top level are implicitly treated
as being part of the config.&lt;/p&gt;
&lt;p&gt;For example, this is valid:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{ pkgs, lib, config, ... }:
{
environment.systemPackages =
lib.mkIf config.appstream.enable [ pkgs.git ];

appstream.enable = true;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Nix will implicitly understand that &lt;code&gt;environment.systemPackages&lt;/code&gt; and
&lt;code&gt;appstream.enable&lt;/code&gt; are configuration settings.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Removing an Option: What Happens to &lt;code&gt;config&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Even if you remove the &lt;code&gt;options&lt;/code&gt; declaration from a module that has a &lt;code&gt;config&lt;/code&gt;
section, the &lt;code&gt;config = { environment.systemPackages = ... };&lt;/code&gt; part will still
function correctly, assuming the option it’s referencing (&lt;code&gt;appstream.enable&lt;/code&gt; in
this case) is defined elsewhere (e.g., in an imported module).&lt;/p&gt;
&lt;/details&gt;
&lt;h4&gt;Conclusion&lt;/h4&gt;
&lt;p&gt;Understanding the nuances of top-level attributes within NixOS modules,
particularly &lt;code&gt;imports&lt;/code&gt;, &lt;code&gt;options&lt;/code&gt;, and &lt;code&gt;config&lt;/code&gt;, is fundamental to structuring
and managing your system’s configuration effectively. As we’ve seen, the module
system provides a powerful and declarative way to define and evaluate system
settings, ultimately contributing to the construction of the
&lt;code&gt;system.build.toplevel&lt;/code&gt; derivation that represents your entire NixOS
environment.&lt;/p&gt;
&lt;p&gt;The concepts of option declaration and value assignment, along with the crucial
rule of organizing non-option attributes under the &lt;code&gt;config&lt;/code&gt; attribute when
&lt;code&gt;options&lt;/code&gt; is present, provide a clear framework for building modular and
maintainable configurations.&lt;/p&gt;
&lt;p&gt;Now that we have a solid grasp of how NixOS modules are structured and how they
contribute to the final system derivation, it’s a natural next step to explore
the tangible results of these configurations: the software and system components
themselves. These are built and managed by a core concept in Nix, known as
&lt;strong&gt;derivations&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;In the next chapter,
&lt;a href=&quot;https://saylesss88.github.io/Package_Definitions_Explained_6.html&quot;&gt;Package Definitions Explained&lt;/a&gt;
we will shift our focus from the abstract configuration to the concrete software
packages. We will learn how Nix uses &lt;em&gt;package definitions&lt;/em&gt; to create
&lt;em&gt;derivations&lt;/em&gt;, which are the actual build plans that produce the software we use
on our NixOS systems. This will bridge the gap between configuring your system
and understanding how the software within it is managed.&lt;/p&gt;
</content></entry><entry><title>Practical Jujutsu</title><id>https://saylesss88.github.io/vcs/practical_jj.html</id><updated>2026-03-16T00:00:00+00:00</updated><author><name>saylesss87@proton.me (saylesss88)</name></author><link href="https://saylesss88.github.io/vcs/practical_jj.html" rel="alternate"/><content type="html">&lt;h1&gt;Practical Jujutsu&lt;/h1&gt;
&lt;p&gt;This post assumes basic understanding of Git and GitHub.&lt;/p&gt;
&lt;p&gt;I’ve spent enough time hovering between the familiarity of Git and the potential
of Jujutsu. It’s time to move past a ‘primitive’ workflow. By truly mastering
one of these tools, I want to turn version control from a chore into a way to
precisely navigate my development stages and build a history that future
contributors can actually follow.&lt;/p&gt;
&lt;p&gt;JJ simplifies keeping a linear history and makes it easy to break down big
changes into smaller atomic changes.&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt; Atomic commits &amp; Linear History explained &lt;/summary&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/angular/angular/blob/main/contributing-docs/commit-message-guidelines.md&quot;&gt;Angular Commit Message Format&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;&amp;lt;header&amp;gt;
&amp;lt;BLANK LINE&amp;gt;
&amp;lt;body&amp;gt;
&amp;lt;BLANK LINE&amp;gt;
&amp;lt;footer&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;header&lt;/code&gt; is mandatory.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;body&lt;/code&gt; is mandatory for all commits except “doc” type commits.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;footer&lt;/code&gt; is optional&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;Atomic Commits&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;An atomic commit is a single unit of work that cannot be broken down further
without losing its meaning.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;One commit should do one thing.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If you had to “undo” that commit later, would it break unrelated features? If
“Yes,” it’s not atomic.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you find a bug, you can pinpoint the exact 10 lines of code that caused it.
In &lt;code&gt;jj&lt;/code&gt;, the &lt;code&gt;split -i&lt;/code&gt; and &lt;code&gt;commit -i&lt;/code&gt; commands are the ultimate tools for
“atomizing” a messy afternoon of coding.&lt;/p&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;Linear History&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;A linear history is a straight line of commits without “merge bubbles” (those
criss-crossing lines you see in Git logs when people use git merge).&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Every commit has exactly one parent and one child.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;It reads like a story. You can follow the evolution of the project from bottom
to top without getting lost in a maze of branches.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;jj&lt;/code&gt; defaults to a rebase-heavy workflow. Instead of “merging” your work and
creating a mess, you are constantly “sliding” your changes on top of the
latest work, keeping that line perfectly straight.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can even insert a commit anywhere in your history with &lt;code&gt;jj new -A&lt;/code&gt;
(&lt;code&gt;--insert-after&lt;/code&gt;), and &lt;code&gt;jj new -B&lt;/code&gt; (&lt;code&gt;--insert-before&lt;/code&gt;) and JJ will rebase every
child in the history.&lt;/p&gt;
&lt;p&gt;Use &lt;code&gt;jj show -r &amp;lt;revision&amp;gt;&lt;/code&gt; to see a diff of the changes made at that Change ID,
and trivially craft it with &lt;code&gt;jj edit -r &amp;lt;revision&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;/details&gt;
&lt;p&gt;Let’s learn about &lt;code&gt;jj&lt;/code&gt; by using it to version control a system Nix Flake.&lt;/p&gt;
&lt;h2&gt;Quick Overview&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.jj-vcs.dev/latest/glossary/&quot;&gt;Jujutsu docs Glossary&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;details&gt;
&lt;summary&gt;Key Terms&lt;/summary&gt;
&lt;blockquote&gt;
&lt;p&gt;“One of the first things to wrap your head around when first coming to Jujutsu
is its approach to its revisions and revsets, i.e. “sets of revision”.
Revisions are the fundamental elements of changes in Jujutsu, not “commits” as
in Git. Revsets are then expressions in a functional language for selecting a
set of revisions.”
–&lt;a href=&quot;https://v5.chriskrycho.com/essays/jj-init/&quot;&gt;Chris Krycho jj init&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Term&lt;/th&gt;&lt;th&gt;What it is&lt;/th&gt;&lt;th&gt;Git Equivalent&lt;/th&gt;&lt;th&gt;JJ Behavior&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Working Copy (&lt;code&gt;@&lt;/code&gt;)&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Your current editable commit. &lt;strong&gt;Everything you do affects &lt;code&gt;@&lt;/code&gt; by default.&lt;/strong&gt; Auto‑amends as you save files.&lt;/td&gt;&lt;td&gt;Untracked files + staging area + HEAD&lt;/td&gt;&lt;td&gt;Always a full commit. No staging. &lt;code&gt;jj st&lt;/code&gt; shows changes relative to &lt;code&gt;@-&lt;/code&gt;.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Change ID&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Stable ID for a logical unit of work (the &lt;code&gt;k&lt;/code&gt;, &lt;code&gt;y&lt;/code&gt; labels in logs). Survives edits/rebases.&lt;/td&gt;&lt;td&gt;N/A&lt;/td&gt;&lt;td&gt;Prefix like &lt;code&gt;k&lt;/code&gt; or &lt;code&gt;y&lt;/code&gt;. Use &lt;code&gt;jj edit k1234&lt;/code&gt; to jump to any change.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Commit ID&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Unique ID for a specific snapshot (the long hex like &lt;code&gt;41deb985&lt;/code&gt;). Changes when you amend.&lt;/td&gt;&lt;td&gt;Commit hash&lt;/td&gt;&lt;td&gt;Full ID for exact snapshots. Rarely used directly.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Bookmarks&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Named pointers to commits (like &lt;code&gt;main&lt;/code&gt;, &lt;code&gt;feature-x&lt;/code&gt;). &lt;strong&gt;Don’t auto‑move&lt;/strong&gt; like Git branches.&lt;/td&gt;&lt;td&gt;Branches&lt;/td&gt;&lt;td&gt;&lt;code&gt;jj bookmark set main -r @&lt;/code&gt; moves it. &lt;code&gt;*&lt;/code&gt; shows if local/remote match.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Parent (&lt;code&gt;@-&lt;/code&gt;)&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;The commit &lt;code&gt;@&lt;/code&gt; is built on top of.&lt;/td&gt;&lt;td&gt;Previous commit&lt;/td&gt;&lt;td&gt;Use &lt;code&gt;-r @-&lt;/code&gt; to target it. Key for squash workflow.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Immutable (&lt;code&gt;◆&lt;/code&gt;)&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Commits that shouldn’t be rewritten (pushed changes, trunk).&lt;/td&gt;&lt;td&gt;Protected branches&lt;/td&gt;&lt;td&gt;&lt;code&gt;jj log&lt;/code&gt; shows &lt;code&gt;◆&lt;/code&gt;. Still editable with force flags.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Revset&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Query language for commits (&lt;code&gt;main..@&lt;/code&gt;, &lt;code&gt;mine()&lt;/code&gt;).&lt;/td&gt;&lt;td&gt;&lt;code&gt;git log --grep&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Super powerful. &lt;code&gt;jj log -r &quot;main..@&quot;&lt;/code&gt; = changes since main.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Revision&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;“revision” is synonymous with “commit”&lt;/td&gt;&lt;td&gt;Commit&lt;/td&gt;&lt;td&gt;A synonym for Commit&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Pro tip&lt;/strong&gt;: &lt;code&gt;@&lt;/code&gt; is &lt;strong&gt;always&lt;/strong&gt; your current position. &lt;code&gt;jj new&lt;/code&gt;, &lt;code&gt;jj desc&lt;/code&gt;,
&lt;code&gt;jj squash&lt;/code&gt; all default to it. &lt;strong&gt;Bookmarks like &lt;code&gt;main&lt;/code&gt; are just labels&lt;/strong&gt; - move
them explicitly with &lt;code&gt;jj bookmark set&lt;/code&gt;.&lt;/p&gt;
&lt;/details&gt;
&lt;p&gt;See
&lt;a href=&quot;https://zerowidth.com/2025/jj-tips-and-tricks/&quot;&gt;zerowidths jj-tips-and-tricks&lt;/a&gt;,
for a more intuitive &lt;code&gt;--interactive&lt;/code&gt; workflow (The &lt;code&gt;git add -p&lt;/code&gt; Hunk-wise
style).&lt;/p&gt;
&lt;h2&gt;Getting Started&lt;/h2&gt;
&lt;p&gt;It’s helpful to grasp a few Git concepts to fully understand some of the
benefits and strengths of jujutsu. I highly suggest reading
&lt;a href=&quot;https://www.w3tutorials.net/blog/how-to-compare-the-working-copy-staging-copy-and-committed-copy-of-a-file-using-git/&quot;&gt;W3Tutorials.net How to Compare Working Copy, Staging Copy, and Committed Copy of a File in Git&lt;/a&gt;,
it covers most of the concepts that make understanding &lt;code&gt;jj&lt;/code&gt; easier.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The &lt;strong&gt;Working Copy&lt;/strong&gt; is the version of the file you’re actively editing in
your filesystem. It’s the “live” version you see in your text editor or IDE.
–W3Tutorials.net&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.jj-vcs.dev/latest/working-copy/&quot;&gt;Jujutsu’s Working copy commit&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;“Unlike most other VCSs, Jujutsu will automatically create commits from the
working-copy contents when they have changed. Most &lt;code&gt;jj&lt;/code&gt; commands you run will
commit the working-copy changes if they have changed. The resulting revision
will replace the previous working-copy revision.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In Git, &lt;code&gt;HEAD&lt;/code&gt; is a pointer (usually to a branch like &lt;code&gt;main&lt;/code&gt;), and that branch
points to the current commit.&lt;/p&gt;
&lt;p&gt;In jj, the working copy &lt;code&gt;@&lt;/code&gt; is itself the current commit. Bookmarks like &lt;code&gt;main&lt;/code&gt;
simply point at other commits in the graph, so your working copy is often a
child of a bookmark, but it can also be off on its own.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;❯&lt;/code&gt; will indicate a command that I ran, the rest is output.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let’s start by cloning a project of mine to see how &lt;code&gt;jj&lt;/code&gt; works:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;❯ jj git clone git@github.com:sayls8/nix-snake.git
Fetching into new repo in &quot;/home/jr/projects/nix-snake&quot;
remote: Enumerating objects: 88, done.
remote: Total 88 (delta 38), reused 78 (delta 30), pack-reused 0 (from 0)
bookmark: main@origin [new] tracked
Setting the revset alias `trunk()` to `main@origin`
Working copy  (@) now at: s f143a1da (empty) (no description set)
Parent commit (@-)      : l 3b759dfe main | feat: fix clippy lints &amp;amp; optimize
Added 12 files, modified 0 files, removed 0 files
Hint: Running `git clean -xdf` will remove `.jj/`!
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;We can see above that the &lt;code&gt;main@origin&lt;/code&gt; bookmark is automatically tracked with
a revset alias of &lt;code&gt;trunk()&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;NOTE: My jj commands show the shortest possible IDs because of the setting:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;programs.jujutsu.settings = {
   template-aliases = {
       &quot;format_short_change_id(id)&quot; = &quot;id.shortest()&quot;;
   };
};
&lt;/code&gt;&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;p&gt;Running &lt;code&gt;jj st&lt;/code&gt; &amp;amp; &lt;code&gt;jj log&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;❯  jj st
The working copy has no changes.
Working copy  (@) : s f143a1da (empty) (no description set)
Parent commit (@-): l 3b759dfe main | feat: fix clippy lints &amp;amp; optimize

❯  jj log
@  s sayls8@proton.me 2026-03-21 16:31:59 f143a1da
│  (empty) (no description set)
◆  l sayls8@proton.me 2026-01-29 16:50:51 main 3b759dfe
│  feat: fix clippy lints &amp;amp; optimize
~
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To show all ancestors of the most recent commit, &lt;code&gt;s&lt;/code&gt; in this case:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;jj log -r ::s
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The &lt;code&gt;@&lt;/code&gt; indicates the working-copy commit. The first ID on a line (e.g. “s”
above) is the change ID. The second ID is the commit ID (“f143a1da”). You can
give either ID to commands that take revisions as arguments.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;jj log&lt;/code&gt; defaults to the &lt;code&gt;ui.default-revset&lt;/code&gt; setting, or
&lt;code&gt;@ | ancestors(immutable_heads().., 2) | heads(immutable_heads())&lt;/code&gt; if it’s not
set. (A revset)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;jj&lt;/code&gt; commands default to operating on the working copy, &lt;code&gt;@&lt;/code&gt;. &lt;code&gt;jj undo&lt;/code&gt;, undoes
your previous &lt;code&gt;jj&lt;/code&gt; command. List all previous &lt;code&gt;jj&lt;/code&gt; commands with &lt;code&gt;jj op log&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In JJ, you are never “on” a branch. You are always “on” a specific change,
building a stack of changes. Until you push those changes, you can continue to
jump around and edit them with &lt;code&gt;jj edit&lt;/code&gt;. Once you push those changes to a
remote, they then become immutable.&lt;/p&gt;
&lt;p&gt;You don’t have to merge &lt;code&gt;Change A&lt;/code&gt; into &lt;code&gt;Change B&lt;/code&gt;, because &lt;code&gt;Change B&lt;/code&gt; is
already built on top of &lt;code&gt;Change A&lt;/code&gt;. It inherits every line of code from the
floors below it.&lt;/p&gt;
&lt;p&gt;In Git, the working copy is the files on disk; changes only become part of the
next commit when you stage them in the index with git add. In Jujutsu, the
working copy is the current commit: your edits live in a “working copy commit”
(&lt;code&gt;@&lt;/code&gt;), which &lt;code&gt;jj&lt;/code&gt; automatically updates from the files on disk, and there is no
separate staging area.&lt;/p&gt;
&lt;p&gt;When you’re ready to push to GitHub, make sure you know where your changes are.
In the example above, the working copy is empty, so to push I’d run
&lt;code&gt;jj bookmark set main -r @-&lt;/code&gt; to point the &lt;code&gt;main&lt;/code&gt; bookmark at the latest changes,
then &lt;code&gt;jj git push&lt;/code&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;jj git push&lt;/code&gt;: By default, pushes tracking bookmarks pointing to
&lt;code&gt;remote_bookmarks(remote=&amp;lt;remote&amp;gt;)..@&lt;/code&gt;. Use &lt;code&gt;--bookmark&lt;/code&gt; to push specific
bookmarks. Use &lt;code&gt;--all&lt;/code&gt; to push all bookmarks. Use &lt;code&gt;--change&lt;/code&gt; to generate
bookmark names based on the change IDs of specific commits.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If the working copy isn’t empty and those changes are what I want to push, I’d
instead run &lt;code&gt;jj bookmark set main -r @&lt;/code&gt;, followed by &lt;code&gt;jj git push&lt;/code&gt;. Once you
understand this distinction, the rest of the workflow feels fairly intuitive.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;jj&lt;/code&gt; is smart enough to know that: If &lt;code&gt;main&lt;/code&gt; is on your Working Copy (&lt;code&gt;@&lt;/code&gt;) and
you have uncommitted changes, it pushes those.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If your Working Copy (&lt;code&gt;@&lt;/code&gt;) is empty , and &lt;code&gt;main&lt;/code&gt; is on the Parent (&lt;code&gt;@-&lt;/code&gt;), it
pushes the parent.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In the above example, if I remove the &lt;code&gt;config&lt;/code&gt; argument to the
&lt;code&gt;configuration.nix&lt;/code&gt; and remove a few comments, then run &lt;code&gt;jj diff&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://saylesss88.github.io/../images/jj-diff.png&quot; alt=&quot;jj diff&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The &lt;code&gt;squash&lt;/code&gt; workflow would benefit especially from &lt;code&gt;jj diff&lt;/code&gt;. It’s like
saying “take the diff I’m looking at right now and bake it directly into the
parent”.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A &lt;strong&gt;change&lt;/strong&gt; is a commit that can evolve while keeping a stable identifier,
the &lt;strong&gt;change ID&lt;/strong&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;Version Control Best Practices&lt;/h2&gt;
&lt;p&gt;It’s helpful to use
&lt;a href=&quot;https://www.conventionalcommits.org/en/v1.0.0/&quot;&gt;Conventional Commits&lt;/a&gt;, a set of
rules for creating an explicit commit history.&lt;/p&gt;
&lt;p&gt;Commit message standard syntax:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;&amp;lt;type&amp;gt;[optional scope]: &amp;lt;description&amp;gt;

[optional body]

[optional footer(s)]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;feat: implement dendretic pattern for boot module
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Useful Utils&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;There is a new project that helps you create conventional commits for jj,
&lt;a href=&quot;https://crates.io/crates/jj-commit&quot;&gt;jj-commit&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://crates.io/crates/commitlint-rs&quot;&gt;commitlint-rs&lt;/a&gt; can be used as a Git
hook on push to enforce conventional commits.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/semantic-release/semantic-release&quot;&gt;semantic-release&lt;/a&gt;
automates the whole package release workflow.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Using Tags&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Tags are just named pointers to commits. Create one pointing to your latest
change:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;jj st
The working copy has no changes.
Working copy  (@) : ysrlmzlt 7d093162 (empty) (no description set)
Parent commit (@-): wmuwoply ea03805e chore: add &apos;release version&apos; to justfile
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;jj tag set v0.1.5 --revision @-
jj git push --tags
cargo publish
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When you publish next, create a new tag &lt;code&gt;v0.1.6&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The simple order:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Write code, commit, &lt;code&gt;jj git push&lt;/code&gt; as normal (many times)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;When ready to release:&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;bump version in &lt;code&gt;Cargo.toml&lt;/code&gt; to &lt;code&gt;0.1.6&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;update CHANGELOG&lt;/li&gt;
&lt;li&gt;commit&lt;/li&gt;
&lt;li&gt;&lt;code&gt;jj git push&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;jj tag set v0.1.6 --revision @-&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;jj git push --tag v0.1.6&lt;/code&gt; # This only pushes the tag, not the commit again&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cargo publish&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That’s it. Tags only appear at step 2, once per release.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;One tag per release, on the commit you publish. Everything in between is just
normal commits with no tags involved.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You can also use &lt;code&gt;-dev&lt;/code&gt; tags between releases. Right after the release, set your
version in your &lt;code&gt;Cargo.toml&lt;/code&gt; to &lt;code&gt;0.1.7-dev&lt;/code&gt; and that will make clear which
commit’s haven’t been published yet.&lt;/p&gt;
&lt;h3&gt;The edit workflow&lt;/h3&gt;
&lt;p&gt;Initialize and colocate the repository:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;❯  mkdir learn-jj

  ~/projects
❯  cd learn-jj


  ~/projects/learn-jj
❯  nix flake new . -t github:nix-community/home-manager#nixos
wrote: &quot;/home/jr/projects/learn-jj/flake.nix&quot;

  ~/projects/learn-jj  ✗
❯  jj git init --colocate
Initialized repo in &quot;.&quot;
Hint: Running `git clean -xdf` will remove `.jj/`!

  learn-jj   main [?]
❯  jj git remote add origin git@github.com:sayls8/learn-jj.git

  learn-j   main [?]
❯  jj bookmark create main -r @
Done importing changes from the underlying Git repo.
Created 1 bookmarks pointing to l bd3847c0 main | (no description set)

  learn-jj   refs/jj/root [!]
❯  jj bookmark track main --remote=origin
Started tracking 1 remote bookmarks.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let’s give it our current change a description:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;❯  jj desc -m &quot;chore: Initialize system flake&quot;
Working copy  (@) now at: l 743b170d main* | chore: Initialize system flake
Parent commit (@-)      : z 00000000 (empty) (no description set)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this example, the Parent commit is the &lt;em&gt;root commit&lt;/em&gt;. The root commit is a
virtual commit at the root of every repository. It has a commit ID consisting of
all ’0’s (&lt;code&gt;00000000...&lt;/code&gt;) and a change ID consisting of all ’z’s (&lt;code&gt;zzzzzzzz...&lt;/code&gt;).
It can be referred to in revsets by the function &lt;code&gt;root()&lt;/code&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;With this workflow, your working copy is typically at your current change.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;JJ treats the working copy as a commit rather than having an index like Git.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If we wanted to push right now we could with &lt;code&gt;jj git push&lt;/code&gt; (or equivalently
&lt;code&gt;jj git push --bookmark main&lt;/code&gt;), but let’s first learn a bit more about how &lt;code&gt;jj&lt;/code&gt;
works.&lt;/p&gt;
&lt;p&gt;Let’s say we’re done with the current change and we’re ready to make it
immutable and start a new change:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;❯ jj new -m &quot;chore: change username &amp;amp; hostname in flake.nix&quot;
Working copy  (@) now at: p 6524f35a (empty) chore: change username &amp;amp; hostname in flake.nix
Parent commit (@-)      : l 743b170d main* | chore: Initialize system flake
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;As stated above, &lt;code&gt;jj&lt;/code&gt; commands default to the working copy. So &lt;code&gt;jj new&lt;/code&gt; is the
same as &lt;code&gt;jj new -r @&lt;/code&gt;. By running &lt;code&gt;jj new&lt;/code&gt; repeatedly, we build a linear stack
where each change is a child of the previous one. When we’re ready to ‘check
in’ our work, we don’t merge; we simply move the &lt;code&gt;main&lt;/code&gt; bookmark to our
current position and push.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If we want to start a separate task without including our current work, we
can run &lt;code&gt;jj new main&lt;/code&gt; (or any other Change ID). This creates a sibling
change. Our previous stack isn’t “lost”; it stays exactly where it was in
the graph, waiting to be described, rebased, or merged later.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Now our Working copy &lt;code&gt;@&lt;/code&gt; is at an &lt;code&gt;(empty)&lt;/code&gt; change with the description
“chore: change username &amp;amp; hostname in flake.nix”.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;As you can see, running &lt;code&gt;jj new&lt;/code&gt;, does not move the &lt;code&gt;main&lt;/code&gt; bookmark. This is
the hardest part to grasp when coming from Git IMO. Let’s make some more
changes to hammer this home.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I’ve added my hostname and username to the &lt;code&gt;flake.nix&lt;/code&gt; template, let’s make them
a part of the permanent record.&lt;/p&gt;
&lt;p&gt;I create a minimal &lt;code&gt;configuration.nix&lt;/code&gt;, check my status and notice that I forgot
to run &lt;code&gt;jj new -m &quot;feat: create minimal configuration.nix&quot;&lt;/code&gt;. Let’s see how to
recover from this and keep our commits atomic.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;jj split -i
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;This opens up a diff editor, I’ll only press &lt;code&gt;y&lt;/code&gt; for the changes related to
username and hostname. After you pass on what you don’t want in this change
and press &lt;code&gt;y&lt;/code&gt; on what you do want, your $EDITOR will open with your previous
commit message. Save it and another commit message will open up in $EDITOR,
this is whatever you didn’t press &lt;code&gt;y&lt;/code&gt; on i.e., the &lt;code&gt;configuration.nix&lt;/code&gt;
changes, just give the second set of changes a different description and
you’re all set.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Another cool thing about &lt;code&gt;jj&lt;/code&gt; is that you can add a description whenever you
want. Running &lt;code&gt;jj desc -m &quot;add configuration.nix&quot;&lt;/code&gt; doesn’t finalize your commit
like it does with Git. So, you can put the description first, last, or in the
middle of a current change with no issue. The equivalent command to
&lt;code&gt;git commit -m &quot;message&quot;&lt;/code&gt; is &lt;code&gt;jj commit -m &quot;message&quot; &amp;amp;&amp;amp; jj new&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Let’s see what the &lt;code&gt;jj split -i&lt;/code&gt; command left us with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;❯  jj
Working copy changes:
A configuration.nix
Working copy  (@) : m b3cc09db feat: create minimal configuration.nix
Parent commit (@-): p cddde3b4 chore: change username &amp;amp; hostname in flake.nix
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;To see a diff of the changes in the parent commit:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;jj show -r @-
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And our &lt;code&gt;log&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;❯  jj log
@  m sayles8@proton.me 2026-03-15 13:43:31 b3cc09db
│  feat: create minimal configuration.nix
○  p sayls8@proton.me 2026-03-15 13:41:19 cddde3b4
│  chore: change username &amp;amp; hostname in flake.nix
○  l sayls8@proton.me 2026-03-15 13:37:29 main* 743b170d
│  chore: Initialize system flake
◆  z root() 00000000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see, &lt;code&gt;main*&lt;/code&gt; is all the way back at change &lt;code&gt;l&lt;/code&gt;. Let’s move our &lt;code&gt;main&lt;/code&gt;
bookmark to our current Working copy.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;❯  jj bookmark set main -r @
Moved 1 bookmarks to m b3cc09db main* | feat: create minimal configuration.nix
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;❯  jj log
@  m sayls8@proton.me 2026-03-15 13:43:31 main* b3cc09db
│  feat: create minimal configuration.nix
○  p sayls8@proton.me 2026-03-15 13:41:19 cddde3b4
│  chore: change username &amp;amp; hostname in flake.nix
○  l sayls8@proton.me 2026-03-15 13:37:29 743b170d
│  chore: Initialize system flake
◆  z root() 00000000
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;❯  jj git push
Changes to push to origin:
  Add bookmark main to b3cc09dba32c
git: Enumerating objects: 9, done.
git: Counting objects: 100% (9/9), done.
git: Delta compression using up to 16 threads
git: Compressing objects: 100% (7/7), done.
git: Writing objects: 100% (9/9), 1.99 KiB | 1019.00 KiB/s, done.
git: Total 9 (delta 1), reused 0 (delta 0), pack-reused 0 (from 0)
remote: Resolving deltas: 100% (1/1), done.
Warning: The working-copy commit in workspace &apos;default&apos; became immutable, so a new commit has been created on top of it.
Working copy  (@) now at: u 551e83ad (empty) (no description set)
Parent commit (@-)      : m b3cc09db main | feat: create minimal configuration.nix
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;❯  jj log
@  u sayls8@proton.me 2026-03-15 13:47:27 551e83ad
│  (empty) (no description set)
◆  m sayls8@proton.me 2026-03-15 13:43:31 main b3cc09db
│  feat: create minimal configuration.nix
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Notice &lt;code&gt;jj log&lt;/code&gt; now shows &lt;code&gt;main&lt;/code&gt; instead of &lt;code&gt;main*&lt;/code&gt;, indicating that &lt;code&gt;main&lt;/code&gt;
and &lt;code&gt;origin@main&lt;/code&gt; are in sync!&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Also notice the &lt;code&gt;◆&lt;/code&gt; next to the &lt;code&gt;m&lt;/code&gt; change, this indicates that this change is
now immutable. This is mentioned in the output of &lt;code&gt;jj git push&lt;/code&gt; above.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;jj&lt;/code&gt; does this so you don’t accidentally rewrite history that others might
have pulled. You can still force-edit if you need to but it’s &lt;code&gt;jj&lt;/code&gt;s way of
saying, “This is now part of the public record”.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I now need to add a minimal &lt;code&gt;home.nix&lt;/code&gt;, then run &lt;code&gt;nix flake check&lt;/code&gt; to see if I
forgot anything.&lt;/p&gt;
&lt;p&gt;If something isn’t being picked up by &lt;code&gt;jj&lt;/code&gt; try running &lt;code&gt;jj st&lt;/code&gt; and check again.
Running any &lt;code&gt;jj&lt;/code&gt; command updates the Working copy.&lt;/p&gt;
&lt;p&gt;Since when running &lt;code&gt;jj git push&lt;/code&gt; &lt;code&gt;jj&lt;/code&gt; automatically creates a new commit on top
of the last one, the next step is to describe this change.&lt;/p&gt;
&lt;p&gt;I ran &lt;code&gt;nix flake check&lt;/code&gt; and needed to add a &lt;code&gt;hardware-configuration.nix&lt;/code&gt;, and
&lt;code&gt;networking.hostId&lt;/code&gt; required by ZFS, if I wanted to be a stickler about atomic
commits I’d run &lt;code&gt;jj split -i&lt;/code&gt; again but it’s fine by me to make 2 small changes
to get the flake to pass the &lt;code&gt;check&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;The squash Workflow&lt;/h2&gt;
&lt;p&gt;The last section left me with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;❯  jj st
Working copy changes:
M configuration.nix
A flake.lock
A hardware-configuration.nix
A home.nix
Working copy  (@) : u e57a7a39 feat: add minimal home.nix
Parent commit (@-): m b3cc09db main | feat: create minimal configuration.nix
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let’s push what we have:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;❯  jj bookmark set main -r @
Moved 1 bookmarks to u e57a7a39 main* | feat: add minimal home.nix

  learn-jj   HEAD [!]
❯  jj git push
Changes to push to origin:
  Move forward bookmark main from b3cc09dba32c to e57a7a39957a
git: Enumerating objects: 8, done.
git: Counting objects: 100% (8/8), done.
git: Delta compression using up to 16 threads
git: Compressing objects: 100% (6/6), done.
git: Writing objects: 100% (6/6), 1.98 KiB | 1.98 MiB/s, done.
git: Total 6 (delta 1), reused 0 (delta 0), pack-reused 0 (from 0)
remote: Resolving deltas: 100% (1/1), completed with 1 local object.
Warning: The working-copy commit in workspace &apos;default&apos; became immutable, so a new commit has been created on top of it.
Working copy  (@) now at: y 53e8a3d9 (empty) (no description set)
Parent commit (@-)      : u e57a7a39 main | feat: add minimal home.nix
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;❯  jj st
The working copy has no changes.
Working copy  (@) : y 53e8a3d9 (empty) (no description set)
Parent commit (@-): u e57a7a39 main | feat: add minimal home.nix
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Great, just what we need, an empty change! Let’s describe what we plan on doing:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;jj desc -m &quot;refactor: restructure flake to multi-host layout in hosts/magic&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we create a new change on top of this one:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;❯  jj new
Working copy  (@) now at: w 43195106 (empty) (no description set)
Parent commit (@-)      : y c66bc991 (empty) refactor: restructure flake to multi-host layout in hosts/magic
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we make our changes to the descriptionless Working copy and &lt;code&gt;squash&lt;/code&gt; our
changes into the parent commit.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mkdir -p hosts/magic
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;jj st
The working copy has no changes.
Working copy  (@) : w 43195106 (empty) (no description set)
Parent commit (@-): y c66bc991 (empty) refactor: restructure flake to multi-host layout in hosts/magic
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ahh, &lt;code&gt;jj&lt;/code&gt; doesn’t pick up empty directories…&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mv configuration.nix home.nix hosts/magic
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt; jj st
Working copy changes:
R {configuration.nix =&amp;gt; hosts/magic/configuration.nix}
R {home.nix =&amp;gt; hosts/magic/home.nix}
Working copy  (@) : w 8c49fc64 (no description set)
Parent commit (@-): y c66bc991 (empty) refactor: restructure flake to multi-host layout in hosts/magic
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;R&lt;/code&gt; = Renamed. &lt;code&gt;jj&lt;/code&gt; is pretty clever here. Since I moved the files but their
contents stayed the same, &lt;code&gt;jj&lt;/code&gt; detected that I didn’t just “delete” one file
and “add” a new one, I actually moved an object from point A to point B.
&lt;ul&gt;
&lt;li&gt;The fact that &lt;code&gt;jj&lt;/code&gt; shows &lt;code&gt;R {home.nix =&amp;gt; hosts/magic/home.nix}&lt;/code&gt; means it is
keeping the history of that file intact. If you were to look at the log for
&lt;code&gt;hosts/magic/home.nix&lt;/code&gt; later, &lt;code&gt;jj&lt;/code&gt; would know to look back into the history
of the old &lt;code&gt;home.nix&lt;/code&gt; as well.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I’m happy with the changes so far:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;❯  jj squash
Working copy  (@) now at: k 41deb985 (empty) (no description set)
Parent commit (@-)      : y 2bee669a refactor: restructure flake to multi-host layout in hosts/magic
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Notice how change &lt;code&gt;w&lt;/code&gt; disappeared and &lt;code&gt;y&lt;/code&gt; is no longer empty? That’s because
we squashed the changes from our Working copy into the parent commit!&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Pushing from the squash workflow&lt;/h3&gt;
&lt;p&gt;Let’s look at what we have:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;❯  jj st
The working copy has no changes.
Working copy  (@) : k 41deb985 (empty) (no description set)
Parent commit (@-): y 2bee669a refactor: restructure flake to multi-host layout in hosts/magic
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since the working copy is at an &lt;code&gt;(empty)&lt;/code&gt; change, it wouldn’t make sense to push
it. We have to move our bookmark to the parent commit, and then push!&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;❯  jj bookmark set main -r @-
Moved 1 bookmarks to y 2bee669a main* | refactor: restructure flake to multi-host layout in hosts/magic
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;❯  jj git push
Changes to push to origin:
  Move forward bookmark main from e57a7a39957a to 2bee669ac276
git: Enumerating objects: 5, done.
git: Counting objects: 100% (5/5), done.
git: Delta compression using up to 16 threads
git: Compressing objects: 100% (3/3), done.
git: Writing objects: 100% (4/4), 452 bytes | 452.00 KiB/s, done.
git: Total 4 (delta 1), reused 0 (delta 0), pack-reused 0 (from 0)
remote: Resolving deltas: 100% (1/1), completed with 1 local object.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This was the biggest Aha moment I had. It makes perfect sense that you wouldn’t
want to push a change that changes nothing. Since we squashed the contents of
the Working copy into &lt;code&gt;@-&lt;/code&gt;, that is where we need &lt;code&gt;main&lt;/code&gt; to point.&lt;/p&gt;
&lt;p&gt;I have an alias:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;  la = [
    &quot;log&quot;
    &quot;-r&quot;
    &quot;all()&quot;
  ];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can also list all commits with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;jj log -r ::
# or
jj log r &apos;all()&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let’s check out our full history so far:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;❯  jj la
@  k sayls8@proton.me 2026-03-15 14:30:43 41deb985
│  (empty) (no description set)
◆  y sayls8@proton.me 2026-03-15 14:30:43 main 2bee669a
│  refactor: restructure flake to multi-host layout in hosts/magic
◆  u sayls8@proton.me 2026-03-15 14:07:28 e57a7a39
│  feat: add minimal home.nix
◆  m sayls8@proton.me 2026-03-15 13:43:31 b3cc09db
│  feat: create minimal configuration.nix
◆  p sayls8@proton.me 2026-03-15 13:41:19 cddde3b4
│  chore: change username &amp;amp; hostname in flake.nix
◆  l sayls8@proton.me 2026-03-15 13:37:29 743b170d
│  chore: Initialize system flake
◆  z root() 00000000
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Textbook linear history. Every single commit has exactly one parent, forming a
single, unbroken chain from the &lt;code&gt;root()&lt;/code&gt; up to the current working copy.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The diamonds &lt;code&gt;◆&lt;/code&gt; show that everything from &lt;code&gt;y&lt;/code&gt; down is now part of the
permanent record (pushed to the remote).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In Git, achieving this usually requires &lt;code&gt;git add&lt;/code&gt;, &lt;code&gt;git commit --amend&lt;/code&gt;, or an
interactive rebase. In jj, you just worked in the working copy and pushed, the
tool handled the “shaping” of the history for you.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Bookmarks and Branches&lt;/h2&gt;
&lt;p&gt;Bookmarks are named pointers to revisions (just like branches are in Git). You
can move them without affecting the target revision’s identity. – Jujutsu docs&lt;/p&gt;
&lt;p&gt;Branches are just multiple “changes” with the same parent.&lt;/p&gt;
&lt;p&gt;List all available bookmarks&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;❯  jj bookmark list --all
feat1 (deleted)
  @origin: xo 9067e7b7 mangowc flake-parts module
main: s b250e6ed testing the push-on-new non working bs
  @git: s b250e6ed testing the push-on-new non working bs
  @origin (behind by 2 commits): o 65e79e4b jj bookmarks push-on-new
Hint: Bookmarks marked as deleted can be *deleted permanently* on the remote by running `jj git push --deleted`. Use `jj bookmark forget` if you don&apos;t want that.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Every time you run &lt;code&gt;jj git push&lt;/code&gt;, &lt;code&gt;jj&lt;/code&gt; automatically runs &lt;code&gt;jj new main&lt;/code&gt; for you
because the working copy becomes immutable after the push.&lt;/p&gt;
&lt;p&gt;Show heads of all anonymous branches:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;jj log -r &apos;heads(all())&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To visualize an anonymous branch, Steve’s Jujutsu tutorial does a great job of
displaying this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;
                     ┌───┐ ┌───┐
                 ┌───┤ F ◄─┤ G │
                 │   └───┘ └───┘
                 │
 ┌───┐  ┌───┐  ┌─▼─┐ ┌───┐ ┌───┐
 │ A ◄──┤ B ◄──┤ C ◄─┤ D ◄─┤ E │
 └───┘  └───┘  └───┘ └───┘ └───┘

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here, we’d say that &lt;code&gt;F&lt;/code&gt; and &lt;code&gt;G&lt;/code&gt; are two changes that are “on a branch,” because
it looks like they’re branching off from &lt;code&gt;D&lt;/code&gt; and &lt;code&gt;E&lt;/code&gt;.
&lt;a href=&quot;https://steveklabnik.github.io/jujutsu-tutorial/branching-merging-and-conflicts/anonymous-branches.html#what-is-a-branch-conceptually&quot;&gt;Steves Jujutsu tutorial What is a branch conceptually?&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The above sentence confused me a bit. &lt;code&gt;F&lt;/code&gt; and &lt;code&gt;G&lt;/code&gt; are described as branching
“off” of the line containing &lt;code&gt;D&lt;/code&gt; and &lt;code&gt;E&lt;/code&gt;. However, in the literal graph
structure, they diverge from &lt;code&gt;C&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Since &lt;code&gt;F&lt;/code&gt; and &lt;code&gt;D&lt;/code&gt; both point to &lt;code&gt;C&lt;/code&gt;, they are “siblings”. Because no other
commits point back to &lt;code&gt;E&lt;/code&gt; and &lt;code&gt;G&lt;/code&gt;, they are the only two heads.&lt;/p&gt;
&lt;p&gt;In Jujutsu, a branch is just any path of commits that hasn’t been merged yet.
Since E and G are both visible and unmerged, we have two “anonymous branches”
currently active.&lt;/p&gt;
&lt;p&gt;The output of &lt;code&gt;jj log -r &apos;heads(all())&apos;&lt;/code&gt; with the above example, would yield:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;○  G
│
~
│
○  E
│
~
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;G&lt;/code&gt; and &lt;code&gt;E&lt;/code&gt; are the heads of the anonymous branches.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Examples&lt;/h2&gt;
&lt;p&gt;Start at an empty change:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;❯  jj log
@  p saylesss87@proton.me 2026-03-24 18:05:54 6469af06
│  (no description set)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Give the current change a description:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;jj desc -m &quot;refactor(waybar): waybar flake-parts module&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;❯  jj log
@  p saylesss87@proton.me 2026-03-24 18:06:31 627d846c
│  refactor(waybar): waybar flake-parts module
○  yl saylesss87@proton.me 2026-03-24 17:55:00 56d13161
│  refactor(foot): foot flake-parts module
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To create a branch we need 2 changes with the same parent, our current changes
parent is &lt;code&gt;yl&lt;/code&gt; so let’s make our new change off of that:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;❯  jj new yl -m &quot;chore: add better documentation to README&quot;
Working copy  (@) now at: v 4697897a (empty) chore: add better documentation to README
Parent commit (@-)      : yl 56d13161 refactor(foot): foot flake-parts module
Added 1 files, modified 1 files, removed 1 files
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let’s check out our log to see our anonymous branches:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;❯  jj
@  v saylesss87@proton.me 2026-03-24 18:12:04 4697897a
│  (empty) chore: add better documentation to README
│ ○  p saylesss87@proton.me 2026-03-24 18:06:31 627d846c
├─╯  refactor(waybar): waybar flake-parts module
○  yl saylesss87@proton.me 2026-03-24 17:55:00 56d13161
│  refactor(foot): foot flake-parts module
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can see that &lt;code&gt;p&lt;/code&gt; branches off from &lt;code&gt;yl&lt;/code&gt;, let’s make this change before
switching to the other branch.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;❯  jj st
Working copy changes:
M README.md
Working copy  (@) : v 23d94e56 chore: add better documentation to README
Parent commit (@-): yl 56d13161 refactor(foot): foot flake-parts module
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# This effectively moves the working copy to the other &quot;branch&quot;
❯  jj edit p
Working copy  (@) now at: p 627d846c refactor(waybar): waybar flake-parts module
Parent commit (@-)      : yl 56d13161 refactor(foot): foot flake-parts module
Added 1 files, modified 2 files, removed 1 files
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;In Git, you &lt;code&gt;checkout&lt;/code&gt; a branch name; in &lt;code&gt;jj&lt;/code&gt;, you just move your working copy
(&lt;code&gt;@&lt;/code&gt;) to whichever commit you want to build on.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I’ve added the new flake-parts module and deleted the old home-manager style
module:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;❯  jj st
Working copy changes:
D home/waybar.nix
M hosts/magic/home.nix
A parts/waybar.nix
Working copy  (@) : p 627d846c refactor(waybar): waybar flake-parts module
Parent commit (@-): yl 56d13161 refactor(foot): foot flake-parts module
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let’s add another change on this branch to solidify these concepts:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;❯  jj new -m &quot;refactor(nh): nh flake-parts module&quot;
Working copy  (@) now at: n ac086b55 (empty) refactor(nh): nh flake-parts module
Parent commit (@-)      : p 627d846c refactor(waybar): waybar flake-parts module
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And our log to show that this branch has grown:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;❯  jj log
@  n saylesss87@proton.me 2026-03-24 18:22:12 ac086b55
│  (empty) refactor(nh): nh flake-parts module
○  p saylesss87@proton.me 2026-03-24 18:06:31 627d846c
│  refactor(waybar): waybar flake-parts module
│ ○  v saylesss87@proton.me 2026-03-24 18:17:51 23d94e56
├─╯  chore: add better documentation to README
○  yl saylesss87@proton.me 2026-03-24 17:55:00 56d13161
│  refactor(foot): foot flake-parts module
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, let’s first merge the README branch into this one. We are worried about the
heads of the anonymous branches right now:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;❯  jj log -r &apos;heads(all())&apos;
@  n saylesss87@proton.me 2026-03-24 18:22:12 ac086b55
│  (empty) refactor(nh): nh flake-parts module
~

○  v saylesss87@proton.me 2026-03-24 18:17:51 23d94e56
│  chore: add better documentation to README
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A merge is a new change that has more than one parent. With JJ, you make a
change with &lt;code&gt;jj new&lt;/code&gt;. We can see that we need to make a change with both &lt;code&gt;n&lt;/code&gt; and
&lt;code&gt;v&lt;/code&gt; as parents from the log output above:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;❯  jj new n v -m &quot;feat: merge in README docs&quot;
Working copy  (@) now at: yn a923db65 (empty) feat: merge in README docs
Parent commit (@-)      : n ac086b55 (empty) refactor(nh): nh flake-parts module
Parent commit (@-)      : v 23d94e56 chore: add better documentation to README
Added 0 files, modified 1 files, removed 0 files
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;❯  jj log
@    yn saylesss87@proton.me 2026-03-24 18:28:47 a923db65
├─╮  (empty) feat: merge in README docs
│ ○  v saylesss87@proton.me 2026-03-24 18:17:51 23d94e56
│ │  chore: add better documentation to README
○ │  n saylesss87@proton.me 2026-03-24 18:22:12 ac086b55
│ │  (empty) refactor(nh): nh flake-parts module
○ │  p saylesss87@proton.me 2026-03-24 18:06:31 627d846c
├─╯  refactor(waybar): waybar flake-parts module
○  yl saylesss87@proton.me 2026-03-24 17:55:00 56d13161
│  refactor(foot): foot flake-parts module
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That messes up our perfectly linear history, let’s rebase instead.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;❯  jj undo
Undid operation: a74a45f02043 (2026-03-24 18:28:47) new empty commit
Restored to operation: 848c180f2a04 (2026-03-24 18:22:12) new empty commit
Working copy  (@) now at: n ac086b55 (empty) refactor(nh): nh flake-parts module
Parent commit (@-)      : p 627d846c refactor(waybar): waybar flake-parts module
Added 0 files, modified 1 files, removed 0 files
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Rebase is your tool for changing the ‘parent’ of a commit. If you have two
parallel features (siblings) and you decide one should follow the other (linear
stack), you rebase the second feature onto the first.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;❯  jj rebase -r v -o n
Rebased 1 commits to destination
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now our history is back to being completely linear:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;❯  jj log
○  v saylesss87@proton.me 2026-03-24 18:30:36 8101b0dd
│  chore: add better documentation to README
@  n saylesss87@proton.me 2026-03-24 18:22:12 ac086b55
│  (empty) refactor(nh): nh flake-parts module
○  p saylesss87@proton.me 2026-03-24 18:06:31 627d846c
│  refactor(waybar): waybar flake-parts module
○  yl saylesss87@proton.me 2026-03-24 17:55:00 56d13161
│  refactor(foot): foot flake-parts module
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To move a stack of changes, use &lt;code&gt;jj rebase -s [Source] -d [Destination]&lt;/code&gt;. If you
want to move a feature to the very tip of your main line, destination is &lt;code&gt;main&lt;/code&gt;.
If you want to chain features together, destination is the previous feature’s
head.&lt;/p&gt;
&lt;p&gt;Pay attention to where &lt;code&gt;@&lt;/code&gt; is while rebasing, to continue on this linear stack
I’ll have to run &lt;code&gt;jj new v&lt;/code&gt;.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Collaborating and opening PRs&lt;/h2&gt;
&lt;p&gt;So far all the examples assumed you are the only person touching this repo. For
collaboration (GitHub / GitLab PRs, code review, etc.), the mental model is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;GitHub only understands &lt;em&gt;branches&lt;/em&gt; and &lt;em&gt;commits&lt;/em&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Jujutsu gives you &lt;em&gt;changes&lt;/em&gt; and &lt;em&gt;bookmarks&lt;/em&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You use &lt;code&gt;jj git push&lt;/code&gt; to translate your clean JJ history into a Git branch
that others can review.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A simple pattern that works well for feature branches and PRs:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Start from &lt;code&gt;main&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Make sure main is up to date and your working copy is clean:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;jj git fetch
jj edit main
jj st
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You should see an &lt;code&gt;(empty)&lt;/code&gt; working copy with &lt;code&gt;main&lt;/code&gt; as the parent.&lt;/p&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;Create a named feature branch as a bookmark&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In JJ, you don’t “checkout” a branch, you create a new change and (optionally)
give it a bookmark name:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Create a new change on top of main and start working there
jj new main -m &quot;feat: add magic host&quot;

# Optionally create a bookmark that GitHub will see as a branch
jj bookmark create feature/magic-host -r @
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;jj new main&lt;/code&gt; makes a sibling of any in‑progress work and starts a fresh
change on top of &lt;code&gt;main&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The bookmark &lt;code&gt;feature/magic-host&lt;/code&gt; is what will become the Git branch name when
you push.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;Hack, split, squash as usual&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Work in your normal JJ style:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# edit files
jj st
jj split -i
jj desc -m &quot;feat: add magic host&quot;
jj new
# more changes, more desc/split/squash, etc.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;All of this is still local, fully mutable history.&lt;/p&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;Point your feature bookmark at the top of the stack&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;When you’re happy with the stack you want to send for review, move the bookmark
to the tip:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# If your working copy @ is the commit you want reviewed:
jj bookmark set feature/magic-host -r @

# If you used the squash workflow and @ is empty:
jj bookmark set feature/magic-host -r @-
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Rule of thumb:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;If &lt;code&gt;jj st&lt;/code&gt; shows actual changes or a non‑empty description at &lt;code&gt;@&lt;/code&gt;, use &lt;code&gt;@&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If &lt;code&gt;@&lt;/code&gt; is (empty) because you squashed into the parent, use &lt;code&gt;@-&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;5&quot;&gt;
&lt;li&gt;Push to Git and open the PR&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now push just like before, but your feature bookmark will become a branch on the
remote:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;jj git push
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Update &lt;code&gt;origin/feature/magic-host&lt;/code&gt; to point at the same commit as your local
&lt;code&gt;feature/magic-host&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Leave &lt;code&gt;main&lt;/code&gt; alone until you explicitly move and push it.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;On GitHub/GitLab:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;You’ll see a branch named &lt;code&gt;feature/magic-host&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Open a PR from &lt;code&gt;feature/magic-host&lt;/code&gt; into &lt;code&gt;main&lt;/code&gt; as usual.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;6&quot;&gt;
&lt;li&gt;Iterate on review feedback&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If a reviewer asks for changes:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Come back to your feature branch stack:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;jj edit feature/magic-host
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;
&lt;p&gt;Make your edits, split/squash/reword history freely.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Move the bookmark to the new tip and push again:&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;jj bookmark set feature/magic-host -r @
jj git push
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;GitHub will show the PR updating in place, but you got to reshuffle history
locally without &lt;code&gt;git rebase -i&lt;/code&gt; pain.&lt;/p&gt;
&lt;p&gt;“I just want a one‑off PR, no named bookmark”&lt;/p&gt;
&lt;p&gt;If you don’t care about a persistent bookmark and just want a quick “one‑shot”
PR from whatever you’re currently working on:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Make sure your stack is in the shape you want.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Put main on the top of that stack:&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Working copy @ is the tip you want:
jj bookmark set main -r @
# Or equivalently `jj bookmark set main` since commands default to the working copy
jj git push
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;On GitHub, create the PR from your fork’s main to upstream main.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This is essentially the same pattern we already use in the “squash workflow,”
but applied with the mental model: “move the bookmark people care about (&lt;code&gt;main&lt;/code&gt;
or a feature name) to the commit I want them to review, then push.”&lt;/p&gt;
&lt;h3&gt;Workflow Considerations&lt;/h3&gt;
&lt;p&gt;You may want to change up how you work depending on your requirements…&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The Tower Workflow (The “Dependent Stack”)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You use a tower when your changes build on top of each other.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;main&lt;/code&gt; → &lt;code&gt;Feature A&lt;/code&gt; → &lt;code&gt;Feature B&lt;/code&gt; → &lt;code&gt;Feature C&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Use it when you are refactoring a Nix module (Feature A), and then you need to
use that new module to configure your desktop (Feature B). You literally
cannot do B without A.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The Collaborative Benefit: You can push the entire stack. Your teammates see the
logical progression of your thought process. They can review “Feature A” while
you are already working on “Feature C.”&lt;/p&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;The Sibling Workflow (&lt;code&gt;jj new main&lt;/code&gt;) You use &lt;code&gt;jj new main&lt;/code&gt; when you are
working on independent ideas that have nothing to do with each other.&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;How it looks: * &lt;code&gt;main&lt;/code&gt; → &lt;code&gt;Fix-Helix-Keys&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;main&lt;/code&gt; → &lt;code&gt;Update-Nix-Channel&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;main&lt;/code&gt; → &lt;code&gt;New-Wallpaper-Script&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When to use it: You’re in the middle of a massive system refactor, but you
suddenly notice your Helix C-p bind is broken. You don’t want the Helix fix to
be “trapped” behind the refactor.&lt;/p&gt;
&lt;p&gt;The Collaborative Benefit: Isolation. If your “System Refactor” is buggy and
takes three days to fix, you can still &lt;code&gt;jj git push&lt;/code&gt; the “Helix Fix” to &lt;code&gt;main&lt;/code&gt;
immediately because it’s a direct child of &lt;code&gt;main&lt;/code&gt;. It isn’t “waiting” for the
other commits.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Fixing Divergent Branches&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;jj&lt;/code&gt; makes it easy to fix divergent branches with &lt;code&gt;jj forget&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;As an example imagine this scenario:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;On your Laptop: You finish a feature and jj git push. Local main and
origin/main are now at Revision B.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;On your Desktop: You forgot to pull. Local main is still at Revision A. You
start hacking and create Revision C.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The Mess: You run &lt;code&gt;jj git fetch&lt;/code&gt;. Now your Desktop sees:&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;main&lt;/code&gt; (local) at C&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;main@origin&lt;/code&gt; at B&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;jj&lt;/code&gt; freaks out and marks the bookmark as diverged (main*).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;jj bookmark forget main&lt;/code&gt; is the “I don’t want to think about it” button. It
deletes the &lt;code&gt;main*&lt;/code&gt; label on your Desktop so you can just fetch the “real” one
from the Laptop/GitHub and rebase your new work (C) on top of it.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Tips &amp;amp; Tricks&lt;/h2&gt;
&lt;p&gt;Your History is just a stack of diffs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;jj commit -i&lt;/code&gt;: Slices a diff off your current work.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;jj split -i&lt;/code&gt;: Slices an existing diff into two.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;jj bookmark forget&lt;/code&gt;: Deletes a label that got messy.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;The Ghost Refactor&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Scenario&lt;/strong&gt;: You’re in the middle of a complex Rust feature (Change C), and
you realize a function in the base (Change A) needs to be public or renamed for
this to work.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;jj new A -m &quot;quick fix&quot;&lt;/code&gt; → Creates a new “slice” right after A.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Make your change.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;jj squash&lt;/code&gt; → This “melts” the fix directly into A.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;strong&gt;Use &lt;code&gt;jj describe&lt;/code&gt; as a Task List&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Because &lt;code&gt;jj&lt;/code&gt; doesn’t require a “commit” to save work, you can use descriptions
to manage your focus.&lt;/p&gt;
&lt;p&gt;When you start a session, create a few empty changes:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;jj new main -m &quot;Update Cargo.toml&quot;&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;jj new -m &quot;Add pixel conversion logic&quot;&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;jj new -m &quot;Fix CLI output formatting&quot;&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now we have a roadmap. Use &lt;code&gt;jj edit&lt;/code&gt; to jump into whichever task you feel like
doing. &lt;code&gt;jj&lt;/code&gt; tracks the progress of each. No need to &lt;code&gt;stash&lt;/code&gt; when jumping between
tasks because of the working copy commit.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;strong&gt;Breaking up your Current set of Changes into Atomic Commits: “The Atomic
Shredder” &lt;code&gt;jj split&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Often I’ll just keep working on a bug or feature until it works, not
particularly concerned about my VCS history. Let’s say I spent 3 hours hacking
on a project of mine to get a feature working and I touched 10 files. It works,
but the commit is a mess.&lt;/p&gt;
&lt;p&gt;With the &lt;code&gt;gitpatch&lt;/code&gt; tool, &lt;code&gt;jj split -i&lt;/code&gt; makes it simple to break down many
changes into logical “atomic commits” using a simple &lt;code&gt;y&lt;/code&gt;/ &lt;code&gt;n&lt;/code&gt; interface.&lt;/p&gt;
&lt;p&gt;You can actually use both &lt;code&gt;jj commit -i&lt;/code&gt; &amp;amp; &lt;code&gt;jj split -i&lt;/code&gt; to break down changes
in &lt;code&gt;@&lt;/code&gt; into smaller changes. &lt;code&gt;jj commit -i&lt;/code&gt; is ergonomically tuned for “push
some current work down, leave the rest in @”.&lt;/p&gt;
&lt;p&gt;If your changes are already in &lt;code&gt;@-&lt;/code&gt; (or any earlier commit):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;jj commit -i&lt;/code&gt; can’t help, because it only ever operates on &lt;code&gt;@&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;jj split -i -r @-&lt;/code&gt; (or equivalent) can be used to modify an existing commit
in history.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;As a rule of thumb&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Changes in &lt;code&gt;@&lt;/code&gt; → use &lt;code&gt;jj commit -i&lt;/code&gt; to peel off a clean piece, and also
&lt;code&gt;jj squash -i&lt;/code&gt; to squash a subset of changes from the working copy into the
parent commit.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Changes in &lt;code&gt;@-&lt;/code&gt; (or earlier) → use &lt;code&gt;jj split -i&lt;/code&gt; to rewrite that commit&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content></entry><entry><title>The Dendritic Pattern with flake-parts</title><id>https://saylesss88.github.io/flakes/dendritic_flake_parts.html</id><updated>2026-03-07T00:00:00+00:00</updated><author><name>saylesss87@proton.me (saylesss88)</name></author><link href="https://saylesss88.github.io/flakes/dendritic_flake_parts.html" rel="alternate"/><content type="html">&lt;h1&gt;The Dendritic Pattern with flake-parts&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;https://saylesss88.github.io/../images/dendritic_nix.png&quot; alt=&quot;Dendritic Logo&quot; /&gt;–&lt;a href=&quot;https://github.com/mightyiam/dendritic&quot;&gt;dendretic repo&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;In the early days of Flakes, users often ended up with a massive, monolithic
&lt;code&gt;flake.nix&lt;/code&gt; or a spaghetti-like web of manual imports. The Dendritic (Tree-like)
Pattern solves this by treating your filesystem as the source of truth, using
&lt;code&gt;flake-parts&lt;/code&gt; as the nervous system that routes code to the correct outputs.&lt;/p&gt;
&lt;h2&gt;Flake-Parts&lt;/h2&gt;
&lt;p&gt;IMO the &lt;code&gt;flake-parts&lt;/code&gt; docs could do a lot better at explaining how to use it to
configure your system. I’ll attempt to explain how to use it and why you might
want to.&lt;/p&gt;
&lt;p&gt;While &lt;code&gt;flake-parts&lt;/code&gt; provides the core structure for standard flake outputs, its
real power lies in its modular ecosystem. You can plug in opinionated modules to
instantly add specialized features to your system’s nervous system.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;When people say “top-level flake attribute”, they mean putting your
configuration inside the &lt;code&gt;flake = { ... };&lt;/code&gt; block.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;The “Top-Level” (&lt;code&gt;flake&lt;/code&gt;):&lt;/h3&gt;
&lt;p&gt;This is for things that are &lt;strong&gt;global&lt;/strong&gt; and don’t change regardless of whether
you’re on a Mac, PC or ARM server.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Example: Your &lt;code&gt;nixosConfigurations&lt;/code&gt; or &lt;code&gt;homeConfigurations&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A NixOS configuration for a specific laptop is a single, static definition. It
doesn’t need to be “multiplied” by system types.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;The “Per-System” (&lt;code&gt;perSystem&lt;/code&gt;)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;perSystem&lt;/code&gt; is for things that must be built for a specific architecture.
(e.g., &lt;code&gt;devShells&lt;/code&gt;, &lt;code&gt;packages&lt;/code&gt;, &lt;code&gt;formatter&lt;/code&gt;)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You can’t run an &lt;code&gt;x86_64&lt;/code&gt; version of &lt;code&gt;helix&lt;/code&gt; on an &lt;code&gt;aarch64&lt;/code&gt; (ARM) MacBook.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Our &lt;code&gt;nixosConfigurations&lt;/code&gt; don’t live in a system specific attribute so it goes
under &lt;code&gt;flake&lt;/code&gt; instead of &lt;code&gt;perSystem&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can place everything in the same file:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;outputs = inputs@{ flake-parts, ... }:
  # https://flake.parts/module-arguments.html
  flake-parts.lib.mkFlake { inherit inputs; } (top@{ config, withSystem, moduleWithSystem, ... }: {
    imports = [
      # Optional: use external flake logic, e.g.
      # inputs.foo.flakeModules.default
    ];
    flake = {
      # Put your original flake attributes here.
    };
    systems = [
      # systems for which you want to build the `perSystem` attributes
      &quot;x86_64-linux&quot;
      # ...
    ];
    perSystem = { config, pkgs, ... }: {
      # Recommended: move all package definitions here.
      # e.g. (assuming you have a nixpkgs input)
      # packages.foo = pkgs.callPackage ./foo/package.nix { };
      # packages.bar = pkgs.callPackage ./bar/package.nix {
      #   foo = config.packages.foo;
      # };
    };
  });
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or you can break it down into modules:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{
  inputs = {
    nixpkgs.url = &quot;github:nixos/nixpkgs/nixos-unstable&quot;;
    flake-parts.url = &quot;github:hercules-ci/flake-parts&quot;;
  };

  outputs = inputs @ { flake-parts, ... }:
    flake-parts.lib.mkFlake { inherit inputs; }
    {
      # Supported Systems
      systems = [ &quot;x86_64-linux&quot; &quot;x86_64-darwin&quot;];
      imports = [ ./devShell.nix ./package.nix ./nixos.nix ]
    };

}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;devShell.nix&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{
  perSystem = { pkgs, ...}: {
  devShells.default = pkgs.mkShell {
    packages = [ pkgs.ripgrep pkgs.fd ];
  };
};
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;package.nix&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{
  perSystem = { pkgs, ...}: {
    packages.myPackage = pkgs.myPackage;
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Getting Started&lt;/h2&gt;
&lt;p&gt;This is the flake that I’m currently using to slowly adopt the dendritic
pattern, or not if I choose not to. Maybe making every single thing a flake
output isn’t necessary or the best idea, we’ll see. With this, you can use both
regular NixOS/home-manager modules and NixOS/home-manager flake-parts modules.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Scalability Toggle&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;I have &lt;code&gt;++ (builtins.attrValues ...)&lt;/code&gt; set for both home-manager and NixOS
where it automatically adds the inputs for files in the &lt;code&gt;~/flake/parts&lt;/code&gt;
directory. This is fine for personal use where you want the custom modules
available everywhere instantly.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If the flake gets too big and you want more control just comment out those 2
lines in the &lt;code&gt;nixos.nix&lt;/code&gt; and start explicitly adding the &lt;code&gt;inputs&lt;/code&gt; to your
&lt;code&gt;imports&lt;/code&gt; list. Now the &lt;code&gt;parts/&lt;/code&gt; directory will act like a library rather than
a Registry, you manually pick what you want for each host.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://nix-community.github.io/home-manager/index.xhtml#sec-flakes-flake-parts-module&quot;&gt;home-manager flake-parts module&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Example: Let’s call this &lt;code&gt;~/flake/flake.nix&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# In this example the top-level configuration is a [`flake-parts`](https://flake.parts) one.
# Therefore, every Nix file (other than this) is a flake-parts module.
# https://github.com/mightyiam/dendritic/blob/master/example/flake.nix
{
  # Declares flake inputs
  inputs = {
    flake-parts = {
      url = &quot;github:hercules-ci/flake-parts&quot;;
      inputs.nixpkgs-lib.follows = &quot;nixpkgs&quot;;
    };

    import-tree.url = &quot;github:vic/import-tree&quot;;

    nixpkgs.url = &quot;github:nixos/nixpkgs/25.11&quot;;
  };

  outputs =
    inputs:
    inputs.flake-parts.lib.mkFlake { inherit inputs; } {
      # This tells flake-parts which systems to generate outputs for
      systems = import inputs.systems;

      imports = [
        # Optional: use external flake logic, e.g.
        inputs.treefmt-nix.flakeModule
        # Import home-manager&apos;s flake module
        # The flake module defines flake.homeModules and flake.homeConfigurations options,
        # allowing them to be properly merged if they are defined in multiple modules
        inputs.home-manager.flakeModules.default
        ./nixos.nix
      ]
      ++ (inputs.import-tree ./parts).imports;

      hosts = {
        magic = {
          username = &quot;jr&quot;;
          system = &quot;x86_64-linux&quot;;
        };
        # Adding a second machine is now 4 lines of code:
        # secondary = { username = &quot;jr&quot;; system = &quot;aarch64-linux&quot;; };
      };

      perSystem =
        {
          system,
          ...
        }:
        {

          # Access pkgs with your specific config
          _module.args.pkgs = import inputs.nixpkgs {
            inherit system;
            config.allowUnfree = false;
          };
        };

    };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;import-tree&lt;/code&gt; is essentially a “smarter” version of the &lt;code&gt;scanPaths&lt;/code&gt; function
that we’ll see later specifically designed for &lt;code&gt;flake-parts&lt;/code&gt;. It ensures that
every file in &lt;code&gt;./parts&lt;/code&gt; is treated as a module that the &lt;code&gt;mkFlake&lt;/code&gt; engine can
digest.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Note, this example has more code than for example the dendritic nix repo has
because it enables you to automatically import &lt;code&gt;flake-parts&lt;/code&gt; modules as well as
standard NixOS and home-manager modules as you move to the new system/pattern.&lt;/p&gt;
&lt;p&gt;And &lt;code&gt;~/flake/nixos.nix&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;/**
  System Configuration Factory Module

  This module defines a custom schema for describing multiple NixOS hosts
  and automatically generates the corresponding `nixosConfigurations` flakes output.
*/
{
  inputs,
  self,
  lib,
  config,
  ...
}:
let
  # Internal Library &amp;amp; Module Imports

  # Initialize a custom library using the project&apos;s internal lib and nixpkgs
  myLib = import &quot;${self}/lib/default.nix&quot; { inherit (inputs.nixpkgs) lib; };

  # Import entry points for global NixOS and Home Manager shared modules
  # `self` points to the root of the flake (requires passing `self` throuth specialArgs)
  nixosModules = import &quot;${self}/nixos&quot;;
  homeManagerModules = import &quot;${self}/home&quot;;

  # Shared Binary Cache Configuration
  caches = {
    nix.settings = {
      builders-use-substitutes = true;
      substituters = [ &quot;https://cache.nixos.org&quot; ];
      trusted-public-keys = [ &quot;cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=&quot; ];
    };
  };
in
{
  /**
    SCHEMA DEFINITION
    Defines the &apos;hosts&apos; option, allowing us to declare system metadata
    (e.g., username, architecture) in a structured attribute set.
  */
  options.hosts = lib.mkOption {
    description = &quot;An attribute set of host definitions to be generated.&quot;;
    type = lib.types.attrsOf (
      lib.types.submodule {
        options = {
          username = lib.mkOption {
            type = lib.types.str;
            default = &quot;jr&quot;;
            description = &quot;Primary user account name for this host.&quot;;
          };
          system = lib.mkOption {
            type = lib.types.str;
            default = &quot;x86_64-linux&quot;;
            description = &quot;The target system architecture.&quot;;
          };
        };
      }
    );
  };
  /**
    CONFIGURATION GENERATION
    Iterates through the &apos;config.hosts&apos; defined above and maps them to
    actual &apos;nixosSystem&apos; instances for the Flake output.
  */
  config.flake.nixosConfigurations = lib.mapAttrs (
    host: cfg:
    inputs.nixpkgs.lib.nixosSystem {
      # Pass global context and metadata into the module system
      specialArgs = {
        inherit
          inputs
          self
          host
          myLib
          ;
        inherit (cfg) username;
      };

      modules = [
        # 1. Project-wide NixOS logic
        nixosModules

        # 2. Host-specific hardware/system configuration file
        &quot;${self}/hosts/${host}/configuration.nix&quot;

        # 3. Home Manager NixOS module (allows configuring HM within NixOS)
        inputs.home-manager.nixosModules.home-manager

        # 4. Standardized cache settings defined in &apos;let&apos; block
        caches

        # 5. Inline configuration for system-specific and user-specific settings
        {
          nixpkgs.hostPlatform = cfg.system;
          home-manager = {
            # By default, Home Manager uses a private pkgs instance that is configured
            #  via the home-manager.users.&amp;lt;name&amp;gt;.nixpkgs options. To instead use the
            #  global pkgs that is configured via the system level nixpkgs options, set
            useGlobalPkgs = true;
            # Install packages to /etc/profiles rather than $HOME/.nix-profile
            useUserPackages = true;
            # Dynamically import the user&apos;s home configuration based on host/username
            users.${cfg.username} = {
              imports = [
                (import &quot;${self}/hosts/${host}/home.nix&quot;)
              ]
              # Automatically import `homeModules` in `./parts`
              # Comment out if you want to be explicit and add
              # e.g., inputs.self.homeModules.helix
              ++ (builtins.attrValues config.flake.homeModules);
            };

            extraSpecialArgs = {
              inherit
                inputs
                homeManagerModules
                myLib
                host
                ;
              inherit (cfg) username;
            };
          };
        }
      ]
      # Automatically import `nixosModules` in `./parts`
      ++ (builtins.attrValues config.flake.nixosModules);
    }
  ) config.hosts;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, any &lt;code&gt;flake-parts&lt;/code&gt; module that we place in the &lt;code&gt;~/flake/parts/&lt;/code&gt; directory
will be automatically imported with &lt;code&gt;import-tree&lt;/code&gt;. The key here is that it has
to be a &lt;code&gt;flake-parts&lt;/code&gt; module, i.e. wrapped in &lt;code&gt;flake&lt;/code&gt; or &lt;code&gt;perSystem&lt;/code&gt;, etc.&lt;/p&gt;
&lt;p&gt;You can place both NixOS modules and home-manager modules in the &lt;code&gt;~/flake/parts&lt;/code&gt;
directory. Just import it to the correct location and you’re good.&lt;/p&gt;
&lt;p&gt;Example NixOS module for amd drivers &lt;code&gt;~/flake/parts/amd-drivers.nix&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{
  flake.nixosModules.amd-drivers =
    {
      lib,
      pkgs,
      config,
      ...
    }:
    with lib;
    let
      cfg = config.custom.amd-drivers;
    in
    {
      options.custom.amd-drivers.enable = mkEnableOption &quot;AMD GPU/CPU optimized for AM06 Pro&quot;;

      config = mkIf cfg.enable {
        # Modern ROCm/HIP support
        systemd.tmpfiles.rules = [ &quot;L+ /opt/rocm/hip - - - - ${pkgs.rocmPackages.clr}&quot; ];
        services.xserver.videoDrivers = [ &quot;amdgpu&quot; ];

        hardware = {
          amdgpu.initrd.enable = true;

          graphics = {
            enable = true;
            enable32Bit = true;
            extraPackages = with pkgs; [
              rocmPackages.clr.icd # For OpenCL/Compute
              # Hardware Acceleration (Video Encoding/Decoding)
              libva
              libva-utils
              libva-vdpau-driver
              libvdpau-va-gl
            ];
          };

          cpu.amd.updateMicrocode = true;
        };

        boot = {
          kernelModules = [
            &quot;kvm-amd&quot;
            &quot;amdgpu&quot;
          ];
          kernelParams = [
            &quot;amd_pstate=active&quot; # Best for Ryzen 5000+ power management
          ];
        };

        boot.kernelPackages = pkgs.linuxPackages_latest;
      };
    };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, to enable this module, I’ll add this to my &lt;code&gt;configuration.nix&lt;/code&gt; or
equivalent:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt; {...}: {
  imports = [
        # Not necessary because of the `++ (builtins.attrValues config.flake.nixosModules)` in nixos.nix
        # for more control remove those lines and explicitly add:
        # inputs.self.nixosModules.amd-drivers
  ];

  custom = {
    amd-drivers.enable = true;
  };
}
# ---snip---
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;p&gt;Example home-manager module &lt;code&gt;~/flake/parts/fzf.nix&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{
  flake.homeModules.fzf =
    { lib, config, ... }:
    let
      cfg = config.custom.fzf;
    in
    {
      options.custom.fzf.enable = lib.mkEnableOption &quot;Enable fzf module&quot;;

      config = lib.mkIf cfg.enable {
        programs.fzf = {
          enable = true;
          # colors = lib.mkForce { };

          defaultOptions = [
            &quot;--height 40%&quot;
            &quot;--reverse&quot;
            &quot;--border&quot;
            &quot;--color=16&quot;
          ];

          defaultCommand = &quot;rg --files --hidden --glob=!.git/&quot;;
        };
      };
    };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And enable with &lt;code&gt;custom.fzf.enable = true;&lt;/code&gt; in your &lt;code&gt;home.nix&lt;/code&gt; or equivalent.&lt;/p&gt;
&lt;p&gt;And this will be automatically imported, same as above but because of the added
&lt;code&gt;++ (builtins.attrValues config.flake.homeModules);&lt;/code&gt; in &lt;code&gt;nixos.nix&lt;/code&gt;. Without
the automatic import, add &lt;code&gt;inputs.self.homeModules.fzf&lt;/code&gt; to your &lt;code&gt;imports&lt;/code&gt; list
in your &lt;code&gt;home.nix&lt;/code&gt; or equivalent.&lt;/p&gt;
&lt;p&gt;If you don’t like the auto-import behavior, just delete or comment out that
line, after that, the &lt;code&gt;import&lt;/code&gt; statements become necessary.&lt;/p&gt;
&lt;hr /&gt;
&lt;details&gt;
&lt;summary&gt;Example of a module thats both NixOS and home-manager zsh.nix &lt;/summary&gt;
&lt;p&gt;&lt;code&gt;~/flake/parts/shells/zsh.nix&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{
  flake.nixosModules.zsh =
    {
      pkgs,
      lib,
      config,
      username,
      ...
    }:
    let
      cfg = config.custom.zsh;
    in
    {
      options.custom.zsh = {
        enable = lib.mkEnableOption &quot;User zsh configuration&quot;;
      };

      config = lib.mkIf cfg.enable {

        # 1. NixOS System-Level (The &quot;Foundation&quot;)
        programs.zsh.enable = true;
        users.defaultUserShell = pkgs.zsh;
        environment.pathsToLink = [ &quot;/share/zsh&quot; ]; # Fixes completion for system packages

        # 2. Home Manager (User Configuration)
        home-manager.users.${username} = {
          programs.zsh = {
            enable = true;
            enableCompletion = true;
            completionInit = &quot;autoload -U compinit &amp;amp;&amp;amp; compinit&quot;;
            autosuggestion.enable = true;
            syntaxHighlighting.enable = true;
            oh-my-zsh = {
              package = pkgs.oh-my-zsh;
              enable = true;
              plugins = [
                &quot;git&quot;
                &quot;sudo&quot;
                &quot;rust&quot;
                &quot;fzf&quot;
              ];
            };
            profileExtra = &apos;&apos;
              if [ -z &quot;$DISPLAY&quot; ] &amp;amp;&amp;amp; [ &quot;$XDG_VTNR&quot; = 1 ]; then
               exec mango
              fi
              # ---snip----
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;NOTE: Passing &lt;code&gt;username&lt;/code&gt; through &lt;code&gt;specialArgs&lt;/code&gt; is what makes this bridge work,
you can also just use your username.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is what &lt;code&gt;~/flake/parts/shells/default.nix&lt;/code&gt; looks like, I had to explicitly
add &lt;code&gt;myLib&lt;/code&gt; in a &lt;code&gt;let&lt;/code&gt; statement to prevent infinite recursion:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{ lib, ... }:
let
  # prevents infinite recursion error
  myLib = import ../../lib { inherit lib; };
in
{
  # Now we can use it safely
  imports = myLib.scanPaths ./.;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Enable it in &lt;code&gt;configuration.nix&lt;/code&gt; or equivalent:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;custom.zsh.enable = true;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;lib/default.nix&lt;/code&gt; is just a function that automatically imports any &lt;code&gt;.nix&lt;/code&gt; file,
skipping &lt;code&gt;default.nix&lt;/code&gt;:&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt; `lib/default.nix` &lt;/summary&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{ lib, ... }:
{
  # Returns a list of all .nix files and directories in a path,
  # skipping default.nix. Perfect for the &apos;imports&apos; list.
  scanPaths =
    path:
    let
      content = builtins.readDir path;
    in
    map (name: path + &quot;/${name}&quot;) (
      builtins.attrNames (
        lib.filterAttrs (
          name: type: (type == &quot;directory&quot;) || (name != &quot;default.nix&quot; &amp;amp;&amp;amp; lib.hasSuffix &quot;.nix&quot; name)
        ) content
      )
    );

  relativeToRoot = lib.path.append ../.;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;
&lt;p&gt;The key here is wrapping the home-manager logic in
&lt;code&gt;home-manager.users.${username} = {}&lt;/code&gt;, this effectively creates a home-manager
sandbox enabling configuration of both in the same file.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;NOTE: You can do this without &lt;code&gt;flake-parts&lt;/code&gt; also but often wasn’t recommended
because the files become a mess that’s hard to understand.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/details&gt;
&lt;hr /&gt;
&lt;h1&gt;Example using perSystem&lt;/h1&gt;
&lt;p&gt;In the &lt;code&gt;flake.nix&lt;/code&gt;, notice the &lt;code&gt;inputs.treefmt-nix.flakeModule&lt;/code&gt;. Since a
formatter is something that you would want to run on every system, you use the
&lt;code&gt;perSystem&lt;/code&gt; attribute.&lt;/p&gt;
&lt;p&gt;Adding the &lt;code&gt;inputs.treefmt-nix.flakeModule&lt;/code&gt; makes the &lt;code&gt;treefmt&lt;/code&gt; options
available&lt;/p&gt;
&lt;p&gt;&lt;code&gt;~/flake/parts/treefmt.nix&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{
  perSystem = _: {
    treefmt = {
      projectRootFile = &quot;flake.nix&quot;;

      programs = {
        deadnix.enable = true;
        statix.enable = true;
        keep-sorted.enable = true;
        nixfmt = {
          enable = true;
          # package = pkgs.nixfmt;
        };
      };

      settings = {
        global.excludes = [
          &quot;LICENSE&quot;
          &quot;README.md&quot;
          &quot;.adr-dir&quot;
          &quot;nu_scripts&quot;
          &quot;*.{gif,png,svg,tape,mts,lock,mod,sum,toml,env,envrc,gitignore,sql,conf,pem,key,pub,py,narHash}&quot;
          &quot;Cargo.lock&quot;
          &quot;flake.lock&quot;
          &quot;justfile&quot;
          &quot;.jj/*&quot;
        ];

        formatter = {
          nixfmt.priority = 1;
          statix.priority = 2;
          deadnix.priority = 3;
        };
      };
    };
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;By using &lt;code&gt;perSystem&lt;/code&gt;, your &lt;code&gt;treefmt&lt;/code&gt; configuration is automatically available
for every architecture you support (x86, ARM, etc.), allowing you to run
&lt;code&gt;nix fmt&lt;/code&gt; on any machine without rewriting the logic.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;code&gt;import-tree&lt;/code&gt; automatically imports this because it’s a &lt;code&gt;flake-parts&lt;/code&gt; module &amp;amp;
flake output.&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;flake-parts &amp;amp; numtide devshells&lt;/h1&gt;
&lt;p&gt;Adding this flake input and &lt;code&gt;flakeModule&lt;/code&gt; make the options available, they’re
similar to NixOS’s &lt;code&gt;devShell&lt;/code&gt; but not the same:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;devshell.url = &quot;github:numtide/devshell&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;~/flake/parts/dev-shell.nix&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{
  perSystem =
    { pkgs, system, ... }:
    {
      devshells.default = {
        name = &quot;nixos-dev&quot;;

        packages = with pkgs; [
          nixfmt
          deadnix
          nixd
          nil
          nh
          nix-diff
          nix-tree
          helix
          git
          ripgrep
          jq
          tree
        ];

        # Message of the Day
        motd = &apos;&apos;
          {2}── NixOS Dev Shell ──────────────────────────────────────────{reset}
          {9}  System: {reset} ${system}
          {2}──────────────────────────────────────────────────────────────{reset}
        &apos;&apos;;

        commands = [
          {
            name = &quot;rebuild&quot;;
            package = &quot;nh&quot;;
            help = &quot;Run nh os switch on the current flake&quot;;
            command = &quot;nh os switch .&quot;;
          }
          {
            name = &quot;fmt&quot;;
            package = &quot;nixfmt&quot;;
            help = &quot;Format all nix files in the project&quot;;
            command = &quot;nix fmt&quot;;
          }
        ];
      };
    };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Enter devShell:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd ~/flake
nix develop
&lt;/code&gt;&lt;/pre&gt;
</content></entry><entry><title>ZFS Bare Metal Impermanence</title><id>https://saylesss88.github.io/installation/enc/zfs_bare-metal.html</id><updated>2026-03-01T00:00:00+00:00</updated><author><name>saylesss87@proton.me (saylesss88)</name></author><link href="https://saylesss88.github.io/installation/enc/zfs_bare-metal.html" rel="alternate"/><content type="html">&lt;h1&gt;ZFS Imperm Bare-Metal&lt;/h1&gt;
&lt;p&gt;I couldn’t get disko to bend to my will so I wrote the following bash script.
The script automates the steps in Graham Christensen’s
&lt;a href=&quot;https://grahamc.com/blog/erase-your-darlings/&quot;&gt;Erase your darlings&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;The Storage Architecture&lt;/h2&gt;
&lt;p&gt;The script below automates the “Erase Your Darlings” setup. It organizes your
data into three distinct “levels” of persistence:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The Volatile (&lt;code&gt;/&lt;/code&gt;)&lt;/strong&gt;: A ZFS dataset that is blank at boot. We take a
snapshot called @blank immediately after creation. In your NixOS
configuration, you will set up a boot-time script to roll back to this @blank
snapshot, effectively “formatting” your root in milliseconds.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The Store (&lt;code&gt;/nix&lt;/code&gt;)&lt;/strong&gt;: A separate dataset for the Nix store. This doesn’t
need to be wiped because Nix already manages its own integrity.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The Safe (&lt;code&gt;/persist&lt;/code&gt; and &lt;code&gt;/home&lt;/code&gt;)&lt;/strong&gt;: These datasets hold the things you
actually care about—your SSH keys, browser profiles, and project files.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;What this script automates&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This bash script handles the “Stage 0” heavy lifting. It will:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Partition&lt;/strong&gt; your disk with an EFI boot partition and a LUKS2 encrypted
container.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Initialize&lt;/strong&gt; a ZFS pool (&lt;code&gt;rpool&lt;/code&gt;) with performance-optimized settings (like
&lt;code&gt;ashift=12&lt;/code&gt; and &lt;code&gt;zstd&lt;/code&gt; compression).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Carve&lt;/strong&gt; out the datasets required for an Impermanence setup.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Mount&lt;/strong&gt; the hierarchy into &lt;code&gt;/mnt&lt;/code&gt; so &lt;code&gt;nixos-generate-config&lt;/code&gt; can detect the
specialized ZFS layout.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;WARNING&lt;/strong&gt;: This is a destructive operation. Running this script will wipe
the target drive completely. Ensure you have backed up any existing data
before proceeding.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/saylesss88/my-flake2/blob/main/install.sh&quot;&gt;The Setup Script&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Quick Start&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Start with the minimal ISO:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://nixos.org/download/&quot;&gt;NixOS Downloads&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://nixos.org/manual/nixos/stable/index.html#sec-installation-manual&quot;&gt;NixOS Manual Installation&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The script handles the partitioning, formatting, mounting, and lastly, runs
&lt;code&gt;nixos-generate-config --root /mnt&lt;/code&gt;. After running the script, edit the files
in the repo matching your user and device. Finally, after you’re sure you
haven’t missed anything, run &lt;code&gt;nixos-install&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;export NIX_CONFIG=&apos;experimental-features = nix-command flakes&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;Clone the &lt;a href=&quot;https://github.com/saylesss88/my-flake2#&quot;&gt;starter repo&lt;/a&gt;:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git clone https://github.com/saylesss88/my-flake2.git
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;Inspect &amp;amp; Run the script provided with the repo &amp;amp; follow prompts.:&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;WARNING&lt;/strong&gt;: This is a destructive operation. Running this script will wipe
the target drive completely. Ensure you have backed up any existing data
before proceeding.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo chmod +x ./install.sh
sudo bash ./install.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;I tested the script on an &lt;code&gt;nvme0n1&lt;/code&gt; drive with no issues.&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;Run the following commands:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Get your UUID#
sudo blkid /dev/YOUR_DISK &amp;gt; /tmp/blk.txt
# Generate a hashed password
mkpasswd -m yescrypt &amp;gt; /tmp/pass.txt
# Generate a rand # for `networking.hostId`
head -c4 /dev/urandom | xxd -p &amp;gt; /tmp/rand.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;5&quot;&gt;
&lt;li&gt;
&lt;p&gt;Edit &lt;code&gt;flake.nix&lt;/code&gt;, &lt;code&gt;configuration.nix&lt;/code&gt;, and replace the repos
&lt;code&gt;hardware-configuration.nix&lt;/code&gt; with your own.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add &lt;code&gt;neededForBoot&lt;/code&gt; to the &lt;code&gt;home&lt;/code&gt; and &lt;code&gt;persist&lt;/code&gt; datasets in the generated
&lt;code&gt;hardware-configuration.nix&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;  fileSystems.&quot;/home&quot; = {
    device = &quot;rpool/safe/home&quot;;
    fsType = &quot;zfs&quot;;
    neededForBoot = true;
  };

  fileSystems.&quot;/persist&quot; = {
    device = &quot;rpool/safe/persist&quot;;
    fsType = &quot;zfs&quot;;
    neededForBoot = true;
  };
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;7&quot;&gt;
&lt;li&gt;Move the flake to &lt;code&gt;/mnt/etc/nixos/&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo mv ~/my-flake2 /mnt/etc/nixos/
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;8&quot;&gt;
&lt;li&gt;Do a final check and install (change &lt;code&gt;host&lt;/code&gt; to your host name)&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo nixos-install --flake /mnt/etc/nixos/myflake2#host
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Read the comments, they let you know of requirements.&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;9&quot;&gt;
&lt;li&gt;
&lt;p&gt;Reboot. I typically run &lt;code&gt;nixos-install&lt;/code&gt; with the minimal requirements,
reboot, and then configure my window manager/DE.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;After reboot, adjust permissions for your &lt;code&gt;$USER&lt;/code&gt;:&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo mkdir -p /persist/home/$USER
# Set ownership for the persistent home directory
sudo chown -R 1000:100 /persist/home/$USER

# Ensure the home dataset itself is accessible
sudo chmod 755 /home
sudo chmod 755 /persist/home
# Test file, should be gone after reboot
sudo touch /etc/rollback-canary
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;10&quot;&gt;
&lt;li&gt;
&lt;p&gt;Uncomment the import of the impermanence module in the &lt;code&gt;configuration.nix&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Reboot, then check:&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo ls /etc/rollback-canary
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;You should get an error:
&lt;code&gt;&quot;/etc/rollback-canary&quot;: No such file or directory (os error 2)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;What gets Wiped vs. What Stays&lt;/h2&gt;
&lt;p&gt;What gets wiped?:&lt;/p&gt;
&lt;p&gt;Since we roll back (&lt;code&gt;rpool/local/root&lt;/code&gt;):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;/etc&lt;/code&gt; (including system configs) -&amp;gt; WIPED&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;/var&lt;/code&gt; (logs, databases, containers) -&amp;gt; WIPED&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;/root&lt;/code&gt; (the root users home directory) -&amp;gt; WIPED&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;/usr&lt;/code&gt; (though in NixOS this is mostly empty) -&amp;gt; WIPED&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What survives?:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;/nix&lt;/code&gt; (mounted from &lt;code&gt;rpool/local/nix&lt;/code&gt;) -&amp;gt; PERSISTS&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;/boot&lt;/code&gt; (mounted from &lt;code&gt;rpool/local/boot&lt;/code&gt;) -&amp;gt; PERSISTS&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;/home&lt;/code&gt; (mounted from &lt;code&gt;rpool/safe/home&lt;/code&gt;) -&amp;gt; PERSISTS&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;/persists&lt;/code&gt; (mounted from &lt;code&gt;rpool/safe/persist&lt;/code&gt;) -&amp;gt; PERSISTS&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content></entry><entry><title>NixOS Containers</title><id>https://saylesss88.github.io/idiomatic_nix.html</id><updated>2026-01-30T00:00:00+00:00</updated><author><name>saylesss87@proton.me (saylesss88)</name></author><link href="https://saylesss88.github.io/idiomatic_nix.html" rel="alternate"/><content type="html">&lt;h1&gt;Idiomatic Nix&lt;/h1&gt;
&lt;p&gt;There are quite a few resources out there that share best practices, but no
single unified place to find them all. I’m going to try to build on
&lt;a href=&quot;https://nix.dev/guides/best-practices&quot;&gt;nix.dev’s Best practices&lt;/a&gt;, by doing some
research as well as examining the code of some of the leaders in the NixOS
world. (Tweag, numtide, etc.)&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ mdbook-nix-repl for interactive code blocks &lt;/summary&gt;
&lt;p&gt;I’ve added a &lt;code&gt;flake.nix&lt;/code&gt; to the &lt;code&gt;mdbook-nix-repl&lt;/code&gt; repo, you can add it as a
flake input:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;flake.nix&lt;/code&gt;:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# flake.nix
{
  inputs = {
    nixpkgs.url = &quot;github:nixos/nixpkgs/nixos-unstable&quot;;

    mdbook-nix-repl.url = &quot;github:saylesss88/mdbook-nix-repl?dir=server&quot;;
  };

  outputs = { self, nixpkgs, mdbook-nix-repl, ... }: {
    nixosConfigurations.magic = nixpkgs.lib.nixosSystem {
      system = &quot;x86_64-linux&quot;;
      modules = [
        ./configuration.nix

        mdbook-nix-repl.nixosModules.default
      ];
    };
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;&lt;code&gt;configuration.nix&lt;/code&gt;:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{ pkgs, ... }:
{
  imports = [
  ];

  # This option is now provided by the module you imported from the flake
  custom.nix-repl-server = {
    enable = true;
    port = 8080;
    tokenFile = &quot;/etc/nix-repl-server.env&quot;;
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;Copy the value of &lt;code&gt;NIX_REPL_TOKEN&lt;/code&gt; in &lt;code&gt;theme/index.hbs&lt;/code&gt;, and add create file
&lt;code&gt;/etc/nix-repl-server.env&lt;/code&gt;:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Create the file with strict permissions (root read-only)
sudo touch /etc/nix-repl-server.env
sudo chmod 600 /etc/nix-repl-server.env

# Edit it to add: NIX_REPL_TOKEN=your_token_from_index_hbs
sudo vim /etc/nix-repl-server.env
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Expected format:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;NIX_REPL_TOKEN=9deb7efadb74b9e962e7911bb5caf3b3fef275a1b915b526
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;Rebuild, and the server will now be running at boot.&lt;/li&gt;
&lt;/ol&gt;
&lt;/details&gt;
&lt;p&gt;All the following examples are interactive, press play to see the result.(The
following examples come directly from &lt;code&gt;nix.dev&lt;/code&gt;)&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;rec {
    a = 1;
    b = a + 2;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Use this instead:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;let
  a = 1;
in {
    a = a;
    b = a + 2;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;💡 TIP Self-reference can be achieved by explicitly naming the attribute set:&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt; let
   argset = {
     a = 1;
     b = argset.a + 2;
  };
in
  argset
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Updating nested attribute sets&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{ a = 1; b = 2; } // { b = 3; c = 4; }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Updates are shallow, names on the right take precidence:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{ a = { b = 1; }; } // { a = { c = 3; }; }
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;let pkgs = import &amp;lt;nixpkgs&amp;gt; {}; in
pkgs.lib.recursiveUpdate { a = { b = 1; }; } { a = { c = 3;}; }
&lt;/code&gt;&lt;/pre&gt;
</content></entry><entry><title>Secure Boot on Libvirt stack</title><id>https://saylesss88.github.io/nix/secureboot_libvirt.html</id><updated>2026-01-19T00:00:00+00:00</updated><author><name>saylesss87@proton.me (saylesss88)</name></author><link href="https://saylesss88.github.io/nix/secureboot_libvirt.html" rel="alternate"/><content type="html">&lt;h1&gt;Secure Boot in a Libvirt (KVM) VM with ZFS on LUKS Impermanence&lt;/h1&gt;
&lt;h2&gt;Initial VM Setup&lt;/h2&gt;
&lt;p&gt;When creating the VM in virt-manager:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Before clicking “Finish”, check the “Customize configuration before install”
box&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In the Overview section, change Firmware from BIOS to UEFI x86_64:
&lt;code&gt;/usr/share/edk2/ovmf/OVMF_CODE_4M.secboot.qcow2&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Proceed with the NixOS installation as normal. For lanzaboote to build
successfully, I had to pin it to nixpkgs &lt;code&gt;25.05&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Known Issue&lt;/strong&gt;: After running &lt;code&gt;nixos-install&lt;/code&gt; and rebooting, the SATA CDROM
source path may be cleared. If the VM fails to boot, manually reselect the NixOS
ISO in the SATA settings and reboot. ​&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Configure Firmware for Custom Secure Boot Keys&lt;/h2&gt;
&lt;p&gt;The default configuration uses Microsoft’s pre-enrolled keys, which won’t trust
your custom-signed kernel. To enable custom key enrollment, you need to modify
the VM’s XML configuration. ​&lt;/p&gt;
&lt;p&gt;On your host, find your VM name:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;virsh -c qemu:///system list --all
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Example Output:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt; Id   Name             State
---------------------------------
 -    nixos-unstable   shut off
 -    nixos            shut off
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Edit the VM configuration:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;virsh edit nixos-unstable
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Make the following changes to the &lt;code&gt;&amp;lt;os&amp;gt;&lt;/code&gt; section:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Change the &lt;code&gt;enrolled-keys&lt;/code&gt; feature from &lt;code&gt;yes&lt;/code&gt; to &lt;code&gt;no&lt;/code&gt;:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;feature enabled=&apos;no&apos; name=&apos;enrolled-keys&apos;/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;Delete the explicit &lt;code&gt;&amp;lt;loader&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;nvram&amp;gt;&lt;/code&gt; lines. These conflict with
libvirt’s firmware autoselection when using &lt;code&gt;enrolled-keys=&apos;no&apos;&lt;/code&gt;:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;!-- DELETE THESE TWO LINES --&amp;gt;
&amp;lt;loader readonly=&apos;yes&apos; secure=&apos;yes&apos; type=&apos;pflash&apos; format=&apos;qcow2&apos;&amp;gt;/usr/share/edk2/ovmf/OVMF_CODE_4M.secboot.qcow2&amp;lt;/loader&amp;gt;
&amp;lt;nvram template=&apos;/usr/share/edk2/ovmf/OVMF_VARS_4M.secboot.qcow2&apos; templateFormat=&apos;qcow2&apos; format=&apos;qcow2&apos;&amp;gt;/var/lib/libvirt/qemu/nvram/nixos-unstable_VARS.qcow2&amp;lt;/nvram&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Your final &lt;code&gt;&amp;lt;os&amp;gt;&lt;/code&gt; section should look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;os firmware=&apos;efi&apos;&amp;gt;
  &amp;lt;type arch=&apos;x86_64&apos; machine=&apos;pc-q35-10.1&apos;&amp;gt;hvm&amp;lt;/type&amp;gt;
  &amp;lt;firmware&amp;gt;
    &amp;lt;feature enabled=&apos;no&apos; name=&apos;enrolled-keys&apos;/&amp;gt;
    &amp;lt;feature enabled=&apos;yes&apos; name=&apos;secure-boot&apos;/&amp;gt;
  &amp;lt;/firmware&amp;gt;
  &amp;lt;boot dev=&apos;hd&apos;/&amp;gt;
&amp;lt;/os&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;Add the &lt;code&gt;&amp;lt;serial&amp;gt;&lt;/code&gt; tag as a best practice&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;    &amp;lt;disk type=&apos;file&apos; device=&apos;disk&apos;&amp;gt;
      &amp;lt;driver name=&apos;qemu&apos; type=&apos;qcow2&apos; discard=&apos;unmap&apos;/&amp;gt;
      &amp;lt;source file=&apos;/var/lib/libvirt/images/nixos-unstable-1.qcow2&apos;/&amp;gt;
      &amp;lt;serial&amp;gt;disk01&amp;lt;/serial&amp;gt;
      &amp;lt;target dev=&apos;vda&apos; bus=&apos;virtio&apos;/&amp;gt;
      &amp;lt;address type=&apos;pci&apos; domain=&apos;0x0000&apos; bus=&apos;0x04&apos; slot=&apos;0x00&apos; function=&apos;0x0&apos;/&amp;gt;
    &amp;lt;/disk&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;This enables commands like: &lt;code&gt;zpool import -d /dev/disk/by-id rpool&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Save and exit the editor. Libvirt will now automatically create a new NVRAM file
in Setup Mode (no keys enrolled).&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;NixOS Installation with ZFS on root with LUKS&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://saylesss88.github.io/nix/encrypted_zfs.html&quot;&gt;ZFS on root with LUKS &amp;amp; Impermanence&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Add the impermanence flake input. I also had to pin lanzaboote to nixpkgs
&lt;code&gt;25.05&lt;/code&gt; for lanzaboote to build successfully:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;inputs = {
   impermanence.url = &quot;github:nix-community/impermanence&quot;;
   nixpkgs.url = &quot;github:nixos/nixpkgs/nixos-unstable&quot;;
   nixpkgs-stable.url = &quot;github:nixos/nixpkgs/nixos-25.05&quot;;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add lanzaboote, if you don’t use &lt;code&gt;unstable&lt;/code&gt;, you can obviously avoid the
overlay:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{ pkgs, lib, inputs, ... }: {
# configuration.nix
nixpkgs.overlays = [
   (final: prev: {
      lanzaboote = (inputs.nixpkgs-stable.legacyPackages.${pkgs.system}.lanzaboote or prev.lanzaboote)
   })
];

environment.systemPackages = [ pkgs.sbctl ];

boot.loader.systemd-boot.enable = lib.mkForce false;

boot.lanzaboote = {
  enable = true;
  pkiBundle = &quot;/var/lib/sbctl&quot;;
};
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And &lt;code&gt;impermanence.nix&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{ inputs, lib, ... }: {
   imports = [
      inputs.impermanence.nixosModules.impermanence
   ];
   boot.initrd.postMountCommands = lib.mkAfter &apos;&apos;
     zfs rollback -r rpool/local/root@blank
   &apos;&apos;;
   environment.persistence.&quot;/persist&quot; = {
      directories = [ &quot;/var/lib/sbctl&quot; &quot;/var/lib/nixos&quot; ];
   };
   fileSystems.&quot;/persist&quot; = {
      device = &quot;rpool/safe/persist&quot;;
      fsType = &quot;zfs&quot;;
      neededForBoot = true;
   };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;Lanzaboote Installation&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://saylesss88.github.io/installation/enc/lanzaboote.html&quot;&gt;Secure Boot with Lanzaboote&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;Enroll the Secure Boot Keys&lt;/h2&gt;
&lt;p&gt;After the XML changes, boot the VM and enter the firmware setup (press ESC
during boot).&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Navigate to Device Manager → Secure Boot Configuration&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Switch to “Custom mode”, uncheck Attempt Secure Boot [ ], select “Reset
Secure Boot Keys”, save with F10, and reboot.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Enroll the keys: &lt;code&gt;sudo sbctl enroll-keys -m&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Reboot: After enrolling the keys and rebooting, the system will automatically
be placed in “Standard mode”, and Attempt Secure Boot [x] selected. You do
not need to re-enter firmware setup mode.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Verify Secure Boot Status: &lt;code&gt;bootctl status&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
</content></entry><entry><title>ZFS with LUKS and Impermanence</title><id>https://saylesss88.github.io/installation/enc/encrypted_ZFS.html</id><updated>2026-01-17T00:00:00+00:00</updated><author><name>saylesss87@proton.me (saylesss88)</name></author><link href="https://saylesss88.github.io/installation/enc/encrypted_ZFS.html" rel="alternate"/><content type="html">&lt;h1&gt;ZFS with LUKS and Impermanence&lt;/h1&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Table of Contents&lt;/summary&gt;
&lt;!-- toc --&gt;
&lt;/details&gt;
&lt;p&gt;I tested this on the libvirt stack with KVM, this should work on bare metal with
a few omissions.&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ SSH Method &lt;/summary&gt;
&lt;p&gt;This saves a ton of typing…&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Boot the minimal ISO&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set a password for the &lt;code&gt;nixos&lt;/code&gt; user: &lt;code&gt;sudo passwd nixos&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Find the IP address: &lt;code&gt;ip a&lt;/code&gt; (look for &lt;code&gt;etho&lt;/code&gt; or &lt;code&gt;wlan0&lt;/code&gt;)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;SSH in from your host or another machine: &lt;code&gt;ssh nixos@192.168.1.x&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/saylesss88/my-flake2&quot;&gt;Starter Repo containing a flake and the configuration.nix from this chapter&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git clone https://github.com/saylesss88/my-flake2.git
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I included a personally tested script that handles all of the steps up until you
start configuring the &lt;code&gt;configuration.nix&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd my-flake2
sudo chmod +x install.sh
sudo ./install.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You will be prompted to enter your disk, just enter the disk, not anything else,
like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;vda
# OR
nvme0n1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After the first script completes, there is a &lt;code&gt;test.sh&lt;/code&gt; script that ensures
everything is in order.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo chmod +x test.sh
sudo ./test.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;
&lt;h2&gt;What is OpenZFS&lt;/h2&gt;
&lt;p&gt;ZFS is an advanced filesystem, originally developed by Sun Microsystems in 05.&lt;/p&gt;
&lt;p&gt;OpenZFS is a fork of the proprietary Oracle ZFS, it was forked over a decade
ago. Most of the code remains the same so you can check the
&lt;a href=&quot;https://docs.oracle.com/en/storage/zfs-storage/index.html&quot;&gt;Oracle website&lt;/a&gt; for
docs and administration guides.&lt;/p&gt;
&lt;p&gt;ZFS is licensed under the
&lt;a href=&quot;https://en.wikipedia.org/wiki/CDDL&quot;&gt;Common Development and Distribution License&lt;/a&gt;
(CDDL). Because the CDDL is incompatible with the GPL,
&lt;a href=&quot;https://sfconservancy.org/blog/2016/feb/25/zfs-and-linux/&quot;&gt;it is not possible&lt;/a&gt;
for ZFS to be included in the Linux Kernel. This requirement, however, does not
prevent a native Linux kernel module from being developed and distributed by a
third party, as is the case with &lt;a href=&quot;https://openzfs.org/&quot;&gt;OpenZFS&lt;/a&gt; (previously
named ZFS on Linux). –arch wiki&lt;/p&gt;
&lt;h2&gt;Comparison: OpenZFS Native Encryption vs. LUKS&lt;/h2&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ OpenZFS Native Encryption vs. LUKS &lt;/summary&gt;
&lt;blockquote&gt;
&lt;p&gt;NOTE: This isn’t an attack on ZFS Native Encryption, I’m just presenting the
information, the choice is yours. Unless you have a high threat model, ZFS
native encryption has many benefits such improved flexibility and
authentication.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;LUKS (Device Layer)&lt;/strong&gt;: LUKS operates on the block device (like
&lt;code&gt;/dev/nvme0n1&lt;/code&gt;). It knows nothing about files, folders, or datasets. It just
sees a stream of bytes and scrambles them.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Everything on the partition is encrypted. The filesystem sits inside the
encrypted container.&lt;/li&gt;
&lt;li&gt;With standard LUKS, an adversary can see you are using encryption (the LUKS
header is visible), but they cannot determine what filesystem (ZFS, ext4,
etc.) is inside. If you go a step further and use a detached header, the
entire drive looks like random noise, providing plausible deniability that
any data exists at all.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;ZFS Native (Filesystem Layer)&lt;/strong&gt;: ZFS encryption operates at the Dataset
(Filesystem) level. It is aware of the structure of your data.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Only the &lt;strong&gt;file blocks&lt;/strong&gt; (the actual content of your files) and the &lt;strong&gt;file
attributes&lt;/strong&gt; (ACLs, permissions) within a specific dataset are encrypted.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;No matter how careful you are with ZFS Native encryption, these cannot be
hidden:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The Structure&lt;/strong&gt;: The fact that &lt;code&gt;dataset A&lt;/code&gt; is a child of &lt;code&gt;dataset B&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The Volume&lt;/strong&gt;: The exact amount of disk space used by each dataset (in bytes)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The Activity&lt;/strong&gt;: The fact that &lt;em&gt;something&lt;/em&gt; changed at a specific time (via
snapshot creation or &lt;code&gt;used&lt;/code&gt; property changing)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;By using generic dataset names (e.g., &lt;code&gt;data/vol1&lt;/code&gt; instead of
&lt;code&gt;data/mistress_photos&lt;/code&gt;) and automated snapshot schedules, you can strip the
semantic value from the exposed metadata. An attacker will see that you have
data and how much you have, but they won’t know what it is or which dataset is
the valuable one.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Sanitizing your ZFS usage (Mitigating risk)&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;By following a few simple best practices you can mitigate most, if not all of
the risk depending on the situation.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Use generic IDs for dataset names, instead of &lt;code&gt;tank/mistress_photos&lt;/code&gt; use
&lt;code&gt;tank/vol-A&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Automate your snapshots to prevent traffic analysis&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Use padding to mitigate size analysis, add dummy files to obfuscate the size
(Can be tedious in ZFS). This is the hardest to mitigate, if this is a real
threat, use LUKS instead.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;OpenZFS native encryption allows you to transparently encrypt data at rest
within ZFS itself. It was initially released in May 2019, giving it much less
time to be scrutinized compared to LUKS (initial release in 2004).&lt;/p&gt;
&lt;p&gt;OpenZFS Encryption operates at the dataset layer, not the disk layer, which
creates several critical security gaps that full-disk encryption (FDE) solutions
like LUKS completely avoid.&lt;/p&gt;
&lt;p&gt;Unlike LUKS, which presents a “black box” of random noise to anyone without the
key, ZFS Native Encryption must leave certain structural elements visible to the
operating system. This is a deliberate design choice that allows ZFS to perform
maintenance tasks (like scrubbing) on locked datasets without requiring the user
to type in a password. (Depending on your threat model, this can be a deal
breaker).&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Threat Example&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Example: Threat Model where ZFS Native Encryption falls short compared to
LUKS&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The “Pattern of Life” Attack (Timestamp Leaks)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;The Leak&lt;/strong&gt;: ZFS snapshot names and creation times are visible in plaintext
(&lt;code&gt;zfs list -t snapshot&lt;/code&gt;)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Threat Model&lt;/strong&gt;: An adversary monitoring your backups or seizing your drive
can build a profile of your behavior without decrypting a single byte.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;If you claim to be asleep at 3AM, but &lt;code&gt;zfs list&lt;/code&gt; shows snapshots being created
or data changing size at 3:15 AM, your alibi is broken.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Or, say a whistleblower contacts a journalist. An adversary seizes the
journalist’s laptop. They can’t read the files, but they see a new dataset
grew by exactly 5GB at the exact time the leak occured. Correlation = Guilt.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Compress &amp;amp; Encrypt&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;It’s actually a common misconception that LUKS prevents compression before
encryption, both methods are able to do this.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;ZFS Native: &lt;code&gt;Data -&amp;gt; Compress -&amp;gt; Encrypt -&amp;gt; Checksum -&amp;gt; Write to Disk&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;ZFS on LUKS: &lt;code&gt;Data -&amp;gt; Compress -&amp;gt; ZFS Write -&amp;gt; LUKS Encrypt -&amp;gt; Write to Disk&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Both methods save disk space.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Integrety and Authentication&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;This is the strongest architectural argument for ZFS Native.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ZFS Native: Uses AES-GCM (default) which is an Authenticated Encryption with
Associated Data (AEAD) mode. If a single bit is flipped on disk (maliciously
or accidentally), ZFS refuses to return the bad data and reports a checksum
error.
&lt;ul&gt;
&lt;li&gt;NOTE: You can get integrity with LUKS2 with &lt;code&gt;dm-integrity&lt;/code&gt;, but it incurs a
massive performance penalty and is considered experimental for production
use.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;ZFS Native Encryption OR LUKS&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Use LUKS if&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;You can’t afford to leak metadata like dataset names or snapshot timestamps.
LUKS makes the drive look like random noise.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You require rock-solid stability. The ZFS-on-LUKS stack is very
“battle-tested”.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Use ZFS Native Encryption if&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;You have “Untrusted” Offsite Backups: This is a really nice feature! You can
&lt;code&gt;zfs send -w&lt;/code&gt; your data to a friend’s server or a cloud VM. The remote host
cannot mount or read your data because they never have the key, but they can
still scrub the pool and verify the data integrity.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You need granular control: ZFS Native Encryption allows you to for example,
keep your OS root unencrypted for fast booting/repair, but your &lt;code&gt;/home&lt;/code&gt;
directory encrypted.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You run many VMs/Containers: You can create a new encrypted dataset for a
specific project/client without repartitioning or creating loopback files.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You want to verify data authenticity.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://arstechnica.com/gadgets/2021/06/a-quick-start-guide-to-openzfs-native-encryption/&quot;&gt;arsTechnica quick start to openzfs-native-encryption&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://discourse.practicalzfs.com/t/is-native-encryption-ready-for-production-use/532&quot;&gt;Practical ZFS is native encryption ready for production use?&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/details&gt;
&lt;h2&gt;Getting Started&lt;/h2&gt;
&lt;p&gt;When creating the VM, before clicking “Finish”, check the “Customize
configuration before install” box and choose EFI Firmware &amp;gt; BIOS. &lt;strong&gt;You will
waste a bunch of time if you forget to do this&lt;/strong&gt;!&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I used &lt;code&gt;OVMF_CODE.fd&lt;/code&gt; in my testing.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Format your disk&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Partition &amp;amp; Format&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo cfdisk /dev/vda
sudo mkfs.fat -F32 /dev/vda1
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;Setup LUKS&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo cryptsetup luksFormat /dev/vda2
sudo cryptsetup open /dev/vda2 cryptroot
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;Create zpool (Edited 2026-01-18 normalization=none)&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo zpool create \
  -o ashift=12 \
  -o autotrim=on \
  -O acltype=posixacl \
  -O canmount=off \
  -O compression=zstd \
  -O normalization=none \
  -O relatime=on \
  -O xattr=sa \
  -O dnodesize=auto \
  -O mountpoint=none \
  rpool /dev/mapper/cryptroot
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;5&quot;&gt;
&lt;li&gt;Dataset Creation&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# root (ephemeral)
sudo zfs create -p -o canmount=noauto -o mountpoint=legacy rpool/local/root
sudo zfs snapshot rpool/local/root@blank

# nix store
sudo zfs create -p -o mountpoint=legacy rpool/local/nix

# persistent data
sudo zfs create -p -o mountpoint=legacy rpool/safe/home
sudo zfs create -p -o mountpoint=legacy rpool/safe/persist
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;mountpoint=legacy&lt;/code&gt; means that systemd will take care of the mounting&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;6&quot;&gt;
&lt;li&gt;Mounting&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 1. Mount root first
sudo mount -t zfs rpool/local/root /mnt

# 2. Create directories
sudo mkdir -p /mnt/{nix,home,persist,boot}

# 3. Mount ESP directly to /boot (simpler and safer for systemd-boot)
sudo mount -t vfat -o umask=0077 /dev/vda1 /mnt/boot

# 4. Mount other ZFS datasets
sudo mount -t zfs rpool/local/nix /mnt/nix
sudo mount -t zfs rpool/safe/home /mnt/home
sudo mount -t zfs rpool/safe/persist /mnt/persist
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;7&quot;&gt;
&lt;li&gt;Configuration Prep&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo nixos-generate-config --root /mnt
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;export NIX_CONFIG=&apos;experimental-features = nix-command flakes&apos;
nix-shell -p helix
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo blkid /dev/vda2
# Copy the uuid
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# configuration.nix
 boot.initrd.luks.devices = {
     cryptroot = {
       device = &quot;/dev/disk/by-uuid/uuid#&quot;;
       allowDiscards = true;
       preLVM = true;
     };
   };
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;boot.initrd.luks.devices.&quot;cryptroot&quot;.device = &quot;/dev/disk/by-uuid/&amp;lt;UUID-OF-PARTITION-2&amp;gt;&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;Prep &lt;code&gt;configuration.nix&lt;/code&gt;&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;head -c4 /dev/urandom | xxd -p &amp;gt; /tmp/rand.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Create password file in a persistent location&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo mkdir -p /mnt/persist/etc/nixos-secrets/passwords

 #1) This is for `initialHashedPassword`
 #   Read this in with `:r /tmp/pass.txt`
mkpasswd --method=yescrypt &amp;gt; /tmp/pass.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After first reboot, the above files will be placed directly under &lt;code&gt;/persist/&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;(Edited 2026-01-18 use &lt;code&gt;postMountCommands&lt;/code&gt; &amp;gt; &lt;code&gt;postResumeCommands&lt;/code&gt;)&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{ config, lib, pkgs, ... }:

{
  # ------------------------------------------------------------------
  # 1. Boot loader – systemd-boot (UEFI only)
  # ------------------------------------------------------------------
  boot.loader = {
    systemd-boot = {
      enable = true;
      consoleMode = &quot;max&quot;;
      editor = false;
    };
    efi = {
      canTouchEfiVariables = true;
      efiSysMountPoint = &quot;/boot&quot;;
    };
  };

  # ------------------------------------------------------------------
  # 2. ZFS support see: https://openzfs.github.io/openzfs-docs/Getting%20Started/NixOS/index.html
  # ------------------------------------------------------------------
  boot.supportedFilesystems = [ &quot;zfs&quot; ];
  boot.zfs.devNodes = &quot;/dev/&quot;;       # Critical for VMs
  # Not needed with LUKS
  boot.zfs.requestEncryptionCredentials = false;
  # systemd handles mounting
  systemd.services.zfs-mount.enable = false;

  services.zfs = {
    autoScrub.enable = true;
    # periodically runs `zpool trim`
    trim.enable = true;
    # autoSnapshot = true;
  };

  # ------------------------------------------------------------------
  # 3. LUKS
  # ------------------------------------------------------------------
   boot.initrd.luks.devices = {
     cryptroot = {
    # replace uuid# with output of UUID # from `sudo blkid /dev/vda2`
       device = &quot;/dev/disk/by-uuid/uuid#&quot;;
       allowDiscards = true;
       preLVM = true;
     };
   };

  # ------------------------------------------------------------------
  # 4. Roll-back root to blank snapshot on **every** boot
  # ------------------------------------------------------------------
 # Uncomment after first reboot
 # boot.initrd.postMountCommands = lib.mkAfter &apos;&apos;
 #   zfs rollback -r rpool/local/root@blank
 # &apos;&apos;;

  # ------------------------------------------------------------------
  # 5. Basic system (root password, serial console for VM)
  # ------------------------------------------------------------------
  # Unique 8-hex hostId (run once in live ISO: head -c4 /dev/urandom | xxd -p)
  networking.hostId = &quot;a1b2c3d4&quot;;    # &amp;lt;&amp;lt;&amp;lt;--- replace with your own value

  users.users.root.initialPassword = &quot;changeme&quot;;   # change after first login

  boot.kernelParams = [ &quot;console=tty1&quot; ];

  # ------------------------------------------------------------------
  #  Users
  # ------------------------------------------------------------------

  users.mutableUsers = false;

  # Change `your-user`
  users.users.your-user = {
    isNormalUser = true;
    extraGroups = [ &quot;wheel&quot; ];
    group = &quot;your-user&quot;;
    # :r /tmp/pass.txt:
    initialHashedPassword = &quot;&quot;;
  };

  # This enables `chown -R your-user:your-user`
  users.groups.your-user = { };

  # ------------------------------------------------------------------
  #  (Optional) Helpful for recovery situations
  # ------------------------------------------------------------------
  # users.users.admin = {
  #  isNormalUser = true;
  #  description = &quot;admin account&quot;;
  #  extraGroups = [ &quot;wheel&quot; ];
  #  group = &quot;admin&quot;;
    # initialHashedPassword = &quot;Output of `:r /tmp/pass.txt`&quot;;
 # };

 # users.groups.admin = { };
  # ------------------------------------------------------------------

  # ------------------------------------------------------------------
  # 6. (Optional) Enable SSH for post-install configuration
  # ------------------------------------------------------------------
  # services.openssh = {
  #  enable = true;
  #  settings.PermitRootLogin = &quot;yes&quot;;
  #};

  # ------------------------------------------------------------------
  # 7. Mark /persist as needed for boot
  # ------------------------------------------------------------------
  fileSystems.&quot;/persist&quot;.neededForBoot = true;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After reboot, you can uncomment:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;  boot.initrd.postMountCommands = lib.mkAfter &apos;&apos;
    zfs rollback -r rpool/local/root@blank
  &apos;&apos;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Uncomment the above script and test: (Don’t forget that the &lt;code&gt;/etc&lt;/code&gt; directory
will be wiped, including your &lt;code&gt;configuration.nix&lt;/code&gt; and
&lt;code&gt;hardware-configuration.nix&lt;/code&gt;!)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Configuration backup&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo mkdir -p /persist/etc
sudo cp /etc/nixos/hardware-configuration.nix /etc/nixos/configuration.nix /persist/etc/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Rollback Test&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo touch /etc/rollback-canary
sudo reboot
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the rollback is working, &lt;code&gt;/etc/rollback-canary&lt;/code&gt; should be gone after reboot
(while things in &lt;code&gt;/persist&lt;/code&gt; remain).&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Adding a disk serial (libvirt XML)&lt;/h2&gt;
&lt;p&gt;NixOS ZFS boot support is broken for virtio drives without serial numbers.
Virtio disks without serials don’t appear in /dev/disk/by-id, but ZFS boot logic
only tries to import pools from /dev/disk/by-id. The official OpenZFS NixOS
documentation explicitly states: “If virtio is used as disk bus, power off the
VM and set serial numbers for disk”&lt;/p&gt;
&lt;p&gt;In the &lt;code&gt;&amp;lt;disk ...&amp;gt;&lt;/code&gt; block of your root disk add:&lt;/p&gt;
&lt;p&gt;Add to &lt;code&gt;.zshrc&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;export LIBVIRT_DEFAULT_URI=&quot;qemu:///system&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;virsh list --all
virsh edit nixos-unstable
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the first &lt;code&gt;&amp;lt;disk ... device=&apos;disk&apos;&amp;gt;&lt;/code&gt; section (the one with target &lt;code&gt;dev=&apos;vda&apos;&lt;/code&gt;
&lt;code&gt;bus=&apos;virtio&apos;&lt;/code&gt;), add a &lt;code&gt;&amp;lt;serial&amp;gt;&lt;/code&gt; line, e.g.:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;disk type=&apos;file&apos; device=&apos;disk&apos;&amp;gt;
  &amp;lt;driver name=&apos;qemu&apos; type=&apos;qcow2&apos; discard=&apos;unmap&apos;/&amp;gt;
  &amp;lt;source file=&apos;/var/lib/libvirt/images/nixos-unstable-1.qcow2&apos; index=&apos;2&apos;/&amp;gt;
  &amp;lt;backingStore/&amp;gt;
  &amp;lt;target dev=&apos;vda&apos; bus=&apos;virtio&apos;/&amp;gt;
  &amp;lt;serial&amp;gt;disk01&amp;lt;/serial&amp;gt;
  &amp;lt;alias name=&apos;virtio-disk0&apos;/&amp;gt;
  ...
&amp;lt;/disk&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;Issues I’ve Come Across&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;sops-nix README&lt;/strong&gt;: Explicitly warns: “If you are using Impermanence, the key
used for secret decryption … must be in a persisted directory, loaded early
enough during boot.” It specifically cites activation timing as the reason this
fails.&lt;/p&gt;
&lt;p&gt;Typically the solution was to add &lt;code&gt;neededForUsers = true;&lt;/code&gt; in your
“password_hash” block but that isn’t working for this setup. (ZFS/LUKS/Imperm)&lt;/p&gt;
&lt;p&gt;I’ve been using &lt;code&gt;initialHashedPassword&lt;/code&gt;, which bypasses the race entirely by
baking the salted hash into the store. This is all that I’ve found that works so
far…&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Without adding the &lt;code&gt;&amp;lt;serial&amp;gt;disk01&amp;lt;/serial&amp;gt;&lt;/code&gt; to the XML when you reboot, your
system will hang before asking for your LUKS password.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Without clicking “Customize configuration before install” box and choosing EFI
Firmware instead of the default BIOS, &lt;strong&gt;you will not be able to boot at all&lt;/strong&gt;.
This seems to be the case for all custom disk layouts with NixOS on the
libvirt stack.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h3&gt;Resources&lt;/h3&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Resources &lt;/summary&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://openzfs.github.io/openzfs-docs/&quot;&gt;openzfs-docs&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://openzfs.github.io/openzfs-docs/Getting%20Started/NixOS/Root%20on%20ZFS.html&quot;&gt;openzfs-docs NixOS Root on ZFS&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://wiki.nixos.org/wiki/ZFS&quot;&gt;NixOS Wiki ZFS&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://klarasystems.com/articles/keeping-data-safe-with-openzfs-security-encryption-delegation/&quot;&gt;klarasystems OpenZFS: Security, Encryption, and Delegation Sept 2025&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://klarasystems.com/articles/improving-replication-security-with-openzfs-delegation/&quot;&gt;klarasystems Improving Replication Security with OpenZFS Delegation&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://forum.level1techs.com/t/zfs-guide-for-starters-and-advanced-users-concepts-pool-config-tuning-troubleshooting/196035&quot;&gt;ZFS Guide for starters &amp;amp; advanced users&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://openzfs.org/wiki/System_Administration&quot;&gt;OpenZFS Sysem Administration&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.oracle.com/cd/E19253-01/819-5461/&quot;&gt;Oracle Solaris ZFS Admin Guide&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.freebsd.org/en/books/handbook/zfs/&quot;&gt;FreeBSD Handbook Chapter 22 The Z File System (ZFS)&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/ankek/awesome-zfs&quot;&gt;awesome-zfs&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://arstechnica.com/series/storage-fundamentals/&quot;&gt;arsTechnica Storage Fundamentals&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/jimsalterjrs/sanoid/&quot;&gt;sanoid&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://discourse.practicalzfs.com/&quot;&gt;Practical ZFS&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/openzfs/zfs&quot;&gt;openzfs/zfs GH Repo&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/openzfs/zfs/issues&quot;&gt;openzfs Issues&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://zfsonlinux.org/&quot;&gt;zfsonlinux.org&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://wiki.archlinux.org/title/ZFS&quot;&gt;arch wiki ZFS&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://tech-couch.com/post/btrfs-vs-zfs&quot;&gt;tech-couch Btrfs Vs ZFS&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://blog.purestorage.com/purely-educational/btrfs-vs-zfs/&quot;&gt;PureStorage btrfs vs zfs&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.cs.hmc.edu/~rhodes/cs134/readings/The%20Zettabyte%20File%20System.pdf&quot;&gt;The Zettabyte File System design docs&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://mrczntt.com/blog/understanding-zfs-the-zettabyte-file-system/&quot;&gt;Understanding ZFS, the Zettabyte File System&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/ZFS&quot;&gt;ZFS - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/details&gt;
</content></entry><entry><title>ZFS Impermanence in a VM</title><id>https://saylesss88.github.io/nix/zfs_impermanence.html</id><updated>2026-01-16T00:00:00+00:00</updated><author><name>saylesss87@proton.me (saylesss88)</name></author><link href="https://saylesss88.github.io/nix/zfs_impermanence.html" rel="alternate"/><content type="html">&lt;h1&gt;ZFS Impermanence in a VM&lt;/h1&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Table of Contents&lt;/summary&gt;
&lt;!-- toc --&gt;
&lt;/details&gt;
&lt;p&gt;Yet another blog post inspired by
&lt;a href=&quot;https://grahamc.com/blog/erase-your-darlings/&quot;&gt;erase your darlings&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I only tested this within a VM although with a few small tweaks it should work
on bare metal. I used the libvirtd stack with KVM for this.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;NOTE: This example doesn’t use encryption, it would be easy to add ZFS Native
Encryption by changing the first &lt;code&gt;zpool&lt;/code&gt; command. It’s good enough for most
people but does leak some metadata. I’ll add a LUKS example eventually which
is more involved.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/saylesss88/my-flake2&quot;&gt;Starter Repo containing a flake and the configuration.nix from this chapter&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git clone https://github.com/saylesss88/my-flake2.git
&lt;/code&gt;&lt;/pre&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ SSH Method to enable copy-paste&lt;/summary&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Boot the minimal ISO&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set a password for the &lt;code&gt;nixos&lt;/code&gt; user: &lt;code&gt;sudo passwd nixos&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Find the IP address: &lt;code&gt;ip a&lt;/code&gt; (look for &lt;code&gt;eth0&lt;/code&gt; or &lt;code&gt;wlan0&lt;/code&gt;)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;SSH in from another machine: &lt;code&gt;ssh nixos@192.168.1.x&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Clone the repo and copy-paste commands from your browser to the terminal.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/details&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Multi-TTY Method (No extra Devices) &lt;/summary&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Log in on the default TTY (usually Alt+F1).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Switch to a second TTY by pressing Alt+F2.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Log in again (user nixos, no password default).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Clone your repo in TTY2: git clone https://github.com/your/repo.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Open the README with a pager: less repo/README.md.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Switch back to TTY1 (Alt+F1) to execute commands.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Toggle back and forth (Alt+F2 / Alt+F1) to read and type.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/details&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ tmux Method (Split Screen) &lt;/summary&gt;
&lt;p&gt;The minimal ISO includes &lt;code&gt;tmux&lt;/code&gt; in the package set, but it’s not installed in
the environment by default.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Run: &lt;code&gt;nix run nixpkgs#tmux&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Once inside tmux, split the screen vertically: Press &lt;strong&gt;Ctrl+b&lt;/strong&gt; then &lt;strong&gt;%&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In the right pane, open the README: &lt;code&gt;less repo/README.md&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In the left pane, type the commands&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Switch panes with &lt;strong&gt;Ctrl+b&lt;/strong&gt; then &lt;strong&gt;Left/Right Arrow&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/details&gt;
&lt;p&gt;Start with a minimal ISO.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://channels.nixos.org/nixos-25.11/latest-nixos-minimal-x86_64-linux.iso&quot;&gt;Download Minimal (64-bit Intel-AMD)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Choose the LTS image, it comes with the &lt;code&gt;zfs&lt;/code&gt; module enabled.&lt;/p&gt;
&lt;p&gt;I’ve also found that for my system it works best to switch the Video Model to
Virtio, with 3D accelleration disabled (causes mouse inversion).&lt;/p&gt;
&lt;p&gt;When creating the VM, before clicking “Finish”, check the “Customize
configuration before install” box and choose EFI Firmware &amp;gt; BIOS. &lt;strong&gt;You will
waste a bunch of time if you forget to do this&lt;/strong&gt;!&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I used &lt;code&gt;OVMF_CODE.fd&lt;/code&gt; in my testing.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Check out your layout:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo fdisk -l
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Format your disk:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo cfdisk /dev/vda
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create a 1G &lt;strong&gt;EFI System&lt;/strong&gt; first, then a &lt;strong&gt;Linux Filesystem&lt;/strong&gt; with the remaining
space. I used (100G)&lt;/p&gt;
&lt;p&gt;For the following guide, you want &lt;code&gt;/dev/vda1&lt;/code&gt; to be your &lt;strong&gt;EFI System&lt;/strong&gt;
partition, and &lt;code&gt;/dev/vda2&lt;/code&gt; to be the &lt;strong&gt;Linux Filesystem&lt;/strong&gt; partition.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo fdisk -l
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo mkfs.vfat -n EFI /dev/vda1
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Create Your ZFS Partitions&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Create a zpool: (Edited 2026-01-18 normalization=none)&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;zpool create \
  -o ashift=12 \
  -o autotrim=on \
  -O acltype=posixacl \
  -O canmount=off \
  -O dnodesize=auto \
  -O normalization=none \
  -O relatime=on \
  -O xattr=sa \
  -O mountpoint=none \
  rpool /dev/vda2
&lt;/code&gt;&lt;/pre&gt;
&lt;details&gt;
&lt;summary&gt; ZFS Native Encryption (Work in Progress) &lt;/summary&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;zpool create -f \
  -o ashift=12 \
  -O encryption=aes-256-gcm \
  -O keyformat=passphrase \
  -O keylocation=prompt \
  -O mountpoint=none \
  -O acltype=posixacl \
  -O compression=lz4 \
  -O xattr=sa \
  rpool /dev/vda2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I just got impermanence working without encryption, I haven’t been able to test
and iron out any quirks of this encryption method..&lt;/p&gt;
&lt;/details&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;Create all datasets with parents (&lt;code&gt;-p&lt;/code&gt;):&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# root (ephemeral – will be rolled back)
zfs create -p -o canmount=noauto -o mountpoint=legacy rpool/local/root

# blank snapshot (the “erase” target)
zfs snapshot rpool/local/root@blank

zfs create -p -o mountpoint=legacy rpool/local/boot
# /nix – read-only store, must survive rollbacks
zfs create -p -o mountpoint=legacy rpool/local/nix

# persisted areas
zfs create -p -o mountpoint=legacy rpool/safe/home
zfs create -p -o mountpoint=legacy rpool/safe/persist
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;Mount everything under &lt;code&gt;/mnt&lt;/code&gt;:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mount -t zfs rpool/local/root /mnt

mkdir -p /mnt/{boot,boot/efi,nix,home,persist}
mount -t vfat -o umask=0077 /dev/vda1 /mnt/boot/efi
mount -t zfs rpool/local/nix   /mnt/nix
mount -t zfs rpool/safe/home  /mnt/home
mount -t zfs rpool/safe/persist /mnt/persist
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;Note: By placing your Nix flake in &lt;code&gt;/home/user/nixos-config&lt;/code&gt; (which lives on
&lt;code&gt;rpool/safe/home&lt;/code&gt;), it persists naturally. You don’t need to add your
configuration files to the &lt;code&gt;environment.persistence&lt;/code&gt; module lists because the
underlying storage isn’t being wiped.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;Continue with the rest of the install&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nixos-generate-config --root /mnt
# edit /mnt/etc/nixos/configuration.nix  (add ZFS + rollback + impermanence)
&lt;/code&gt;&lt;/pre&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Quick checklist: &lt;/summary&gt;
&lt;p&gt;Quick checklist to confirm that you’ve taken all of the necessary steps.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 1. pool
zpool create -o ashift=12 -o autotrim=on -O acltype=posixacl -O canmount=off \
  -O dnodesize=auto -O normalization=formD -O relatime=on -O xattr=sa \
  -O mountpoint=none rpool /dev/vda2

# 2. datasets + snapshot
zfs create -p -o canmount=noauto -o mountpoint=legacy rpool/local/root
zfs snapshot rpool/local/root@blank
zfs create -p -o mountpoint=legacy rpool/local/nix
zfs create -p -o mountpoint=legacy rpool/safe/home
zfs create -p -o mountpoint=legacy rpool/safe/persist
# add a /boot dataset
zfs create -p -o mountpoint=legacy rpool/local/boot

# 3. mounts
mount -t zfs rpool/local/root /mnt
mkdir -p /mnt/{boot,boot/efi,nix,home,persist}

# /boot on ZFS
mount -t zfs rpool/local/boot /mnt/boot

# ESP on /boot/efi
mount -t vfat -o umask=0077 /dev/vda1 /mnt/boot/efi

mount -t zfs rpool/local/nix /mnt/nix
mount -t zfs rpool/safe/home /mnt/home
mount -t zfs rpool/safe/persist /mnt/persist
&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;
&lt;h2&gt;Prep &lt;code&gt;configuration.nix&lt;/code&gt;&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;head -c4 /dev/urandom | xxd -p &amp;gt; /tmp/rand.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Create password file in a persistent location&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo mkdir -p /mnt/persist/etc/nixos-secrets/passwords

# 2) Create the password hash and write it to the persistent file
# Replace &quot;your-password&quot; and &quot;your-user&quot;
sudo sh -c &apos;mkpasswd -m yescrypt &quot;your-password&quot; &amp;gt; /mnt/persist/etc/nixos-secrets/passwords/your-user&apos;

# 3) Lock down permissions
sudo chown root:root /mnt/persist/etc/nixos-secrets/passwords/your-user
sudo chmod 600 /mnt/persist/etc/nixos-secrets/passwords/your-user
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;After first reboot, the above files will be placed directly under &lt;code&gt;/persist/&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You will read &lt;code&gt;rand.txt&lt;/code&gt; into the &lt;code&gt;configuration.nix&lt;/code&gt; with &lt;code&gt;:r /tmp/rand.txt&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Edit the &lt;code&gt;/mnt/etc/nixos/configuration.nix&lt;/code&gt; (Edited 2026-01-18 use
&lt;code&gt;postMountCommands&lt;/code&gt; instead of &lt;code&gt;postResumeCommands&lt;/code&gt;) :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{ config, lib, pkgs, ... }:

{
  # ------------------------------------------------------------------
  # 1. Boot loader – systemd-boot (UEFI only)
  # ------------------------------------------------------------------
  boot.loader = {
    systemd-boot = {
      enable = true;
      consoleMode = &quot;max&quot;;           # Full 80×25 console in VM
      editor = false;                # Security – no edit at boot
    };
    efi = {
      canTouchEfiVariables = true;   # libvirt provides /sys/firmware/efi
      efiSysMountPoint = &quot;/boot/efi&quot;;    # Our 1 GiB FAT32 partition
    };
  };

  # ------------------------------------------------------------------
  # 2. ZFS support
  # ------------------------------------------------------------------
  boot.supportedFilesystems = [ &quot;zfs&quot; ];
  boot.zfs.devNodes = &quot;/dev/&quot;;       # Critical for VMs

  # Unique 8-hex hostId (run once in live ISO: head -c4 /dev/urandom | xxd -p)
  networking.hostId = &quot;a1b2c3d4&quot;;    # &amp;lt;&amp;lt;&amp;lt;--- replace with your own value

  # ------------------------------------------------------------------
  # 3. Roll-back root to blank snapshot on **every** boot
  # ------------------------------------------------------------------
# Uncomment after first reboot
#  boot.initrd.postMountCommands = lib.mkAfter &apos;&apos;
#    zfs rollback -r rpool/local/root@blank
#  &apos;&apos;;

  # ------------------------------------------------------------------
  # 4. Basic system (root password, serial console for VM)
  # ------------------------------------------------------------------
  users.users.root.initialPassword = &quot;changeme&quot;;   # change after first login
  boot.kernelParams = [ &quot;console=ttyS0,115200n8&quot; ];

  users.mutableUsers = false;

  users.users.your-user = {
    isNormalUser = true;
    extraGroups = [ &quot;wheel&quot; ];
    group = &quot;your-user&quot;;
    # The location of `hashedPasswordFile` after first reboot
    hashedPasswordFile = &quot;/persist/etc/nixos-secrets/passwords/your-user&quot;;
  };

  # This enables `chown -R your-user:your-user`
  users.groups.your-user = { };

  # ------------------------------------------------------------------
  # 5. (Optional) Enable SSH for post-install configuration
  # ------------------------------------------------------------------
  # services.openssh = {
  #  enable = true;
  #  settings.PermitRootLogin = &quot;yes&quot;;
  #};

  # ------------------------------------------------------------------
  # 6. Mark /persist as needed for boot
  # ------------------------------------------------------------------
  fileSystems.&quot;/persist&quot;.neededForBoot = true;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo nixos-install --root /mnt
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;reboot
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Copy your system files to a persistent location before uncommenting the
impermanence script.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo mkdir -p /persist/etc
sudo cp /etc/nixos/configuration.nix /etc/nixos/hardware-configuration.nix /persist/etc/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, you can uncomment this block:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;  boot.initrd.postMountCommands = lib.mkAfter &apos;&apos;
    zfs rollback -r rpool/local/root@blank
  &apos;&apos;;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo touch /etc/rollback-canary
sudo reboot
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the rollback is working, &lt;code&gt;/etc/rollback-canary&lt;/code&gt; should be gone after reboot
(while things in &lt;code&gt;/persist&lt;/code&gt; remain).&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;What gets Wiped vs. What Stays&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;What gets wiped?&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;Since we roll back &lt;code&gt;/&lt;/code&gt;(&lt;code&gt;rpool/local/root&lt;/code&gt;):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;/etc&lt;/code&gt; (including system configs) -&amp;gt; WIPED&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;/var&lt;/code&gt; (logs, databases, containers) -&amp;gt; WIPED&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;/root&lt;/code&gt; (the root users home directory) -&amp;gt; WIPED&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;/usr&lt;/code&gt; (though in NixOS this is mostly empty) -&amp;gt; WIPED&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;What survives?&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;/nix&lt;/code&gt; (mounted from &lt;code&gt;rpool/local/nix&lt;/code&gt;) -&amp;gt; PERSISTS&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;/boot&lt;/code&gt; (mounted from &lt;code&gt;rpool/local/boot&lt;/code&gt;) -&amp;gt; PERSISTS&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;/home&lt;/code&gt; (mounted from &lt;code&gt;rpool/safe/home&lt;/code&gt;) -&amp;gt; PERSISTS&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;/persists&lt;/code&gt; (mounted from &lt;code&gt;rpool/safe/persist&lt;/code&gt;) -&amp;gt; PERSISTS&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Why this matters for secrets?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;SSH Host keys&lt;/strong&gt; typically live in &lt;code&gt;/etc/ssh&lt;/code&gt;. Since &lt;code&gt;/etc&lt;/code&gt; is wiped, they
disappear. Store them in &lt;code&gt;/persist/etc/ssh&lt;/code&gt; and tell NixOS to look there. (or
symlink them)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;User Secrets&lt;/strong&gt; (&lt;code&gt;~/.config/sops&lt;/code&gt;): They live in &lt;code&gt;/home&lt;/code&gt; so they’re safe.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Integrating into a Flake&lt;/h2&gt;
&lt;p&gt;After first reboot, I recommend setting up a flake in a persistent location such
as &lt;code&gt;/home/your-user/flake&lt;/code&gt;. Because subsequent reboots will wipe the &lt;code&gt;/etc&lt;/code&gt;
directory.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/saylesss88/flakey&quot;&gt;Example Flake&lt;/a&gt;, this is a WIP
adaptation from another flake I had.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo mkdir /imperm_test
echo &quot;This should be Gone after Reboot&quot; | sudo tee /imperm_test/testfile
sudo ls -l /imperm_test/testfile # Verify the file exists
sudo cat /imperm_test/testfile # Verify content
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Reboot and check again:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo ls -l /imperm_test/testfile # Verify the file no longer exists
sudo cat /imperm_test/testfile # Verify content is missing
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;Persisting SSH Keys&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo mkdir -p /persist/etc/ssh
sudo ssh-keygen -t ed25519 -f /persist/etc/ssh/ssh_host_ed25519_key -N &quot;&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;OR if you still have keys in &lt;code&gt;/etc/ssh&lt;/code&gt; you want to keep just copy them to the
persistent location:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo cp /etc/ssh/ssh_host_ed25519_key* /persist/etc/ssh/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Tell NixOS where to find them&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;services.openssh = {
  hostKeys = [
    {
      path = &quot;/persist/etc/ssh/ssh_host_ed25519_key&quot;;
      type = ed25519;
    }
  ];
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After I initially get things working, I switch to &lt;code&gt;sops-nix&lt;/code&gt;, the following
guide works for this setup:
&lt;a href=&quot;https://saylesss88.github.io/installation/enc/sops-nix.html&quot;&gt;sops-nix Guide&lt;/a&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Resources&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://grahamc.com/blog/erase-your-darlings/&quot;&gt;erase-your-darlings&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://wiki.nixos.org/wiki/ZFS&quot;&gt;NixOS Wiki ZFS&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content></entry><entry><title>GnuPG gpg-agent</title><id>https://saylesss88.github.io/nix/gpg-agent.html</id><updated>2026-01-15T00:00:00+00:00</updated><author><name>saylesss87@proton.me (saylesss88)</name></author><link href="https://saylesss88.github.io/nix/gpg-agent.html" rel="alternate"/><content type="html">&lt;h1&gt;GnuPG &amp;amp; &lt;code&gt;gpg-agent&lt;/code&gt; on NixOS&lt;/h1&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Table of Contents&lt;/summary&gt;
&lt;!-- toc --&gt;
&lt;/details&gt;
&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;SECURITY WARNING&lt;/strong&gt;: This guide involves sensitive cryptographic material.
&lt;strong&gt;Never share your private key or passphrase&lt;/strong&gt;. Backup your keys and handle
them with extreme care.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;https://saylesss88.github.io/../images/gnupg.png&quot; alt=&quot;GnuPG&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;⚠️ gpg.fail (practical OpenPGP vulnerabilities) (Added on 2026-01-15)&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://gpg.fail&quot;&gt;gpg.fail&lt;/a&gt; is a write-up of real-world weaknesses in
GPG/OpenPGP implementations and edge cases in the OpenPGP ecosystem—not the
underlying math of modern cryptography. ​ The core idea is that signature
verification needs two things: correct cryptography and confidence that the data
you think was verified is actually the same data the verifier processed, which
can break down when formats are complex and tooling is permissive or ambiguous.&lt;/p&gt;
&lt;h3&gt;Why this matters even with good key hygiene&lt;/h3&gt;
&lt;p&gt;Most of the hardening in this guide (offline primary key, subkeys, strict
permissions, agent separation) is still worth doing because it protects your
private key material and reduces the blast radius if a workstation is
compromised.&lt;/p&gt;
&lt;p&gt;But that kind of key hygiene doesn’t automatically protect you from OpenPGP
“sharp edges” like ambiguous parsing rules, weird message constructions, or
implementation bugs because those problems happen at the message/format/tooling
layer, not the key-storage layer.&lt;/p&gt;
&lt;h3&gt;How to avoid the sharp edges (actionable)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Don’t treat “Good signature” as the end of the story. Treat verification as
“verify + inspect what was verified,” especially if the output will be
consumed by other tools or humans who may misinterpret it. ​&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Prefer OpenPGP for narrow, well-understood tasks (verifying release artifacts,
encrypting files) and be extra cautious when dealing with untrusted,
attacker-controlled OpenPGP inputs that flow through multiple tools (mail
clients, import pipelines, automation). ​&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Keep GnuPG and related tooling updated; gpg.fail covers vulnerabilities that
include classic implementation issues and not just “protocol design” pitfalls.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;If you automate verification (important)&lt;/h3&gt;
&lt;p&gt;If you’re writing automation around signature verification, ensure your pipeline
clearly distinguishes:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;“signature cryptographically valid” from&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;“the extracted/displayed message is exactly what was verified”. Some gpg.fail
items specifically call out cases where output does not make this distinction
obvious enough to prevent misuse.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;NixOS/Home-Manager hardening can break GPG (common failure mode)&lt;/h3&gt;
&lt;p&gt;Some “hardening” choices can cause GPG to fail in ways that look mysterious but
are just UI/agent integration problems (for example, &lt;strong&gt;No pinentry&lt;/strong&gt; / “gpg
failed to sign the data” when the agent can’t prompt). If GPG signing/encryption
suddenly stops working, first confirm pinentry is configured correctly (via your
&lt;code&gt;services.gpg-agent.pinentryPackage&lt;/code&gt; or &lt;code&gt;gpg-agent.conf&lt;/code&gt;) and restart the agent
with &lt;code&gt;gpgconf --kill gpg-agent&lt;/code&gt; then &lt;code&gt;gpgconf --launch gpg-agent&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;🔑 Key Concepts&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;GnuPG&lt;/strong&gt; is a complete and free implementation of the OpenPGP standard. It
allows you to encrypt and sign your data and communications, has a versatile key
management system, and access modules for many kinds of public key directories.
GnuPG (GPG), is a command line tool for secure communication.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;PGP (Pretty Good Privacy)&lt;/strong&gt; and &lt;strong&gt;GPG (GNU Privacy Guard)&lt;/strong&gt;. While distinct,
they are deeply interconnected and, for the rest of this section, I’ll use the
terms interchangeably.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;PGP&lt;/strong&gt; was the original, groundbreaking software that brought robust public-key
cryptography to the masses. It set the standard for secure email communication.
However, PGP later became a commercial product.&lt;/p&gt;
&lt;p&gt;To provide a free and open-source alternative that anyone could use and inspect,
&lt;strong&gt;GPG&lt;/strong&gt; was created. Crucially, &lt;strong&gt;GPG&lt;/strong&gt; is a complete implementation of the
OpenPGP standard. This open standard acts as a universal language for encryption
and digital signatures.&lt;/p&gt;
&lt;p&gt;GnuPG uses a more complex scheme in which a user has a primary keypair and then
zero or more additional subordinate keypairs.&lt;/p&gt;
&lt;p&gt;Signing public keys with the corresponding private key is called &lt;em&gt;self-signing&lt;/em&gt;,
and a public key that has self-signed user IDs bound to it is called a
&lt;em&gt;certificate&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Web of Trust&lt;/strong&gt;: Rather than validate every single key individually, you can
rely on other factors such as if it has been signed by a key that you fully
trust or if it has been signed by three marginally trusted keys to validate
keys.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;gpg-agent&lt;/code&gt; is a daemon to manage secret (private) keys independently from any
protocol. It is used as a backed for &lt;code&gt;gpg&lt;/code&gt; and &lt;code&gt;gpgsm&lt;/code&gt; as well as for a couple
of other utilities. –&lt;a href=&quot;https://man.cx/gpg-agent&quot;&gt;man gpg-agent&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;There are numerous front-ends for gpg as well, i.e., GUI apps that simplify many
of the commands and processes. Two that I touch on in this overview are
&lt;code&gt;seahorse&lt;/code&gt; and &lt;code&gt;kleopatra&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Asymmetric Encryption (Public-Key cryptography)&lt;/h3&gt;
&lt;p&gt;E2ee requires that every sender and recipient does a one time preparation, which
involves the generation of personal random numbers. Two such random numbers are
necessary, one will be called your secret key and another one will be called
your public key (together your &lt;em&gt;personal key&lt;/em&gt;). These numbers are very big, they
consist of hundreds or thousands of digits.&lt;/p&gt;
&lt;p&gt;A message can be encrypted using the recipients public key and can only be
decrypted with the matching private key. In other words, if you exchange
&lt;strong&gt;public keys&lt;/strong&gt; with someone you both can encrypt messages that only the other
can decrypt with their own &lt;strong&gt;private key&lt;/strong&gt;. &lt;strong&gt;You must never share the private
key or the private keys passphrase with anyone else&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What’s safe to share?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Your public key (used to encrypt files and verify signatures)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Your key ID (identifies your key, useful for sharing public keys or configs)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Your keys fingerprint &lt;code&gt;gpg --fingerprint&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;What must never be shared?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Your private (secret) key, usually in your &lt;code&gt;~/.gnupg/private-keys-v1.d/&lt;/code&gt;
directory. Usually called your &lt;em&gt;private keyring&lt;/em&gt;. &lt;strong&gt;Your main goal should be
the protection of your private key&lt;/strong&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Your passphrase for your private key&lt;/strong&gt;. Even if someone is able to somehow
get your private key, they need to break the passphrase to access it
unencrypted. &lt;strong&gt;Protect this passphrase&lt;/strong&gt;!&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Best Practices&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Don’t rely on the short KeyID, at least use long OpenPGP Key IDs (for example
0xA1E6148633874A3D), they are 64 bits long and harder to spoof. Even better, use
the fingerprint.This is accomplished in the configuration with
&lt;code&gt;keyid-format = &quot;0xlong&quot;;&lt;/code&gt;, and &lt;code&gt;with-fingerprint&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Always sign your public keys before you publish them to prevent man in the
middle attacks and other modifications. When a subkey or userID is generated it
is self-signed automatically, which is why you need to enter your password.&lt;/p&gt;
&lt;p&gt;Don’t blindly trust keys from keyservers. You should verify the full key
fingerprint with the owner over the phone if possible.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.kicksecure.com/wiki/Verifying_Software_Signatures&quot;&gt;Verifying software signatures&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Use a strong primary key, don’t use 1024-bit DSA, 1024-bit RSA, or SHA-1 for
signing they are no longer recommended.&lt;/p&gt;
&lt;p&gt;Choose an expiration date less than 2 years, you can add time if needed.
Remember this date.&lt;/p&gt;
&lt;p&gt;Rotate your subkeys.&lt;/p&gt;
&lt;p&gt;Keep your primary key offline, this ensures that it can’t be stolen by an
attacker allowing him to create new identities. We accomplish this by creating
subkeys and only adding the subkeys keygrip and the subkeys &lt;code&gt;default-key&lt;/code&gt; to our
configuration keeping the primary key out of it.&lt;/p&gt;
&lt;p&gt;Since we will be removing our primary key, even we won’t be able to create
additional keys so it’s important to think ahead and make all the keys you’ll
need. However, it is as easy as reimporting it to give yourself access again.&lt;/p&gt;
&lt;p&gt;Many of these best practices come from the following guide:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://riseup.net/ru/security/message-security/openpgp/gpg-best-practices&quot;&gt;RiseUp gpg-best-practices&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;Home Manager module with &lt;code&gt;gpg-agent&lt;/code&gt;, &lt;code&gt;gnupg&lt;/code&gt;, and &lt;code&gt;pinentry-gnome3&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# gpg-agent.nix
{
  config,
  lib,
  pkgs,
  ...
}: {
  options = {
    custom.pgp = {
      enable = lib.mkEnableOption {
        description = &quot;Enable PGP Gnupgp&quot;;
        default = false;
      };
    };
  };

  config = lib.mkIf config.custom.pgp.enable {
    services = {
      ## Enable gpg-agent with ssh support
      gpg-agent = {
        enable = true;
        enableSshSupport = true;
        enableZshIntegration = true;
        # pinentry is a collection of simple PIN or passphrase dialogs used for
        # password entry
        pinentryPackage = pkgs.pinentry-qt;
      };

      ## We will put our keygrip here
      gpg-agent.sshKeys = [];
    };
    home.packages = [pkgs.gnupg];
    programs = {
      gpg = {
        ## Enable GnuPG
        enable = true;

        # homedir = &quot;/home/userName/.config/gnupg&quot;;
        settings = {
          # Default/trusted key ID (helpful with throw-keyids)
          # Example, you will put your own keyid here
          # Use `gpg --list-keys`
          # default-key = &quot;0x37ACBCDA569C5C44788&quot;;
          # trusted-key = &quot;0x37ACBCDA569C5C44788&quot;;
          # https://github.com/drduh/config/blob/master/gpg.conf
          # https://www.gnupg.org/documentation/manuals/gnupg/GPG-Configuration-Options.html
          # https://www.gnupg.org/documentation/manuals/gnupg/GPG-Esoteric-Options.html
          # Some Best Practices, stronger algos etc
          # Use AES256, 192, or 128 as cipher
          personal-cipher-preferences = &quot;AES256 AES192 AES&quot;;
          # Use SHA512, 384, or 256 as digest
          personal-digest-preferences = &quot;SHA512 SHA384 SHA256&quot;;
          # Use ZLIB, BZIP2, ZIP, or no compression
          personal-compress-preferences = &quot;ZLIB BZIP2 ZIP Uncompressed&quot;;
          # Default preferences for new keys
          default-preference-list = &quot;SHA512 SHA384 SHA256 AES256 AES192 AES ZLIB BZIP2 ZIP Uncompressed&quot;;
          # SHA512 as digest to sign keys
          cert-digest-algo = &quot;SHA512&quot;;
          # SHA512 as digest for symmetric ops
          s2k-digest-algo = &quot;SHA512&quot;;
          # AES256 as cipher for symmetric ops
          s2k-cipher-algo = &quot;AES256&quot;;
          # UTF-8 support for compatibility
          charset = &quot;utf-8&quot;;
          # Show Unix timestamps
          fixed-list-mode = &quot;&quot;;
          # No comments in signature
          no-comments = &quot;&quot;;
          # No version in signature
          no-emit-version = &quot;&quot;;
          # Disable banner
          no-greeting = &quot;&quot;;
          # Long hexidecimal key format
          keyid-format = &quot;0xlong&quot;;
          # Display UID validity
          list-options = &quot;show-uid-validity&quot;;
          verify-options = &quot;show-uid-validity&quot;;
          # Display all keys and their fingerprints
          with-fingerprint = &quot;&quot;;
          # Cross-certify subkeys are present and valid
          require-cross-certification = &quot;&quot;;
          # Disable caching of passphrase for symmetrical ops
          no-symkey-cache = &quot;&quot;;
          # Enable smartcard
          # use-agent = &quot;&quot;;
        };
      };
    };
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;🤔 Fun Fact: Elliot Alderson mentions encrypting Evil Corps files with 256 bit
AES encryption ensuring that it’s impossible to break in &lt;code&gt;eps1.9_zer0.daY.avi&lt;/code&gt;
of Mr. Robot.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The default path is &lt;code&gt;~/.gnupg&lt;/code&gt;, if you prefer placing it in the &lt;code&gt;~/.config&lt;/code&gt;
directory or elsewhere, uncomment the &lt;code&gt;homedir&lt;/code&gt; line and change &lt;code&gt;userName&lt;/code&gt; to
your username.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I use sway so &lt;code&gt;pinentry-qt&lt;/code&gt; works for me, there is also the following options
for this attribute:&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;pinentry-tty&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;pinentry-gnome3&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;pinentry-gtk2&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And more, research what you need and use the correct one.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://search.nixos.org/packages?channel=unstable&amp;amp;query=pinentry&quot;&gt;search.nixos.org pinentry&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Enable in your &lt;code&gt;home.nix&lt;/code&gt; or equivalent:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# home.nix
# ... snip ...
imports = [
    ./gpg-agent.nix
];
custom.pgp.enable = true;
# ... snip ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;gpg --full-generate-key&lt;/code&gt; can be used to generate a basic keypair.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;gpg --expert --full-generate-key&lt;/code&gt; can be used for keys that require more
capabilities.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;❗ NOTE: We will first generate our GPG primary key that is required to
atleast have sign capabilities, we will then derive subkeys from said primary
key and use them for signing and encrypting. It is recommended to generate a
revoke certificate right after creating your primary key.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;To generate your gpg primary key you can do the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;gpg --full-generate-key
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Choose &lt;code&gt;(10) (sign only)&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Give it a name and description&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Give it an expiration date, 1y is common&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Use a strong passphrase or password&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Give it a comment, I typically add the date&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you see a warning about incorrect permissions, you can run the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;chmod 700 ~/.gnupg
chmod 600 ~/.gnupg/*
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Verify:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ls -ld ~/.gnupg
# Should show: drwx------

ls -l ~/.gnupg
# Files should show: -rw-------
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Generate a Revocation Certificate&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;mykey&lt;/code&gt; must be a key specifier, either the keyID of the primary keypair or any
part of the user ID that identifies the keypair:&lt;/p&gt;
&lt;p&gt;Replace &lt;code&gt;mykeyID&lt;/code&gt; with the keyID of your primary key and store the cert in a
safe place:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;gpg --output revoke.asc --gen-revoke mykeyID
Create a revocation certificate for this key? (y/N)
Please select the reason for the revocation:
  0 = No reason specified
  1 = Key has been compromised
  2 = Key is superseded
  3 = Key is no longer used
  Q = Cancel
(Probably you want to select 1 here)
Your decision?
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The certificate will be output to a file &lt;code&gt;revoke.asc&lt;/code&gt;. If the &lt;code&gt;--output&lt;/code&gt; is
ommitted, the result will be placed on stdout.&lt;/p&gt;
&lt;p&gt;Since it’s a short certificate, you can print a hardcopy and store it somewhere
safe. The cert shouldn’t be somewhere that others can access it since anyone
could publish the revoke cert and render the corresponding public key useless.&lt;/p&gt;
&lt;p&gt;To apply the revoke cert, import it:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;NOTE: Only import the &lt;code&gt;revoce.asc&lt;/code&gt; if you want to revoke (i.e., make it not
work anymore)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;gpg --import revoke.asc
# And optionally push the revoked key to public keyservers to notify others:
gpg --keyserver keyserver.ubuntu.com --send-keys YOUR_KEYID
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;p&gt;After fixing, run &lt;code&gt;gpg --list-keys --with-fingerprint&lt;/code&gt;, which lists your public
keys:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Take note of your public key
gpg --list-keys --with-fingerprint
/home/jr/.gnupg/pubring.kbx
---------------------------
pub   ed25519/0x095782A1B124AF15 2025-08-23 [SCA] [expires: 2026-08-23]
Key fingerprint = 5908 9C5B FEC8 0D75 FCB0  E206 0958 82C1 A124 CF15
uid                   [ultimate] Jr (08-23-25) &amp;lt;sayls8@proton.me&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Copy the KeyID, in this example it would be &lt;code&gt;0x095722B2A123CF15&lt;/code&gt;. We will use
it for the command below.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The warning should be gone.&lt;/p&gt;
&lt;p&gt;Now we will generate 2 subkeys, 1 for encryption and 1 for authentication.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;gpg --expert --edit-key 0x095722B2A123CF15
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When the screen opens, type &lt;code&gt;addkey&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Choose 11 (set your own capabilities) and add A (Authenticate) and type &lt;code&gt;save&lt;/code&gt;
to save and exit. Repeat this again and choose 12 ECC (encrypt only).&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;❗ &lt;code&gt;gpg --edit-key&lt;/code&gt; has many more capabilities, after launching type &lt;code&gt;help&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Add Keygrip of Authenticate Subkey to &lt;code&gt;sshcontrol&lt;/code&gt; for gpg-agent&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;gpg --list-secret-keys --with-keygrip --keyid-format LONG
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Copy the keygrip of the subkey with Authenticate capabilities&lt;/p&gt;
&lt;p&gt;Add the keygrip number to your &lt;code&gt;gpg-agent.sshKeys&lt;/code&gt; and rebuild, this adds an SSH
key to &lt;code&gt;gpg-agent&lt;/code&gt;. This is for the SSH key functionality of &lt;code&gt;gpg-agent&lt;/code&gt;, while
the key ID (&lt;code&gt;default-key&lt;/code&gt;) is for GPG-specific operations like signing commits:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# gpg-agent.nix
gpg-agent.sshKeys = [&quot;6BD11826F3845BC222127FE3D22C92C91BB3FB32&quot;];
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;By itself, a keygrip cannot be used to reconstruct your private key. It’s
derived from the public key material, not from the secret key itself so it’s
safe to version control. Don’t put your keygrip in a public repo if you don’t
want people to know you use that key for signing/authentication. It’s not a
security risk, but it leaks a tiny bit of metadata.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The following article mentions the keygrip being computed from public elements
of the key:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://gnupg-users.gnupg.narkive.com/q5JtahdV/gpg-agent-what-is-a-keygrip&quot;&gt;gnupg-users what-is-a-keygrip&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Add the KeyId to your &lt;code&gt;gpg-agent.nix&lt;/code&gt;, this declares your default-key to persist
through rebuilds:&lt;/p&gt;
&lt;p&gt;Copy the public key of the same subkey with Authenticate capabilities you will
see something like &lt;code&gt;[SA]&lt;/code&gt; next to it for Sign and Authenticate:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# gpg-agent.nix
gpg.settings = {
    # Replace with your own Subkeys KeyID `gpg --list-keys --keyid-format LONG`
    default-key = &quot;Ox37ACA569C5C44787&quot;;
    trusted-key = &quot;Ox37ACA569C5C44787&quot;;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This key should be signed automatically, ensure that it is:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;gpg --sign-key Ox37ACA569C5C44787
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Rebuild, and check that everything is correct with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ssh-add -L
# you should see something like:
ssh-ed25519 AABCC3NzaC1lZDI1NTE5ABBAIHyujgyCjjBTqIuFM3EMUSo6RGklmOXQW3uWRhWdJ1Mm (none)
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Never version-control your private key files or &lt;code&gt;.gnupg&lt;/code&gt; contents.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Add the following to your shell config:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# zsh.nix
# ... snip ...
initContent = &apos;&apos;
    export GPG_TTY=$(tty)
    export SSH_AUTH_SOCK=$(gpgconf --list-dirs agent-ssh-socket)
    gpgconf --launch gpg-agent
&apos;&apos;;
# ... snip ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Rebuild and then restart &lt;code&gt;gpg-agent&lt;/code&gt; if necessary:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;gpgconf --kill gpg-agent
gpgconf --launch gpg-agent
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Test, these should match:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;echo &quot;$SSH_AUTH_SOCK&quot;
# output
/run/user/1000/gnupg/d.wft5hcsny4qqq3g31c76534j/S.gpg-agent.ssh

gpgconf --list-dirs agent-ssh-socket
# output
/run/user/1000/gnupg/d.wft5hcsny4qqq3g31c76834j/S.gpg-agent.ssh
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ssh-add -L
# Copy the entire following line:
ssh-ed25519 AABBC3NzaC1lZDI1NTE5AAAAIGXwhVokJ6cKgodYT+0+0ZrU0sBqMPPRDPJqFxqRtM+I (none)
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Mine shows &lt;code&gt;(none)&lt;/code&gt; because I left the comment field blank when creating the
key and doesn’t affect functionality.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Then, in your server’s NixOS configuration (e.g., &lt;code&gt;configuration.nix&lt;/code&gt;): Change
&lt;code&gt;yourUser&lt;/code&gt; to your username. This is how you grant access to a remote machine,
and the public key from the GPG subkey is what’s added here, the output of
&lt;code&gt;ssh-add -L&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;users.users.yourUser = {
openssh = {
  authorizedKeys.keys = [
    # Replace with the output of `ssh-add -L`
    &quot;ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGXwhVokJ6cKgodYT+0+0ZrU0sBqMPPRDPJqFxqRtM+I (none)&quot;
  ];
};
};
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;❗ NOTE: Only the &lt;strong&gt;public&lt;/strong&gt; key goes here, it’s safe to commit to version
control. If you prefer not to hardcode it in the config, you can reference it
from a &lt;code&gt;.pub&lt;/code&gt; file in your repo and read it with
&lt;code&gt;builtins.readFile ./mykey.pub&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Rebuild your system and test an SSH connection into the server:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ssh -p &amp;lt;your-port&amp;gt; user@hostname
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;your-port&amp;gt;&lt;/code&gt; is often &lt;code&gt;22&lt;/code&gt; so it would be something like:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ssh -p 22 bill@xps
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once you successfully sign in to SSH, it will ask you if you’re sure you trust
the remote server’s SSH host key. Once you type &lt;code&gt;yes&lt;/code&gt;, it will automatically be
used for tasks such as file decryption.&lt;/p&gt;
&lt;h3&gt;Remove and Store your Primary Key offline&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;❗ NOTE: After you remove your primary key, you will no longer be able to
derive subkeys from it or sign keys unless you re-import it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# extract the primary key
gpg -a --export-secret-key sayls8@proton.me &amp;gt; secret_key
# extract the subkeys, which we will reimport later
gpg -a --export-secret-subkeys sayls8@proton.me &amp;gt; secret_subkeys.gpg
# delete the secret keys from the keyring, so only subkeys are left
gpg --delete-secret-keys sayls8@proton.me
Delete this key from the keyring? (y/N) y
This is a secret key! - really delete? (y/N) y
# reimport the subkeys
gpg --import secret_subkeys.gpg
# verify everything is in order
gpg --list-secret-keys
# remove the subkeys from disk
rm secret_subkeys.gpg
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I recommend also keeping a &lt;code&gt;.gpg&lt;/code&gt; version to make it easy to re-import your
primary key: &lt;code&gt;gpg --export-secret-keys --armor --output private-key-bak.gpg&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Then store &lt;code&gt;secret_key&lt;/code&gt; on an encrypted USB drive or somewhere offline. If you
want to protect it for now, you can just use the encryption subkey that we
created to encrypt &lt;code&gt;secret_key&lt;/code&gt; with a passphrase:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;gpg --list-keys --keyid-format LONG
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Copy the KeyID of the subkey with encrypt capabilities for the following
command:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Encrypting your secret key for yourself
gpg --encrypt --recipient Ox37ACA569C5C44787 secret_key
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can check that the secret key material is missing with
&lt;code&gt;gpg --list-secret-keys&lt;/code&gt;, you should see &lt;code&gt;sec#&lt;/code&gt; instead of &lt;code&gt;sec&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;gpg --list-secret-keys
# Output:
sec#  ed25519/0x
# ...snip...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The above set of commands are from the
&lt;a href=&quot;https://riseup.net/ru/security/message-security/openpgp/gpg-best-practices#keep-your-primary-key-entirely-offline&quot;&gt;RiseUp Keep your primary key offline&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Add your GPG Key to GitHub&lt;/h2&gt;
&lt;p&gt;Plug your own public key from &lt;code&gt;gpg --list-keys&lt;/code&gt; in the following command:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;gpg --armor --export &amp;lt;Public-Key&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Copy the entire block from &lt;code&gt;-----BEGIN PGP PUBLIC KEY BLOCK-----&lt;/code&gt; to
&lt;code&gt;-----END PGP PUBLIC KEY BLOCK-----&lt;/code&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;❗ You can also paste the above block into a public keyserver such as
&lt;code&gt;keys.openpgp.org&lt;/code&gt;. This allows others to find and use your key to encrypt
messages or verify your signatures. Many tools and users rely on public key
servers to fetch keys automatically. You can also publish your revocation
certificates, which help others know if your key is compromised or revoked.
This can be a privacy concern as key servers publish (and keep) associated
user IDs and metadata linked to your key, such as your email.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It’s the same process as adding an SSH key, Go to Settings, SSH and GPG keys,
&lt;code&gt;New GPG key&lt;/code&gt; and your all set.&lt;/p&gt;
&lt;h3&gt;Sign your Commits for Git&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# git.nix
{...}: {
    programs.git = {
        enable = true;
      extraConfig = {
          commit.gpgsign = true;
          user.signingkey = &quot;0x0666C1A265F156&quot;
      };
    };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After this, you will be prompted for your Private Keys password on every commit.&lt;/p&gt;
&lt;p&gt;If you look at your commits on GitHub, after adding the GPG key and the above
settings to your git setup it will show your commits are &lt;code&gt;Verified&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Backing up Your Keys&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;gpg --export-secret-keys --armor --output my-private-key-backup.gpg
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Your private keys will be encrypted with a passphrase into a .gpg file. Store
this backup in a secure location line an encrypted USB drive. This can prevent
you from losing access to your keys in the case of disk failure or accidents.&lt;/p&gt;
&lt;p&gt;You can export your public keys and publish them publicly if you choose:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;gpg --export --armor --output my-public-keys.gpg
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now if your keys ever get lost or corrupted, you can import these backups.&lt;/p&gt;
&lt;h2&gt;Encrypt a File with PGP&lt;/h2&gt;
&lt;p&gt;The easy way to do this is with an app like Kleopatra, available as
&lt;code&gt;pkgs.kdePackages.kleopatra&lt;/code&gt;. Kleopatra will automatically recognize your gpg
keys and enable you to easily encrypt messages by clicking the Notepad, typing
your message and clicking &lt;code&gt;Sign/Encrypt Notepad&lt;/code&gt;. You can also choose to encrypt
the message with a password, where anyone that has the password can read the
message.&lt;/p&gt;
&lt;p&gt;Using the above and below methods enable you to encrypt any message for
basically any service and just copy past the encrypted text into the service for
added privacy.&lt;/p&gt;
&lt;p&gt;Encrypting a whole directory is a bit more involved and requires using
compression.&lt;/p&gt;
&lt;h3&gt;List your keys and get the key ID&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;gpg --list-keys --keyid-format LONG
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Example output, don’t use RSA keys:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;pub   rsa4096/ABCDEF1234567890 2024-01-01 [SC]
uid           [ultimate] Your Name &amp;lt;you@example.com&amp;gt;
sub   rsa4096/1234567890ABCDEF 2024-01-01 [E]
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Notice the &lt;code&gt;sub&lt;/code&gt; and the &lt;code&gt;[E]&lt;/code&gt; for the subkey with encrypt capabilities.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The part after the slash on the &lt;code&gt;pub&lt;/code&gt; line is your key ID (&lt;code&gt;ABCDEF1234567890&lt;/code&gt;
in the example)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You can also use your email or name to refer to the key in most commands.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Encrypt a file&lt;/h3&gt;
&lt;p&gt;In order to encrypt a document you must have the public keys of the intended
recipients.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;echo &quot;This file will be encrypted&quot; &amp;gt; file.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Encrypting for yourself (using a key ID as recipient):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;gpg --encrypt --recipient ABCDEF1234567890 file.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ls
│  7 │ file.txt            │ file │     28 B │ now           │
│  8 │ file.txt.gpg        │ file │    191 B │ now           │
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Encrypting for someone else with their email (public key identifier):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;gpg --output file.gpg --encrypt --recipient jake@proton.me file.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ls
file.txt
file.gpg
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;gpg --encrypt&lt;/code&gt; doesn’t modify the original file. It creates a new encrypted
file by default with &lt;code&gt;gpg&lt;/code&gt; amended to the filename.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;gpg --decrypt file.txt.gpg
gpg: encrypted with cv25519 key, ID 0x4AC131B80CEC833E, created 2025-07-31
      &quot;GPG Key &amp;lt;sayls8@proton.me&amp;gt;&quot;
This file will be encrypted
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or, to save the decrypted text to a file:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;gpg --output decrypted_file.txt --decrypt file.txt.gpg
cat decrypted_file.txt
# Output
File: decrypted.txt
This file will be encrypted
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;You will be asked for the passphrase you used when creating the key in order
to decrypt the file.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Sign and Verify Signatures&lt;/h3&gt;
&lt;p&gt;When you sign a document it is certified and timestamped. If after the doc is
signed, if the doc is further modified in any way the verification of the
signature will fail.&lt;/p&gt;
&lt;p&gt;A signature is created using the private key of the signer, and verified with
the corresponding public key. For example, to verify Jakes signature you would
use Jake’s public key to see that the work indeed came from him and hasn’t been
modified since.&lt;/p&gt;
&lt;p&gt;To sign the above &lt;code&gt;file.txt&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;gpg output doc.sig --sign file.txt
# To clearsign use:
gpg --clearsign file.txt
# For a detached signature use:
gpg --output doc.sig --detach-sig file.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You will be prompted for your passphrase.&lt;/p&gt;
&lt;p&gt;You can either check the signature or check the signature and recover the
original document.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# To only check:
gpg --output doc --verify doc.sig
# To verify and extract the document
gpg --output doc --decrypt doc.sig
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Email Encryption&lt;/h2&gt;
&lt;p&gt;Email is inherently insecure, and email-based attacks remain one of the top
vectors for data breaches. Encrypting your email protects your privacy by
ensuring that only the intended recipient can read it. Encrypting your emails
with PGP provides valuable security benefits but also has inherent limitations
that prevent it from being considered truly “secure communication” by modern
standards.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://emailselfdefense.fsf.org/en/&quot;&gt;Email Self-Defense&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What its Good for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Confidentiality, it prevents unauthorized third parties (like email providers
or network eavesdroppers) from reading your email content.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Integrity and authenticity: Digital signatures verify that the email genuinely
came from the claimed sender and hasn’t been altered in transit.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Long-term confidentiality: Encrypted emails stored on servers or devices
remain protected even if the storage is later compromised. With companies like
Gmail giving you a “free” account, that usually means that you are the product
and you should tread lightly.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;To securely communicate with someone never use email, use a dedicated service
such as Threema, Signal or Brair.&lt;/strong&gt; It’s hard to recommend any messaging service
at the moment, always do your own research and stay informed on the companies
policies. Signal has taken some heat for the way it has implemented it’s
MobileCoin. The biggest issue I’ve faced is getting other people to care or use
the same e2ee app.&lt;/p&gt;
&lt;p&gt;With Thunderbird you can go to settings, Privacy and Security, and scroll to the
bottom where it says “End to End Encryption”, Click the Settings tab there,
finally click End-To-End Encryption on the left.&lt;/p&gt;
&lt;p&gt;From there, you can click &lt;code&gt;+ Add Key&lt;/code&gt; next to your email address and either
generate a new key through Thunderbird. If you use this, choose the Curve
protocol or whatever isn’t RSA.&lt;/p&gt;
&lt;p&gt;Or import your own key which is definitely more secure since you’re not trusting
someone else with your private key:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;gpg --export --armor sayls8@gmail.com &amp;gt; publickey.asc
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then select &lt;code&gt;+ Add Key&lt;/code&gt; and choose import your own, this didn’t work for me.
What did work was to start composing an email and click on the &lt;code&gt;OpenPGP&lt;/code&gt; button,
Go to &lt;code&gt;Key Manager&lt;/code&gt;, &lt;code&gt;File&lt;/code&gt;, &lt;code&gt;Import Public Key from a File&lt;/code&gt; and choose your
&lt;code&gt;publickey.asc&lt;/code&gt;. This way, only you have access to your private key.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://support.mozilla.org/en-US/kb/introduction-to-e2e-encryption#w_how-e2ee-with-openpgp-works-in-general&quot;&gt;How e2ee with OpenPGP works in general&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Import your recipient’s public key&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;When you start composing an email, you’ll see that you need to resolve key
issues if you don’t already have the recipients public key. Click &lt;code&gt;Resolve&lt;/code&gt;, and
either Discover Public Keys Online… or Import Public Keys From File…&lt;/p&gt;
&lt;p&gt;Thunderbird has the option to use the OpenPGP Key Manager to view or manage
public keys of your correspondents.&lt;/p&gt;
&lt;p&gt;If you’re sending encrypted emails to someone you’ll need their public key,
there are a few methods of doing this just ensure you verify the Fingerprint
with the person your talking to.&lt;/p&gt;
&lt;p&gt;Exporting your public key means creating a copy of the public part of your
cryptographic key pair that you can share with others.&lt;/p&gt;
&lt;p&gt;For example, say that Jake wants to send you his public key. First he has to
export his key:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;gpg --output jake.gpg --export jake@proton.me
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The key is exported in binary format, to export in ASCII-armored format use:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;gpg --armor --export jake@proton.me
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, once he sends this to you, you’ll need to import and validate it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;gpg --import jake.gpg
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once the key is imported it should be validated. If you need to validate a key
manually, it is done by verifying the key’s fingerprint and then signing the key
to certify it as a valid key.&lt;/p&gt;
&lt;p&gt;Check that it exists in your keychain:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;gpg --list-keys
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You should see Jakes key in the above list.&lt;/p&gt;
&lt;p&gt;To certify the key you need to edit it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;gpg --edit-key jake@proton.me
# List the fingerprint
gpg&amp;gt; fpr
# once the fingerprint is verified with the owner, sign it
gpg&amp;gt; sign
# once signed, you can check the key to list signatures on it
gpg&amp;gt; check
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;❗ NOTE: You can use PGP to encrypt any message and paste it into &lt;strong&gt;any&lt;/strong&gt;
software and send it. As long as only you and your recipient are the only
people to have the private keys, you will be the only people able to decrypt
the messages. Implementing this correctly is a good way to stop government
mass surveillance.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Make your Public Key Highly Available&lt;/h2&gt;
&lt;p&gt;You should always make sure that you sign your public key before you publish it.
When you distribute your public key, you’re distributing the public components
of your master and subkeys as well as the user IDs. If unsigned, this is a
security risk because it’s possible for an attacker to tamper with it. The
public key can be modified by adding or substituting keys, or changing user IDs.&lt;/p&gt;
&lt;p&gt;Signing the keys provides a web of trust, only the corresponding public key can
be used to verify the signature and ensure it hasn’t been modified. Since we are
already only using subkeys for public keys, they are automatically self-signed.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;gpg --output ~/mygpg.key --armor --export your_email@address.com
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can then send this file to the other party.&lt;/p&gt;
&lt;p&gt;You can also use the GPG interface to upload your key to a key server:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;gpg --list-keys your_email@address.com
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Copy the key ID for the following command, remember its on the &lt;code&gt;pub&lt;/code&gt; line after
the &lt;code&gt;/&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;gpg --send-keys --keyserver pgp.mit.edu key_id
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The key will be uploaded to the server and likely be distributed to other key
servers around the world. This is why expiration dates are important, if your
key is lost or stolen, the damage window is limited to the expiration period.
Also remember, you can add more time even after the key has expired.&lt;/p&gt;
&lt;h3&gt;Example: Verifying Arch Linux Download&lt;/h3&gt;
&lt;details&gt;
&lt;summary&gt;
&lt;p&gt;✔️ Click to Expand Example of verifying and signing the archlinux public key&lt;/p&gt;
&lt;/summary&gt;
&lt;p&gt;First, download both the arch &lt;code&gt;.iso&lt;/code&gt; and &lt;code&gt;.sig&lt;/code&gt; files.&lt;/p&gt;
&lt;p&gt;I tried a few different methods from &lt;a href=&quot;https://archlinux.org/download/#checksums&quot;&gt;https://archlinux.org/download/#checksums&lt;/a&gt;
and the easiest by far was using Sequoia available in Nixpkgs as
&lt;code&gt;pkgs.sequoia-sq&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;Download the archlinux public key:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sq network wkd search pierre@archlinux.org --output release-key.pgp

Found 2 certificates related to the query:

 - 3E80CA1A8B89F69CBA57D98A76A5EF9054449A5C
   - Pierre Schmitz &amp;lt;pierre@archlinux.org&amp;gt; (UNAUTHENTICATED)
   - created 2022-10-31 09:11:51 UTC
   - found via: WKD

 - 4AA4767BBC9C4B1D18AE28B77F2D434B9741E8AC
   - Pierre Schmitz &amp;lt;pierre@archlinux.de&amp;gt; (UNAUTHENTICATED)
   - created 2011-04-10 09:35:33 UTC
   - found via: WKD

Hint: To extract a particular certificate from release-key.pgp, use any of:

  $ sq cert export --keyring=release-key.pgp --cert=3E80CA1A8B89F69CBA57D98A76A5EF9054449A5C

  $ sq cert export --keyring=release-key.pgp --cert=4AA4767BBC9C4B1D18AE28B77F2D434B9741E8AC
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Export the chosen key to a &lt;code&gt;.pgp&lt;/code&gt; file:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sq cert export --keyring=release-key.pgp --cert=3E80CA1A8B89F69CBA57D98A76A5EF9054449A5C &amp;gt; pierre-archlinux.pgp
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Import into your keychain:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt; gpg --import pierre-archlinux.pgp
gpg: key 0x76A5EF9054449A5C: 9 signatures not checked due to missing keys
gpg: key 0x76A5EF9054449A5C: public key &quot;Pierre Schmitz &amp;lt;pierre@archlinux.org&amp;gt;&quot; imported
gpg: Total number processed: 1
gpg:               imported: 1
gpg: marginals needed: 3  completes needed: 1  trust model: pgp
gpg: depth: 0  valid:   3  signed:   0  trust: 0-, 0q, 0n, 0m, 0f, 3u
gpg: next trustdb check due at 2026-08-23
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, you should see &lt;code&gt;&amp;lt;pierre@archlinux.org&amp;gt;&lt;/code&gt; and his keys when you run
&lt;code&gt;gpg --list-keys&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Finally, verify the signature:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sq verify --signer-file release-key.pgp --signature-file archlinux-2025.08.01-x86_64.iso.sig archlinux-2025.08.01-x86_64.iso
Authenticated signature made by 3E80CA1A8B89F69CBA57D98A76A5EF9054449A5C (Pierre Schmitz &amp;lt;pierre@archlinux.org&amp;gt;)

1 authenticated signature.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This shows that the signature was made by the key with the ID
&lt;code&gt;3E80CA1A8B89F69CBA57D98A76A5EF9054449A5C&lt;/code&gt; (Pierre Schmitz).&lt;/p&gt;
&lt;p&gt;GPG authenticated that the signature is valid and that the key used to sign is
trusted in our keyring.&lt;/p&gt;
&lt;p&gt;1 authenticated signature confirms the files integrity and authenticity.&lt;/p&gt;
&lt;p&gt;We have successfully verified that the file was signed by Pierr’s official Arch
Linux key and has not been tampered with.&lt;/p&gt;
&lt;p&gt;Since the key has been verified we can now sign it. We will have to import our
primary key to do so since we are keeping it offline for safety.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;gpg --import backup.gpg
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;List your keys to get the arch keyID:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;gpg --list-keys
# ... snip ...
pub   ed25519/0x76A5EF9054449A5C 2022-10-31 [SC] [expires: 2037-10-27]
      Key fingerprint = 3E80 CA1A 8B89 F69C BA57  D98A 76A5 EF90 5444 9A5C
uid                   [  full  ] Pierre Schmitz &amp;lt;pierre@archlinux.org&amp;gt;
uid                   [  full  ] Pierre Schmitz &amp;lt;pierre@archlinux.de&amp;gt;
sub   ed25519/0xD6D13C45BFCFBAFD 2022-10-31 [A] [expires: 2037-10-27]
sub   cv25519/0x7F56ADE50CA3D899 2022-10-31 [E] [expires: 2037-10-27]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Sign the key:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;gpg --sign-key 0x76A5EF9054449A5C
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now you can Export and publish the new public key and send it to a keyserver:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;gpg --export --armor 0x76A5EF9054449A5C &amp;gt; archlinux-signed.asc
gpg --send-keys 0x76A5EF9054449A5C
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The more people that verify, sign, and re-export and publish their keys the
better for the web of trust that gpg uses making the network more secure for
everyone.&lt;/p&gt;
&lt;h3&gt;Edit your trust level of the key&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;gpg --edit-key pierre@archlinux.org
gpg&amp;gt; trust
Please decide how far you trust this user to correctly verify other users&apos; keys
(by looking at passports, checking fingerprints from different sources, etc.)

  1 = I don&apos;t know or won&apos;t say
  2 = I do NOT trust
  3 = I trust marginally
  4 = I trust fully
  5 = I trust ultimately
  m = back to the main menu

Your decision? 3
# Output:
pub  ed25519/0x76A5EF9054449A5C
     created: 2022-10-31  expires: 2037-10-27  usage: SC
     trust: marginal      validity: full
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can see that the trust is &lt;code&gt;marginal&lt;/code&gt; and validity is &lt;code&gt;full&lt;/code&gt;.&lt;/p&gt;
&lt;/details&gt;
</content></entry><entry><title>NixOS Containers</title><id>https://saylesss88.github.io/nixos_containers.html</id><updated>2026-01-11T00:00:00+00:00</updated><author><name>saylesss87@proton.me (saylesss88)</name></author><link href="https://saylesss88.github.io/nixos_containers.html" rel="alternate"/><content type="html">&lt;h1&gt;NixOS Containers&lt;/h1&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Click to Expand Table of Contents&lt;/summary&gt;
&lt;!-- toc --&gt;
&lt;/details&gt;
&lt;p&gt;&lt;img src=&quot;https://saylesss88.github.io/images/boxes.cleaned.png&quot; alt=&quot;boxes&quot; /&gt;&lt;/p&gt;
&lt;p&gt;NixOS containers are lightweight &lt;code&gt;systemd-nspawn&lt;/code&gt; containers managed
declaratively through your NixOS configuration. They allow you to run separate,
minimal NixOS instances on the same machine, each with its own services,
packages, and (optionally) network stack.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.freedesktop.org/software/systemd/man/latest/systemd-nspawn.html?__goaway_challenge=meta-refresh&amp;amp;__goaway_id=5497ebb54af7da76c7cff2e5210fe9ab&amp;amp;__goaway_referer=https%3A%2F%2Fsearch.brave.com%2F&quot;&gt;freedesktop systemd-nspawn&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;❗ NixOS’ containers do not provide full security out of the box (just like
docker). They do give you a separate chroot, but a privileged user (root) in a
container can escape the container and become root on the host system.
–&lt;a href=&quot;https://blog.beardhatcode.be/2020/12/Declarative-Nixos-Containers.html&quot;&gt;beardhatcode Declarative-Nixos-Containers&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Common Use Cases&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Isolating services&lt;/strong&gt;: Run a web server, database, or any service in its own
container, so it can’t interfere with the main system or other services&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Testing and development&lt;/strong&gt;: Try out new configurations, packages, or services
in a sandboxed environment.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Reproducible deployments&lt;/strong&gt;: Because containers are defined declaratively,
you can reproduce the exact same environment anywhere.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Running multiple versions of a service&lt;/strong&gt;: For example, testing different
versions of Git or HTTP servers side by side.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;Hosting mdBook&lt;/h2&gt;
&lt;p&gt;Let’s say you want to host your mdBook. You can define a NixOS container that
runs only the necessary service, isolated from your main system:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{
  config,
  lib,
  ...
}: {
  containers.mdbook-host = {
    autoStart = true;
    ephemeral = true;
    privateNetwork = false;  # Use the hosts network

    bindMounts.&quot;/var/www/mdbook&quot; = {
      hostPath = &quot;/home/jr/nix-book/book&quot;;
      isReadOnly = true;
    };

    config = {containerPkgs, ...}: {
      networking.useDHCP = lib.mkDefault true;

      services.httpd = {
        enable = true;
        adminAddr = &quot;yourEmail.com&quot;;
        virtualHosts.&quot;localhost&quot; = {
          documentRoot = &quot;/var/www/mdbook&quot;;
          serverAliases = [];
        };
      };

      networking.firewall.allowedTCPPorts = [80];
      environment.systemPackages = with containerPkgs; [];
      system.stateVersion = &quot;25.05&quot;;
    };
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;ephemeral&lt;/code&gt;: if true, the container resets on each restart.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;autoStart&lt;/code&gt;: Ensures the container starts automatically at boot.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;config&lt;/code&gt;: Defines the containers NixOS configuration, just like a regular
NixOS system.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Mounts&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;    bindMounts.&quot;/var/www/mdbook&quot; = {
      hostPath = &quot;/home/jr/nix-book/book&quot;;
      isReadOnly = true;
    };
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;bindMount&lt;/code&gt; settings above specify that &lt;code&gt;/var/www/mdbook&lt;/code&gt; in the container
should be linked to &lt;code&gt;/home/jr/nix-book/book&lt;/code&gt; on the host.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;hostPath&lt;/code&gt; must exist, and &lt;code&gt;/var/www/mdbook&lt;/code&gt; must not exist for this to work.&lt;/p&gt;
&lt;p&gt;The above container is fairly simple because its &lt;code&gt;ReadOnly&lt;/code&gt;, things get more
complicated when you need HTTPD to have write privileges.&lt;/p&gt;
&lt;p&gt;When you create and run a NixOS container like &lt;code&gt;mdbook-host&lt;/code&gt;. NixOS stores the
container’s root filesystem and related container state data under:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ls /var/lib/nixos-containers/
╭────────────╮
│ empty list │  # It&apos;s empty because we set ephemeral to true
╰────────────╯
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This directory holds the container’s own filesystem image, including system
files, installed packages, configuration, and any data internal to the
container.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Check Container Status&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nixos-container list
mdbook-host
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo systemctl status container@mdbook-host
 Main PID: 32938 (systemd-nspawn)
     Status: &quot;Container running: Ready.&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Test HTTP server inside the container&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We configured Apache (&lt;code&gt;httpd&lt;/code&gt;) to serve &lt;code&gt;/var/www/mdbook&lt;/code&gt; at &lt;code&gt;localhost&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Let’s check if Apache is running:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo nixos-container run mdbook-host -- systemctl status httpd
● httpd.service - Apache HTTPD
     Loaded: loaded (/etc/systemd/system/httpd.service; enabled; preset: ignored)
     Active: active (running) since Fri 2025-08-15 10:14:39 EDT; 2min 18s ago
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Check the Bind Mount:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo nixos-container run mdbook-host -- ls -l /var/www/mdbook
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;You should see an &lt;code&gt;index.html&lt;/code&gt; and any other files from &lt;code&gt;~/nix-book/book&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Test the Web Server:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;curl http://localhost
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;You should see your book in HTTP format as raw HTML.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Test on the web, in your browser visit:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;http://localhost/
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;You should see your book fully served&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h3&gt;Troubleshooting&lt;/h3&gt;
&lt;p&gt;Make sure your book has the correct permissions to allow &lt;code&gt;hostPath&lt;/code&gt; to read it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo chmod -R o+rX ~/nix-book/book
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If needed restart the container:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo nixos-container stop mdbook-host
sudo nixos-container start mdbook-host
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ensure that &lt;code&gt;/var/www/mdbook&lt;/code&gt; is being populated:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo nixos-container run mdbook-host -- ls -l /var/www/mdbook
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You should see an &lt;code&gt;index.html&lt;/code&gt; and more&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo nixos-container run mdbook-host -- systemctl status httpd
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;You should see &lt;code&gt;enabled&lt;/code&gt; &amp;amp; &lt;code&gt;active (running)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Check the containers status:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo nixos-container status mdbook-host
up
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;Why Bother Serving your book to localhost?&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Real-time updates without rebuilding the container&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;Files added, changed, or removed from &lt;code&gt;~/nix-book/book&lt;/code&gt; on the host are
immediately reflected inside the container. This allows for:
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Rapid iteration and testing of your books content without rebuilding&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Easier debugging and fixing content or config issues on the fly.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;Keeps container images small and immutable&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;Instead of baking book files into the container image (which requires
rebuilding every change), the container image remains clean and generic.&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;Separation of concerns&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;The container focuses on running the service, while the content is managed
independently on the host. This separation improves maintainability and more.&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;Data persistence&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;Since the files live on the host, they persist independently of the containers
lifecycle: restarting, recreating, or destroying the container won’t lose your
content.&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;5&quot;&gt;
&lt;li&gt;Security Control&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;You can carefully set permissions on the host directory, control read/write
access, and isolate the container runtime from sensitive data.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;Removing the State&lt;/h2&gt;
&lt;p&gt;To remove &lt;code&gt;/var/lib/nixos-containers/mdbook-host&lt;/code&gt;, you need to remove the
container configuration, rebuild, and then run the following commands to remove
the immutable sticky bits that prevent deletion.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Forcibly remove all attributes
sudo chattr -R -i mdbook-host/
sudo rm -rf mdbook-host/
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;OCI deployment pipeline building a Rust App&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;If you want to use &lt;code&gt;mdbook-nix-repl&lt;/code&gt; check out the README, the following shows
how I tested locally before eventually adding a &lt;code&gt;flake.nix&lt;/code&gt; to the repo
streamlining this for users of the project.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/saylesss88/mdbook-nix-repl&quot;&gt;mdbook-nix-repl README&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This documents how to work with a local Rust crate repository without a
&lt;code&gt;flake.nix&lt;/code&gt; for testing. The README above explains how to generate a token and
use the project, this is just for educational purposes if you wanted to
implement something similar:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;nix-repl-server.nix&lt;/code&gt;, place this in the same dir as your
&lt;code&gt;configuration.nix&lt;/code&gt;:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{
  config,
  lib,
  pkgs,
  inputs,
  ...
}:

let
  cfg = config.custom.nix-repl-server;

  serverSource = inputs.mdbook-nix-repl + &quot;/server&quot;;

  # 1. Build the binary using your package definition
  # serverPkg = pkgs.callPackage ./server-pkg.nix { };
  serverPkg = pkgs.callPackage ./server-pkg.nix {
    src = serverSource;
  };

  # 2. Build a minimal container image containing just the server + nix + deps
  nixReplImage = pkgs.dockerTools.buildLayeredImage {
    name = &quot;nix-repl-server&quot;;
    tag = &quot;latest&quot;;

    # dependencies needed at runtime inside the container
    contents = [
      serverPkg
      pkgs.nix
      pkgs.bashInteractive
      pkgs.cacert
      pkgs.tini
      pkgs.coreutils
    ];

    config = {
      Entrypoint = [
        &quot;${pkgs.tini}/bin/tini&quot;
        &quot;--&quot;
      ];
      Cmd = [ &quot;${serverPkg}/bin/nix-repl-server&quot; ];
      ExposedPorts = {
        &quot;8080/tcp&quot; = { };
      };
      # Important: Container must see 0.0.0.0 to receive traffic from host port mapping
      Env = [
        &quot;NIX_REPL_BIND=0.0.0.0&quot;
        &quot;NIX_CONFIG=experimental-features = nix-command flakes&quot;
        &quot;SSL_CERT_FILE=${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt&quot;
      ];
    };
  };
in
{
  options.custom.nix-repl-server = {
    enable = lib.mkEnableOption &quot;nix-repl-server container&quot;;
    port = lib.mkOption {
      type = lib.types.port;
      default = 8080;
      description = &quot;Host port to map to the container&quot;;
    };
    tokenFile = lib.mkOption {
      type = lib.types.path;
      default = &quot;/etc/nix-repl-server.env&quot;;
      description = &quot;Path to file containing NIX_REPL_TOKEN=...&quot;;
    };
  };

  config = lib.mkIf cfg.enable {
    # Enable Podman backend
    virtualisation.podman.enable = true;
    virtualisation.oci-containers.backend = &quot;podman&quot;;

    # The OCI container definition
    virtualisation.oci-containers.containers.nix-repl-server = {
      image = &quot;nix-repl-server:latest&quot;;

      # This effectively &quot;loads&quot; the image into Podman on boot
      imageFile = nixReplImage;

      ports = [ &quot;127.0.0.1:${toString cfg.port}:8080&quot; ];

      # Inject the token safely at runtime (not in Nix store)
      environmentFiles = [ cfg.tokenFile ];

      extraOptions = [
        &quot;--cap-drop=ALL&quot;
        &quot;--security-opt=no-new-privileges&quot;
        &quot;--pull=never&quot; # Use the local loaded image
      ];
    };
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;&lt;code&gt;server-pkg.nix&lt;/code&gt;, place this in the same dir as &lt;code&gt;nix-repl-server.nix&lt;/code&gt;:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{
  lib,
  rustPlatform,
  nix,
  pkg-config,
  openssl,
  makeWrapper,
  inputs,
  src,
}:

rustPlatform.buildRustPackage {
  pname = &quot;nix-repl-server&quot;;
  version = &quot;0.1.0&quot;;

  # Point this to your actual source root (where Cargo.toml is)
  # src = ./.;
  inherit src;

  # You must commit Cargo.lock for this to work
  # cargoLock.lockFile = ../../../mdbook-nix-repl/server/Cargo.lock;
  cargoLock.lockFile = &quot;${src}/Cargo.lock&quot;;

  postPatch = &apos;&apos;
    cp Cargo.toml.inc Cargo.toml
  &apos;&apos;;

  # Runtime dependencies (nix for evaluation)
  nativeBuildInputs = [
    pkg-config
    makeWrapper
  ];
  buildInputs = [ openssl ];

  doCheck = false;

  # Ensure &apos;nix&apos; is available in the path if your binary calls Command::new(&quot;nix&quot;)
  postInstall = &apos;&apos;
    wrapProgram $out/bin/nix-repl-server --prefix PATH : ${lib.makeBinPath [ nix ]}
  &apos;&apos;;

  meta = with lib; {
    description = &quot;Secure Nix REPL server for mdbook-nix-repl&quot;;
    platforms = platforms.linux;
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;&lt;code&gt;flake.nix&lt;/code&gt;, this URL leads to a Rust crate repo:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;inputs = {
  mdbook-nix-repl = {
    url = &quot;path:/home/jr/mdbook-nix-repl&quot;;
    flake = false;
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;&lt;code&gt;configuration.nix&lt;/code&gt;:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{ pkgs, inputs, ... }:
{
  imports = [
    # Include the results of the hardware scan.
    ./hardware-configuration.nix
    ./users.nix
    ./nix-repl-server.nix
  ];

  custom.nix-repl-server = {
    enable = true;
    port = 8080; # Optional, defaults to 8080
    tokenFile = &quot;/etc/nix-repl-server.env&quot;;
  };
# --snip--
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h3&gt;Resources&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developers.redhat.com/blog/2018/02/22/container-terminology-practical-introduction#&quot;&gt;RedHat A Practical Intro to Container Technology&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content></entry><entry><title>Packaging a Rust crate for Nixpkgs</title><id>https://saylesss88.github.io/nixpkgs/rust_crate_to_nixpkgs.html</id><updated>2025-12-31T00:00:00+00:00</updated><author><name>saylesss87@proton.me (saylesss88)</name></author><link href="https://saylesss88.github.io/nixpkgs/rust_crate_to_nixpkgs.html" rel="alternate"/><content type="html">&lt;details&gt;
&lt;summary&gt; ✔️ Table of Contents&lt;/summary&gt;
&lt;!-- toc --&gt;
&lt;/details&gt;
&lt;h1&gt;Packaging a Rust crate for Nixpkgs&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;NOTE: This example assumes you’re packaging a crate that’s already on
crates.io, or you’re packaging an existing Rust project for nixpkgs.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Nixpkgs is a big repository, so it helps to start with a focused workflow:
create a branch, add a package under &lt;code&gt;pkgs/by-name/&lt;/code&gt;, build it, then open a PR.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Clone nixpkgs&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Fork and clone &lt;code&gt;NixOS/nixpkgs&lt;/code&gt;:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git clone git@github.com:your-user/nixpkgs.git
cd nixpkgs
git remote add upstream git@github.com:NixOS/nixpkgs.git
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(SSH avoids HTTPS helper issues)&lt;/p&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;If your clone is shallow, convert it to full history (doesn’t lose work):&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git fetch --unshallow --tags
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;Create a branch and add package:&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Create a branch before changes preferably:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git switch -c mdbook-rss-feed
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;Add the package under pkgs/by-name&lt;/h2&gt;
&lt;p&gt;New top-level packages should generally go under
&lt;code&gt;pkgs/by-name/&amp;lt;2 letters&amp;gt;/&amp;lt;name&amp;gt;/package.nix&lt;/code&gt; (e.g.
&lt;code&gt;pkgs/by-name/md/mdbook-rss-feed/package.nix&lt;/code&gt;). Packages in &lt;code&gt;pkgs/by-name&lt;/code&gt; are
picked up automatically and usually don’t require edits to &lt;code&gt;all-packages.nix&lt;/code&gt;.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Write package.nix (Rust crate example)&lt;/h2&gt;
&lt;p&gt;Start with &lt;code&gt;rustPlatform.buildRustPackage&lt;/code&gt; and &lt;code&gt;fetchCrate&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{
  lib,
  rustPlatform,
  fetchCrate,
  versionCheckHook,
}:
rustPlatform.buildRustPackage rec {
  pname = &quot;mdbook-rss-feed&quot;;
  version = &quot;1.3.0&quot;;

  src = fetchCrate {
    inherit pname version;
    hash = &quot;output of `nix hash convert` shown below&quot;;
  };

  cargoHash = lib.fakeHash;

  nativeInstallCheckInuts = [
    versionCheckHook
  ];
  doInstallCheck = true;

  meta = {
    description = &quot;mdBook preprocessor that generates RSS, Atom, and JSON feeds&quot;;
    mainProgram = &quot;mdbook-rss-feed&quot;;
    homePage = &quot;https://crates.io/crates/mdbook-rss-feed&quot;;
    license = lib.licenses.asl20;
    maintainers = [ lib.maintainers.sayls88 ];
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;Prefetch the crate hash:&lt;/h2&gt;
&lt;p&gt;Use &lt;code&gt;fetchCrate&lt;/code&gt; / &lt;code&gt;crate2nix&lt;/code&gt; style workflow, or just prefetch the &lt;code&gt;crates.io&lt;/code&gt;
tarball:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix-prefetch-url \
  --unpack \
  https://crates.io/api/v1/crates/mdbook-rss-feed/1.3.0/download
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That prints a base32 hash: &lt;code&gt;0932843lknasdlfkm2lkdnflaknldvdsvser&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Convert it to sri format:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix hash convert --hash-algo sha256 --from nix32 --to sri 0932843lknasdlfkm2lkdnflaknldvdsvser
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The above commands output looks like: &lt;code&gt;sha256-...=&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Put the resulting &lt;code&gt;sha256-...&lt;/code&gt; into &lt;code&gt;src.hash&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;  src = fetchCrate {
    inherit pname version;
    hash = &quot;sha256-...&quot;;
  };
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;Get cargoHash via a failing build&lt;/h2&gt;
&lt;p&gt;In the &lt;code&gt;nixpkgs&lt;/code&gt; root (i.e., the &lt;code&gt;nixpkgs&lt;/code&gt; directory), run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix-build -A mdbook-rss-feed
# OR nix3 format
nix build .#mdbook-rss-feed
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Nix will fail with a message like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;hash mismatch
specified: sha256-....
got: sha256-1...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Copy the &lt;code&gt;got&lt;/code&gt; value into &lt;code&gt;cargoHash&lt;/code&gt;, rebuild, and it should succeed.&lt;/p&gt;
&lt;p&gt;Sanity check: from &lt;code&gt;nixpkgs&lt;/code&gt; root :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;./result/bin/mdbook-rss-feed --version
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;Adding yourself as maintainer&lt;/h2&gt;
&lt;p&gt;Edit &lt;code&gt;nixpkgs/maintainers/maintainer-list.nix&lt;/code&gt; add your user in alphabetical
order:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;your-handle = {
  email = &quot;you@example.com&quot;;
  name = &quot;Your Name&quot;;
  github = &quot;your-gh-handle&quot;;
  githubId = 12345678;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you specify &lt;code&gt;github&lt;/code&gt;, nixpkgs expects &lt;code&gt;githubId&lt;/code&gt; too. You can get it from:
&lt;code&gt;https://api.github.com/users/&amp;lt;user&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The nixpkgs maintainers prefer if you add the &lt;code&gt;maintainer-list.nix&lt;/code&gt; as a
separate commit.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git commit -m &quot;maintainers: add &amp;lt;user&amp;gt;&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;Treefmt&lt;/h2&gt;
&lt;p&gt;Run treefmt the nixpkgs way, from the repo root. Run this right before you push,
my editors formatter does something different with single entry lists than what
Nixpkgs wants:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix develop --command treefmt
nix fmt
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;Rebase and push safely&lt;/h2&gt;
&lt;p&gt;From the &lt;code&gt;mdbook-rss-feed&lt;/code&gt; branch:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git fetch upstream --tags
git rebase upstream/master
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Commit and push your PR branch:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Then commit and push&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git commit -m &quot;mdbook-rss-feed: init at 1.3.0&quot;
# First push
git push origin mdbook-rss-feed
# Use `--force-with-lease` only if you rebased/amended and need to rewrite the PR branch.
# git push --force-with-lease origin mdbook-rss-feed
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;--force-with-lease&lt;/code&gt; is the recommended safe force-push for PR branches.&lt;/p&gt;
&lt;p&gt;If &lt;code&gt;--force-with-lease&lt;/code&gt; says “stale info”, fetch the remote branch ref first,
then retry.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Then commit and push&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git commit -m &quot;mdbook-rss-feed: init at 1.3.0&quot;
git push -u origin mdbook-rss-feed
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Go to GitHub -&amp;gt; your fork&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Click “Compare &amp;amp; pull request” on the &lt;code&gt;mdbook-rss-feed&lt;/code&gt; branch&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fill out the PR template (why useful, tested on x86_64-linux, etc.)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Submit!&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The package will go through CI checks, and once green + approved by a
maintainer, it’ll land in nixpkgs.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Recovering from Mistakes&lt;/h2&gt;
&lt;p&gt;You’re bound to make mistakes, if you learn some Git basics it will help you
quite a bit.&lt;/p&gt;
&lt;p&gt;You should avoid adding new commits for small fixes like typos, formatting, or
minor adjustments requested in review. For substantial changes that add
functionality, a new commit may be more appropriate.&lt;/p&gt;
&lt;p&gt;Say that we pushed our PR and one of the maintainers gave us a suggested change,
(they want us to follow conventions and remove a trailing period from our
packages description for this example).&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Make the edit locally (remove the trailing period in the file)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Stage the change: &lt;code&gt;git add pkgs/by-name/xx/your-package/package.nix&lt;/code&gt; (avoid
&lt;code&gt;git add -A&lt;/code&gt; as it stages everything, which can accidentally include
unrelated files)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Amend the commit: &lt;code&gt;git commit --amend --no-edit&lt;/code&gt;(this preserves your original
commit message, if you want to change the commit message use
&lt;code&gt;git commit --amend&lt;/code&gt;)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Force push: &lt;code&gt;git push --force-with-lease&lt;/code&gt; (This is safer than just using
&lt;code&gt;--force&lt;/code&gt; because it will fail if someone else has pushed commits to your
branch that you don’t have locally)&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Alternative for Multiple Commits&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Interactive rebase is useful when your PR has several “WIP” commits (or you
added a small review fix as a separate commit) and you want to present a cleaner
history before merge.&lt;/p&gt;
&lt;p&gt;You can use interactive rebase to squash all your would be small fix commits
into a single commit they belong to.&lt;/p&gt;
&lt;p&gt;Avoid squashing if the commits represent distinct, reviewable changes that stand
on their own.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Basic Workflow (squash/fixup)&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Decide how many commits back you want to edit (example: last 3 commits):&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git rebase -i HEAD~3
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;Your editor opens with a “todo” list (oldest at top). Change later commits
from &lt;code&gt;pick&lt;/code&gt; to &lt;code&gt;fixup&lt;/code&gt; or &lt;code&gt;squash&lt;/code&gt;:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;fixup&lt;/code&gt; = combine into the previous commit, discard this commit message.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;squash&lt;/code&gt; = combine, but keep/edit commit messages.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Example todo:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;pick 1111111 mdbook-rss-feed: init at 0.1.0
pick 2222222 mdbook-rss-feed: fix trailing period
pick 3333333 mdbook-rss-feed: formatting
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Change to:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;pick 1111111 mdbook-rss-feed: init at 0.1.0
fixup 2222222 mdbook-rss-feed: fix trailing period
fixup 3333333 mdbook-rss-feed: formatting
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;Save/close: if you squashed, Git will prompt you to edit the final combined
message.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Push updated history&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You have to force-push because &lt;code&gt;rebase&lt;/code&gt; rewrites commit SHAs:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git push --force-with-lease
# If something goes wrong
# git rebase --abort
# If you hit conflicts, fix the files, then:
# git add &amp;lt;files&amp;gt;
# git rebase --continue
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you have an unrelated change accidentally included (for example: you staged
an extra file), it’s usually better to fix it via rebase/splitting before
reviewers spend time re-reviewing noise.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;As of 01-13-26 I have been waiting for 2 weeks for the darwin checks to complete,
I guess this &lt;a href=&quot;https://discourse.nixos.org/t/ofborg-aarch64-darwin-builds-causing-bottleneck/55290&quot;&gt;bottleneck&lt;/a&gt;
has gotten worse. I guess most PRs take about 6 weeks to resolve FYI.&lt;/p&gt;
&lt;/blockquote&gt;
</content></entry><entry><title>Hardening Networking</title><id>https://saylesss88.github.io/nix/hardening_networking.html</id><updated>2025-12-25T00:00:00+00:00</updated><author><name>saylesss87@proton.me (saylesss88)</name></author><link href="https://saylesss88.github.io/nix/hardening_networking.html" rel="alternate"/><content type="html">&lt;h1&gt;Hardening Networking&lt;/h1&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Table of Contents&lt;/summary&gt;
&lt;!-- toc --&gt;
&lt;/details&gt;
&lt;blockquote&gt;
&lt;p&gt;Since networks and systems vary, some adjustments may cause unexpected issues,
especially around critical components like DNS or firewalls. Always review and
test changes in a controlled environment before applying them broadly.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Understand the trade-offs and tailor the settings to your threat model and
workflow. Take what’s useful, adapt as needed, and seek expert guidance for
more advanced scenarios.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Every setup is unique, feel free to adapt or skip sections based on your needs.
Start with the basics and build up as you gain confidence. The goal is
practical, tested hardening tailored to you.&lt;/p&gt;
&lt;h3&gt;Safe Browsing / Privacy Enhancing Habits&lt;/h3&gt;
&lt;p&gt;I recently broke this chapter down and added another chapter:
&lt;a href=&quot;https://saylesss88.github.io/nix/browsing_security.html&quot;&gt;Browser/Browsing Security&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Adopt Encrypted DNS and HTTPS Everywhere&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Configure your system and browsers to use DNS over HTTPS (DoH), DNS over TLS
(DoT), or DNSCrypt to prevent DNS leakage. Use HTTPS-Only mode in browsers to
encrypt all web traffic. Prefer browsers with strong privacy defaults or add
recommended extensions.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.privacyguides.org/en/dns/#dnscrypt-proxy&quot;&gt;Privacy Guides dnscrypt-proxy recommendation&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Disable browser “remember password” and autofill features, clear cookies and
site data upon exit, and carefully vet suspicious URLs with tools like
&lt;a href=&quot;https://www.virustotal.com/gui/home/url&quot;&gt;VirusTotal&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Limit Account Linking and Use Unique Credentials&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create separate accounts with unique passwords instead of signing in with
Google, Facebook, or similar services to limit broad data exposure from
compromises.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Use Metadata Cleaning Tools&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Many files like images, PDFs, and office documents contain hidden metadata
information such as location data, device details, and more that can reveal
your identity or other sensitive information when you share files publicly.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;To protect your privacy, always sanitize files by removing this metadata
before sharing. Tools like &lt;a href=&quot;https://0xacab.org/jvoisin/mat2&quot;&gt;mat2&lt;/a&gt; are
designed to strip metadata from a wide range of media files efficiently.
(&lt;code&gt;pkgs.mat2&lt;/code&gt;). You just type &lt;code&gt;mat2 swappy-2025.png&lt;/code&gt; for example and there will
then be a new &lt;code&gt;mat2 swappy-2025.cleaned.png&lt;/code&gt; that can safely be shared.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Use Anonymous File-Sharing Tools&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;For sensitive transfers, consiter tools like
&lt;a href=&quot;https://github.com/onionshare/onionshare&quot;&gt;OnionShare&lt;/a&gt; that provide anonymity
and security.(&lt;code&gt;pkgs.onionshare&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Avoid Scanning Random QR Codes Without Verification&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use QR code scanner apps that check for malicious content before loading
links.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Understand Your Threat Model&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Apply these basics universally, but tailor advanced hardening according to
your unique environment, connectivity needs, and risk profile.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Delete cookies and site data when the browser is closed&lt;/strong&gt;. (security not
usability).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Use Strong, Unique Passwords and a Password Manager&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Avoid reused passwords by using reliable password managers like KeePassXC or
Bitwarden, both available on NixOS. Pair this with enabling two-factor
authentication &lt;strong&gt;(2FA) wherever possible&lt;/strong&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;It’s advisable to only use the desktop version and not the browser extension
for a number of reasons. One is that you can store your passwords completely
offline and have complete ownership of them.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;environment.systemPackages = [
    pkgs.keepassxc
    pkgs.kpcli     # KeePass CLI
    # OR
    pkgs.bitwarden-desktop
    pkgs.bitwarden-cli
];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With KeePassXC, you can require 3 different authentication methods at the same
time. You can choose a password, a keyfile, and a security key where it won’t
open unless all 3 are present giving you additional security. All 3 might not be
necessary but it’s possible. It’s also easy to migrate to KeePassXC, you can
import your vault from many different managers.&lt;/p&gt;
&lt;p&gt;KeepassXC also makes it easy to keep your complete password database offline
which can significantly reduce the risk of a breach.&lt;/p&gt;
&lt;p&gt;With Bitwarden, to enable 2 factor authentication, you need to log in with your
master password through the web interface.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.privacyguides.org/en/basics/passwords-overview/&quot;&gt;PrivacyGuides Intro to Passwords&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h3&gt;Why Follow These Basics?&lt;/h3&gt;
&lt;p&gt;These recommended steps help protect your privacy and security while maintaining
usability and minimizing system interruptions. They catch common threats like
network eavesdropping, password reuse, fingerprinting, and data leakage,
providing a solid foundation to build on.&lt;/p&gt;
&lt;p&gt;A vast majority of secure and privacy-focused browsers available for NixOS are
based on Firefox.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;❗ NOTE: Firefox does lack some security features available in Chrome and
sandbox escapes in Linux are relatively easy. People such as madaidan say to
never use Linux or Firefox period when you’re worried about security and
privacy. I’m not personally going to jump to proprietary software with known
backdoors in a misguided attempt at increasing security/privacy.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://techstory.in/eu-hits-google-with-3-5-billion-antitrust-fine-over-adtech-practices/&quot;&gt;EU Hits Google with 3.5 Billion Antitrust&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This &lt;a href=&quot;https://grapheneos.org/usage#web-browsing&quot;&gt;GrapheneOS article&lt;/a&gt;, breaks
down why they use Chromium-based browsers and specifically mentions that it’s
not recommended to use Firefox, especially on Linux because of the weak
sandboxing.&lt;/p&gt;
&lt;p&gt;As a Chromium-based browser, Brave has been growing on me. Brave uses
randomization rather than standardization for fingerprinting protection. If you
run Cover Your Tracks with Brave, it will show a randomized fingerprint.&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Click To Expand United States Patriot Act Overview &lt;/summary&gt;
&lt;p&gt;&lt;a href=&quot;https://www.csis.org/analysis/fact-sheet-section-215-usa-patriot-act&quot;&gt;Section 215 USA Patriot Act&lt;/a&gt;
permits the collection of “Tangible Things” or “Business Records”, e.g., your
phone records, medical records, etc. for an investigation to obtain foreign
intelligence information. If it does relate to a US person it must be relevant
to preventing terrorism or espionage, and not be based solely on activities
protected by the first amendment. “Relevant” is the key word here and it is at
the governments discretion meaning they sweep everything and sift it later.
Criticized for violating American citizens Fourth Amendment protections against
warrantless search and seizure and proven to be ineffective.&lt;/p&gt;
&lt;/details&gt;
&lt;p&gt;What is “normal” and allowed today might be suppressed tomorrow, look at the UK
&lt;a href=&quot;https://en.wikipedia.org/wiki/Online_Safety_Act_2023&quot;&gt;Online Safety Act&lt;/a&gt;
purported to protect children, accused of banning privacy. This is because the
only way to verify age is to make everyone submit KYC with their drivers license
or ID, completely taking away any anonymity of adults and children alike.&lt;/p&gt;
&lt;p&gt;Also see
&lt;a href=&quot;https://www.bbc.com/news/articles/cq68j5g2nr1o&quot;&gt;BBC 4chan refuses to pay fine&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The mere existence of a surveillance state breeds fear and conformity and
stifles free
expression.–&lt;a href=&quot;https://theintercept.com/2016/04/28/new-study-shows-mass-surveillance-breeds-meekness-fear-and-self-censorship/&quot;&gt;The Intercept&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;There are much more scary examples in
&lt;a href=&quot;https://thenewoil.org/en/guides/prologue/why/&quot;&gt;Privacy, The new Oil&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Protections from Surveillance in the U.S.&lt;/h2&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Click to Expand U.S. Surveillance protections &lt;/summary&gt;
&lt;blockquote&gt;
&lt;p&gt;⚠️ A crucial caveat to keep in mind regarding surveillance protections in the
U.S., whether grounded in the Fourth Amendment, the First Amendment, or
statutory laws is that &lt;strong&gt;these protections are not foolproof and have
repeatedly failed or been circumvented in practice&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Fourth Amendment Basics&lt;/strong&gt;: It demands reasonableness in searches and usually
requires a warrant. This means government agents cannot arbitrarily listen to
your private communications or search your digital data without judicial
approval&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Electronic Surveillance Challenges&lt;/strong&gt;: Courts have wrestled with how the
Fourth Amendment applies to modern communications. The Supreme Court has ruled
in some cases that pervasive or non-consensual electronic surveillance
violates reasonable expectations of privacy, but other rulings have allowed
broader state actions in national security contexts.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The Third-Party Doctrine&lt;/strong&gt;: A major limitation arises from the “third-party
doctrine,” which holds that information voluntarily shared with third parties
(like phone companies or internet providers) has reduced Fourth Amendment
protections. This means data held by third parties may be subject to
government access without a warrant in some cases&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The First Amendment&lt;/strong&gt; guarantees free speech and the freedom to receive
information without government censorship or intimidation. Excessive or
secretive government surveillance can chill free speech by making people
afraid their communications are monitored, discouraging open expression and
participation in public discourse.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Advocates argue that courts should recognize government surveillance not
only as a Fourth Amendment search issue but also as a First Amendment
violation where surveillance suppresses or chills constitutionally protected
expression.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While the Fourth Amendment traditionally governs searches and surveillance
legality, the First Amendment frames the broader impact on free speech and
democratic engagement. Invoking both provides a more comprehensive
constitutional shield against intrusive surveillance practices.&lt;/p&gt;
&lt;/details&gt;
&lt;hr /&gt;
&lt;h2&gt;Encrypted DNS&lt;/h2&gt;
&lt;p&gt;DNS (Domain Name System) resolution is the process of translating a website’s
domain name into its corresponding IP address. By default, this traffic isn’t
encrypted, which means anyone on the network, from your ISP to potential
hackers, can see the websites you’re trying to visit. &lt;strong&gt;Encrypted DNS&lt;/strong&gt; uses
protocols to scramble this information, protecting your queries and responses
from being intercepted and viewed by others.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;❗ NOTE: There are many other ways for someone monitoring your traffic to see
what domain you looked up via DNS that it’s effectiveness is questionable
without also using Tor or a VPN. Encrypted DNS will not help you hide any of
your browsing activity.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;There are 3 main types of DNS protection:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;DNS over HTTPS (DoH)&lt;/strong&gt;: Uses the HTTPS protocol to encrypt data between the
client and the resolver.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;DNS over TLS (DoT)&lt;/strong&gt;: Similar to (DoH), differs in the methods used for
encryption and delivery using a separate port from HTTPS.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;DNSCrypt&lt;/strong&gt;: Uses end-to-end encryption with the added benefit of being able
to prevent DNS spoofing attacks.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Useful resources:&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Click to Expand DNS Resources &lt;/summary&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://wiki.nixos.org/wiki/Encrypted_DNS&quot;&gt;NixOS Wiki Encrypted DNS&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.cloudflare.com/learning/dns/what-is-dns/&quot;&gt;Domain Name System (DNS)&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/DNS_over_HTTPS&quot;&gt;Wikipedia DNS over HTTPS (DoH)&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/DNS_over_TLS&quot;&gt;Wikipedia DNS over TLS (DoT)&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://blog.cloudflare.com/dns-encryption-explained/&quot;&gt;Cloudflare Dns Encryption Explained&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://nordvpn.com/blog/encrypted-dns-traffic/&quot;&gt;NordVPN Encrypted Dns Traffic&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Hot Take&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://madaidans-insecurities.github.io/encrypted-dns.html&quot;&gt;Encrypted DNS is ineffective without a VPN or Tor by madaidan&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/details&gt;
&lt;p&gt;The following sets up dnscrypt-proxy using ODoH (Oblivious DNS over HTTPS) with
an oisd blocklist:&lt;/p&gt;
&lt;p&gt;Add &lt;code&gt;oisd&lt;/code&gt; to your flake inputs:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# flake.nix
inputs = {
    oisd = {
      url = &quot;https://big.oisd.nl/domainswild&quot;;
      flake = false;
    };
};
&lt;/code&gt;&lt;/pre&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Add more blocklists: HaGeZi Multi PRO &lt;/summary&gt;
&lt;p&gt;To use the Hagezi Multi PRO Blocklist either with oisd or alone you could do the
following:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# flake.nix
inputs = {
    oisd = {
      url = &quot;https://big.oisd.nl/domainswild&quot;;
      flake = false;
    };
    hagezi = {
      url = &quot;https://raw.githubusercontent.com/hagezi/dns-blocklists/main/wildcard/pro-onlydomains.txt&quot;;
      flake = false;
    };
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;add it to the &lt;code&gt;extraBlocklist&lt;/code&gt; variable in the following &lt;code&gt;dnscrypt-proxy.nix&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# dnscrypt-proxy.nix
extraBlocklist = builtins.readFile inputs.hagezi;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;More blocklist url’s:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;# NextDNS CNAME cloaking list
https://raw.githubusercontent.com/nextdns/cname-cloaking-blocklist/master/domains

# AdGuard Simplified Domain Names filter
https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt

# OISD Big list
https://big.oisd.nl/domainswild

# HaGeZi Multi PRO
https://raw.githubusercontent.com/hagezi/dns-blocklists/main/wildcard/pro-onlydomains.txt

# HaGeZi Threat Intelligence Feeds
https://raw.githubusercontent.com/hagezi/dns-blocklists/main/wildcard/tif-onlydomains.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;
&lt;blockquote&gt;
&lt;p&gt;❗ NOTE: The &lt;code&gt;oisd&lt;/code&gt; blocklist is a plain text file that updates frequently.
This can cause &lt;code&gt;nh os switch&lt;/code&gt; to fail with a &lt;code&gt;NarHash&lt;/code&gt; mismatch error. To fix
this, you need to run &lt;code&gt;nix flake update&lt;/code&gt; to refresh the blocklist and its hash
in your &lt;code&gt;flake.lock&lt;/code&gt; file. After that, you can run your &lt;code&gt;nh&lt;/code&gt; command again.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And the import the following into your &lt;code&gt;configuration.nix&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# dnscrypt-proxy.nix
{
  pkgs,
  lib,
  inputs,
  ...
}: let
  blocklist_base = builtins.readFile inputs.oisd;
  extraBlocklist = &quot;&quot;;
  blocklist_txt = pkgs.writeText &quot;blocklist.txt&quot; &apos;&apos;
    ${extraBlocklist}
    ${blocklist_base}
  &apos;&apos;;
  hasIPv6Internet = true;
  StateDirName = &quot;dnscrypt-proxy&quot;; # Used for systemd StateDirectory
  StatePath = &quot;/var/lib/${StateDirName}&quot;;
in {
  networking = {
    nameservers = [&quot;127.0.0.1&quot; &quot;::1&quot;];
    networkmanager.dns = &quot;none&quot;;
  };

  services.resolved.enable = lib.mkForce false;

  services.dnscrypt-proxy = {
    enable = true;
    settings = {
      sources.public-resolvers = {
        urls = [
          &quot;https://raw.githubusercontent.com/DNSCrypt/dnscrypt-resolvers/master/v3/public-resolvers.md&quot;
          &quot;https://download.dnscrypt.info/resolvers-list/v3/public-resolvers.md&quot;
        ];
        minisign_key = &quot;RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3&quot;;
        cache_file = &quot;${StatePath}/public-resolvers.md&quot;;
      };

      sources.relays = {
        urls = [
          &quot;https://raw.githubusercontent.com/DNSCrypt/dnscrypt-resolvers/master/v3/relays.md&quot;
          &quot;https://download.dnscrypt.info/resolvers-list/v3/relays.md&quot;
        ];
        cache_file = &quot;${StatePath}/relays.md&quot;;
        minisign_key = &quot;RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3&quot;;
      };

      sources.odoh-servers = {
        urls = [
          &quot;https://raw.githubusercontent.com/DNSCrypt/dnscrypt-resolvers/master/v3/odoh-servers.md&quot;
          &quot;https://download.dnscrypt.info/resolvers-list/v3/odoh-servers.md&quot;
        ];
        cache_file = &quot;${StatePath}/odoh-servers.md&quot;;
        minisign_key = &quot;RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3&quot;;
      };

      sources.odoh-relays = {
        urls = [
          &quot;https://raw.githubusercontent.com/DNSCrypt/dnscrypt-resolvers/master/v3/odoh-relays.md&quot;
          &quot;https://download.dnscrypt.info/resolvers-list/v3/odoh-relays.md&quot;
        ];
        cache_file = &quot;${StatePath}/odoh-relays.md&quot;;
        minisign_key = &quot;RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3&quot;;
      };

      server_names = [&quot;odoh-cloudflare&quot; &quot;odoh-snowstorm&quot;];

      # This creates the [anonymized_dns] section in dnscrypt-proxy.toml
      anonymized_dns = {
        skip_incompatible = true;
        routes = [
          {
            server_name = &quot;odoh-snowstorm&quot;;
            via = [&quot;odohrelay-crypto-sx&quot;];
          }
          {
            server_name = &quot;odoh-cloudflare&quot;;
            via = [&quot;odohrelay-crypto-sx&quot;];
          }
        ];
      };

      ipv6_servers = hasIPv6Internet;
      block_ipv6 = !hasIPv6Internet;
      blocked_names.blocked_names_file = &quot;${blocklist_txt}&quot;;
      require_dnssec = true;
      require_nolog = false;
      require_nofilter = false;
      odoh_servers = true;
      dnscrypt_servers = true;
    };
  };

  # This creates /var/lib/dnscrypt-proxy with correct permissions
  systemd.services.dnscrypt-proxy2.serviceConfig.StateDirectory = StateDirName;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This module follows a “Zero Trust” model for your internet traffic, ensuring no
single entity can see both &lt;strong&gt;who you are&lt;/strong&gt; and &lt;strong&gt;where you are going&lt;/strong&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# You should see that dnscrypt-proxy chooses the Server with the lowest initial latency
sudo systemctl status dnscrypt-proxy2
# verify that dnscrypt-proxy is listening
sudo ss -lnp | grep 53
# Test a DNS query, if you get valid responses it&apos;s working
dig @127.0.0.1 example.com +short
# check the logs
sudo journalctl -u dnscrypt-proxy2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;dnscrypt-proxy2&lt;/code&gt; acts as your local DNS resolver listening on your machine
(&lt;code&gt;127.0.0.1&lt;/code&gt;) for IPv4 and &lt;code&gt;::1&lt;/code&gt; for iPv6.&lt;/p&gt;
&lt;p&gt;The system’s DNS settings (&lt;code&gt;networking.nameservers&lt;/code&gt;) point to localhost, so
&lt;strong&gt;all DNS queries&lt;/strong&gt; go to dnscrypt-proxy accept for your browser. Your browser
has to be configured separately with a local resolver in which I haven’t figured
out yet. I recommend setting your browsers DNS over HTTPS to strict with a
respected custom DNS resolver such as &lt;code&gt;https://dns.quad9.net/dns-query&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;inputs.oisd&lt;/code&gt; refers to the flake input oisd blocklist, it prevents your device
from connecting to unwanted or harmful domains.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;dnscrypt-proxy2&lt;/code&gt; then encrypts and forwards our DNS requests to third-party
public DNSCrypt or DoH servers.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ODoH Relays: This is the “Oblivious” part. It breaks the link between your IP
address and your browsing history.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Setting up Tailscale&lt;/h3&gt;
&lt;p&gt;I was surprised at how easy this actually was to set up. Either go to
&lt;a href=&quot;https://www.tailscale.com&quot;&gt;https://www.tailscale.com&lt;/a&gt; and/or download the app for either Android or IOS,
sign up with your identity provider, and click &lt;code&gt;Start connecting devices -&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://tailscale.com/kb/1017/install&quot;&gt;Tailscale quickstart&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To add tailscale to NixOS:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# tailscale.nix
{...}: {
  services.tailscale.enable = true;
  # Tell the firewall to implicitly trust packets routed over Tailscale:
  networking.firewall.trustedInterfaces = [&quot;tailscale0&quot;];
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Tailscale will automatically use the hostname of your device as the name of the
network. If you want to change it to something else:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo tailscale set --hostname=&amp;lt;name&amp;gt;
# You can also give your account a nickname
sudo tailscale set --nickname=&amp;lt;name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This allows you to refer to your network by &lt;code&gt;name&lt;/code&gt; rather than IP address.&lt;/p&gt;
&lt;p&gt;Tailscale uses &lt;a href=&quot;https://tailscale.com/kb/1081/magicdns&quot;&gt;MagicDNS&lt;/a&gt; which is
enabled by default, and they recommend you keep it enabled.&lt;/p&gt;
&lt;p&gt;The docs say that by default, devices in your tailnet prefer their local DNS
settings and only use the tailnet’s DNS servers when needed. I had to completely
disable my Androids DNS settings for tailscale to access the internet through
MagicDNS.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo tailscale set --accept-dns=false
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To connect to tailscale after rebuilding you can run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo tailscale up
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Use &lt;code&gt;nslookup&lt;/code&gt; to review and debug DNS responses:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nslookup google.com
Server:         127.0.0.1
Address:        127.0.0.1#53

Non-authoritative answer:
Name:   google.com
Address: 142.251.40.206
Name:   google.com
Address: 2a00:1450:4001:827::200e
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;127.0.0.1#53&lt;/code&gt; indicate that instead of using the DNS server pushed by
your ISP, router, or Tailscale’s MagicDNS, the system is sending all DNS
requests through the loopback device to &lt;code&gt;dnscrypt-proxy&lt;/code&gt; in my case.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Get the status of your connections to other Tailscale devices:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;tailscale status
1           2         3           4         5
100.1.2.3   device-a  apenwarr@   linux     active; direct &amp;lt;ip-port&amp;gt;, tx 1116 rx 1124
100.4.5.6   device-b  crawshaw@   macOS     active; relay &amp;lt;relay-server&amp;gt;, tx 1351 rx 4262
100.7.8.9   device-c  danderson@  windows   idle; tx 1214 rx 50
100.0.1.2   device-d  ross@       iOS       —
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://tailscale.com/kb/1196/security-hardening&quot;&gt;Tailscale Best Practices&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://tailscale.com/kb/1080/cli&quot;&gt;Tailscale CLI&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;There is much more you can do with Tailscale, including integrating
Mullvad-VPN and using Exit Nodes.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;MAC Randomization&lt;/h2&gt;
&lt;p&gt;All network cards have a unique identifier called a MAC address. They’re stored
in hardware and are used to assign an address to computers on the local network.&lt;/p&gt;
&lt;p&gt;The MAC address is typically only traceable on the local network, it’s not
passively sent out beyond the local router making it more critical on untrusted,
public networks.&lt;/p&gt;
&lt;p&gt;Leak-proof MAC randomization is very difficult to implement:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.kicksecure.com/wiki/Dev/MAC#Leak-proof_MAC_Randomization_-_Technical_Implementation_Challenges&quot;&gt;Leak-proof MAC Randomization Implementation Challenges&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Android and iPhone already implement MAC Randomization by default.&lt;/p&gt;
&lt;p&gt;MAC Randomization enhances privacy by making it harder for third parties to
track users across different networks.&lt;/p&gt;
&lt;p&gt;Randomizing MAC adresses obscures a device’s unique hardware identity when
scanning for or connecting to Wi-Fi, blocking passive tracking as well as
location tracking across networks.&lt;/p&gt;
&lt;p&gt;If you use NetworkManager you can set MAC randomization with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;    networking = {
      networkmanager = {
        enable = true;
        wifi.scanRandMacAddress = true;
        wifi.macAddress = &quot;random&quot;;
        plugins = [];
      };
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Right when I rebuilt, I got an alert from my router saying that a new device
just connected to the network.&lt;/p&gt;
&lt;p&gt;There is also a utility for viewing/manipulating the MAC address of network
interfaces, &lt;code&gt;pkgs.macchanger&lt;/code&gt;. This is less reliable than the NetworkManager
setting.&lt;/p&gt;
&lt;h2&gt;Firewalls&lt;/h2&gt;
&lt;p&gt;NixOS includes an integrated firewall based on iptables/nftables.&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Click to Expand Firewall Resources &lt;/summary&gt;
&lt;p&gt;&lt;a href=&quot;https://www.cloudflare.com/learning/security/what-is-a-firewall/&quot;&gt;Cloudflare What is a Firewall&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://linux-audit.com/networking/nftables/nftables-beginners-guide-to-traffic-filtering/&quot;&gt;Beginners guide to nftables&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://wiki.archlinux.org/title/Nftables&quot;&gt;Arch Wiki nftables&lt;/a&gt;&lt;/p&gt;
&lt;/details&gt;
&lt;p&gt;The following firewall setup is based on the dnscrypt setup above utilizing
nftables.&lt;/p&gt;
&lt;p&gt;This nftables firewall configuration is a strong recommended practice for
enforcing encrypted DNS on your system by restricting all outbound DNS traffic
to a local dnscrypt-proxy process. It greatly reduces DNS leak risks and
enforces privacy by limiting DNS queries to trusted, encrypted upstream
servers.(This was edited on 08-08-25) replace &lt;code&gt;&amp;lt;DNSCRYPT-UID&amp;gt;&lt;/code&gt; with the UID
given from the command &lt;code&gt;ps -o uid,user,pid,cmd -C dnscrypt-proxy&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{ ... }: {
  networking.nftables = {
    enable = true;

    ruleset = &apos;&apos;
      table inet filter {
        chain output {
          type filter hook output priority 0; policy accept;

          # Allow localhost DNS for dnscrypt-proxy2
          ip daddr 127.0.0.1 udp dport 53 accept
          ip6 daddr ::1 udp dport 53 accept
          ip daddr 127.0.0.1 tcp dport 53 accept
          ip6 daddr ::1 tcp dport 53 accept

          # Allow dnscrypt-proxy2 to talk to upstream servers
          # Replace &amp;lt;DNSCRYPT-UID&amp;gt; with:
          # ps -o uid,user,pid,cmd -C dnscrypt-proxy
          meta skuid &amp;lt;DNSCRYPT-UID&amp;gt; udp dport { 443, 853 } accept
          meta skuid &amp;lt;DNSCRYPT-UID&amp;gt; tcp dport { 443, 853 } accept

          # Block all other outbound DNS
          udp dport { 53, 853 } drop
          tcp dport { 53, 853 } drop
        }
      }
    &apos;&apos;;
  };
  networking.firewall = {
    enable = true;
    allowedTCPPorts = [
      # Ports open for inbound connections.
      # Limit these to reduce the attack surface.

      22 # SSH – Keep open only if you need remote access.
         # To change the SSH port in NixOS:
         # services.openssh.ports = [ 2222 ];
         # Update this list to match the new port.

      # 53  # DNS – Only if running a public DNS server.
      # 80  # HTTP – Only if hosting a website.
      # 443 # HTTPS – Only if hosting a secure website.
    ];
    allowedUDPPorts = [
      # Ports open for inbound UDP traffic.
      # Most NixOS workstations won&apos;t need any here.

      # 53 # DNS – Only if running a public DNS server.
    ];
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Click to Expand Tip on changing the default SSH Port &lt;/summary&gt;
&lt;blockquote&gt;
&lt;p&gt;❗ TIP: Reduce SSH noise by changing the default port On most systems, SSH
listens on TCP port 22 — which means automated bots and scanners will hit it
constantly. While this doesn’t replace real security measures, moving SSH to a
different port drastically cuts down on drive-by brute-force attempts you’ll
see in your logs.&lt;/p&gt;
&lt;p&gt;In NixOS, change both the SSH daemon port and your firewall rule:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt; # Example: Move SSH to port 2222
 networking.firewall.allowedTCPPorts = [ 2222 ];
 services.openssh.ports = [ 2222 ];
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;After rebuilding, test from another terminal/session before closing your
existing one:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ssh -p 2222 user@host
&lt;/code&gt;&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;/details&gt;
&lt;p&gt;&lt;code&gt;nft&lt;/code&gt; is a cli tool used to set up, maintain and inspect packet filtering and
classification rules in the Linux kernel, in the nftables framework. The Linux
kernel subsystem is known as nftables, and ‘nf’ stands for Netfilter.–&lt;code&gt;man nft&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo nft list ruleset
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Since we declare our firewall, we’ll only use &lt;code&gt;nft&lt;/code&gt; to inspect our ruleset.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;NixOS Firewall vs &lt;code&gt;nftables&lt;/code&gt; Ruleset&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;networking.nftables&lt;/code&gt;: This section provides a raw &lt;code&gt;nftables&lt;/code&gt; ruleset that gives
you granular, low-level control. The rules here are more specific and are meant
to handle the intricate logic of the DNS proxy setup. They will be applied
directly to the kernel’s &lt;code&gt;nftables&lt;/code&gt; subsystem and prevent DNS leaks.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;networking.firewall&lt;/code&gt;: This is a higher-level, simpler NixOS option that uses
&lt;code&gt;iptables&lt;/code&gt; rules to open ports for inbound traffic. The rules defined here
(allowing port 22) is for incoming SSH connections to the machine, not for
outbound traffic, so they do not interfere with the &lt;code&gt;nftables&lt;/code&gt; rules that filter
the outgoing traffic. (Make sure to comment out or remove this if you don’t SSH
into your machine).&lt;/p&gt;
&lt;p&gt;The firewall ensures only authorized, local encrypted DNS proxy process can
speak DNS with the outside world, and that all other DNS requests from any other
process are blocked unless they’re to &lt;code&gt;127.0.0.1&lt;/code&gt; (our local proxy). This is a
robust policy against both DNS leaks and local compromise.&lt;/p&gt;
&lt;h2&gt;Testing&lt;/h2&gt;
&lt;p&gt;Review listening ports: After each rebuild, use &lt;code&gt;ss -tlpn&lt;/code&gt;, &lt;code&gt;nmap&lt;/code&gt; or &lt;code&gt;netstat&lt;/code&gt;
to see which services are accepting connections. Close or firewall anything
unnecessary.&lt;/p&gt;
&lt;p&gt;You can also test firewall DNS restrictions using &lt;code&gt;dig&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;dig @127.0.0.1 example.com  # Should work

dig @8.8.8.8 example.com    # Should fail/time out for normal users
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;This test is actually what alerted me of an improper configuration in the
above firewalls nftables rules allowing me to fix it. Initially the second
&lt;code&gt;dig&lt;/code&gt; command gave results letting me know that the restrictions weren’t being
applied correctly.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Since we defined an &lt;code&gt;output&lt;/code&gt; chain inside &lt;code&gt;table inet filter&lt;/code&gt; with the line:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;type filter hook output priority 0; policy accept;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This attaches the chain to the kernel’s OUTPUT hook, so all locally generated
packets, including DNS queries are filtered by this chain.&lt;/p&gt;
&lt;p&gt;Within this chain, the rules:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Explicitly allow DNS queries to localhost addresses (&lt;code&gt;127.0.0.1&lt;/code&gt; and &lt;code&gt;::1&lt;/code&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Allow the &lt;code&gt;dnscrypt-proxy&lt;/code&gt; process (running with UID &lt;code&gt;62396&lt;/code&gt;) to send DNS
queries on ports 443 and 853 (for DNS-over-HTTPS and DNS-over-TLS).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Drop all other outbound DNS traffic on ports &lt;code&gt;53&lt;/code&gt; and &lt;code&gt;853&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Because of this setup, dig queries to your local resolver at &lt;code&gt;127.0.0.1&lt;/code&gt; pass,
but queries directly to public DNS servers like &lt;code&gt;8.8.8.8&lt;/code&gt; are blocked for
users/processes other than the allowed DNS proxy.&lt;/p&gt;
&lt;h2&gt;OpenSnitch&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://wiki.nixos.org/wiki/OpenSnitch&quot;&gt;NixOS Wiki OpenSnitch&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/evilsocket/opensnitch&quot;&gt;Opensnitch&lt;/a&gt; is an open-source
application firewall that focuses on monitoring and controlling outgoing network
connections on a per-application basis.&lt;/p&gt;
&lt;p&gt;This can be used to block apps from accessing the internet that shouldn’t need
to (i.e., block telemetry and more). Opensnitch will report that the app has
attempted to make an outbound internet connection and block it or allow it based
on the rules you set.&lt;/p&gt;
&lt;h3&gt;Resources&lt;/h3&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Click to Expand Resources &lt;/summary&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://cloudflare.com/learning/ssl/what-is-https&quot;&gt;Cloudflare What is HTTPS&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://ssd.eff.org/&quot;&gt;Surveillance Self-Defence&lt;/a&gt; has a lot of helpful info to
protect your privacy.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://ssd.eff.org/module/what-fingerprinting&quot;&gt;What is Fingerprinting&lt;/a&gt;, more
than you realize is being tracked constantly.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://oisd.nl/&quot;&gt;oisd.nl&lt;/a&gt; the oisd website&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;For potentially dangerous file types like PDFs, office documents, or images,
especially those downloaded from untrusted sources such as torrents, consider
converting them to a safe PDF format with
&lt;a href=&quot;https://github.com/freedomofpress/dangerzone&quot;&gt;dangerzone&lt;/a&gt;. Dangerzone not
only removes metadata but also applies robust sanitization to neutralize
malicious content.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://wiki.nixos.org/wiki/Librewolf&quot;&gt;NixOS Wiki LibreWolf&lt;/a&gt;, the options in
the wiki make it less secure and aren’t recommended settings to use. They
explicitly disable several of LibreWolf’s default privacy-enhancing features,
such as fingerprinting resistance and clearing session data on shutdown.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://librewolf.net/docs/features/&quot;&gt;LibreWolf Features&lt;/a&gt; You still need to
enable DNS over HTTPS through privacy settings.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://wiki.nixos.org/wiki/SearXNG&quot;&gt;SearXNG on NixOS&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.searxng.org/&quot;&gt;Welcome to SearXNG&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://brainfucksec.github.io/firefox-hardening-guide&quot;&gt;Firefox Hardening Guide&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.ghacks.net/2015/08/18/a-comprehensive-list-of-firefox-privacy-and-security-settings/&quot;&gt;Firefox ghacks&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/arkenfox/user.js&quot;&gt;Arkenfox&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.privacytools.io/private-browser&quot;&gt;PrivacyTools.io&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/simeononsecurity/FireFox-Privacy-Script&quot;&gt;simeononsecurity Firefox-Privacy-Script&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://brainfucksec.github.io/firefox-hardening-guide&quot;&gt;brianfucksec firefox-hardening-Guide 2023&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://simeononsecurity.com/guides/enhance-firefox-security-configuring-guide/&quot;&gt;STIG Firefox Hardening&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;If you should trust the U.S. Governments recommendations is another story but
it can be good to compare and contrast with other trusted resources. You’ll
have to think whether the CISA recommending that everyone uses Signal is solid
advice or guiding you towards a honeypot, I can’t say for sure.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://stigviewer.com/stigs/mozilla_firefox&quot;&gt;Mozilla Firefox Security Technical Implementation Guide&lt;/a&gt;
The STIG for Mozilla Firefox (Security Technical Implementation Guide) is a
set of security configuration standards developed by the U.S. Department of
Defense. They are created by the Defense Information Systems Agency (DISA) to
secure and harden DoD information systems and software.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://thenewoil.org/en/guides/prologue/why/&quot;&gt;Privacy, The New Oil (Why Privacy &amp;amp; Security Matter)&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.privacyguides.org/en/&quot;&gt;PrivacyGuides&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://relay.firefox.com/accounts/profile/&quot;&gt;Firefox Relay&lt;/a&gt; can be used to
create email aliases that forward to your real email address. The paid plan
also lets you create phone number aliases that forward to your phone number.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://zebracrossing.narwhalacademy.org/&quot;&gt;Zebra Crossing digital safety checklist&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://datadetoxkit.org/en/privacy/essentials#step-1&quot;&gt;DataDetoxKit&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://datadetoxkit.org/en/privacy/degooglise/&quot;&gt;DataDetox Degooglise&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://tb-manual.torproject.org/&quot;&gt;Tor Browser User Manual&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://gitlab.torproject.org/tpo/team/-/wikis/home&quot;&gt;Tor Wiki&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://tldp.org/LDP/nag2/x-087-2-intro.html&quot;&gt;Linux Network Administrators Guide&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.ibm.com/think/topics/networking&quot;&gt;IBM Networking&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/details&gt;
</content></entry><entry><title>Browsing Security</title><id>https://saylesss88.github.io/nix/browsing_security.html</id><updated>2025-12-21T00:00:00+00:00</updated><author><name>saylesss87@proton.me (saylesss88)</name></author><link href="https://saylesss88.github.io/nix/browsing_security.html" rel="alternate"/><content type="html">&lt;details&gt;
&lt;summary&gt; ✔️ Table of Contents&lt;/summary&gt;
&lt;!-- toc --&gt;
&lt;/details&gt;
&lt;h1&gt;Browser/Browsing Security: Defense in Depth&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;“The major problem with current systems is their inability to provide
effective isolation between various programs running on one machine. E.g. if
the user’s Web browser gets compromised (due to a bug exploited by a malicious
web site), the OS is usually unable to protect other user’s applications and
data from also being compromised.” –Qubes arch-spec&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The web browser is the most complex, most exposed, and most vulnerable
application on a hardened Linux system. It is your primary interface with the
internet and, consequently, the primary vector for exploitation and tracking.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;Three Pillars of Web Defense&lt;/strong&gt; To secure your browsing, you must balance
three often-conflicting goals:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Security (Exploit Mitigation)&lt;/strong&gt;: Preventing malicious sites from escaping
the browser sandbox to access your local files or execute code.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Privacy (Tracking Protection)&lt;/strong&gt;: Preventing advertisers and sites from
linking your current session to your real-world identity or browsing history.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Anonymity (Identity Obfuscation)&lt;/strong&gt;: Making your traffic indistinguishable
from thousands of other users to hide your physical location and legal
identity.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Methods of Protection&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Browser hardening&lt;/strong&gt; focuses on reducing attack surface and blocking tracking
by disabling or restricting features like JavaScript, cookies, telemetry, and
third-party scripts.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Fingerprint protection&lt;/strong&gt;, on the other hand, aims to make your browser
indistinguishable from others. Instead of just blocking data collection, it
ensures that your browser’s configuration; screen size, fonts, user agent,
etc. matches a large group of users, so you blend in.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Anonymity&lt;/strong&gt;: Maximizing anonymity often means restricting or masking
features (setting a generic fingerprint, disabling browser APIs, blocking
trackers) so the browser blends in with many others. This reduces uniqueness
but can break website functionality, cause CAPTCHAs, and limit usability.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Browser compartmentalization&lt;/strong&gt; is a technique where different browsers are
dedicated to distinct online activities to isolate cookies, trackers, and
browsing data. For example, Mullvad Browser can be used solely for activities
where fingerprinting resistance is critical, such as anonymous browsing or
visiting privacy-sensitive sites. Meanwhile, a hardened LibreWolf or Firefox
can be used for general browsing, email, or banking where you want solid
security and feature flexibility but aren’t as concerned about fingerprint
uniqueness.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;On a hardened Linux system, the browser is most often the weakest link exposed
to the internet, and so security, privacy, and anti-tracking features of
browsers are now as important, or even more important than platform-level
protections.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Browsers leak identity in two main ways: &lt;strong&gt;network identifiers&lt;/strong&gt; (IP address,
DNS, TLS metadata) and &lt;strong&gt;browser identifiers&lt;/strong&gt; (fingerprinting + tracking). This
chapter focuses on the browser side first, then covers when a VPN or Tor changes
the network side. Before tweaking anything, pick the browsing goal (anonymity vs
privacy vs convenience), because the “best” settings differ.&lt;/p&gt;
&lt;p&gt;If the goal is blending in, prefer a browser that ships with a shared,
consistent fingerprint (Tor Browser / Mullvad Browser). If the goal is mainly
reducing cross-site tracking for normal browsing, a hardened Firefox/LibreWolf
profile with minimal extensions is usually easier to live with.&lt;/p&gt;
&lt;h3&gt;Fingerprinting&lt;/h3&gt;
&lt;p&gt;Modern web APIs make rich, customized experiences possible, but they also reveal
enough low‑level details about your device and browser to build a unique
fingerprint. This fingerprint can be used for hidden, persistent tracking, even
when cookies are blocked.&lt;/p&gt;
&lt;p&gt;Browser fingerprinting is a tracking technique, often done by third-party
companies that specialize in it. They provide code (usually JavaScript) that a
website owner can embed on their site. When you visit the site, the script runs
in the background, silently collecting data about your device and browser.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Entropy_(computing)&quot;&gt;Entropy&lt;/a&gt;: in this
context, is a measure of how much unique information a specific browser
feature contributes to your fingerprint. It’s often quantified in &lt;strong&gt;bits of
entropy&lt;/strong&gt;, where higher bits mean more uniqueness (i.e., easier to identify
you).
&lt;ul&gt;
&lt;li&gt;A “bit” is a basic unit of information for computers. Entropy measuring
sites results are measured in “bits of identifying information”.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There are two main approaches to obfuscating your fingerprint:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Standardization&lt;/strong&gt;: Make browsers standardized and therefore have the same
fingerprint to blend into a crowd. This is what Tor and Mullvad Browser do.
Best for anonymity; increases the crowd you blend into, but may decrease
usability (site breakage, CAPTCHAs); adversaries may still find subtle
differences.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Randomization&lt;/strong&gt;: Randomize fingerprint metrics so it’s not directly linkable
to you. Brave has this feature, if you run coveryourtracks with Brave you will
get a result of “your browser has a randomized fingerprint”. This is good for
privacy but may be detectable by advanced scripts.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Fingerprint Testing&lt;/h3&gt;
&lt;p&gt;You can test your browser to see how well you are protected from tracking and
fingerprinting at &lt;a href=&quot;https://coveryourtracks.eff.org/&quot;&gt;Cover Your Tracks&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Also check out, &lt;a href=&quot;https://amiunique.org/fingerprint&quot;&gt;Am I Unique&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;⚠️ WARNING: Don’t put too much weight into the results as people often check
their fingerprint, change one metric and check it again over and over skewing
the results. It is helpful for knowing the fingerprint values that trackers
track.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://forum.torproject.org/t/browser-fingerprinting/1228/25&quot;&gt;Browser Fingerprinting Tor Forum&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://madaidans-insecurities.github.io/browser-tracking.html&quot;&gt;Madaidans Hot Take on Browser Tracking&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h3&gt;Browsers&lt;/h3&gt;
&lt;p&gt;I currently run NixOS in a VM with a secureblue host,
&lt;a href=&quot;https://github.com/secureblue/Trivalent&quot;&gt;Trivalent&lt;/a&gt; is my default browser, a
security-focused, Chromium-based browser.&lt;/p&gt;
&lt;p&gt;In this section, the goal is to outline several browser options on NixOS and
show concrete configurations (with a focus on LibreWolf that can be adapted to
Firefox). Browsers are complicated and people have different security, privacy,
and usability needs, so this is not an endorsement of a single “best” choice.
Instead, the following subsections describe trade-offs and example setups so you
can decide which browser and configuration best match your own threat model and
workflow.&lt;/p&gt;
&lt;p&gt;IMO there aren’t many Chromium-based browsers on NixOS that hit the sweet spot
of being both security-forward and privacy-respecting. Many of the “privacy
browser” options you’ll see on NixOS are Firefox forks; with the right tweaks
they can be made pretty private, but they generally don’t match Chromium-family
browsers on exploit mitigations and sandbox depth. ​&lt;/p&gt;
&lt;h4&gt;Brave&lt;/h4&gt;
&lt;p&gt;Brave is basically “Chromium, but with privacy features turned on by default,”
and the big win is that most of the protection comes from the browser itself
(Shields + anti-fingerprinting) instead of from a pile of extensions. Brave’s
fingerprinting story is also unusually practical: instead of trying to make
every Brave user look identical, it mixes API blocking with per-site/per-session
randomization (“farbling”) so the fingerprint is harder to reuse across
contexts.&lt;/p&gt;
&lt;p&gt;On NixOS, IMO the best available Chromium-based browser is Brave. Brave strips
out Google’s tracking code and includes a native add/tracker blocker (Brave
Shields). It also ships a best‑effort anti‑fingerprinting system that combines
(1) blocking/removing/modifying certain high-signal APIs and (2) “privacy
through randomization” (farbling), where it returns slightly altered values so
fingerprints don’t stay stable across sites/sessions. ​&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Things to Note about Brave&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Shields is a bundle of controls (trackers/ads, cookies, fingerprinting
defenses, HTTPS upgrades, referrer/query stripping, storage cleanup), so
turning Shields off for a broken site is a big privacy downgrade for that
site—not just “adblock off.” ​&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Brave explicitly recommends validating fingerprinting defenses with realistic
tests (new private window, restart browser, different profile, clear site
storage) and expects the fingerprint to change across those boundaries. ​&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Extension bloat will break Brave’s privacy and security story: each extension
increases attack surface and usually has broad privileges, while Brave’s core
pitch is that you can get most of the privacy wins without third-party code in
your browser process&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Drawbacks worth mentioning&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Brave is still Chromium/Blink, so using it doesn’t help with engine diversity;
if Gecko dies, the web becomes even more “whatever Chromium implements,” which
is a long-term ecosystem risk.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Brave ships a lot (Rewards, Wallet, VPN upsells, Leo/AI features depending on
build/channel), and every extra subsystem is more UI complexity and
potentially more bugs/attack surface than some browsers.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Brave’s model assumes the browser does most privacy work; piling on extensions
increases privileged code, fingerprint uniqueness, and the chance of a
malicious/compromised extension.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Farbling and API defenses reduce stability/linkability, but sophisticated
trackers can still correlate behavior, logins, IP ranges, and high-level
patterns—so “I enabled anti-fingerprinting” shouldn’t be read as “I can’t be
tracked.”&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Brave’s built-in ad system (Rewards) is opt-in, but Brave also promotes
features like Rewards and can show sponsored content (e.g., sponsored new-tab
images) unless you disable/hide it&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Firefox&lt;/h4&gt;
&lt;p&gt;Firefox is kind of its own thing: it runs on Gecko, not on Chromium or WebKit,
so it’s one of the only mainstream browsers that isn’t just another Chrome fork.
That uniqueness matters if you care about engine diversity and not having the
entire web effectively dictated by a single vendor. It’s also very tweakable, so
if you’re willing to flip some prefs and add a couple of key extensions, you can
turn it into a solid privacy‑focused daily driver without giving up a
non‑Chromium stack.&lt;/p&gt;
&lt;p&gt;Firefox will usually get security fixes sooner than any fork, and some forks lag
behind on patching, which can leave known vulnerabilities exploitable for
longer. If you use Firefox’s built‑in Enhanced Tracking Protection (ETP), Resist
Fingerprinting (RFP), and hardening templates like ghacks or Arkenfox together
with uBlock Origin configured for dynamic filtering, you can replicate what used
to require a pile of separate extensions.&lt;/p&gt;
&lt;h4&gt;Site Isolation &amp;amp; Firefox Links&lt;/h4&gt;
&lt;p&gt;Firefox does implement Site Isolation via
&lt;a href=&quot;https://wiki.mozilla.org/Project_Fission&quot;&gt;Project Fission&lt;/a&gt;, but it’s newer and
historically less mature than Chromium’s site‑per‑process model, and it may not
be enabled everywhere by default. To check that it is active, go to
&lt;code&gt;about:config&lt;/code&gt; and ensure both &lt;code&gt;fission.autostart&lt;/code&gt; and &lt;code&gt;gfx.webrender.all&lt;/code&gt; are
set to &lt;code&gt;true&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;With uBlock Origin you can disable JavaScript per‑site (similar to NoScript),
enable a bunch of high‑quality blocklists, and selectively relax rules when a
trusted site breaks. To turn on Enhanced Tracking Protection and fingerprinting
protections in the UI, go to &lt;code&gt;Settings -&amp;gt; Privacy &amp;amp; Security&lt;/code&gt; -&amp;gt;
&lt;code&gt;Enhanced Tracking Protection -&amp;gt; Custom&lt;/code&gt;; if that causes breakage on a
particular website, click the shield icon in the URL bar and disable protections
just for that site.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/gorhill/uBlock/wiki&quot;&gt;uBlock Wiki&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once you select &lt;code&gt;Custom&lt;/code&gt;, you’ll see that among the options is to block
&lt;code&gt;Known fingerprinters&lt;/code&gt; as well as &lt;code&gt;Suspected fingerprinters&lt;/code&gt;. The “Known
Fingerprinters” protection works by blocking scripts listed in
&lt;a href=&quot;https://disconnect.me/trackerprotection#categories_of_trackers&quot;&gt;Disconnect’s fingerprinting list&lt;/a&gt;
For most users they suggest using the above FPP to avoid breakage. To go further
and enable RFP, go to &lt;code&gt;about:config&lt;/code&gt; and set &lt;code&gt;privacy.resistFingerprinting&lt;/code&gt; to
&lt;code&gt;true&lt;/code&gt;.&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Further Reading on Firefox Defenses &lt;/summary&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://support.mozilla.org/en-US/kb/resist-fingerprinting&quot;&gt;Mozilla Resist Fingerprinting&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;To ensure Site Isolation is enabled, in &lt;code&gt;about:config&lt;/code&gt;, set
&lt;code&gt;fission.autostart&lt;/code&gt;, and &lt;code&gt;gfx.webrender.all&lt;/code&gt; prefs to &lt;code&gt;true&lt;/code&gt;.(It’s disabled
by default on android).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Entropy_(computing)&quot;&gt;Entropy&lt;/a&gt;: in this
context, is a measure of how much unique information a specific browser
feature contributes to your fingerprint. It’s often quantified in &lt;strong&gt;bits of
entropy&lt;/strong&gt;, where higher bits mean more uniqueness (i.e., easier to identify
you).&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A “bit” is a basic unit of information for computers. Entropy measuring
sites results are measured in “bits of identifying information”.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Glossary/Origin&quot;&gt;Origin&lt;/a&gt;: Web
content’s &lt;em&gt;origin&lt;/em&gt; is defined by the &lt;em&gt;scheme&lt;/em&gt; (protocol), &lt;em&gt;hostname&lt;/em&gt; (domain),
and port of the URL used to access it. Two objects have the same origin only
when the scheme, hostname, and port all match.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy&quot;&gt;Same-origin policy&lt;/a&gt;:
is a critical security mechanism that restricts how a document or script
loaded by one origin can interact with a resource from another origin. It
helps isolate potentially malicious documents, reducing possible attack
vectors.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://blog.mozilla.org/security/2021/05/18/introducing-site-isolation-in-firefox/&quot;&gt;Firefox Site-Isolation&lt;/a&gt;.
Firefox does provide site-isolation as well.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.mozilla.org/en-US/security/advisories/mfsa2018-01/&quot;&gt;Protection from side-channel attacks&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/Security/Insecure_passwords&quot;&gt;MDN Insecure passwords&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.mozilla.org/tanvi/2016/01/28/no-more-passwords-over-http-please/&quot;&gt;Risks of reused passwords&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/details&gt;
&lt;hr /&gt;
&lt;h2&gt;LibreWolf&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;LibreWolf&lt;/strong&gt; is an open-source fork of Firefox with a strong focus on privacy,
security, and user freedom. LibreWolf enables always HTTPS, includes
uBlockOrigin, and only includes privacy focused search engines by default.&lt;/p&gt;
&lt;p&gt;Example LibreWolf config implementing many of the STIG recommendations:&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Click to expand LibreWolf Example &lt;/summary&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# librewolf.nix
{pkgs, lib, config, ...}: let
  cfg = config.custom.librewolf;
in {
  options.custom.librewolf = {
    enable = lib.mkOption {
      type = lib.types.bool;
      default = true;
      description = &quot;Enable the LibreWolf Module&quot;;
    };
  };

  config = lib.mkIf cfg.enable {
    programs.librewolf = {
      enable = true;
      policies = {
        # A bit annoying
        DontCheckDefaultBrowser = true;
        # Pocket is insecure according to DoD
        DisablePocket = true;
        # No imperative updates
        DisableAppUpdate = true;
      };
      settings = {
        # // SV-16925 - DTBF030
        &quot;security.enable_tls&quot; = true;
        # // SV-16925 - DTBF030
        &quot;security.tls.version.min&quot; = 2;
        # // SV-16925 - DTBF030
        &quot;security.tls.version.max&quot; = 4;

        # // SV-111841 - DTBF210
        &quot;privacy.trackingprotection.fingerprinting.enabled&quot; = true;

        # // V-252881 - Retaining Data Upon Shutdown
        &quot;browser.sessionstore.privacy_level&quot; = 0;

        # // SV-251573 - Customizing the New Tab Page
        &quot;browser.newtabpage.activity-stream.enabled&quot; = false;
        &quot;browser.newtabpage.activity-stream.feeds.section.topstories&quot; = false;
        &quot;browser.newtabpage.activity-stream.showSponsored&quot; = false;
        &quot;browser.newtabpage.activity-stream.feeds.snippets&quot; = false;

        # // V-251580 - Disabling Feedback Reporting
        &quot;browser.chrome.toolbar_tips&quot; = false;
        &quot;browser.selfsupport.url&quot; = &quot;&quot;;
        &quot;extensions.abuseReport.enabled&quot; = false;
        &quot;extensions.abuseReport.url&quot; = &quot;&quot;;

        # // V-251558 - Controlling Data Submission
        &quot;datareporting.policy.dataSubmissionEnabled&quot; = false;
        &quot;datareporting.healthreport.uploadEnabled&quot; = false;
        &quot;datareporting.policy.firstRunURL&quot; = &quot;&quot;;
        &quot;datareporting.policy.notifications.firstRunURL&quot; = &quot;&quot;;
        &quot;datareporting.policy.requiredURL&quot; = &quot;&quot;;

        # // V-252909 - Disabling Firefox Studies
        &quot;app.shield.optoutstudies.enabled&quot; = false;
        &quot;app.normandy.enabled&quot; = false;
        &quot;app.normandy.api_url&quot; = &quot;&quot;;

        # // V-252908 - Disabling Pocket
        &quot;extensions.pocket.enabled&quot; = false;

        # // V-251555 - Preventing Improper Script Execution
        &quot;dom.disable_window_flip&quot; = true;

        # // V-251554 - Restricting Window Movement and Resizing
        &quot;dom.disable_window_move_resize&quot; = true;

        # // V-251551 - Disabling Form Fill Assistance
        &quot;browser.formfill.enable&quot; = false;

        # // V-251550 - Blocking Unauthorized MIME Types
        &quot;plugin.disable_full_page_plugin_for_types&quot; = &quot;application/pdf,application/fdf,application/xfdf,application/lso,application/lss,application/iqy,application/rqy,application/lsl,application/xlk,application/xls,application/xlt,application/pot,application/pps,application/ppt,application/dos,application/dot,application/wks,application/bat,application/ps,application/eps,application/wch,application/wcm,application/wb1,application/wb3,application/rtf,application/doc,application/mdb,application/mde,application/wbk,application/ad,application/adp&quot;;
      };
    };
    xdg.desktopEntries.librewolf = {
      name = &quot;LibreWolf&quot;;
      exec = &quot;${pkgs.librewolf}/bin/librewolf&quot;;
    };
    xdg.mimeApps = {
      enable = true;
      defaultApplications = {
        &quot;text/html&quot; = &quot;librewolf.desktop&quot;;
        &quot;x-scheme-handler/http&quot; = &quot;librewolf.desktop&quot;;
        &quot;x-scheme-handler/https&quot; = &quot;librewolf.desktop&quot;;
        &quot;x-scheme-handler/about&quot; = &quot;librewolf.desktop&quot;;
        &quot;x-scheme-handler/unknown&quot; = &quot;librewolf.desktop&quot;;
      };
    };
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And enable it in your &lt;code&gt;home.nix&lt;/code&gt; or equivalent with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# home.nix
custom.librewolf.enable = true;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;xdg&lt;/code&gt; settings at the end make LibreWolf the defaults for what is listed.&lt;/p&gt;
&lt;p&gt;Thanks to &lt;code&gt;JosefKatic&lt;/code&gt; for putting the above STIG settings in NixOS format.&lt;/p&gt;
&lt;p&gt;Also, go to
&lt;a href=&quot;https://accounts.firefox.com/settings#data-collection&quot;&gt;accounts.firefox&lt;/a&gt; and
turn off “Allow Mozilla accounts to send technical and interaction data to
Mozilla”. Also set 2-fa in
&lt;a href=&quot;https://accounts.firefox.com/settings#security&quot;&gt;Security Settings&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I always set &lt;code&gt;Max Protection&lt;/code&gt; for DNS over HTTPS and personally set a custom
resolver to &lt;code&gt;https://dns.quad9.net/dns-query&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Mullvad is also a good option:
&lt;a href=&quot;https://mullvad.net/en/help/no-logging-data-policy&quot;&gt;Mullvad no-logging-data-policy&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Alternative LibreWolf Configuration utilizing Arkenfox &lt;/summary&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{
  pkgs,
  lib,
  config,
  ...
}: let
  cfg = config.custom.librewolf;
in {
  options.custom.librewolf = {
    enable = lib.mkOption {
      type = lib.types.bool;
      default = true;
      description = &quot;Enable the LibreWolf Module&quot;;
    };
  };

  config = lib.mkIf cfg.enable {
    programs.librewolf = {
      enable = true;
      policies = {
        DontCheckDefaultBrowser = true;
        DisablePocket = true;
        DisableAppUpdate = true;
      };
      profiles.my-default = {
        isDefault = true;
        name = &quot;Default Profile&quot;;
        extraConfig = &apos;&apos;
          ${builtins.readFile ./user.js}
          &quot;general.autoScroll&quot; = true;
          &quot;sidebar.verticalTabs&quot; = true;
        &apos;&apos;;

        settings = {
        };
      };
    };
    xdg.desktopEntries.librewolf = {
      name = &quot;LibreWolf&quot;;
      exec = &quot;${pkgs.librewolf}/bin/librewolf&quot;;
    };
    xdg.mimeApps = {
      enable = true;
      defaultApplications = {
        &quot;text/html&quot; = &quot;librewolf.desktop&quot;;
        &quot;x-scheme-handler/http&quot; = &quot;librewolf.desktop&quot;;
        &quot;x-scheme-handler/https&quot; = &quot;librewolf.desktop&quot;;
        &quot;x-scheme-handler/about&quot; = &quot;librewolf.desktop&quot;;
        &quot;x-scheme-handler/unknown&quot; = &quot;librewolf.desktop&quot;;
      };
    };
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Download the
&lt;a href=&quot;https://github.com/arkenfox/user.js/blob/master/user.js&quot;&gt;Arkenfox user.js&lt;/a&gt; and
review it making sure that you agree with the settings. If you do, place it in
the same directory as your &lt;code&gt;librewolf.nix&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Read the &lt;a href=&quot;https://github.com/arkenfox/user.js/wiki&quot;&gt;Arkenfox Wiki&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;user.js&lt;/code&gt; is full of comments and information, read it and adjust it for
your needs. The following enables RFP fingerprint protection:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;***/ user.js ***/
user_pref(&quot;privacy.resistFingerprinting&quot;, true); // [FF41+]
user_pref(&quot;privacy.resistFingerprinting.pbmode&quot;, true); // [FF114+]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you learn more, you can get more strict if you so choose.&lt;/p&gt;
&lt;p&gt;Rebuild, launch LibreWolf, and check your &lt;code&gt;~/.librewolf/my-default/user.js&lt;/code&gt;. It
should match the Arkenfox settings. Initially, only the &lt;code&gt;user.js&lt;/code&gt; will be
listed, as you run LibreWolf other profile files and folders are created
dynamically.&lt;/p&gt;
&lt;p&gt;In LibreWolf type &lt;code&gt;Ctrl + Shift + J&lt;/code&gt; and look for any errors.&lt;/p&gt;
&lt;p&gt;Type &lt;code&gt;about:config&lt;/code&gt; into the address bar and search a few of the settings that
Arkenfox changes, do they match?&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;user.js&lt;/code&gt; is read &lt;strong&gt;in order&lt;/strong&gt;, if there are 2 of the same setting, the last
one will be applied. Adding overrides to the settings attribute above places the
changes at the &lt;strong&gt;beginning&lt;/strong&gt; of the &lt;code&gt;user.js&lt;/code&gt; which isn’t what we want. Placing
them after the &lt;code&gt;${builtins.readFile ./user.js}&lt;/code&gt; in &lt;code&gt;extraConfig&lt;/code&gt; amends them to
the &lt;strong&gt;end&lt;/strong&gt; of the &lt;code&gt;user.js&lt;/code&gt; allowing us to override the defaults.&lt;/p&gt;
&lt;p&gt;The process is the same with Firefox but since Arkenfox strongly recommends
Ublock Origin and it is built into LibreWolf it makes sense to use the browser
with the stronger defaults.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;❗ NOTE: There is a home-manager module called &lt;code&gt;arkenfox-nixos&lt;/code&gt; that is
supposed to make updates easier but IMO the documentation leaves you guessing
how to use it. As updates come in to Firefox/LibreWolf some of the settings
become unnecessary so it’s important to keep an eye on both Firefox and
Arkenfox updates. Which both have RSS feeds that will alert you upon changes.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I personally use &lt;a href=&quot;https://feeder.co/&quot;&gt;Feeder&lt;/a&gt; as my open-source RSS feed reader,
available in most app stores including F-Droid. It is listed on
&lt;a href=&quot;https://www.privacytools.io/privacy-rss-feed-readers&quot;&gt;PrivacyTools&lt;/a&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/arkenfox/user.js/commits/master.atom&quot;&gt;Arkenfox Recent Commits RSS feed&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/arkenfox/user.js/releases.atom&quot;&gt;Arkenfox Release Notes RSS&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.mozilla.org/en-US/firefox/nightly/notes/feed/&quot;&gt;Firefox Nightly release notes&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/details&gt;
&lt;h4&gt;Search Defaults&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;Startpage&lt;/strong&gt;: Advertised as the world’s most private search engine. “Startpage
delivers Google search results via their proprietary personal data protection
technology.”&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.startpage.com/&quot;&gt;Startpage&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;To add Startpage as a search engine, add
&lt;code&gt;https://www.startpage.com/sp/search?query=%s&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;SearXNG&lt;/strong&gt; an open-source, privacy-respecting metasearch engine that aggregates
results from various search services, such as Google, DuckDuckGo, etc. without
tracking you or profiling your searches. You can add SearXNG to firefox by going
to &lt;code&gt;about:preferences#search&lt;/code&gt; and at the bottom click &lt;code&gt;Add&lt;/code&gt;, URL will be
&lt;code&gt;https://searx.be/search?q=%s&lt;/code&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;❗️ NOTE: SearXNGs google results are not working as of 11-17-25 and haven’t
for a while now leading to bad results being returned for most instances. It’s
my understanding this is because Google is actively blocking automated
requests from SearXNG. Devs sometimes publish patches or workarounds, but
these are quickly blocked when Google changes their back-end.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;❗️ NOTE: The above searx is the default and doesn’t give many relevant
results. To get relevant results find a
&lt;a href=&quot;https://searx.space/&quot;&gt;public instance&lt;/a&gt; with a good rating from your area and
add the &lt;code&gt;search?q=%s&lt;/code&gt; to the end of it. For example, I’m using
&lt;code&gt;https://priv.au/search?q=%s&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Searx is a bit different, you can choose which search engine you want for your
current search with &lt;code&gt;!ddg search term&lt;/code&gt; to use duckduckgo for example.&lt;/p&gt;
&lt;/details&gt;
&lt;h4&gt;Tor Browser&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;❗ NOTE: Tor is &lt;strong&gt;not&lt;/strong&gt; the most secure browser, anonymity and security can
often be at odds with each other. Having the exact same browser as many other
people isn’t the best security practice, but it is great for anonymity. Tor is
also based on Firefox Esr, which only receives patches for vulnerabilities
considered Critical or High which can be taken advantage of.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Tor is a modified version of Firefox specifically designed for use with Tor.&lt;/p&gt;
&lt;p&gt;Tor routes your internet traffic through a global volunteer-operated network,
masking your IP address and activities from local observers, ISPs, websites, and
surveillance systems. This helps you protect personal information and maintain
anonymity when browsing, communicating, or using online services.&lt;/p&gt;
&lt;p&gt;Adding browser plugins to Tor can de-anonymize you, don’t do it. Tor is already
built with the necessary plugins and privacy protecting rules, so adding more is
unnecessary and actually dangerous for your anonymity.&lt;/p&gt;
&lt;p&gt;A Tor exit node can easily see your traffic, and if you’re not using HTTPS then
it may be able to modify that traffic. Only use HTTPS when browsing the clear
net with Tor, this doesn’t apply to onion services (&lt;code&gt;.onion&lt;/code&gt;) as the traffic
stays inside the Tor network all the way to the destination.&lt;/p&gt;
&lt;p&gt;You can visit both the clear web and &lt;code&gt;.onion&lt;/code&gt; sites on Tor. Whenever possible
you should utilize Onion Services (&lt;code&gt;.onion&lt;/code&gt; addresses) so communications and web
browsing stay within the Tor network. &lt;code&gt;.onion&lt;/code&gt; URLS form a tunnel that is
end-to-end encrypted using a random rendezvous point and incorporating
&lt;a href=&quot;https://en.wikipedia.org/wiki/Forward_secrecy&quot;&gt;perfect forward secrecy (PFS)&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Bridges are only necessary in countries that don’t allow people to use Tor.
Using Bridges when they aren’t needed takes resources away from people in
oppressive regimes that need, only use them if necessary. Read the guides, and
use Tails OS, or Whonix when it really matters.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://saylesss88.github.io/nix/whonix_kvm.html&quot;&gt;Whonix KVM on NixOS&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You will see a lot of conflicting information about using Tor with a VPN. If you
are in an area that blocks access to Tor or it is dangerous to use Tor, by all
means use a trusted VPN.&lt;/p&gt;
&lt;h3&gt;TorPlusVPN&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://gitlab.torproject.org/legacy/trac/-/wikis/doc/TorPlusVPN&quot;&gt;Tor Project Wiki TorPlusVPN&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.privacyguides.org/en/advanced/tor-overview/#safely-connecting-to-tor&quot;&gt;Safely Connecting to Tor&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Learn about Tor&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I recommend starting with
&lt;a href=&quot;https://www.privacyguides.org/articles/2025/04/30/in-praise-of-tor/#onion-sites-you-can-visit-using-the-tor-browser&quot;&gt;Privacy Guides In Praise of Tor&lt;/a&gt;
and then reading their
&lt;a href=&quot;https://www.privacyguides.org/en/advanced/tor-overview/&quot;&gt;Tor Overview&lt;/a&gt; they
have been the most informative resources I’ve come across yet.&lt;/p&gt;
&lt;p&gt;The Electronic Frontier Foundation sponsors and helps fund Tor and so does the
United States Government.&lt;/p&gt;
&lt;p&gt;If you are fortunate to live outside of oppressive regimes with extreme
censorship, using Tor for every day, mundane activities is likely safe and won’t
put you on any harmful “list.” Even if it did, you’d be in good company—these
lists mostly contain great people working tirelessly to defend human rights and
online privacy worldwide.&lt;/p&gt;
&lt;p&gt;By using Tor regularly for ordinary browsing, you help strengthen the network,
making it more robust and anonymous for everyone. This collective support makes
staying private easier for activists, journalists, and anyone facing online
surveillance or censorship. The writer of the PrivacyGuides article mentions
using Tor when he needs to access Google Maps to protect his privacy&lt;/p&gt;
&lt;p&gt;So, consider embracing Tor not only for sensitive browsing but also for daily
routine tasks. Every user adds valuable noise to the network, helping protect
privacy and freedom for all.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tor is at risk, and needs our help&lt;/strong&gt;. Despite its strength and history, Tor
isn’t safe from the same attacks oppressive regimes and misinformed legislators
direct at encryption and many other privacy-enhancing
technologies.–&lt;a href=&quot;https://www.privacyguides.org/articles/2025/04/30/in-praise-of-tor/#how-to-support-tor&quot;&gt;How to Support Tor&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://wiki.nixos.org/wiki/Tor&quot;&gt;Tor on NixOS&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://tb-manual.torproject.org/&quot;&gt;Tor Browser User Manual&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://support.torproject.org/faq/staying-anonymous/&quot;&gt;Tor staying-anonymous&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://ssd.eff.org/module/how-to-use-tor&quot;&gt;How to Use Tor&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://torproject.github.io/manual/secure-connections/&quot;&gt;Cool Graphic Showing Secure Connections with Tor&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h4&gt;Mullvad-Browser&lt;/h4&gt;
&lt;p&gt;Rather than try to tweak a browser into fingerprinting submission, I recommend
using either Tor or Mullvad-Browser when fingerprintability is the highest
issue. Both Tor and Mullvad-Browser were designed specifically for this purpose
and you likely won’t get as much out of tweaking another browser.&lt;/p&gt;
&lt;p&gt;Mullvad-Browser is free and open-source and was developed by the Tor Project in
collaboration with Mullvad VPN.(Another Firefox Derivative). It is also the top
recommended browser from PrivacyGuides.&lt;/p&gt;
&lt;p&gt;It is the Tor Browser without the Tor Network, allowing you to use the privacy
features Tor created along with a VPN if you so choose.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://mullvad.net/en/browser&quot;&gt;Mullvad-Browser&lt;/a&gt;, is in Nixpkgs as:
&lt;code&gt;pkgs.mullvad-browser&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;Making Your Browser Amnesic, the Nix Way&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Problem&lt;/strong&gt;: Browsers leak data via &lt;code&gt;.cache&lt;/code&gt; and &lt;code&gt;.config&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pro Tip&lt;/strong&gt;: Even if you use a persistent home directory, you should mount your
&lt;code&gt;~/.cache&lt;/code&gt; folder to &lt;code&gt;tmpfs&lt;/code&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Performance&lt;/strong&gt;: Browsers perform thousands of small read/writes to the cache.
In-memory storage is significantly faster.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Disk Health&lt;/strong&gt;: It prevents “SSD wear” from constant caching of temporary web
assets.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Forensic Hygiene&lt;/strong&gt;: It ensures that volatile “junk” like images, scripts,
and stylesheets never touches your physical platter.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: This will &lt;strong&gt;not&lt;/strong&gt; wipe your browser session (tabs, cookies,
history), as those are stored in &lt;code&gt;~/.config&lt;/code&gt;. If you want a truly “amnesic”
browsing session that wipes everything on reboot, you must also mount your
browser’s profile directory (e.g., &lt;code&gt;~/.mozilla&lt;/code&gt; or &lt;code&gt;~/.config/BraveSoftware&lt;/code&gt;)
to &lt;code&gt;tmpfs&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;fileSystems.&quot;/home/youruser/.cache&quot; = {
  device = &quot;none&quot;;
  fsType = &quot;tmpfs&quot;;
  options = [ &quot;size=4G&quot; &quot;mode=777&quot; ];
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ensure it was applied with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;findmnt /home/youruser/.cache
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can apply the same &lt;code&gt;tmpfs&lt;/code&gt; logic to your config folder. &lt;strong&gt;Warning&lt;/strong&gt;: This
will wipe your settings, extensions, and history every reboot.&lt;/p&gt;
&lt;p&gt;Manual &lt;code&gt;tmpfs&lt;/code&gt; mounts in &lt;code&gt;configuration.nix&lt;/code&gt; are powerful because they happen at
the system level, but they require explicit ownership (&lt;code&gt;uid&lt;/code&gt;/&lt;code&gt;gid&lt;/code&gt;) to work with
user-level applications.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;fileSystems.&quot;/home/youruser/.config/BraveSoftware&quot; = {
  device = &quot;none&quot;;
  fsType = &quot;tmpfs&quot;;
  options = [
  &quot;size=4G&quot;
  &quot;mode=700&quot; # Use 700 for privacy
  &quot;noatime&quot;
  # Replace `1000` with the output of `id -u`
  &quot;uid=1000&quot;
  # Replace `100` with output of `id -g`
  &quot;gid=100&quot;
  ];
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;How to find your UID/GID&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;id -u &amp;amp;&amp;amp; id -g
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Replace the above &lt;code&gt;uid=&lt;/code&gt;, and &lt;code&gt;gid=&lt;/code&gt; values with the output of the above
command.&lt;/p&gt;
&lt;hr /&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Example Script to wipe cache and generate new `machine-id` &lt;/summary&gt;
&lt;p&gt;If you followed the above “Nix way” of Amnesic cache, the following script is
unnecessary, I’m leaving it here for now for those that are interested in
changing their machine-id imperatively.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.man7.org/linux/man-pages/man5/machine-id.5.html&quot;&gt;man page machine-id(5)&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The following example is adapted from
&lt;a href=&quot;https://firejail.wordpress.com/all-about-tor/&quot;&gt;Firejail All About Tor&lt;/a&gt;
section, adapted for NixOS.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Save the following script as &lt;code&gt;cleanup.sh&lt;/code&gt;, change &lt;code&gt;Your-User&lt;/code&gt; to your username:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#!/bin/sh -e
USER=&quot;Your-User&quot;
HOME_DIR=&quot;/home/$USER&quot;
# clear user cache directly as root
sudo -u &quot;$USER&quot; rm -fr &quot;$HOME_DIR/.cache&quot;
# generate a new machine-id
rm -f /var/lib/machine-id
dbus-uuidgen &amp;gt; /var/lib/machine-id
cp /var/lib/machine-id /etc/machine-id
chmod 444 /etc/machine-id
exit 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;~/.cache&lt;/code&gt; directory is where most programs store runtime information:
webpages you visited, torrent trackers you connected to, and deleted emails.
It’s a good idea to remove them at shutdown. –Firejail all-about-tor&lt;/p&gt;
&lt;p&gt;Check &lt;code&gt;/etc/machine-id&lt;/code&gt; &amp;amp; &lt;code&gt;~/.cache&lt;/code&gt; before running the script:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cat /etc/machine-id
# Output
0b46feb27a20469da0ee62baaeb51c5c
ls ~/.cache
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;chmod +x cleanup.sh
sudo ./cleanup.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Recheck your &lt;code&gt;machine-id&lt;/code&gt; and &lt;code&gt;~/.cache&lt;/code&gt; directories, you should have a newly
generated &lt;code&gt;machine-id&lt;/code&gt; and minimal files in the &lt;code&gt;~/.cache&lt;/code&gt; directory. The
Firejail example shows a systemd unit that runs the above script at every
shutdown but that may be overkill, I suggest running it occasionally to make it
harder for sites to link your &lt;code&gt;machine-id&lt;/code&gt; to you.&lt;/p&gt;
&lt;/details&gt;
&lt;p&gt;Privacy protection doesn’t need to be perfect to make a difference. The best
protection against tracking and fingerprinting available is to use Tor. Many
add-ons are redundant, do some research and avoid using an add-on for something
that can be accomplished with built-in settings.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://ssd.eff.org/module/how-to-use-tor&quot;&gt;Surveillance Self-Defense How to: Use Tor&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There are more hardening parameters that can be set but this should be a good
starting point for a hardened version of LibreWolf. When testing with Cover your
tracks, customized LibreWolf tested as having stronger tracking protection than
default Mullvad-Browser and NoScript significantly cuts down the data available
for fingerprinting by disabling JavaScript.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;a href=&quot;https://wiki.garudalinux.org/en/privacy-guide&quot;&gt;Garuda Privacy-Guide&lt;/a&gt; has
good tips and recommendations for browser add-ons.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h3&gt;Virtual Private Networks (VPNs)&lt;/h3&gt;
&lt;p&gt;A &lt;strong&gt;VPN&lt;/strong&gt; (Virtual Private Network) encrypts your Internet connection and routes
your traffic through a VPN provider’s servers, masking your IP address from
local network observers, ISPs, and websites. Using a VPN can prevent your ISP or
local Wi-Fi owner from tracking what sites you visit (they only see a connection
to the VPN), and can help circumvent some regional restrictions or filtering.&lt;/p&gt;
&lt;p&gt;However, VPNs simply shift your trust: Instead of your ISP seeing your activity,
your VPN provider can, so you must trust their privacy policies and
infrastructure. Quality and privacy protections vary widely from one VPN company
to another.&lt;/p&gt;
&lt;p&gt;I see over and over again that Mullvad VPN is the best, I am in no way
affiliated with them this is just what I hear. They allow you to pay with cash
completely anonymously and keep very minimal metadata. Metadata is a big deal,
the US gov has admitted to killing people based solely on their metadata.&lt;/p&gt;
&lt;p&gt;Your ISP almost certainly does sketchy stuff with your data, personally I would
rather trust a company like Mullvad whose whole reputation is based on their
trustworthiness, transparency, and data protection.&lt;/p&gt;
&lt;p&gt;You can use a VPN with Tor, but it’s not recommended by the Tor Project unless
you’re an advanced user who knows how to configure both in a way that doesn’t
compromise your privacy.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Popular VPNs on NixOS&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://wiki.nixos.org/wiki/Mullvad_VPN&quot;&gt;Mullvad VPN&lt;/a&gt; Mullvad VPN uses
WireGuard under the hood and only works if &lt;code&gt;systemd-resolvd&lt;/code&gt; is enabled.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://wiki.nixos.org/wiki/WireGuard&quot;&gt;WireGuard VPN&lt;/a&gt;, WireGuard is a
protocol, but also a VPN provider on NixOS.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://wiki.nixos.org/wiki/Tailscale&quot;&gt;Tailscale&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://wiki.nixos.org/wiki/OpenVPN&quot;&gt;OpenVPN&lt;/a&gt;, OpenVPN is both a protocol and
full-featured VPN provider on NixOS.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content></entry><entry><title>JJ VCS</title><id>https://saylesss88.github.io/vcs/jujutsu.html</id><updated>2025-12-08T00:00:00+00:00</updated><author><name>saylesss87@proton.me (saylesss88)</name></author><link href="https://saylesss88.github.io/vcs/jujutsu.html" rel="alternate"/><content type="html">&lt;h1&gt;Version Control with JJ&lt;/h1&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Click to Expand Table of Contents&lt;/summary&gt;
&lt;!-- toc --&gt;
&lt;/details&gt;
&lt;p&gt;&lt;img src=&quot;https://saylesss88.github.io/../images/jujutsu.png&quot; alt=&quot;JJ Logo&quot; /&gt;&lt;/p&gt;
&lt;div style=&quot;font-size: 0.8em; margin-top: 10px;&quot;&gt;
  **Image Source:** This image is from the [Jujutsu VCS repository](https://github.com/jj-vcs/jj) and is licensed under the Apache 2.0 License.
&lt;/div&gt;
&lt;p&gt;⚠️ &lt;strong&gt;Security Reminder&lt;/strong&gt;: Never commit secrets (passwords, API keys, tokens,
etc.) in plain text to your Git repository. If you plan to publish your NixOS
configuration, always use a secrets management tool like &lt;code&gt;sops-nix&lt;/code&gt; or &lt;code&gt;agenix&lt;/code&gt;
to keep sensitive data safe. See the
&lt;a href=&quot;https://saylesss88.github.io/installation/enc/sops-nix.html&quot;&gt;Sops-Nix Guide&lt;/a&gt;
for details.&lt;/p&gt;
&lt;h2&gt;Getting Started&lt;/h2&gt;
&lt;p&gt;Jujutsu (jj) is a modern, Git-compatible version control system designed to
simplify and improve the developer experience. It offers a new approach to
distributed version control, focusing on a more intuitive workflow, powerful
undo capabilities, and a branchless model that reduces common pitfalls of Git.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Recommended resources&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://steveklabnik.github.io/jujutsu-tutorial/&quot;&gt;Steve’s Jujutsu Tutorial&lt;/a&gt;
(most up to date). Steve does an excellent job explaining the ins and outs of
Jujutsu.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://zerowidth.com/2025/jj-tips-and-tricks/&quot;&gt;zerowidth jj-tips-and-tricks&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Official:&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;jj help -k tutorial
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Every time you run a &lt;code&gt;jj&lt;/code&gt; command, it examines the working copy and takes a
snapshot.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Command help:&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;jj &amp;lt;command&amp;gt; --help
jj git init --help
jj git push --help
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;🔑 Key Concepts&lt;/h2&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Click to Expand Key Concepts &lt;/summary&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Working Copy as Commit&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;In JJ your working copy is always a real commit. Any changes you make are
automatically recorded in this working commit. The working copy is always
(&lt;code&gt;@&lt;/code&gt;) and the Parent commit is always &lt;code&gt;(@-)&lt;/code&gt; keep this in mind.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;There is &lt;strong&gt;no staging area&lt;/strong&gt; (index) as in Git. You do not need to run
&lt;code&gt;git add&lt;/code&gt; or &lt;code&gt;git commit&lt;/code&gt; for every change. Modifications are always tracked
in the current commit.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;&lt;strong&gt;Branchless Workflow and Bookmarks&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;JJ does not have the concept of a “current branch.” Instead, use bookmarks,
which are named pointers to specific commits.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Bookmarks do not move automatically. Commands like &lt;code&gt;jj new&lt;/code&gt; and &lt;code&gt;jj commit&lt;/code&gt;
move the working copy, but the bookmark stays were it was. Use
&lt;code&gt;jj bookmark move&lt;/code&gt; to move bookmarks. (e.g., &lt;code&gt;jj bookmark move main&lt;/code&gt;). You can
also use &lt;code&gt;jj bookmark set main -r @&lt;/code&gt; to explicitly set the main bookmark to
point at the working copy commit.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Only commits referenced by bookmarks are pushed to remotes, preventing
accidental sharing of unfinished work.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;&lt;strong&gt;Automatic Tracking and Simpler Workflow&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;To stop tracking a specific file, first add it to your &lt;code&gt;.gitignore&lt;/code&gt;, then run
&lt;code&gt;jj untrack &amp;lt;file&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The working copy acts as a live snapshot of your workspace. Commands first
sync filesystem changes into this commit, then perform the requested
operation, and finally update the working copy if needed.&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;Operation Log and Undo&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;JJ records every operation (commits, merges, rebases, etc.) in an &lt;strong&gt;operation
log&lt;/strong&gt;. Inspect it with: &lt;code&gt;jj op log&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You can view and undo any previous operation, not just the most recent one,
making it easy to recover from mistakes, a feature not present in Git’s core
CLI.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;5&quot;&gt;
&lt;li&gt;First-Class Conflict Handling&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Conflicts happen when JJ can’t figure out how to merge different changes made to
the same file.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Conflicts are stored inside commits, not just in the working directory. You
can resolve them at any time, not just during a merge or rebase.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Conflict markers are inserted directly into files, and JJ can reconstruct the
conflict state from these markers. You can resolve conflicts by editing the
files or using &lt;code&gt;jj resolve&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;6&quot;&gt;
&lt;li&gt;Revsets and Filesets&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Revsets&lt;/strong&gt;: JJ’s powerful query language for selecting sets of commits,
inspired by Mercurial. For example, &lt;code&gt;jj log -r &quot;author(alice) &amp;amp; file(*.py)&quot;&lt;/code&gt;
lists all commits by Alice that touch Python files.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Filesets&lt;/strong&gt;:JJ supports a functional language for selecting sets of files,
allowing advanced file-based queries and operations.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th style=&quot;text-align: left&quot;&gt;Feature&lt;/th&gt;&lt;th style=&quot;text-align: left&quot;&gt;Git&lt;/th&gt;&lt;th style=&quot;text-align: left&quot;&gt;Jujutsu (jj)&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: left&quot;&gt;Staging Area&lt;/td&gt;&lt;td style=&quot;text-align: left&quot;&gt;Yes (git add/index)&lt;/td&gt;&lt;td style=&quot;text-align: left&quot;&gt;No, working copy is always a commit&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: left&quot;&gt;Commit Workflow&lt;/td&gt;&lt;td style=&quot;text-align: left&quot;&gt;Stage → Commit&lt;/td&gt;&lt;td style=&quot;text-align: left&quot;&gt;All changes auto-recorded in working commit&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: left&quot;&gt;Branches&lt;/td&gt;&lt;td style=&quot;text-align: left&quot;&gt;Central to workflow&lt;/td&gt;&lt;td style=&quot;text-align: left&quot;&gt;Optional, bookmarks used for sharing&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: left&quot;&gt;Undo/Redo&lt;/td&gt;&lt;td style=&quot;text-align: left&quot;&gt;Limited, complex&lt;/td&gt;&lt;td style=&quot;text-align: left&quot;&gt;Easy, operation log for undo&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: left&quot;&gt;Conflict Handling&lt;/td&gt;&lt;td style=&quot;text-align: left&quot;&gt;Manual, can be confusing&lt;/td&gt;&lt;td style=&quot;text-align: left&quot;&gt;Conflicts tracked in commits, easier to fix&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: left&quot;&gt;Integration with Git&lt;/td&gt;&lt;td style=&quot;text-align: left&quot;&gt;Native&lt;/td&gt;&lt;td style=&quot;text-align: left&quot;&gt;Fully compatible, can switch back anytime&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;ol start=&quot;7&quot;&gt;
&lt;li&gt;Anonymous branches: In Git a branch is a pointer to a commit that needs a
name.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If you haven’t taken the time to deep dive Git, it may be a good time to learn
about a new way of doing Version Control that is actually less complex and
easier to mentally map out in my opinion.&lt;/p&gt;
&lt;p&gt;Jujutsu is a new front-end to Git, and it’s a new design for distributed version
control. –jj init&lt;/p&gt;
&lt;p&gt;You can use jujutsu (jj) with existing Git repositories with one command.
&lt;code&gt;jj git init --colocate&lt;/code&gt; or &lt;code&gt;jj git init --git-repo /path/to/git_repository&lt;/code&gt;.
The native repository format for jj is still a work in progress so people
typically use a &lt;code&gt;git&lt;/code&gt; repository for backend.&lt;/p&gt;
&lt;p&gt;Unlike &lt;code&gt;git&lt;/code&gt;, &lt;code&gt;jj&lt;/code&gt; has no index “staging area”. It treats the working copy as an
actual commit. When you make changes to files, these changes are automatically
recorded to the working commit. There’s no need to explicitly stage changes
because they are already part of the commit that represents your current working
state.&lt;/p&gt;
&lt;/details&gt;
&lt;p&gt;&lt;strong&gt;Simplified Workflow&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Check where you’re at, JJ doesn’t care about commits without descriptions but
Git and GitHub do:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;❯  jj st
The working copy has no changes.
Working copy  (@) : n a8b19ca2 (empty) (no description set)
Parent commit (@-): k 8c487558 edit(jj): ui.color = always diff.format color-words
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can see that the Working copy is &lt;code&gt;(empty)&lt;/code&gt; and has &lt;code&gt;(no description set)&lt;/code&gt;,
lets give it a description:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;❯  jj desc -m &quot;chore: nix flake update&quot;
Working copy  (@) now at: n 5c36a33d (empty) chore: nix flake update
Parent commit (@-)      : k 8c487558 edit(jj): ui.color = always diff.format color-words
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I ran &lt;code&gt;nix flake update&lt;/code&gt;, let’s check our status:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;❯  jj st
Working copy changes:
M flake.lock
Working copy  (@) : n d54ab019 chore: nix flake update
Parent commit (@-): k 8c487558 edit(jj): ui.color = always diff.format color-words
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;We can see that running &lt;code&gt;nix flake update&lt;/code&gt; modified &lt;code&gt;M&lt;/code&gt; our &lt;code&gt;flake.lock&lt;/code&gt;. To
finalize this change we can run &lt;code&gt;jj new&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;❯  jj new
Working copy  (@) now at: v cad9d50b (empty) (no description set)
Parent commit (@-)      : n d54ab019 chore: nix flake update
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, looking at the output of &lt;code&gt;jj new&lt;/code&gt; above, we can see that the Working copy
is empty and has no description set. If we want to push these changes to GitHub,
we have to point the &lt;code&gt;main&lt;/code&gt; bookmark where the changes exist, the Parent commit
in this case:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;❯  jj bookmark set main -r @-
Moved 1 bookmarks to n d54ab019 main* | chore: nix flake update
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Notice the &lt;code&gt;main*&lt;/code&gt;, the &lt;code&gt;*&lt;/code&gt; indicates that our local &lt;code&gt;main&lt;/code&gt; has changes that
&lt;code&gt;main@origin&lt;/code&gt; does not have.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ok, our &lt;code&gt;main&lt;/code&gt; bookmark is now pointing at our latest changes. We can now run
&lt;code&gt;jj git push&lt;/code&gt; to push them to the remote and make them a part of the permanent
record:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;❯  jj git push
Changes to push to origin:
  Move forward bookmark main from 3956b1386d0a to d54ab0197bef
git: Enumerating objects: 14, done.
git: Counting objects: 100% (14/14), done.
git: Delta compression using up to 16 threads
git: Compressing objects: 100% (10/10), done.
git: Writing objects: 100% (10/10), 1.52 KiB | 520.00 KiB/s, done.
git: Total 10 (delta 7), reused 0 (delta 0), pack-reused 0 (from 0)
remote: Resolving deltas: 100% (7/7), completed with 4 local objects.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Success! Let’s check out our status again:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;❯  jj st
The working copy has no changes.
Working copy  (@) : v cad9d50b (empty) (no description set)
Parent commit (@-): n d54ab019 main | chore: nix flake update
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Notice that &lt;code&gt;main*&lt;/code&gt; is now just &lt;code&gt;main&lt;/code&gt;, indicating our local and remotes are
in sync!&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let’s check out the log:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;❯  jj log
@  v sayls8@proton.me 2026-03-22 12:35:34 5835b760
│  (no description set)
◆  n sayls8@proton.me 2026-03-22 12:26:20 main d54ab019
│  chore: nix flake update
~
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;◆&lt;/code&gt; indicates that change &lt;code&gt;n&lt;/code&gt; is now immutable after the push. Since it is
now immutable, &lt;code&gt;jj&lt;/code&gt; automatically creates a new change on top of &lt;code&gt;main&lt;/code&gt; and
moves the working copy to it.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is the hardest part for most people to grasp so let’s try another example
where this time we push changes from the working copy.&lt;/p&gt;
&lt;p&gt;I’ll add a simple &lt;code&gt;README.md&lt;/code&gt; to our flake root:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;❯ touch README.md


❯  jj st
Working copy changes:
A README.md
Working copy  (@) : v 5835b760 (no description set)
Parent commit (@-): n d54ab019 main | chore: nix flake update
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let’s give the change a description. Remember that &lt;code&gt;jj&lt;/code&gt; commands default to the
working copy, so &lt;code&gt;jj desc&lt;/code&gt; is the same is &lt;code&gt;jj desc -r @&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;❯  jj desc -m &quot;chore: add README&quot;
Working copy  (@) now at: v cdbb489f chore: add README
Parent commit (@-)      : n d54ab019 main | chore: nix flake update
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now rather than finalizing the current change with &lt;code&gt;jj new&lt;/code&gt;, we will just point
the &lt;code&gt;main&lt;/code&gt; bookmark at the working copy &lt;code&gt;@&lt;/code&gt; and then push.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;❯  jj bookmark set main -r @
Moved 1 bookmarks to v cdbb489f main* | chore: add README

  flake   HEAD [!]
❯  jj git push
Changes to push to origin:
  Move forward bookmark main from d54ab0197bef to cdbb489fecc0
  git: Enumerating objects: 4, done.
  git: Counting objects: 100% (4/4), done.
  git: Delta compression using up to 16 threads
  git: Compressing objects: 100% (2/2), done.
  git: Writing objects: 100% (3/3), 332 bytes | 332.00 KiB/s, done.
  git: Total 3 (delta 1), reused 0 (delta 0), pack-reused 0 (from 0)
  remote: Resolving deltas: 100% (1/1), completed with 1 local object.
  Warning: The working-copy commit in workspace &apos;default&apos; became immutable, so a new commit has been created on top of it.
  Working copy  (@) now at: s 51306e16 (empty) (no description set)
  Parent commit (@-)      : v cdbb489f main | chore: add README
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With jujutsu, most commands allow you to pass &lt;code&gt;-r&lt;/code&gt;/&lt;code&gt;--revision&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;What is the Jujutsu Working Copy&lt;/h2&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Click To Expand Working Copy Description &lt;/summary&gt;
&lt;p&gt;&lt;code&gt;@&lt;/code&gt; is a revset for “whichever commit the working copy reflects”. Think of &lt;code&gt;@&lt;/code&gt;
as where you are currently making changes.&lt;/p&gt;
&lt;p&gt;Every time you run a &lt;code&gt;jj&lt;/code&gt; command, it examines the working copy (the files on
disk) and takes a snapshot. –Steves JJ Tutorial&lt;/p&gt;
&lt;p&gt;Let’s version control an existing nix development environment.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd projects/rust

❯  ls
 flake.lock   flake.nix

jj git init --colocate
Initialized repo in &quot;.&quot;
Hint: Running `git clean -xdf` will remove `.jj/`!
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;❯  jj log
@  t sayls8@proton.me 2026-03-15 08:49:21 e8fd7ee0
│  (no description set)
◆  z root() 00000000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let’s give this change a description:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt; jj desc -m &quot;Initial commit of dev environment&quot;
Working copy  (@) now at: t d671f27c Initial commit of dev environment
Parent commit (@-)      : z 00000000 (empty) (no description set)

❯  jj log
# The change ID stays the same, but the commit ID changes
@  t sayls8@proton.me 2026-03-15 09:04:17 d671f27c
│  Initial commit of dev environment
◆  z root() 00000000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ok, I’m done with that change. Let’s start a new one, based off of &lt;code&gt;t&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;jj new
Working copy  (@) now at: q a3f5afe8 (empty) (no description set)
Parent commit (@-)      : t d671f27c Initial commit of dev environment

jj log
@  q sayls8@proton.me 2026-03-15 09:10:46 a3f5afe8
│  (empty) (no description set)
○  t sayls8@proton.me 2026-03-15 09:04:17 d671f27c
│  Initial commit of dev environment
◆  z root() 00000000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since the repo wasn’t an existing git repo there are no existing branches
(bookmarks). To share our work we’ll want to create a branch:&lt;/p&gt;
&lt;p&gt;Above is a repo that was just created with &lt;code&gt;jj git init --colocate&lt;/code&gt;. Notice that
there is already 2 changes with change IDs &lt;code&gt;t&lt;/code&gt; &amp;amp; &lt;code&gt;z&lt;/code&gt; and 2 commits with
identifiers &lt;code&gt;e8fd7ee0&lt;/code&gt; &amp;amp; &lt;code&gt;00000000&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Every &lt;code&gt;jj&lt;/code&gt; repo has a root commit with &lt;code&gt;zzzzzzzz&lt;/code&gt; &lt;code&gt;00000000 &lt;/code&gt; identifiers.
&lt;code&gt;The diamond &lt;/code&gt;◆` represents an immutable, protected revision. This is the
foundation of the repo. Jujutsu created a second change based on top of the
empty root commit.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;working copy&lt;/strong&gt; in Jujutsu is an actual &lt;strong&gt;commit&lt;/strong&gt; that represents the
current state of the files you’re working on. Unlike Git, where the working copy
is separate from commits and changes must be explicitly staged and committed, in
JJ the working copy is a live commit that automatically records changes as you
modify files.&lt;/p&gt;
&lt;p&gt;Adding or removing files in the working copy implicitly tracks or untracks them
without needing explicit commands like &lt;code&gt;git add&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;The working copy commit acts as a snapshot of your current workspace. When you
run commands, Jujutsu first syncs the filesystem changes into this commit, then
performs the requested operation, and finally updates the working copy if needed&lt;/p&gt;
&lt;p&gt;To finalize your current changes and start a new set of changes, you use the
&lt;code&gt;jj new&lt;/code&gt; command, which creates a new working-copy commit on top of the current
one. This replaces the traditional Git workflow of staging and committing
changes separately.&lt;/p&gt;
&lt;p&gt;Conflicts in the working copy are represented by inserting conflict markers
directly into the files. Jujutsu tracks the conflicting parts and can
reconstruct the conflict state from these markers. You resolve conflicts by
editing these markers and then committing the resolution in the working copy&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;This means that you don’t need to worry about making a change, running
&lt;code&gt;git add .&lt;/code&gt;, running &lt;code&gt;git commit -m &quot;commit message&quot;&lt;/code&gt; because it’s already
done for you. This is handy with flakes by preventing a “dirty working tree”
and can instantly be rebuilt after making a change.&lt;/li&gt;
&lt;/ul&gt;
&lt;/details&gt;
&lt;h2&gt;Example JJ Module&lt;/h2&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Click to Expand JJ home-manager module example &lt;/summary&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;For &lt;code&gt;lazygit&lt;/code&gt; fans, Nixpkgs has &lt;code&gt;lazyjj&lt;/code&gt;. I’ve seen that it’s recommended to
use jj with &lt;code&gt;meld&lt;/code&gt;. I’ll share my &lt;code&gt;jj.nix&lt;/code&gt; here for an example:&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I got a lot of the aliases and such from the
&lt;a href=&quot;https://zerowidth.com/2025/jj-tips-and-tricks/&quot;&gt;zerowidth&lt;/a&gt; post, this has
been a game changer:&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{
  lib,
  config,
  pkgs,
  # userVars ? {},
  #
  #
  #
  ...
}: let
  cfg = config.custom.jj;
in {
  options.custom.jj = {
    enable = lib.mkOption {
      type = lib.types.bool;
      default = true;
      description = &quot;Enable the Jujutsu (jj) module&quot;;
    };

    userName = lib.mkOption {
      type = lib.types.nullOr lib.types.str;
      default = &quot;sayls8&quot;;
      description = &quot;Jujutsu user name&quot;;
    };

    userEmail = lib.mkOption {
      type = lib.types.nullOr lib.types.str;
      default = &quot;sayls8@proton.me&quot;;
      description = &quot;Jujutsu user email&quot;;
    };

    packages = lib.mkOption {
      type = lib.types.listOf lib.types.package;
      default = with pkgs; [lazyjj meld];
      description = &quot;Additional Jujutsu-related packages to install&quot;;
    };

    settings = lib.mkOption {
      type = lib.types.attrs;
      default = {
        ui = {
          # default-command = &quot;log-recent&quot;;
          default-command = [&quot;status&quot; &quot;--no-pager&quot;];
          diff-editor = &quot;gitpatch&quot;;
          # diff-editor = [&quot;nvim&quot; &quot;-c&quot; &quot;DiffEditor&quot; &quot;$left&quot; &quot;$right&quot; &quot;$output&quot;];
          # diff-formatter = [&quot;meld&quot; &quot;$left&quot; &quot;$right&quot;];
          merge-editor = &quot;:builtin&quot;;
          conflict-marker-style = &quot;diff&quot;;
        };
        git = {
          # remove the need for `--allow-new` when pushing new bookmarks
          auto-local-bookmark = true;
          push-new-bookmarks = true;
        };
        revset-aliases = {
          &quot;closest_bookmark(to)&quot; = &quot;heads(::to &amp;amp; bookmarks())&quot;;
          &quot;immutable_heads()&quot; = &quot;builtin_immutable_heads() | remote_bookmarks()&quot;;
          # The following command is incorrect, TODO
          # &quot;default()&quot; = &quot;coalesce(trunk(),root())::present(@) | ancestors(visible_heads() &amp;amp; recent(), 2)&quot;;
          &quot;recent()&quot; = &quot;committer_date(after:&apos;1 month ago&apos;)&quot;;
          trunk = &quot;main@origin&quot;;
        };
        template-aliases = {
          &quot;format_short_change_id(id)&quot; = &quot;id.shortest()&quot;;
        };
        merge-tools.gitpatch = {
          program = &quot;sh&quot;;
          edit-args = [
            &quot;-c&quot;
            &apos;&apos;
              set -eu
              rm -f &quot;$right/JJ-INSTRUCTIONS&quot;
              git -C &quot;$left&quot; init -q
              git -C &quot;$left&quot; add -A
              git -C &quot;$left&quot; commit -q -m baseline --allow-empty
              mv &quot;$left/.git&quot; &quot;$right&quot;
              git -C &quot;$right&quot; add --intent-to-add -A
              git -C &quot;$right&quot; add -p
              git -C &quot;$right&quot; diff-index --quiet --cached HEAD &amp;amp;&amp;amp; { echo &quot;No changes done, aborting split.&quot;; exit 1; }
              git -C &quot;$right&quot; commit -q -m split
              git -C &quot;$right&quot; restore . # undo changes in modified files
              git -C &quot;$right&quot; reset .   # undo --intent-to-add
              git -C &quot;$right&quot; clean -q -df # remove untracked files
            &apos;&apos;
          ];
        };
        aliases = {
          c = [&quot;commit&quot;];
          ci = [&quot;commit&quot; &quot;--interactive&quot;];
          e = [&quot;edit&quot;];
          i = [&quot;git&quot; &quot;init&quot; &quot;--colocate&quot;];
          tug = [&quot;bookmark&quot; &quot;move&quot; &quot;--from&quot; &quot;closest_bookmark(@-)&quot; &quot;--to&quot; &quot;@-&quot;];
          log-recent = [&quot;log&quot; &quot;-r&quot; &quot;default() &amp;amp; recent()&quot;];
          nb = [&quot;bookmark&quot; &quot;create&quot; &quot;-r&quot; &quot;@-&quot;]; # new bookmark
          upmain = [&quot;bookmark&quot; &quot;set&quot; &quot;main&quot;];
          squash-desc = [&quot;squash&quot; &quot;::@&quot; &quot;-d&quot; &quot;@&quot;];
          rebase-main = [&quot;rebase&quot; &quot;-d&quot; &quot;main&quot;];
          amend = [&quot;describe&quot; &quot;-m&quot;];
          pushall = [&quot;git&quot; &quot;push&quot; &quot;--all&quot;];
          push = [&quot;git&quot; &quot;push&quot; &quot;--allow-new&quot;];
          pull = [&quot;git&quot; &quot;fetch&quot;];
          dmain = [&quot;diff&quot; &quot;-r&quot; &quot;main&quot;];
          l = [&quot;log&quot; &quot;-T&quot; &quot;builtin_log_compact&quot;];
          lf = [&quot;log&quot; &quot;-r&quot; &quot;all()&quot;];
          r = [&quot;rebase&quot;];
          s = [&quot;squash&quot;];
          si = [&quot;squash&quot; &quot;--interactive&quot;];
        };
        revsets = {
          # log = &quot;main@origin&quot;;
          # log = &quot;master@origin&quot;;
        };
      };
      description = &quot;Jujutsu configuration settings&quot;;
    };
  };

  config = lib.mkIf cfg.enable {
    home.packages = cfg.packages;

    programs.jujutsu = {
      enable = true;
      settings = lib.mergeAttrs cfg.settings {
        user = {
          name = cfg.userName;
          email = cfg.userEmail;
        };
      };
    };
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In my &lt;code&gt;home.nix&lt;/code&gt; I have this to enable it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;custom = {
    jj = {
        enable = true;
        userName = &quot;sayls8&quot;;
        userEmail = &quot;sayls8@proton.me&quot;;
        packages = &quot;&quot;;
    };
};
&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;
&lt;p&gt;The &lt;code&gt;custom.jj&lt;/code&gt; module allows me to override the username, email, packages, and
whether jj is enabled from a single, centralized place within my Nix
configuration. So only if jj is enabled, &lt;code&gt;lazyjj&lt;/code&gt; and &lt;code&gt;meld&lt;/code&gt; will be installed.&lt;/p&gt;
&lt;p&gt;With the above &lt;code&gt;gitpatch&lt;/code&gt; setup, say you did more work than you want to commit
which is common with jj since it automatically tracks everything. I can now run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;jj commit -i
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And an interactive diff will come up allowing you to choose what to include in
the current commit. This also works for &lt;code&gt;jj split -i&lt;/code&gt; and &lt;code&gt;jj squash -i&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Example, using &lt;code&gt;jj commit -i&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://saylesss88.github.io/../images/jj-gitpatch.png&quot; alt=&quot;jj commit -i&quot; /&gt;&lt;/p&gt;
&lt;p&gt;You can also use the &lt;code&gt;jj tug&lt;/code&gt; command to make pushing to a remote more
straightforward. Since JJ’s bookmarks don’t automatically move as they do with
Git, you can use &lt;code&gt;jj tug&lt;/code&gt; after you’ve made a few commits to move the bookmark
that is closest to the parent commit of your current position to your current
commit:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;jj tug
jj git push
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;tug&lt;/code&gt; alias works for both the squash and edit workflows. After running
&lt;code&gt;jj tug&lt;/code&gt;, &lt;code&gt;jj git push&lt;/code&gt; should work. If you get an error saying no bookmarks to
move, you can run &lt;code&gt;jj new&lt;/code&gt; and then run &lt;code&gt;jj tug&lt;/code&gt;, this happens when the bookmark
is already at the parent commit.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# jj.nix
mb = [&quot;bookmark&quot; &quot;set&quot; &quot;-r&quot; &quot;@&quot;];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Another option would be to run &lt;code&gt;jj mb main&lt;/code&gt; before running &lt;code&gt;jj git push&lt;/code&gt; in this
situation, but you will have to describe the commit first.&lt;/p&gt;
&lt;h2&gt;Issues I’ve Noticed&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://saylesss88.github.io/../images/jj2.png&quot; alt=&quot;jj tree&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I have run into a few issues, such as every flake command reloading every single
input every time. &lt;strong&gt;What I mean by this is what you see when you run a flake
command for the first time, it adds all of your flakes inputs.&lt;/strong&gt; I believe the
fix for this is deleting and regenerating your &lt;code&gt;flake.lock&lt;/code&gt;. The same thing can
happen when you move your flake from one location to another.&lt;/p&gt;
&lt;p&gt;JJ doesn’t seem to automatically track completely new files, running
&lt;code&gt;git add /file/path.nix&lt;/code&gt; enables JJ to start tracking the new file.&lt;/p&gt;
&lt;p&gt;That said, I recommend doing just that after running something like
&lt;code&gt;jj git init --colocate&lt;/code&gt;. Delete your &lt;code&gt;flake.lock&lt;/code&gt; and run &lt;code&gt;nix flake update&lt;/code&gt;,
&lt;code&gt;nix flake lock --recreate-lock-file&lt;/code&gt; still works but is being depreciated.&lt;/p&gt;
&lt;p&gt;Sometimes the auto staging doesn’t pick up the changes in your configuration so
rebuilding changes nothing, this has been more rare but happens occasionally.&lt;/p&gt;
&lt;p&gt;One of the most fundamental differences between Jujutsu and Git is how pushing
works. If you’re coming from Git, it’s important to understand this shift so you
don’t get tripped up by “nothing happened” warnings or missing changes on your
remote.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;In Git, you’re always “on” a branch (e.g., &lt;code&gt;main&lt;/code&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;When you make a commit, the branch pointer automatically moves forward.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;git push&lt;/code&gt; pushes the current branch’s new commits to the remote.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If you forget to switch branches, you might accidentally push to the wrong
place, but you rarely have to think about “moving” the branch pointer
yourself.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;The JJ Push Model&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;JJ has no concept of a “currrent branch”&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Bookmarks &lt;strong&gt;do not&lt;/strong&gt; move automatically. When you make a new commit, the
bookmark (e.g., &lt;code&gt;main&lt;/code&gt;) stays where it was. You must explicitly move it to
your new commit with &lt;code&gt;jj bookmark set main&lt;/code&gt; (or create a new one).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;JJ only pushes commits that are referenced by bookmarks. If your latest work
isn’t pointed to by a bookmark, &lt;code&gt;jj git push&lt;/code&gt; will do nothing and warn you.
This is to prevent accidental pushes and gives you more control over what gets
shared.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Typical JJ Push Workflow&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Check out where your working copy and Parent commit are, you will notice that
jj highlights the minimal amount of characters needed to reference this
change:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;jj st
Working copy changes:
M README.md
Working copy  (@) : mnkrokmt 7f0558f8 say hello and goodbye
Parent commit (@-): ywyvxrts 986d16f5 main | test3
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Being more explicit about your commands ensures both you and jj know where
everything should go. (i.e. &lt;code&gt;jj desc @ -m&lt;/code&gt; explicitly describes &lt;code&gt;@&lt;/code&gt;, the working
copy.) This will save you some headaches.&lt;/p&gt;
&lt;p&gt;Our new change, the Working copy is now built off of &lt;code&gt;main&lt;/code&gt;. The working copy
will always be (&lt;code&gt;@&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Make some changes&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;jj st
Working copy changes:
A dev/flake.lock
A dev/flake.nix
Working copy  (@) : kxwrsmmu 42b011cd Add a devShell
Parent commit (@-): ywyvxrts 986d16f5 main | test3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now I’m done, and since we built this change on top of &lt;code&gt;main&lt;/code&gt; the following
command will tell jj we know what we want to push:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;jj bookmark set main
jj git push
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you forget to move a bookmark, JJ will warn you and nothing will be pushed.
This is a safety feature, not a bug. That’s what the &lt;code&gt;mb&lt;/code&gt; alias does, moves the
bookmark to the working copy.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# home-manager alias (move bookmark)
mb = [&quot;bookmark&quot; &quot;set&quot; &quot;-r&quot; &quot;@&quot;];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you really have problems, &lt;code&gt;jj git push --change @&lt;/code&gt; explicitly pushes the
working copy.&lt;/p&gt;
&lt;p&gt;This is a bit different than Git and takes some getting used to but you don’t
need to move the bookmark after every commit, just when you want to push. I know
I’ve made the mistake of pushing to the wrong branch before this should prevent
that.&lt;/p&gt;
&lt;h2&gt;Here’s an example of using JJ in an existing Git repo&lt;/h2&gt;
&lt;p&gt;Say I have my configuration flake in the &lt;code&gt;~/flakes/&lt;/code&gt; directory that is an
existing Git repository. To use JJ as the front-end I could do something like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd ~/flakes
jj git init --colocate
Done importing changes from the underlying Git repo.
Setting the revset alias `trunk()` to `main@origin`
Initialized repo in &quot;.&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;By default, JJ defines &lt;code&gt;trunk()&lt;/code&gt; as the main development branch of your remote
repository. This is usually set to &lt;code&gt;main@origin&lt;/code&gt;, but could be named something
else. This means that whenever you use &lt;code&gt;trunk()&lt;/code&gt; in JJ commands, it will
resolve to the latest commit on &lt;code&gt;main@origin&lt;/code&gt;. This makes it easier to refer
to the main branch in scripts and commands without hardcoding the branch name.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Bookmarks&lt;/strong&gt; in jj are named pointers to specific revisions, similar to
branches in Git. When you first run &lt;code&gt;jj git init --colocate&lt;/code&gt; in a git repo, you
will likely get a Hint saying “Run the following command to keep local bookmarks
updated on future pulls”.:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;jj bookmark list
track main@origin

jj st
The working copy has no changes.
Working copy  (@) : qzxomtxq 925eca75 (empty) (no description set)
Parent commit (@-): qnpnrklz bf291074 main | notes
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This shows that running &lt;code&gt;jj git init --colocate&lt;/code&gt; automatically started tracking
&lt;code&gt;main&lt;/code&gt; in this case. If it doesn’t, use &lt;code&gt;jj bookmark track main@origin&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I’ll create a simple change in the &lt;code&gt;README.md&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;jj st
Working copy changes:
M README.md
Working copy  (@) : qzxomtxq b963dff0 (no description set)
Parent commit (@-): qnpnrklz bf291074 main | notes
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can see that the working copy now contains a modified file &lt;code&gt;M README.md&lt;/code&gt; and
has no description set. Lets give it a description before pushing to github.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;jj desc @ -m &quot;Added to README&quot;
jj bookmark set main -r @
Moved 1 bookmarks to pxwnopqo 1e6e08a2 main* | Added to README
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;jj bookmark set main -r @&lt;/code&gt; moves the &lt;code&gt;main&lt;/code&gt; bookmark to the current revision
(the working copy), which is the explicit, recommended way to update bookmarks
in JJ. Without this step, your bookmark will continue to point at the old
commit, not your latest work. This is a major difference from Git.&lt;/p&gt;
&lt;p&gt;And finally push to GitHub:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;jj git push
Changes to push to origin:
  Move forward bookmark main from bf291074125e to e2a75e45237b
remote: Resolving deltas: 100% (1/1), completed with 1 local object.
Warning: The working-copy commit in workspace &apos;default&apos; became immutable, so a new commit has been created on top of it.
Working copy  (@) now at: pxwnopqo 8311444b (empty) (no description set)
Parent commit (@-)      : qzxomtxq e2a75e45 main | Added to README
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;Create a Repo without an existing Git Repo&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Or&lt;/strong&gt; to do this in a directory that isn’t already a git repo you can do
something like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cargo new hello-world --vcs=none
cd hello-world
jj git init
Initialized repo in &quot;.&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h3&gt;JJ and Git Side by Side&lt;/h3&gt;
&lt;p&gt;Or for example, with Git if you wanted to move to a different branch before
running &lt;code&gt;nix flake update&lt;/code&gt; to see if it introduced errors before merging with
your main branch, you could do something like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git checkout -b update-test

nix flake update

sudo nixos-rebuild test --flake .
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you’re satisfied you can merge:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git checkout main
git add . # Stage the change
git commit -m &quot;update&quot;
git merge update-test
git branch -D update-test
sudo nixos-rebuild switch --flake .
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With JJ a similar workflow could be:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Run &lt;code&gt;jj st&lt;/code&gt; to see what you have:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;jj st
The working copy has no changes.
Working copy  (@) : ttkstzzn 3f55c42c (empty) (no description set)
Parent commit (@-): wppknozq e3558ef5 main@origin | jj diff
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you don’t have a description set for the working copy set it now.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;jj desc @ -m &quot;enable vim&quot;
jj st
The working copy has no changes.
Working copy  (@) : ttkstzzn 63fda123 (empty) enable vim
Parent commit (@-): wppknozq e3558ef5 main@origin | jj diff
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;Start from the working copy (which is mutable). The working copy in JJ is
itself a commit that you can edit and squash changes into. Since &lt;code&gt;main&lt;/code&gt; is
immutable, you can create your new change by working on top of the working
copy commit.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Create a new change off of the working copy:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;jj new @
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;Make your edits:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;jj st
Working copy changes:
M home/editors/vim.nix
Working copy  (@) : qrsxltmt 494b5f18 (no description set)
Parent commit (@-): wytnnnto a07e775c (empty) enable vim
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;Squash your changes into the new change:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;jj squash
The working copy has no changes.
Working copy  (@) : tmlwppnu ba06bb99 (empty) (no description set)
Parent commit (@-): wytnnnto 52928ed9 enable vim
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This moves your working copy changes into the new commit you just created.&lt;/p&gt;
&lt;ol start=&quot;5&quot;&gt;
&lt;li&gt;Describe the new change, this might feel weird but the &lt;code&gt;jj squash&lt;/code&gt; command
created a new commit that you have to describe again:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;jj desc @ -m &quot;Enabled Vim&quot;
Working copy  (@) : tmlwppnu 5c1569c3 (empty) Enabled Vim
Parent commit (@-): wytnnnto 52928ed9 enable vim
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;6&quot;&gt;
&lt;li&gt;Set the bookmark to the Parent commit that was squashed into:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;jj bookmark set wyt
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;7&quot;&gt;
&lt;li&gt;Finally Push to the remote repository:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;jj git push --allow-new
Changes to push to origin:
  Add bookmark wyt to 5c1569c35b22
remote: Resolving deltas: 100% (4/4), completed with 4 local objects.
remote:
remote: Create a pull request for &apos;wyt&apos; on GitHub by visiting:
remote:      https://github.com/sayls8/flake/pull/new/wyt
remote:
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This command does the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Uploads your bookmark and the associated commit to the remote repository
(e.g., GitHub).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If the bookmark is new (not present on the remote), &lt;code&gt;--allow-new&lt;/code&gt; tells JJ
it’s okay to create it remotely.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;After pushing, GitHub (or your code host) will usually suggest creating a pull
request for your new branch/bookmark, allowing you or your collaborators to
review and merge the change into main.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Merging your Change into &lt;code&gt;main&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Option 1. Go to the URL suggested in the output, visit in this case:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;https://github.com/sayls8/flake/pull/new/wyt
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Click Create PR&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Click Merge PR if it shows it can merge cleanly.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Option 2.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Switch to &lt;code&gt;main&lt;/code&gt; (if not already there):&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;jj bookmark set main
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;Create a new change that combines the new change with &lt;code&gt;main&lt;/code&gt;:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;jj new tml wyt -m &quot;Merge: enable vim&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This creates a new commit with both &lt;code&gt;tml&lt;/code&gt; and &lt;code&gt;wyt&lt;/code&gt; as parents, which is how JJ
handles merges (since &lt;code&gt;jj merge&lt;/code&gt; depreciated). JJ merges are additive and
history-preserving by design especially for folks used to Git’s fast-forward and
squash options.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Summary&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;With &lt;code&gt;jj&lt;/code&gt; you’re creating a new commit rather than a new branch.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Amending vs. Squashing: Git’s &lt;code&gt;git commit --amend&lt;/code&gt; updates the last commit.
&lt;code&gt;jj squash&lt;/code&gt; combines the current commit with its parent, effectively doing the
same thing in terms of history.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Merging: Git’s merge command is explicit. In &lt;code&gt;jj&lt;/code&gt;, the concept is similar, but
since there’s no branch, you’re “merging” by moving your working commit to
include these changes.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;No need to delete branches: Since there are no branches in &lt;code&gt;jj&lt;/code&gt;, there’s no
equivalent to &lt;code&gt;git branch -D&lt;/code&gt; to clean up. Instead commits that are no longer
needed can be “abandoned” with &lt;code&gt;jj abandon&lt;/code&gt; if you want to clean up your
commit graph.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;jj describe&lt;/code&gt; without a flag just opens &lt;code&gt;$EDITOR&lt;/code&gt; where you can write your
commit message save and exit.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In &lt;code&gt;git&lt;/code&gt;, we finish a set of changes to our code by committing, but in &lt;code&gt;jj&lt;/code&gt; we
start new work by creating a change, and &lt;em&gt;then&lt;/em&gt; make changes to our code. It’s
more useful to write an initial description of your intended changes, and then
refine it as you work, than it is creating a commit message after the fact.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I have heard that jj can struggle with big repositories such as Nixpkgs and
have noticed some issues here and there when using with NixOS. I’m hoping that
as the project matures, it gets better on this front.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;The 2 main JJ Workflows&lt;/h2&gt;
&lt;h3&gt;The Squash Workflow&lt;/h3&gt;
&lt;p&gt;This workflow is the most similar to Git and Git’s index.&lt;/p&gt;
&lt;p&gt;The workflow:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Describe the work we want to do with &lt;code&gt;jj desc -m &quot;message&quot;&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;We create a new empty change on top of that one with &lt;code&gt;jj new&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;When we are done with a feature, we run &lt;code&gt;jj squash&lt;/code&gt; to move the changes from
&lt;code&gt;@&lt;/code&gt; into the change we described in step 1. &lt;code&gt;@&lt;/code&gt; is where your working copy is
positioned currently.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For example, let’s say we just ran &lt;code&gt;jj git init --colocate&lt;/code&gt; in our configuration
Flake directory making it a &lt;code&gt;jj&lt;/code&gt; repo as well using git for backend.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd flake
jj git init --colocate
jj log
@  lnmmxwko sayls8@proton.me 2025-06-27 10:14:57 1eac6aa0
│  (empty) (no description set)
○  qnknltto sayls8@proton.me 2025-06-27 09:04:08 git_head() 5358483a
│  (empty) jj
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The above log output shows that running &lt;code&gt;jj git init&lt;/code&gt; creates an empty working
commit (&lt;code&gt;@&lt;/code&gt;) on top of the &lt;code&gt;git_head()&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;jj desc -m &quot;Switch from nixVim to NVF&quot;
jj new  # Create a new empty change
jj log
@  nmnmznmm sayls8@proton.me 2025-06-27 10:16:30 52dd7ee0
│  (empty) (no description set)
○  lnmmxwko sayls8@proton.me 2025-06-27 10:16:24 git_head() 3e8f9f3a
│  (empty) Switch from nixVim to NVF
○  qnknltto sayls8@proton.me 2025-06-27 09:04:08 5358483a
│  (empty) jj
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The above log shows that running &lt;code&gt;jj desc&lt;/code&gt; changes the current (&lt;code&gt;@&lt;/code&gt;) commits
description, and then &lt;code&gt;jj new&lt;/code&gt; creates a new empty commit on top of it, moving
(&lt;code&gt;@&lt;/code&gt;) to this new empty commit.&lt;/p&gt;
&lt;p&gt;The “Switch from nixVim to NVF” commit is now the parent of (&lt;code&gt;@&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Now, we’d make the necessary changes and to add them to the commit we just
described in the previous steps.&lt;/p&gt;
&lt;p&gt;The changes are automatically “staged” so theres no need to &lt;code&gt;git add&lt;/code&gt; them, so
we just make the changes and squash them.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;jj squash  # Squash the commit into its parent commit (i.e., our named commit)
jj log
@  zsxsolsq sayls8@proton.me 2025-06-27 10:18:01 2c35d83f
│  (empty) (no description set)
○  lnmmxwko sayls8@proton.me 2025-06-27 10:18:01 git_head() 485eaee9
│  (empty) Switch from nixVim to NVF
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This shows &lt;code&gt;jj squashes&lt;/code&gt; effect, it merges the changes from the current (&lt;code&gt;@&lt;/code&gt;)
commit into its parent. The (&lt;code&gt;@&lt;/code&gt;) then moves to this modified parent, and a new
empty commit is created on top, ready for the next set of changes.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo nixos-rebuild switch --flake .
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We’re still in the nameless commit and can either continue working or run
&lt;code&gt;jj desc -m &quot;&quot;&lt;/code&gt; again describing our new change, then &lt;code&gt;jj new&lt;/code&gt; and &lt;code&gt;jj squash&lt;/code&gt;
it’s pretty simple. The nameless commit is used as an adhoc staging area.&lt;/p&gt;
&lt;p&gt;When you are ready to push, it’s important to know where your working copy
currently is and if it’s attached to a bookmark. It’s common for &lt;code&gt;jj new&lt;/code&gt; to
detach the head, all you have to do is tell JJ which branch to attach to, then
push:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;jj st
Working copy changes:
M hosts/magic/configuration.nix
M hosts/magic/container.nix
Working copy  (@) : youptvvn 988e6fc9 (no description set)
Parent commit (@-): qlwqromx 4bb754fa mdbook container
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The above output means that the working copy has modifications (&lt;code&gt;M&lt;/code&gt;) in two
files. And these changes are not yet committed.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;jj bookmark set main
jj git push
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h3&gt;The Edit Workflow&lt;/h3&gt;
&lt;p&gt;This workflow adds a few new commands &lt;code&gt;jj edit&lt;/code&gt;, and &lt;code&gt;jj next&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Here’s the workflow:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Create a new change to work on the new feature with &lt;code&gt;jj new&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If everything works exactly as planned, we’re done.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If we realize we want to break this big change up into multiple smaller ones,
we do it by making a new change before the current one, swapping to it, and
making the necessary change.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Lastly, we go back to the main change.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The squash workflow leaves &lt;code&gt;@&lt;/code&gt; at an empty undescribed change, with this
workflow, &lt;code&gt;@&lt;/code&gt; will often be on the existing change.&lt;/p&gt;
&lt;p&gt;If &lt;code&gt;@&lt;/code&gt; wasn’t at an empty change, we would start this workflow with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;jj new -m &quot;Switch from NVF to nixVim&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;since our &lt;code&gt;@&lt;/code&gt; is already at an empty change, we’ll just describe it and get
started:&lt;/p&gt;
&lt;p&gt;For this example, lets say we want to revert back to nixVim:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;jj desc -m &quot;Switch from NVF to nixVim&quot;
jj log
@  zsxsolsq sayls8@proton.me 2025-06-27 10:18:47 606abaa7
│  (empty) Switch from NVF to nixVim
○  lnmmxwko sayls8@proton.me 2025-06-27 10:18:01 git_head() 485eaee9
│  (empty) Switch from nixVim to NVF
○  qnknltto sayls8@proton.me 2025-06-27 09:04:08 5358483a
│  (empty) jj
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Again, this shows &lt;code&gt;jj desc&lt;/code&gt; renaming the current empty &lt;code&gt;@&lt;/code&gt; commit.&lt;/p&gt;
&lt;p&gt;We make the changes, and it’s pretty straightforward so we’re done, every change
is automatically staged so we can just run &lt;code&gt;sudo nixos-rebuild switch --flake .&lt;/code&gt;
now to apply the changes.&lt;/p&gt;
&lt;p&gt;If we wanted to make more changes that aren’t described we can use &lt;code&gt;jj new -B&lt;/code&gt;
which is similar to &lt;code&gt;git add -a&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;jj new -B @ -m &quot;Adding LSP to nixVim&quot;
Rebased 1 descendant commits
Working copy  (@) now at: lpnxxxpo bf929946 (empty) Adding LSP to nixVim
Parent commit (@-)      : lnmmxwko 485eaee9 (empty) Switch from nixVim to NVF
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;-B&lt;/code&gt; tells jj to create the new change &lt;em&gt;before&lt;/em&gt; the current one and it
creates a rebase. We created a change before the one we’re on, it automatically
rebased our original change. This operation will &lt;em&gt;always&lt;/em&gt; succeed with jj, we
will have our working copy at the commit we’ve just inserted.&lt;/p&gt;
&lt;p&gt;You can see below that &lt;code&gt;@&lt;/code&gt; moved down one commit:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;jj log
○  zsxsolsq sayls8@proton.me 2025-06-27 10:22:03 ad0713b6
│  (empty) Switch from NVF to nixVim
@  lpnxxxpo sayls8@proton.me 2025-06-27 10:22:03 bf929946
│  (empty) Adding LSP to nixVim
○  lnmmxwko sayls8@proton.me 2025-06-27 10:18:01 git_head() 485eaee9
│  (empty) Switch from nixVim to NVF
○  qnknltto sayls8@proton.me 2025-06-27 09:04:08 5358483a
│  (empty) jj
○  qnknltto sayls8@proton.me 2025-06-27 09:04:08 git_head()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The “Adding LSP to nixVim” commit is directly above “Switch from nixVim to NVF”
(the old &lt;code&gt;git_head()&lt;/code&gt;)&lt;/p&gt;
&lt;p&gt;The “Switch from NVF to nixVim” commit (which was your &lt;code&gt;@&lt;/code&gt; before &lt;code&gt;jj new -B&lt;/code&gt;)
is now above “Adding LSP to nixVim” in the log output, meaning “Adding LSP to
nixVim” is its new parent.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;@&lt;/code&gt; has moved to “Adding LSP to nixVim”&lt;/p&gt;
&lt;p&gt;&lt;code&gt;jj log&lt;/code&gt; example output&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Operation Log and Undo&lt;/h2&gt;
&lt;p&gt;JJ records every operation (commits, merges, rebases, etc.) in an operation log.
You can view and undo previous operations, making it easy to recover from
mistakes, a feature not present in Git’s core CLI&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;jj op log
@  fbf6e626df22 jr@magic 15 minutes ago, lasted 9 milliseconds
│  new empty commit
│  args: jj new -B @ -m &apos;Adding LSP to nixVim&apos;
○  bde40b7c17cf jr@magic 19 minutes ago, lasted 8 milliseconds
│  describe commit 2c35d83f75031dc582bf28b64d4af1c218177f90
│  args: jj desc -m &apos;Switch from NVF to nixVim&apos;
○  3a2bfe1c0b0a jr@magic 19 minutes ago, lasted 8 milliseconds
│  squash commits into 3e8f9f3a6a58fef86906e16e9b4375afb43e73e3
│  args: jj squash
○  80abcb58dcb6 jr@magic 21 minutes ago, lasted 8 milliseconds
│  new empty commit
│  args: jj new
○  8c80314cbcd7 jr@magic 21 minutes ago, lasted 8 milliseconds
│  describe commit 1eac6aa0b88ba014785ee9c1c2ad6e2abc6206e9
│  args: jj desc -m &apos;Switch from nixVim to NVF&apos;
○  44b5789cb4d1 jr@magic 22 minutes ago, lasted 6 milliseconds
│  track remote bookmark main@origin
│  args: jj bookmark track main@origin
○  dbefee04aa85 jr@magic 23 minutes ago, lasted 4 milliseconds
│  import git head
│  args: jj git init --git-repo .
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;jj op undo &amp;lt;operation-id&amp;gt;
# or
jj op restore &amp;lt;operation-id&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;Conflict Resolution&lt;/h2&gt;
&lt;p&gt;In JJ, conflicts live inside commits and can be resolved at any time, not just
during a merge. This makes rebasing and history editing safer and more flexible&lt;/p&gt;
&lt;p&gt;JJ treats conflicts as first-class citizens: conflicts can exist inside commits,
not just in the working directory. This means if a merge or rebase introduces a
conflict, the conflicted state is saved in the commit itself, and you can
resolve it at any time there’s no need to resolve conflicts immediately or use
“&lt;code&gt;--continue&lt;/code&gt;” commands as in Git&lt;/p&gt;
&lt;p&gt;Here’s how it works:&lt;/p&gt;
&lt;p&gt;When you check out or create a commit with conflicts, JJ materializes the
conflicts as markers in your files (similar to Git’s conflict markers)&lt;/p&gt;
&lt;p&gt;You can resolve conflicts by editing the files to remove the markers, or by
using:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;jj resolve
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;Revsets&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://jj-vcs.github.io/jj/latest/revsets/&quot;&gt;Jujutsu Revsets&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;JJ includes a powerful query language for selecting commits. For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;jj log -r &quot;author(alice) &amp;amp; file(*.py)&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This command lists all commits by Alice that touch Python files.&lt;/p&gt;
&lt;h2&gt;Filesets&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://jj-vcs.github.io/jj/latest/filesets/&quot;&gt;Jujutsu Filesets&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Jujutsu supports a functional language for selecting a set of files. Expressions
in this language are called “filesets” (the idea comes from Mercurial). The
language consists of file patterns, operators, and functions. –JJ Docs&lt;/p&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;Jujutsu (jj) offers a streamlined, branchless, and undo-friendly approach to
version control, fully compatible with Git but designed to be easier to use and
reason about. Its workflows, operation log, and conflict handling provide a
safer and more flexible environment for managing code changes, making it a
compelling alternative for both new and experienced developers.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Resources&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://steveklabnik.github.io/jujutsu-tutorial/&quot;&gt;steves_jj_tutorial&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/jj-vcs/jj&quot;&gt;jj_github&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://jj-vcs.github.io/jj/latest/tutorial/&quot;&gt;official_tutorial&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://v5.chriskrycho.com/essays/jj-init/&quot;&gt;jj_init&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content></entry><entry><title>KVM</title><id>https://saylesss88.github.io/nix/kvm.html</id><updated>2025-12-06T00:00:00+00:00</updated><author><name>saylesss87@proton.me (saylesss88)</name></author><link href="https://saylesss88.github.io/nix/kvm.html" rel="alternate"/><content type="html">&lt;h1&gt;Running NixOS in a VM with Maximum Isolation (Beginner Guide)&lt;/h1&gt;
&lt;details&gt;
&lt;summary&gt; Click to Expand Table of Contents&lt;/summary&gt;
&lt;!-- toc --&gt;
&lt;/details&gt;
&lt;p&gt;&lt;img src=&quot;https://saylesss88.github.io/images/steampunk5.cleaned.png&quot; alt=&quot;sp5&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Why This Setup?&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Host&lt;/strong&gt; &lt;code&gt;secureblue&lt;/code&gt; = Fedora Atomic with &lt;strong&gt;SELinux enforcing&lt;/strong&gt;, &lt;strong&gt;sVirt&lt;/strong&gt;,
&lt;strong&gt;Secure Boot&lt;/strong&gt;, and hardened defaults.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Guest&lt;/strong&gt;: NixOS in a VM → full declarative power, near zero risk to host.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Isolation&lt;/strong&gt;: Mandatory Access Control (MAC) via SELinux + KVM + no direct
hardware access.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;NOTE: Secureblue enables the &lt;code&gt;hardened_malloc&lt;/code&gt; by default which causes
problems for many browsers and will cause screen flashing with Firefox and
others within the VM. See:&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://secureblue.dev/faq#standard-malloc&quot;&gt;secureblue standard_malloc&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Step 1: Install secureblue (Hardened Host)&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Download a &lt;a href=&quot;https://secureblue.dev/install&quot;&gt;secureblue image&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Use &lt;strong&gt;Fedora Media Writer&lt;/strong&gt; (Flatpak):&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;flatpak install flathub org.fedoraproject.MediaWriter
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;
&lt;p&gt;Flash the secureblue image &amp;amp; enable Secure Boot in UEFI &lt;strong&gt;before&lt;/strong&gt; install.
This is now possible with Fedora, when you boot into Fedora Media Writer (not
Ventoy or Rufus), you will be allowed to enroll the secure boot key with
secure boot pre-enabled.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;On first boot:&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ujust enroll-secureblue-secure-boot-key
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Reboot -&amp;gt; Enroll key in MOK manager with password: &lt;code&gt;secureblue&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;5&quot;&gt;
&lt;li&gt;
&lt;p&gt;Post-install hardening See:
&lt;a href=&quot;https://secureblue.dev/post-install&quot;&gt;post-install&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Install virtualization stack:&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ujust install-libvirt-packages
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The above command enables &lt;code&gt;qemu&lt;/code&gt;, &lt;code&gt;libvirt&lt;/code&gt;, &amp;amp; &lt;code&gt;virt-manager&lt;/code&gt; with SELinux
labels.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Read the &lt;a href=&quot;https://secureblue.dev/faq&quot;&gt;secureblue FAQ&lt;/a&gt; to learn the quirks of
an atomic fedora image.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Secureblue recommends installing GUI apps with Flatpak, CLI apps with homebrew,
and apps that require more system access to be layered with &lt;code&gt;rpm-ostree&lt;/code&gt;. It
takes some getting used to but is very stable.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://secureblue.dev/faq#software&quot;&gt;secureblue how to install software&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;Create NixOS VM (via virt-manager)&lt;/h2&gt;
&lt;p&gt;Easiest way to get a working configuration IMO:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Download: &lt;a href=&quot;https://nixos.org/download/&quot;&gt;NixOS Graphical ISO&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Open &lt;code&gt;virt-manager&lt;/code&gt; -&amp;gt; File -&amp;gt; New Virtual Machine&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Select ISO&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;CPU: &lt;code&gt;host-passthrough&lt;/code&gt; (optional, for performance)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Do some research to find the ideal Memory and Storage for your system.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;
&lt;p&gt;Ensure SELinux is enabled (the default for secureblue) with: &lt;code&gt;getenforce&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Ensure sVirt is enabled (the default) with &lt;code&gt;run0 ps -eZ | grep qemu&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;run0 ps -eZ | grep qemu
# Output
system_u:system_r:svirt_t:s0:c383,c416 14793 ?   00:01:37 qemu-system-x86
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;5&quot;&gt;
&lt;li&gt;Boot -&amp;gt; Follow graphical installer:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Enable LUKS&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create an admin user&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Optionally skip desktop -&amp;gt; install your own after first boot.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The attack surface is reduced significantly when running NixOS within a hardened
hosts VM. The VM operates on virtualized hardware, which is a powerful form of
attack surface reduction.&lt;/p&gt;
&lt;p&gt;Devices like your host’s Bluetooth adapter, Wi-Fi card, microphone, webcam, and
USB ports are not directly exposed to the guest operating system. The VM only
sees virtual versions of these devices. If an exploit targets a vulnerability in
the Bluetooth stack within the VM, it compromises the VM environment, but it
cannot typically reach and exploit the physical Bluetooth hardware on the host.&lt;/p&gt;
&lt;p&gt;You can also choose not to pass through certain devices, like Bluetooth or
webcam to the VM at all, effectively disabling that attack vector. Since your
host likely already has these hardened features you may not need the additional
functionality within the VM.&lt;/p&gt;
&lt;p&gt;If something breaks, you have an option to rollback to the previous generation
with &lt;code&gt;rpm-ostree rollback&lt;/code&gt;. The previous generation will be applied on next
reboot. You can also just reboot and choose the previous generation through the
grub menu, this way it is temporary and will revert back on next reboot.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;🔒 How Host MAC Secures the NixOS VM&lt;/h2&gt;
&lt;p&gt;The host uses a classic defense-in-depth model: the hardened outer layer (the
host) is treated as the real security boundary, and it is designed to remain
safe even if the inner layer (the NixOS guest) is fully compromised.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;MAC confinement with SELinux and sVirt&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;On the secureblue host, sVirt automatically applies SELinux labels to all
VM-related processes and resources.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;QEMU process confinement&lt;/strong&gt;: The QEMU process that runs the NixOS VM runs
under a dedicated SELinux type, typically &lt;code&gt;svirt_t&lt;/code&gt;. The host’s MAC policy
tightly restricts what this process can access, so even a successful VM escape
is still trapped inside a very limited sandbox rather than gaining normal host
privileges.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Disk image protection&lt;/strong&gt;: VM disk images are labeled (for example,
&lt;code&gt;virt_image_t&lt;/code&gt;), which prevents unrelated host processes from reading or
modifying them and keeps the VM’s storage isolated from the rest of the
system.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;&lt;strong&gt;KVM and host hardening&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;KVM provides the hardware-assisted virtualization layer and forms a strong
barrier between the guest and the host kernel. On top of that, the secureblue
host is hardened with SELinux in enforcing mode, Secure Boot, a hardened kernel,
and hardened_malloc by default. Together, these measures reduce the attack
surface and help ensure the integrity of the platform that is actually running
the VM.&lt;/p&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;&lt;strong&gt;Isolation and “zero host compromise”&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The host and guest are deliberately decoupled from a security perspective. The
assumption is that the NixOS VM can be misconfigured, vulnerable, or even fully
compromised. If that happens, KVM plus the host MAC policy (SELinux + sVirt) are
responsible for containing the damage. In other words, the security boundary is
not the NixOS configuration inside the VM, but the hypervisor and the host’s
mandatory access control rules that enforce strict isolation of the guest from
the host.&lt;/p&gt;
&lt;h2&gt;Hardening the NixOS Guest is Still Worth It&lt;/h2&gt;
&lt;p&gt;Even with a hardened host and MAC confinement, treating the NixOS VM as
“untrusted but hardened” adds another independent safety layer. The goal is to
minimize what an attacker can do inside the guest, even if they never manage a
breakout.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Minimize VM device exposure&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Use snapshots aggressively&lt;/strong&gt;: Take a snapshot right after a fresh install
and initial configuration. That snapshot becomes your “known-good” state for
testing risky software or malware, so you can revert and wipe out any changes
afterward.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Avoid unnecessary passthrough&lt;/strong&gt;: Only pass through hardware (USB, GPU,
network interfaces, etc.) if the VM genuinely needs it. Every extra device is
another potential attack surface.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Prefer simple virtual devices&lt;/strong&gt;: Use virtio and other paravirtualized
devices where possible, and avoid legacy or fully emulated devices unless
there is a specific need.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Network isolation for guests&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Keep networks virtual and segmented&lt;/strong&gt;: Favor isolated virtual networks,
VLANs, or internal-only networks over bridged physical interfaces, so VMs
cannot talk to the host or each other unless you explicitly design for it.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Filter traffic tightly&lt;/strong&gt;: Use libvirt nwfilter, firewall rules
(nftables/iptables/firewalld), and similar tools to restrict VM-to-VM and
VM-to-external traffic, especially for services exposed on multiple guests.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Be cautious with IPv6&lt;/strong&gt;: Full IPv6 inside the VM usually implies bridged
networking, which connects the VM more directly to the host’s LAN. That
improves connectivity but reduces isolation, so enable it only if you truly
need it.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Guest-side hardening measures&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Harden the allocator&lt;/strong&gt;: Enabling &lt;code&gt;graphene-hardened&lt;/code&gt; or
&lt;code&gt;graphene-hardened-light&lt;/code&gt; inside the guest improves memory safety for many
applications:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# configuration.nix
environment.memoryAllocator.provider = &quot;graphene-hardened&quot;;
# OR for a more permissive and better performing allocator:
# environment.memoryAllocator.provider = &quot;graphene-hardened-light&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Some software (notably certain browsers) can be finicky with hardened mallocs
and may require rebuilding or alternatives; be prepared to switch to another
browser or allocator profile when you hit incompatibilities.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Disable nonessential features&lt;/strong&gt;: Turn off USB redirection/debugging, audio
devices, and other “extras” you do not need in the VM. These often pull in
complex subsystems that are rarely worth the extra attack surface for a
security-focused guest.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For deeper NixOS-specific hardening, see:
&lt;a href=&quot;https://saylesss88.github.io/nix/hardening_NixOS.html&quot;&gt;hardening NixOS&lt;/a&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Nix Toolbox&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;⚠️ Warning: toolbx containers are integrated with the host system, don’t do
things you wouldn’t do on your host. Toolbx containers are not fully isolated
environments like VMs.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That said, they are a fast and convenient way to spin up a Nix development
environment. Know the limitations and benefits, and when you need more
isolation, just spin up a VM instead.&lt;/p&gt;
&lt;p&gt;Secureblue enforces restrictive container image policies by default, blocking
unsigned or unverified images from registries like GitHub Container Registry.
This requires explicit trust configuration for each container source.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ujust set-container-userns on
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Without this setting, containers will fail with &lt;code&gt;OCI permission denied&lt;/code&gt; errors.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Allow the Nix-Toolbox Image&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# For system-wide configuration (affects all users)
run0 podman image trust set -t accept ghcr.io/thrix/nix-toolbox

# For user-specific configuration (recommended for development)
podman image trust set -t accept ghcr.io/thrix/nix-toolbox
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;-t accept&lt;/code&gt; flag allows images from this registry without requiring
signature verification.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Check that the policy has been updated correctly:
podman image trust show
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create the &lt;code&gt;nix-toolbox&lt;/code&gt; container:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;toolbox create --image ghcr.io/thrix/nix-toolbox:42
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You will be prompted whether you want Home Manager installed or not as well.&lt;/p&gt;
&lt;p&gt;Enter the toolbox:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;toolbox enter nix-toolbox-42
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can then use nix and home-manager to setup a fully declarative
dev-environment.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Real-world recovery example&lt;/h3&gt;
&lt;p&gt;Secureblue’s design and the underlying firmware safeguards also make certain
failures recoverable. On a mini PC, running a firmware update command resulted
in a boot error (“Something went seriously wrong, MOK is full”) and a forced
shutdown. Resetting NVRAM by moving the jumper on the motherboard briefly, then
restoring it to the original position, allowed the system to retrain and boot
again, after which the Secure Boot key could be re-enrolled and the system
returned to a known-good, secure state.&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ My Experience with Secureblue &lt;/summary&gt;
&lt;p&gt;Using secureblue as the host OS with NixOS in a VM has been surprisingly smooth
for day‑to‑day work. Performance has been more than adequate for editing,
development, and browsing, and every issue so far has been fixable with
rollbacks or small config changes—no “nuke and reinstall” moments required.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Software installation and workflows&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Flatpak takes a bit of relearning if you are used to installing everything with
full root on a mutable distro. Tools like Flatseal help a lot: you can see
exactly which permissions an app has, then selectively tighten them instead of
blindly trusting defaults. On secureblue, running the
&lt;code&gt;ujust flatpak-permissions-lockdown&lt;/code&gt; helper gives you a very strict baseline,
then you add back only what each app truly needs.&lt;/p&gt;
&lt;p&gt;In practice, a hybrid approach has worked best. One editor runs as a Flatpak,
and another is installed via &lt;code&gt;rpm-ostree&lt;/code&gt; for tighter system integration and the
“traditional” root behavior when needed. The same thing happened with &lt;code&gt;yazi&lt;/code&gt;: to
get the exact workflow wanted, it was easier to install it via &lt;code&gt;rpm-ostree&lt;/code&gt;
rather than poking so many holes in the Flatpak sandbox that most isolation
benefits disappeared.&lt;/p&gt;
&lt;p&gt;Toolbx also fits into this nicely. Putting Homebrew and Flatpak inside a toolbox
lets more config be shared while keeping the host image clean. The general
pattern on Silverblue/secureblue is: Flatpak for most GUI apps, toolbox (plus
brew or distro packages) for CLI tooling, and only a small number of host‑layer
&lt;code&gt;rpm-ostree&lt;/code&gt; installs when deep integration is really warranted.&lt;/p&gt;
&lt;p&gt;One quirk worth knowing about: on secureblue, &lt;code&gt;/home&lt;/code&gt; is a symlink to
&lt;code&gt;/var/home&lt;/code&gt;. Most tools don’t care, but a few development workflows get confused
by the indirection. In those cases, pointing the tool directly at
&lt;code&gt;/var/home/username&lt;/code&gt; instead of &lt;code&gt;/home/username&lt;/code&gt; usually clears things up.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Graphics and drivers in the NixOS VM&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;For GPU and display, the safest approach has been to let secureblue own the
hardware stack and keep the NixOS guest as simple as possible. Extra GPU drivers
or compositor tweaks inside the VM tended to make things less stable, showing up
as flicker, random freezes, or generally janky graphics because the guest was
effectively fighting the host’s configuration. Sticking close to the defaults in
the VM has consistently produced smoother and more predictable graphics
behavior.&lt;/p&gt;
&lt;p&gt;The main issue I’ve had is with the dns-selector occasionally causing networking
problems. I configure global DNS with their &lt;code&gt;ujust&lt;/code&gt; command and I’m assuming
that updates were incompatible with my DNS setup. Running &lt;code&gt;ujust dns-selector&lt;/code&gt;
and pressing &lt;code&gt;1&lt;/code&gt; (Reset to defaults), and a reboot typically fix the connection
and within a few days the global DNS will work again.&lt;/p&gt;
&lt;/details&gt;
&lt;hr /&gt;
&lt;h3&gt;Resources&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.redhat.com/en/topics/virtualization/what-is-virtualization&quot;&gt;RedHat What is virtualization?&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://sumit-ghosh.com/posts/virtualization-hypervisors-explaining-qemu-kvm-libvirt/&quot;&gt;virtualization &amp;amp; hypervisors&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://bitgrounds.tech/posts/kvm-qemu-libvirt-virtualization/&quot;&gt;Virtualization on Linux using the KVM/QEMU/Libvirt stack&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content></entry><entry><title>Understanding the Helix Flake</title><id>https://saylesss88.github.io/flakes/helix_flake_4.4.html</id><updated>2025-12-05T00:00:00+00:00</updated><author><name>saylesss87@proton.me (saylesss88)</name></author><link href="https://saylesss88.github.io/flakes/helix_flake_4.4.html" rel="alternate"/><content type="html">&lt;h1&gt;Chapter 4.4&lt;/h1&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Click to Expand Table of Contents&lt;/summary&gt;
&lt;!-- toc --&gt;
&lt;/details&gt;
&lt;p&gt;&lt;img src=&quot;https://saylesss88.github.io/../images/helix.png&quot; alt=&quot;Helix Logo&quot; /&gt;–&lt;a href=&quot;https://helix-editor.com/&quot;&gt;helix-editor.com&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;Understanding the Helix Flake and Modifying its Behavior&lt;/h1&gt;
&lt;p&gt;As we’ve seen from previous examples, the helix editor repository includes a few
&lt;code&gt;.nix&lt;/code&gt; files including a &lt;code&gt;flake.nix&lt;/code&gt;. Their flake uses a lot of idiomatic Nix
code and advanced features. First I will break down their &lt;code&gt;flake.nix&lt;/code&gt; and
&lt;code&gt;default.nix&lt;/code&gt; to understand why they do certain things. And finally, we will
change the build to “debug” mode demonstrating how easily you can modify the
behavior of a package defined within a Nix flake without changing the original
source code or the upstream flake directly.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Let’s clone the Helix repository:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git clone https://github.com/helix-editor/helix.git
cd helix
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When you enter the &lt;code&gt;helix&lt;/code&gt; directory, &lt;code&gt;direnv&lt;/code&gt; is setup for you already. All you
would have to do is &lt;code&gt;direnv allow&lt;/code&gt; and it will ask you a few questions then you
are good to go. Looking at their &lt;code&gt;.envrc&lt;/code&gt; it mentions “try to use flakes, if it
fails use normal nix (i.e., shell.nix)”. If it’s successful you’ll see a long
list of environment variables displayed.&lt;/p&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;Enter the Development Shell:&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The Helix project’s &lt;code&gt;flake.nix&lt;/code&gt; includes a &lt;code&gt;devShells.default&lt;/code&gt; output,
specifically designed for development.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix develop
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;You’re now in a fully configured development environment:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;When you run &lt;code&gt;nix develop&lt;/code&gt;, Nix builds and drops you into a shell environment
with all the dependencies specified in &lt;code&gt;devShells.default&lt;/code&gt;. This means you
don’t have to manually install or manage tools like Rust, Cargo, or Clang,
it’s all handled declaratively through Nix.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can now build and run the project using its standard tooling:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cargo check
cargo build
cargo run
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;Making Changes and Testing Them&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Since you’re in a reproducible environment, you can confidently hack on the
project without worrying about your system setup. Try modifying some code in
&lt;code&gt;helix&lt;/code&gt; and rebuilding with Cargo. The Nix shell ensures consistency for every
contributor or device you work on.&lt;/p&gt;
&lt;ol start=&quot;5&quot;&gt;
&lt;li&gt;Run Just the Binary&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If you only want to run the compiled program without entering the shell, use the
nix run command:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix run
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This builds and runs the default package defined by the flake. In the case of
Helix, this launches the &lt;code&gt;hx&lt;/code&gt; editor directly.&lt;/p&gt;
&lt;ol start=&quot;6&quot;&gt;
&lt;li&gt;Build Without Running&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;To just build the project and get the path to the output binary:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix build
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You’ll find the compiled binary under &lt;code&gt;./result/bin&lt;/code&gt;.&lt;/p&gt;
&lt;ol start=&quot;7&quot;&gt;
&lt;li&gt;Pinning and Reproducing&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Because the project uses a flake, you can ensure full reproducibility by pinning
the inputs. For example, you can clone with &lt;code&gt;--recurse-submodules&lt;/code&gt; and copy the
&lt;code&gt;flake.lock&lt;/code&gt; to ensure you’re using the same dependency versions as upstream.
This is great for debugging or sharing exact builds.&lt;/p&gt;
&lt;p&gt;✅ Recap:&lt;/p&gt;
&lt;p&gt;With flakes, projects like Helix provide everything you need for development and
running in a single &lt;code&gt;flake.nix&lt;/code&gt;. You can nix develop to get started hacking, nix
run to quickly try it out, and nix build to produce binaries all without
installing or polluting your system.&lt;/p&gt;
&lt;h2&gt;Understanding the Helix flake.nix&lt;/h2&gt;
&lt;p&gt;The helix flake is full of idiomatic Nix code and displays some of the more
advanced things a flake can provide:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{
  description = &quot;A post-modern text editor.&quot;;

  inputs = {
    nixpkgs.url = &quot;github:nixos/nixpkgs/nixos-unstable&quot;;
    rust-overlay = {
      url = &quot;github:oxalica/rust-overlay&quot;;
      inputs.nixpkgs.follows = &quot;nixpkgs&quot;;
    };
  };

  outputs = {
    self,
    nixpkgs,
    rust-overlay,
    ...
  }: let
    inherit (nixpkgs) lib;
    systems = [
      &quot;x86_64-linux&quot;
      &quot;aarch64-linux&quot;
      &quot;x86_64-darwin&quot;
      &quot;aarch64-darwin&quot;
    ];
    eachSystem = lib.genAttrs systems;
    pkgsFor = eachSystem (system:
      import nixpkgs {
        localSystem.system = system;
        overlays = [(import rust-overlay) self.overlays.helix];
      });
    gitRev = self.rev or self.dirtyRev or null;
  in {
    packages = eachSystem (system: {
      inherit (pkgsFor.${system}) helix;
      /*
      The default Helix build. Uses the latest stable Rust toolchain, and unstable
      nixpkgs.

      The build inputs can be overridden with the following:

      packages.${system}.default.override { rustPlatform = newPlatform; };

      Overriding a derivation attribute can be done as well:

      packages.${system}.default.overrideAttrs { buildType = &quot;debug&quot;; };
      */
      default = self.packages.${system}.helix;
    });
    checks =
      lib.mapAttrs (system: pkgs: let
        # Get Helix&apos;s MSRV toolchain to build with by default.
        msrvToolchain = pkgs.pkgsBuildHost.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml;
        msrvPlatform = pkgs.makeRustPlatform {
          cargo = msrvToolchain;
          rustc = msrvToolchain;
        };
      in {
        helix = self.packages.${system}.helix.override {
          rustPlatform = msrvPlatform;
        };
      })
      pkgsFor;

    # Devshell behavior is preserved.
    devShells =
      lib.mapAttrs (system: pkgs: {
        default = let
          commonRustFlagsEnv = &quot;-C link-arg=-fuse-ld=lld -C target-cpu=native --cfg tokio_unstable&quot;;
          platformRustFlagsEnv = lib.optionalString pkgs.stdenv.isLinux &quot;-Clink-arg=-Wl,--no-rosegment&quot;;
        in
          pkgs.mkShell {
            inputsFrom = [self.checks.${system}.helix];
            nativeBuildInputs = with pkgs;
              [
                lld
                cargo-flamegraph
                rust-bin.nightly.latest.rust-analyzer
              ]
              ++ (lib.optional (stdenv.isx86_64 &amp;amp;&amp;amp; stdenv.isLinux) cargo-tarpaulin)
              ++ (lib.optional stdenv.isLinux lldb)
              ++ (lib.optional stdenv.isDarwin darwin.apple_sdk.frameworks.CoreFoundation);
            shellHook = &apos;&apos;
              export RUST_BACKTRACE=&quot;1&quot;
              export RUSTFLAGS=&quot;&apos;&apos;${RUSTFLAGS:-&quot;&quot;} ${commonRustFlagsEnv} ${platformRustFlagsEnv}&quot;
            &apos;&apos;;
          };
      })
      pkgsFor;

    overlays = {
      helix = final: prev: {
        helix = final.callPackage ./default.nix {inherit gitRev;};
      };

      default = self.overlays.helix;
    };
  };
  nixConfig = {
    extra-substituters = [&quot;https://helix.cachix.org&quot;];
    extra-trusted-public-keys = [&quot;helix.cachix.org-1:ejp9KQpR1FBI2onstMQ34yogDm4OgU2ru6lIwPvuCVs=&quot;];
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Top-Level Metadata&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{
  description = &quot;A post-modern text editor.&quot;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;This sets a human-readable description for the flake.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Inputs&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;inputs = {
  nixpkgs.url = &quot;github:nixos/nixpkgs/nixos-unstable&quot;;
  rust-overlay = {
    url = &quot;github:oxalica/rust-overlay&quot;;
    inputs.nixpkgs.follows = &quot;nixpkgs&quot;;
  };
};
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;nixpkgs&lt;/code&gt;: Uses the &lt;code&gt;nixos-unstable&lt;/code&gt; branch of the Nixpkgs repository.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;rust-overlay&lt;/code&gt;: follows the same &lt;code&gt;nixpkgs&lt;/code&gt;, ensuring compatibility between
inputs.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Outputs Function&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;outputs = { self, nixpkgs, rust-overlay, ... }:
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;This defines what this flake exports, including &lt;code&gt;packages&lt;/code&gt;, &lt;code&gt;devShells&lt;/code&gt;, etc.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Common Setup&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;let
  inherit (nixpkgs) lib;
  systems = [ ... ];
  eachSystem = lib.genAttrs systems;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;systems&lt;/code&gt;: A list of the supported systems&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;eachSystem&lt;/code&gt;: A Helper to map over all platforms.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;pkgsFor = eachSystem (system:
  import nixpkgs {
    localSystem.system = system;
    overlays = [(import rust-overlay) self.overlays.helix];
  });
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;This imports &lt;code&gt;nixpkgs&lt;/code&gt; for each system and applies overlays&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;📦 &lt;code&gt;packages&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;packages = eachSystem (system: {
  inherit (pkgsFor.${system}) helix;
  default = self.packages.${system}.helix;
});
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;For each platform:
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Includes a &lt;code&gt;helix&lt;/code&gt; package (defined in &lt;code&gt;./default.nix&lt;/code&gt;)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Sets &lt;code&gt;default&lt;/code&gt; to &lt;code&gt;helix&lt;/code&gt; (used by &lt;code&gt;nix build&lt;/code&gt;, &lt;code&gt;nix run&lt;/code&gt;)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let’s look at the helix &lt;code&gt;default.nix&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{
  lib,
  rustPlatform,
  callPackage,
  runCommand,
  installShellFiles,
  git,
  gitRev ? null,
  grammarOverlays ? [],
  includeGrammarIf ? _: true,
}: let
  fs = lib.fileset;

  src = fs.difference (fs.gitTracked ./.) (fs.unions [
    ./.envrc
    ./rustfmt.toml
    ./screenshot.png
    ./book
    ./docs
    ./runtime
    ./flake.lock
    (fs.fileFilter (file: lib.strings.hasInfix &quot;.git&quot; file.name) ./.)
    (fs.fileFilter (file: file.hasExt &quot;svg&quot;) ./.)
    (fs.fileFilter (file: file.hasExt &quot;md&quot;) ./.)
    (fs.fileFilter (file: file.hasExt &quot;nix&quot;) ./.)
  ]);

  # Next we actually need to build the grammars and the runtime directory
  # that they reside in. It is built by calling the derivation in the
  # grammars.nix file, then taking the runtime directory in the git repo
  # and hooking symlinks up to it.
  grammars = callPackage ./grammars.nix {inherit grammarOverlays includeGrammarIf;};
  runtimeDir = runCommand &quot;helix-runtime&quot; {} &apos;&apos;
    mkdir -p $out
    ln -s ${./runtime}/* $out
    rm -r $out/grammars
    ln -s ${grammars} $out/grammars
  &apos;&apos;;
in
  rustPlatform.buildRustPackage (self: {
    cargoLock = {
      lockFile = ./Cargo.lock;
      # This is not allowed in nixpkgs but is very convenient here: it allows us to
      # avoid specifying `outputHashes` here for any git dependencies we might take
      # on temporarily.
      allowBuiltinFetchGit = true;
    };

    nativeBuildInputs = [
      installShellFiles
      git
    ];

    buildType = &quot;release&quot;;

    name = with builtins; (fromTOML (readFile ./helix-term/Cargo.toml)).package.name;
    src = fs.toSource {
      root = ./.;
      fileset = src;
    };

    # Helix attempts to reach out to the network and get the grammars. Nix doesn&apos;t allow this.
    HELIX_DISABLE_AUTO_GRAMMAR_BUILD = &quot;1&quot;;

    # So Helix knows what rev it is.
    HELIX_NIX_BUILD_REV = gitRev;

    doCheck = false;
    strictDeps = true;

    # Sets the Helix runtime dir to the grammars
    env.HELIX_DEFAULT_RUNTIME = &quot;${runtimeDir}&quot;;

    # Get all the application stuff in the output directory.
    postInstall = &apos;&apos;
      mkdir -p $out/lib
      installShellCompletion ${./contrib/completion}/hx.{bash,fish,zsh}
      mkdir -p $out/share/{applications,icons/hicolor/{256x256,scalable}/apps}
      cp ${./contrib/Helix.desktop} $out/share/applications/Helix.desktop
      cp ${./logo.svg} $out/share/icons/hicolor/scalable/apps/helix.svg
      cp ${./contrib/helix.png} $out/share/icons/hicolor/256x256/apps/helix.png
    &apos;&apos;;

    meta.mainProgram = &quot;hx&quot;;
  })
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Breaking Down &lt;code&gt;helix/default.nix&lt;/code&gt;&lt;/h3&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Click to Expand `helix/default.nix` breakdown &lt;/summary&gt;
&lt;p&gt;This &lt;code&gt;default.nix&lt;/code&gt; file is a Nix derivation that defines how to build the Helix
editor itself. It’s designed to be called by the main &lt;code&gt;flake.nix&lt;/code&gt; as part of its
&lt;code&gt;packages&lt;/code&gt; output.&lt;/p&gt;
&lt;p&gt;Here’s a breakdown of its components:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Function Arguments&lt;/strong&gt;:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{
  lib,
  rustPlatform,
  callPackage,
  runCommand,
  installShellFiles,
  git,
  gitRev ? null,
  grammarOverlays ? [],
  includeGrammarIf ? _: true,
}:
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;lib&lt;/code&gt;: The Nixpkgs &lt;code&gt;lib&lt;/code&gt; (library) functions, essential for common operations
like &lt;code&gt;fileset&lt;/code&gt; and &lt;code&gt;strings&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;rustPlatform&lt;/code&gt;: A helper function from Nixpkgs specifically for building Rust
projects. It provides a &lt;code&gt;buildRustPackage&lt;/code&gt; function, which simplifies the
process significantly.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;callPackage&lt;/code&gt;: A Nixpkgs function used to instantiate a Nix expression (like
&lt;code&gt;grammars.nix&lt;/code&gt;) with its dependencies automatically supplied from the current
Nix environment.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;runCommand&lt;/code&gt;: A Nixpkgs primitive that creates a derivation by running a shell
command. It’s used here to construct the &lt;code&gt;runtimeDir&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;installShellFiles&lt;/code&gt;: A utility from Nixpkgs for installing shell completion
files.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;git&lt;/code&gt;: The Git package, needed for determining the &lt;code&gt;gitRev&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;gitRev ? null&lt;/code&gt;: The Git revision of the Helix repository. It’s an optional
argument, defaulting to null. This is passed in from the main &lt;code&gt;flake.nix&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;grammarOverlays ? []&lt;/code&gt;: An optional list of overlays for grammars, allowing
customization.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;includeGrammarIf ? _: true&lt;/code&gt;: An optional function to control which grammars are
included.&lt;/p&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;&lt;strong&gt;Local Variables&lt;/strong&gt; (&lt;code&gt;let ... in&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;let
  fs = lib.fileset;

  src = fs.difference (fs.gitTracked ./.) (fs.unions [
    ./.envrc
    ./rustfmt.toml
    ./screenshot.png
    ./book
    ./docs
    ./runtime
    ./flake.lock
    (fs.fileFilter (file: lib.strings.hasInfix &quot;.git&quot; file.name) ./.)
    (fs.fileFilter (file: file.hasExt &quot;svg&quot;) ./.)
    (fs.fileFilter (file: file.hasExt &quot;md&quot;) ./.)
    (fs.fileFilter (file: file.hasExt &quot;nix&quot;) ./.)
  ]);

  grammars = callPackage ./grammars.nix { inherit grammarOverlays includeGrammarIf; };
  runtimeDir = runCommand &quot;helix-runtime&quot; {} &apos;&apos;
    mkdir -p $out
    ln -s ${./runtime}/* $out
    rm -r $out/grammars
    ln -s ${grammars} $out/grammars
  &apos;&apos;;
in
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;fs = lib.fileset;&lt;/code&gt;: Aliases &lt;code&gt;lib.fileset&lt;/code&gt; for convenient file set operations.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;src&lt;/code&gt;: This is a crucial part. It defines the source files that will be used to
build Helix by:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Taking all Git-tracked files in the current directory (&lt;code&gt;fs.gitTracked ./.&lt;/code&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Excluding configuration files (e.g., &lt;code&gt;.envrc&lt;/code&gt;, &lt;code&gt;flake.lock&lt;/code&gt;), documentation
(&lt;code&gt;.md&lt;/code&gt;), images (&lt;code&gt;.svg&lt;/code&gt;), and Nix files (&lt;code&gt;.nix&lt;/code&gt;) using &lt;code&gt;fs.difference&lt;/code&gt; and
&lt;code&gt;fs.unions&lt;/code&gt;. This ensures a clean build input, reducing Nix store size and
avoiding unnecessary rebuilds.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;grammars&lt;/code&gt;: Builds syntax grammars by calling &lt;code&gt;grammars.nix&lt;/code&gt;, passing
&lt;code&gt;grammarOverlays&lt;/code&gt; (for customizing grammar builds) and &lt;code&gt;includeGrammarIf&lt;/code&gt; (a
filter for selecting grammars).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;runtimeDir&lt;/code&gt;: Creates a runtime directory for Helix by:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Symlinking the &lt;code&gt;runtime&lt;/code&gt; directory from the source.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Replacing the &lt;code&gt;grammars&lt;/code&gt; subdirectory with a symlink to the &lt;code&gt;grammars&lt;/code&gt;
derivation, ensuring Helix uses Nix-managed grammars.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;&lt;strong&gt;The Build Derivation&lt;/strong&gt; (&lt;code&gt;rustPlatform.buildRustPackage&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The core of this &lt;code&gt;default.nix&lt;/code&gt; is the &lt;code&gt;rustPlatform.buildRustPackage&lt;/code&gt; call,
which is a specialized builder for Rust projects:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;in
  rustPlatform.buildRustPackage (self: {
    cargoLock = {
      lockFile = ./Cargo.lock;
      # ... comments ...
      allowBuiltinFetchGit = true;
    };
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;cargoLock&lt;/code&gt;: Specifies how Cargo dependencies are handled.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;lockFile = ./Cargo.lock;&lt;/code&gt; Points to the &lt;code&gt;Cargo.lock&lt;/code&gt; file for reproducible
builds.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;allowBuiltinFetchGit = true&lt;/code&gt;: Allows Cargo to fetch Git dependencies directly
from repositories specified in &lt;code&gt;Cargo.lock&lt;/code&gt;. This is discouraged in Nixpkgs
because it can break build reproducibility, but it’s used here for convenience
during development, eliminating the need to manually specify &lt;code&gt;outputHashes&lt;/code&gt; for
Git dependencies.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;nativeBuildInputs = [
      installShellFiles
      git
    ];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;nativeBuildInputs&lt;/code&gt;: Are tools needed during the build process but not
necessarily at runtime.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;buildType = &quot;release&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;buildType&lt;/code&gt;: Specifies that Helix should be built in “release” mode (optimized).&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;name = with builtins; (fromTOML (readFile ./helix-term/Cargo.toml)).package.name;
    src = fs.toSource {
      root = ./.;
      fileset = src;
    };
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;name&lt;/code&gt;: Dynamically sets the package name by reading it from the &lt;code&gt;Cargo.toml&lt;/code&gt;
file.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;src&lt;/code&gt;: Uses the &lt;code&gt;src&lt;/code&gt; file set defined earlier as the source for the build.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# Helix attempts to reach out to the network and get the grammars. Nix doesn&apos;t allow this.
    HELIX_DISABLE_AUTO_GRAMMAR_BUILD = &quot;1&quot;;

    # So Helix knows what rev it is.
    HELIX_NIX_BUILD_REV = gitRev;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Environment Variables&lt;/strong&gt;: Sets environment variables that Helix uses.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;HELIX_DISABLE_AUTO_GRAMMAR_BUILD = &quot;1&quot;&lt;/code&gt;: Prevents Helix from downloading
grammars during the build, as Nix’s sandboxed environment disallows network
access. Instead, grammars are provided via the &lt;code&gt;runtimeDir&lt;/code&gt; derivation.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;HELIX_NIX_BUILD_REV = gitRev&lt;/code&gt;: Embeds the specified Git revision (or &lt;code&gt;null&lt;/code&gt; if
unspecified) into the Helix binary, allowing Helix to display its version or
commit hash.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;doCheck = false;
   strictDeps = true;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;doCheck = false;&lt;/code&gt;: Skips running tests during the build. This is common for
faster builds, especially in CI/CD, but tests are often run in a separate
&lt;code&gt;checks&lt;/code&gt; output (as seen in the &lt;code&gt;flake.nix&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;&lt;code&gt;strictDeps = true;&lt;/code&gt;: Ensures that all dependencies are explicitly declared.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# Sets the Helix runtime dir to the grammars
env.HELIX_DEFAULT_RUNTIME = &quot;${runtimeDir}&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# Sets the Helix runtime dir to the grammars
env.HELIX_DEFAULT_RUNTIME = &quot;${runtimeDir}&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;env.HELIX_DEFAULT_RUNTIME&lt;/code&gt;: Tells Helix where to find its runtime files
(including the Nix-managed grammars).&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# Get all the application stuff in the output directory.
postInstall = &apos;&apos;
  mkdir -p $out/lib
  installShellCompletion ${./contrib/completion}/hx.{bash,fish,zsh}
  mkdir -p $out/share/{applications,icons/hicolor/{256x256,scalable}/apps}
  cp ${./contrib/Helix.desktop} $out/share/applications/Helix.desktop
  cp ${./logo.svg} $out/share/icons/hicolor/scalable/apps/helix.svg
  cp ${./contrib/helix.png} $out/share/icons/hicolor/256x256/apps/helix.png
&apos;&apos;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;postInstall&lt;/code&gt;: A shell script that runs after the main build is complete. This
is used for installing additional files that are part of the Helix distribution
but not directly built by Cargo.&lt;/p&gt;
&lt;p&gt;Installs shell completion files (&lt;code&gt;hx.bash&lt;/code&gt;, &lt;code&gt;hx.fish&lt;/code&gt;, &lt;code&gt;hx.zsh&lt;/code&gt;). This enables
tab completion.&lt;/p&gt;
&lt;p&gt;Installs desktop entry files (&lt;code&gt;Helix.desktop&lt;/code&gt;) and icons (&lt;code&gt;logo.svg&lt;/code&gt;,
&lt;code&gt;helix.png&lt;/code&gt;) for desktop integration for GUI environments.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;    meta.mainProgram = &quot;hx&quot;;

})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;meta.mainProgram&lt;/code&gt;: Specifies the primary executable provided by this package,
allowing &lt;code&gt;nix run&lt;/code&gt; to automatically execute &lt;code&gt;hx&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;A lot going on in this derivation!&lt;/p&gt;
&lt;/details&gt;
&lt;h3&gt;Making Actual Changes&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Locate the &lt;code&gt;packages&lt;/code&gt; output section. It looks like this:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;packages = eachSystem (system: {
      inherit (pkgsFor.${system}) helix;
      /*
      The default Helix build. Uses the latest stable Rust toolchain, and unstable
      nixpkgs.

      The build inputs can be overridden with the following:

      packages.${system}.default.override { rustPlatform = newPlatform; };

      Overriding a derivation attribute can be done as well:

      packages.${system}.default.overrideAttrs { buildType = &quot;debug&quot;; };
      */
      default = self.packages.${system}.helix;
    });
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;Modify the &lt;code&gt;default&lt;/code&gt; package. The comments actually tell us exactly how to do
this. We want to use &lt;code&gt;overrideAttrs&lt;/code&gt; to change the &lt;code&gt;buildType&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Change this line:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;default = self.packages.${system}.helix;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;default = self.packages.${system}.helix.overrideAttrs { buildType = &quot;debug&quot;; };
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;This tells Nix to take the standard Helix package definition and override one
of its internal attributes (&lt;code&gt;buildType&lt;/code&gt;) to “debug” instead of “release”.&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;Build the “Hacked” Helix:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix build
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Nix will now rebuild Helix, but this time, it will compile it in debug mode.
You’ll likely notice the build takes a bit longer, and the resulting binary
will be larger due to the included debugging symbols.&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;Run the Debug Binary:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;./result/bin/hx
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;You’re now running your custom-built debug version of Helix! This is useful if
you were, for example, attatching a debugger.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is a simple yet powerful “hack” that demonstrates how easily you can modify
the behavior of a package defined within a Nix flake without changing the
original source code or the upstream flake directly. You’re simply telling Nix
how you’d like your version of the package to be built.&lt;/p&gt;
&lt;h3&gt;Another way to Modify Behavior&lt;/h3&gt;
&lt;p&gt;Since we are already familiar with the structure and behavior of Helix’s
&lt;code&gt;flake.nix&lt;/code&gt;, we can leverage that understanding to create our own Nix flake. By
analyzing how Helix organizes its &lt;code&gt;inputs&lt;/code&gt;, &lt;code&gt;outputs&lt;/code&gt;, and package definitions,
we gain the confidence to modify and extend a flake’s functionality to suit our
specific needs—whether that’s customizing builds, adding overlays, or
integrating with home-manager.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create a &lt;code&gt;flake.nix&lt;/code&gt; in your own directory (outside the helix repo):&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{
  description = &quot;Customized Helix build with debug features&quot;;

  inputs = {
    helix.url = &quot;github:helix-editor/helix&quot;;
    nixpkgs.url = &quot;github:nixos/nixpkgs/nixos-unstable&quot;;
    rust-overlay = {
      url = &quot;github:oxalica/rust-overlay&quot;;
      inputs.nixpkgs.follows = &quot;nixpkgs&quot;;
    };
  };
  outputs = {
    self,
    helix,
    nixpkgs,
    rust-overlay,
  }: let
    system = &quot;x86_64-linux&quot;;
    pkgs = import nixpkgs {
      system = system;
      overlays = [rust-overlay.overlay.overlays.default];
    };
  in {
    packages.${system}.default = helix.packages.${system}.helix.overrideAttrs (old: {
      buildType = &quot;debug&quot;;

      # Add additional cargo features
      cargoBuildFlags =
        (old.cargoBuildFlags or [])
        ++ [
          &quot;--features&quot;
          &quot;tokio-console&quot;
        ];

      # Inject custom RUSTFLAGS
      RUSTFLAGS = (old.RUSTFLAGS or &quot;&quot;) + &quot; -C debuginfo=2 -C opt-level=1&quot;;
    });
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Check it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix flake check
warning: creating lock file &apos;&quot;/home/jr/world/flake.lock&quot;&apos;:
• Added input &apos;helix&apos;:
    &apos;github:helix-editor/helix/8961ae1dc66633ea6c9f761896cb0d885ae078ed?narHash=sha256-f14perPUk%2BH15GyGRbg0Akqhn3rxFnc6Ez5onqpzu6A%3D&apos; (2025-05-29)
• Added input &apos;helix/nixpkgs&apos;:
    &apos;github:nixos/nixpkgs/5135c59491985879812717f4c9fea69604e7f26f?narHash=sha256-Vr3Qi346M%2B8CjedtbyUevIGDZW8LcA1fTG0ugPY/Hic%3D&apos; (2025-02-26)
• Added input &apos;helix/rust-overlay&apos;:
    &apos;github:oxalica/rust-overlay/d342e8b5fd88421ff982f383c853f0fc78a847ab?narHash=sha256-3SdPQrZoa4odlScFDUHd4CUPQ/R1gtH4Mq9u8CBiK8M%3D&apos; (2025-02-27)
• Added input &apos;helix/rust-overlay/nixpkgs&apos;:
    follows &apos;helix/nixpkgs&apos;
• Added input &apos;nixpkgs&apos;:
    &apos;github:nixos/nixpkgs/96ec055edbe5ee227f28cdbc3f1ddf1df5965102?narHash=sha256-7doLyJBzCllvqX4gszYtmZUToxKvMUrg45EUWaUYmBg%3D&apos; (2025-05-28)
• Added input &apos;rust-overlay&apos;:
    &apos;github:oxalica/rust-overlay/405ef13a5b80a0a4d4fc87c83554423d80e5f929?narHash=sha256-k0nhPtkVDQkVJckRw6fGIeeDBktJf1BH0i8T48o7zkk%3D&apos; (2025-05-30)
• Added input &apos;rust-overlay/nixpkgs&apos;:
    follows &apos;nixpkgs&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;nix flake check&lt;/code&gt; command will generate a &lt;code&gt;flake.lock&lt;/code&gt; file if one doesn’t
exist, and the warnings you see indicate that new inputs are being added and
locked to specific versions for reproducibility. This is expected behavior for
a new or modified flake.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Inspect the outputs:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix flake show
path:/home/jr/world?lastModified=1748612128&amp;amp;narHash=sha256-WEYtptarRrrm0Jb/0PJ/b5VPqLkCk5iEenjbKYU4Xm8%3D
└───packages
    └───x86_64-linux
        └───default: package &apos;helix-term&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The &lt;code&gt;└───packages&lt;/code&gt; line indicates that our flake exposes a top-level
&lt;code&gt;packages&lt;/code&gt; attribute.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;└───x86_64-linux&lt;/code&gt;: System architecture specificity&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;└───default: package &apos;helix-term&apos;&lt;/code&gt; Signifies that within the &lt;code&gt;x86_64-linux&lt;/code&gt;
packages, there’s a package named &lt;code&gt;default&lt;/code&gt;. This is a special name that
allows you to omit the package name when using commands like &lt;code&gt;nix build&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;package &apos;helix-term&apos;&lt;/code&gt; This is the most direct confirmation of our “hack”. It
tells us that our &lt;code&gt;default&lt;/code&gt; package is &lt;code&gt;helix-term&lt;/code&gt;. This confirms that our
&lt;code&gt;overrideAttrs&lt;/code&gt; in the &lt;code&gt;packages.${system}.default&lt;/code&gt; section successfully
targeted and modified the Helix editor package, which is internally named
&lt;code&gt;helix-term&lt;/code&gt; by the Helix flake.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;What This Does&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;overrideAttrs&lt;/code&gt; lets you change &lt;em&gt;only&lt;/em&gt; parts of the derivation without
rewriting everything.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;buildType = &quot;debug&quot;&lt;/code&gt; enables debug builds.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;cargoBuildFlags&lt;/code&gt; adds extra features passed to Cargo, e.g.,
&lt;code&gt;--features tokio-console&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;RUSTFLAGS&lt;/code&gt; gives you even more control over compiler behavior, optimization
levels, etc.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Run It&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix run
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or drop into the dev shell:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix develop
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;(assuming you also wire in a &lt;code&gt;devShells&lt;/code&gt; output)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Adding the &lt;code&gt;devShells&lt;/code&gt; output&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;Since we already have the helix flake as an input to our own &lt;code&gt;flake.nix&lt;/code&gt; we can
now forward or extend Helix’s &lt;code&gt;devShells&lt;/code&gt; like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;outputs = { self, nixpkgs, helix, rust-overlay, ... }: {
  devShells = helix.devShells;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or if you want to pick a specific system:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;outputs = { self, nixpkgs, helix, rust-overlay ... }:
  let
    system = &quot;x86_64-linux&quot;;
  in {
    devShells.${system} = helix.devShells.${system};
  };
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Optional: Combine with your own&lt;/strong&gt; &lt;code&gt;devShell&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;You can also extend or merge it with your own shell like so:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;outputs = { self, nixpkgs, helix, rust-overlay, ... }:
  let
    system = &quot;x86_64-linux&quot;;
    pkgs = import nixpkgs { inherit system; };
  in {
    devShells.${system} = {
      default = pkgs.mkShell {
        name = &quot;my-shell&quot;;
        inputsFrom = [ helix.devShells.${system}.default ];
        buildInputs = [ pkgs.git ];
      };
    };
  };
&lt;/code&gt;&lt;/pre&gt;
</content></entry><entry><title>Cachix devour-flake</title><id>https://saylesss88.github.io/nix/cachix_devour.html</id><updated>2025-12-05T00:00:00+00:00</updated><author><name>saylesss87@proton.me (saylesss88)</name></author><link href="https://saylesss88.github.io/nix/cachix_devour.html" rel="alternate"/><content type="html">&lt;h1&gt;Cachix and the devour-flake&lt;/h1&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Table of Contents&lt;/summary&gt;
&lt;!-- toc --&gt;
&lt;/details&gt;
&lt;p&gt;Using devour-flake to Cache All Your Flake Outputs to Cachix&lt;/p&gt;
&lt;p&gt;When working with Nix flakes, it’s common to have many outputs: packages, apps,
dev shells, NixOS or Darwin configurations, and more. Efficiently building and
caching all these outputs can be challenging, especially in CI or when
collaborating. This is where devour-flake and Cachix shine.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why Use the devour-flake?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;By default, building all outputs of a flake with &lt;code&gt;nix build .#a .#b ... .#z&lt;/code&gt; can
be slow and inefficient, as Nix will evaluate the flake multiple times, once for
each output. devour-flake solves this by generating a “consumer” flake that
depends on all outputs, allowing you to build everything in one go with a single
evaluation&lt;/p&gt;
&lt;h2&gt;Installation&lt;/h2&gt;
&lt;p&gt;There quite a few ways to do this, choose a method of installation from the
&lt;a href=&quot;https://github.com/srid/devour-flake&quot;&gt;devour-flake&lt;/a&gt; repository and then
continue with step 1.&lt;/p&gt;
&lt;p&gt;Nix will only download binaries from binary caches if they are cryptographically
signed with any of the keys listed in &lt;code&gt;nix.settings.trusted-public-keys&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# This is the default
nix.settings.require-sigs = true;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;nix.settings = {
  builders-use-substitutes = true;
  substituters = [
    &quot;https://cache.nixos.org&quot;
  ];
  trusted-public-keys = [
    &quot;cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=&quot;
  ];
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can even build it without installing with the following command:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix build github:srid/devour-flake \
  -L --no-link --print-out-paths \
  --override-input flake path/to/flake | cachix push &amp;lt;name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix-shell -p cachix
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will push all flake outputs to cachix if you have a valid authentication
token and have created a cache already.&lt;/p&gt;
&lt;p&gt;How to Use devour-flake with Cachix&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Prerequisites&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;A Cachix cache&lt;/strong&gt;: Create one on &lt;a href=&quot;https://www.cachix.org/&quot;&gt;Cachix&lt;/a&gt; and
generate a “Write + Read” auth token. You’ll click the cache you just created
and select Settings, in the settings you’ll find Auth Tokens. When in the Auth
Tokens section give your token a Description, Expiration date, and finally
click Generate.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;(Optional) Configure your token locally, copy your auth token for the following
command:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cachix authtoken &amp;lt;YOUR_TOKEN&amp;gt;
# Use cachix cli for the following
cachix use your-cache-name
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;cachix use&lt;/code&gt; adds your substitutors and trusted-public-keys to your
&lt;code&gt;~/.config/nix/nix.conf&lt;/code&gt; and creates one if it doesn’t exist.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Push All Flake Inputs to Cachix&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Replace &lt;code&gt;&amp;lt;mycache&amp;gt;&lt;/code&gt; with the name of the cache you just created.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix flake archive --json \
  | jq -r &apos;.path,(.inputs|to_entries[].value.path)&apos; \
  | cachix push &amp;lt;mycache&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You should see output similar to the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;Pushing 637 paths (2702 are already present) using zstd to cache sayls8 ⏳

✓ /nix/store/0aqvmjvhkar3j2f7zag2wjl4073apnvk-vimplugin-crates.nvim-2025-05-30 (734.65 KiB)
✓ /nix/store/02wm10zck7rb836kr0h3afjxl80866dp-X-Restart-Triggers-keyd (184.00 B)
✓ /nix/store/0asdaaax0lf1wa6m6lqqdvc8kp6qn3f6-dconf-cleanup (1008.00 B)
✓ /nix/store/09ki2jlh6sqbn01yw6n15h8d55ihxygf-helix-tree-sitter-mojo-3d7c53b8038f9ebbb57cd2e61296180aa5c1cf64 (601.37 KiB)
✓ /nix/store/0i2c29nldqvb9pnypvp3ika4i7fhc0ck-devour-output (312.00 B)
✓ /nix/store/0c0mwfb78xm862a7g4h9fhgzn55zppj6-helix-term (29.88 MiB)
✓ /nix/store/0fhdpb2qck1kbngq1dlc8lyqqadj2pb1-hyprcursor-0.1.12+date=2025-06-05_45fcc10-lib (487.30 KiB)
✓ /nix/store/0mfpi51bswgd91l8clqcz6mxy5k5zcd4-vimplugin-auto-pairs-2019-02-27 (40.60 KiB)
✓ /nix/store/0k2zq8y78vrhhkf658j6i45vz3y89v11-helix-tree-sitter-tcl-56ad1fa6a34ba800e5495d1025a9b0fda338d5b8 (110.25 KiB)
✓ /nix/store/0qxmahrw935136dbxkmvrg14fgnzi6bb-vimplugin-obsidian.nvim-2025-07-01 (493.02 KiB)
✓ /nix/store/0wjppqzcbnlf9srhr6k27pz403j3mg2j-hm-session-vars.sh (1.86 KiB)
✓ /nix/store/0z41071z33zg1zqyasccc3cfhxj389k0-helix-tree-sitter-swift-57c1c6d6ffa1c44b330182d41717e6fe37430704 (2.77 MiB)
✓ /nix/store/0n5f1x8lpc93zm81bxrfh6yccyngvrdl-unit-plymouth-read-write.service (1.19 KiB)
✓ /nix/store/0z8ac35n89lv2knzaj6kkp0cfxr6pmgc-hm_face.png (300.60 KiB)
✓ /nix/store/0zp5846pry5rknnvzz81zlvj4ghnkxp5-hyprutils-0.8.1+date=2025-07-07_a822973 (421.64 KiB)
✓ /nix/store/118ihgwjw6kp0528igns3pnvzbszljmg-unit-dbus.service (1.34 KiB)
✓ /nix/store/0pajdq9mfgkcdwbqp38j7d4clc9h9iik-hm_.mozillafirefoxdefault.keep (112.00 B)
✓ /nix/store/0nlvffvpx6s8mpd2rpnqb1bl5idd16yk-hm-dconf.ini (224.00 B)
✓ /nix/store/1fiqgqvi574rdckav0ikdh8brwdhvh69-vimplugin-alpha-nvim-2025-05-26 (69.38 KiB)
✓ /nix/store/1fqxw31p1llag0g7wg7izq22x5msz47r-vimplugin-persistence.nvim-2025-02-25 (37.74
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;❗ NOTE: The effectiveness of pushing the rest to cachix depend on your
network speed. I actually noticed a slow down after pushing the &lt;code&gt;nix/store&lt;/code&gt;.
Pushing the &lt;code&gt;nix/store&lt;/code&gt; is rarely necessary and can be very slow and
bandwidth-intensive. Most users will only need to push relevant outputs.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Push the Entire /nix/store&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix path-info --all | cachix push &amp;lt;mycache&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Pushing shell environment&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix develop --profile dev-profile -c true
# then run
cachix push &amp;lt;mycache&amp;gt; dev-profile
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;For the Flake way of doing things you would create something like the
following:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{
  config,
  lib,
  pkgs,
  ...
}: let
  cfg = config.custom.cachix;
in {
  options = {
    custom.cachix.enable = lib.mkEnableOption &quot;Enable custom cachix configuration&quot;;
  };

  config = lib.mkIf cfg.enable {
    environment.systemPackages = with pkgs; [cachix];

    # to prevent garbage collection of outputs immediately after building
    nix.extraOptions = &quot;gc-keep-outputs = true&quot;;
    nix.settings = {
      substituters = [
        &quot;https://nix-community.cachix.org&quot;
        &quot;https://hyprland.cachix.org&quot;
        &quot;https://ghostty.cachix.org&quot;
        &quot;https://neovim-nightly.cachix.org&quot;
        &quot;https://yazi.cachix.org&quot;
        &quot;https://helix.cachix.org&quot;
        &quot;https://nushell-nightly.cachix.org&quot;
        &quot;https://wezterm.cachix.org&quot;
        &quot;https://sayls88.cachix.org&quot;
        # &quot;https://nixpkgs-wayland.cachix.org&quot;
      ];
      trusted-public-keys = [
        &quot;nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs=&quot;
        &quot;hyprland.cachix.org-1:a7pgxzMz7+chwVL3/pzj6jIBMioiJM7ypFP8PwtkuGc=&quot;
        &quot;ghostty.cachix.org-1:QB389yTa6gTyneehvqG58y0WnHjQOqgnA+wBnpWWxns=&quot;
        &quot;neovim-nightly.cachix.org-1:feIoInHRevVEplgdZvQDjhp11kYASYCE2NGY9hNrwxY=&quot;
        &quot;yazi.cachix.org-1:Dcdz63NZKfvUCbDGngQDAZq6kOroIrFoyO064uvLh8k=&quot;
        &quot;helix.cachix.org-1:ejp9KQpR1FBI2onstMQ34yogDm4OgU2ru6lIwPvuCVs=&quot;
        &quot;nushell-nightly.cachix.org-1:nLwXJzwwVmQ+fLKD6aH6rWDoTC73ry1ahMX9lU87nrc=&quot;
        &quot;wezterm.cachix.org-1:kAbhjYUC9qvblTE+s7S+kl5XM1zVa4skO+E/1IDWdH0=&quot;
        &quot;sayls88.cachix.org-1:LT8JnboX8mKhabC3Mj/ONHb5tyrjlnsdauQkD8Lu0us=&quot;
        # &quot;nixpkgs-wayland.cachix.org-1:3lwxaILxMRkVhehr5StQprHdEo4IrE8sRho9R9HOLYA=&quot;
      ];
    };
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The sayls8 entries are my custom cache. To find your trusted key go to the
cachix website, click on your cache and it is listed near the top.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I enable this with &lt;code&gt;custom.cachix.enable = true;&lt;/code&gt; in my &lt;code&gt;configuration.nix&lt;/code&gt; or
equivalent.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Another option is to use the top-level &lt;code&gt;nixConfig&lt;/code&gt; attribute for adding your
substitutors and trusted-public-keys. You only need to choose 1 method FYI:&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{
  description = &quot;NixOS &amp;amp; Flake Config&quot;;

# the nixConfig here only affects the flake itself, not the system configuration!
  nixConfig = {
    experimental-features = [ &quot;nix-command&quot; &quot;flakes&quot; ];
    trusted-users = [ &quot;ryan&quot; ];

    substituters = [
      # replace official cache with a mirror located in China
      &quot;https://mirrors.ustc.edu.cn/nix-channels/store&quot;
      &quot;https://cache.nixos.org&quot;
    ];

    # nix community&apos;s cache server
    extra-substituters = [
      &quot;https://nix-community.cachix.org&quot;
      &quot;https://nixpkgs-wayland.cachix.org&quot;
    ];
    extra-trusted-public-keys = [
      &quot;cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=&quot;
      &quot;nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs=&quot;
      &quot;nixpkgs-wayland.cachix.org-1:3lwxaILxMRkVhehr5StQprHdEo4IrE8sRho9R9HOLYA=&quot;
    ];
  };
# ... snip
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;❗️ WARNING: &lt;code&gt;trusted-users&lt;/code&gt; is a list of users who are allowed to bypass many
of Nix’s normal safety restrictions and change daemon-level settings. Keep
&lt;code&gt;trusted-users&lt;/code&gt; as small as possible (often just &lt;code&gt;root&lt;/code&gt; and maybe your own
user on a single-user box)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;Building and Caching All Outputs&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You can build and push all outputs of your flake to Cachix using the following
command when in your flake directory:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix build github:srid/devour-flake \
 -L --no-link --print-out-paths \
 --override-input flake . \
 | cachix push &amp;lt;your-cache-name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Replace &lt;code&gt;your-cache-name&lt;/code&gt; with your actual Cachix cache name.&lt;/p&gt;
&lt;p&gt;This command will:&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Use devour-flake to enumerate and build all outputs of your flake (including
packages, devShells, NixOS configs, etc.)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Pipe the resulting store paths to cachix push, uploading them to your binary
cache.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;Example&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Suppose your cache is named my-flake-cache:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix build github:srid/devour-flake \
 -L --no-link --print-out-paths \
 --override-input flake . \
 | cachix push my-flake-cache
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;Integration in CI&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This approach is particularly useful in CI pipelines, where you want to ensure
all outputs are built and cached for collaborators and future builds. You can
add the above command to your CI workflow, ensuring the Cachix auth token is
provided as a secret&lt;/p&gt;
&lt;ol start=&quot;5&quot;&gt;
&lt;li&gt;Advanced: Using as a Nix App&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You can add devour-flake as an input to your flake for local development:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{
  inputs = {
    devour-flake.url = &quot;github:srid/devour-flake&quot;;
    devour-flake.flake = false;
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And in your flake’s &lt;code&gt;outputs&lt;/code&gt;, add an overlay that makes &lt;code&gt;devour-flake&lt;/code&gt;
available in your package set:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;outputs = { self, nixpkgs, devour-flake, ... }@inputs: {
  overlays.default = final: prev: {
    devour-flake = import devour-flake { inherit (prev) pkgs; };
  };

  # Example: Add devour-flake to your devShell
  devShells.x86_64-linux.default = let
    pkgs = import nixpkgs {
      system = &quot;x86_64-linux&quot;;
      overlays = [ self.overlays.default ];
    };
  in pkgs.mkShell {
    buildInputs = [ pkgs.devour-flake ];
  };
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Use devour-flake in your devShell:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix develop
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You’ll have the &lt;code&gt;devour-flake&lt;/code&gt; command available for local use, so you can
quickly build and push all outputs as needed.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;TIP: Alternatively, use &lt;code&gt;devour-flake&lt;/code&gt; as an app:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;apps.x86_64-linux.devour-flake = {
 type = &quot;app&quot;;
 program = &quot;${self.packages.x86_64-linux.devour-flake}/bin/devour-flake&quot;;
};

&lt;/code&gt;&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;p&gt;What Gets Built and Cached?&lt;/p&gt;
&lt;p&gt;&lt;code&gt;devour-flake&lt;/code&gt; detects and builds all standard outputs of a flake, including:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;packages&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;apps&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;checks&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;devShells&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;nixosConfigurations.*&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;darwinConfigurations.*&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;home-manager configurations&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This ensures that everything your flake produces is available in your Cachix
cache for fast, reproducible builds.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;References:&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/srid/devour-flake&quot;&gt;devour-flake documentation&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://discourse.nixos.org/t/how-to-set-up-cachix-in-flake-based-nixos-config/31781&quot;&gt;Discourse Cachix for Flakes&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.cachix.org/installation#flakes&quot;&gt;Cachix docs: Flakes&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.tweag.io/blog/2020-06-25-eval-cache/#:~:text=The%20overhead%20for%20creating%20the,nixpkgs%20blender%20takes%204.9%20seconds.&quot;&gt;Tweag Evaluation Caching&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://scrive.github.io/nix-workshop/06-infrastructure/01-caching-nix.html&quot;&gt;Scrive Caching&lt;/a&gt;&lt;/p&gt;
</content></entry><entry><title>Whonix KVM on NixOS</title><id>https://saylesss88.github.io/nix/whonix_kvm.html</id><updated>2025-12-04T00:00:00+00:00</updated><author><name>saylesss87@proton.me (saylesss88)</name></author><link href="https://saylesss88.github.io/nix/whonix_kvm.html" rel="alternate"/><content type="html">&lt;h1&gt;Whonix KVM on NixOS&lt;/h1&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Table of Contents&lt;/summary&gt;
&lt;!-- toc --&gt;
&lt;/details&gt;
&lt;p&gt;&lt;img src=&quot;https://saylesss88.github.io/../images/swappy-20250901-101339.cleaned.png&quot; alt=&quot;Whonix Logo&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;⚠️ WARNING: There is no general software that can guarantee absolute anonymity
or security; perfect security is a myth. Security is a continuous process, not
a one-time product. It also depends on time and resources: if an adversary has
enough of either, eventual compromise is probable. However, by layering
defenses and following best practices, we can make attacks costly and
time-consuming, deterring all but highly targeted adversaries.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It is highly recommended to harden your Host Machine as Type 2 hypervisors are
only as secure as their host. KVM is actually a type 1 hypervisor but relies on
QEMU for emulation which is a Type 2 hypervisor. This actually makes it a sort
of hybrid in between Type 1 and Type 2 but is in theory less secure than running
a Xen hypervisor (Type 1) on bare-metal.&lt;/p&gt;
&lt;p&gt;Whonix offers many benefits, including the convenience of running within your
current operating system without needing to reboot or use a separate Tails USB.
It provides similar strong anonymity protections by routing all traffic through
Tor in isolated virtual machines. The Whonix documentation is transparent about
its limitations, which helps build trust and confidence in its security model.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.whonix.org/wiki/Comparison_with_Others&quot;&gt;Whonix Compared to Tails&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Tails is great but they add an add blocker to Tor that makes every Tails user
unique from the rest of Tor Browser users reducing anonymity.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;⚠️ Never rely solely on the Virtual Machine to protect you, if your host OS
isn’t secure a Virtual Machine won’t protect you. If you have high threat
model, you may want to choose a Host with better support for AppArmor and
Selinux as they are highly limited on NixOS.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That being said, there is a lot you can do to harden NixOS…&lt;/p&gt;
&lt;h3&gt;Harden NixOS and set up GnuPG&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://saylesss88.github.io/nix/hardening_NixOS.html&quot;&gt;Hardening NixOS&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://saylesss88.github.io/nix/hardening_networking.html&quot;&gt;Hardening Networking&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://saylesss88.github.io/nix/gpg-agent.html&quot;&gt;GnuPG and gpg-agent on NixOS&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;A Few Things to Consider when using Whonix&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;No activity conducted inside &lt;code&gt;Whonix-Workstation&lt;/code&gt; can cause IP/DNS leaks so
long as &lt;code&gt;Whonix-Gateway&lt;/code&gt; is left unchanged or only documented changes are made
like configuring bridges, establishing onion services and running updates.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Whonix does not and does not claim to protect users against advanced
adversaries such as nation state actors, if they target you, you will be
infected! If used correctly, Whonix can provide partial protection against
passive surveillance programs, it all depends on whether Tor can provide
adequate protection or not, which is not clear at this time.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You shouldn’t use a VPN with Whonix and it is obvious that you’re using Tor
because connections are made to known Tor Relays, which are publicly listed
and identifiable.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;⚠️It is impossible to Hide Tor use from the internet service provider (ISP).
It has been concluded this goal is difficult beyond practicality.
–&lt;a href=&quot;https://www.whonix.org/wiki/Hide_Tor_from_your_Internet_Service_Provider&quot;&gt;Whonix Hide Tor from your ISP&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Millions of people use Tor daily for wholly legitimate reasons, particularly
to assert their privacy rights when faced with countless corporate /
government network observers and censors.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;True anonymity is very difficult to successfully pull off and not something
that you can maintain for a long time.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.whonix.org/wiki/Tips_on_Remaining_Anonymous&quot;&gt;Whonix Tips for remaining Anonymous&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;🔑 Key Terms&lt;/h2&gt;
&lt;p&gt;Whonix is an operating system based on Debian base (Kicksecure Hardened) and the
Tor network, which is designed for maximum anonymity and security. Whonix
consists of two Debian based VMs, the &lt;code&gt;Whonix-Gateway&lt;/code&gt; and &lt;code&gt;Whonix-Workstation&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;In this case NixOS is the &lt;strong&gt;Host Operating System&lt;/strong&gt;, NixOS runs the KVM kernel
module, libvirtd service, and QEMU virtualization service which together enable
hosting VMs. It is recommended to harden the host before moving on.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Guests&lt;/strong&gt; are the virtualized operating systems running inside the host’s
virtual machines. In this case the Whonix VMs are the &lt;strong&gt;Guest Machines&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Whonix-Gateway&lt;/code&gt; the first of 2 VMs runs Tor processes and forces all traffic
through the Tor network using iptables.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Whonix-Workstation&lt;/code&gt; the second VM, is responsible for running user applications
such as the Tor Browser. The Whonix-Workstation is isolated from both the
Whonix-Gateway and the Host OS, if an app misbehaves, it is contained within the
isolated Whonix-Workstation. It is largely unaware of sensitive info and won’t
leak unless an advanced adversary is able to break out of the VM.&lt;/p&gt;
&lt;p&gt;The primary goal of Whonix is to be safer than Tor alone and that no one can
find out the user’s IP, location, or de-anonymize the user. It offers full
spectrum anti-tracking protection that is much safer than VPNs. Whonix provides
this through security by isolation, no app is trusted.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Whonix Concept&lt;/code&gt;: Whonix is an Isolating Proxy with an additional Transparent
Proxy, which can be optionally disabled. –Whonix Docs&lt;/p&gt;
&lt;p&gt;Since Whonix is based on Kicksecure which is based on Debian stable, you can
typically look up solutions in a Kicksecure, Debian, or Ubuntu forum.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The Whonix Team recommends KVM over VirtualBox for a number of
reasons:&lt;a href=&quot;https://www.whonix.org/wiki/KVM#Why_Use_KVM_Over_VirtualBox?&quot;&gt;Why choose KVM over VirtualBox&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you really want to use VirtualBox, I got it working off of this config:&lt;/p&gt;
&lt;p&gt;VirtualBox = Type 2 hypervisor&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Click to Expand VirtualBox Example &lt;/summary&gt;
&lt;p&gt;Change &lt;code&gt;your-user&lt;/code&gt; to your username&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# vbox.nix
{
  config,
  lib,
  ...
}: let
  cfg = config.custom.virtualbox;
in {
  options.custom.virtualbox = {
    enable = lib.mkEnableOption &quot;Enable VirtualBox&quot;;
  };

  config = lib.mkIf cfg.enable {
    virtualisation.virtualbox.host = {
      enable = false;
      # enableExtensionPack = true;
    };

    user.user.your-user.extraGroups = [&quot;vboxusers&quot;];

    boot.kernelModules =
      if config.hardware.cpu.amd.updateMicrocode
      then [&quot;kvm-amd&quot;]
      else [&quot;kvm-intel&quot;];
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Enable it with &lt;code&gt;custom.virtualbox.enable = true;&lt;/code&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.whonix.org/wiki/VirtualBox&quot;&gt;Whonix VBox Download&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;After rebuilding with virtualbox enabled and downloading the virtualbox whonix,
open VirtualBox and import the Whonix file.&lt;/p&gt;
&lt;p&gt;Fix the error:: VirtualBox can’t enable the AMD-V extension. Please disable the
KVM kernel extension:&lt;/p&gt;
&lt;p&gt;If both of these are active, they compete with each other:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo lsmod | grep -E &apos;kvm|vbox&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Check the currently in use modules:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;modprobe -r kvm
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Disable kvm and kvm_amd:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo rmmod kvm_amd
sudo rmmod kvm
# To re-enable them when necessary
# sudo modprobe kvm
# sudo modprobe kvm_amd
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://atetux.com/quick-fix-virtualbox-cant-enable-the-amd-v-extension&quot;&gt;Quick fix&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There is an opposite viewpoint,
&lt;a href=&quot;https://www.whonix.org/wiki/Dev/VirtualBox#Why_use_VirtualBox_over_KVM?&quot;&gt;Why choose VirtualBox over KVM&lt;/a&gt;&lt;/p&gt;
&lt;/details&gt;
&lt;h2&gt;Whonix-Gateway&lt;/h2&gt;
&lt;p&gt;The whonix-gateway is software designed to run Tor.&lt;/p&gt;
&lt;p&gt;The Gateway acts as a firewall and is what is routing all your traffic through
Tor.&lt;/p&gt;
&lt;p&gt;You will spend minimal time in the Gateway, it’s mainly used for Tor
configuration which is reserved for advanced users.&lt;/p&gt;
&lt;h3&gt;Whonix-Workstation&lt;/h3&gt;
&lt;p&gt;All user applications should only be launched from Whonix-Workstation to ensure
they utilize the Tor network. (Never launch the Tor browser or any other user
app from Whonix-Gateway.)&lt;/p&gt;
&lt;p&gt;Leaky applications can’t breakout of the Workstation, all network connections
are forced to go through the Whonix-Gateway where they are torrified and routed
to the internet.&lt;/p&gt;
&lt;h2&gt;Whonix KVM (Kernel Virtual Machine) on NixOS&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;KVM&lt;/strong&gt; (Kernel-based Virtual Machine) is a Linux kernel module that provides
hardware-assisted virtualization.&lt;/p&gt;
&lt;p&gt;It allows the Linux kernel to act as a hypervisor, enabling virtual machines
(VMs) to run with near-native speeds by using CPU virtualization extensions
(Intel VT-x or AMD-V).&lt;/p&gt;
&lt;p&gt;KVM itself doesn’t handle the entire VM lifecycle; it provides the core
virtualization infrastructure.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;QEMU&lt;/strong&gt; (Quick Emulator) is an open-source user-space program that emulates
hardware for virtual machines.&lt;/p&gt;
&lt;p&gt;When combined with KVM, QEMU uses hardware acceleration to run VMs much faster
by offloading CPU virtualization to KVM.&lt;/p&gt;
&lt;p&gt;So, QEMU provides the device emulation and VM management interface, while KVM
provides the fast virtualization engine within the kernel.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Install Qemu-KVM&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{
  config,
  pkgs,
  ...
}: {
  ##  QEMU-KVM
  environment.systemPackages = with pkgs; [
    qemu
    # Optional
    virt-viewer
  ];

  # Virt-Manager GUI
  programs.virt-manager.enable = true;
  virtualisation = {
    # libvirtd daemon
    libvirtd = {
      enable = true;
      qemu = {
        # enables a TPM emulator
        swtpm.enable = true;
      };
    };
    # allow USB device to be forwarded
    spiceUSBRedirection.enable = true;
  };
  # Spice protocol improves VM display and input responsiveness
  services.spice-vdagentd.enable = true;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;p&gt;The &lt;strong&gt;libvirtd&lt;/strong&gt; is the primary daemon (service) in the libvirt virtualization
management system. It runs on your host machine and acts as the core management
component for virtual machines (VMs).&lt;/p&gt;
&lt;p&gt;Add &lt;code&gt;libvirtd&lt;/code&gt; &amp;amp; &lt;code&gt;kvm&lt;/code&gt; to your users &lt;code&gt;extraGroups&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;users.users = {
    your-user = {
        extraGroups = [
            &quot;libvirtd&quot;
            &quot;kvm&quot;
        ];
    };
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Restart &lt;code&gt;libvirtd&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo systemctl restart libvirtd
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;Network Start&lt;/h2&gt;
&lt;p&gt;Ensure KVM’s / QEMU’s default network is enabled and has started:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo virsh -c qemu:///system net-autostart default
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo virsh -c qemu:///system net-start default
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h3&gt;Download Whonix (KVM) (stable)&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.whonix.org/download/libvirt/17.4.4.6/Whonix-Xfce-17.4.4.6.Intel_AMD64.qcow2.libvirt.xz&quot;&gt;Whonix (KVM) (stable) Download&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Go to &lt;a href=&quot;https://www.whonix.org/wiki/KVM&quot;&gt;whoniix.org&lt;/a&gt; to verify the signature.
Download the &lt;code&gt;OpenPGP Signature&lt;/code&gt;, and the &lt;code&gt;Download Whonix OpenPGP Key&lt;/code&gt;. Your
Downloads directory will look like this:&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;~/Downloads󰏫 ls
╭───┬───────────────────────────────────────────────────────┬──────┬─────────┬───────────────╮
│ # │                         name                          │ type │  size   │   modified    │
├───┼───────────────────────────────────────────────────────┼──────┼─────────┼───────────────┤
│ 0 │ Whonix-Xfce-17.4.4.6.Intel_AMD64.qcow2.libvirt.xz     │ file │  3.3 GB │ 2 minutes ago │
│ 1 │ Whonix-Xfce-17.4.4.6.Intel_AMD64.qcow2.libvirt.xz.asc │ file │  1.0 kB │ a minute ago  │
│ 2 │ derivative.asc                                        │ file │ 77.3 kB │ 3 minutes ago │
╰───┴───────────────────────────────────────────────────────┴──────┴─────────┴───────────────╯
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Import &lt;code&gt;derivative.asc&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;gpg --import derivative.asc
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Verify the Public Key:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;gpg --verify Whonix-Xfce-17.4.4.6.Intel_AMD64.qcow2.libvirt.xz.asc Whonix-Xfce-17.4.4.6.Intel_AMD64.qcow2.libvirt.xz
gpg: Signature made Sun 10 Aug 2025 09:04:13 AM EDT
gpg:                using RSA key 6E979B28A6F37C43BE30AFA1CB8D50BB77BB3C48
gpg: Good signature from &quot;Patrick Schleizer &amp;lt;adrelanos@kicksecure.com&amp;gt;&quot; [unknown]
gpg:                 aka &quot;Patrick Schleizer &amp;lt;adrelanos@riseup.net&amp;gt;&quot; [unknown]
gpg:                 aka &quot;Patrick Schleizer &amp;lt;adrelanos@whonix.org&amp;gt;&quot; [unknown]
gpg: WARNING: This key is not certified with a trusted signature!
gpg:          There is no indication that the signature belongs to the owner.
Primary key fingerprint: 916B 8D99 C38E AF5E 8ADC  7A2A 8D66 066A 2EEA CCDA
     Subkey fingerprint: 6E97 9B28 A6F3 7C43 BE30  AFA1 CB8D 50BB 77BB 3C48
~/Downloads󰏫                                                                                                 09/04/2025 11:53:10 AM
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now &lt;code&gt;gpg --list-keys&lt;/code&gt; will show Patrick Schleizer’s Key.&lt;/p&gt;
&lt;p&gt;It is good practice to sign your verified key and then push it to the public
keyserver to contribute to the web of trust but optional.&lt;/p&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://www.whonix.org/wiki/KVM#Decompress&quot;&gt;Decompress the Image&lt;/a&gt; and follow
the rest of the Whonix KVM install instructions from there.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Nixpkgs doesn’t have the &lt;code&gt;xz-utils&lt;/code&gt; package but it does have the &lt;code&gt;xz&lt;/code&gt; package.&lt;/p&gt;
&lt;p&gt;Nixpkgs also has &lt;code&gt;nixpkgs.safe-rm&lt;/code&gt; if you wanted to follow the suggestions from
Whonix.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix-shell -p xz safe-rm
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;tar -xvf Whonix*.libvirt.xz
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h3&gt;Import the Whonix VM Templates&lt;/h3&gt;
&lt;p&gt;The following commands come directly from the
&lt;a href=&quot;https://www.whonix.org/wiki/KVM#Importing_Whonix_VM_Templates&quot;&gt;Whonix KVM Docs Importing Whonix VM Templates&lt;/a&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Add the virtual networks. This step only needs to be done once and not with
every upgrade.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo virsh -c qemu:///system net-define Whonix_external*.xml
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo virsh -c qemu:///system net-define Whonix_internal*.xml
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;Activate the virtual networks:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo virsh -c qemu:///system net-autostart Whonix-External
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo virsh -c qemu:///system net-start Whonix-External
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo virsh -c qemu:///system net-autostart Whonix-Internal
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo virsh -c qemu:///system net-start Whonix-Internal
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;Import the Whonix Gateway and Workstation images:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo virsh -c qemu:///system define Whonix-Gateway*.xml
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo virsh -c qemu:///system define Whonix-Workstation*.xml
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After the above steps, either copy or move the &lt;code&gt;qcow2&lt;/code&gt; images to
&lt;code&gt;/var/lib/libvirt/images&lt;/code&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;❗ It’s recommended to move the files into place, if you want to copy them you
need to use a special command FYI.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo mkdir -p /var/lib/libvirt/images
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo mv Whonix-Gateway*.qcow2 /var/lib/libvirt/images/Whonix-Gateway.qcow2
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo mv Whonix-Workstation*.qcow2 /var/lib/libvirt/images/Whonix-Workstation.qcow2
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Cleanup&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;safe-rm Whonix*
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;safe-rm -r WHONIX*
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Launch virt-manager and start the VMs&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;virt-manager
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;From here it will take a bit to load both VMs, you can click on one and go to
&lt;code&gt;Edit&lt;/code&gt;, &lt;code&gt;Virtual Machine Details&lt;/code&gt; and from there you have some options to give
the VM more CPUs and memory.&lt;/p&gt;
&lt;p&gt;Considering that the Whonix-Workstation is where all of the user applications
will be opened, it makes sense to give it more CPUs and memory.&lt;/p&gt;
&lt;p&gt;I’ve seen recommendations for a minimum of 4G of RAM for the Workstation and 2GB
for the Gateway.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Increase vCPU count for better performance&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Enable XML editing in settings&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Enable copy pasting by adding &lt;code&gt;&amp;lt;clipboard copypaste=&quot;yes&quot;/&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Start Whonix-Gateway&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://saylesss88.github.io/../images/swappy-20250901-101351.cleaned.png&quot; alt=&quot;Whonix Old Logo&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Always start the Whonix-Gateway first.&lt;/p&gt;
&lt;p&gt;Click on Whonix-Gateway, press Play, and choose the default Persistent VM.&lt;/p&gt;
&lt;p&gt;To view the gateway press &lt;code&gt;Open&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;You can use the “System Maintenance Panel” to &lt;code&gt;Check for Updates&lt;/code&gt; and then
&lt;code&gt;Install Updates&lt;/code&gt;. This can also be used for user and password creation, the
default user is &lt;code&gt;user&lt;/code&gt; with a passwordless login.&lt;/p&gt;
&lt;p&gt;Change the password manually:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo passwd
changeme
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Change the passwords and disable auto-login.&lt;/p&gt;
&lt;p&gt;Run a systemcheck if it wasn’t run automatically. Click the Xfce Logo and go to
&lt;code&gt;System&lt;/code&gt;, &lt;code&gt;System Check&lt;/code&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.whonix.org/wiki/Common_CLI_Commands&quot;&gt;Whonix Common CLI Commands&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Whonix-Workstation&lt;/h2&gt;
&lt;p&gt;Whonix-Workstation is another VM, designed to provide users with a secure and
anonymous environment for running applications and performing online tasks.&lt;/p&gt;
&lt;p&gt;When you first launch &lt;code&gt;Whonix-Workstation&lt;/code&gt;, choose the second option down or
reboot, and then choose “Persistent Mode Sysmaint Session”. From there, you can
go through the same steps as you did for the Gateway.&lt;/p&gt;
&lt;p&gt;With the workstation, a security feature disables &lt;code&gt;sudo&lt;/code&gt; for the default user.
Instead of the &lt;code&gt;user&lt;/code&gt; account, a separate &lt;code&gt;sysmaint&lt;/code&gt; (system maintenance)
account is used for administrative tasks that require root privileges, such as
updates and package installations.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Change all user passwords and disable auto-login&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;After you get your system updated and upgraded, you’ll want to reboot the
Workstation and start it in the first Persistent mode available rather than the
&lt;code&gt;sysmaint&lt;/code&gt; mode.&lt;/p&gt;
&lt;p&gt;Once Workstation is running and both VMs are updated and upgraded, check that
your IP address is a Tor IP:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;curl ip.me
#
curl ip.me
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each consecutive time that you run &lt;code&gt;curl ip.me&lt;/code&gt;, Tor establishes a new circuit
and you will get a different IP returned each time for as many Tor nodes are
available. Not that you would want to but it’s cool functionality giving us a
visual of the new circuit.&lt;/p&gt;
&lt;p&gt;Start Tor and check what you are fingerprinted as by typing &lt;code&gt;deviceinfo.me&lt;/code&gt; into
the URL.&lt;/p&gt;
&lt;h4&gt;Launching Tor Browser&lt;/h4&gt;
&lt;p&gt;Click the Xfce logo and choose Tor Browser. On the first launch, you will need
to update Tor by clicking in the top right corner.&lt;/p&gt;
&lt;p&gt;Or you can open the terminal and type:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;update-torbrowser
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Every time you run the above command, the old browser will be killed, along
with your old browser profile, including bookmarks and passwords. If the
update suggests a downgrade from your current version don’t do it, it is
likely a downgrade attack.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Make sure you don’t forget to go to the Settings, Privacy and Security, and set
the &lt;code&gt;Security Level&lt;/code&gt; to &lt;code&gt;Safest&lt;/code&gt; to disable JavaScript and more before exploring
the dark web.&lt;/p&gt;
&lt;p&gt;Visit &lt;code&gt;https://check.torproject.org&lt;/code&gt;, you should see “Congratulations. This
browser is configured to use Tor.”&lt;/p&gt;
&lt;p&gt;If you need a place to start, check out &lt;code&gt;https://tor.taxi&lt;/code&gt; by plugging that into
the URL. Always include the &lt;code&gt;https&lt;/code&gt; yourself!&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;❗ NOTE: Use HTTPS and TLS wherever possible, since Tor only encrypts traffic
as it travels through the network of three nodes. Traffic at Exit nodes is
vulnerable if unencrypted, because when it reaches the Exit node it is plain
text. Prefer the use of &lt;code&gt;.onion&lt;/code&gt; services because they form a tunnel that is
encrypted end-to-end, using a random rendezvous point within the Tor network;
HTTPS isn’t required within Onion services. Prefer the use of &lt;code&gt;.onion&lt;/code&gt;
services because they form a tunnel that is encrypted end-to-end, using a
random rendezvous point within the Tor network; HTTPS isn’t required within
Onion services.
–&lt;a href=&quot;https://www.whonix.org/wiki/Tor_Myths_and_Misconceptions#All_my_traffic_is_encrypted_by_default&quot;&gt;All my traffic is encrypted by default?&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Live Mode&lt;/h2&gt;
&lt;p&gt;To get Whonix to perform more similarly to Tails you could run Whonix in Live
Mode. Live Mode is a privacy-focused mode where nothing is saved at shutdown,
making it great for handling sensitive data.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.kicksecure.com/wiki/Live_Mode&quot;&gt;Live Mode&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Same process, reboot the Workstation and Choose
&lt;code&gt;LIVE Mode | USER Session | disposable use&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.kicksecure.com/wiki/Anti-Forensics_Precautions&quot;&gt;Anti Forensics Precautions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Download and Verify Kicksecure KVM&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.kicksecure.com/wiki/KVM&quot;&gt;Kicksecure KVM wiki&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.kicksecure.com/download/libvirt/17.4.4.6/Kicksecure-Xfce-17.4.4.6.Intel_AMD64.qcow2.libvirt.xz&quot;&gt;Download Kicksecure Xfce (KVM) (stable) (FREE!)&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.kicksecure.com/download/libvirt/17.4.4.6/Kicksecure-Xfce-17.4.4.6.Intel_AMD64.qcow2.libvirt.xz.asc&quot;&gt;Download OpenPGP Signature&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.kicksecure.com/keys/derivative.asc&quot;&gt;Download Kicksecure OpenPGP Key&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Import the &lt;code&gt;derivative.asc&lt;/code&gt; file:&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;gpg --import derivative.asc
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;5&quot;&gt;
&lt;li&gt;Make sure both files are done downloading and run the following to verify,
your file names might be slightly different:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;gpg --verify Kicksecure-Xfce-17.4.4.6.Intel_AMD64.qcow2.libvirt.xz.asc Kicksecure-Xfce-17.4.4.6.Intel_AMD64.qcow2.libvirt.xz
gpg: Signature made Sun 10 Aug 2025 07:32:52 AM EDT
gpg:                using RSA key 6E979B28A6F37C43BE30AFA1CB8D50BB77BB3C48
gpg: Good signature from &quot;Patrick Schleizer &amp;lt;adrelanos@kicksecure.com&amp;gt;&quot; [unknown]
gpg:                 aka &quot;Patrick Schleizer &amp;lt;adrelanos@riseup.net&amp;gt;&quot; [unknown]
gpg:                 aka &quot;Patrick Schleizer &amp;lt;adrelanos@whonix.org&amp;gt;&quot; [unknown]
gpg: WARNING: This key is not certified with a trusted signature!
gpg:          There is no indication that the signature belongs to the owner.
Primary key fingerprint: 916B 8D99 C38E AF5E 8ADC  7A2A 8D66 066A 2EEA CCDA
     Subkey fingerprint: 6E97 9B28 A6F3 7C43 BE30  AFA1 CB8D 50BB 77BB 3C48
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;6&quot;&gt;
&lt;li&gt;&lt;strong&gt;Decompress&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;tar -xvf Kicksecure*.libvirt.xz
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Don’t use &lt;code&gt;unxz&lt;/code&gt;!&lt;/p&gt;
&lt;h3&gt;Resources&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.whonix.org/wiki/Documentation&quot;&gt;Whonix Docs&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.whonix.org/wiki/About&quot;&gt;Whonix Overview&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.whonix.org/wiki/Dev/Technical_Introduction&quot;&gt;Whonix Technical Intro&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.kicksecure.com/wiki/Computer_Security_Introduction&quot;&gt;Kicksecure Computer Security Intro&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.kicksecure.com/wiki/Computer_Security_Introduction#Advanced_Security_Guide&quot;&gt;Kicksecure Advanced Security Guide&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;k
&lt;a href=&quot;https://www.kicksecure.com/wiki/System_Hardening_Checklist&quot;&gt;System Hardening Checklist&lt;/a&gt;&lt;/p&gt;
</content></entry><entry><title>Functions and NixOS Modules</title><id>https://saylesss88.github.io/functions/functions_and_modules_2.2.html</id><updated>2025-11-30T00:00:00+00:00</updated><author><name>saylesss87@proton.me (saylesss88)</name></author><link href="https://saylesss88.github.io/functions/functions_and_modules_2.2.html" rel="alternate"/><content type="html">&lt;h1&gt;Functions and NixOS Modules&lt;/h1&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Click to Expand Table of Contents&lt;/summary&gt;
&lt;!-- toc --&gt;
&lt;/details&gt;
&lt;p&gt;When you start exploring NixOS configurations or tools like Home Manager, you’ll
encounter a concept called Nix Modules. Modules are also functions, but they
behave differently regarding their arguments, which can be a source of
confusion.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What are NixOS Modules&lt;/strong&gt;?&lt;/p&gt;
&lt;p&gt;Nix Modules are a powerful system built on top of basic Nix functions, primarily
used for declarative system configurations (like NixOS, Home Manager, NixOps,
etc.). They allow you to define parts of your system configuration in separate
files that are then composed together.&lt;/p&gt;
&lt;p&gt;Each module is typically a Nix function that returns an attribute set with
specific keys like &lt;code&gt;options&lt;/code&gt;, &lt;code&gt;config&lt;/code&gt;, and &lt;code&gt;imports&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Automatic Arguments in Modules&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Unlike the functions we’ve been writing, Nix’s module system automatically
passes a standard set of arguments to every module function it evaluates. You
don’t explicitly pass these arguments when you &lt;code&gt;import&lt;/code&gt; a module file; the
module system handles it for you.&lt;/p&gt;
&lt;p&gt;The most common automatic arguments you’ll see are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;config&lt;/code&gt;: The aggregated configuration options of all modules combined. This
is what you use to read other configuration values.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;options&lt;/code&gt;: The definitions of all available configuration options across all
modules.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;pkgs&lt;/code&gt;: The standard Nixpkgs set, equivalent to &lt;code&gt;import &amp;lt;nixpkgs&amp;gt; {}&lt;/code&gt;. This is
incredibly convenient as you don’t need to import it in every module.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;lib&lt;/code&gt;: The Nixpkgs utility library (&lt;code&gt;pkgs.lib&lt;/code&gt;), providing helper functions
for common tasks.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;specialArgs&lt;/code&gt;: An attribute set of extra arguments to be passed to the module
functions.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A typical module might start like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# Example NixOS module
{ config, pkgs, lib, ... }: # These arguments are passed automatically by the module system
{
  # ... module options and configuration
  environment.systemPackages = [ pkgs.firefox pkgs.git ];
  services.nginx.enable = true;
  # ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the above module, the only required argument is &lt;code&gt;pkgs&lt;/code&gt; because we explicitly
use it in the module (i.e. &lt;code&gt;pkgs.firefox&lt;/code&gt;). Editors have pretty good support for
letting you know if you’re missing arguments or have unnecessary ones. &lt;code&gt;config&lt;/code&gt;,
and &lt;code&gt;lib&lt;/code&gt; and would be required if we were setting any options in this module.&lt;/p&gt;
&lt;p&gt;This automatic passing of arguments is a core feature of the module system that
simplifies writing configurations, as you always have access to &lt;code&gt;pkgs&lt;/code&gt;, &lt;code&gt;lib&lt;/code&gt;,
and the evolving &lt;code&gt;config&lt;/code&gt; and &lt;code&gt;options&lt;/code&gt; without boilerplate.&lt;/p&gt;
&lt;h4&gt;&lt;code&gt;specialArgs&lt;/code&gt;: Passing Custom Arguments to Modules&lt;/h4&gt;
&lt;p&gt;While the module system passes a standard set of arguments automatically, what
if you need to pass additional, custom data to your modules that isn’t part of
the standard &lt;code&gt;config&lt;/code&gt;, &lt;code&gt;pkgs&lt;/code&gt;, &lt;code&gt;lib&lt;/code&gt;, or &lt;code&gt;options&lt;/code&gt;? This is where &lt;code&gt;specialArgs&lt;/code&gt;
comes in.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;specialArgs&lt;/code&gt; is an attribute you can pass to the &lt;code&gt;import&lt;/code&gt; function when you
load a module (or a set of modules). It’s typically used to provide data that
your modules need but isn’t something Nixpkgs would normally manage.&lt;/p&gt;
&lt;p&gt;For example, in a &lt;code&gt;configuration.nix&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# From your configuration.nix
{ config, pkgs, lib, ... }: # Standard module arguments

let
  myCustomValue = &quot;helloWorld&quot;;
in
{
  # ... imports all modules, including your custom ones
  imports = [
    ./hardware-configuration.nix
    ./my-webserver-module.nix
  ];

  # This is where specialArgs would be used (often in import statements)
  # Example: passing a custom value to ALL modules:
  # (in module context, this is more complex, but conceptually)
  # let
  #   allModules = [ ./my-module.nix ];
  # in
  # lib.nixosSystem {
  #   modules = allModules;
  #   specialArgs = {
  #     username = &quot;johndoe&quot;;
  #     mySecretKey = &quot;/run/keys/ssh_key&quot;;
  #   };
  #   # ...
  # };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And then, inside &lt;code&gt;my-webserver-module.nix&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# my-webserver-module.nix
{ config, pkgs, lib, username, mySecretKey, ... }: # username and mySecretKey come from specialArgs
{
  # ... use username and mySecretKey in your module
  users.users.${username} = {
    isNormalUser = true;
    extraGroups = [ &quot;wheel&quot; &quot;networkmanager&quot; ];
    # ...
  };
  # ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Any argument listed in a module’s function signature that is not one of the
standard &lt;code&gt;config&lt;/code&gt;, &lt;code&gt;pkgs&lt;/code&gt;, &lt;code&gt;lib&lt;/code&gt;, &lt;code&gt;options&lt;/code&gt; (or &lt;code&gt;pkgs.callPackage&lt;/code&gt;, etc., which
are often implicit through &lt;code&gt;pkgs&lt;/code&gt;) must be provided via &lt;code&gt;specialArgs&lt;/code&gt; at the
point where the modules are composed.&lt;/p&gt;
&lt;p&gt;Any values listed in a module that aren’t automatically passed via Nixpkgs must
be explicitly provided through &lt;code&gt;specialArgs&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;specialArgs&lt;/code&gt; and &lt;code&gt;extraSpecialArgs&lt;/code&gt; with Flakes&lt;/h3&gt;
&lt;p&gt;NixOS modules use &lt;code&gt;specialArgs&lt;/code&gt; and Home-Manager uses &lt;code&gt;extraSpecialArgs&lt;/code&gt; to
allow you to pass extra arguments.&lt;/p&gt;
&lt;p&gt;Or with Flakes it would look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{
  description = &quot;My Flake&quot;;
  inputs = {
    nixpkgs.url = &quot;github:nixos/nixpkgs/nixos-unstable&quot;;
    home-manager.url = &quot;github:nix-community/home-manager&quot;;
    home-manager.inputs.nixpkgs.follows = &quot;nixpkgs&quot;;
   };

  outputs = { self, nixpkgs, home-manager, ... }:
    let
      lib = nixpkgs.lib;
      pkgs = nixpkgs.legacyPackages.${&quot;x86_64-linux&quot;};
      system = &quot;x86_64-linux&quot;;
  host = &quot;magic&quot;;
  username = &quot;jr&quot;;
  userVars = {
    timezone = &quot;America/New_York&quot;;
    locale = &quot;en_US.UTF-8&quot;;
    gitUsername = &quot;TSawyer87&quot;;
    dotfilesDir = &quot;~/.dotfiles&quot;;
    wm = &quot;hyprland&quot;;
    browser = &quot;firefox&quot;;
    term = &quot;ghostty&quot;;
    editor = &quot;hx&quot;;
    keyboardLayout = &quot;us&quot;;
  };
    in {
      nixosConfigurations = {
        YOURHOSTNAME = lib.nixosSystem {
          system = &quot;x86_64-linux&quot;;
          modules = [ ./configuration.nix ];
          specialArgs = {
            inherit userVars; # == userVars = userVars;
            inherit host;
            inherit username;
          };
        };
      };
      homeConfigurations = {
        USERNAME = home-manager.lib.homeManagerConfiguration {
          inherit pkgs;
          modules = [ ./home.nix ];
          extraSpecialArgs = {
            inherit userVars;
            inherit host;
            inherit username;
            # or it can be written like this:
            # inherit userVars host username;
          };
        };
      };
    };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now if I want to use any of these arguments in modules I can by any module file
referenced by my configuration.&lt;/p&gt;
&lt;p&gt;For example, the following is a &lt;code&gt;git.nix&lt;/code&gt; module that uses the variables from
the flake passed from &lt;code&gt;extraSpecialArgs&lt;/code&gt; in this case because it’s a
home-manager module:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# git.nix
{ userVars, ... }: {
  programs = {
    git = {
      enable = true;
      userName = userVars.gitUsername;
    };
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th style=&quot;text-align: left&quot;&gt;Feature&lt;/th&gt;&lt;th style=&quot;text-align: left&quot;&gt;Regular Nix Function (e.g., &lt;code&gt;hello.nix&lt;/code&gt;)&lt;/th&gt;&lt;th style=&quot;text-align: left&quot;&gt;Nix Module (e.g., &lt;code&gt;my-config-module.nix&lt;/code&gt;)&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: left&quot;&gt;&lt;strong&gt;Arguments&lt;/strong&gt;&lt;/td&gt;&lt;td style=&quot;text-align: left&quot;&gt;&lt;strong&gt;You must explicitly pass every single argument.&lt;/strong&gt;&lt;/td&gt;&lt;td style=&quot;text-align: left&quot;&gt;&lt;strong&gt;Automatically receives &lt;code&gt;config&lt;/code&gt;, &lt;code&gt;pkgs&lt;/code&gt;, &lt;code&gt;lib&lt;/code&gt;, &lt;code&gt;options&lt;/code&gt;, etc.&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: left&quot;&gt;&lt;strong&gt;Custom Args&lt;/strong&gt;&lt;/td&gt;&lt;td style=&quot;text-align: left&quot;&gt;Passed directly in the function call.&lt;/td&gt;&lt;td style=&quot;text-align: left&quot;&gt;Passed via &lt;code&gt;specialArgs&lt;/code&gt; when the modules are composed.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: left&quot;&gt;&lt;strong&gt;Boilerplate&lt;/strong&gt;&lt;/td&gt;&lt;td style=&quot;text-align: left&quot;&gt;Often needs &lt;code&gt;pkgs = import &amp;lt;nixpkgs&amp;gt; {};&lt;/code&gt; if not explicitly passed.&lt;/td&gt;&lt;td style=&quot;text-align: left&quot;&gt;&lt;code&gt;pkgs&lt;/code&gt; and &lt;code&gt;lib&lt;/code&gt; are always available automatically.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: left&quot;&gt;&lt;strong&gt;Purpose&lt;/strong&gt;&lt;/td&gt;&lt;td style=&quot;text-align: left&quot;&gt;Defines a package, a utility, or a single value.&lt;/td&gt;&lt;td style=&quot;text-align: left&quot;&gt;Defines a reusable part of a declarative system configuration.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
</content></entry><entry><title>Git</title><id>https://saylesss88.github.io/vcs/git.html</id><updated>2025-11-30T00:00:00+00:00</updated><author><name>saylesss87@proton.me (saylesss88)</name></author><link href="https://saylesss88.github.io/vcs/git.html" rel="alternate"/><content type="html">&lt;h1&gt;Version Control with Git&lt;/h1&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Click to Expand Table of Contents&lt;/summary&gt;
&lt;!-- toc --&gt;
&lt;/details&gt;
&lt;!-- ![Git Logo](../images/git1.png) --&gt;
&lt;p&gt;⚠️ &lt;strong&gt;Important&lt;/strong&gt;: Never commit secrets (passwords, API keys, tokens, etc.) in
plain text to your Git repository. If you plan to publish your NixOS
configuration, always use a secrets management tool like sops-nix or agenix to
keep sensitive data safe. See the
&lt;a href=&quot;https://saylesss88.github.io/installation/enc/sops-nix.html&quot;&gt;Sops-Nix Guide&lt;/a&gt;
for details.&lt;/p&gt;
&lt;p&gt;It’s also important to understand that &lt;strong&gt;all files in the &lt;code&gt;/nix/store&lt;/code&gt; are
world-readable by default&lt;/strong&gt; This has important security implications for anyone
managing sensitive data on a NixOS system.&lt;/p&gt;
&lt;p&gt;What Does “World-Readable” Mean?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;All files in /nix/store are readable by any user on the system.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;This is by design, the nix store is intended to be shared, immutable store of
all packages and configuration files.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Permissions are typically set to &lt;code&gt;r-xr-xr-x&lt;/code&gt;(read and execute for everyone)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Security Implications&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Never store secrets or sensitive data in plane text in the Nix store.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If you include secrets directly in your configuration, they will end up in the
&lt;code&gt;/nix/store&lt;/code&gt; and be accessible to any user or process on the system.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;This applies to files, environment variables, and any data embedded in
derivations.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Best Practices&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Always use a secrets management tool (like &lt;code&gt;sops-nix&lt;/code&gt; or &lt;code&gt;agenix&lt;/code&gt;) that
decrypts secrets at activation time and stores them outside the Nix store,
with restricted permissions.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Do not embed secrets directly in Nix expressions or configuration files that
will be build into the store.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Even hashed passwords can be vulnerable when stored in a public repository, be
conscious of what you store where.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If you’re unsure about what’s safe to share, start with a private repository.
This gives you time to learn about secrets management and review your
configuration before making anything public.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;First, I’ll briefly explain some of the limitations of NixOS Rollbacks and then
I’ll go into how Git compliments them.&lt;/p&gt;
&lt;h2&gt;Limitations of NixOS Rollbacks&lt;/h2&gt;
&lt;p&gt;NixOS is famous for its ability to roll back to previous system generations,
either from the boot menu or with commands like &lt;code&gt;nixos-rebuild --rollback&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;When you perform rollbacks in NixOS, whether from the boot menu or using
commands like &lt;code&gt;nixos-rebuild --rollback&lt;/code&gt; only the contents and symlinks managed
by the Nix store are affected. The rollback works by switching which system
generation is active, atomically updating symlinks to point to the previous
version of all packages, &lt;code&gt;systemd&lt;/code&gt; units and services stored in &lt;code&gt;/nix/store&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;However, it’s important to understand what these rollbacks actually do and what
they don’t do. What NixOS Rollbacks Cover&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;System generations: When you rebuild your system, NixOS creates a new
“generation” that you can boot into or roll back to. This includes all
packages, services, and system configuration managed by Nix.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Quick recovery: If an upgrade breaks your system, you can easily select an
older generation at boot and get back to a working state&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Key Limitations&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Configuration files are not reverted&lt;/strong&gt;: Rolling back only changes which
system generation is active, it does not revert your actual configuration
files (like &lt;code&gt;configuration.nix&lt;/code&gt; or your flake files)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;User data and service data are not rolled back&lt;/strong&gt;: Only files managed by Nix
are affected. Databases, user files, and other persistent data remain
unchanged, which can cause problems if, for example, a service migrates its
database schema during an upgrade&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Manual changes persist&lt;/strong&gt;: Any manual edits to configuration files or system
state outside of Nix are not reverted by a rollback&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;How Git Helps&lt;/h2&gt;
&lt;!-- ![Git Logo 2](../images/git3.png) --&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The &lt;a href=&quot;https://docs.github.com/en/github-cli/github-cli/quickstart&quot;&gt;gh-cli&lt;/a&gt;,
simplifies quite a few things for working with GitHub from the command line.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Tracks every configuration change&lt;/strong&gt;: By version-controlling your NixOS
configs with Git, you can easily see what changed, when, and why.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;True config rollback&lt;/strong&gt;: If a configuration change causes issues, you can use
&lt;code&gt;git checkout&lt;/code&gt; or &lt;code&gt;git revert&lt;/code&gt; to restore your config files to a previous good
state, then rebuild your system&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Safer experimentation&lt;/strong&gt;: You can confidently try new settings or upgrades,
knowing you can roll back both your system state (with NixOS generations) and
your config files (with Git).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Collaboration and backup&lt;/strong&gt;: Git lets you share your setup, collaborate with
others, and restore your configuration if your machine is lost or damaged.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In summary: NixOS rollbacks are powerful for system state, but they don’t manage
your configuration file history. Git fills this gap, giving you full control and
traceability over your NixOS configs making your system both robust and truly
reproducible. Version control is a fundamental tool for anyone working with
NixOS, whether you’re customizing your desktop, managing servers, or sharing
your configuration with others. Git is the most popular version control system
and is used by the NixOS community to track, share, and back up system
configurations.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why use Git with NixOS?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Track every change&lt;/strong&gt;: Git lets you record every modification to your
configuration files, so you can always see what changed, when, and why.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Experiment safely&lt;/strong&gt;: Try new settings or packages without fear—if something
breaks, you can easily roll back to a previous working state.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Sync across machines&lt;/strong&gt;: With Git, you can keep your NixOS setups in sync
between your laptop, desktop, or servers, and collaborate with others.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Disaster recovery&lt;/strong&gt;: Accidentally delete your config? With Git, you can
restore it from your repository in minutes.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Installing Git on NixOS&lt;/p&gt;
&lt;p&gt;You can install Git by adding it to your system packages in your
configuration.nix or via Home Manager:&lt;/p&gt;
&lt;h2&gt;Git Tips&lt;/h2&gt;
&lt;!-- ![Octocat](../images/octocat.png) --&gt;
&lt;p&gt;If you develop good git practices on your own repositories it will make it
easier to contribute with others as well as get help from others.&lt;/p&gt;
&lt;h2&gt;Atomic Commits&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Atomic commits&lt;/strong&gt; are a best practice in Git where each commit represents a
single, focused, and complete change to the codebase. The main characteristics
of atomic commits are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;One purpose&lt;/strong&gt;: Each commit should address only one logical change or task.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Complete&lt;/strong&gt;: The commit should leave the codebase in a working state.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Descriptive&lt;/strong&gt;: The commit message should be able to clearly summarize the
change in a single sentence.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Why Atomic Commits Matter&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Easier debugging&lt;/strong&gt;: You can use tools like &lt;code&gt;git bisect&lt;/code&gt; to quickly find
which commit introduced a bug, since each commit is isolated.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Simpler reverts&lt;/strong&gt;: You can revert without affecting unrelated changes.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Better collaboration&lt;/strong&gt;: Code reviews and merges are more manageable when
changes are small and focused.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When you lump together a bunch of changes into a single commit it can lead to
quite a few undesirable consequences. They make it harder to track down bugs,
it’s more difficult to revert undesired changes without reverting desired ones,
make larger tickets harder to manage.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Every time a logical component is completed, commit it&lt;/strong&gt;. Smaller commits make
it easier for other devs and yourself to understand the changes and roll them
back if necessary. This also makes it easier to share your code with others to
get help when needed and makes merge conflicts less frequent and complex.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Finish the component, then commit it&lt;/strong&gt;: There’s really no reason to commit
unfinished work, use &lt;code&gt;git stash&lt;/code&gt; for unfinished work and &lt;code&gt;git commit&lt;/code&gt; for when
the logical component is complete. Use common sense and break complex components
into logical chunks that can be finished quickly to allow yourself to commit
more often.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Write Good Commit Messages&lt;/strong&gt;: Begin with a summary of your changes, add a line
of whitespace between the summary and the body of your message. Make it clear
why this change was necessary. Use consistent language with generated messages
from commands like &lt;code&gt;git merge&lt;/code&gt; which is imperative and present tense
(&lt;code&gt;&amp;lt;&amp;lt;change&amp;gt;&amp;gt;&lt;/code&gt;, not &lt;code&gt;&amp;lt;&amp;lt;changed&amp;gt;&amp;gt;&lt;/code&gt; or &lt;code&gt;&amp;lt;&amp;lt;changes&amp;gt;&amp;gt;&lt;/code&gt;).&lt;/p&gt;
&lt;h3&gt;Tips for Keeping Commits Atomic with a Linear History&lt;/h3&gt;
&lt;p&gt;Squashing limits the benefits of atomic commits as it combines them all into a
single commit as if you didn’t take the time to write them all out atomically.&lt;/p&gt;
&lt;p&gt;🧠 Why Rebasing Wins for Linear History&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;No Merge Bubbles: Rebasing avoids those extra merge commits that clutter
&lt;code&gt;git log --graph&lt;/code&gt;. You get a clean, readable timeline.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Atomic Commit Integrity: Each commit stands alone and tells a story. Rebasing
preserves that narrative without diluting it with merge noise.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Better Blame &amp;amp; Bisect: Tools like git blame and git bisect work best when
history is linear and logical.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Time-Travel Simplicity: Cherry-picking or reverting is easier when commits
aren’t tangled in merge commits.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By default, when you run &lt;code&gt;git pull&lt;/code&gt; git merges the commits into your local repo.
To change this to a rebase you can set the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git config --global pull.rebase true
git config --global rebase.autoStash true
git config --global fetch.prune true  # auto delets remote-tracking branches that no longer exist
git config --global pull.ff only          # blocks merge pulls
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note: With pull.ff only pulls will fail if they would have had to merge. This
could happen if your local branch has diverged from the remote (e.g., someone
pushed new commits and you also committed locally) &lt;code&gt;git pull&lt;/code&gt; will throw an
error like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;fatal: Not possible to fast-forward, aborting.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;How to fix it&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You basically do what Git won’t auto-do:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git fetch origin
git rebase origin/main
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This rewinds your local commits, applies remote commits, and replays yours on
top, keeping the history linear.&lt;/p&gt;
&lt;p&gt;If you don’t care about your local changes and want to discard them you can use
the following command:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git reset --hard origin/main
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This just makes your branch identical to the remote, no rebase required. This
prevents rogue merge commits, preserving atomic commits and linear logs.&lt;/p&gt;
&lt;p&gt;You could set an alias for this with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git config --global alias.grs &apos;!git fetch origin &amp;amp;&amp;amp; git rebase origin/main&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To check whether a setting is active or now you can use:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git config --get rebase.autoStash
true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To set these options with home-manager:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# ... snip ...
    extraConfig = lib.mkOption {
      type = lib.types.attrs;
      default = {
        commit.gpgsign = true;
        gpg.format = &quot;ssh&quot;;
        user.signingkey = &quot;/etc/ssh/ssh_host_ed25519_key.pub&quot;;
        extraConfig = {
          pull = {
            rebase = true;
            ff = &quot;only&quot;;
        };
        };
        rebase = {
          autoStash = true; # Auto stashes and unstashes local changes during rebase
        };
        fetch = {
          prune = true; # Automatically deletes remote-tracking branches that no longer exist
        };
# ... snip ...
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Time Travel in Git&lt;/h2&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Click to Expand Time Travel Section &lt;/summary&gt;
&lt;p&gt;&lt;strong&gt;View an old commit&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git checkout &amp;lt;commit_hash&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This puts you in a “detached HEAD” state, letting you explore code as it was at
that commit. To return, checkout your branch again.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Go back and keep history (revert)&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git revert &amp;lt;commit_hash&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Go back and rewrite history (reset)&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Soft reset (keep changes staged):&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git reset --soft &amp;lt;commit_hash&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Mixed reset (keep changes in working directory):&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git reset &amp;lt;commit_hash&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Hard reset (discard all changes after the commit):&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git reset --hard &amp;lt;commit_hash&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Use the above command with caution, it can delete commits from history.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Relative time travel:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git reset --hard HEAD@{5.minutes.ago}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git reset --hard HEAD@{yesterday}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Create a branch from the past&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git checkout -b &amp;lt;new-brach&amp;gt; &amp;lt;commit_hash&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This starts a new branch from any previous commit, preserving current changes.&lt;/p&gt;
&lt;/details&gt;
&lt;p&gt;Some repositories have guidelines, such as Nixpkgs:&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Click to Expand Nixpkgs Commit Conventions &lt;/summary&gt;
&lt;p&gt;&lt;strong&gt;Commit conventions&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Create a commit for each logical unit.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Check for unnecessary whitespace with &lt;code&gt;git diff --check&lt;/code&gt; before committing.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If you have commits pkg-name: oh, forgot to insert whitespace: squash commits
in this case. Use &lt;code&gt;git rebase -i&lt;/code&gt;. See Squashing Commits for additional
information.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;For consistency, there should not be a period at the end of the commit
message’s summary line (the first line of the commit message).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;When adding yourself as maintainer in the same pull request, make a separate
commit with the message maintainers: &lt;code&gt;add &amp;lt;handle&amp;gt;&lt;/code&gt;. Add the commit before
those making changes to the package or module. See Nixpkgs Maintainers for
details.&lt;/p&gt;
&lt;p&gt;Make sure you read about any commit conventions specific to the area you’re
touching. See: Commit conventions for changes to &lt;code&gt;pkgs&lt;/code&gt;. Commit conventions
for changes to &lt;code&gt;lib&lt;/code&gt;. Commit conventions for changes to &lt;code&gt;nixos&lt;/code&gt;. Commit
conventions for changes to &lt;code&gt;doc&lt;/code&gt;, the Nixpkgs manual.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Writing good commit messages&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In addition to writing properly formatted commit messages, it’s important to
include relevant information so other developers can later understand why a
change was made. While this information usually can be found by digging code,
mailing list/Discourse archives, pull request discussions or upstream changes,
it may require a lot of work.&lt;/p&gt;
&lt;p&gt;Package version upgrades usually allow for simpler commit messages, including
attribute name, old and new version, as well as a reference to the relevant
release notes/changelog. Every once in a while a package upgrade requires more
extensive changes, and that subsequently warrants a more verbose message.&lt;/p&gt;
&lt;p&gt;Pull requests should not be squash merged in order to keep complete commit
messages and GPG signatures intact and must not be when the change doesn’t make
sense as a single commit.&lt;/p&gt;
&lt;/details&gt;
&lt;p&gt;A &lt;strong&gt;Git workflow&lt;/strong&gt; is a recipe or recommendation for how to use Git to
accomplish work in a consistent and productive manner. Having a defined workflow
lets you leverage Git effectively and consistently. This is especially important
when working on a team.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Origin&lt;/strong&gt; is the &lt;em&gt;default name&lt;/em&gt; (alias) for the &lt;strong&gt;remote repository&lt;/strong&gt; that your
&lt;strong&gt;local repository&lt;/strong&gt; is connected to, usually the one you cloned from.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Remote Repositories&lt;/strong&gt; are versions of your project that are hosted on the
internet or network somewhere.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;When you run &lt;code&gt;git push origin main&lt;/code&gt;, you’re telling Git to push your changes
to the remote repo called &lt;code&gt;origin&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You can see which URL &lt;code&gt;origin&lt;/code&gt; points to with &lt;code&gt;git remote -v&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You can have multiple remotes (like &lt;code&gt;origin&lt;/code&gt;, &lt;code&gt;upstream&lt;/code&gt;, etc.) each pointing
to a different remote repo. Each of which is generally either read-only or
read/write for you. Collaborating involves managing these remotes and pushing
and pulling data to and from them when you need to share work.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;❗ You can have a remote repo on your local machine. The word “remote” doesn’t
imply that the repository is somewhere else, only that it’s elsewhere.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;The name &lt;code&gt;origin&lt;/code&gt; is just a convention, it’s not special. It is automatically
set when you clone a repo.&lt;/li&gt;
&lt;/ul&gt;
&lt;!-- ![git local remote](../images/git_local-remote.png) --&gt;
&lt;p&gt;&lt;strong&gt;Local&lt;/strong&gt; is your local copy of the repository, git tracks the differences
between &lt;strong&gt;local&lt;/strong&gt; and &lt;strong&gt;remote&lt;/strong&gt; which is a repo hosted elsewhere (e.g., GitHub
GitLab etc.)&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;Upstream&lt;/strong&gt; in Git typically refers to the original repository from which
your local repository or fork was derived. The &lt;strong&gt;Upstream&lt;/strong&gt; is the remote repo
that serves as the main source of truth, often the original project you forked
from. You typically fetch changes from upstream to update your local repo with
the latest updates from the original project, but you don’t push to upstream
unless you have write access.&lt;/p&gt;
&lt;h3&gt;A Basic Git Workflow&lt;/h3&gt;
&lt;!-- ![Git logo 3](../images/git2.png) --&gt;
&lt;ol&gt;
&lt;li&gt;Initialize your Repository:&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If you haven’t already created a Git repo in your NixOS config directory (for
example, in your flake or &lt;code&gt;/etc/nixos&lt;/code&gt;):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd ~/flake
git init
git add .
git commit -m &quot;Initial commit: NixOS Configuration&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Taking this initial snapshot with Git is a best practice—it captures the exact
state of your working configuration before you make any changes.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The command &lt;code&gt;git add .&lt;/code&gt; stages all files in the directory (and its
subdirectories) for commit, meaning Git will keep track of them in your
project history.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The command &lt;code&gt;git commit -m &quot;message&quot;&lt;/code&gt; then saves a snapshot of these staged
files, along with your descriptive message, into the repository.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Think of a commit as a “save point” in your project. You can always go back
to this point if you need to, making it easy to experiment or recover from
mistakes. This two-step process, staging with &lt;code&gt;git add&lt;/code&gt; and saving with
&lt;code&gt;git commit&lt;/code&gt; is at the heart of how Git tracks and manages changes over
time.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;!-- ![git commit add](../images/git-add-commit.png) --&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;Make and Track Changes:&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now that you’ve saved a snapshot of your working configuration, you’re free to
experiment and try new things, even if they might break your setup.&lt;/p&gt;
&lt;p&gt;Suppose you want to try a new desktop environment, like Xfce. You edit your
&lt;code&gt;configuration.nix&lt;/code&gt; to add:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;services.xserver.desktopManager.xfce.enable = true;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo nixos-rebuild switch # if configuration.nix is in /etc/nixos/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But something goes wrong: the system boots, but your desktop is broken or won’t
start. You decide to roll back using the boot menu or:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo nixos-rebuild switch --rollback
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;What happens?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Your system reverts to the previous working generation in &lt;code&gt;/nix/store&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;But: Your &lt;code&gt;configuration.nix&lt;/code&gt; file is still changed, it still has the line
enabling Xfce. If you rebuild again, you’ll get the same broken system,
because your config itself wasn’t rolled back.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;How does Git Help on Failure?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Git gives you quite a few options and ways to inspect what has been done.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Use &lt;code&gt;git status&lt;/code&gt; to see what’s changed, and &lt;code&gt;git checkout -- &amp;lt;file&amp;gt;&lt;/code&gt; to
restore any file to its last committed state.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Review your changes with &lt;code&gt;git diff&lt;/code&gt; to see exactly what you modified before
deciding whether to keep or revert those changes.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Reset everything with &lt;code&gt;git reset --hard HEAD&lt;/code&gt;, this will discard all local
changes and return to your last commit.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With Git you can simply run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git checkout HEAD~1 configuration.nix
# or, if you committed before the change:
git revert &amp;lt;commit-hash&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Show the full hash of the latest commit:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git rev-parse HEAD
f53fef375d89496c0174e70ce94993d43335098e
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Short hash:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git log --pretty=format:&apos;%h&apos; -n 1
f53fef3
git revert f53fef3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Show a list of Recent commits:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git log
# a list of all commits, with hashes, author, date, and message
git log --oneline
git log --oneline
f53fef3 (HEAD -&amp;gt; main) thunar
b34ea22 thunar
801cbcf thunar
5e72ba5 sops
8b67c59 sops
1a353cb sops
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can copy the commit hash from any of these and use it in commands like
&lt;code&gt;git checkout &amp;lt;hash&amp;gt;&lt;/code&gt; or &lt;code&gt;git revert &amp;lt;hash&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Commit successful experiments&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If your changes work, stage, and commit them:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git add .
# or more specifically the file you changed or created
git add configuration.nix
git commit -m &quot;Describe the new feature or fix&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Basic Branching&lt;/h3&gt;
&lt;p&gt;With Git you’re always on a branch and the default branch is &lt;code&gt;master&lt;/code&gt;. Many
change it to &lt;code&gt;main&lt;/code&gt; because of the suggestion Git gives you. I think people are
too easily offended these days, just keep this in mind that &lt;code&gt;main&lt;/code&gt; and &lt;code&gt;master&lt;/code&gt;
refer to the main development branch.&lt;/p&gt;
&lt;p&gt;You can get a listing of your current branches with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git branch
* (no branch)
  main
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;*&lt;/code&gt; is next to the current branch and is where the &lt;code&gt;HEAD&lt;/code&gt; is currently
pointing. It says &lt;code&gt;(no branch)&lt;/code&gt; because I’m currently in detached &lt;code&gt;HEAD&lt;/code&gt; where
&lt;code&gt;HEAD&lt;/code&gt; points to no branch. The reason for this is because I’ve been trying out
Jujutsu VCS and that’s JJ’s default setting, a detached &lt;code&gt;HEAD&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Git actually gives you a warning about working in a detached &lt;code&gt;HEAD&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;You are in &apos;detached HEAD&apos; state. You can make experimental
changes and commit them, and you can discard any commits you make
in this state without impacting any branch by switching back.

If you want to create a new branch to retain commits you create,
you can do so now (using &apos;git switch -c &amp;lt;new-branch-name&amp;gt;&apos;) or
later (using &apos;git branch &amp;lt;new-branch-name&amp;gt; &amp;lt;commit-id&amp;gt;&apos;).

See &apos;git help switch&apos; for details.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To attach the &lt;code&gt;HEAD&lt;/code&gt; (i.e., have the pointer pointing to a branch), use the
&lt;code&gt;git checkout&lt;/code&gt; command&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git checkout main
Switched to branch &apos;main&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git branch
* main
# Ensure that you have the latest &quot;tip&quot; from the remote repository `origin`
git fetch origin main
From github.com:sayls8/flake
 * branch            main       -&amp;gt; FETCH_HEAD
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Although we’re working on our own repo and there is basically no chance of our
local branch diverging from our remote, it’s still good to get in the practice
of getting everything in sync before merging or rebasing etc.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;git fetch&lt;/code&gt; doesn’t update &lt;code&gt;main&lt;/code&gt;, it just updates your references. To update
&lt;code&gt;main&lt;/code&gt; you would use &lt;code&gt;git pull origin/main&lt;/code&gt; or &lt;code&gt;git rebase origin/main&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;You can inspect your upstream branches with the following command:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git remote show origin
* remote origin
  Fetch URL: git@github.com:saylesss88/flake.git
  Push  URL: git@github.com:saylesss88/flake.git
  HEAD branch: main
  Remote branch:
    main tracked
  Local ref configured for &apos;git push&apos;:
    main pushes to main (fast-forwardable)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;* branch     main      -&amp;gt; FETCH_HEAD&lt;/code&gt;: This line signifies that the &lt;code&gt;main&lt;/code&gt;
branch from the remote repository (likely &lt;code&gt;origin&lt;/code&gt;) was successfully fetched,
and the commit ID of its current tip (its latest commit) is now stored in your
local &lt;code&gt;FETCH_HEAD&lt;/code&gt; reference.&lt;/p&gt;
&lt;p&gt;Now that we know our local &lt;code&gt;main&lt;/code&gt; is up to date with our remote &lt;code&gt;origin/main&lt;/code&gt; we
can safely create a new feature branch:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git checkout -b feature/prose_wrap
Switched to a new branch &apos;feature/prose_wrap&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Right now the branch &lt;code&gt;feature/prose_wrap&lt;/code&gt; is exactly the same as &lt;code&gt;main&lt;/code&gt; and we
can safely make changes without affecting &lt;code&gt;main&lt;/code&gt;. We can try crazy or even
“dangerous” things and always be able to revert to a working state with
&lt;code&gt;git checkout main&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If our crazy idea works out, we can then merge our feature branch into &lt;code&gt;main&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Ok the feature works, I’ve added and committed the change. Now it’s time to
point the &lt;code&gt;HEAD&lt;/code&gt; to &lt;code&gt;main&lt;/code&gt; and then either merge or rebase the feature branch
into &lt;code&gt;main&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git checkout main
git fetch origin main
git merge feature/prose_wrap
Updating c8bd54c..b281f79
Fast-forward
 home/editors/helix/default.nix | 69 +++++++++++++++++++++++++++++++--------------------------------------
 1 file changed, 31 insertions(+), 38 deletions(-)
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;“fast-forward” means that our &lt;code&gt;feature/prose_wrap&lt;/code&gt; branch was directly ahead
of the last commit on &lt;code&gt;main&lt;/code&gt;. When you merge one commit with another commit
that can be reached by following the first commits history, remember the
feature branch is exactly the same as &lt;code&gt;main&lt;/code&gt; until I made another commit. If
the branches diverged more and the history can’t be followed, Git will perform
a 3-way merge where it creates a new “merge commit” that combines the 2
changes.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you have a bunch of branches and forget which have been merged yet use:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git branch --merged
feature/prose_wrap
* main
# OR to see branches that haven&apos;t been merged use:
git branch --no-merged
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It’s now safe to delete the feature branch:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git branch -d feature/prose_wrap
Deleted branch feature/prose_wrap (was b281f79)
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;❗ TIP: If your feature branch has a lot of sloppy commits that won’t be of
much benefit to anyone, squash them first then merge. The workflow would look
something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt; # Make sure you&apos;re on the main branch
 git checkout main

 # Merge the feature branch with squash
 git merge --squash feature/prose_wrap
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;This combines all the commits in your branch and adds them to your &lt;code&gt;main&lt;/code&gt;
staging area, it doesn’t move HEAD or create a merge commit for you. To
apply the changes into one big commit, finalize it with:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt; git commit -m &quot;Add prose wrapping feature&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is often referred to as the “squash commit”.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Branching means to diverge from the main line of development and continue to do
work without risking messing up your main branch. There are a few commits on
your main branch so to visualize this it would look something like this, image
is from &lt;a href=&quot;https://git-scm.com/book/en/v2&quot;&gt;Pro Git&lt;/a&gt;:&lt;/p&gt;
&lt;!-- ![Git Branch 1](../images/git-branch3.png) --&gt;
&lt;h2&gt;Nix flake update example with branches&lt;/h2&gt;
&lt;p&gt;Let’s say you haven’t ran &lt;code&gt;nix flake update&lt;/code&gt; in a while and you don’t want to
introduce errors to your working configuration. To do so we can first, make sure
we don’t lose any changes on our main branch:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git add .
git commit -m &quot;Staging changes before switching branches&quot;
# I always like to make sure the configuration will build before pushing to git
sudo nixos-rebuild switch --flake .
# If everything builds and looks correct
git push origin main
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;OR, if you have incomplete changes that you don’t want to commit yet you can
stash them with &lt;code&gt;git stash&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git status
On branch main
Your branch is ahead of &apos;origin/main&apos; by 1 commit.
  (use &quot;git push&quot; to publish your local commits)

Changes not staged for commit:
  (use &quot;git add &amp;lt;file&amp;gt;...&quot; to update what will be committed)
  (use &quot;git restore &amp;lt;file&amp;gt;...&quot; to discard changes in working directory)
        modified:   home/git.nix

no changes added to commit (use &quot;git add&quot; and/or &quot;git commit -a&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we want to switch branches, without committing the incomplete changes to
&lt;code&gt;git.nix&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git stash
Saved working directory and index state WIP on main: 0e46d6b git: lol alias

git status
On branch main
Your branch is ahead of &apos;origin/main&apos; by 1 commit.
  (use &quot;git push&quot; to publish your local commits)

nothing to commit, working tree clean
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;❗ &lt;code&gt;git stash&lt;/code&gt; is equivalent to &lt;code&gt;git stash push&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;To see which stashes you have stored, use &lt;code&gt;git sash list&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git stash list
stash@{0}: WIP on main: 0e46d6b git: lol alias
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To apply the most recent stash:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git stash apply
git add home/git.nix
On branch main
Your branch is ahead of &apos;origin/main&apos; by 1 commit.
  (use &quot;git push&quot; to publish your local commits)

Changes not staged for commit:
  (use &quot;git add &amp;lt;file&amp;gt;...&quot; to update what will be committed)
  (use &quot;git restore &amp;lt;file&amp;gt;...&quot; to discard changes in working directory)
        modified:   home/git.nix

# or for multiple stashes
git stash apply stash@{2}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Running &lt;code&gt;git stash apply&lt;/code&gt; applies the changes that were in your stash but
doesn’t automatically restage them, to apply the changes and stage them in one
command:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git stash apply --index
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now let’s create our branch so we can safely update:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git checkout -b update-test
Switched to a new branch &apos;update-test&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;-b&lt;/code&gt; is to switch to the branch that was just created&lt;/p&gt;
&lt;p&gt;Some may prefer a more descriptive branch name such as: &lt;code&gt;update/flake-inputs&lt;/code&gt;, I
kept it short for the example. Or if your company uses an issue tracker,
including the ticket number in the branch name can be helpful:
&lt;code&gt;update/123-flake-inputs&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;The above command is equivalent to:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git branch update-test
git checkout update-test
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;del&gt;Now our branches would look something like this, note how both branches
currently point to the same commit:&lt;/del&gt; I discovered that Git Book has pretty
restrictive licensing and will eventually find a replacement.&lt;/p&gt;
&lt;!-- ![Git Branch 2](../images/git-branch2.png) --&gt;
&lt;p&gt;Now, lets run our update:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix flake update
sudo nixos-rebuild test --flake .
# If everything looks ok let&apos;s try applying the changes
sudo nixos-rebuild switch --flake .
# And if everything looks ok:
git add .
git commit -m &quot;feat: Updated all flake inputs&quot;
git push origin update-test
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;❗ This is the same workflow for commiting a PR. After you first fork and
clone the repo you want to work on, you then create a new feature branch and
push to that branch on your fork. This allows you to create a PR comparing
your changes to their existing configuration.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;del&gt;At this point our graph would look similar to the following&lt;/del&gt;:&lt;/p&gt;
&lt;!-- ![Git Branch 3](../images/git-branch1.png) --&gt;
&lt;p&gt;If we are satisfied, we can switch back to our &lt;code&gt;main&lt;/code&gt; branch and merge
&lt;code&gt;update-test&lt;/code&gt; into it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git checkout main
git merge origin/update-test
git branch -D update-test
sudo nixos-rebuild test --flake .
sudo nixos-rebuild switch --flake .
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It’s good practice to delete a branch after you’ve merged and are done with it.&lt;/p&gt;
&lt;h2&gt;Rebasing Branches&lt;/h2&gt;
&lt;p&gt;To combine two seperate branches into one unified history you typically use
&lt;code&gt;git merge&lt;/code&gt; or &lt;code&gt;git rebase&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;git merge&lt;/code&gt; takes two commit pointers and finds a common base commit between
them, it then creates a “merge commit” that combines the changes.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;git rebase&lt;/code&gt; is used to move a sequence of commits to a new base commit.&lt;/p&gt;
&lt;!-- ![Git rebase](../images/rebase.png) --&gt;
&lt;h2&gt;Configure Git Declaratively&lt;/h2&gt;
&lt;p&gt;The following example is the &lt;code&gt;git.nix&lt;/code&gt; from the hydenix project it shows some
custom options and a way to manage everything from a single location:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# git.nix from hydenix: declarative Git configuration for Home Manager
{ lib, config, ... }:

let
  cfg = config.hydenix.hm.git;
in
{

  options.hydenix.hm.git = {
    enable = lib.mkOption {
      type = lib.types.bool;
      default = config.hydenix.hm.enable;
      description = &quot;Enable git module&quot;;
    };

    name = lib.mkOption {
      type = lib.types.nullOr lib.types.str;
      default = null;
      description = &quot;Git user name&quot;;
    };

    email = lib.mkOption {
      type = lib.types.nullOr lib.types.str;
      default = null;
      description = &quot;Git user email&quot;;
    };
  };

  config = lib.mkIf cfg.enable {

    programs.git = {
      enable = true;
      userName = cfg.name;
      userEmail = cfg.email;
      extraConfig = {
        init.defaultBranch = &quot;main&quot;;
        pull.rebase = false;
      };
    };
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;❗ You can easily change the name of the option, everything after &lt;code&gt;config.&lt;/code&gt; is
custom. So you could change it to for example, &lt;code&gt;config.custom.git&lt;/code&gt; and you
would enable it with &lt;code&gt;custom.git.enable = true;&lt;/code&gt; in your &lt;code&gt;home.nix&lt;/code&gt; or
equivalent.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Then he has a &lt;code&gt;hm/default.nix&lt;/code&gt; with the following to enable it.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;#...snip...

 # hydenix home-manager options go here
  hydenix.hm = {
    #! Important options
    enable = true;
      git = {
        enable = true; # enable git module
        name = null; # git user name eg &quot;John Doe&quot;
        email = null; # git user email eg &quot;john.doe@example.com&quot;
      };
    }

    # ... snip ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can enable git, and set your git username as well as git email right here.&lt;/p&gt;
&lt;h3&gt;Resources&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://gist.github.com/luismts/495d982e8c5b1a0ced4a57cf3d93cf60&quot;&gt;GitCommitBestPractices&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://git-scm.com/book/en/v2&quot;&gt;ProGit&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://ohshitgit.com/&quot;&gt;Oh shit Git&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content></entry><entry><title>Intro to Nushell</title><id>https://saylesss88.github.io/intro_to_nushell_on_NixOS.html</id><updated>2025-11-30T00:00:00+00:00</updated><author><name>saylesss87@proton.me (saylesss88)</name></author><link href="https://saylesss88.github.io/intro_to_nushell_on_NixOS.html" rel="alternate"/><content type="html">&lt;h1&gt;Chapter 12&lt;/h1&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Click to Expand Table of Contents&lt;/summary&gt;
&lt;!-- toc --&gt;
&lt;/details&gt;
&lt;p&gt;&lt;img src=&quot;https://saylesss88.github.io/images/nu.png&quot; alt=&quot;Nu&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Intro to Nushell on NixOS&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;:I recently switched default shells from zsh to nushell, this post is
about some of the challenges and advantages of using nushell with NixOS.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;While the average user might not immediately see significant advantages, those
who frequently work with structured data formats like JSON, YAML, and CSV –
such as developers interacting with APIs, system administrators managing
configurations, and data professionals – will likely find Nushell’s native
data handling and powerful pipeline capabilities a plus. Additionally, users
who value a more consistent and safer scripting experience might appreciate
Nushell’s language-first design and features like strong typing.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I’ll start with some of the unique build design choices and unique features
that I think make Nushell special, then show an example using Nushell to
manipulate JSON data. Finally, I will highlight some of the visually appealing
aspects of Nushell and lastly I share some resources for learning more.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;The Good&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Nushell borrows concepts from many shells and languages and is itself both a
programming language and a shell. Because of this, it has its own way of
working with files, directories, websites, and more.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Nushell is powerful and has many essential commands built directly into the
shell (“internal” commands) rather than a link to an executable. You can use
this set of commands across different operating systems, having this
consistency is helpful when creating cross-platform code.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;When internal Nushell commands (like &lt;code&gt;ls&lt;/code&gt;, &lt;code&gt;open&lt;/code&gt;, &lt;code&gt;where&lt;/code&gt;, &lt;code&gt;get&lt;/code&gt;, &lt;code&gt;sort-by&lt;/code&gt;,
etc.) produce output, they generally do so in Nushell’s structured data format
(tables or records). This is the shell’s native way of representing
information.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Beyond these foundational strengths, Nushell offers a range of unique features
that enhance its functionality and make it particularly well-suited for
data-heavy tasks. Here are some highlights that showcase its versatility.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Some Unique Features&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Besides the built-in commands, Nushell has a
&lt;a href=&quot;https://www.nushell.sh/book/standard_library.html&quot;&gt;standard library&lt;/a&gt; Nushell
operates on &lt;em&gt;structured data&lt;/em&gt;. You could call it a “data-first” shell and
programming language.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Also included, is a full-featured dataframe processing engine using
&lt;a href=&quot;https://github.com/pola-rs/polars&quot;&gt;Polars&lt;/a&gt; if you want to process large data
efficiently directly in your shell, check out the
&lt;a href=&quot;https://www.nushell.sh/book/dataframes.html&quot;&gt;Dataframes-Docs&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Multi-Line Editing&lt;/strong&gt;:&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;When writing a long command you can press Enter to add a newline and move to
the next line. For example:&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-nu&quot;&gt;ls            |    # press enter
where name =~ |    # press enter, comments after pipe ok
get name      |    # press enter
mv ...$in ./backups/
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;This allows you to cycle through the entire multi-line command using the up
and down arrow keys and then customize different lines or sections of the
command.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You can manually insert a newline using &lt;code&gt;Alt+Enter&lt;/code&gt; or &lt;code&gt;Shift+Enter&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The &lt;a href=&quot;https://www.nushell.sh/book/line_editor.html&quot;&gt;Reedline-Editor&lt;/a&gt; is
powerful and provides good &lt;code&gt;vi-mode&lt;/code&gt; or &lt;code&gt;emacs&lt;/code&gt; support built in.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;It’s default &lt;code&gt;Ctrl+r&lt;/code&gt; history command is nice to work with out of the box.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The &lt;a href=&quot;https://www.nushell.sh/book/explore.html#parameters&quot;&gt;explore&lt;/a&gt; command, is
nu’s version of a table pager, just like &lt;code&gt;less&lt;/code&gt; but for table structured data:&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-nu&quot;&gt;$nu | explore --peek
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;With the above command you can navigate with vim keybinds or arrow keys.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;These features demonstrate Nushell’s user-friendly interface, but what truly
sets it apart is its underlying design as a structured data scripting
language. This “language-first” approach powers many of its distinctive
capabilities.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://saylesss88.github.io/images/explore.png&quot; alt=&quot;explore&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Unique design&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Fundamentally designed as a structured data scripting language&lt;/strong&gt;: and then
it acts as a shell on top of that foundation. This “language first” approach
is what gives it many of its distinctive features and makes it a powerful
scripting language. I reiterate this here because of the implications of this.
A few of those features are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Pipelines of structured data&lt;/strong&gt;: Unlike traditional shells that primarily
deal with plain text streams, Nushell pipelines operate on tables of
structured data. Each command can understand and manipulate this structured
data directly.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Consistent syntax&lt;/strong&gt;: Its syntax is more consistent and predictable
compared to the often quirky syntax of Bash and Zsh, drawing inspiration
from other programming languages.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Strong typing&lt;/strong&gt; Nushell has a type system, which helps catch errors early
and allows for more robust scripting.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;First-class data types&lt;/strong&gt;: It treats various data formats (like JSON, CSV,
TOML) as native data types, making it easier to work with them. Because of
this, Nushell aims to replace the need for external tools like &lt;code&gt;jq&lt;/code&gt;, &lt;code&gt;awk&lt;/code&gt;,
&lt;code&gt;sed&lt;/code&gt;, &lt;code&gt;cut&lt;/code&gt;, and even some uses of &lt;code&gt;grep&lt;/code&gt; and &lt;code&gt;curl&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Variables are Immutable by Default&lt;/strong&gt;: Nushell’s commands are based on a
functional-style of programming which requires immutability, sound familiar?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Nushell’s Environment is Scoped&lt;/strong&gt;: Nushell takes many design cues from
compiled languages, one is that languages should avoid global mutable state.
Shells have commonly used global mutation to update the environment, Nushell
attempts to steer clear of this increasing reproducability.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Single-use Environment Variables&lt;/strong&gt;:&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-nu&quot;&gt;FOO=BAR $env.FOO
# =&amp;gt; BAR
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Permanent Environment Variables&lt;/strong&gt;: In your &lt;code&gt;config.nu&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-nu&quot;&gt;# config.nu
$env.FOO = &apos;BAR&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.nushell.sh/book/coming_from_bash.html&quot;&gt;Coming-From-Bash&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;These design principles make Nushell a powerful tool for scripting, but
they’re best understood through a hands-on example. Let’s see how Nushell’s
structured data capabilities shine in a common task: processing a JSON file.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;: I wanted to provide a practical example to illustrate some of these
“Good” features in action. And break it down for better understanding.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Let’s consider a common task: processing data from a JSON file. Imagine you
have a file containing a list of users with their names and ages. With
traditional shells, you’d likely need to rely on external tools like &lt;code&gt;jq&lt;/code&gt; to
parse and filter this data. However, Nushell can handle this directly within
its own commands.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;For this example you could create a &lt;code&gt;test&lt;/code&gt; directory and move to it:&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mkdir test ; cd test
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Create a &lt;code&gt;users.json&lt;/code&gt; with the following contents:&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;👇 users.json&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;[
  { &quot;name&quot;: &quot;Alice&quot;, &quot;age&quot;: 25 },
  { &quot;name&quot;: &quot;Bob&quot;, &quot;age&quot;: 30 },
  { &quot;name&quot;: &quot;Charlie&quot;, &quot;age&quot;: 20 }
]
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;And create the following &lt;code&gt;filter.nu&lt;/code&gt; that first converts &lt;code&gt;users.json&lt;/code&gt; into its
own internal structured data format with the &lt;code&gt;open&lt;/code&gt; command, then to filters
out people under &lt;code&gt;21&lt;/code&gt; with the &lt;code&gt;where&lt;/code&gt; control flow construct, then selects
the &lt;code&gt;name&lt;/code&gt; and &lt;code&gt;age&lt;/code&gt; columns, sorts them by age, and finally converts them
back to &lt;code&gt;json&lt;/code&gt; and saves them to a file called &lt;code&gt;filtered_users.json&lt;/code&gt;. A lot
happening in a 6 line script.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-nu&quot;&gt;# filter.nu
open users.json           # Read JSON file into structured data
| where age &amp;gt; 21         # Filter users older than 21
| select name age        # Select only name and age columns
| sort-by age            # Sort by age
| to json                # Convert back to JSON
| save filtered_users.json # Save result to a new file
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;open&lt;/code&gt; command takes data from a file (or even a URL in some cases) and
parses it and converts it into Nushells own internal structured data format.
So this command isn’t just showing you the contents of &lt;code&gt;users.json&lt;/code&gt; but doing
a conversion to Nu’s special structured format.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-nu&quot;&gt;open users.json
╭───┬─────────┬─────╮
│ # │  name   │ age │
├───┼─────────┼─────┤
│ 0 │ Alice   │  25 │
│ 1 │ Bob     │  30 │
│ 2 │ Charlie │  20 │
╰───┴─────────┴─────╯
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;source&lt;/code&gt; command in Nushell is used to execute the commands within a
script file (like &lt;code&gt;filter.nu&lt;/code&gt;) in the current Nushell environment. It’s
similar to running the script directly in the shell, but keeps the shell open
for further use. In this example, &lt;code&gt;source filter.nu&lt;/code&gt; runs the commands inside
&lt;code&gt;filter.nu&lt;/code&gt;, processing the &lt;code&gt;users.json&lt;/code&gt; file and creating the
&lt;code&gt;filtered_users.json&lt;/code&gt; file:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-nu&quot;&gt;source filter.nu
# View the contents with bat
bat filtered_users.json
───────┬──────────────────────────────────────────────────────────────────────────────────────
       │ File: filtered_users.json
───────┼──────────────────────────────────────────────────────────────────────────────────────
   1   │ [
   2   │   {
   3   │     &quot;name&quot;: &quot;Alice&quot;,
   4   │     &quot;age&quot;: 25
   5   │   },
   6   │   {
   7   │     &quot;name&quot;: &quot;Bob&quot;,
   8   │     &quot;age&quot;: 30
   9   │   }
  10   │ ]
───────┴───────────────────────────────────────────────────────────────────────────────────
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;As you can see, without needing any external tools, Nushell was able to read,
filter, select, sort, and then re-serialize JSON data using a clear and
concise pipeline. This demonstrates its power in handling structured data
natively, making common data manipulation tasks within the shell significantly
more streamlined and readable compared to traditional approaches.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;In the filter.nu example:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nu&quot;&gt;# filter.nu
open users.json           # Read JSON file into structured data
| where age &amp;gt; 21         # Filter users older than 21
| select name age        # Select only name and age columns
| sort-by age            # Sort by age
| to json                # Convert back to JSON
| save filtered_users.json # Save result to a new file
&lt;/code&gt;&lt;/pre&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Summary of above Command (Click to Expand)&lt;/summary&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;open users.json&lt;/code&gt;: Produces a &lt;strong&gt;Nushell table&lt;/strong&gt; representing the data.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;| where age &amp;gt; 21&lt;/code&gt;: Receives the table, filters rows based on the &lt;code&gt;age&lt;/code&gt;
column, and outputs a new, filtered table.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;| select name age&lt;/code&gt;: Receives the filtered table, selects only the &lt;code&gt;name&lt;/code&gt; and
&lt;code&gt;age&lt;/code&gt; columns, and outputs a table with fewer columns.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;| sort-by age&lt;/code&gt;: Receives the table, sorts the rows based on the &lt;code&gt;age&lt;/code&gt;
column, and outputs a sorted table.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;| to json&lt;/code&gt;: Receives the sorted table and converts it back into JSON text.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;| save filtered_users.json&lt;/code&gt;: Receives the JSON text and saves it to a file.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;So, while the concept of piping is the same, the nature of the data flowing
through the Nushell pipeline is richer and more structured, enabling more
powerful and direct data manipulation.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;While Nushell’s strengths, like its structured data pipelines, make it a
game-changer for many tasks, it’s not without its challenges, especially when
integrated with NixOS’s Bash-centric ecosystem. Let’s explore some of the
limitations you might encounter when adopting Nushell as your default shell.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/details&gt;
&lt;h3&gt;The Bad&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;While the project is still maturing, the active community and ongoing
improvements are promising. Don’t get too discouraged by the following, there
would be a bad section for any shell imo.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;There are many similarities so it can be easy to forget that some Bash (and
POSIX in general) style constructs just won’t work in Nushell. Considering
that NixOS seems to have been designed for bash, even Zsh isn’t fully
compatable you may want to think twice before you choose Nushell as your
default.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The documentation is incomplete and written by devs for devs imo, it is quite
a bit different from anything else I’ve seen so there is a bit of a learning
curve. Nushell is generally still considered to be in a stage where it might
not be the most seamless or trouble-free experience as a daily driver default
shell for most users, especially on a system like NixOS known for its unique
approach.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The &lt;a href=&quot;https://github.com/haslersn/any-nix-shell&quot;&gt;any-nix-shell&lt;/a&gt; project doesn’t
include Nushell as with many others because of it’s lack of maturity.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The following addition comes from Joey_McKur’s sugggestion, on mentioning the
&lt;code&gt;job&lt;/code&gt; command as one of the biggest criticisms against Nu because it doesn’t
support background tasks. I should also note that Nushell’s team is aware of
these criticisms and actively working on improving job control.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Limited Feature Set Compared to Traditional Job Control:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Lack of Full POSIX Job Control&lt;/strong&gt;: Nushell’s job control doesn’t yet fully
implement all the features and signals defined by POSIX job control (e.g.,
more nuanced signal handling, stopped jobs). While it covers the basics, users
accustomed to advanced Bash job control might find it lacking.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Foregrounding Behavior&lt;/strong&gt;: There have been criticisms about how foregrounding
jobs interacts with the terminal and potential issues with signal propagation.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Output Handling Challenges&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Interleaved Output&lt;/strong&gt;: Managing the output of multiple backgrounded jobs can
sometimes be messy, with output from different jobs potentially interleaving
in the terminal. While Nushell tries to handle this, it’s not always as clean
as desired.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Redirection Complexity&lt;/strong&gt;: Redirecting the input and output of backgrounded
jobs can be less straightforward than in Bash, sometimes requiring more
explicit handling.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Integration with Pipelines:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Backgrounding Pipelines&lt;/strong&gt;: Backgrounding complex pipelines with multiple
stages can sometimes lead to unexpected behavior or difficulties in managing
the entire pipeline as a single job.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Error Reporting:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Difficult to Track Errors in Background Jobs&lt;/strong&gt;: Identifying and debugging
errors in backgrounded jobs can be less direct than with foreground processes,
and the job command’s output might not always provide sufficient information
for troubleshooting.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Many of Nushell’s challenges stem from its departure from traditional shell
conventions, particularly those of Bash, which NixOS heavily relies on. To
better understand these differences and how they impact your workflow, let’s
compare Nushell’s static, structured approach to Bash’s dynamic, text-based
model.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Key Differences Between Nushell &amp;amp; Bash&lt;/h3&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;strong&gt;Feature&lt;/strong&gt;&lt;/th&gt;&lt;th&gt;&lt;strong&gt;Bash (Dynamic)&lt;/strong&gt;&lt;/th&gt;&lt;th&gt;&lt;strong&gt;Nushell (Static)&lt;/strong&gt;&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Code Execution&lt;/td&gt;&lt;td&gt;Line-by-line&lt;/td&gt;&lt;td&gt;Whole script parsed first&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Error Detection&lt;/td&gt;&lt;td&gt;Runtime errors only&lt;/td&gt;&lt;td&gt;Catches errors before running&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Support for &lt;code&gt;eval&lt;/code&gt;&lt;/td&gt;&lt;td&gt;✅ Allowed&lt;/td&gt;&lt;td&gt;❌ Not supported&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Custom Parsing&lt;/td&gt;&lt;td&gt;Limited&lt;/td&gt;&lt;td&gt;Built-in semantic analysis&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;IDE Features&lt;/td&gt;&lt;td&gt;Basic syntax highlighting&lt;/td&gt;&lt;td&gt;Advanced integration, linting, and formatting&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; doesn’t work use &lt;code&gt;;&lt;/code&gt; instead.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;&amp;gt;&lt;/code&gt; is used as the greater-than operator for comparisons:&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-nu&quot;&gt;&quot;hello&quot; | save output.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;is equivalent to the following in bash:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;echo &quot;hello&quot; &amp;gt; output.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;If you notice above the nushell command doesn’t require an &lt;code&gt;echo&lt;/code&gt; prefix, this
is because Nushell has &lt;strong&gt;Implicit Return&lt;/strong&gt;:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-nu&quot;&gt;&quot;Hello, World&quot; == (echo &quot;Hello, World&quot;)
# =&amp;gt; true
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The above example shows that the string, &lt;code&gt;&quot;Hello, World&quot;&lt;/code&gt; is equivalent to the
output value from &lt;code&gt;echo &quot;Hello, World&quot;&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Every Command Returns a Value&lt;/strong&gt;:&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-nu&quot;&gt;let p = 7
print $p  # 7
$p * 6    # 42
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Understanding these differences highlights why Nushell feels so distinct from
Bash, but it’s the shell’s advanced features and integrations that truly make
it shine. Let’s dive into some of the beautiful and powerful tools and custom
commands that elevate Nushell for NixOS users.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;The Beautiful and Powerful&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Ctrl+t&lt;/code&gt; List Commands with carapace and fzf:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://saylesss88.github.io/images/nu4.png&quot; alt=&quot;nu4&quot; /&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Carapace&lt;/code&gt;
&lt;a href=&quot;https://carapace-sh.github.io/carapace-bin/install.html&quot;&gt;Carapace-Bin Install&lt;/a&gt;:&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The folling is showing tab completion, I typed &lt;code&gt;hx fl&amp;lt;TAB&amp;gt;&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://saylesss88.github.io/images/nu9.png&quot; alt=&quot;nu9&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Carapace&lt;/code&gt; man example:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://saylesss88.github.io/images/nu7.png&quot; alt=&quot;nu7&quot; /&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Custom Nushell Commands&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Most of the following scripts come from the
&lt;a href=&quot;https://github.com/nushell/nu_scripts#&quot;&gt;nu_scripts repo&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The following command allows you to choose which input to update interactively
with fzf.&lt;/li&gt;
&lt;/ul&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Click to See Command&lt;/summary&gt;
&lt;pre&gt;&lt;code class=&quot;language-nu&quot;&gt;# nix.nu
# upgrade system packages
# `nix-upgrade` or `nix-upgrade -i`
def nix-upgrade [
  flake_path: string = &quot;/home/jr/flake&quot;, # path that contains a flake.nix
  --interactive (-i) # select packages to upgrade interactively
]: nothing -&amp;gt; nothing {
  let working_path = $flake_path | path expand
  if not ($working_path | path exists) {
    echo &quot;path does not exist: $working_path&quot;
    exit 1
  }
  let pwd = $env.PWD
  cd $working_path
  if $interactive {
    let selections = nix flake metadata . --json
    | from json
    | get locks.nodes
    | columns
    | str join &quot;\n&quot;
    | fzf --multi --tmux center,20%
    | lines
    # Debug: Print selections to verify
    print $&quot;Selections: ($selections)&quot;
    # Check if selections is empty
    if ($selections | is-empty) {
      print &quot;No selections made.&quot;
      cd $pwd
      return
    }
    # Use spread operator to pass list items as separate arguments
    nix flake update ...$selections
  } else {
    nix flake update
  }
  cd $pwd
  nh os switch $working_path
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;
&lt;p&gt;&lt;strong&gt;Usage&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nu&quot;&gt;nix-upgrade
# or for individual packages
nix-upgrade -i
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://saylesss88.github.io/images/nu5.png&quot; alt=&quot;nu5&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;ns&lt;/code&gt; command is designed to search for Nix packages using &lt;code&gt;nix search&lt;/code&gt; and
present the results in a cleaner format, specifically removing the
architecture and operating system prefix that nix search often includes.&lt;/li&gt;
&lt;/ul&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Click To Expand&lt;/summary&gt;
&lt;pre&gt;&lt;code class=&quot;language-nu&quot;&gt;def ns [
    term: string # Search target.
] {

    let info = (
        sysctl -n kernel.arch kernel.ostype
        | lines
        | {arch: ($in.0|str downcase), ostype: ($in.1|str downcase)}
    )

    nix search --json nixpkgs $term
        | from json
        | transpose package description
        | flatten
        | select package description version
        | update package {|row| $row.package | str replace $&quot;legacyPackages.($info.arch)-($info.ostype).&quot; &quot;&quot;}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;
&lt;p&gt;&lt;strong&gt;Usage&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nu&quot;&gt;ns fzf&amp;lt;ENTER&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://saylesss88.github.io/images/nu10.png&quot; alt=&quot;nu10&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;nufetch&lt;/code&gt; command:&lt;/li&gt;
&lt;/ul&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Click To Expand&lt;/summary&gt;
&lt;pre&gt;&lt;code class=&quot;language-nu&quot;&gt;# `nufetch` `(nufetch).packages`
def nufetch [] {
{
&quot;kernel&quot;: $nu.os-info.kernel_version,
&quot;nu&quot;: $env.NU_VERSION,
&quot;packages&quot;: (ls /etc/profiles/per-user | select name | prepend [[name];
[&quot;/run/current-system/sw&quot;]] | each { insert &quot;number&quot; (nix path-info --recursive
 ($in | get name) | lines | length) | insert &quot;size&quot; ( nix path-info -S
 ($in | get name) | parse -r &apos;\s(.*)&apos; | get capture0.0 | into filesize) | update
 &quot;name&quot; ($in | get name | parse -r &apos;.*/(.*)&apos; | get capture0.0 | if $in == &quot;sw&quot;
 {&quot;system&quot;} else {$in}) | rename &quot;environment&quot;}),
&quot;uptime&quot;: (sys host).uptime
}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;
&lt;p&gt;&lt;img src=&quot;https://saylesss88.github.io/images/nu1.png&quot; alt=&quot;nu1&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;duf&lt;/code&gt; command, I have mine aliased to &lt;code&gt;df&lt;/code&gt;:&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://saylesss88.github.io/images/nu8.png&quot; alt=&quot;nu8&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ps&lt;/code&gt; command:&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://saylesss88.github.io/images/ps.png&quot; alt=&quot;ps&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;nix-list-system&lt;/code&gt; command lists all installed packages:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-nu&quot;&gt;# list all installed packages
def nix-list-system []: nothing -&amp;gt; list&amp;lt;string&amp;gt; {
  ^nix-store -q --references /run/current-system/sw
  | lines
  | filter { not ($in | str ends-with &apos;man&apos;) }
  | each { $in | str replace -r &apos;^[^-]*-&apos; &apos;&apos; }
  | sort
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Usage&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix-list-system
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://saylesss88.github.io/images/nu6.png&quot; alt=&quot;nu6&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;These custom Nushell commands showcase its flexibility, but sometimes you need
to work around Nushell’s limitations, like compatability with certain NixOS
tools. This is where &lt;code&gt;just&lt;/code&gt; and &lt;code&gt;justfiles&lt;/code&gt; come in, simplifying complex
workflows and bridging gaps in Nushell’s functionality.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Using Just and Justfiles&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The following is my &lt;code&gt;justfile&lt;/code&gt; that I keep right next to my &lt;code&gt;flake.nix&lt;/code&gt; it
simplifies some commands and makes things work that weren’t working with
nushell for my case, you’ll have to change it to match your configuration.
It’s not perfect but works for my use case, take whats useful and leave the
rest.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You’ll first need to install &lt;a href=&quot;https://github.com/casey/just&quot;&gt;just&lt;/a&gt; to make use
of &lt;code&gt;justfiles&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# nix shell nixpkgs#just nixpkgs#nushell
set shell := [&quot;nu&quot;, &quot;-c&quot;]
flake_path := &quot;/home/jr/flake&quot;
hostname := &quot;magic&quot;
home_manager_output := &quot;jr@magic&quot;

utils_nu := absolute_path(&quot;utils.nu&quot;)

default:
    @just --list
# Rebuild
[group(&apos;nix&apos;)]
fr:
    nh os switch --hostname {{hostname}} {{flake_path}}

# Flake Update
[group(&apos;nix&apos;)]
fu:
    nh os switch  --hostname {{hostname}} --update {{flake_path}}

# Update specific input
# Usage: just upp nixpkgs
[group(&apos;nix&apos;)]
upp input:
    nix flake update {{input}}
# Test
[group(&apos;nix&apos;)]
ft:
    nh os test --hostname {{hostname}} {{flake_path}}
# Collect Garbage
[group(&apos;nix&apos;)]
ncg:
    nix-collect-garbage --delete-old ; sudo nix-collect-garbage -d ; sudo /run/current-system/bin/switch-to-configuration boot

[group(&apos;nix&apos;)]
cleanup:
    nh clean all

&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;To list available commands type, (you must be in the same directory as the
justfile): &lt;code&gt;just&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://saylesss88.github.io/images/just2.png&quot; alt=&quot;just&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;So &lt;code&gt;just fmt&lt;/code&gt; will run &lt;code&gt;nix fmt&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A lot of the &lt;code&gt;.nu&lt;/code&gt; files came from this repo by BlindFS:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/blindFS/modern-dot-files/tree/main&quot;&gt;modern-dot-files&lt;/a&gt; he
uses Nix Darwin so there are a few changes for NixOS. I found this through
&lt;a href=&quot;https://github.com/nushell/this_week_in_nu&quot;&gt;this_week_in_nu&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/TSawyer87/flakes/tree/main/homeManagerModules/shells/nushell&quot;&gt;my-nu-config&lt;/a&gt;
If you use this, you’ll need to change the first line of &lt;code&gt;fzf.nu&lt;/code&gt; to the
location of your config. You’ll also need to change the constants at the top
of &lt;code&gt;config.nu&lt;/code&gt;. These are my old dotfiles, I have recently updated and made
sure this config is up to date with recent nushell changes. Also, change the
&lt;code&gt;let flake_path = ($env.HOME | path join &quot;flake&quot;)&lt;/code&gt; to your flake path.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The examples use this starship
config&lt;a href=&quot;https://github.com/Aylur/dotfiles/blob/main/home/starship.nix&quot;&gt;Aylur-dotfiles&lt;/a&gt;
The logic on the bottom enables starship for Nushell, Zsh, and Bash!&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If you wan’t to use my config you’ll have to enable the experimental-feature
&lt;code&gt;pipe-operators&lt;/code&gt; in the same place you enable flakes and nix-command.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;There are still situations where I need to switch to zsh or bash to get
something to work i.e. &lt;code&gt;nix-shell&lt;/code&gt; and a few others.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;From custom commands to &lt;code&gt;justfile&lt;/code&gt; integrations, Nushell offers a wealth of
tools to enhance your NixOS experience, even if occasional workarounds are
needed. To dive deeper into Nushell and tailor it to your needs, here are some
valuable resources to explore, from official documentation to community-driven
configurations.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Resources&lt;/h4&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Click to Expand Resources &lt;/summary&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.nushell.sh/book/&quot;&gt;Nushell-Book&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.nushell.sh/cookbook/&quot;&gt;Nushell-Cookbook&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/nushell/nu_scripts&quot;&gt;nu_scripts&lt;/a&gt; some of the custom
commands came from here.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/nushell/nushell/tree/main/crates/nu-utils/src/default_files&quot;&gt;nushell sample-config&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/nushell/awesome-nu#plugins&quot;&gt;awesome-nu repo&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/nushell/showcase&quot;&gt;nu showcase-repo&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://discord.com/invite/NtAbbGn&quot;&gt;discord&lt;/a&gt; You can find custom commands,
configurations, etc here.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/details&gt;
</content></entry><entry><title>Intro to Derivations</title><id>https://saylesss88.github.io/Intro_to_Nix_Derivations_7.html</id><updated>2025-11-29T00:00:00+00:00</updated><author><name>saylesss87@proton.me (saylesss88)</name></author><link href="https://saylesss88.github.io/Intro_to_Nix_Derivations_7.html" rel="alternate"/><content type="html">&lt;h1&gt;Chapter 7&lt;/h1&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Click to Expand Table of Contents&lt;/summary&gt;
&lt;!-- toc --&gt;
&lt;/details&gt;
&lt;h2&gt;Introduction to Nix Derivations&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://saylesss88.github.io/images/gruv10.png&quot; alt=&quot;gruv10&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Nix’s build instructions, known as &lt;strong&gt;derivations&lt;/strong&gt;, are defined using the Nix
Language. These derivations can describe anything from individual software
packages to complete system configurations. The Nix package manager then
deterministically “realizes” (builds) these derivations, ensuring consistency
because they rely solely on a predefined set of inputs.&lt;/p&gt;
&lt;p&gt;Most things in NixOS are built around derivations. Your NixOS system is
described by such a single system derivation. When you want to apply a new
configuration, &lt;code&gt;nixos-rebuild&lt;/code&gt; handles the process:&lt;/p&gt;
&lt;p&gt;It first builds this derivation:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix-build &apos;&amp;lt;nixpkgs/nixos&amp;gt;&apos; -A system
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, once the build is complete, it switches to that new system:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;result/bin/switch-to-configuration
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After the build, &lt;code&gt;nixos-rebuild&lt;/code&gt; updates a crucial symbolic link:
&lt;code&gt;/run/current-system&lt;/code&gt; This symlink always points to the active, running version
of your system in the Nix store. In essence, the &lt;code&gt;/run/current-system&lt;/code&gt; path is
the currently active system derivation. This design choice gives NixOS its
powerful atomic upgrade and rollback capabilities: changing your system involves
building a new system derivation and updating this symlink to point to the
latest version.&lt;/p&gt;
&lt;blockquote&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt; ls -lsah /run/current-system
 0 lrwxrwxrwx 1 root root 85 May 23 12:11 /run/current-system -&amp;gt; /nix/store/
 cy2c0kxpjrl7ajlg9v3zh898mhj4dyjv-nixos-system-magic-25.11.20250520.2795c50
&lt;/code&gt;&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The &lt;code&gt;-&amp;gt;&lt;/code&gt; indicates a symlink and it’s pointing to a &lt;strong&gt;store path&lt;/strong&gt; which is
the result of a derivation being built (the system closure)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;For beginners, the analogy of a cooking recipe is helpful:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Ingredients (Dependencies):&lt;/strong&gt; What other software or libraries are needed.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Steps (Build Instructions):&lt;/strong&gt; The commands to compile, configure, and
install.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Final Dish (Output):&lt;/strong&gt; The resulting package or resource.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A Nix derivation encapsulates all this information, telling Nix what inputs to
use, how to build it, and what the final output should be.&lt;/p&gt;
&lt;p&gt;Nix derivations run in &lt;strong&gt;pure&lt;/strong&gt;, &lt;strong&gt;isolated environments&lt;/strong&gt;, meaning they
&lt;strong&gt;cannot&lt;/strong&gt; access the internet during the build phase. This ensures that builds
are reproducible – they don’t depend on external sources that might change over
time.&lt;/p&gt;
&lt;p&gt;There are &lt;code&gt;Fixed-output-derivations&lt;/code&gt; that allow fetching resources during the
build process by explicitly specifying the expected hash upfront. Just keep this
in mind that normal derivations don’t have network access.&lt;/p&gt;
&lt;h2&gt;Creating Derivations in Nix&lt;/h2&gt;
&lt;p&gt;The primary way to define packages in Nix is through the &lt;code&gt;mkDerivation&lt;/code&gt;
function, which is part of the standard environment (&lt;code&gt;stdenv&lt;/code&gt;). While a
lower-level &lt;code&gt;derivation&lt;/code&gt; function exists for advanced use cases, &lt;code&gt;mkDerivation&lt;/code&gt;
simplifies the process by automatically managing dependencies and the build
environment.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;mkDerivation&lt;/code&gt; (and &lt;code&gt;derivation&lt;/code&gt;) takes a set of attributes as its argument. At
a minimum, you’ll often encounter these essential attributes:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;name:&lt;/strong&gt; A human-readable identifier for the derivation (e.g., “foo”,
“hello.txt”). This helps you and Nix refer to the package.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;system:&lt;/strong&gt; Specifies the target architecture for the build (e.g.,
&lt;code&gt;builtins.currentSystem&lt;/code&gt; for your current machine).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;builder:&lt;/strong&gt; Defines the program that will execute the build instructions
(e.g., &lt;code&gt;bash&lt;/code&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;How do we pass these required attributes to the &lt;code&gt;derivation&lt;/code&gt; function?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Functions in Nix often take a single argument which is an attribute set. For
&lt;code&gt;derivation&lt;/code&gt; and &lt;code&gt;mkDerivation&lt;/code&gt;, this takes the form
&lt;code&gt;functionName { attribute1 = value1; attribute2 = value2; ... }&lt;/code&gt;, where the &lt;code&gt;{}&lt;/code&gt;
encloses the set of attributes being passed as the function’s argument.&lt;/p&gt;
&lt;p&gt;Remember that &lt;code&gt;derivation&lt;/code&gt; and &lt;code&gt;mkDerivation&lt;/code&gt; take a set (i.e. &lt;code&gt;{}&lt;/code&gt;) of
attributes as its first argument. So, in order to pass the required attributes
you would do something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;nix-repl&amp;gt; pkgs = import &amp;lt;nixpkgs&amp;gt; {}

nix-repl&amp;gt; d = derivation {
            name = &quot;mydrv&quot;;
            builder = &quot;${pkgs.bash}/bin/bash&quot;;
            args = [
              &quot;-c&quot; # Tells bash to execute the following string as a command
              &apos;&apos;
                # Explicitly set PATH to include coreutils bin directory
                export PATH=&quot;${pkgs.coreutils}/bin:$PATH&quot;
                mkdir $out
              &apos;&apos;
            ];
            system = builtins.currentSystem;
          }

nix-repl&amp;gt; :b d
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;When I was starting out, seeing the above written in the following format made
it clearer in my mental map that we were passing these attributes as arguments
but both accomplish the same thing.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;d = derivation { name = &quot;myname&quot;; builder = &quot;${coreutils}/bin/true&quot;; system = builtins.currentSystem; }
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;When you write &lt;code&gt;pkgs = import &amp;lt;nixpkgs&amp;gt; {};&lt;/code&gt;, you are importing the Nixpkgs
&lt;code&gt;default.nix&lt;/code&gt; file, which resolves to a function. Calling that function by
passing it an empty attribute set &lt;code&gt;{}&lt;/code&gt; as its argument. The function then
evaluates and returns the entire &lt;code&gt;pkgs&lt;/code&gt; attribute set. To specify a different
system for example, you could do something like:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;pkgsForAarch64 = import &amp;lt;nixpkgs&amp;gt; { system = &quot;aarch64-linux&quot;; };
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So when you see:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;import &amp;lt;nixpkgs&amp;gt; { overlays = []; config = {}; }
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Instead, these empty sets explicitly override any global or implicit
overlays/configurations that Nix might otherwise pick up from environment
variables (like &lt;code&gt;NIXPKGS_CONFIG&lt;/code&gt;), default locations (like
&lt;code&gt;~/.config/nixpkgs/config.nix&lt;/code&gt; or &lt;code&gt;~/.config/nixpkgs/overlays&lt;/code&gt;), or other
mechanisms.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;This is to prevent accidental partial application from other parts of your
configuration and is saying “Do not pass any custom configuration options for
this particular import”&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;derivation&lt;/code&gt; is a pre-made, built-in function in the Nix language. Here, we
are passing it an attribute set as argument with the three required
attributes. (&lt;code&gt;name&lt;/code&gt;, &lt;code&gt;builder&lt;/code&gt;, &lt;code&gt;system&lt;/code&gt;, and we added an extra argument
&lt;code&gt;args&lt;/code&gt;.)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;The Hello World Derivation&lt;/h2&gt;
&lt;p&gt;For this example, first create a &lt;code&gt;hello&lt;/code&gt; directory and add the
&lt;a href=&quot;https://ftp.gnu.org/gnu/hello/hello-2.12.1.tar.gz&quot;&gt;Hello tarball&lt;/a&gt; to said
directory.&lt;/p&gt;
&lt;p&gt;Now lets create the classic Hello derivation:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# hello.nix
let
  pkgs = import &amp;lt;nixpkgs&amp;gt; { };
in
derivation {
  name = &quot;hello&quot;;
  builder = &quot;${pkgs.bash}/bin/bash&quot;;
  args = [ ./hello_builder.sh ];
  inherit (pkgs)
    gnutar
    gzip
    gnumake
    gcc
    coreutils
    gawk
    gnused
    gnugrep
    ;
  bintools = pkgs.binutils.bintools;
  src = ./hello-2.12.1.tar.gz;
  system = builtins.currentSystem;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;As you can see, this isn’t the only required file but is a recipe outlining
how to build the &lt;code&gt;hello&lt;/code&gt; package. The &lt;code&gt;tar.gz&lt;/code&gt; package can be found
&lt;a href=&quot;https://ftp.gnu.org/gnu/hello/hello-2.12.1.tar.gz&quot;&gt;here&lt;/a&gt; You would just place
the tarball in the same directory as the derivation along with the following
&lt;code&gt;hello_builder.sh&lt;/code&gt;:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# hello_builder.sh
export PATH=&quot;$gnutar/bin:$gcc/bin:$gnumake/bin:$coreutils/bin:$gawk/bin:$gzip/bin:$gnugrep/bin:$gnused/bin:$bintools/bin&quot;
tar -xzf $src
cd hello-2.12.1
./configure --prefix=$out
make
make install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And build it with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix-build hello.nix
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally execute it with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;./result/bin/hello
Hello, world!
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Simple Rust Derivation&lt;/h2&gt;
&lt;p&gt;Create a &lt;code&gt;simple.rs&lt;/code&gt; with the following contents:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;fn main() {
  println!(&quot;Simple Rust!&quot;)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And a &lt;code&gt;rust_builder.sh&lt;/code&gt; like this (this is our builder script):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# rust_builder.sh
# Set up the PATH to include rustc coreutils and gcc
export PATH=&quot;$rustc/bin:$coreutils/bin:$gcc/bin&quot;

# IMPORTANT: Create the $out directory BEFORE rustc tries to write to it
mkdir -p &quot;$out&quot;

# Compile the Rust source code and place the executable inside $out
rustc -o &quot;$out/simple_rust&quot; &quot;$src&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we’ll enter the &lt;code&gt;nix repl&lt;/code&gt; and build it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;❯ nix repl
Nix 2.28.3
Type :? for help.

nix-repl&amp;gt; :l &amp;lt;nixpkgs&amp;gt;
added 3950 variables.

# Define the variables for rustc, coreutils, bash, AND gcc from the loaded nixpkgs
nix-repl&amp;gt; rustc = pkgs.rustc

nix-repl&amp;gt; coreutils = pkgs.coreutils

nix-repl&amp;gt; bash = pkgs.bash

nix-repl&amp;gt; gcc = pkgs.gcc

# Now define the derivation
nix-repl&amp;gt; simple_rust_program = derivation {
            name = &quot;simple-rust-program&quot;;
            builder = &quot;${bash}/bin/bash&quot;;
            args = [ ./rust_builder.sh ];
            rustc = rustc;
            coreutils = coreutils;
            gcc = gcc;
            src = ./simple.rs;
            system = builtins.currentSystem;
          }

nix-repl&amp;gt; :b simple_rust_program
This derivation produced the following outputs:
out -&amp;gt; /nix/store/fmyqr2d3ph0lpnxd0xppwvwyhv3iyb7y-simple-rust-program
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix-store -r /nix/store/fmyqr2d3ph0lpnxd0xppwvwyhv3iyb7y-simple-rust-program

warning: you did not specify &apos;--add-root&apos;; the result might be removed by the garbage collector
/nix/store/fmyqr2d3ph0lpnxd0xppwvwyhv3iyb7y-simple-rust-program
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This simple Rust example, built with a direct derivation call, illustrates:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;How Nix explicitly manages every single tool in your build environment
(&lt;code&gt;bash&lt;/code&gt;, &lt;code&gt;rustc&lt;/code&gt;, &lt;code&gt;gcc&lt;/code&gt;, &lt;code&gt;coreutils&lt;/code&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The strict isolation of Nix builds, where nothing is implicitly available.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The deterministic mapping of inputs to unique output paths in the Nix store.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The above example shows the fundamental structure of a Nix derivation, how
it’s defined within the &lt;code&gt;nix-repl&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;.drv&lt;/code&gt; files are intermediate files that describe how to build a derivation;
it’s the bare minimum information.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;When Derivations are Built&lt;/h2&gt;
&lt;p&gt;Nix doesn’t build derivations during the evaluation of your Nix expressions.
Instead, it processes your code in two main phases (and why you need to use
&lt;code&gt;:b simple_rust_program&lt;/code&gt; or &lt;code&gt;nix-store -r&lt;/code&gt; to actually build or realize it):&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Evaluation/Instantiate Phase: This is when Nix parses and interprets your
.nix expression. The result is a precise derivation description (often
represented as a .drv file on disk), and the unique “out paths” where the
final built products will go are calculated. No actual code is compiled or
executed yet. Achieved with &lt;code&gt;nix-instantiate&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Realize/Build Phase: Only after a derivation has been fully described does
Nix actually execute its build instructions. It first ensures all the
derivation’s inputs (dependencies) are built, then runs the builder script
in an isolated environment, and places the resulting products into their
designated “out paths” in the Nix store. Achieved with &lt;code&gt;nix-store -r&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Referring to other derivations&lt;/h2&gt;
&lt;p&gt;The way that we can refer to other packages/derivations is to use the &lt;code&gt;outPath&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;outPath&lt;/code&gt; describes the location of the files of that derivation. Nix can
then convert the derivation set into a string:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix repl
nix-repl&amp;gt; :l &amp;lt;nixpkgs&amp;gt;
nix-repl&amp;gt; fzf
«derivation /nix/store/vw1zag9q4xvf10z24j1qybji7wfsz78v-fzf-0.62.0.drv»
nix-repl&amp;gt; fzf.outPath
&quot;/nix/store/z3ayhjslz72ldiwrv3mn5n7rs96p2g8a-fzf-0.62.0&quot;
nix-repl&amp;gt; builtins.toString fzf
&quot;/nix/store/z3ayhjslz72ldiwrv3mn5n7rs96p2g8a-fzf-0.62.0&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;As long as there is an &lt;code&gt;outPath&lt;/code&gt; attribute, Nix will do the “set to string
conversion”.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Produce a development shell from a derivation&lt;/h2&gt;
&lt;p&gt;Building on the concept of a derivation as a recipe, let’s create our first
practical derivation. This example shows how to define a temporary development
environment (a shell) using stdenv.mkDerivation, which is the primary function
for defining packages in Nix.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# my-shell.nix
# We use a `let` expression to bring `pkgs` and `stdenv` into scope.
# This is a recommended practice over `with import &amp;lt;nixpkgs&amp;gt; {}`
# for clarity and to avoid potential name collisions.
let
  pkgs = import &amp;lt;nixpkgs&amp;gt; {};
  stdenv = pkgs.stdenv; # Access stdenv from the imported nixpkgs
in

# Make a new &quot;derivation&quot; that represents our shell
stdenv.mkDerivation {
  name = &quot;my-environment&quot;;

  # The packages in the `buildInputs` list will be added to the PATH in our shell
  buildInputs = [
    # cowsay is an arbitrary package
    # see https://nixos.org/nixos/packages.html to search for more
    pkgs.cowsay
    pkgs.fortune
  ];
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Usage&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix-shell my-shell.nix
fortune | cowsay
 _________________________________________
/ &quot;Lines that are parallel meet at        \
| Infinity!&quot; Euclid repeatedly, heatedly, |
| urged.                                  |
|                                         |
| Until he died, and so reached that      |
| vicinity: in it he found that the       |
| damned things diverged.                 |
|                                         |
\ -- Piet Hein                            /
 -----------------------------------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;To exit type: &lt;code&gt;exit&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This Nix expression defines a temporary development shell. Let’s break it down:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;pkgs = import &amp;lt;nixpkgs&amp;gt; {};&lt;/code&gt;: Standard way to get access to all the packages
and helper functions (i.e. &lt;code&gt;nixpkgs.lib&lt;/code&gt;)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;stdenv = pkgs.stdenv;&lt;/code&gt;: &lt;code&gt;stdenv&lt;/code&gt; provides us &lt;code&gt;mkDerivation&lt;/code&gt; and is from the
&lt;code&gt;nixpkgs&lt;/code&gt; collection.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;stdenv.mkDerivation { ... };&lt;/code&gt;: This is the core function for creating
packages.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;stdenv&lt;/code&gt; provides a set of common build tools and conventions.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;mkDerivation&lt;/code&gt; takes an attribute set (a collection of key-value pairs) as its
argument.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;name = &quot;my-environment&quot;;&lt;/code&gt;: This gives your derivation a human-readable name.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;buildInputs = [ pkgs.cowsay ];&lt;/code&gt;: This is a list of dependencies that will be
available in the build environment of this derivation (or in the &lt;code&gt;PATH&lt;/code&gt; if you
enter the shell created by this derivation). &lt;code&gt;pkgs.cowsay&lt;/code&gt; refers to the
&lt;code&gt;cowsay&lt;/code&gt; package from the imported &lt;code&gt;pkgs&lt;/code&gt; collection.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The command &lt;code&gt;nix-instantiate --eval my-shell.nix&lt;/code&gt; evaluates the Nix expression
in the file. It does not build the derivation. Instead, it returns the Nix value
that the expression evaluates to.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix-instantiate --eval my-shell.nix
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This value is a structured data type that encapsulates all the attributes (like
&lt;code&gt;name&lt;/code&gt;, &lt;code&gt;system&lt;/code&gt;, &lt;code&gt;buildInputs&lt;/code&gt;, etc.) required to build the derivation. Your
output shows this detailed internal representation of the derivation’s “recipe”
as understood by Nix. This is useful for debugging and inspecting the
derivation’s definition.&lt;/p&gt;
&lt;h2&gt;Our Second Derivation: Understanding the Builder&lt;/h2&gt;
&lt;details&gt;
&lt;summary&gt; Understanding the Builder (Click to Expand) &lt;/summary&gt;
&lt;ul&gt;
&lt;li&gt;To understand how derivations work, let’s create a very basic example using a
bash script as our &lt;code&gt;builder&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Why a Builder Script?&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;builder&lt;/code&gt; attribute in a derivation tells Nix &lt;em&gt;how&lt;/em&gt; to perform the build
steps. A simple and common way to define these steps is with a bash script.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;The Challenge with Shebangs in Nix&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;In typical Unix-like systems, you might start a bash script with a shebang
(&lt;code&gt;#!/bin/bash&lt;/code&gt; or &lt;code&gt;#!/usr/bin/env bash&lt;/code&gt;) to tell the system how to execute it.
However, in Nix derivations, we generally avoid this.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Reason:&lt;/strong&gt; Nix builds happen in an isolated environment where the exact path
to common tools like &lt;code&gt;bash&lt;/code&gt; isn’t known beforehand (it resides within the Nix
store). Hardcoding a path or relying on the system’s &lt;code&gt;PATH&lt;/code&gt; would break Nix’s
stateless property.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;The Importance of Statelessness in Nix&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Stateful Systems (Traditional):&lt;/strong&gt; When you install software traditionally,
it often modifies the core system environment directly. This can lead to
dependency conflicts and makes rollbacks difficult.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Stateless Systems (Nix):&lt;/strong&gt; Nix takes a different approach. When installing a
package, it creates a unique, immutable directory in the Nix store. This
means:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;No Conflicts:&lt;/strong&gt; Different versions of the same package can coexist without
interfering with each other.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Reliable Rollback:&lt;/strong&gt; You can easily switch back to previous versions
without affecting system-wide files.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Reproducibility:&lt;/strong&gt; Builds are more likely to produce the same result
across different machines if they are “pure” (don’t rely on external system
state).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;The Isolated Nix Build Environment: A Quick Overview&lt;/h3&gt;
&lt;p&gt;When Nix executes a builder script, it sets up a highly controlled and pristine
environment to ensure &lt;strong&gt;reproducibility&lt;/strong&gt; and &lt;strong&gt;isolation&lt;/strong&gt;. Here’s what
happens:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Fresh Start:&lt;/strong&gt; Nix creates a temporary, empty directory for the build and
makes it the current working directory.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Clean Environment:&lt;/strong&gt; It completely clears the environment variables from
your shell.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Controlled Inputs:&lt;/strong&gt; Nix then populates the environment with &lt;em&gt;only&lt;/em&gt; the
variables essential for the build, such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;$NIX_BUILD_TOP&lt;/code&gt;: The path to the temporary build directory.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;$PATH&lt;/code&gt;: Carefully set to include only the explicit &lt;code&gt;buildInputs&lt;/code&gt; you’ve
specified, preventing reliance on arbitrary system tools.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;$HOME&lt;/code&gt;: Set to &lt;code&gt;/homeless-shelter&lt;/code&gt; to prevent programs from reading
user-specific configuration files.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Variables for each declared output (&lt;code&gt;$out&lt;/code&gt;, etc.), indicating where the
final results should be placed in the Nix store.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Execution &amp;amp; Logging:&lt;/strong&gt; The builder script is run with its specified
arguments. All its output (stdout/stderr) is captured in a log.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Clean Up &amp;amp; Registration:&lt;/strong&gt; If successful, the temporary directory is
removed. Nix then scans the build outputs for references to other store
paths, ensuring all dependencies are correctly tracked for future use and
garbage collection. Finally, it normalizes file permissions and timestamps
in the output for consistent hashing.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This meticulous setup ensures that your builds are independent of the machine
they run on and always produce the same result, given the same inputs.&lt;/p&gt;
&lt;h2&gt;Our builder Script&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;For our first derivation, we’ll create a simple &lt;code&gt;builder.sh&lt;/code&gt; file in the
current directory:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# builder.sh
declare -xp
echo foo &amp;gt; $out
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The command &lt;code&gt;declare -xp&lt;/code&gt; lists exported variables (it’s a bash builtin
function).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Nix needs to know where the final built product (the “cake” in our earlier
analogy) should be placed. So, during the derivation process, Nix calculates a
unique output path within the Nix store. This path is then made available to
our builder script as an environment variable named &lt;code&gt;$out&lt;/code&gt;. The &lt;code&gt;.drv&lt;/code&gt; file,
which is the recipe, contains instructions for the builder, including setting
up this &lt;code&gt;$out&lt;/code&gt; variable. Our builder script will then put the result of its
work (in this case, the “foo” file) into this specific &lt;code&gt;$out&lt;/code&gt; directory.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;As mentioned earlier we need to find the nix store path to the bash
executable, common way to do this is to load Nixpkgs into the repl and check:&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix-repl&amp;gt; :l &amp;lt;nixpkgs&amp;gt;
Added 3950 variables.
nix-repl&amp;gt; &quot;${bash}&quot;
&quot;/nix/store/ihmkc7z2wqk3bbipfnlh0yjrlfkkgnv6-bash-4.2-p45&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So, with this little trick we are able to refer to &lt;code&gt;bin/bash&lt;/code&gt; and create our
derivation:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix-repl&amp;gt; d = derivation { name = &quot;foo&quot;; builder = &quot;${bash}/bin/bash&quot;;
 args = [ ./builder.sh ]; system = builtins.currentSystem; }
nix-repl&amp;gt; :b d
[1 built, 0.0 MiB DL]

this derivation produced the following outputs:
  out -&amp;gt; /nix/store/gczb4qrag22harvv693wwnflqy7lx5pb-foo
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The contents of the resulting store path (&lt;code&gt;/nix/store/...-foo&lt;/code&gt;) now contain
the file &lt;code&gt;foo&lt;/code&gt;, as intended. We have successfully built a derivation!&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Derivations are the primitive that Nix uses to define packages. “Package” is a
loosely defined term, but a derivation is simply the result of calling
&lt;code&gt;builtins.derivation&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/details&gt;
&lt;h2&gt;Our Last Derivation&lt;/h2&gt;
&lt;p&gt;Create a new directory and a &lt;code&gt;hello.nix&lt;/code&gt; with the following contents:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# hello.nix
{
  stdenv,
  fetchzip,
}:

stdenv.mkDerivation {
  pname = &quot;hello&quot;;
  version = &quot;2.12.1&quot;;

  src = fetchzip {
    url = &quot;https://ftp.gnu.org/gnu/hello/hello-2.12.1.tar.gz&quot;;
    sha256 = &quot;&quot;;
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Save this file to &lt;code&gt;hello.nix&lt;/code&gt; and run &lt;code&gt;nix-build&lt;/code&gt; to observe the build failure:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Click to expand output:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;$ nix-build hello.nix
~error: cannot evaluate a function that has an argument without a value (&apos;stdenv&apos;)
~       Nix attempted to evaluate a function as a top level expression; in
~       this case it must have its arguments supplied either by default
~       values, or passed explicitly with &apos;--arg&apos; or &apos;--argstr&apos;. See
~       https://nix.dev/manual/nix/stable/language/constructs.html#functions.
~
~       at /home/nix-user/hello.nix:3:3:
~
~            2| {
~            3|   stdenv,
~             |   ^
~            4|   fetchzip,
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Problem&lt;/strong&gt;: The expression in &lt;code&gt;hello.nix&lt;/code&gt; is a &lt;em&gt;function&lt;/em&gt;, which only produces
it’s intended output if it is passed the correct &lt;em&gt;arguments&lt;/em&gt;.(i.e. &lt;code&gt;stdenv&lt;/code&gt; is
available from &lt;code&gt;nixpkgs&lt;/code&gt; so we need to import &lt;code&gt;nixpkgs&lt;/code&gt; before we can use
&lt;code&gt;stdenv&lt;/code&gt;):&lt;/p&gt;
&lt;p&gt;The recommended way to do this is to create a &lt;code&gt;default.nix&lt;/code&gt; file in the same
directory as the &lt;code&gt;hello.nix&lt;/code&gt; with the following contents:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# default.nix
let
  nixpkgs = fetchTarball &quot;https://github.com/NixOS/nixpkgs/tarball/nixos-24.05&quot;;
  pkgs = import nixpkgs { config = {}; overlays = []; };
in
{
  hello = pkgs.callPackage ./hello.nix { };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This allows you to run &lt;code&gt;nix-build -A hello&lt;/code&gt; to realize the derivation in
&lt;code&gt;hello.nix&lt;/code&gt;, similar to the current convention used in Nixpkgs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Click to expand Output:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;nix-build -A hello
~error: hash mismatch in fixed-output derivation &apos;/nix/store/pd2kiyfa0c06giparlhd1k31bvllypbb-source.drv&apos;:
~         specified: sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
~            got:    sha256-1kJjhtlsAkpNB7f6tZEs+dbKd8z7KoNHyDHEJ0tmhnc=
~error: 1 dependencies of derivation &apos;/nix/store/b4mjwlv73nmiqgkdabsdjc4zq9gnma1l-hello-2.12.1.drv&apos; failed to build
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Another way to do this is with
&lt;a href=&quot;https://nix.dev/manual/nix/2.24/command-ref/nix-prefetch-url&quot;&gt;nix-prefetch-url&lt;/a&gt;
It is a utility to calculate the sha beforehand.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix-prefetch-url https://ftp.gnu.org/gnu/hello/hello-2.12.1.tar.gz
path is &apos;/nix/store/pa10z4ngm0g83kx9mssrqzz30s84vq7k-hello-2.12.1.tar.gz&apos;
086vqwk2wl8zfs47sq2xpjc9k066ilmb8z6dn0q6ymwjzlm196cd
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;When you use &lt;code&gt;nix-prefetch-url&lt;/code&gt;, you get a Base32 hash when nix needs SRI
format.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Run the following command to convert from Base32 to SRI:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix hash to-sri --type sha256 086vqwk2wl8zfs47sq2xpjc9k066ilmb8z6dn0q6ymwjzlm196cd
sha256-jZkUKv2SV28wsM18tCqNxoCZmLxdYH2Idh9RLibH2yA=
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;This actually fetched a different sha than the Nix compiler returned in the
example where we replace the empty sha with the one Nix gives us. The
difference was that &lt;code&gt;fetchzip&lt;/code&gt; automatically extracts archives before
computing the hash and slight differences in the metadata cause different
results. I had to switch from &lt;code&gt;fetchzip&lt;/code&gt; to &lt;code&gt;fetchurl&lt;/code&gt; to get the correct
results.
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Extracted archives can differ in timestamps, permissions, or compression
details, causing different hash values.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A simple takeaway is to use &lt;code&gt;fetchurl&lt;/code&gt; when you need an exact match, and
&lt;code&gt;fetchzip&lt;/code&gt; when working with extracted contents.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://nixos.org/manual/nixpkgs/stable/#fetchurl&quot;&gt;fetchurl&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;fetchurl&lt;/code&gt; returns a &lt;code&gt;fixed-output derivation&lt;/code&gt;(FOD): A derivation where a
cryptographic hash of the output is determined in advance using the
outputHash attribute, and where the builder executable has access to the
network.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Lastly replace the empty sha256 placeholder with the returned value from the
last command:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# hello.nix
{
  stdenv,
  fetchzip,
}:

stdenv.mkDerivation {
  pname = &quot;hello&quot;;
  version = &quot;2.12.1&quot;;

  src = fetchzip {
    url = &quot;https://ftp.gnu.org/gnu/hello/hello-2.12.1.tar.gz&quot;;
    sha256 = &quot;sha256-1kJjhtlsAkpNB7f6tZEs+dbKd8z7KoNHyDHEJ0tmhnc=&quot;;
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Run &lt;code&gt;nix-build -A hello&lt;/code&gt; again and you’ll see the derivation successfully
builds.&lt;/p&gt;
&lt;h2&gt;Best Practices&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Reproducible source paths&lt;/strong&gt;: If we built the following derivation in
&lt;code&gt;/home/myuser/myproject&lt;/code&gt; then the store path of &lt;code&gt;src&lt;/code&gt; will be
&lt;code&gt;/nix/store/&amp;lt;hash&amp;gt;-myproject&lt;/code&gt; causing the build to no longer be reproducible:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;let pkgs = import &amp;lt;nixpkgs&amp;gt; {}; in

pkgs.stdenv.mkDerivation {
  name = &quot;foo&quot;;
  src = ./.;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;❗ TIP: Use &lt;code&gt;builtins.path&lt;/code&gt; with the &lt;code&gt;name&lt;/code&gt; attribute set to something fixed.
This will derive the symbolic name of the store path from the &lt;code&gt;name&lt;/code&gt; instead
of the working directory:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;let pkgs = import &amp;lt;nixpkgs&amp;gt; {}; in

pkgs.stdenv.mkDerivation {
  name = &quot;foo&quot;;
  src = builtins.path { path = ./.; name = &quot;myproject&quot;; };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;In this chapter, we’ve laid the groundwork for understanding Nix derivations,
the fundamental recipes that define how software and other artifacts are built
within the Nix ecosystem. We’ve explored their key components – inputs, builder,
build phases, and outputs – and how they contribute to Nix’s core principles of
reproducibility and isolated environments. Derivations are the workhorses behind
the packages and tools we use daily in Nix.&lt;/p&gt;
&lt;p&gt;As you’ve learned, derivations offer a powerful and principled approach to
software management. However, the way we organize and manage these derivations,
along with other Nix expressions and dependencies, has evolved over time.
Traditionally, Nix projects often relied on patterns involving &lt;code&gt;default.nix&lt;/code&gt;
files, channel subscriptions, and manual dependency management.&lt;/p&gt;
&lt;p&gt;A more recent and increasingly popular approach to structuring Nix projects and
managing dependencies is through Nix Flakes. Flakes introduce a standardized
project structure, explicit input tracking, and a more robust way to ensure
reproducible builds across different environments.&lt;/p&gt;
&lt;p&gt;In our next chapter,
&lt;a href=&quot;https://saylesss88.github.io/Comparing_Flakes_and_Traditional_Nix_8.html&quot;&gt;Comparing Flakes and Traditional Nix&lt;/a&gt;,
we will directly compare and contrast these two approaches. We’ll examine the
strengths and weaknesses of traditional Nix practices in contrast to the
benefits and features offered by Nix Flakes. This comparison will help you
understand the motivations behind Flakes and when you might choose one approach
over the other for your Nix projects.&lt;/p&gt;
&lt;p&gt;As you can see below, there is a ton of information on derivations freely
available.&lt;/p&gt;
&lt;h4&gt;Links To Articles about Derivations&lt;/h4&gt;
&lt;details&gt;
&lt;summary&gt; Click To Expand Resources &lt;/summary&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://nixos.org/guides/nix-pills/06-our-first-derivation&quot;&gt;NixPillsOurFirstDerivation&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://nixos.org/guides/nix-pills/07-working-derivation&quot;&gt;NixPills-WorkingDerivation&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://nix.dev/manual/nix/2.24/language/derivations&quot;&gt;nix.dev-Derivations&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://nix.dev/tutorials/packaging-existing-software&quot;&gt;nix.dev-packagingExistingSoftware&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://ianthehenry.com/posts/how-to-learn-nix/my-first-derivation/&quot;&gt;howToLearnNix-MyFirstDerivation&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://ianthehenry.com/posts/how-to-learn-nix/derivations-in-detail/&quot;&gt;howToLearnNix-DerivationsInDetail&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.sam.today/blog/creating-a-super-simple-derivation-learning-nix-pt-3&quot;&gt;Sparky/blog-creatingASuperSimpleDerivation&lt;/a&gt; #
How to learn Nix&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.sam.today/blog/derivations-102-learning-nix-pt-4&quot;&gt;Sparky/blog-Derivations102&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://scrive.github.io/nix-workshop/04-derivations/01-derivation-basics.html&quot;&gt;ScriveNixWorkshop-nixDerivationBasics&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://zero-to-nix.com/concepts/derivations/&quot;&gt;zeroToNix-Derivations&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.tweag.io/blog/2021-02-17-derivation-outputs-and-output-paths/&quot;&gt;Tweag-derivationOutputs&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://ayats.org/blog/nix-tuto-2&quot;&gt;theNixLectures-Derivations&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://bmcgee.ie/posts/2023/02/nix-what-are-fixed-output-derivations-and-why-use-them/&quot;&gt;bmcgee-whatAreFixed-OutputDerivations&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/details&gt;
</content></entry><entry><title>Flake Inputs</title><id>https://saylesss88.github.io/flakes/flake_inputs_4.1.html</id><updated>2025-11-28T00:00:00+00:00</updated><author><name>saylesss87@proton.me (saylesss88)</name></author><link href="https://saylesss88.github.io/flakes/flake_inputs_4.1.html" rel="alternate"/><content type="html">&lt;h1&gt;Nix Flake Inputs&lt;/h1&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Click to Expand Table of Contents&lt;/summary&gt;
&lt;!-- toc --&gt;
&lt;/details&gt;
&lt;p&gt;The attribute &lt;code&gt;inputs&lt;/code&gt; specifies the dependencies of a flake, as an attrset
mapping input names to flake references.&lt;/p&gt;
&lt;p&gt;If a repository provides a &lt;code&gt;flake.nix&lt;/code&gt; you can include it as an input in your
&lt;code&gt;flake.nix&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;For example, I like yazi as my file explorer and have been using helix as my
editor. To be able to get yazi to work with helix I needed the latest versions
of both yazi and helix. One way to get the latest versions was to add their
flakes as inputs to my flake:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{
	inputs = {
		nixpkgs.url = &quot;github:NixOS/nixpkgs/nixos-24.11&quot;;
		home-manager = {
			url = &quot;github:nix-community/home-manager/release-24.11&quot;;
			inputs.nixpkgs.follows = &quot;nixpkgs&quot;;
		};
    helix = {
      url = &quot;github:helix-editor/helix&quot;;
      inputs.nixpkgs.follows = &quot;nixpkgs&quot;;
    };
		yazi.url = &quot;github:sxyazi/yazi&quot;;
	};
	outputs = { nixpkgs, home-manager, ... } @ inputs: {
	# ... snip ... #
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Now to use this input, I would reference these inputs in both my yazi and
helix modules:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# yazi.nix
{ pkgs, config, inputs, ... }: {
	programs.yazi = {
		enable = true;
		package = inputs.yazi.packages.${pkgs.system}.default;
	};
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# helix.nix
{ pkgs, config, inputs, ... }: {
	programs.helix = {
		enable = true;
		package = inputs.helix.packages.${pkgs.system}.helix;
	};
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Understanding &lt;code&gt;.default&lt;/code&gt; vs. Named Outputs (e.g., &lt;code&gt;.helix&lt;/code&gt;) from the Source&lt;/p&gt;
&lt;p&gt;The difference between &lt;code&gt;inputs.yazi.packages.${pkgs.system}.default&lt;/code&gt; and
&lt;code&gt;inputs.helix.packages.${pkgs.system}.helix&lt;/code&gt; comes down to how the respective
upstream flakes define their outputs. You can always inspect a flake’s
&lt;code&gt;flake.nix&lt;/code&gt; or use &lt;code&gt;nix flake show &amp;lt;flake-reference&amp;gt;&lt;/code&gt; to understand its
structure.&lt;/p&gt;
&lt;h2&gt;Helix &lt;code&gt;flake.nix&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Let’s look at the relevant section of Helix’s &lt;code&gt;flake.nix&lt;/code&gt; click the eye to see
the full flake:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;~ {
~   description = &quot;A post-modern text editor.&quot;;
~
~   inputs = {
~     nixpkgs.url = &quot;github:nixos/nixpkgs/nixos-unstable&quot;;
~     rust-overlay = {
~       url = &quot;github:oxalica/rust-overlay&quot;;
~       inputs.nixpkgs.follows = &quot;nixpkgs&quot;;
~     };
~   };
~
~   outputs = {
~     self,
~     nixpkgs,
~     rust-overlay,
~     ...
~   }: let
~     inherit (nixpkgs) lib;
~     systems = [
~       &quot;x86_64-linux&quot;
~       &quot;aarch64-linux&quot;
~       &quot;x86_64-darwin&quot;
~       &quot;aarch64-darwin&quot;
~     ];
~     eachSystem = lib.genAttrs systems;
~     pkgsFor = eachSystem (system:
~       import nixpkgs {
~         localSystem.system = system;
~         overlays = [(import rust-overlay) self.overlays.helix];
~       });
~     gitRev = self.rev or self.dirtyRev or null;
   in {
     packages = eachSystem (system: {
       inherit (pkgsFor.${system}) helix;
       /*
       The default Helix build. Uses the latest stable Rust toolchain, and unstable
       nixpkgs.

       The build inputs can be overridden with the following:

       packages.${system}.default.override { rustPlatform = newPlatform; };

       Overriding a derivation attribute can be done as well:

       packages.${system}.default.overrideAttrs { buildType = &quot;debug&quot;; };
       */
      default = self.packages.${system}.helix;
    });
~    checks =
~      lib.mapAttrs (system: pkgs: let
~        # Get Helix&apos;s MSRV toolchain to build with by default.
~        msrvToolchain = pkgs.pkgsBuildHost.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml;
~        msrvPlatform = pkgs.makeRustPlatform {
~          cargo = msrvToolchain;
~          rustc = msrvToolchain;
~        };
~      in {
~        helix = self.packages.${system}.helix.override {
~          rustPlatform = msrvPlatform;
~        };
~      })
~      pkgsFor;
~
~    # Devshell behavior is preserved.
~    devShells =
~      lib.mapAttrs (system: pkgs: {
~        default = let
~          commonRustFlagsEnv = &quot;-C link-arg=-fuse-ld=lld -C target-cpu=native --cfg tokio_unstable&quot;;
~          platformRustFlagsEnv = lib.optionalString pkgs.stdenv.isLinux &quot;-Clink-arg=-Wl,--no-rosegment&quot;;
~        in
~          pkgs.mkShell {
~            inputsFrom = [self.checks.${system}.helix];
~            nativeBuildInputs = with pkgs;
~              [
~                lld
~                cargo-flamegraph
~                rust-bin.nightly.latest.rust-analyzer
~              ]
~              ++ (lib.optional (stdenv.isx86_64 &amp;amp;&amp;amp; stdenv.isLinux) cargo-tarpaulin)
~              ++ (lib.optional stdenv.isLinux lldb)
~              ++ (lib.optional stdenv.isDarwin darwin.apple_sdk.frameworks.CoreFoundation);
~            shellHook = &apos;&apos;
~              export RUST_BACKTRACE=&quot;1&quot;
~              export RUSTFLAGS=&quot;&apos;&apos;${RUSTFLAGS:-&quot;&quot;} ${commonRustFlagsEnv} ${platformRustFlagsEnv}&quot;
~            &apos;&apos;;
~          };
~      })
~      pkgsFor;
~
~    overlays = {
~      helix = final: prev: {
~        helix = final.callPackage ./default.nix {inherit gitRev;};
~      };
~
~      default = self.overlays.helix;
~    };
~  };
~  nixConfig = {
~    extra-substituters = [&quot;https://helix.cachix.org&quot;];
~    extra-trusted-public-keys = [&quot;helix.cachix.org-1:ejp9KQpR1FBI2onstMQ34yogDm4OgU2ru6lIwPvuCVs=&quot;];
~  };
~}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Dissecting &lt;code&gt;inherit (pkgsFor.${system}) helix;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Imagine the Nix evaluation process for Helix &lt;code&gt;flake.nix&lt;/code&gt; in the &lt;code&gt;outputs&lt;/code&gt;
section:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;packages = eachSystem (system: { ... });&lt;/code&gt; Part iterates through each
&lt;code&gt;system&lt;/code&gt; (like &lt;code&gt;x86_64-linux&lt;/code&gt;). For each &lt;code&gt;system&lt;/code&gt;, it’s creating an attribute
set that will become &lt;code&gt;self.packages.${system}&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Inside the &lt;code&gt;eachSystem&lt;/code&gt; function, for a specific system (e.g.
&lt;code&gt;x86_64-linux&lt;/code&gt;): The code is building an attribute set that will ultimately
be assigned to &lt;code&gt;self.packages.x86_64-linux&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;When you write &lt;code&gt;inherit (sourceAttrset) attributeName;&lt;/code&gt;, it’s equivalent to
writing &lt;code&gt;attributeName = sourceAttrset.attributeName;&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;So, &lt;code&gt;inherit (pkgsFor.${system}) helix;&lt;/code&gt; is equivalent to:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;helix = pkgsFor.${system}.helix;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Therefore, because of &lt;code&gt;inherit (pkgsFor.${system}) helix;&lt;/code&gt;, the helix attribute
is explicitly defined under
&lt;code&gt;packages.${system}``. This is why you access it as &lt;/code&gt;inputs.helix.packages.${pkgs.system}.helix;`.&lt;/p&gt;
&lt;h2&gt;Yazi &lt;code&gt;flake.nix&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Now this is yazi’s &lt;code&gt;flake.nix&lt;/code&gt;, yazi’s documentation tells you to use &lt;code&gt;.default&lt;/code&gt;
but lets examine the flake and see why:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;~{
~  inputs = {
~    nixpkgs.url = &quot;github:NixOS/nixpkgs/nixpkgs-unstable&quot;;
~    flake-utils.url = &quot;github:numtide/flake-utils&quot;;
~    rust-overlay = {
~      url = &quot;github:oxalica/rust-overlay&quot;;
~      inputs.nixpkgs.follows = &quot;nixpkgs&quot;;
~    };
~  };
~
~  outputs =
~    {
~      self,
~      nixpkgs,
~      rust-overlay,
~      flake-utils,
~      ...
~    }:
~    flake-utils.lib.eachDefaultSystem (
~      system:
~      let
~        pkgs = import nixpkgs {
~          inherit system;
~          overlays = [ rust-overlay.overlays.default ];
~        };
~        toolchain = pkgs.rust-bin.stable.latest.default;
~        rustPlatform = pkgs.makeRustPlatform {
~          cargo = toolchain;
~          rustc = toolchain;
~        };
~
~        rev = self.shortRev or self.dirtyShortRev or &quot;dirty&quot;;
~        date = self.lastModifiedDate or self.lastModified or &quot;19700101&quot;;
~        version =
~          (builtins.fromTOML (builtins.readFile ./yazi-fm/Cargo.toml)).package.version
~          + &quot;pre${builtins.substring 0 8 date}_${rev}&quot;;
~      in
      {
        packages = {
          yazi-unwrapped = pkgs.callPackage ./nix/yazi-unwrapped.nix {
            inherit
              version
              rev
              date
              rustPlatform
              ;
          };
          yazi = pkgs.callPackage ./nix/yazi.nix { inherit (self.packages.${system}) yazi-unwrapped; };
          default = self.packages.${system}.yazi;
        };

~        devShells = {
~          default = pkgs.callPackage ./nix/shell.nix { };
~        };
~
~        formatter = pkgs.nixfmt-rfc-style;
~      }
~    )
~    // {
~      overlays = {
~        default = self.overlays.yazi;
~        yazi = _: prev: { inherit (self.packages.${prev.stdenv.system}) yazi yazi-unwrapped; };
~      };
~    };
~}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this case using &lt;code&gt;inputs.yazi.packages.${pkgs.system}.yazi&lt;/code&gt; would also work&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;yazi = pkgs.callPackage ./nix/yazi.nix { inherit (self.packages.${system}) yazi-unwrapped; };&lt;/code&gt;
This line defines the yazi variable (or, more precisely, creates an attribute
named yazi within the &lt;code&gt;packages.${system}&lt;/code&gt; set). It assigns to this yazi
attribute the result of calling the Nix expression in &lt;code&gt;./nix/yazi.nix&lt;/code&gt; with
yazi-unwrapped as an argument. This yazi attribute represents the actual,
runnable Yazi package.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;default = self.packages.${system}.yazi;&lt;/code&gt; This line then aliases the yazi
package. It creates another attribute named &lt;code&gt;default&lt;/code&gt; within the same
&lt;code&gt;packages.${system}&lt;/code&gt; set and points it directly to the yazi attribute that was
just defined.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;So, when you access &lt;code&gt;inputs.yazi.packages.${pkgs.system}.default&lt;/code&gt;, you’re
effectively following the alias to the yazi package.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The choice to use &lt;code&gt;.default&lt;/code&gt; is primarily for convenience and adherence to a
common flake convention, making the flake easier for users to consume without
needing to dive into its internal structure.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content></entry><entry><title>Flake outputs</title><id>https://saylesss88.github.io/flakes/flake_outputs_4.2.html</id><updated>2025-11-28T00:00:00+00:00</updated><author><name>saylesss87@proton.me (saylesss88)</name></author><link href="https://saylesss88.github.io/flakes/flake_outputs_4.2.html" rel="alternate"/><content type="html">&lt;h1&gt;Nix Flake Outputs&lt;/h1&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Click to Expand Table of Contents&lt;/summary&gt;
&lt;!-- toc --&gt;
&lt;/details&gt;
&lt;p&gt;Flake outputs are what the flake produces when built. Flakes can have multiple
outputs simultaneously such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Packages&lt;/strong&gt;: Self-contained bundles that are built using derivations and
provide either some kind of software or dependencies of software.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://saylesss88.github.io/NixOS_Modules_Explained_3.html&quot;&gt;NixOS modules&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Nix development environments&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/NixOS/templates&quot;&gt;Nix templates&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The &lt;code&gt;outputs&lt;/code&gt; top-level attribute is actually a function that takes an
attribute set of inputs and returns an attribute set that is essentially a
recipe for building the flake.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Output Schema&lt;/h2&gt;
&lt;p&gt;Once the inputs are resolved, they’re passed to the &lt;code&gt;outputs&lt;/code&gt; attribute. This
&lt;code&gt;outputs&lt;/code&gt; attribute is, in fact, a function, as indicated by the &lt;code&gt;:&lt;/code&gt; colon (or
the &lt;code&gt;lambda&lt;/code&gt; syntax) that follows its definition. This function takes the
resolved inputs (and &lt;code&gt;self&lt;/code&gt;, the flake’s directory in the store) as arguments,
and its return value dictates the outputs of the flake, following this schema:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{ self, nixpkgs, ... }@inputs:
{
  # Executed by `nix flake check`
  checks.&quot;&amp;lt;system&amp;gt;&quot;.&quot;&amp;lt;name&amp;gt;&quot; = derivation;
  # Executed by `nix build .#&amp;lt;name&amp;gt;`
  packages.&quot;&amp;lt;system&amp;gt;&quot;.&quot;&amp;lt;name&amp;gt;&quot; = derivation;
  # Executed by `nix build .`
  packages.&quot;&amp;lt;system&amp;gt;&quot;.default = derivation;
  # Executed by `nix run .#&amp;lt;name&amp;gt;`
  apps.&quot;&amp;lt;system&amp;gt;&quot;.&quot;&amp;lt;name&amp;gt;&quot; = {
    type = &quot;app&quot;;
    program = &quot;&amp;lt;store-path&amp;gt;&quot;;
  };
  # Executed by `nix run . -- &amp;lt;args?&amp;gt;`
  apps.&quot;&amp;lt;system&amp;gt;&quot;.default = { type = &quot;app&quot;; program = &quot;...&quot;; };

  # Formatter (alejandra, nixfmt or nixpkgs-fmt)
  formatter.&quot;&amp;lt;system&amp;gt;&quot; = derivation;
  # Used for nixpkgs packages, also accessible via `nix build .#&amp;lt;name&amp;gt;`
  legacyPackages.&quot;&amp;lt;system&amp;gt;&quot;.&quot;&amp;lt;name&amp;gt;&quot; = derivation;
  # Overlay, consumed by other flakes
  overlays.&quot;&amp;lt;name&amp;gt;&quot; = final: prev: { };
  # Default overlay
  overlays.default = final: prev: { };
  # Nixos module, consumed by other flakes
  nixosModules.&quot;&amp;lt;name&amp;gt;&quot; = { config, ... }: { options = {}; config = {}; };
  # Default module
  nixosModules.default = { config, ... }: { options = {}; config = {}; };
  # Used with `nixos-rebuild switch --flake .#&amp;lt;hostname&amp;gt;`
  # nixosConfigurations.&quot;&amp;lt;hostname&amp;gt;&quot;.config.system.build.toplevel must be a derivation
  nixosConfigurations.&quot;&amp;lt;hostname&amp;gt;&quot; = {};
  # Used by `nix develop .#&amp;lt;name&amp;gt;`
  devShells.&quot;&amp;lt;system&amp;gt;&quot;.&quot;&amp;lt;name&amp;gt;&quot; = derivation;
  # Used by `nix develop`
  devShells.&quot;&amp;lt;system&amp;gt;&quot;.default = derivation;
  # Hydra build jobs
  hydraJobs.&quot;&amp;lt;attr&amp;gt;&quot;.&quot;&amp;lt;system&amp;gt;&quot; = derivation;
  # Used by `nix flake init -t &amp;lt;flake&amp;gt;#&amp;lt;name&amp;gt;`
  templates.&quot;&amp;lt;name&amp;gt;&quot; = {
    path = &quot;&amp;lt;store-path&amp;gt;&quot;;
    description = &quot;template description goes here?&quot;;
  };
  # Used by `nix flake init -t &amp;lt;flake&amp;gt;`
  templates.default = { path = &quot;&amp;lt;store-path&amp;gt;&quot;; description = &quot;&quot;; };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first line &lt;code&gt;{ self, nixpkgs, ... }@ inputs:&lt;/code&gt; defines the functions
parameters: It’s important to understand that within the scope of the &lt;code&gt;outputs&lt;/code&gt;
function &lt;code&gt;nixpkgs&lt;/code&gt; is available at the top-level because we explicitly passed it
as an argument but for individual modules outside this flake the scope is lost,
and you need to use &lt;code&gt;inputs.nixpkgs&lt;/code&gt; (or equivalent)&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;It explicitly names the &lt;code&gt;self&lt;/code&gt; attribute, making it directly accessible. The
variadic &lt;code&gt;...&lt;/code&gt; ellipses part of the function signature is what allows all
your flake inputs to be brought into the function’s scope without having to
list each one explicitly.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;It destructures all other attributes (your defined &lt;code&gt;inputs&lt;/code&gt;) into the
functions scope.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;It gives you a convenient single variable, &lt;code&gt;inputs&lt;/code&gt;, that refers to the
entire attribute set passed to the &lt;code&gt;outputs&lt;/code&gt; function. This allows you to
access inputs either individually (e.g. &lt;code&gt;nixpkgs&lt;/code&gt;) or through the &lt;code&gt;inputs&lt;/code&gt;
variable (e.g. &lt;code&gt;inputs.nixpkgs&lt;/code&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You can also define additional arbitrary attributes, but these are the outputs
that Nix knows about.&lt;/p&gt;
&lt;p&gt;As you can see, the majority of the outputs within the outputs schema expect a
derivation. This means that for packages, applications, formatters, checks, and
development shells, you’ll be defining a Nix derivation—a set of instructions
that tells Nix how to build a particular software component. This is central to
Nix’s declarative nature.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The command &lt;code&gt;nix flake show&lt;/code&gt;, takes a flake URI and prints all the outputs of
the flake as a nice tree structure, mapping attribute paths to the types of
values.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;  ~/players/third  3s
❯ nix flake show
path:/home/jr/players/third?lastModified=1748272555&amp;amp;narHash=sha256-oNzkC6X9hA0MpOBmJSZ89w4znXxv4Q5EkFhp0ewehY0%3D
├───nixosConfigurations
│   └───testing: NixOS configuration
└───nixosModules
    └───default: NixOS module
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To show you the structure of this little flake project:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;  ~/players
❯ tree
 .
├──  first
│   ├──  flake.lock
│   ├──  flake.nix
│   └──  result -&amp;gt; /nix/store/701vyaanmqchd2nnaq71y65v8ws11zx0-nixos-system-nixos-24.11.20250523.f09dede
├──  second
│   ├──  flake.lock
│   └──  flake.nix
└──  third
    ├──  flake.lock
    ├──  flake.nix
    └──  result -&amp;gt; /nix/store/mlszr5ws3xaly8m4q9jslgs31w6w76y2-nixos-system-nixos-24.11.20250523.f09dede
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Simple Example providing an output&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# flake.nix
{
  outputs = { self }: {
    bada = &quot;bing&quot;;
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can then evaluate this specific output using &lt;code&gt;nix eval&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix eval .#bada
&quot;bing&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Outputs understood by Nix&lt;/h2&gt;
&lt;p&gt;While the attribute set that &lt;code&gt;outputs&lt;/code&gt; returns may contain arbitrary attributes,
meaning any valid Nix value. Some of the standard outputs are understood by
various &lt;code&gt;nix&lt;/code&gt; utilities. &lt;code&gt;packages&lt;/code&gt; is one of these:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# flake.nix
{
  inputs = {
    nixpkgs.url = &quot;github:nixos/nixpkgs&quot;;
  };

  outputs = { self, nixpkgs }: {
    # this is the re-exporting part!
    packages.x86_64-linux.hello = nixpkgs.legacyPackages.x86_64-linux.hello;
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Re-exporting happens when you take the value of &lt;code&gt;hello&lt;/code&gt; in its standard
derivation format, exactly as &lt;code&gt;nixpkgs&lt;/code&gt; produces it and assign it to an
attribute in your own flake’s outputs.
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;packages.x86_64-linux.hello&lt;/code&gt;(your flake’s output path) &lt;code&gt;=&lt;/code&gt;
&lt;code&gt; nixpkgs.legacyPackages.x86_64-linux.hello&lt;/code&gt;(the source from the &lt;code&gt;nixpkgs&lt;/code&gt;
flake’s output)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;We’re saying, My flakes &lt;code&gt;hello&lt;/code&gt; package is exactly the same as the &lt;code&gt;hello&lt;/code&gt;
package found inside the &lt;code&gt;nixpkgs&lt;/code&gt; input flake.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;It’s important to understand that within the scope of the &lt;code&gt;outputs&lt;/code&gt; function
(i.e. within your flake), &lt;code&gt;nixpkgs&lt;/code&gt; is available at the top-level (i.e. the
&lt;code&gt;= nixpkgs&lt;/code&gt; part) because we explicitly passed it as an argument but for
individual modules outside of this flake the scope is lost, and
&lt;code&gt;inputs.nixpkgs&lt;/code&gt; is needed.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The following command builds the reexported package:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix build .#hello
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or run it with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix run .#hello
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You might notice &lt;code&gt;x86_64-linux&lt;/code&gt; appearing in the package path, and there’s a
good reason for it. Flakes are designed to provide &lt;em&gt;hermetic evaluation&lt;/em&gt;,
meaning their outputs should be identical regardless of the environment where
they’re built. A key factor in any build system is the platform (which combines
the architecture and operating system, like &lt;code&gt;x86_64-linux&lt;/code&gt; or &lt;code&gt;aarch64-darwin&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Because of Nix’s commitment to reproducibility across different systems, any
flake output that involves building software packages must explicitly specify
the platform. The standard approach is to structure these outputs as an
attribute set where the names are platforms, and the values are the outputs
specific to that platform. For the packages output, each platform-specific value
is itself an attribute set containing the various packages built for that
particular system.&lt;/p&gt;
&lt;h2&gt;Exporting Functions&lt;/h2&gt;
&lt;p&gt;This example outputs a &lt;code&gt;sayGoodbye&lt;/code&gt; function, via the &lt;code&gt;lib&lt;/code&gt; attribute, that
takes a name for its input and outputs a string saying Goodbye very nicely to
the person with that name:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{
  outputs = { self }: {
    lib = {
      sayGoodbye = name: &quot;Goodbye F*** Off, ${name}!&quot;;
    };
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You could then specify this flake as an input to another flake and use
&lt;code&gt;sayGoodbye&lt;/code&gt; however you’d like.&lt;/p&gt;
&lt;p&gt;Or load it into the &lt;code&gt;nix repl&lt;/code&gt; like so:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix repl
nix-repl&amp;gt; :lf .
nix-repl&amp;gt; lib.sayGoodbye
«lambda sayGoodbye @ /nix/store/665rwfvkwdx6kwvk9ldijp2a6jvcgv1n-source/flake.nix:4:20»
nix-repl&amp;gt; lib.sayGoodbye &quot;Jr&quot;
&quot;Goodbye F*** Off, Jr!&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;As you can see, specifying &lt;code&gt;lib.sayGoodbye&lt;/code&gt; without any arguments returns a
function. (a lambda function)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Simplifying Multi-Platform Outputs with flake-utils&lt;/h2&gt;
&lt;p&gt;Manually repeating these platform definitions for every output (&lt;code&gt;packages&lt;/code&gt;,
&lt;code&gt;devShells&lt;/code&gt;, &lt;code&gt;checks&lt;/code&gt;, etc.) can quickly become verbose. This is where the
flake-utils helper flake comes in handy. It provides utilities to reduce
boilerplate when defining outputs for multiple systems.&lt;/p&gt;
&lt;p&gt;A commonly used function is &lt;code&gt;flake-utils.lib.eachDefaultSystem&lt;/code&gt;, which
automatically generates outputs for common platforms (like &lt;code&gt;x86_64-linux&lt;/code&gt;,
&lt;code&gt;aarch64-linux&lt;/code&gt;, &lt;code&gt;x86_64-darwin&lt;/code&gt;, &lt;code&gt;aarch64-darwin&lt;/code&gt;). This transforms your
outputs definition from manually listing each system to a more concise
structure:&lt;/p&gt;
&lt;h1&gt;Example using flake-utils&lt;/h1&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{
  inputs = {
    nixpkgs.url = &quot;github:NixOS/nixpkgs/nixos-unstable&quot;;
    flake-utils.url = &quot;github:numtide/flake-utils&quot;; # Don&apos;t forget to add flake-utils to inputs!
  };

  outputs = {
    self,
    nixpkgs,
    flake-utils,
    ...
  }:
    flake-utils.lib.eachDefaultSystem (
      system: let
        pkgs = import nixpkgs {inherit system;};
      in {
        packages.hello = pkgs.hello; # Now directly defines &apos;hello&apos; for the current &apos;system&apos; # packages.default = self.packages.${system}.hello; # Optional default alias
        devShells.default = pkgs.mkShell {
          packages = [pkgs.hello];
        };
      }
    );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;This flake-utils pattern is particularly useful for defining consistent
development environments across platforms, which can then be activated simply
by running &lt;code&gt;nix develop&lt;/code&gt; in the flake’s directory.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Adding Formatter, Checks, and Devshell Outputs&lt;/h3&gt;
&lt;p&gt;This is a minimal flake for demonstration with a hardcoded &lt;code&gt;system&lt;/code&gt;, for more
portability:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{
  description = &quot;NixOS configuration&quot;;

  inputs = {
    nixpkgs.url = &quot;github:nixos/nixpkgs/nixos-unstable&quot;;
    home-manager.url = &quot;github:nix-community/home-manager&quot;;
    home-manager.inputs.nixpkgs.follows = &quot;nixpkgs&quot;;
    treefmt-nix.url = &quot;github:numtide/treefmt-nix&quot;;
   };

  outputs = inputs@{ nixpkgs, home-manager, treefmt-nix, ... }: let

    system = &quot;x86_64-linux&quot;;
    host = &quot;your-hostname-goes-here&quot;;
      # Define pkgs with allowUnfree
    pkgs = import inputs.nixpkgs {
      inherit system;
      config.allowUnfree = true;
    };

        # Formatter configuration
    treefmtEval = treefmt-nix.lib.evalModule pkgs ./lib/treefmt.nix;

in {

    formatter.${system} = treefmtEval.config.build.wrapper;

    # Style check for CI
    checks.${system}.style = treefmtEval.config.build.check self;

    # Development shell
    devShells.${system}.default = import ./lib/dev-shell.nix {
      inherit inputs;
    };


    nixosConfigurations = {
      hostname = nixpkgs.lib.nixosSystem {
        system = &quot;x86_64-linux&quot;;
        modules = [
          ./configuration.nix
          home-manager.nixosModules.home-manager
          {
            home-manager.useGlobalPkgs = true;
            home-manager.useUserPackages = true;
            home-manager.users.jdoe = ./home.nix;

            # Optionally, use home-manager.extraSpecialArgs to pass
            # arguments to home.nix
          }
        ];
      };
    };
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And in &lt;code&gt;lib/treefmt.nix&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# treefmt.nix
{
  projectRootFile = &quot;flake.nix&quot;;
  programs = {
    alejandra.enable = true;
    deadnix.enable = true;
    # rustfmt.enable = true;
    # shellcheck.enable = true;
    # prettier.enable = true;
    statix.enable = true;
    keep-sorted.enable = true;
    # nixfmt = {
    #   enable = true;
    #   # strict = true;
    # };
  };
  settings = {
    global.excludes = [
      &quot;LICENSE&quot;
      &quot;README.md&quot;
      &quot;.adr-dir&quot;
      &quot;nu_scripts&quot;
      # unsupported extensions
      &quot;*.{gif,png,svg,tape,mts,lock,mod,sum,toml,env,envrc,gitignore,sql,conf,pem,*.so.2,key,pub,py,narHash}&quot;
      &quot;data-mesher/test/networks/*&quot;
      &quot;nss-datamesher/test/dns.json&quot;
      &quot;*.age&quot;
      &quot;*.jpg&quot;
      &quot;*.nu&quot;
      &quot;*.png&quot;
      &quot;.jj/*&quot;
      &quot;Cargo.lock&quot;
      &quot;flake.lock&quot;
      &quot;hive/moonrise/borg-key-backup&quot;
      &quot;justfile&quot;
    ];
    formatter = {
      deadnix = {
        priority = 1;
      };
      statix = {
        priority = 2;
      };
      alejandra = {
        priority = 3;
      };
    };
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we have a few commands available to us in our flake directory:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;nix fmt&lt;/code&gt;: Will format your whole configuration consistently&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;nix flake check&lt;/code&gt;: While this command was already available, it is now tied to
treefmt’s check which will check the style of your syntax and provide
suggestions.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And this is &lt;code&gt;lib/dev-shell.nix&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{
  inputs,
  system ? &quot;x86_64-linux&quot;,
}: let
  # Instantiate nixpkgs with the given system and allow unfree packages
  pkgs = import inputs.nixpkgs {
    inherit system;
    config.allowUnfree = true;
    overlays = [
      # Add overlays if needed, e.g., inputs.neovim-nightly-overlay.overlays.default
    ];
  };
in
  pkgs.mkShell {
    name = &quot;nixos-dev&quot;;
    packages = with pkgs; [
      # Nix tools
      nixfmt-rfc-style # Formatter
      deadnix # Dead code detection
      nixd # Nix language server
      nil # Alternative Nix language server
      nh # Nix helper
      nix-diff # Compare Nix derivations
      nix-tree # Visualize Nix dependencies

      # Code editing
      helix # Your editor

      # General utilities
      git
      ripgrep
      jq
      tree
    ];

    shellHook = &apos;&apos;
      echo &quot;Welcome to the NixOS development shell!&quot;
      echo &quot;System: ${system}&quot;
      echo &quot;Tools available: nixfmt, deadnix, nixd, nil, nh, nix-diff, nix-tree, helix, git, ripgrep, jq, tree&quot;
    &apos;&apos;;
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now you can run &lt;code&gt;nix develop&lt;/code&gt; in the flake directory and if successfull, you’ll
see the &lt;code&gt;echo&lt;/code&gt; commands above and you will have all the tools available in your
environment without having to explicitly install them.&lt;/p&gt;
</content></entry><entry><title>Flake outputs</title><id>https://saylesss88.github.io/flakes/flake_examples_4.3.html</id><updated>2025-11-28T00:00:00+00:00</updated><author><name>saylesss87@proton.me (saylesss88)</name></author><link href="https://saylesss88.github.io/flakes/flake_examples_4.3.html" rel="alternate"/><content type="html">&lt;h1&gt;Nix Flake Examples&lt;/h1&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Click to Expand Table of Contents&lt;/summary&gt;
&lt;!-- toc --&gt;
&lt;/details&gt;
&lt;p&gt;This chapter provides practical examples to illustrate the concepts discussed in
“Nix Flakes Explained.”&lt;/p&gt;
&lt;h2&gt;Example showing the extensibility of Flakes&lt;/h2&gt;
&lt;p&gt;NixOS modules and configurations offer us a powerful and composable way to
define and share system configurations. Imagine we have several independent
“players,” each with their own unique set of configurations or modules. How do
we combine these individual contributions into a single, cohesive system without
directly altering each player’s original flake?&lt;/p&gt;
&lt;p&gt;This example demonstrates how flakes can extend and compose each other, allowing
you to layer configurations on top of existing ones. This is particularly useful
when you want to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Build upon a base configuration without modifying its source.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Combine features from multiple independent flakes into a single system.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create specialized versions of an existing configuration.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let’s simulate this by creating a players directory with three sub-directories:
&lt;code&gt;first&lt;/code&gt;, &lt;code&gt;second&lt;/code&gt;, and &lt;code&gt;third&lt;/code&gt;. Each of these will contain its own &lt;code&gt;flake.nix&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mkdir players
cd players
mkdir first
mkdir second
mkdir third
cd first
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now create a &lt;code&gt;flake.nix&lt;/code&gt; with the following contents:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# flake.nix
{
  inputs = {
    nixpkgs.url = &quot;github:nixos/nixpkgs/nixos-24.11&quot;;
  };

  outputs = {
    self,
    nixpkgs,
  }: {
    nixosModules.default = {
      config,
      pkgs,
      lib,
      ...
    }: {
      # Create a file `/etc/first-file`
      environment.etc.first-file.text = &quot;Hello player # 1!&quot;;
      boot.initrd.includeDefaultModules = false;
      documentation.man.enable = false;
      boot.loader.grub.enable = false;
      fileSystems.&quot;/&quot;.device = &quot;/dev/null&quot;;
      system.stateVersion = &quot;24.11&quot;;
    };
    nixosConfigurations.testing = nixpkgs.lib.nixosSystem {
      system = &quot;x86_64-linux&quot;;
      modules = [
        self.nixosModules.default
      ];
    };
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;This demonstrates using &lt;code&gt;self&lt;/code&gt; to reference this flake from within its own
outputs. This is the main use for &lt;code&gt;self&lt;/code&gt; with flakes. Without &lt;code&gt;self&lt;/code&gt;, I
wouldn’t have a direct way to refer to the &lt;code&gt;nixosModules.default&lt;/code&gt; that’s
defined within the same flake.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now in the &lt;code&gt;players/second&lt;/code&gt; directory create this &lt;code&gt;flake.nix&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# flake.nix
{
  inputs = {
    nixpkgs.url = &quot;github:nixos/nixpkgs/nixos-24.11&quot;;
  };

  outputs = {
    self,
    nixpkgs,
  }: {
    nixosModules.default = {
      config,
      pkgs,
      lib,
      ...
    }: {
      # Create a file `/etc/second-file`
      environment.etc.second-file.text = &quot;Hello player # 2!&quot;;
    };
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;nixosModules.default&lt;/code&gt; is a module which is a function that, when called by
the NixOS module system, returns an attribute set representing a piece of
system configuration.
&lt;ul&gt;
&lt;li&gt;Within that attribute set, it specifies that the file &lt;code&gt;/etc/second-file&lt;/code&gt;
should exist with “Hello player # 2!” as its content.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And finally in &lt;code&gt;players/third&lt;/code&gt; create another &lt;code&gt;flake.nix&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# flake.nix
{
  inputs = {
    first.url = &quot;/home/jr/players/first&quot;;
    nixpkgs.follows = &quot;first/nixpkgs&quot;;
    second = {
      url = &quot;/home/jr/players/second&quot;;
      inputs.nixpkgs.follows = &quot;first/nixpkgs&quot;;
    };
  };

  outputs = {
    self,
    nixpkgs,
    first,
    second,
  }:
    first.outputs
    // {
      nixosConfigurations.testing = first.nixosConfigurations.testing.extendModules {
        modules = [
          second.nixosModules.default
        ];
      };
    };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;You’ll have to change the locations to where you placed your &lt;code&gt;players&lt;/code&gt;
directory in the &lt;code&gt;inputs&lt;/code&gt; above.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In your &lt;code&gt;third&lt;/code&gt; directory inspect it with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;  ~/players/third
❯ nix flake show
path:/home/jr/players/third?lastModified=1748271697&amp;amp;narHash=sha256-oNzkC6X9hA0MpOBmJSZ89w4znXxv4Q5EkFhp0ewehY0%3D
├───nixosConfigurations
│   └───testing: NixOS configuration
└───nixosModules
    └───default: NixOS module
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and build it with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix build .#nixosConfigurations.testing.config.system.build.toplevel
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cat result/etc/first-file
Hello player # 1!
cat result/etc/second-file
Hello player # 2!
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Understanding the Extension&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;As you saw in the &lt;code&gt;flake.nix&lt;/code&gt; for the third player, we leveraged two key flake
features to combine and extend the previous configurations:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Attribute Set Union&lt;/strong&gt; (&lt;code&gt;//&lt;/code&gt; operator):&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;outputs = { ..., first, second, ... }:
first.outputs // { # ... your extensions here ...
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;//&lt;/code&gt; (attribute set union) operator allows us to take all the outputs from
&lt;code&gt;first.outputs&lt;/code&gt; (which includes its &lt;code&gt;nixosConfigurations&lt;/code&gt; and &lt;code&gt;nixosModules&lt;/code&gt;)
and then overlay or add to them on the right-hand side. This means our third
flake will inherit all the outputs from first, but we can then modify or add new
ones without changing the first flake itself.&lt;/p&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;&lt;code&gt;config.extendModules&lt;/code&gt;:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;    nixosConfigurations.testing = first.nixosConfigurations.testing.extendModules {
      modules = [
        second.nixosModules.default
      ];
    };
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is the core of the extension. We’re taking the testing NixOS configuration
defined in the first flake (&lt;code&gt;first.nixosConfigurations.testing&lt;/code&gt;) and then
calling its &lt;code&gt;extendModules&lt;/code&gt; function. This function allows us to inject
additional NixOS modules into an already defined system configuration. In this
case, we’re adding the default module from the second flake
(&lt;code&gt;second.nixosModules.default&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;By combining these techniques, the third flake successfully creates a NixOS
configuration that includes both the settings from first (like &lt;code&gt;/etc/first-file&lt;/code&gt;
and the base system options) and the settings from second (like
&lt;code&gt;/etc/second-file&lt;/code&gt;), all without directly altering the first or second flakes.
This demonstrates the incredible power of flake extensibility for building
complex, modular, and composable systems.&lt;/p&gt;
</content></entry><entry><title>Nix Pull Requests</title><id>https://saylesss88.github.io/Nix_Pull_Requests_11.html</id><updated>2025-11-27T00:00:00+00:00</updated><author><name>saylesss87@proton.me (saylesss88)</name></author><link href="https://saylesss88.github.io/Nix_Pull_Requests_11.html" rel="alternate"/><content type="html">&lt;h1&gt;Chapter 11&lt;/h1&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Click to Expand Table of Contents&lt;/summary&gt;
&lt;!-- toc --&gt;
&lt;/details&gt;
&lt;p&gt;&lt;img src=&quot;https://saylesss88.github.io/images/gruv16.png&quot; alt=&quot;gruv16&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Nix Pull Requests&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Pull requests&lt;/strong&gt; communicate changes to a branch in a repository. Once a pull
request is opened, you can review changes with collaborators and add follow-up
commits.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;A &lt;strong&gt;pull request&lt;/strong&gt; is a proposal to merge a set of changes from one branch
into another. In a pull request, collaborators can review and discuss the
proposed set of changes before they integrate the changes into the main
codebase.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Pull requests display the differences, or diffs, between the content in the
source branch and the content in the target branch.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;graph LR
    A[Your Local Repository] --&amp;gt; B(Feature Branch);
    B --&amp;gt; C{GitHub Repository};
    C -- &quot;Open Pull Request&quot; --&amp;gt; D[Pull Request on GitHub];
    D -- &quot;Review &amp;amp; Discussion&quot; --&amp;gt; D;
    D -- &quot;Merge&quot; --&amp;gt; E(Main Branch on GitHub);
    E --&amp;gt; F[Nixpkgs Users];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Explanation of the Diagram&lt;/strong&gt;:&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Click to see Explanation &lt;/summary&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;A[Your Local Repository]&lt;/strong&gt;: This represents the copy of the Nixpkgs repo on
your computer where you make changes.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;B (Feature Branch)&lt;/strong&gt;: You create a dedicated branch (e.g.&lt;code&gt;my-pack-update&lt;/code&gt;)
to isolate your changes.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;C {GitHub Repository}&lt;/strong&gt;: This is the central online repo for Nixpkgs on
Github. You push your feature branch to this repo.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;C – “Open Pull Request” – D [Pull Request on Github]&lt;/strong&gt;: You initiate a
pull request from your feature branch to the main branch (usually &lt;code&gt;master&lt;/code&gt; or
&lt;code&gt;main&lt;/code&gt;) through the GitHub interface.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;D [Pull Request on GitHub]&lt;/strong&gt;: This is where collaborators can see your
proposed changes, discuss them, and provide feedback.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;D – “Review &amp;amp; Discussion” –&amp;gt; D&lt;/strong&gt;: The pull request facilitates
communication and potential revisions based on the review.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;D – “Merge” –&amp;gt; E (Main Branch on GitHub)&lt;/strong&gt;: Once the changes are approved,
they are merged into the main branch of the Nixpkgs repository.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;E (Main Branch on GitHub)&lt;/strong&gt;: The main branch now contains the integrated
changes.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;E –&amp;gt; F [Nixpkgs Users]&lt;/strong&gt;): Eventually, these changes become available to
all Nixpkgs users through updates to their Nix installations.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/details&gt;
&lt;p&gt;Flakes often rely on having access to the full history of the Git repository to
correctly determine dependencies, identify specific revisions of inputs, and
evaluate the flake. Not in all situations will a shallow clone work and this is
one of them.&lt;/p&gt;
&lt;p&gt;If you have any changes to your local copy of Nixpkgs make sure to stash them
before the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git stash -u
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;This command saves your uncommited changes (including staged files)
temporarily. You can restore them later with &lt;code&gt;git stash pop&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Step 1 Clone Nixpkgs Locally&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If you don’t have Nixpkgs locally, you’ll need to clone it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git clone https://github.com/NixOS/nixpkgs.git
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Step 2 Find a Relevant Pull Request&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;To find specifig commits and releases:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://status.nixos.org/&quot;&gt;status.nixos.org&lt;/a&gt; provides the latest tested commits
for each release - use when pinning to specific commits. List of active release
channels - use when tracking latest channel versions.&lt;/p&gt;
&lt;p&gt;The complete list of channels is available at
&lt;a href=&quot;https://channels.nixos.org/&quot;&gt;nixos.org/channels&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;To find a relevant PR you can go to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/NixOS/nixpkgs/pulls&quot;&gt;Nixpkgs Pull Requests&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The following example actually uses the
&lt;a href=&quot;https://github.com/NixOS/nix/pulls&quot;&gt;Nix Pull Requests&lt;/a&gt; the process is the
same, but that is an important distinction.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In the Filters enter &lt;code&gt;stack trace&lt;/code&gt; for this example.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The pull request I chose was &lt;a href=&quot;https://github.com/nixos/nix/pull/8623&quot;&gt;8623&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Step 3 Add the Remote Repository (if necessary)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If the pull request is from a different repository than your local clone (as in
the case of the &lt;code&gt;nix&lt;/code&gt; PR while working in a &lt;code&gt;nixpkgs&lt;/code&gt; clone), you need to add
that repository as a remote. It’s common to name the main Nixpkgs remote
&lt;code&gt;origin&lt;/code&gt; and other related repositories like &lt;code&gt;nix&lt;/code&gt; as &lt;code&gt;upstream&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Assuming you are in your &lt;code&gt;nixpkgs&lt;/code&gt; clone and want to test a PR from the &lt;code&gt;nix&lt;/code&gt;
repository:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git remote add upstream https://github.com/NixOS/nix.git
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Step 4 Fetch the Pull Request Changes&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Fetch the Pull Request Information:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git fetch upstream refs/pull/8623/head:pr-8623
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;This command fetches the branch named &lt;code&gt;head&lt;/code&gt; from the pull request &lt;code&gt;8623&lt;/code&gt; in
the &lt;code&gt;upstream&lt;/code&gt; remote and creates a local branch named &lt;code&gt;pr-8623&lt;/code&gt; that tracks
it.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Output&lt;/strong&gt;:&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Output (Click to Enlarge) &lt;/summary&gt;
&lt;pre&gt;&lt;code&gt;remote: Enumerating objects: 104651, done.
remote: Counting objects: 100% (45/45), done.
remote: Compressing objects: 100% (27/27), done.
remote: Total 104651 (delta 33), reused 20 (delta 18), pack-reused 104606 (from 1)
Receiving objects: 100% (104651/104651), 61.64 MiB | 12.56 MiB/s, done.
Resolving deltas: 100% (74755/74755), done.
From https://github.com/NixOS/nix
 * [new ref]             refs/pull/8623/head -&amp;gt; pr-8623
 * [new tag]             1.0                 -&amp;gt; 1.0
 * [new tag]             1.1                 -&amp;gt; 1.1
 * [new tag]             1.10                -&amp;gt; 1.10
 * [new tag]             1.11                -&amp;gt; 1.11
 * [new tag]             1.11.1              -&amp;gt; 1.11.1
 * [new tag]             1.2                 -&amp;gt; 1.2
 * [new tag]             1.3                 -&amp;gt; 1.3
 * [new tag]             1.4                 -&amp;gt; 1.4
 * [new tag]             1.5                 -&amp;gt; 1.5
 * [new tag]             1.5.1               -&amp;gt; 1.5.1
 * [new tag]             1.5.2               -&amp;gt; 1.5.2
 * [new tag]             1.5.3               -&amp;gt; 1.5.3
 * [new tag]             1.6                 -&amp;gt; 1.6
 * [new tag]             1.6.1               -&amp;gt; 1.6.1
 * [new tag]             1.7                 -&amp;gt; 1.7
 * [new tag]             1.8                 -&amp;gt; 1.8
 * [new tag]             1.9                 -&amp;gt; 1.9
 * [new tag]             2.0                 -&amp;gt; 2.0
 * [new tag]             2.2                 -&amp;gt; 2.2
&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;
&lt;p&gt;&lt;strong&gt;Step 5 Checkout the Local Branch:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git checkout pr-8623
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or with the &lt;code&gt;gh&lt;/code&gt; cli:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;gh pr checkout 8623
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Build and Test the Changes&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Now we want to see if the code changes introduced by the pull request actually
build correctly within the Nix ecosystem.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix build
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Output:&lt;/strong&gt;&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Output (Click to Enlarge) &lt;/summary&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;error: builder for &apos;/nix/store/rk86daqgf6a9v6pdx6vcc5b580lr9f09-nix-2.20.0pre20240115_20b4959.drv&apos; failed with exit code 2;
   last 25 log lines:
   &amp;gt;
   &amp;gt;         _NIX_TEST_ACCEPT=1 make tests/functional/lang.sh.test
   &amp;gt;
   &amp;gt;     to regenerate the files containing the expected output,
   &amp;gt;     and then view the git diff to decide whether a change is
   &amp;gt;     good/intentional or bad/unintentional.
   &amp;gt;     If the diff contains arbitrary or impure information,
   &amp;gt;     please improve the normalization that the test applies to the output.
   &amp;gt; make: *** [mk/lib.mk:90: tests/functional/lang.sh.test] Error 1
   &amp;gt; make: *** Waiting for unfinished jobs....
   &amp;gt; ran test tests/functional/selfref-gc.sh... [PASS]
   &amp;gt; ran test tests/functional/store-info.sh... [PASS]
   &amp;gt; ran test tests/functional/suggestions.sh... [PASS]
   &amp;gt; ran test tests/functional/path-from-hash-part.sh... [PASS]
   &amp;gt; ran test tests/functional/gc-auto.sh... [PASS]
   &amp;gt; ran test tests/functional/path-info.sh... [PASS]
   &amp;gt; ran test tests/functional/flakes/show.sh... [PASS]
   &amp;gt; ran test tests/functional/fetchClosure.sh... [PASS]
   &amp;gt; ran test tests/functional/completions.sh... [PASS]
   &amp;gt; ran test tests/functional/build.sh... [PASS]
   &amp;gt; ran test tests/functional/impure-derivations.sh... [PASS]
   &amp;gt; ran test tests/functional/build-delete.sh... [PASS]
   &amp;gt; ran test tests/functional/build-remote-trustless-should-fail-0.sh... [PASS]
   &amp;gt; ran test tests/functional/build-remote-trustless-should-pass-2.sh... [PASS]
   &amp;gt; ran test tests/functional/nix-profile.sh... [PASS]
   For full logs, run:
     nix log /nix/store/rk86daqgf6a9v6pdx6vcc5b580lr9f09-nix-2.20.0pre20240115_20b4959.drv
&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;nix build&lt;/code&gt;&lt;/strong&gt; (Part of the Nix Unified CLI):
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Declarative: when used within a Nix flake (&lt;code&gt;flake.nix&lt;/code&gt;), &lt;code&gt;nix build&lt;/code&gt; is a
bit more declarative. It understands the outputs defined in your flake.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Clearer Output Paths: &lt;code&gt;nix build&lt;/code&gt; typically places build outputs in the
&lt;code&gt;./result&lt;/code&gt; directory by default (similar to &lt;code&gt;nix-build&lt;/code&gt;’s &lt;code&gt;result&lt;/code&gt; symlink)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Better Error Reporting: It gives more informative error messages.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Future Direction&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Benefits of using &lt;code&gt;nix build&lt;/code&gt;:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Flake Integration:&lt;/strong&gt; &lt;code&gt;nix build&lt;/code&gt; naturally understands the flake’s outputs.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Development Shells:&lt;/strong&gt; When you are in a &lt;code&gt;nix develop&lt;/code&gt; shell, &lt;code&gt;nix build&lt;/code&gt; is
the more idiomatic way to build packages defined in your dev environment.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Consistency:&lt;/strong&gt; Using the unified CLI promotes a more consistent workflow.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Next Steps&lt;/h2&gt;
&lt;p&gt;As you can see this build failed, as for why the build failed, the key part of
the error message is:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;make: *** [mk/lib.mk:90: tests/functional/lang.sh.test] Error 1
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;This suggests that one of the functional tests (&lt;code&gt;lang.sh.test&lt;/code&gt;) failed. This
happens when the expected output of the test doesn’t match the actual output.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This can heppen when:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;The test expectations are outdated due to changes in the codebase.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The test captures environment-specific or transient outputs that are not
properly normalized.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The test includes impure or non-deterministic information, making it hard to
verify.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;To address this, _NIX_TEST_ACCEPT=1 is used as an override mechanism that tells
the test framework: &amp;gt; “Accept whatever output is generated as the new expected
result.”&lt;/p&gt;
&lt;p&gt;The message advises running:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;_NIX_TEST_ACCEPT=1 make tests/functional/lang.sh.test
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;This will regenerate the expected output files, allowing you to inspect what
changed with &lt;code&gt;git diff&lt;/code&gt;:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git diff tests/functional/lang.sh.test
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Verifies if Changes are Intentional:&lt;/strong&gt; If the difference is reasonable and
expected (due to a legitimate update in the logic), you can commit these
changes to update the test suit. If not, you have to refine the test
normalization process further.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If the changes seem valid, commit them:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git add tests/functional/lang.sh.test
git commit -m &quot;Update expected test output for lang.sh.test&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Running the following will provide the full logs:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix log /nix/store/rk86daqgf6a9v6pdx6vcc5b580lr9f09-nix-2.20.0pre20240115_20b4959.drv
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Testing Nixpkgs pull requests is a vital part of contributing to a healthy and
reliable Nix ecosystem. By following these steps, you can help ensure that
changes are well-vetted before being merged, ultimately benefiting all Nix
users. Your efforts in testing contribute significantly to the quality and
stability of Nixpkgs.&lt;/p&gt;
</content></entry><entry><title>Unencrypted BTRFS Impermanence with Flakes</title><id>https://saylesss88.github.io/installation/unenc/unenc_impermanence.html</id><updated>2025-11-24T00:00:00+00:00</updated><author><name>saylesss87@proton.me (saylesss88)</name></author><link href="https://saylesss88.github.io/installation/unenc/unenc_impermanence.html" rel="alternate"/><content type="html">&lt;h1&gt;Unencrypted BTRFS Impermanence with Flakes&lt;/h1&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Click to Expand Table of Contents&lt;/summary&gt;
&lt;!-- toc --&gt;
&lt;/details&gt;
&lt;p&gt;Figure 1: Impermanence Logo: Image of the Impermanence logo. Sourced from the&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/nix-community/impermanence&quot;&gt;Impermanence repo&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This guide is for an unencrypted setup, there are a few links at the end for
encrypted setups. This guide follows the previous
&lt;a href=&quot;https://saylesss88.github.io/installation/unencrypted_setups.html&quot;&gt;minimal install guide&lt;/a&gt;
but you should be able to adjust it carefully to meet your needs.&lt;/p&gt;
&lt;p&gt;This section details how to set up impermanence on your NixOS system using BTRFS
subvolumes. With impermanence, your operating system’s root filesystem will
reset to a pristine state on each reboot, while designated directories and files
remain persistent. This provides a highly reliable and rollback-friendly system.&lt;/p&gt;
&lt;p&gt;In NixOS, “state” is any data or condition of the system that isn’t defined in
your declarative configuration. The impermanence approach aims to make this
state temporary (ephemeral) or easily resettable, so your system always matches
your configuration and can recover from unwanted changes or corruption.&lt;/p&gt;
&lt;h2&gt;Impermanence: The Concept and Its BTRFS Implementation&lt;/h2&gt;
&lt;p&gt;In a traditional Linux system, most of this state is stored on the disk and
persists indefinitely unless manually deleted or modified. However, this can
lead to configuration drift, where the system accumulates changes (e.g., log
files, temporary files, or unintended configuration tweaks) that make it harder
to reproduce or maintain.&lt;/p&gt;
&lt;p&gt;Impermanence, in the context of operating systems, refers to a setup where the
majority of the system’s root filesystem (&lt;code&gt;/&lt;/code&gt;) is reset to a pristine state on
every reboot. This means any changes made to the system (e.g., installing new
packages, modifying system files outside of configuration management, creating
temporary files) are discarded upon shutdown or reboot.&lt;/p&gt;
&lt;h2&gt;What Does Impermanence Do?&lt;/h2&gt;
&lt;p&gt;Impermanence is a NixOS approach that makes the system stateless (or nearly
stateless) by wiping the root filesystem (&lt;code&gt;/&lt;/code&gt;) on each boot, ensuring a clean,
predictable starting point. Only explicitly designated data (persistent state)
is preserved across reboots, typically stored in specific locations like the
/nix/persist subvolume. This is possible because NixOS can boot with only the
&lt;code&gt;/boot&lt;/code&gt;, and &lt;code&gt;/nix&lt;/code&gt; directories. This achieves:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Clean Root Filesystem:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The root subvolume is deleted and recreated on each boot, erasing transient
state (e.g., temporary files, runtime data).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;This ensures the system starts fresh, reducing clutter and making it behave
closer to a declarative system defined by your NixOS configuration.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;Selective Persistence:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Critical state (e.g., user files, logs, system configuration) is preserved in
designated persistent subvolumes (e.g., /nix/persist, /var/log, /var/lib) or
files.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You control exactly what state persists by configuring
&lt;code&gt;environment.persistence.&quot;/nix/persist&quot;&lt;/code&gt; or other mechanisms.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;❗ The understanding around persisting &lt;code&gt;/var/lib/nixos&lt;/code&gt; seems to be evolving.
See,The importance of persisting &lt;code&gt;/var/lib/nixos&lt;/code&gt; See also necessary system
state&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;Reproducibility and Security:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;By wiping transient state, impermanence prevents unintended changes from
accumulating, making the system more reproducible.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;It enhances security by ensuring sensitive temporary data (e.g., /tmp, runtime
credentials) is erased on reboot.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Getting Started&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Add impermanence to your flake.nix. You will change the hostname in the flake
to match your networking.hostName.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# flake.nix
{
  description = &quot;NixOS configuration&quot;;

  inputs = {
    nixpkgs.url = &quot;github:nixos/nixpkgs/nixos-unstable&quot;;
    disko.url = &quot;github:nix-community/disko/latest&quot;;
    disko.inputs.nixpkgs.follows = &quot;nixpkgs&quot;;
    impermanence.url = &quot;github:nix-community/impermanence&quot;;
  };

  outputs = inputs@{ nixpkgs, ... }: {
    nixosConfigurations = {
      hostname = nixpkgs.lib.nixosSystem {
        system = &quot;x86_64-linux&quot;;
        modules = [
          ./configuration.nix
          inputs.disko.nixosModules.disko
          inputs.impermanence.nixosModules.impermanence
        ];
      };
    };
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;Discover where your root subvolume is located with &lt;code&gt;findmnt&lt;/code&gt;:&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Before configuring impermanence, it’s crucial to know the device path and
subvolume path of your main BTRFS partition where the root filesystem (/) is
located. This information is needed for the mount command within the
impermanence script.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;findmnt /
TARGET   SOURCE         FSTYPE OPTIONS
/        /dev/disk/by-partlabel/disk-main-root[/root]
                        btrfs  rw,noatime,compress=zstd:3,ssd,discard=async,space_cache=v2,subvolid=275,sub
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;From the SOURCE column, note the full path, including the device (e.g.,
&lt;code&gt;/dev/disk/by-partlabel/disk-main-root&lt;/code&gt;) and the subvolume in brackets (e.g.,
&lt;code&gt;[/root]&lt;/code&gt;). You will use the device path in the next step&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/dev/disk/by-partlabel/disk-main-root&lt;/code&gt; is a symlink to the actual device path
(e.g. &lt;code&gt;/dev/nvme0n1p2&lt;/code&gt;), but using the partlabel is generally more robust for
scripts.&lt;/p&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;Create an impermanence.nix:&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now, create a new file named &lt;code&gt;impermanence.nix&lt;/code&gt; in your configuration directory
(i.e. your flake directory). This file will contain all the specific settings
for your impermanent setup, including BTRFS subvolume management and persistent
data locations. Since this file is right next to your &lt;code&gt;configuration.nix&lt;/code&gt;,
you’ll just add an &lt;code&gt;imports = [ ./impermanence.nix&lt;/code&gt; ] to your
&lt;code&gt;configuration.nix&lt;/code&gt; apply it to your configuration.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{lib, ...}: {
  #  Reset root subvolume on boot
  boot.initrd.postResumeCommands = lib.mkAfter &apos;&apos;
    mkdir /btrfs_tmp
      mount /dev/disk/by-partlabel/disk-main-root /btrfs_tmp # CONFIRM THIS IS CORRECT FROM findmnt
      if [[ -e /btrfs_tmp/root ]]; then
        mkdir -p /btrfs_tmp/old_roots
        timestamp=$(date --date=&quot;@$(stat -c %Y /btrfs_tmp/root)&quot; &quot;+%Y-%m-%-d_%H:%M:%S&quot;)
        mv /btrfs_tmp/root &quot;/btrfs_tmp/old_roots/$timestamp&quot;
      fi

      delete_subvolume_recursively() {
        IFS=$&apos;\n&apos;
        for i in $(btrfs subvolume list -o &quot;$1&quot; | cut -f 9- -d &apos; &apos;); do
          delete_subvolume_recursively &quot;/btrfs_tmp/$i&quot;
        done
        btrfs subvolume delete &quot;$1&quot;
      }

      for i in $(find /btrfs_tmp/old_roots/ -maxdepth 1 -mtime +30); do
        delete_subvolume_recursively &quot;$i&quot;
      done

      btrfs subvolume create /btrfs_tmp/root
      umount /btrfs_tmp
  &apos;&apos;;

  # Use /persist as the persistence root, matching Disko&apos;s mountpoint
  environment.persistence.&quot;/nix/persist&quot; = {
    hideMounts = true;
    directories = [
      &quot;/etc&quot; # System configuration (Keep this here for persistence via bind-mount)
      &quot;/var/spool&quot; # Mail queues, cron jobs
      &quot;/srv&quot; # Web server data, etc.
      &quot;/root&quot;
    ];
    files = [
    ];
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With btrfs subvolumes since each directory is its own subvolume, when the root
is wiped on reboot the subvolumes are untouched.&lt;/p&gt;
&lt;h3&gt;Applying Your Impermanence Configuration&lt;/h3&gt;
&lt;p&gt;Once you have completed all the steps and created or modified the necessary
files (&lt;code&gt;flake.nix&lt;/code&gt;, &lt;code&gt;impermanence.nix&lt;/code&gt;), you need to apply these changes to your
NixOS system.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Navigate to your NixOS configuration directory (where your flake.nix is
located).&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd /path/to/your/flake
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;Rebuild and Switch: Execute the &lt;code&gt;nixos-rebuild switch&lt;/code&gt; command. This command
will:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Evaluate your flake.nix and the modules it imports (including your new
impermanence.nix).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Build a new NixOS system closure based on your updated configuration.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Activate the new system configuration, making it the current running system.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;❗ NOTE: On the first rebuild after setting up impermanence, you may find that
you’re not in the password database or cannot log in/sudo. This occurs because
the initial state of your new ephemeral root filesystem, including /etc (where
user passwords are stored), is fresh. It has to do with the timing of when
environment.persistence takes effect during the first boot.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;To avoid this password issue, before your first nixos-rebuild switch for
impermanence, run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo mkdir -p /nix/persist/etc # Ensure the target directory exists
sudo cp -a /etc/* /nix/persist/etc
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;This copies your current /etc directory contents (including existing user
passwords) into your persistent &amp;gt;&amp;gt;storage.&lt;/li&gt;
&lt;li&gt;Crucially: You must also ensure that &lt;code&gt;/etc&lt;/code&gt; is explicitly included in your
&lt;code&gt;environment.persistence.&quot;/nix/persist&quot;&lt;/code&gt;.directories list in your
&lt;code&gt;impermanence.nix&lt;/code&gt; like we did above, (or main configuration). This
configures &amp;gt;NixOS to persistently bind-mount &lt;code&gt;/nix/persist/etc&lt;/code&gt; over &lt;code&gt;/etc&lt;/code&gt;
on every subsequent boot. Once these steps are done and you reboot, your
user passwords should function correctly, and future rebuilds will &amp;gt; not
present this problem.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo nixos-rebuild switch --flake .#hostname # Replace &apos;hostname&apos; with your actual system hostname
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;Perform an Impermanence Test (Before Reboot):&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;Before you reboot, create a temporary directory and file in a non-persistent
location. Since you haven’t explicitly added &lt;code&gt;/imperm_test&lt;/code&gt; to your
&lt;code&gt;environment.persistence.&quot;/nix/persist&quot;&lt;/code&gt; directories, this file should not
survive a reboot.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mkdir /imperm_test
echo &quot;This should be Gone after Reboot&quot; | sudo tee /imperm_test/testfile
ls -l /imperm_test/testfile # Verify the file exists
cat /imperm_test/testfile # Verify content
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;Reboot Your System: For the impermanence setup to take full effect and for
your root filesystem to be reset for the first time, you must reboot your
machine.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo reboot
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;5&quot;&gt;
&lt;li&gt;Verify Impermanence (After Reboot):&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;After the system has rebooted, check if the test directory and file still
exist:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ls -l /imperm_test/testfile
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You should see an output like &lt;code&gt;ls: cannot access &apos;/imperm_test/testfile&apos;&lt;/code&gt;: No
such file or directory. This confirms that the &lt;code&gt;/imperm_test&lt;/code&gt; directory and its
contents were indeed ephemeral and were removed during the reboot process,
indicating your impermanence setup is working correctly!&lt;/p&gt;
&lt;p&gt;Your system should now come up with a fresh root filesystem, and only the data
specified in your &lt;code&gt;environment.persistence.&quot;/nix/persist&quot;&lt;/code&gt; configuration will be
persistent.&lt;/p&gt;
&lt;h3&gt;Recovery with nixos-enter and chroot&lt;/h3&gt;
&lt;p&gt;This is if you followed the minimal_install guide, it will need to be changed
for a different disk layout.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Chroot&quot;&gt;Chroot&lt;/a&gt; is an operation that changes the
apparent root directory for the current running process and their children. A
program that is run in such a modified environment cannot access files and
commands outside that environmental directory tree. This modified environment is
called a chroot jail. –NixOS wiki&lt;/p&gt;
&lt;p&gt;&lt;code&gt;nixos-enter&lt;/code&gt; allows you to access a NixOS installation from a NixOS rescue
system. To use, setup &lt;code&gt;/mnt&lt;/code&gt; as described in the
&lt;a href=&quot;https://nixos.org/manual/nixos/stable/#sec-installation&quot;&gt;installation manual&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;🛠️ Recovery: Chroot into Your NixOS Btrfs+Impermanence System&lt;/p&gt;
&lt;p&gt;Take note of your layout from commands like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo fdisk -l
lsblk
sudo btrfs subvol list /
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Also inspect your &lt;code&gt;disk-config.nix&lt;/code&gt; to ensure you refer to the correct &lt;code&gt;subvol=&lt;/code&gt;
names.&lt;/p&gt;
&lt;p&gt;If you need to repair your system (e.g., forgot root password, fix a broken
config, etc.), follow these steps to chroot into your NixOS install:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Boot a Live ISO&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Boot from a NixOS (or any recent Linux) live USB.&lt;/p&gt;
&lt;p&gt;Open a terminal and become root:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo -i
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;Identify Your Devices&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Your main disk is &lt;code&gt;/dev/nvme0n1&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;EFI partition: &lt;code&gt;/dev/nvme0n1p1&lt;/code&gt; (mounted at &lt;code&gt;/boot&lt;/code&gt;)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Root partition: &lt;code&gt;/dev/nvme0n1p2&lt;/code&gt; (Btrfs, with subvolumes)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;Mount the Btrfs Root Subvolume&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;First, mount the Btrfs partition somewhere temporary (not as / yet):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mount -o subvol=root,compress=zstd,noatime /dev/nvme0n1p2 /mnt
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;Mount Other Subvolumes&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now mount your other subvolumes as defined in your &lt;code&gt;disko.nix&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Mount Other Subvolumes
# (Ensure /mnt directories are created for each *mountpoint*)

# Home
mkdir -p /mnt/home
mount -o subvol=home,compress=zstd,noatime /dev/nvme0n1p2 /mnt/home

# IMPORTANT: No separate mount for /mnt/home/user, as it&apos;s a nested subvolume
# and handled by the /home mount.

# Nix store
mkdir -p /mnt/nix
mount -o subvol=nix,compress=zstd,noatime /dev/nvme0n1p2 /mnt/nix

# Nix persist
mkdir -p /mnt/nix/persist
# CRITICAL: Based our disko.nix, the subvolume name is &apos;persist&apos;, not &apos;nix/persist&apos;
mount -o subvol=persist,compress=zstd,noatime /dev/nvme0n1p2 /mnt/nix/persist

# /var/log
mkdir -p /mnt/var/log
mount -o subvol=log,compress=zstd,noatime /dev/nvme0n1p2 /mnt/var/log

# /var/lib
mkdir -p /mnt/var/lib
# Confirmed: The subvolume named &apos;lib&apos; is mounted to /var/lib
mount -o subvol=lib,compress=zstd,noatime /dev/nvme0n1p2 /mnt/var/lib
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note: If you get “subvolume not found,” check the subvolume names with
&lt;code&gt;btrfs subvol list /mnt&lt;/code&gt;.&lt;/p&gt;
&lt;ol start=&quot;5&quot;&gt;
&lt;li&gt;Mount the EFI Partition&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mkdir -p /mnt/boot mount /dev/nvme0n1p1 /mnt/boot
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;6&quot;&gt;
&lt;li&gt;(Optional) Mount Virtual Filesystems&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mount --bind /dev /mnt/dev mount --bind /proc /mnt/proc mount --bind /sys
/mnt/sys mount --bind /run /mnt/run
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;7&quot;&gt;
&lt;li&gt;Chroot&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;chroot /mnt /run/current-system/sw/bin/bash
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or, if using a non-NixOS live system:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nixos-enter
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(You may need to install nixos-enter with nix-shell -p nixos-enter.) 8. You’re
In!&lt;/p&gt;
&lt;p&gt;You can now run nixos-rebuild, reset passwords, or fix configs as needed. 🔎&lt;/p&gt;
&lt;p&gt;📓 Notes&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Adjust &lt;code&gt;compress=zstd,noatime&lt;/code&gt; if your config uses different mount options.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;For impermanence, make sure to mount all persistent subvolumes you need.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If you use swap, you may want to enable it too (e.g., swapon /dev/zram0 if
relevant).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can now recover, repair, or maintain your NixOS system as needed!&lt;/p&gt;
&lt;h4&gt;Related Material&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://wiki.nixos.org/wiki/Change_root&quot;&gt;Change root (chroot&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.mankier.com/8/nixos-enter&quot;&gt;nixos-enter&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://grahamc.com/blog/erase-your-darlings/&quot;&gt;erase your darlings&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://haseebmajid.dev/posts/2024-07-30-how-i-setup-btrfs-and-luks-on-nixos-using-disko/&quot;&gt;Guide for Btrfs with LUKS&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://notashelf.dev/posts/impermanence&quot;&gt;notashelf impermanence&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://wiki.nixos.org/wiki/Impermanence&quot;&gt;NixOS wiki Impermanence&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/nix-community/impermanence&quot;&gt;nix-community impermanence module&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content></entry><entry><title>Nix Lang</title><id>https://saylesss88.github.io/nix/nix_language.html</id><updated>2025-11-22T00:00:00+00:00</updated><author><name>saylesss87@proton.me (saylesss88)</name></author><link href="https://saylesss88.github.io/nix/nix_language.html" rel="alternate"/><content type="html">&lt;h1&gt;Nix Language&lt;/h1&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Click to Expand Table of Contents&lt;/summary&gt;
&lt;!-- toc --&gt;
&lt;/details&gt;
&lt;!-- ![lambda1](../images/lambda1.png) --&gt;
&lt;h2&gt;Nix Expression Language Syntax Overview&lt;/h2&gt;
&lt;p&gt;The Nix language is designed for conveniently creating and composing
&lt;em&gt;derivations&lt;/em&gt; precise descriptions of how contents of files are used to derive
new files. –&lt;a href=&quot;https://nix.dev/manual/nix/2.28/language/&quot;&gt;Nix Reference Manual&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Nix is often described as “JSON with functions.” It’s a declarative language
where you define outcomes, not step-by-step instructions. Instead of writing
sequential code, you create expressions that describe data structures,
functions, and dependencies. These expressions are evaluated lazily, meaning Nix
computes values only when needed, making it efficient for managing large
systems.&lt;/p&gt;
&lt;p&gt;You can plug most of the following into the &lt;code&gt;nix repl&lt;/code&gt; I’m showing it in a
single code block here for brevity:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix,editable&quot;&gt;# Comments Look Like This!

# Strings
&quot;This is a string&quot;          # String literal

&apos;&apos;
one
two                        # multi-line String
three
&apos;&apos;

(&quot;foo&quot; + &quot;bar&quot;)           # =&amp;gt; &quot;foobar&quot;

&quot;foo&quot; != &quot;bar&quot;   # Inequality test  # =&amp;gt; true

!false      # =&amp;gt; true

(&quot;Home dir is ${builtins.getEnv &quot;HOME&quot;}&quot;)  # String Interpolation
# =&amp;gt; &quot;Home dir is /home/jr&quot;

&quot;3 6 ${builtins.toString 9}&quot;
# =&amp;gt; &quot;3 6 9&quot;

&quot;goodbye ${ { d = &quot;world&quot;;}.d}&quot;
# =&amp;gt; &quot;goodbye world&quot;

# Booleans

(false &amp;amp;&amp;amp; true)    # AND         # =&amp;gt; false

(true || false)    # OR         # =&amp;gt; true

(if 6 &amp;lt; 9 then &quot;yay&quot; else &quot;nay&quot;)  # =&amp;gt; &quot;yay&quot;

null      # Null Value

679       # Integer

(6 + 7 + 9) # =&amp;gt; 22   # Addition

(9 - 3  - 2) # =&amp;gt; 4   # Subtraction

(6 / 3)  # =&amp;gt; 2       # Division
6.79      # Floating Point

/etc/nixos      # Absolute Path

../modules/nixos/boot.nix    # relative

# Let expressions

(let a = &quot;2&quot;; in                   # Let expressions are a way to create variables
a + a + builtins.toString &quot;4&quot;)
# =&amp;gt; &quot;224&quot;

(let first = &quot;firstname&quot;; in
&quot;lastname &quot; first)
# =&amp;gt; &quot;lastname firstname&quot;

# Lists

[ 1 2 &quot;three&quot; &quot;bar&quot; &quot;baz&quot; ]   # lists are whitespace separated

builtins.elemAt [ 1 2 3 4 5 ] 3
# =&amp;gt; 4

builtins.length [ 1 2 3 4 ]
# =&amp;gt; 4

# Attrsets

{ first = &quot;Jim&quot;; last = &quot;Bo&quot;; }.last # Attribute selection
# =&amp;gt; &quot;Bo&quot;

{ a = 1; b = 3; } // { c = 4; b = 2; }   # Attribute Set merging
# =&amp;gt; { a = 1; b = 2; c = 4; }               # Right Side takes precedence

builtins.listToAttrs [ { name = &quot;Jr&quot;; value = &quot;Jr Juniorville&quot;; } {name = &quot;$&quot;; value = &quot;JR&quot;; } { name = &quot;jr&quot;; value = &quot;jr
ville&quot;; }]
# =&amp;gt; { &quot;$&quot; = &quot;JR&quot;; Jr = &quot;Jr Juniorville&quot;; jr = &quot;jrville&quot;; }

# Control Flow

if 2 * 2 == 4
then &quot;yes!&quot;
else &quot;no!&quot;
# =&amp;gt; &quot;yes!&quot;

assert 2 * 2
== 4; &quot;yes!&quot;
# =&amp;gt; &quot;yes!&quot;

with builtins;
head [ 5 6 7 ]
# =&amp;gt; 5

# or

builtins.head[ 5 6 7 ]

inherit pkgs     # pkgs = pkgs;
src;             # src = src;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Understanding Laziness&lt;/h3&gt;
&lt;p&gt;Nix expressions are evaluated lazily, meaning Nix computes values only when
needed. This is a powerful feature that makes Nix efficient for managing large
systems, as it avoids unnecessary computations.&lt;/p&gt;
&lt;p&gt;For example, observe how &lt;code&gt;a&lt;/code&gt; is never evaluated in the following &lt;code&gt;nix-repl&lt;/code&gt;
session:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;nix-repl&amp;gt; let a = builtins.div 4 0; b = 6; in b
6
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Since &lt;code&gt;a&lt;/code&gt; isn’t used in the final result, there’s no division by zero error.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Strings and String Interpolation&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Strings&lt;/strong&gt;: Strings are enclosed in double quotes (&lt;code&gt;&quot;&lt;/code&gt;) or two single quotes
(&lt;code&gt;&apos;&apos;&lt;/code&gt;).&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;nix-repl&amp;gt; &quot;stringDaddy&quot;
&quot;stringDaddy&quot;
nix-repl&amp;gt; &apos;&apos;
  This is a
  multi-line
  string
&apos;&apos;
&quot;This is a\nmulti-line\nstring.\n&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;https://nix.dev/manual/nix/2.24/language/string-interpolation&quot;&gt;string interpolation&lt;/a&gt;.
is a language feature where a string, path, or attribute name can contain an
expressions enclosed in &lt;code&gt;${ }&lt;/code&gt;. This construct is called an &lt;em&gt;interpolated
string&lt;/em&gt;, and the expression inside is an &lt;em&gt;interpolated expression&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Rather than writing:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;let path = &quot;/usr/local&quot;; in &quot;--prefix=${path}&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This evaluates to &lt;code&gt;&quot;--prefix=/usr/local&quot;&lt;/code&gt;. Interpolated expressions must
evaluate to a string, path, or an attribute set with an &lt;code&gt;outPath&lt;/code&gt; or
&lt;code&gt;__toString&lt;/code&gt; attribute.&lt;/p&gt;
&lt;h3&gt;Attribute Sets&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Attribute sets&lt;/strong&gt; are all over Nix code and deserve their own section, they are
name-value pairs wrapped in curly braces, where the names must be unique:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{
  string = &quot;hello&quot;;
  int = 8;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Attribute names usually don’t need quotes.&lt;/p&gt;
&lt;p&gt;You can access attributes using &lt;em&gt;dot notation&lt;/em&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;let person = { name = &quot;Alice&quot;; age = 30; }; in person.name
&quot;Alice&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You will sometimes see attribute sets with &lt;code&gt;rec&lt;/code&gt; prepended. This allows access
to attributes within the set:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;rec {
  x = y;
  y = 123;
}.x
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Output&lt;/strong&gt;: &lt;code&gt;123&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;or&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;rec {
  one = 1;
  two = one + 1;
  three = two + 1;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Output&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt; {
  one = 1;
  three = 3;
  two = 2;
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# This would fail:
{
  one = 1;
  two = one + 1;  # Error: undefined variable &apos;one&apos;
  three = two + 1;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Recursive sets introduce the danger of &lt;em&gt;infinite recursion&lt;/em&gt; For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;rec {
  x = y;
  y = x;
}.x
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Will crash with an &lt;code&gt;infinite recursion encountered&lt;/code&gt; error message.&lt;/p&gt;
&lt;p&gt;The
&lt;a href=&quot;https://nix.dev/manual/nix/2.24/language/operators.html#update&quot;&gt;attribute set update operator&lt;/a&gt;
merges two attribute sets.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{ a = 1; b = 2; } // { b = 3; c = 4; }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Output&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{ a = 1; b = 3; c = 4; }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However, names on the right take precedence, and updates are shallow.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{ a = { b = 1; }; } // { a = { c = 3; }; }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Output&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{ a = { c = 3; }; }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Above, key &lt;code&gt;b&lt;/code&gt; was completely removed, because the whole &lt;code&gt;a&lt;/code&gt; value was replaced.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Inheriting Attributes&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Click to see Output:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;let x = 123; in
{
  inherit x;
  y = 456;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;is equivalent to&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;let x = 123; in
{
  x = x;
  y = 456;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;which are both equivalent to&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{
  x = 123;
  y = 456;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;❗: This works because &lt;code&gt;x&lt;/code&gt; is added to the lexical scope by the &lt;code&gt;let&lt;/code&gt;
construct.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Now that we understand attribute sets lets move on to functions, a powerful
feature of the Nix language that gives you the ability to reuse and share
logical pieces of code.&lt;/p&gt;
&lt;h3&gt;Functions(lambdas):&lt;/h3&gt;
&lt;p&gt;Functions in Nix help you build reusable components and are the the building
blocks of Nix. In the next chapter we’ll go even further into Nix functions and
how to use them but I will touch on them here.&lt;/p&gt;
&lt;p&gt;Nix functions have this form:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;pattern: body
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The following is a function that expects an integer and returns it increased by
1:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;x: x + 1   # lambda function, not bound to a variable
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The pattern tells us what the argument of the function has to look like, and
binds variables in the body to (parts of) the argument.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;(x: x + 5) 200
205
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;They are all lambdas (i.e. anonymous functions without names) until we assign
them to a variable like the following example.&lt;/p&gt;
&lt;p&gt;Functions are defined using this syntax, where &lt;code&gt;x&lt;/code&gt; and &lt;code&gt;y&lt;/code&gt; are attributes passed
into the function:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{
  my_function = x: y: x + y;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The code below calls a function called &lt;code&gt;my_function&lt;/code&gt; with the parameters &lt;code&gt;2&lt;/code&gt; and
&lt;code&gt;3&lt;/code&gt;, and assigns its output to the &lt;code&gt;my_value&lt;/code&gt; field:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{
  my_value = my_function 2 3;
}
my_value
5
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The body of the function automatically returns the result of the function.
Functions are called by spaces between it and its parameters. No commas are
needed to separate parameters.&lt;/p&gt;
&lt;p&gt;The following is a function that expects an attribute set with required
attributes &lt;code&gt;a&lt;/code&gt; and &lt;code&gt;b&lt;/code&gt; and concatenates them:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{ a, b }: a + b
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Default Values in Functions&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;Functions in Nix can define &lt;strong&gt;default values&lt;/strong&gt; for their arguments. This allows
for more flexible function calls where some arguments are optional.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{ x, y ? &quot;foo&quot;, z ? &quot;bar&quot; }: z + y + x
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Specifies a function that only requires an attribute named &lt;code&gt;x&lt;/code&gt;, but optionally
accepts &lt;code&gt;y&lt;/code&gt; and &lt;code&gt;z&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;@-patterns in functions&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;An &lt;code&gt;@-pattern&lt;/code&gt; provides a means of referring to the whole value being matched by
the function’s argument pattern, in addition to destructuring it. This is
especially useful when you want to access attributes that are not explicitly
destructured in the pattern:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;args@{ x, y, z, ... }: z + y + x + args.a
# or
{ x, y, z, ... } @ args: z + y + x + args.a
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Here, &lt;code&gt;args&lt;/code&gt; is bound to the argument as &lt;em&gt;passed&lt;/em&gt;, which is further matched
against the pattern &lt;code&gt;{ x, y, z, ... }&lt;/code&gt;. The &lt;code&gt;@-pattern&lt;/code&gt; makes mainly sense
with an ellipsis(&lt;code&gt;...&lt;/code&gt;) as you can access attribute names as &lt;code&gt;a&lt;/code&gt;, using
&lt;code&gt;args.a&lt;/code&gt;, which was given as an additional attribute to the function.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;We will expand on Functions in
&lt;a href=&quot;https://saylesss88.github.io/Understanding_Nix_Functions_2.html&quot;&gt;This Chapter&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;If, Let, and With Expressions&lt;/h3&gt;
&lt;p&gt;Nix is a pure expression language, meaning every construct evaluates to a value
— there are no statements. Because of this, &lt;strong&gt;if expressions&lt;/strong&gt; in Nix work
differently than in imperative languages, where conditional logic often relies
on statements (&lt;code&gt;if&lt;/code&gt;, &lt;code&gt;elsif&lt;/code&gt;, etc.).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If expressions in Nix&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;Since everything in Nix is an expression, an &lt;code&gt;if&lt;/code&gt; expression must always produce
a value:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;nix-repl&amp;gt; a = 6
nix-repl&amp;gt; b = 10
nix-repl&amp;gt; if a &amp;gt; b then &quot;yes&quot; else &quot;no&quot;
&quot;no&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here, &lt;code&gt;&quot;no&quot;&lt;/code&gt; is the result because &lt;code&gt;a&lt;/code&gt;(6) is not greater than &lt;code&gt;b&lt;/code&gt;(10). Notice
that there’s no separate conditional statement – the entire construct evaluates
to a value.&lt;/p&gt;
&lt;p&gt;Another example, integrating built-in functions:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{
  key = if builtins.pathExists ./path then &quot;YES&quot; else &quot;NO!&quot;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If &lt;code&gt;./path&lt;/code&gt; exists it will evaluate to the value &lt;code&gt;&quot;YES&quot;&lt;/code&gt; or else it will
evaluate to &lt;code&gt;&quot;NO!&quot;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Thus, the final result of the expression would be:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{ key = &quot;YES&quot;; }
# or
{ key = &quot;NO!&quot;; }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since Nix does not have statements, Nix’s &lt;code&gt;if&lt;/code&gt; statements behave more like
&lt;a href=&quot;https://en.wikipedia.org/wiki/Ternary_conditional_operator&quot;&gt;ternary operators&lt;/a&gt;
(&lt;code&gt;condition ? value_if_true : value_if_false&lt;/code&gt;) in other languages.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Let expressions&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;Let expressions in Nix is primarily a mechanism for local variable binding and
scoping. It allows you to define named values that are only accessible within
the &lt;code&gt;in&lt;/code&gt; block of the &lt;code&gt;let&lt;/code&gt; expression. This is useful for keeping code clean
and avoiding repitition.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;let
  a = &quot;foo&quot;;
  b = &quot;fighter&quot;;
in a + b
&quot;foofighter&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here, &lt;code&gt;a&lt;/code&gt; and &lt;code&gt;b&lt;/code&gt; are defined inside the &lt;code&gt;let&lt;/code&gt; block, and their values are used
in the &lt;code&gt;in&lt;/code&gt; expression. Since everything in Nix is an expression, &lt;code&gt;a + b&lt;/code&gt;
evaluates to &lt;code&gt;&quot;foofighter&quot;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Using Let Expressions Inside Attribute Sets&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Let expressions are commonly used when defining attribute sets (Click for
output):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;let
  appName = &quot;nix-app&quot;;
  version = &quot;1.0&quot;;
in {
  name = appName;
  fullName = appName + &quot;-&quot; + version;
}
~{
~  name = &quot;nix-app&quot;;
~  fullName = &quot;nix-app-1.0&quot;;
~}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This allows you to reuse values within an attribute set, making the code more
modular and preventing duplication.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Let Expressions in Function Arguments&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You can also use let expressions within function arguments to define
intermediate values before returning an output:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{ pkgs, lib }:
let
  someVar = &quot;hello&quot;;
  otherVar = &quot;world&quot;;
in
{ inherit pkgs lib someVar otherVar; }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Result:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{
  pkgs = &amp;lt;value&amp;gt;;
  lib = &amp;lt;value&amp;gt;;
  someVar = &quot;hello&quot;;
  otherVar = &quot;world&quot;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here, &lt;code&gt;inherit&lt;/code&gt; brings &lt;code&gt;pkgs&lt;/code&gt; and &lt;code&gt;lib&lt;/code&gt; into the resulting attribute set,
alongside the locally defined variables &lt;code&gt;someVar&lt;/code&gt; and &lt;code&gt;otherVar&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Key Takeaways&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Let expressions allow local variable bindings that are only visible inside the
in block. They also help avoid repitition and improve readability.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Commonly used inside attribute sets or function arguments.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Their scope is limited to the expression in which they are declared.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;With expressions&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;A &lt;code&gt;with&lt;/code&gt; expression in Nix is primarily used to simplify access to attributes
within an attribute set. Instead of repeatedly referring to a long attribute
path, with temporarily brings the attributes into scope, allowing direct access
without prefixing them.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Basic Example: Reducing Attribute Path Usage&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Consider the following expressions:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;nix-repl&amp;gt; longName = { a = 3; b = 4; }
nix-repl&amp;gt; longName.a + longName.b
7
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here, we must explicitly reference &lt;code&gt;longName.a&lt;/code&gt; and &lt;code&gt;longName.b&lt;/code&gt;. Using a &lt;code&gt;with&lt;/code&gt;
expression simplifies this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;nix-repl&amp;gt; with longName; a + b
7
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, within the scope of the with expression, &lt;code&gt;a&lt;/code&gt; and &lt;code&gt;b&lt;/code&gt; are accessible without
prefixing them with &lt;code&gt;longName&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Practical Use Case: Working with &lt;code&gt;pkgs&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;One of the most common uses of &lt;code&gt;with&lt;/code&gt; that you’ll see is when dealing with
packages from &lt;code&gt;nixpkgs&lt;/code&gt; is writing the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{ pkgs }:
with pkgs; {
  myPackages = [ vim git neofetch ];
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Instead of writing this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{ pkgs }:
{
  myPackages = [ pkgs.vim pkgs.git pkgs.neofetch ];
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;Tip: Overusing &lt;code&gt;with lib;&lt;/code&gt; or &lt;code&gt;with pkgs;&lt;/code&gt; can reduce clarity, it may be fine
for smaller modules where the scope is limited. For larger configurations,
explicit references (&lt;code&gt;pkgs.something&lt;/code&gt;) often make dependencies clearer and
prevent ambiguity.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Nix Language Quirks&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;with&lt;/code&gt; gets less priority than &lt;code&gt;let&lt;/code&gt;. This can be confusing, especially if
you like to write &lt;code&gt;with pkgs;&lt;/code&gt;:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;nix-repl&amp;gt; pkgs = { x = 2; }

nix-repl&amp;gt; with pkgs; x
2

nix-repl&amp;gt; with pkgs; let x = 4; in x
4
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This shows us that the &lt;code&gt;let&lt;/code&gt; binding overrides the &lt;code&gt;with&lt;/code&gt; binding.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;let x = 4; in with pkgs; x
4
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Still returns &lt;code&gt;4&lt;/code&gt;, but the reasoning is different. The &lt;code&gt;with&lt;/code&gt; expression doesn’t
define new bindings; it simply makes attributes from &lt;code&gt;pkgs&lt;/code&gt; available as
unqualified names. However, because &lt;code&gt;let x = 4&lt;/code&gt; is &lt;strong&gt;outside&lt;/strong&gt; the &lt;code&gt;with&lt;/code&gt;, it
already extablished &lt;code&gt;x = 4&lt;/code&gt;, so when &lt;code&gt;with pkgs; x&lt;/code&gt; is evaluated inside, &lt;code&gt;x&lt;/code&gt;
still refers to the &lt;strong&gt;outer&lt;/strong&gt; &lt;code&gt;let&lt;/code&gt; binding, not the one from &lt;code&gt;pkgs&lt;/code&gt;.&lt;/p&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;Default values aren’t bound in &lt;code&gt;@-patterns&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In the following example, calling a function that binds a default value &lt;code&gt;&quot;baz&quot;&lt;/code&gt;
to the attribute &lt;code&gt;b&lt;/code&gt; of an argument using an alias (&lt;code&gt;@&lt;/code&gt;) pattern, with an empty
attribute set as argument, results in the alias variable inputs being bound to
the original empty attribute set instead of including the default value:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;(inputs@(b ? &quot;baz&quot;): inputs) {}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Output:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This happens because the alias &lt;code&gt;inputs@&lt;/code&gt; binds to the argument as passed, before
the default value for &lt;code&gt;b&lt;/code&gt; is applied.&lt;/p&gt;
&lt;p&gt;The syntax requires curly brackets around the attribute set pattern for
correctness, so the fixed syntax would be:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;(inputs@{b ? &quot;baz&quot;}: inputs) {}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However, even with this fix, the inputs alias still refers to the original
argument without defaults applied. So the quirk persists, showing how default
values in &lt;code&gt;@-patterns&lt;/code&gt; do not propagate into the aliased variable.&lt;/p&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;Destructuring function arguments:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;nix-repl&amp;gt; f = { x ? 2, y ? 4 }: x + y

nix-repl&amp;gt; f { }
6
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The function &lt;code&gt;f&lt;/code&gt; takes an attribute set with default values (&lt;code&gt;x = 2&lt;/code&gt;, &lt;code&gt;y = 4&lt;/code&gt;)&lt;/p&gt;
&lt;p&gt;When called with &lt;code&gt;{}&lt;/code&gt; (an empty set), it falls back to the default values
(&lt;code&gt;2 + 4&lt;/code&gt; -&amp;gt; &lt;code&gt;6&lt;/code&gt;)&lt;/p&gt;
&lt;p&gt;Using &lt;code&gt;@args&lt;/code&gt; to capture the entire input set:&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;@args&lt;/code&gt; syntax allows us to retain access to the full attribute set, even
after destructuring:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;nix-repl&amp;gt; f = { x ? 1, y ? 2, ... }@args: with args; x + y + z

nix-repl&amp;gt; f { z = 3; }
6
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;{ x ? 1, y ? 2, ... }&lt;/code&gt; syntax means &lt;code&gt;x&lt;/code&gt; and &lt;code&gt;y&lt;/code&gt; have defaults, while &lt;code&gt;...&lt;/code&gt;
allows additional attributes.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;@args&lt;/code&gt; binds the entire attribute set (&lt;code&gt;args&lt;/code&gt;) so that we can access &lt;code&gt;z&lt;/code&gt;, which
wouldn’t be destructured by default.&lt;/p&gt;
&lt;p&gt;When calling &lt;code&gt;f { z = 3; }&lt;/code&gt;, we pass an extra attribute (&lt;code&gt;z = 3&lt;/code&gt;), making
&lt;code&gt;x + y + z&lt;/code&gt; → &lt;code&gt;1 + 2 + 3 = 6&lt;/code&gt;.&lt;/p&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;Imports and namespaces&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;There is a keyword import, but it’s equivalent in other languages is eval. It
can be used for namespacing too:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;let
  pkgs = import &amp;lt;nixpkgs&amp;gt; {};
  lib = import &amp;lt;nixpkgs/lib&amp;gt;;
in
  pkgs.runCommand (lib.strings.removePrefix &quot;....
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;consider using &lt;code&gt;import&lt;/code&gt; here as using &lt;code&gt;qualified import ...&lt;/code&gt; in Haskell or
&lt;code&gt;import ...&lt;/code&gt; in Python.&lt;/p&gt;
&lt;p&gt;Another way of importing is with &lt;code&gt;import ...;&lt;/code&gt;, which corresponds to Python
&lt;code&gt;from ... import *&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;But because of not very great IDE support in Nix, &lt;code&gt;with import ...;&lt;/code&gt; is
discouraged. Rather use inherit, especially if you are targeting source code for
Nix newcomers:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;let
  lib = import &amp;lt;nixpkgs/lib&amp;gt;;
  inherit (lib.strings)
    removePrefix removeSuffix
  ;
  inherit (lib.lists)
    isList init drop
  ;
in
  removePrefix ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;inherit&lt;/code&gt; has higher priority than &lt;code&gt;with&lt;/code&gt;, and conflicts with &lt;code&gt;let&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;nix-repl&amp;gt; let pkgs = { x = 1; }; x = 2; x = 3; inherit (pkgs) x; in x
error: attribute ‘x’ at (string):1:31 already defined at (string):1:24
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This makes it a sane citizen of Nix lanugage… except it has a twin, called
&lt;code&gt;{ inherit ...; }&lt;/code&gt;. They DON’T do the same - &lt;code&gt;let inherit ...&lt;/code&gt; adds
let-bindings, and &lt;code&gt;{ inherit ...; }&lt;/code&gt; adds attributes to a record.
–&lt;a href=&quot;https://nixos.wiki/wiki/Nix_Language_Quirks&quot;&gt;https://nixos.wiki/wiki/Nix_Language_Quirks&lt;/a&gt;&lt;/p&gt;
&lt;ol start=&quot;5&quot;&gt;
&lt;li&gt;Only attribute names can be interpolated, not Nix code:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;nix-repl&amp;gt; let ${&quot;y&quot;} = 4; in y
4

nix-repl&amp;gt; with { ${&quot;y&quot;} = 4; }; y
4

let y = 1; x = ${y}; in x
error: syntax error, unexpected DOLLAR_CURLY
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;let&lt;/code&gt; bindings introduce new local values and override anything from &lt;code&gt;with&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;with&lt;/code&gt; doesn’t create bindings - it only makes attributes available within its
scope.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The order matters: If &lt;code&gt;let x = 4&lt;/code&gt; is outside &lt;code&gt;with&lt;/code&gt;, then &lt;code&gt;x = 4&lt;/code&gt; already
exists before &lt;code&gt;with&lt;/code&gt; runs, so &lt;code&gt;with pkgs; x&lt;/code&gt; resolves to &lt;code&gt;4&lt;/code&gt;, not the value
from &lt;code&gt;pkgs&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Resources&lt;/h4&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Resources (Click to Expand) &lt;/summary&gt;
&lt;p&gt;A few resources to help get you started with the Nix Language, I have actually
grown to love the language. I find it fairly simple but powerful!&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://nix.dev/tutorials/nix-language.html&quot;&gt;nix.dev nixlang-basics&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://nix.dev/manual/nix/2.24/language/&quot;&gt;Nix Language Overview&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://learnxinyminutes.com/nix/&quot;&gt;learn nix in y minutes&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/tazjin/nix-1p&quot;&gt;nix onepager&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://zero-to-nix.com/concepts/nix-language/&quot;&gt;zero-to-nix nix lang&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://nixos.org/guides/nix-pills/04-basics-of-language.html&quot;&gt;nix-pills basics of nixlang&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://nixos.org/guides/nix-pills/04-basics-of-language&quot;&gt;Basics of the Language Pill&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/details&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;builtins.head[ 5 6 7 ]
&lt;/code&gt;&lt;/pre&gt;
</content></entry><entry><title>Nix Package Manager</title><id>https://saylesss88.github.io/nix/nix_package_manager.html</id><updated>2025-11-22T00:00:00+00:00</updated><author><name>saylesss87@proton.me (saylesss88)</name></author><link href="https://saylesss88.github.io/nix/nix_package_manager.html" rel="alternate"/><content type="html">&lt;h1&gt;Nix Package Manager&lt;/h1&gt;
&lt;details&gt;
&lt;summary&gt; Click to Expand Table of Contents&lt;/summary&gt;
&lt;!-- toc --&gt;
&lt;/details&gt;
&lt;!-- ![nix99](../images/nix99.png) --&gt;
&lt;h2&gt;Nix Package Manager&lt;/h2&gt;
&lt;p&gt;Nix is a &lt;em&gt;purely functional package manager&lt;/em&gt;. This means that it treats packages
like values in purely functional programming languages – they are built by
functions that don’t have side-effects, and they never change after they have
been built.&lt;/p&gt;
&lt;p&gt;Nix stores packages in the &lt;em&gt;Nix store&lt;/em&gt;, usually the directory &lt;code&gt;/nix/store&lt;/code&gt;,
where each package has its own unique subdirectory such as:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;/nix/store/y53c0lamag5wpx7vsiv7wmnjdgq97yd6-yazi-25.5.14pre20250526_74a8ea9
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can use the Nix on most Linux distributions and Mac OS also has good support
for Nix. It should work on most platforms that support POSIX threads and have a
C++11 compiler.&lt;/p&gt;
&lt;p&gt;When I install Nix on a distro like Arch Linux I usually use the Zero to Nix
installer as it automates several steps, such as enabling flakes by default:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;curl --proto &apos;=https&apos; --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you have concerns about the “curl to Bash” approach you could examine the
installation script
&lt;a href=&quot;https://raw.githubusercontent.com/DeterminateSystems/nix-installer/main/nix-installer.sh&quot;&gt;here&lt;/a&gt;
then download and run it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;curl --proto &apos;=https&apos; --tlsv1.2 -sSf -L https://install.determinate.systems/nix &amp;gt; nix-installer.sh
chmod +x nix-installer.sh
./nix-installer.sh install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I got the above commands from
&lt;a href=&quot;https://zero-to-nix.com/start/install/&quot;&gt;zero-to-nix&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The main difference between using the nix package manager on another
distribution and NixOS is that NixOS uses Nix not just for package management
but also to manage the system configuration (e.g., to build config files in
&lt;code&gt;/etc&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://nix-community.github.io/home-manager/&quot;&gt;Home Manager&lt;/a&gt; is a Nix-powered
tool for reproducible management of the contents of the users’ home directories.
This includes programs, configuration files, environment variables, and
arbitrary files. Home manager uses the same module system as NixOS.&lt;/p&gt;
&lt;p&gt;Now that we’ve discussed some of the basics of the Nix package manager, lets see
how it is used to build and manage software in NixOS.&lt;/p&gt;
&lt;h2&gt;Channels&lt;/h2&gt;
&lt;p&gt;Nix packages are distributed through Nix channels; mechanisms for distributing
Nix expressions and the associated binary caches for them. Channels are what
determine which versions your packages have. (i.e. &lt;em&gt;stable&lt;/em&gt; or &lt;em&gt;unstable&lt;/em&gt;). A
channel is a name for the latest “verified” git commits in Nixpkgs. Each channel
represents a different policy for what “verified” means. Whenever a new commit
in &lt;code&gt;Nixpkgs&lt;/code&gt; passes the verification process, the respective channel is updated
to point to that new commit.&lt;/p&gt;
&lt;p&gt;While channels provide a convenient way to get the latest stable or unstable
packages, they introduce a challenge for strict reproducibility. Because a
channel like &lt;code&gt;nixos-unstable&lt;/code&gt; is constantly updated, fetching packages from it
today might give you a different set of package versions than fetching from it
tomorrow, even if your configuration remains unchanged. This “rolling release”
nature at a global level can make it harder to share and reproduce exact
development environments or system configurations across different machines or
at different points in time.&lt;/p&gt;
&lt;h2&gt;Channels vs. Flakes Enhancing Reproducibility&lt;/h2&gt;
&lt;p&gt;Before the introduction of &lt;strong&gt;Nix Flakes&lt;/strong&gt;, channels were the primary mechanism
for sourcing &lt;code&gt;Nixpkgs&lt;/code&gt;. While functional, they posed a challenge for exact
reproducibility because they point to a moving target (the latest commit on a
branch). This meant that a &lt;code&gt;nix-build&lt;/code&gt; command run yesterday might produce a
different result than one run today, simply because the channel updated.&lt;/p&gt;
&lt;p&gt;Nix Flakes were introduced to address this. Flakes bring a built-in,
standardized way to define the exact inputs to a Nix build, including the
precise Git revision of &lt;code&gt;Nixpkgs&lt;/code&gt; or any other dependency.&lt;/p&gt;
&lt;p&gt;Here’s a quick comparison:&lt;/p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th style=&quot;text-align: left&quot;&gt;Feature&lt;/th&gt;&lt;th style=&quot;text-align: left&quot;&gt;Nix Channels (traditional)&lt;/th&gt;&lt;th style=&quot;text-align: left&quot;&gt;Nix Flakes (modern approach)&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: left&quot;&gt;&lt;strong&gt;Input Source&lt;/strong&gt;&lt;/td&gt;&lt;td style=&quot;text-align: left&quot;&gt;Global system configuration (&lt;code&gt;nix-channel --update&lt;/code&gt;)&lt;/td&gt;&lt;td style=&quot;text-align: left&quot;&gt;Explicitly defined in &lt;code&gt;flake.nix&lt;/code&gt; (e.g., &lt;code&gt;github:NixOS/nixpkgs/nixos-23.11&lt;/code&gt;)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: left&quot;&gt;&lt;strong&gt;Reproducibility&lt;/strong&gt;&lt;/td&gt;&lt;td style=&quot;text-align: left&quot;&gt;“Rolling release”; less reproducible across time/machines&lt;/td&gt;&lt;td style=&quot;text-align: left&quot;&gt;Highly reproducible due to locked inputs (&lt;code&gt;flake.lock&lt;/code&gt;)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: left&quot;&gt;&lt;strong&gt;Dependency Mgmt.&lt;/strong&gt;&lt;/td&gt;&lt;td style=&quot;text-align: left&quot;&gt;Implicitly managed by global channel&lt;/td&gt;&lt;td style=&quot;text-align: left&quot;&gt;Explicitly declared and version-locked within &lt;code&gt;flake.nix&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: left&quot;&gt;&lt;strong&gt;Sharing&lt;/strong&gt;&lt;/td&gt;&lt;td style=&quot;text-align: left&quot;&gt;Relies on users having same channel version&lt;/td&gt;&lt;td style=&quot;text-align: left&quot;&gt;Self-contained; &lt;code&gt;flake.lock&lt;/code&gt; ensures everyone gets same inputs&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: left&quot;&gt;&lt;strong&gt;Learning Curve&lt;/strong&gt;&lt;/td&gt;&lt;td style=&quot;text-align: left&quot;&gt;Simpler initial setup, but tricky reproducibility debugging&lt;/td&gt;&lt;td style=&quot;text-align: left&quot;&gt;Higher initial learning curve, but simplifies reproducibility&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The ability of Flakes to “lock” the exact version of all dependencies in a
&lt;code&gt;flake.lock&lt;/code&gt; file is a game-changer for collaboration and long-term
reproducibility, ensuring that your Nix configuration builds the same way, every
time, everywhere.&lt;/p&gt;
&lt;h2&gt;Nixpkgs&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Nixpkgs&lt;/strong&gt; is the largest repository of Nix packages and NixOS modules.&lt;/p&gt;
&lt;p&gt;For &lt;strong&gt;NixOS&lt;/strong&gt; users, &lt;code&gt;nixos-unstable&lt;/code&gt; channel branch is the rolling release,
where the packages are tested and must pass integration tests.&lt;/p&gt;
&lt;p&gt;For &lt;strong&gt;standalone Nix&lt;/strong&gt; users, &lt;code&gt;nixpkgs-unstable&lt;/code&gt; channel branch is the rolling
release, where packages pass only basic build tests and are upgraded often.&lt;/p&gt;
&lt;p&gt;For Flakes, as mentioned above they don’t use channels so &lt;code&gt;nixpkgs&lt;/code&gt; will be
listed as an &lt;code&gt;input&lt;/code&gt; to your flake. (e.g.,
&lt;code&gt;inputs.nixpkgs.url = &quot;github:nixos/nixpkgs/nixos-unstable&quot;;&lt;/code&gt;) When using flakes
you can actually disable channels and actually recommended to avoid conflicts
between traditional channel-based workflows and the flake system.&lt;/p&gt;
&lt;h3&gt;Updates&lt;/h3&gt;
&lt;p&gt;The mechanism for updating your Nix environment differs fundamentally between
channels and flakes, directly impacting reproducibility and control.&lt;/p&gt;
&lt;h4&gt;Updating with Channels (Traditional Approach)&lt;/h4&gt;
&lt;p&gt;With channels, updates are a global operation that pulls the latest state of a
specific branch.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How it works&lt;/strong&gt;: You typically use &lt;code&gt;nix-channel --update&lt;/code&gt; to fetch the latest
commit from the channels you’ve subscribed to. For instance,
&lt;code&gt;sudo nix-channel --update nixos&lt;/code&gt; (for NixOS) or &lt;code&gt;nix-channel --update nixpkgs&lt;/code&gt;
(for &lt;code&gt;nix-env&lt;/code&gt; on other Linux distributions).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Implication&lt;/strong&gt;: This command updates your local system’s understanding of what
“nixos” or “nixpkgs-unstable” means. From that point on, any
&lt;code&gt;nixos-rebuild switch&lt;/code&gt;, &lt;code&gt;nix-env -iA&lt;/code&gt;, or &lt;code&gt;nix-build&lt;/code&gt; commands that implicitly
or explicitly refer to &lt;code&gt;nixpkgs&lt;/code&gt; will use this newly updated version.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Reproducibility Challenge&lt;/strong&gt;: The update itself is not recorded in your
configuration files. If you share your &lt;code&gt;configuration.nix&lt;/code&gt; with someone, they
might run &lt;code&gt;nix-channel --update&lt;/code&gt; on a different day and get a different set of
package versions because the channel has moved. This makes it challenging to
guarantee that two users building the “same” configuration will get identical
results. You’re effectively relying on the implicit, globally managed state of
your channels.&lt;/p&gt;
&lt;h4&gt;Updating with Flakes (Modern Approach)&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;Flakes&lt;/strong&gt;, by contrast, use a more explicit and localized update mechanism tied
to your &lt;code&gt;flake.lock&lt;/code&gt; file.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How it works&lt;/strong&gt;: When you define a &lt;code&gt;flake.nix&lt;/code&gt;, you specify the exact URL
(e.g., a Git repository with a specific branch or tag) for each input. When you
first use a flake, Nix resolves these URLs to a precise Git commit hash and
records this hash, along with a content hash, in a &lt;code&gt;flake.lock&lt;/code&gt; file.&lt;/p&gt;
&lt;p&gt;To update your flake inputs, you run &lt;code&gt;nix flake update&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Implication&lt;/strong&gt;: This command goes to each input’s specified URL (e.g.,
&lt;code&gt;github:NixOS/nixpkgs/nixos-unstable&lt;/code&gt;) and fetches the latest commit for that
input. It then updates your &lt;code&gt;flake.lock&lt;/code&gt; file with the new, precise Git commit
hash and content hash for that input. Your &lt;code&gt;flake.nix&lt;/code&gt; itself doesn’t change,
but the &lt;code&gt;flake.lock&lt;/code&gt; file now points to newer versions of your dependencies.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Reproducibility Advantage&lt;/strong&gt;: The &lt;code&gt;flake.lock&lt;/code&gt; file acts as a manifest of your
exact dependency versions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sharing&lt;/strong&gt;: When you share your flake (the &lt;code&gt;flake.nix&lt;/code&gt; and &lt;code&gt;flake.lock&lt;/code&gt; files),
anyone using it will fetch precisely the same Git commit hashes recorded in the
&lt;code&gt;flake.lock&lt;/code&gt;, guaranteeing identical inputs and thus, identical builds (assuming
the same system architecture).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Updating Selectively&lt;/strong&gt;: You can update individual inputs within your flake by
specifying them: &lt;code&gt;nix flake update nixpkgs&lt;/code&gt;. This provides fine-grained control
over which parts of your dependency graph you want to advance.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Rolling Back&lt;/strong&gt;: Because the &lt;code&gt;flake.lock&lt;/code&gt; explicitly records the versions, you
can easily revert to a previous state by checking out an older &lt;code&gt;flake.lock&lt;/code&gt; from
your version control system.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;In essence&lt;/strong&gt;: Channels involve a global “pull” of the latest branch state,
making reproducibility harder to guarantee across time and machines. Flakes,
however, explicitly pin all inputs in &lt;code&gt;flake.lock&lt;/code&gt;, and updates involve
explicitly refreshing these pins, providing strong reproducibility and version
control out of the box.&lt;/p&gt;
&lt;h3&gt;Managing software with Nix&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Derivation Overview&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In Nix, the process of managing software starts with &lt;strong&gt;package definitions&lt;/strong&gt;.
These are files written in the Nix language that describe how a particular piece
of software should be built. These package definitions, when processed by Nix,
are translated into derivations.&lt;/p&gt;
&lt;p&gt;At its core, a derivation in Nix is a blueprint or a recipe that describes how
to build a specific software package or any other kind of file or directory.
It’s a declarative specification of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Inputs&lt;/strong&gt;: What existing files or other derivations are needed as
dependencies.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Build Steps&lt;/strong&gt;: The commands that need to be executed to produce the desired
output.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Environment&lt;/strong&gt;: The specific environment (e.g., build tools, environment
variables) required for the build process.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Outputs&lt;/strong&gt;: The resulting files or directories that the derivation produces.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Think of a package definition as the initial instructions, and the derivation as
the detailed, low-level plan that Nix uses to actually perform the build.&lt;/p&gt;
&lt;p&gt;Again, a derivation is like a blueprint that describes how to build a specific
software package or any other kind of file or directory.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Key Characteristics of Derivations:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Declarative&lt;/strong&gt;: You describe the desired outcome and the inputs, not the
exact sequence of imperative steps. Nix figures out the necessary steps based
on the builder and args.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Reproducible&lt;/strong&gt;: Given the same inputs and build instructions, a derivation
will always produce the same output. This is a cornerstone of Nix’s
reproducibility.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Tracked by Nix&lt;/strong&gt;: Nix keeps track of all derivations and their outputs in
the Nix store. This allows for efficient management of dependencies and
ensures that different packages don’t interfere with each other.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Content-Addressed&lt;/strong&gt;: The output of a derivation is stored in the Nix store
under a unique path that is derived from the hash of all its inputs and build
instructions. This means that if anything changes in the derivation, the
output will have a different path.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here’s a simple Nix derivation that creates a file named hello in the Nix store
containing the text “Hello, World!”:&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Hello World Derivation Example (Click to expand):&lt;/summary&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{pkgs ? import &amp;lt;nixpkgs&amp;gt; {}}:
pkgs.stdenv.mkDerivation {
  name = &quot;hello-world&quot;;

  dontUnpack = true;

  # No need for src = null; when dontUnpack = true;
  # src = null;

  buildPhase = &apos;&apos;
     # Create a shell script that prints &quot;Hello, World!&quot;
    echo &apos;#!${pkgs.bash}/bin/bash&apos; &amp;gt; hello-output-file # Shebang line
    echo &apos;echo &quot;Hello, World!&quot;&apos; &amp;gt;&amp;gt; hello-output-file # The command to execute
    chmod +x hello-output-file # Make it executable
  &apos;&apos;;

  installPhase = &apos;&apos;
    mkdir -p $out/bin
    cp hello-output-file $out/bin/hello # Copy the file from build directory to $out/bin
  &apos;&apos;;

  meta = {
    description = &quot;A simple Hello World program built with Nix&quot;;
    homepage = null;
    license = pkgs.lib.licenses.unfree; # licenses.mit is often used as well
    maintainers = [];
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And a &lt;code&gt;default.nix&lt;/code&gt; with the following contents:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{ pkgs ? import &amp;lt;nixpkgs&amp;gt; {} }:

import ./hello.nix { pkgs = pkgs; }
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;{ pkgs ? import &amp;lt;nixpkgs&amp;gt; {} }&lt;/code&gt;: This is a function that takes an optional
argument &lt;code&gt;pkgs&lt;/code&gt;. We need Nixpkgs to access standard build environments like
&lt;code&gt;stdenv&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;pkgs.stdenv.mkDerivation { ... }:&lt;/code&gt; This calls the mkDerivation function from
the standard environment (stdenv). mkDerivation is the most common way to
define software packages in Nix.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;name = &quot;hello-world&quot;;&lt;/code&gt;: Human-readable name of the derivation&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The rest are the build phases and package metadata.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To use the above derivation, save it as a &lt;code&gt;.nix&lt;/code&gt; file (e.g. &lt;code&gt;hello.nix&lt;/code&gt;). Then
build the derivation using,:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix-build
this derivation will be built:
  /nix/store/9mc855ijjdy3r6rdvrbs90cg2gf2q160-hello-world.drv
building &apos;/nix/store/9mc855ijjdy3r6rdvrbs90cg2gf2q160-hello-world.drv&apos;...
Running phase: patchPhase
Running phase: updateAutotoolsGnuConfigScriptsPhase
Running phase: configurePhase
no configure script, doing nothing
Running phase: buildPhase
Running phase: installPhase
Running phase: fixupPhase
shrinking RPATHs of ELF executables and libraries in /nix/store/2ydxh5pd9a6djv7npaqi9rm6gmz2f73b-hello-world
checking for references to /build/ in /nix/store/2ydxh5pd9a6djv7npaqi9rm6gmz2f73b-hello-world...
patching script interpreter paths in /nix/store/2ydxh5pd9a6djv7npaqi9rm6gmz2f73b-hello-world
stripping (with command strip and flags -S -p) in  /nix/store/2ydxh5pd9a6djv7npaqi9rm6gmz2f73b-hello-world/bin
/nix/store/2ydxh5pd9a6djv7npaqi9rm6gmz2f73b-hello-world
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Nix will execute the &lt;code&gt;buildPhase&lt;/code&gt; and &lt;code&gt;installPhase&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;After a successful build, the output will be in the Nix store. You can find
the exact path by looking at the output of the nix build command (it will be
something like &lt;code&gt;/nix/store/your-hash-hello-world&lt;/code&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Run the “installed” program:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;./result/bin/hello
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;This will execute the &lt;code&gt;hello&lt;/code&gt; file from the Nix store and print
&lt;code&gt;&quot;Hello, World!&quot;&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/details&gt;
</content></entry><entry><title>Nix Paths</title><id>https://saylesss88.github.io/nix/nixLang/nix_paths.html</id><updated>2025-11-22T00:00:00+00:00</updated><author><name>saylesss87@proton.me (saylesss88)</name></author><link href="https://saylesss88.github.io/nix/nixLang/nix_paths.html" rel="alternate"/><content type="html">&lt;h1&gt;Nix Paths&lt;/h1&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Click to Expand Table of Contents&lt;/summary&gt;
&lt;!-- toc --&gt;
&lt;/details&gt;
&lt;p&gt;The following examples are done with a local &lt;code&gt;nixpkgs&lt;/code&gt; clone located at
&lt;code&gt;~/src/nixpkgs&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Paths in Nix always need a &lt;code&gt;/&lt;/code&gt; in them and always expand to absolute paths
relative to your current directory.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix repl
nix-repl&amp;gt; ./.
/home/jr/src/nixpkgs
nix-repl&amp;gt; ./. + &quot;/lib&quot;
/home/jr/src/nixpkgs/lib
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Nix does &lt;em&gt;path normalization&lt;/em&gt; every time you append strings, so if you just add
a slash &lt;code&gt;/&lt;/code&gt; its not actually there:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix-repl&amp;gt; ./.
/home/jr/src/nixpkgs
nix-repl&amp;gt; ./. + &quot;/&quot;
/home/jr/src/nixpkgs
nix-repl&amp;gt; ./. + &quot;/&quot; + &quot;lib&quot;
/home/jr/src/nixpkgslib
nix-repl&amp;gt; &quot;${./.}/lib&quot;
# using ${./.} causes a store copy
copying &apos;/homr/jr/src/nixpkgs&apos; to the store
&quot;/nix/store/3z9fzx8z03wslxvri5syv3jnnhn5fkbd-nixpkgs/lib&quot;
nix-repl&amp;gt; &quot;${toString ./.}/lib&quot;
# using toString avoids making a store copy
&quot;/home/jr/src/nixpkgs/lib&quot;
nix-repl&amp;gt; ./lib/..             # nix removes all `..` to avoid redundant path resolutions
/home/jr/src/nixpkgs
nix-repl&amp;gt; :q
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;realpath ./lib/..
/home/jr/src/nixpkgs
ln -s pkgs/applications lib-symlink
realpath ./lib-symlink/..
/home/jr/src/nixpkgs/pkgs
nix repl
nix-repl&amp;gt; ./lib-symlink/..   # Nix doesn&apos;t read this file at all like realpath did
/home/jr/src/nixpkgs
nix-repl&amp;gt; builtins.readDir ./. # listing of all entries in current dir and their types
{
  &quot;.devcontainer&quot; = &quot;directory&quot;;
  &quot;.editorconfig&quot; = &quot;regular&quot;;
  &quot;.git&quot; = &quot;directory&quot;;
  &quot;.git-blame-ignore-revs&quot; = &quot;regular&quot;;
  &quot;.gitattributes&quot; = &quot;regular&quot;;
  &quot;.github&quot; = &quot;directory&quot;;
  &quot;.gitignore&quot; = &quot;regular&quot;;
  &quot;.mailmap&quot; = &quot;regular&quot;;
  &quot;.mergify.yml&quot; = &quot;regular&quot;;
  &quot;.version&quot; = &quot;symlink&quot;;
  &quot;CONTRIBUTING.md&quot; = &quot;regular&quot;;
  COPYING = &quot;regular&quot;;
  &quot;README.md&quot; = &quot;regular&quot;;
  ci = &quot;directory&quot;;
  &quot;default.nix&quot; = &quot;regular&quot;;
  doc = &quot;directory&quot;;
  &quot;flake.nix&quot; = &quot;regular&quot;;
  lib = &quot;directory&quot;;
  maintainers = &quot;directory&quot;;
  nixos = &quot;directory&quot;;
  pkgs = &quot;directory&quot;;
  &quot;shell.nix&quot; = &quot;regular&quot;;
}
nix-repl&amp;gt; builtins.readFile ./default.nix
&quot;let\n  requiredVersion = import ./lib/minver.nix;\nin\n\nif !builtins ? nixVersion
 || builtins.compareVersions requiredVersion builtins.nixVersion == 1 then\n\n  abort
 &apos;&apos;\n\n    This version of Nixpkgs requires Nix &amp;gt;= \${requiredVersion}, please
 upgrade:\n\n    - If you are running NixOS, `nixos-rebuild&apos; can be used to upgrade
 your system.\n\n    - Alternatively, with Nix &amp;gt; 2.0 `nix upgrade-nix&apos; can be used
 to imperatively\n      upgrade Nix. You may use `nix-env --version&apos; to check which
 version you have.\n\n    - If you installed Nix using the install script (https://nixos.org/nix/install),\n
  it is safe to upgrade by running it again:\n\n          curl -L https://nixos.org/nix/install | sh\n\n
For more information, please see the NixOS release notes at\n    https://nixos.org/nixos/manual
 or locally at\n    \${toString ./nixos/doc/manual/release-notes}.\n\n    If you need further help,
 see https://nixos.org/nixos/support.html\n  &apos;&apos;\n\nelse\n\n  import ./pkgs/top-level/impure.nix\n&quot;
nix-repl&amp;gt; :l &amp;lt;nixpkgs/lib&amp;gt;
nix-repl&amp;gt; importJSON ./pkgs/development/python-modules/notebook/missing-hashes.json # Return the nix value for JSON
{
  &quot;@nx/nx-darwin-arm64@npm:16.10.0&quot; = &quot;aabcc8499602b98c9fc3b768fe46dfd4e1b818caa84b740bd4f73a2e4528c719b979ecb1c10a0d793a1fead83073a08bc86417588046aa3e587e80af880bffd3&quot;;
  &quot;@nx/nx-darwin-x64@npm:16.10.0&quot; = &quot;9dd20f45f646d05306f23f5abb7ade69dcb962e23a013101e93365847722079656d30a19c735fdcfa5c4e0fdf08691f9d621073c58aef2861c26741ff4638375&quot;;
  &quot;@nx/nx-freebsd-x64@npm:16.10.0&quot; = &quot;35b93aabe3b3274d53157a6fc10fec7e341e75e6818e96cfbc89c3d5b955d225ca80a173630b6aa43c448c6b53b23f06a2699a25c0c8bc71396ee20a023d035f&quot;;
  &quot;@nx/nx-linux-arm-gnueabihf@npm:16.10.0&quot; = &quot;697b9fa4c70f84d3ea8fe32d47635864f2e40b0ceeb1484126598c61851a2ec34b56bb3eeb9654c37d9b14e81ce85a36ac38946b4b90ca403c57fe448be51ccb&quot;;
  &quot;@nx/nx-linux-arm64-gnu@npm:16.10.0&quot; = &quot;001e71fedfc763a4dedd6c5901b66a4a790d388673fb74675235e19bb8fe031ff3755568ed867513dd003f873901fabda31a7d5628b39095535cb9f6d1dc7191&quot;;
  &quot;@nx/nx-linux-arm64-musl@npm:16.10.0&quot; = &quot;58e3b71571bdadd2b0ddd24ea6e30cd795e706ada69f685403412c518fba1a2011ac8c2ac46145eab14649aa5a78e0cedcdb4d327ccb3b6ec12e055171f3840b&quot;;
  &quot;@nx/nx-linux-x64-gnu@npm:16.10.0&quot; = &quot;97729a7efb27301a67ebf34739784114528ddb54047e63ca110a985eaa0763c5b1ea7c623ead1a2266d07107951be81e82ffa0a30e6e4d97506659303f2c8c78&quot;;
  &quot;@nx/nx-linux-x64-musl@npm:16.10.0&quot; = &quot;442bdbd5e61324a850e4e7bd6f54204108580299d3c7c4ebcec324da9a63e23f48d797a87593400fc32af78a3a03a3c104bfb360f107fe732e6a6c289863853a&quot;;
  &quot;@nx/nx-win32-arm64-msvc@npm:16.10.0&quot; = &quot;b5c74184ebfc70294e85f8e309f81c3d40b5cf99068891e613f3bef5ddb946bef7c9942d9e6c7688e22006d45d786342359af3b4fc87aadf369afcda55c73187&quot;;
  &quot;@nx/nx-win32-x64-msvc@npm:16.10.0&quot; = &quot;c5b174ebd7a5916246088e17d3761804b88f010b6b3f930034fa49af00da33b6d1352728c733024f736e4c2287def75bafdc3d60d8738bd24b67e9a4f11763f8&quot;;
}
nix-repl&amp;gt; builtins.toJSON  # serialize
«primop toJSON»
nix-repl&amp;gt; builtins.fromTOML
«primop fromTOML»
nix-repl&amp;gt; builtins.toXML
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For more serialization formats see &lt;code&gt;nixpkgs/lib/generators.nix&lt;/code&gt; as well as in
&lt;code&gt;nixpkgs/pkgs/pkgs-lib/formats/&lt;/code&gt; we can see them with the &lt;code&gt;nix repl&lt;/code&gt; as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd ~/src/nixpkgs
nix repl
nix-repl&amp;gt; :l .
nix-repl&amp;gt; lib.generators.toYAML {} { a = 10; }
&quot;{\&quot;a\&quot;:10}&quot;
nix-repl&amp;gt; lib.generators.toYAML {} { a.b.c = 10; }
&quot;{\&quot;a\&quot;:{\&quot;b\&quot;:{\&quot;c\&quot;:10}}}&quot;
nix-repl&amp;gt; builtins.trace (lib.generators.toYAML {} { a.b.c = 10; }) null
trace: {&quot;a&quot;:{&quot;b&quot;:{&quot;c&quot;:10}}}
null
nix-repl&amp;gt; yamlFormat = pkgs.formats.yaml {}

nix-repl&amp;gt; yamlFormat
{
  generate = «lambda generate @ /home/jr/src/nixpkgs/pkgs/pkgs-lib/formats.nix:111:9»;
  type = { ... };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;We can see that it provides a &lt;code&gt;generate&lt;/code&gt; function that we can use. &lt;code&gt;generate&lt;/code&gt;
doesn’t just generate a string anymore because if we want to lift the
restriction at evaluation time we can’t return the formatted form at
evaluation time anymore. We need a name to return a derivation continued
below:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;yamlFormat.generate &quot;name&quot; { a.b.c = 10; }
«derivation /nix/store/xakajb2rzbmqqkjbh08bxwqdf0xqvjly-name.drv»
nix-repl&amp;gt; :b yamlFormat.generate &quot;name&quot; { a.b.c = 10; }
This derivation produced the following outputs:
out -&amp;gt; /nix/store/y4c5029k6w3l0qmdw7cq396zrdy5x8yj-name
nix-repl&amp;gt; :q
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let’s cat the result to see if it’s formatted correctly as YAML:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cat /nix/store/y4c5029k6w3l0qmdw7cq396zrdy5x8yj-name
───────┬───────────────────────────────────────────────────────────────
       │ File: /nix/store/y4c5029k6w3l0qmdw7cq396zrdy5x8yj-name
───────┼──────────────────────────────────────────────────────────────
   1   │ a:
   2   │   b:
   3   │     c: 10
───────┴───────────────────────────────────────────────────────────
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Looks good. There is also a &lt;code&gt;type&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix repl
nix-repl&amp;gt; :l .
nix-repl&amp;gt; yamlFormat = pkgs.format.yaml {}
nix-repl&amp;gt; yamlFormat.type
{
  _type = &quot;option-type&quot;;
  check = «lambda check @ /home/jr/src/nixpkgs/lib/types.nix:1029:19»;
  deprecationMessage = null;
  description = &quot;YAML value&quot;;
  descriptionClass = &quot;conjunction&quot;;
  emptyValue = { ... };
  functor = { ... };
  getSubModules = null;
  getSubOptions = «lambda @ /home/jr/src/nixpkgs/lib/types.nix:214:25»;
  merge = «lambda merge @ /home/jr/src/nixpkgs/lib/types.nix:1031:13»;
  name = &quot;nullOr&quot;;
  nestedTypes = { ... };
  substSubModules = «lambda substSubModules @ /home/jr/src/nixpkgs/lib/types.nix:1046:29»;
  typeMerge = «lambda defaultTypeMerge @ /home/jr/src/nixpkgs/lib/types.nix:115:10»;
}
nix-repl&amp;gt; lib.modules.mergeDefinitions [] yamlFormat.type [ { value = null; } ]
{
  defsFinal = [ ... ];
  defsFinal&apos; = { ... };
  isDefined = true;
  mergedValue = null;
  optionalValue = { ... };
}
nix-repl&amp;gt; (lib.modules.mergeDefinitions [] yamlFormat.type [ { value = null; } ]).mergedValue
null
nix-repl&amp;gt; :p (lib.modules.mergeDefinitions [] yamlFormat.type [ { value = { a.b.c = 10; }; } ]).mergedValue
{
  a = {
    b = { c = 10; };
  };
}
nix-repl&amp;gt; :p (lib.modules.mergeDefinitions [] yamlFormat.type [ { value = { a.b.c = 10; }; } { value = { a.b.d = 20; }; } ]).mergedValue
{
  a = {
    b = {
      c = 10;
      d = 20;
    };
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;lib&lt;/code&gt; can’t access any packages, it is entirely at evaluation time. It can’t
access any formatters or things like that. If we lift that restriction as is
done in &lt;code&gt;pkgs.formats&lt;/code&gt; we can make it look much nicer.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd ~/src/nixpkgs
nix-build -A hello
warning: Nix search path entry &apos;/nix/var/nix/profiles/per-user/root/channels&apos; does not exist, ignoring
this path will be fetched (0.06 MiB download, 0.26 MiB unpacked):
  /nix/store/29mhfr5g4dsv07d80b7n4bgs45syk3wl-hello-2.12.2
copying path &apos;/nix/store/29mhfr5g4dsv07d80b7n4bgs45syk3wl-hello-2.12.2&apos; from &apos;https://cache.nixos.org&apos;...
/nix/store/29mhfr5g4dsv07d80b7n4bgs45syk3wl-hello-2.12.2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Say we rely on this store path in a derivation:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix-repl&amp;gt; thePath = &quot;/nix/store/29mhfr5g4dsv07d80b7n4bgs45syk3wl-hello-2.12.2&quot;
nix-repl&amp;gt; thePath + &quot;/bin/hello&quot;
&quot;/nix/store/29mhfr5g4dsv07d80b7n4bgs45syk3wl-hello-2.12.2/bin/hello&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;hx ~/src/nixpkgs/test2.nix
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# test2.nix
with import ./. {};

runCommand &quot;test&quot; {
    nativeBuildInputs = [
        hello
    ];
}&apos;&apos;
  hello &amp;gt; $out
&apos;&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Try building it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix-build test2.nix &amp;amp;&amp;amp; cat result
warning: Nix search path entry &apos;/nix/var/nix/profiles/per-user/root/channels&apos; does not exist, ignoring
/nix/store/m55p4vpb8s7s28s20vs89i467kxbrdac-test
Hello, world!
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now if we try it with the store path:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# test2.nix
with import ./. {};

runCommand &quot;test&quot; {
}&apos;&apos;
  /nix/store/29mhfr5g4dsv07d80b7n4bgs45syk3wl-hello-2.12.2/bin/hello &amp;gt; $out
&apos;&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This doesn’t work&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix-build test2.nix
last 1 log lines:
&amp;gt; /build/.attr-0l2nkwhif96f51f4amnlf414lhl4rv9vh8iffyp431v6s28gsr90: line 1: /nix/store/29mhfr5g4dsv07d80b7n4bgs45syk3wl-hello-2.12.2/bin/hello: No such file or directory
For full logs, run:
nix log /nix/store/58zcp9xwgf1sirmzf9sh61j8gz9lkw34-test.drv
nix-instantiate test2.nix
/nix/store/58zcp9xwgf1sirmzf9sh61j8gz9lkw34-test.drv
nix derivation show /nix/store/58zcp9xwgf1sirmzf9sh61j8gz9lkw34-test.drv | jq
{
  &quot;/nix/store/58zcp9xwgf1sirmzf9sh61j8gz9lkw34-test.drv&quot;: {
    &quot;args&quot;: [
      &quot;-e&quot;,
      &quot;/nix/store/vj1c3wf9c11a0qs6p3ymfvrnsdgsdcbq-source-stdenv.sh&quot;,
      &quot;/nix/store/shkw4qm9qcw5sc5n1k5jznc83ny02r39-default-builder.sh&quot;
    ],
    &quot;builder&quot;: &quot;/nix/store/xy4jjgw87sbgwylm5kn047d9gkbhsr9x-bash-5.2p37/bin/bash&quot;,
    &quot;env&quot;: {
      &quot;__structuredAttrs&quot;: &quot;&quot;,
      &quot;buildCommand&quot;: &quot;/nix/store/29mhfr5g4dsv07d80b7n4bgs45syk3wl-hello-2.12.2/bin/hello &amp;gt; $out\n&quot;,
      &quot;buildInputs&quot;: &quot;&quot;,
      &quot;builder&quot;: &quot;/nix/store/xy4jjgw87sbgwylm5kn047d9gkbhsr9x-bash-5.2p37/bin/bash&quot;,
      &quot;cmakeFlags&quot;: &quot;&quot;,
      &quot;configureFlags&quot;: &quot;&quot;,
      &quot;depsBuildBuild&quot;: &quot;&quot;,
      &quot;depsBuildBuildPropagated&quot;: &quot;&quot;,
      &quot;depsBuildTarget&quot;: &quot;&quot;,
      &quot;depsBuildTargetPropagated&quot;: &quot;&quot;,
      &quot;depsHostHost&quot;: &quot;&quot;,
      &quot;depsHostHostPropagated&quot;: &quot;&quot;,
      &quot;depsTargetTarget&quot;: &quot;&quot;,
      &quot;depsTargetTargetPropagated&quot;: &quot;&quot;,
      &quot;doCheck&quot;: &quot;&quot;,
      &quot;doInstallCheck&quot;: &quot;&quot;,
      &quot;enableParallelBuilding&quot;: &quot;1&quot;,
      &quot;enableParallelChecking&quot;: &quot;1&quot;,
      &quot;enableParallelInstalling&quot;: &quot;1&quot;,
      &quot;mesonFlags&quot;: &quot;&quot;,
      &quot;name&quot;: &quot;test&quot;,
      &quot;nativeBuildInputs&quot;: &quot;&quot;,
      &quot;out&quot;: &quot;/nix/store/ljrkx5midby3j7p4g96d74jrq8f9rpya-test&quot;,
      &quot;outputs&quot;: &quot;out&quot;,
      &quot;passAsFile&quot;: &quot;buildCommand&quot;,
      &quot;patches&quot;: &quot;&quot;,
      &quot;propagatedBuildInputs&quot;: &quot;&quot;,
      &quot;propagatedNativeBuildInputs&quot;: &quot;&quot;,
      &quot;stdenv&quot;: &quot;/nix/store/aq801xbgs98nxx3lckrym06qfvl8mfsf-stdenv-linux&quot;,
      &quot;strictDeps&quot;: &quot;&quot;,
      &quot;system&quot;: &quot;x86_64-linux&quot;
    },
    &quot;inputDrvs&quot;: {
      &quot;/nix/store/bmncp7arkdhrl6nkyg0g420935x792gl-stdenv-linux.drv&quot;: {
        &quot;dynamicOutputs&quot;: {},
        &quot;outputs&quot;: [
          &quot;out&quot;
        ]
      },
      &quot;/nix/store/rfkzz952hz2d58d90mscxvk87v5wa5bz-bash-5.2p37.drv&quot;: {
        &quot;dynamicOutputs&quot;: {},
        &quot;outputs&quot;: [
          &quot;out&quot;
        ]
      }
    },
    &quot;inputSrcs&quot;: [
      &quot;/nix/store/shkw4qm9qcw5sc5n1k5jznc83ny02r39-default-builder.sh&quot;,
      &quot;/nix/store/vj1c3wf9c11a0qs6p3ymfvrnsdgsdcbq-source-stdenv.sh&quot;
    ],
    &quot;name&quot;: &quot;test&quot;,
    &quot;outputs&quot;: {
      &quot;out&quot;: {
        &quot;path&quot;: &quot;/nix/store/ljrkx5midby3j7p4g96d74jrq8f9rpya-test&quot;
      }
    },
    &quot;system&quot;: &quot;x86_64-linux&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You see the &lt;code&gt;&quot;inputDrvs&quot;&lt;/code&gt;, they are the derivations that we depend on and it
doesn’t know about the &lt;code&gt;hello.drv&lt;/code&gt;. In Nix for the builder sandbox it creates a
sandbox that only contains the derivations that you depend on which ensures that
you can’t depend on any derivation that you haven’t explicitly decalred.&lt;/p&gt;
&lt;p&gt;Nix does have &lt;code&gt;builtins.storePath&lt;/code&gt; that allows you to do this, otherwise it’s
kind of an anti pattern.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# test2.nix
# test2.nix
# test2.nix
with import ./. {};
  runCommand &quot;test&quot; {
  } &apos;&apos;
    ${builtins.storePath &quot;/nix/store/29mhfr5g4dsv07d80b7n4bgs45syk3wl-hello-2.12.2&quot;}/bin/hello &amp;gt; $out
  &apos;&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;builtins.storePath&lt;/code&gt;: Turns a store path into the thing that it represents in
the store.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix-build test2.nix &amp;amp;&amp;amp; cat result
/nix/store/x48741w0k9hgqywzv6wc7rk90r1y75js-test
Hello, world!
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To demonstrate what &lt;code&gt;builtins.storePath&lt;/code&gt; does:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix-repl&amp;gt; builtins.storePath &quot;/nix/store/29mhfr5g4dsv07d80b7n4bgs45syk3wl-hello-2.12.2/bin/hello&quot;
&quot;/nix/store/29mhfr5g4dsv07d80b7n4bgs45syk3wl-hello-2.12.2/bin/hello&quot;
nix-repl&amp;gt; builtins.getContext &quot;/nix/store/29mhfr5g4dsv07d80b7n4bgs45syk3wl-hello-2.12.2&quot;
{ }
nix-repl&amp;gt; builtins.getContext (builtins.storePath &quot;/nix/store/29mhfr5g4dsv07d80b7n4bgs45syk3wl-hello-2.12.2&quot;)
{
  &quot;/nix/store/29mhfr5g4dsv07d80b7n4bgs45syk3wl-hello-2.12.2&quot; = { ... };
}
nix-repl&amp;gt; :p builtins.getContext (builtins.storePath &quot;/nix/store/29mhfr5g4dsv07d80b7n4bgs45syk3wl-hello-2.12.2&quot;)
{
  &quot;/nix/store/29mhfr5g4dsv07d80b7n4bgs45syk3wl-hello-2.12.2&quot; = { path = true; };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix-repl&amp;gt; :l .
warning: Nix search path entry &apos;/nix/var/nix/profiles/per-user/root/channels&apos; does not exist, ignoring
Added 24878 variables.

nix-repl&amp;gt; hello.outPath
# this is the output path of the hello derivation
&quot;/nix/store/29mhfr5g4dsv07d80b7n4bgs45syk3wl-hello-2.12.2&quot;
nix-repl&amp;gt; :p builtins.getContext hello.outPath
# we see that this is a `.drv`, this is because derivations can have multiple outputs
{
  &quot;/nix/store/ljxsxdy1syy03b9kfnnh8x7zsk21fdcq-hello-2.12.2.drv&quot; = {
    outputs = [ &quot;out&quot; ];
  };
}
# for example
nix-repl&amp;gt; openssl.outputs
[
  &quot;bin&quot;
  &quot;dev&quot;
  &quot;out&quot;
  &quot;man&quot;
  &quot;doc&quot;
  &quot;debug&quot;
]
nix-repl&amp;gt; openssl.all
# a list of all the derivations
[
  «derivation /nix/store/rw3y8k94ib37dc86n0wivr551wyzxgsk-openssl-3.4.1.drv»
  «derivation /nix/store/rw3y8k94ib37dc86n0wivr551wyzxgsk-openssl-3.4.1.drv»
  «derivation /nix/store/rw3y8k94ib37dc86n0wivr551wyzxgsk-openssl-3.4.1.drv»
  «derivation /nix/store/rw3y8k94ib37dc86n0wivr551wyzxgsk-openssl-3.4.1.drv»
  «derivation /nix/store/rw3y8k94ib37dc86n0wivr551wyzxgsk-openssl-3.4.1.drv»
  «derivation /nix/store/rw3y8k94ib37dc86n0wivr551wyzxgsk-openssl-3.4.1.drv»
]
nix-repl&amp;gt; lib.concatStringsSep &quot; &quot; openssl.all
&quot;/nix/store/rjzx8v679rwd6dsb6s08iy3j2rrax72s-openssl-3.4.1-bin /nix/store/kcgqglb4iax0zh5jlrxmjdik93wlgsrq-openssl-3.4.1-dev /nix/store/8pviily4fgsl02ijm65binz236717wfs-openssl-3.4.1 /nix/store/1l5b31cnswnbcdcac9rzs9xixnc2n9r5-openssl-3.4.1-man /nix/store/9fz5qmj0z70cbzy7mapml0sbi8z6ap0a-openssl-3.4.1-doc /nix/store/yk2g2gfcj2fy1ffyi1g91q7jmp4h8pxa-openssl-3.4.1-debug&quot;
nix-repl&amp;gt; :p builtins.getContext (builtins.unsafeDiscardOutputDependency (lib.concatStringsSep &quot; &quot; openssl.all))
{
  &quot;/nix/store/rw3y8k94ib37dc86n0wivr551wyzxgsk-openssl-3.4.1.drv&quot; = {
    outputs = [
      &quot;bin&quot;
      &quot;debug&quot;
      &quot;dev&quot;
      &quot;doc&quot;
      &quot;man&quot;
      &quot;out&quot;
    ];
  };
}
nix-repl&amp;gt; :p builtins.getContext openssl.drvPath
{
  &quot;/nix/store/rw3y8k94ib37dc86n0wivr551wyzxgsk-openssl-3.4.1.drv&quot; = { allOutputs = true; };
}
# useful if you need to create a derivation that copies this derivation to another machine
# remote builders usually take care of this but you may need it occasionally
nix-repl&amp;gt; :p builtins.getContext (builtins.unsafeDiscardOutputDependency openssl.drvPath)
{
  &quot;/nix/store/rw3y8k94ib37dc86n0wivr551wyzxgsk-openssl-3.4.1.drv&quot; = { path = true; };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Relying on paths outside of the nix store is generally not recommended because
of garbage collection and it’s considered unsafe.&lt;/p&gt;
</content></entry><entry><title>Practical Nix Functions</title><id>https://saylesss88.github.io/functions/practical_functions_2.1.html</id><updated>2025-11-22T00:00:00+00:00</updated><author><name>saylesss87@proton.me (saylesss88)</name></author><link href="https://saylesss88.github.io/functions/practical_functions_2.1.html" rel="alternate"/><content type="html">&lt;h1&gt;Practical Nix Functions&lt;/h1&gt;
&lt;details&gt;
&lt;summary&gt;
✔️
If you want to follow along with this example you&apos;ll have to place the following
in your project directory. Section is collapsed to focus on functions:
&lt;/summary&gt;
&lt;p&gt;&lt;img src=&quot;https://saylesss88.github.io/images/coding6.png&quot; alt=&quot;coding6&quot; /&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://gitlab.com/api/v4/projects/4207231/packages/generic/graphviz-releases/2.49.3/graphviz-2.49.3.tar.gz&quot;&gt;graphviz&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://ftp.gnu.org/gnu/hello/hello-2.12.1.tar.gz&quot;&gt;hello&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;autotools.nix&lt;/code&gt;:&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# autotools.nix
pkgs: attrs:
with pkgs; let
  defaultAttrs = {
    builder = &quot;${bash}/bin/bash&quot;;
    args = [./builder.sh];
    setup = ./setup.sh;
    baseInputs = [gnutar gzip gnumake gcc binutils-unwrapped coreutils gawk gnused gnugrep patchelf findutils];
    buildInputs = [];
    system = builtins.currentSystem;
  };
in
  derivation (defaultAttrs // attrs)
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;&lt;code&gt;setup.sh&lt;/code&gt;:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# setup.sh (This is a library of functions setting up the environment, not directly executable)
unset PATH
for p in $baseInputs $buildInputs; do
  if [ -d $p/bin ]; then
    export PATH=&quot;$p/bin${PATH:+:}$PATH&quot;
  fi
  if [ -d $p/lib/pkgconfig ]; then
    export PKG_CONFIG_PATH=&quot;$p/lib/pkgconfig${PKG_CONFIG_PATH:+:}$PKG_CONFIG_PATH&quot;
  fi
done

function unpackPhase() {
  tar -xzf $src

  for d in *; do
    if [ -d &quot;$d&quot; ]; then
      cd &quot;$d&quot;
      break
    fi
  done
}

function configurePhase() {
  ./configure --prefix=$out
}

function buildPhase() {
  make
}

function installPhase() {
  make install
}

function fixupPhase() {
  find $out -type f -exec patchelf --shrink-rpath &apos;{}&apos; \; -exec strip &apos;{}&apos; \; 2&amp;gt;/dev/null
}

function genericBuild() {
  unpackPhase
  configurePhase
  buildPhase
  installPhase
  fixupPhase
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;5&quot;&gt;
&lt;li&gt;And finally &lt;code&gt;builder.sh&lt;/code&gt;:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# builder.sh (This is the actual builder script specified in the derivation and
# what `nix-build` expects)
set -e
source $setup
genericBuild
&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;
&lt;p&gt;This is another example from the Nix-Pill series shown in another way to show
some powerful aspects of functions.&lt;/p&gt;
&lt;p&gt;If you have a &lt;code&gt;default.nix&lt;/code&gt; like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# default.nix
{
  hello = import ./hello.nix;
  graphviz = import ./graphviz.nix;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It expects the files that it imports to look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# graphviz.nix
let
  pkgs = import &amp;lt;nixpkgs&amp;gt; { };
  mkDerivation = import ./autotools.nix pkgs;
in
mkDerivation {
  name = &quot;graphviz&quot;;
  src = ./graphviz-2.49.3.tar.gz;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And &lt;code&gt;hello.nix&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# hello.nix
let
  pkgs = import &amp;lt;nixpkgs&amp;gt; { };
  mkDerivation = import ./autotools.nix pkgs;
in
mkDerivation {
  name = &quot;hello&quot;;
  src = ./hello-2.12.1.tar.gz;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You would build these with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix-build -A hello
nix-build -A graphviz
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see both derivations are dependendent on &lt;code&gt;nixpkgs&lt;/code&gt; which they
&lt;strong&gt;both&lt;/strong&gt; import directly. To centralize our dependencies and avoid redundant
imports, we’ll refactor our individual package definitions (&lt;code&gt;hello.nix&lt;/code&gt;,
&lt;code&gt;graphviz.nix&lt;/code&gt;) into functions. Our &lt;code&gt;default.nix&lt;/code&gt; will then be responsible for
setting up the common inputs (like &lt;code&gt;pkgs&lt;/code&gt; and &lt;code&gt;mkDerivation&lt;/code&gt;) and passing them
as arguments when it imports and calls these package functions.&lt;/p&gt;
&lt;p&gt;Here is what our &lt;code&gt;default.nix&lt;/code&gt; will look like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;let
  pkgs = import &amp;lt;nixpkgs&amp;gt; { };
  mkDerivation = import ./autotools.nix pkgs;
in
with pkgs;
{
  hello = import ./hello.nix { inherit mkDerivation; };
  graphviz = import ./graphviz.nix {
    inherit
      mkDerivation
      lib
      gd
      pkg-config
      ;
  };
  graphvizCore = import ./graphviz.nix {
    inherit
      mkDerivation
      lib
      gd
      pkg-config
      ;
    gdSupport = false;
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We define some local variables in the &lt;code&gt;let&lt;/code&gt; expression and pass them around.&lt;/p&gt;
&lt;p&gt;The whole expression in the above &lt;code&gt;default.nix&lt;/code&gt; returns an attribute set with
the keys &lt;code&gt;hello&lt;/code&gt;, &lt;code&gt;graphviz&lt;/code&gt;, and &lt;code&gt;graphvizCore&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;We import &lt;code&gt;hello.nix&lt;/code&gt; and &lt;code&gt;graphviz.nix&lt;/code&gt;, which both return a function. We call
the functions, passing them a set of inputs with the &lt;code&gt;inherit&lt;/code&gt; construct.&lt;/p&gt;
&lt;p&gt;Let’s change &lt;code&gt;hello.nix&lt;/code&gt; into a function to match what the &lt;code&gt;default.nix&lt;/code&gt; now
expects.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# hello.nix
{mkDerivation}:
mkDerivation {
  name = &quot;hello&quot;;
  src = ./hello-2.12.1.tar.gz;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now our &lt;code&gt;graphviz&lt;/code&gt; attribute expects &lt;code&gt;graphviz.nix&lt;/code&gt; to be a function that takes
the arguments listed in the above &lt;code&gt;default.nix&lt;/code&gt;, here’s what &lt;code&gt;graphviz.nix&lt;/code&gt; will
look like as a function:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# graphviz.nix
{
  mkDerivation,
  lib,
  gdSupport ? true,
  gd,
  pkg-config,
}:
mkDerivation {
  name = &quot;graphviz&quot;;
  src = ./graphviz-2.49.3.tar.gz;
  buildInputs =
    if gdSupport
    then [
      pkg-config
      (lib.getLib gd)
      (lib.getDev gd)
    ]
    else [];
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We factorized the import of &lt;code&gt;nixpkgs&lt;/code&gt; and &lt;code&gt;mkDerivation&lt;/code&gt;, and also added a
variant of &lt;code&gt;graphviz&lt;/code&gt; with gd support disabled. The result is that both
&lt;code&gt;hello.nix&lt;/code&gt; and &lt;code&gt;graphviz.nix&lt;/code&gt; are independent of the repository and
customizable by passing specific inputs.&lt;/p&gt;
&lt;p&gt;Now, we can build the package with &lt;code&gt;gd&lt;/code&gt; support disabled with the &lt;code&gt;graphvizCore&lt;/code&gt;
attribute:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix-build -A graphvizCore
# or we can still build the package that now defaults to gd support
nix-build -A graphviz
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This example showed us how to turn expressions into functions. We saw how
functions are passed around and shared between Nix expressions and derivations.&lt;/p&gt;
</content></entry><entry><title>Overlays</title><id>https://saylesss88.github.io/flakes/overlays_4.5.html</id><updated>2025-11-22T00:00:00+00:00</updated><author><name>saylesss87@proton.me (saylesss88)</name></author><link href="https://saylesss88.github.io/flakes/overlays_4.5.html" rel="alternate"/><content type="html">&lt;h1&gt;Extending Flakes with Custom Packages using Overlays&lt;/h1&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Click to Expand Table of Contents&lt;/summary&gt;
&lt;!-- toc --&gt;
&lt;/details&gt;
&lt;p&gt;&lt;img src=&quot;https://saylesss88.github.io/../images/pokego.png&quot; alt=&quot;Pokego Logo&quot; /&gt;–&lt;a href=&quot;https://github.com/rubiin/pokego&quot;&gt;pokego repo&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Overlays are Nix functions that accept two arguments, &lt;code&gt;final&lt;/code&gt; and &lt;code&gt;prev&lt;/code&gt; and
return a set of packages. Overlays are similar to &lt;code&gt;packageOverrides&lt;/code&gt; as a way to
customize Nixpkgs, &lt;code&gt;packageOverrides&lt;/code&gt; acts as an overlay with only the &lt;code&gt;prev&lt;/code&gt;
argument. Therefore, &lt;code&gt;packageOverrides&lt;/code&gt; is appropriate for basic use, but
overlays are more powerful and easier to distribute.&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;final: prev: {
  firefox = prev.firefox.overrideAttrs (old: {
    buildInputs = (old.buildInputs or []) ++ [ prev.vlc ];
    env.FIREFOX_DISABLE_GMP_UPDATER = &quot;1&quot;;
  });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To see the original derivation, run &lt;code&gt;nix edit -f &quot;&amp;lt;nixpkgs&amp;gt;&quot; firefox&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This modifies Firefox by:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Adding &lt;code&gt;vlc&lt;/code&gt; to &lt;code&gt;buildInputs&lt;/code&gt;, useful if a package requires additional
dependencies.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Setting an environment variable (&lt;code&gt;FIREFOX_DISABLE_GMP_UPDATER=1&lt;/code&gt;) to disable
automatic updates of the Gecko Media Plugin.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It is very common to use overlays in Nix to install packages that aren’t
available in the standard Nixpkgs repository.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Overlays&lt;/strong&gt; are one of the primary and recommended ways to extend and customize
your Nix environment. It’s important to remember that Nix overlays are made to
allow you to modify or extend the package set provided by Nixpkgs (or other Nix
sources) without directly altering the original package definitions. This is
crucial for maintaining reproducibility and avoiding conflicts. Overlays are
essentially functions that take the previous package set and allow you to add,
modify, or remove packages.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;To better understand the structure of my &lt;code&gt;flake.nix&lt;/code&gt; it may be helpful to
first read &lt;a href=&quot;https://tsawyer87.github.io/posts/nix_flakes_tips/&quot;&gt;This&lt;/a&gt; blog
post first.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Adding the overlays output to your Flake&lt;/h2&gt;
&lt;p&gt;I’ll show the process of adding the &lt;code&gt;pokego&lt;/code&gt; package that is not in Nixpkgs:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;In my &lt;code&gt;flake.nix&lt;/code&gt; I have a custom inputs variable within my let block of my
flake like so just showing the necessary parts for brevity:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# flake.nix
  outputs = my-inputs @ {
    self,
    nixpkgs,
    treefmt-nix,
    ...
  }: let
    system = &quot;x86_64-linux&quot;;
    host = &quot;magic&quot;;
    userVars = {
      username = &quot;jr&quot;;
      gitUsername = &quot;saylesss88&quot;;
      editor = &quot;hx&quot;;
      term = &quot;ghostty&quot;;
      keys = &quot;us&quot;;
      browser = &quot;firefox&quot;;
      flake = builtins.getEnv &quot;HOME&quot; + &quot;/flake&quot;;
    };

    inputs =
      my-inputs
      // {
        pkgs = import inputs.nixpkgs {
          inherit system;
        };
        lib = {
          overlays = import ./lib/overlay.nix;
          nixOsModules = import ./nixos;
          homeModules = import ./home;
          inherit system;
        };
      };
      # ... snip ...
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Why I Created &lt;code&gt;inputs.lib&lt;/code&gt; in My &lt;code&gt;flake.nix&lt;/code&gt;. In the above example, you’ll
notice a &lt;code&gt;lib&lt;/code&gt; attribute defined within the main &lt;code&gt;let&lt;/code&gt; block.
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;This might seem a bit unusual at first, as inputs are typically defined at
the top level of a flake. However, this structure provides a powerful way to
organize and reuse common Nix functions and configurations across my flake.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;By bundling my custom logic and modules into &lt;code&gt;inputs.lib&lt;/code&gt;, I can pass
&lt;code&gt;inputs&lt;/code&gt; (which now includes my custom &lt;code&gt;lib&lt;/code&gt;) as a &lt;code&gt;specialArgs&lt;/code&gt; to other
modules. This provides a clean way for all modules to access these shared
resources. For example, in &lt;code&gt;configuration.nix&lt;/code&gt;, &lt;code&gt;inputs.lib.overlays&lt;/code&gt;
directly references my custom overlay set.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;My &lt;code&gt;inputs.lib&lt;/code&gt; is my own project-specific library, designed to hold
functions and attribute sets relevant to my flake’s custom configurations.
While &lt;code&gt;nixpkgs.lib&lt;/code&gt; is globally available, my custom &lt;code&gt;lib&lt;/code&gt; contains my
unique additions.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While defining &lt;code&gt;inputs&lt;/code&gt; within the &lt;code&gt;let&lt;/code&gt; block to achieve this structure is a
personal preference and works well for my setup, the core benefit is the
creation of a dedicated, centralized &lt;code&gt;lib&lt;/code&gt; attribute that encapsulates my
flake’s reusable Nix code, leading to a more organized and maintainable
configuration.&lt;/p&gt;
&lt;h2&gt;The Actual Overlay&lt;/h2&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;In the &lt;code&gt;overlay.nix&lt;/code&gt; I have this helper function and the defined package:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# overlay.nix
_final: prev: let
  # Helper function to import a package
  callPackage = prev.lib.callPackageWith (prev // packages);

  # Define all packages
  packages = {
    # Additional packages
    pokego = callPackage ./pac_defs/pokego.nix {};
  };
in
  packages
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;_final: prev:&lt;/code&gt;: This is the function definition of the overlay.&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;_final&lt;/code&gt;: This argument represents the final, merged package set after all
overlays have been applied. It’s often unused within a single overlay, hence
the &lt;code&gt;_&lt;/code&gt; prefix (a Nix convention for unused variables).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;prev&lt;/code&gt;: This is the crucial argument. It represents the package set before
this overlay is applied. This allows you to refer to existing packages and
functions from Nixpkgs.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;let ... in packages&lt;/code&gt;: This introduces a &lt;code&gt;let&lt;/code&gt; expression, which defines
local variables within the scope of this overlay function. The &lt;code&gt;in packages&lt;/code&gt;
part means that the overlay function will ultimately return the &lt;code&gt;packages&lt;/code&gt;
attribute set defined within the &lt;code&gt;let&lt;/code&gt; block.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;callPackage = prev.lib.callPackageWith (prev // packages)&lt;/code&gt;: This line
defines a helper function called &lt;code&gt;callPackage&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;prev.lib.callPackageWith&lt;/code&gt; Is a function provided by Nixpkgs’ &lt;code&gt;lib&lt;/code&gt;.
&lt;code&gt;callPackageWith&lt;/code&gt; is like &lt;code&gt;prev.lib.callPackage&lt;/code&gt;, but allows the passing of
additional arguments that will then be passed to the package definition.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;(prev // packages)&lt;/code&gt;: This is an attribute set merge operation. It takes the
&lt;code&gt;prev&lt;/code&gt; package set (Nixpkgs before this overlay) and merges it with the
&lt;code&gt;packages&lt;/code&gt; attribute set defined later in this overlay.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;By using &lt;code&gt;callPackageWith&lt;/code&gt; with this merged attribute set, the &lt;code&gt;callPackage&lt;/code&gt;
function defined here is set up to correctly import package definitions,
ensuring they have access to both the original Nixpkgs and any other packages
defined within this overlay.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;packages = { ... };&lt;/code&gt;: This defines an attribute set named &lt;code&gt;packages&lt;/code&gt;. This
set will contain all the new or modified packages introduced by this overlay.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;pokego = callPackages ./pac_defs/pokego.nix { };&lt;/code&gt;: This is the core of how
the &lt;code&gt;pokego&lt;/code&gt; package is added.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;pokego =&lt;/code&gt;: This defines a new attribute named &lt;code&gt;pokego&lt;/code&gt; within the packages
attribute set. This name will be used to refer to the pokego package later.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;callPackage ./pac_defs/pokego.nix {}&lt;/code&gt;: This calls the callPackage helper
function defined earlier.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;./pac_defs/pokego.nix&lt;/code&gt;: This is the path to another Nix file(&lt;code&gt;pokego.nix&lt;/code&gt;)
that contains the actual package definition for pokego. This file would define
how to fetch, build, and install the pokego software&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;{}&lt;/code&gt;: This is an empty attribute set passed as additional arguments to the
&lt;code&gt;pokego.nix&lt;/code&gt; package definition. If &lt;code&gt;pokego.nix&lt;/code&gt; expected any specific
parameters (like versions or dependencies), you would provide them here. Since
it’s empty, it implies pokego.nix either has no required arguments or uses
default values.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;6&quot;&gt;
&lt;li&gt;&lt;code&gt;in packages&lt;/code&gt;: As mentioned earlier, the overlay function returns the
packages attribute set. When this overlay is applied, the packages defined
within this packages set (including pokego) will be added to the overall Nix
package set.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;The pokego Package definition&lt;/h2&gt;
&lt;p&gt;The following is the &lt;code&gt;./pac_defs/pokego.nix&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# pokego.nix
{
  lib,
  buildGoModule,
  fetchFromGitHub,
}:
buildGoModule rec {
  pname = &quot;pokego&quot;;
  version = &quot;0.3.0&quot;;

  src = fetchFromGitHub {
    owner = &quot;rubiin&quot;;
    repo = &quot;pokego&quot;;
    rev = &quot;v${version}&quot;;
    hash = &quot;sha256-cFpEi8wBdCzAl9dputoCwy8LeGyK3UF2vyylft7/1wY=&quot;;
  };

  vendorHash = &quot;sha256-7SoKHH+tDJKhUQDoVwAzVZXoPuKNJEHDEyQ77BPEDQ0=&quot;;

  # Install shell completions
  postInstall = &apos;&apos;
    install -Dm644 completions/pokego.bash &quot;$out/share/bash-completion/completions/pokego&quot;
    install -Dm644 completions/pokego.fish &quot;$out/share/fish/vendor_completions.d/pokego.fish&quot;
    install -Dm644 completions/pokego.zsh &quot;$out/share/zsh/site-functions/_pokego&quot;
  &apos;&apos;;

  meta = with lib; {
    description = &quot;Command-line tool that lets you display Pokémon sprites in color directly in your terminal&quot;;
    homepage = &quot;https://github.com/rubiin/pokego&quot;;
    license = licenses.gpl3Only;
    maintainers = with maintainers; [
      rubiin
      jameskim0987
      vinibispo
    ];
    mainProgram = &quot;pokego&quot;;
    platforms = platforms.all;
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Adding the overlay to your configuration&lt;/h2&gt;
&lt;p&gt;There are a few places you could choose to put the following, I choose to use my
&lt;code&gt;configuration.nix&lt;/code&gt; because of my setup:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# configuration.nix
nixpkgs.overlays = [inputs.lib.overlays]
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Installing Pokego&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;If you are managing your entire system configuration with NixOS, you would
typically add &lt;code&gt;pokego&lt;/code&gt; to your &lt;code&gt;environment.systemPackages&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# configuration.nix
environment.systemPackages = with pkgs; [
  pokego
]
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;If you prefer home-manager you can install &lt;code&gt;pokego&lt;/code&gt; with home-manager also:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# home.nix
home.packages = [
  pkgs.pokego
]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Another Overlay Example&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{
  inputs = {
    nixpkgs.url = &quot;https://flakehub.com/NixOS/nixpkgs/*.tar.gz&quot;;

    nix.url = &quot;https://flakehub.com/f/NixOS/nix/2.17.0.tar.gz&quot;;
  };

  outputs = { self, nixpkgs, nix }:

    let
      system = &quot;aarch64-darwin&quot;;
      pkgs = import nixpkgs {
        inherit system;
        overlays = [
          nix.overlays.default
        ];
      };
    in
    {
     # `pkgs` is nixpkgs for the system, with nix&apos;s overlay applied
    };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Normally,
&lt;code&gt;pkgs = import nixpkgs { }`` imports Nixpkgs with default settings.  However, the example above customizes this import by passing arguments:  &lt;/code&gt;pkgs
= import nixpkgs { inherit system; overlays = [
nix.overlays.default];}&lt;code&gt;.  This makes the pkgs variable represent nixpkgs specifically for the &lt;/code&gt;aarch64-darwin`
system, with the overlay from the nix flake applied.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Consequently, any packages built using this customized &lt;code&gt;pkgs&lt;/code&gt; will now depend
on or use the specific nix version (&lt;code&gt;2.17.0&lt;/code&gt;) provided by the nix flake,
instead of the version that comes with the fetched &lt;code&gt;nixpkgs&lt;/code&gt;. This technique
can be useful for ensuring a consistent environment or testing specific
package versions.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Customizing Nixpkgs Imports and Overlays&lt;/h2&gt;
&lt;p&gt;While overlays are typically used to add or modify packages within a single
&lt;code&gt;nixpkgs&lt;/code&gt; instance, Nix’s lazy evaluation and flake inputs allow for even more
powerful scenarios. You can have multiple versions of nixpkgs in a single flake,
and they will only be evaluated when a package from that specific version is
actually referenced. This complements overlays by giving you fine-grained
control over which nixpkgs instance an overlay applies to, or which &lt;code&gt;nixpkgs&lt;/code&gt;
version a specific part of your project depends on.&lt;/p&gt;
&lt;p&gt;Consider this example where we import nixpkgs with a specific overlay applied
directly at the import site:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{
  inputs = {
    nixpkgs.url = &quot;[https://flakehub.com/NixOS/nixpkgs/*.tar.gz](https://flakehub.com/NixOS/nixpkgs/*.tar.gz)&quot;; # This will be the base nixpkgs

    nix.url = &quot;[https://flakehub.com/f/NixOS/nix/2.17.0.tar.gz](https://flakehub.com/f/NixOS/nix/2.17.0.tar.gz)&quot;; # This flake provides an overlay for a specific Nix version
  };

  outputs = { self, nixpkgs, nix }:

    let
      system = &quot;aarch64-darwin&quot;;
      # Here, we import nixpkgs and apply the &apos;nix&apos; flake&apos;s overlay.
      # This &apos;pkgs&apos; variable now holds a customized Nix package set.
      # In this &apos;pkgs&apos; set, the &apos;nix&apos; package (and anything that depends on it)
      # will be version 2.17.0 as defined by the &apos;nix&apos; flake&apos;s overlay.
      pkgs_with_custom_nix = import nixpkgs {
        inherit system;
        overlays = [
          nix.overlays.default # Apply the overlay from the &apos;nix&apos; flake here
        ];
      };
    in
    {
      # We can then expose packages or devShells that use this customized `pkgs` set.
      devShells.${system}.default = pkgs_with_custom_nix.mkShell {
        packages = [
          pkgs_with_custom_nix.nix # This &apos;nix&apos; package is now version 2.17.0 due to the overlay!
        ];
        shellHook = &apos;&apos;
          echo &quot;Using Nix version: &amp;lt;span class=&quot;math-inline&quot;&amp;gt;\(nix \-\-version\)&quot;
&apos;&apos;;
};
# You can also make this customized package set available as a top-level overlay
# if other parts of your flake or configuration want to use it.
# overlays.custom-nix-version = final: prev: {
#   inherit (pkgs_with_custom_nix) nix; # Expose the specific nix package from our overlayed pkgs
# };
# You can also import multiple versions of nixpkgs and select packages from them:
# pkgs-2505 = import (inputs.nixpkgs-2505 or nixpkgs) { inherit system; }; # Example, assuming 2505 is an input
# packages.&amp;lt;/span&amp;gt;{system}.my-tool-2505 = pkgs-2505.myTool; # Using a package from a specific stable version
    };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Normally, &lt;code&gt;pkgs = import nixpkgs { }&lt;/code&gt; imports Nixpkgs with default settings.
However, the example above customizes this import by passing arguments:
&lt;code&gt;pkgs = import nixpkgs { inherit system; overlays = [ nix.overlays.default];}&lt;/code&gt;.
This makes the &lt;code&gt;pkgs_with_custom_nix&lt;/code&gt; variable represent Nixpkgs specifically
for the &lt;code&gt;aarch64-darwin&lt;/code&gt; system, with the overlay from the nix flake applied at
the time of import.&lt;/p&gt;
&lt;p&gt;Consequently, any packages built using this customized &lt;code&gt;pkgs_with_custom_nix&lt;/code&gt;
will now depend on or use the specific Nix version (&lt;code&gt;2.17.0&lt;/code&gt;) provided by the
nix flake’s overlay, instead of the version that comes with the base &lt;code&gt;nixpkgs&lt;/code&gt;
input. This technique is highly useful for ensuring a consistent environment or
testing specific package versions without affecting the entire system’s
&lt;code&gt;nixpkgs&lt;/code&gt; set.&lt;/p&gt;
</content></entry><entry><title>Specialisations</title><id>https://saylesss88.github.io/flakes/specialisations_4.6.html</id><updated>2025-11-22T00:00:00+00:00</updated><author><name>saylesss87@proton.me (saylesss88)</name></author><link href="https://saylesss88.github.io/flakes/specialisations_4.6.html" rel="alternate"/><content type="html">&lt;h1&gt;NixOS Specialisations For Multiple Profiles&lt;/h1&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Click to Expand Table of Contents&lt;/summary&gt;
&lt;!-- toc --&gt;
&lt;/details&gt;
&lt;p&gt;&lt;strong&gt;NixOS specialisations&lt;/strong&gt; are a powerful feature that allow you to define
alternative system configurations variations within a single NixOS setup. Each
specialisation can modify or extend the base configuration, and NixOS will
generate separate boot entries for each, letting you choose at boot time (or
switch at runtime) which environment to use. This is ideal for testing,
hardware-specific tweaks, or separating work and personal environments without
maintaining multiple configuration files&lt;/p&gt;
&lt;h2&gt;How Specialisations Work&lt;/h2&gt;
&lt;p&gt;Specialisations are defined as attributes under the &lt;code&gt;specialisation&lt;/code&gt; option in
your configuration. Each key (e.g., &lt;code&gt;niri-test&lt;/code&gt;) represents a named
specialisation, and its configuration attribute contains the NixOS options to
apply on top of the base system&lt;/p&gt;
&lt;p&gt;By default, a specialisation inherits the parent configuration and applies its
changes on top. You can also set &lt;code&gt;inheritParentConfig = false;&lt;/code&gt; to create a
completely separate configuration.&lt;/p&gt;
&lt;p&gt;After running &lt;code&gt;nixos-rebuild boot&lt;/code&gt;, your bootloader will present extra entries
for each specialisation. Selecting one boots into the system with that
specialisation’s settings applied&lt;/p&gt;
&lt;p&gt;Runtime Switching: You can switch to a specialisation at runtime using
activation scripts, e.g.:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nixos-rebuild switch --specialisation niri-test
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;/run/current-system/specialisation/niri-test/bin/switch-to-configuration switch
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;Note: Some changes (like kernel switches) require a reboot to take effect&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Example: Let’s create a basic specialisation to try out the Niri Window Manager:&lt;/p&gt;
&lt;p&gt;First we have to add the &lt;code&gt;niri-flake&lt;/code&gt; as an input to our &lt;code&gt;flake.nix&lt;/code&gt; and add the
module to install it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# flake.nix
inputs = {
     niri.url = &quot;github:sodiboo/niri-flake&quot;;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# configuration.nix
{ pkgs, inputs, ... }: {
# ... snip ...
imports = [
    inputs.niri.nixosModules.niri
];

# This is the top-level overlay
  nixpkgs.overlays = [inputs.niri.overlays.niri];

# ... snip ...

  specialisation = {
    niri-test.configuration = {
      system.nixos.tags = [&quot;niri&quot;];

      # Add the Niri overlay for this specialisation
      nixpkgs.overlays = [inputs.niri.overlays.niri];

      # Enable Niri session
      programs.niri = {
        enable = true;
        package = pkgs.niri-unstable;
      };

      # Optionally, add a test user and greetd for login
      users.users.niri = {
        isNormalUser = true;
        extraGroups = [&quot;networkmanager&quot; &quot;video&quot; &quot;wheel&quot;];
        initialPassword = &quot;test&quot;; # for testing only!
        createHome = true;
      };

      services.greetd = {
        enable = true;
        settings = rec {
          initial_session = {
            command = lib.mkForce &quot;${pkgs.niri}/bin/niri&quot;;
            user = lib.mkForce &quot;niri&quot;;
          };
          default_session = initial_session;
        };
      };

      environment.etc.&quot;niri/config.kdl&quot;.text = &apos;&apos;
        binds {
          Mod+T { spawn &quot;alacritty&quot;; }
          Mod+D { spawn &quot;fuzzel&quot;; }
          Mod+Q { close-window; }
          Mod+Shift+Q { exit; }
        }
      &apos;&apos;;
      environment.systemPackages = with pkgs; [
        alacritty
        waybar
        fuzzel
        mako
        firefox
      ];

      programs.firefox.enable = true;

      services.pipewire = {
        enable = true;
        alsa.enable = true;
        pulse.enable = true;
        # Optionally:
        jack.enable = true;
      };

      hardware.alsa.enablePersistence = true;

      networking.networkmanager.enable = true;
    };
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I chose to use the nightly version so it was required to add the overlay at the
top-level as well as inside the &lt;code&gt;specialisation&lt;/code&gt; block.&lt;/p&gt;
&lt;p&gt;On my system it sped up build times to first run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo nixos-rebuild switch --flake .
# And Then Run
sudo nixos-rebuild boot --flake .
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;What this does&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Creates a boot entry called &lt;code&gt;niri-test&lt;/code&gt; with the Niri Wayland compositor, a
test user, and a &lt;code&gt;greetd&lt;/code&gt; login manager.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Installs a set of packages and enables PipeWire with ALSA, PulseAudio, and
JACK support.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Provides a custom Niri configuration file for a few keybinds and enables
NetworkManager.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Using Your Specialisation After Boot&lt;/h2&gt;
&lt;p&gt;Once you have rebooted and selected your specialisation from the boot menu, you
can use your system as usual. If you want to add or remove programs, change
settings, or update your environment within a specialisation, simply:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Edit your configuration: Add or remove packages (e.g., add &lt;code&gt;ghostty&lt;/code&gt; to
&lt;code&gt;environment.systemPackages&lt;/code&gt;) or change any other options inside the
relevant &lt;code&gt;specialisation&lt;/code&gt; block in your NixOS configuration.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Apply changes with a rebuild: Run the standard NixOS rebuild command. If you
are currently running the specialisation you want to update, use:&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo nixos-rebuild switch
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will apply your changes to the current specialisation&lt;/p&gt;
&lt;p&gt;If you want to build and activate a different specialisation from your current
session, use:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo nixos-rebuild switch --specialisation name
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or, you can activate a specialisation directly with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo /run/current-system/specialisation/&amp;lt;name&amp;gt;/bin/switch-to-configuration switch
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Replace &lt;code&gt;&amp;lt;name&amp;gt;&lt;/code&gt; with your specialisation’s name.&lt;/p&gt;
&lt;p&gt;Reboot if needed: Most changes apply immediately, but some (like kernel or
&lt;code&gt;initrd&lt;/code&gt; changes) require a reboot for the specialisation to fully take effect&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tip&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;Each specialisation can have its own set of installed programs. Only those
listed in the &lt;code&gt;environment.systemPackages&lt;/code&gt; (or enabled via modules) inside the
&lt;code&gt;specialisation&lt;/code&gt; block will be available when you boot into that context.&lt;/p&gt;
&lt;p&gt;You manage and update your specialisation just like your main NixOS system no
special commands or workflow are required beyond specifying the specialisation
when rebuilding or switching.&lt;/p&gt;
&lt;h2&gt;Use Cases for Specialisations&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Hardware Profiles&lt;/strong&gt;: Enable/disable drivers or services for specific
hardware (e.g., eGPU, WiFi, or SR-IOV setups)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Desktop Environments&lt;/strong&gt;: Quickly switch between different desktop
environments or compositors (e.g., GNOME, Plasma, Niri)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Testing&lt;/strong&gt;: Safely try out unstable packages, new kernels, or experimental
features without risking your main environment&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;User Separation&lt;/strong&gt;: Create profiles for different users, each with their own
settings, packages, and auto-login&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Secure Environments&lt;/strong&gt;: Combine with encrypted partitions for more secure,
isolated setups&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Securely Separated Contexts with NixOS Specialisations&lt;/h2&gt;
&lt;p&gt;I will just explain the concept here for completeness, if you want to implement
this I recommend following:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.tweag.io/blog/2022-11-01-hard-user-separation-with-nixos/&quot;&gt;Tweag Hard User Separation with NixOS&lt;/a&gt;&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Click To Expand Section on Separate Contexts &lt;/summary&gt;
&lt;p&gt;If you use the same computer in different contexts such as for work and for your
private life you may worry about the risks of mixing sensitive environments. For
example, a cryptolocker received through a compromised work email could
potentially encrypt your personal files, including irreplaceable family photos.&lt;/p&gt;
&lt;p&gt;A common solution is to install two different operating systems and dual-boot
between them, keeping work and personal data isolated. However, this approach
means you have two systems to maintain, update, and configure, which can be a
significant hassle.&lt;/p&gt;
&lt;p&gt;NixOS offers a third alternative: With NixOS specialisations, you can manage two
(or more) securely separated contexts within a single operating system. At boot
time, you select which context you want to use work or personal. Each context
can have its own encrypted root partition, user accounts, and configuration, but
both share the same Nix store for packages. This means:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;No duplicated packages: Both contexts use the same system-wide package store,
saving space and simplifying updates.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Single system to maintain: You update and manage only one NixOS installation,
not two.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Strong security boundaries: Each context can have its own encrypted root, so a
compromise in one context (such as malware in your work environment) cannot
access the data in the other context.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Flexible management: You can configure both contexts from either environment,
making administration easier.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This approach combines the security of dual-booting with the convenience and
efficiency of a single, unified system.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How It Works&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Encrypted Partitions: Each context (work and personal) has its own encrypted
root partition. The shared /nix/store partition is also encrypted, but can be
unlocked by either context.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Specialisations at Boot: NixOS generates multiple boot entries, one for each
context. You simply choose your desired environment at boot time.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Separation of Data: Your work and personal home directories, settings, and
documents remain isolated from each other, while still benefiting from shared
system packages.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Benefits Over Traditional Dual-Boot&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Only one system to update and configure.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;No wasted disk space on duplicate packages.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Seamless switching between contexts with a reboot.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Consistent NixOS tooling and workflows in both environments.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What You Need&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;A physical or virtual machine supported by NixOS.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Willingness to erase the system disk during setup.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;LVM (Logical Volume Manager) support: This setup requires using LVM for disk
partitioning and management. LVM allows you to create multiple logical volumes
on a single physical disk, making it possible to securely separate your work
and personal environments while sharing a common Nix store. You will use LVM
commands such as &lt;code&gt;pvcreate&lt;/code&gt;, &lt;code&gt;vgcreate&lt;/code&gt;, and &lt;code&gt;lvcreate&lt;/code&gt; to prepare your disk
layout&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In summary: With NixOS specialisations and careful disk partitioning, you can
achieve secure, convenient, and efficient context separation—no need to
compromise between security and manageability.&lt;/p&gt;
&lt;/details&gt;
&lt;h3&gt;Tips and Best Practices&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Overriding Values: Use &lt;code&gt;lib.mkDefault&lt;/code&gt; or &lt;code&gt;lib.mkForce&lt;/code&gt; to make options
overridable or forced in specialisations. I had to do it above because I have
greetd setup for my main configuration as well.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Selective Configuration: If you want certain options only in the default
(non-specialised) system, use:&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;config = lib.mkIf (config.specialisation != {}) { ... }
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;This condition checks if you’re in a specialisation.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Any settings inside this block will &lt;strong&gt;not&lt;/strong&gt; be inherited by specialisations,
keeping them exclusive to the main system.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Runtime Limitations: Not all changes (e.g., kernel or &lt;code&gt;initrd&lt;/code&gt;) can be fully
applied at runtime; a reboot is required for those.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Modularity: Specialisations work well with modular NixOS configs keep
hardware, user, and service configs in separate files for easier management&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;References to Official Documentation and Community Resources&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.tweag.io/blog/2022-08-18-nixos-specialisations/&quot;&gt;Tweag: Introduction to NixOS specialisations&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://wiki.nixos.org/wiki/Specialisation&quot;&gt;NixOS Wiki: Specialisation&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.tweag.io/blog/2022-11-01-hard-user-separation-with-nixos/&quot;&gt;Tweag Hard User Separation with NixOS&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content></entry><entry><title>Comparing Flakes and Traditional Nix</title><id>https://saylesss88.github.io/Comparing_Flakes_and_Traditional_Nix_8.html</id><updated>2025-11-22T00:00:00+00:00</updated><author><name>saylesss87@proton.me (saylesss88)</name></author><link href="https://saylesss88.github.io/Comparing_Flakes_and_Traditional_Nix_8.html" rel="alternate"/><content type="html">&lt;h1&gt;Chapter 8&lt;/h1&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Click to Expand Table of Contents&lt;/summary&gt;
&lt;!-- toc --&gt;
&lt;/details&gt;
&lt;!-- ![nixWinter](images/nixWinter.png) --&gt;
&lt;h2&gt;Comparing Flakes and Traditional Nix&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;This post is based on notes from Nix-Hour #4, comparing Traditional Nix and
Flakes, focusing on achieving pure build results. See the
&lt;a href=&quot;https://www.youtube.com/watch?v=atmoYyBAhF4&quot;&gt;YouTube video&lt;/a&gt; for the original
content. This guide adapts the information for clarity and ease of
understanding.&lt;/li&gt;
&lt;/ul&gt;
&lt;details&gt;
&lt;summary&gt; What is Purity in Nix? (click here) &lt;/summary&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;A key benefit of Nix Flakes is their &lt;em&gt;default&lt;/em&gt; enforcement of &lt;strong&gt;pure
evaluation&lt;/strong&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In Nix, an &lt;strong&gt;impure operation&lt;/strong&gt; depends on something &lt;em&gt;outside&lt;/em&gt; its explicit
inputs. Examples include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;User’s system configuration&lt;/li&gt;
&lt;li&gt;Environment variables&lt;/li&gt;
&lt;li&gt;Current time&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Impurity leads to unpredictable builds that may differ across systems or time.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/details&gt;
&lt;h2&gt;Building a Simple “hello” Package: Flakes vs. Traditional Nix&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;We’ll demonstrate building a basic “hello” package using both Flakes and
Traditional Nix to highlight the differences in handling purity.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Using Nix Flakes&lt;/h2&gt;
&lt;details&gt;
&lt;summary&gt; Building Hello with Flakes (click here) &lt;/summary&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Setup:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mkdir hello &amp;amp;&amp;amp; cd hello/
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Create &lt;code&gt;flake.nix&lt;/code&gt; (Initial Impure Example):&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# flake.nix
{
  outputs = { self, nixpkgs }: {
    myHello = (import nixpkgs {}).hello;
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Note: Flakes don’t have access to &lt;code&gt;builtins.currentSystem&lt;/code&gt; directly.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Impure Build (Fails):&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix build .#myHello
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;This fails because Flakes enforce purity by default.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Force Impure Build:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix build .#myHello --impure
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Making the Flake Pure:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# flake.nix
{
  inputs = {
    nixpkgs.url = &quot;github:NixOS/nixpkgs&quot;;
    flake-utils.url = &quot;github:numtide/flake-utils&quot;;
  };

  outputs = { self, nixpkgs, flake-utils }:
    flake-utils.lib.eachDefaultSystem (system:
      let
        pkgs = nixpkgs.legacyPackages.${system};
      in {
        packages.myHello = pkgs.hello;
      }
    );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;flake-utils&lt;/code&gt; simplifies making flakes system-agnostic and provides the
&lt;code&gt;system&lt;/code&gt; attribute.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Pure Build (Success):&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix build .#myHello
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
  &lt;/details&gt;
&lt;h2&gt;Using Traditional Nix&lt;/h2&gt;
&lt;details&gt;
&lt;summary&gt; Building hello with Traditional Nix &lt;/summary&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Setup:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mkdir hello2 &amp;amp;&amp;amp; cd hello2/
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Create &lt;code&gt;default.nix&lt;/code&gt; (Initial Impure Example):&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# default.nix
{ myHello = (import &amp;lt;nixpkgs&amp;gt; { }).hello; }
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Build (Impure):&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix-build -A myHello
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Impurity Explained:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix repl
nix-repl&amp;gt; &amp;lt;nixpkgs&amp;gt;
/nix/var/nix/profiles/per-user/root/channels/nixos
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;nixpkgs&amp;gt;&lt;/code&gt; depends on the user’s environment (Nixpkgs channel), making it
impure. Even with channels disabled, it relies on a specific Nixpkgs
version in the store.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Achieving Purity: Using &lt;code&gt;fetchTarball&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;GitHub allows downloading repository snapshots at specific commits,
crucial for reproducibility.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Get Nixpkgs Revision from &lt;code&gt;flake.lock&lt;/code&gt; (from the Flake example):&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# flake.lock
&quot;nixpkgs&quot;: {
  &quot;locked&quot;: {
    &quot;lastModified&quot;: 1746372124,
    &quot;narHash&quot;: &quot;sha256-n7W8Y6bL7mgHYW1vkXKi9zi/sV4UZqcBovICQu0rdNU=&quot;,
    &quot;owner&quot;: &quot;NixOS&quot;,
    &quot;repo&quot;: &quot;nixpkgs&quot;,
    &quot;rev&quot;: &quot;f5cbfa4dbbe026c155cf5a9204f3e9121d3a5fe0&quot;,
    &quot;type&quot;: &quot;github&quot;
  },
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Modify &lt;code&gt;default.nix&lt;/code&gt; for Purity:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# default.nix
let
  nixpkgs = fetchTarball {
    url = &quot;[https://github.com/NixOS/nixpkgs/archive/f5cbfa4dbbe026c155cf5a9204f3e9121d3a5fe0.tar.gz](https://github.com/NixOS/nixpkgs/archive/f5cbfa4dbbe026c155cf5a9204f3e9121d3a5fe0.tar.gz)&quot;;
    sha256 = &quot;0000000000000000000000000000000000000000000000000000&quot;; # Placeholder
  };
in {
  myHello = (import nixpkgs {}).hello;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Replace &lt;code&gt;&amp;lt;nixpkgs&amp;gt;&lt;/code&gt; with &lt;code&gt;fetchTarball&lt;/code&gt; and a specific revision. A
placeholder &lt;code&gt;sha256&lt;/code&gt; is used initially.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Build (Nix provides the correct &lt;code&gt;sha256&lt;/code&gt;):&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix-build -A myHello
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Verification:&lt;/strong&gt; Both Flake and Traditional Nix builds now produce the same
output path.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Remaining Impurities in Traditional Nix:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Default arguments to &lt;code&gt;import &amp;lt;nixpkgs&amp;gt; {}&lt;/code&gt; can introduce impurity:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;overlays&lt;/code&gt;: &lt;code&gt;~/.config/nixpkgs/overlays&lt;/code&gt; (user-specific)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;config&lt;/code&gt;: &lt;code&gt;~/.config/nixpkgs/config.nix&lt;/code&gt; (user-specific)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;system&lt;/code&gt;: &lt;code&gt;builtins.currentSystem&lt;/code&gt; (machine-specific)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Making Traditional Nix Fully Pure:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# default.nix
{system ? builtins.currentSystem}:
let
  nixpkgs = fetchTarball {
    url =
      &quot;[https://github.com/NixOS/nixpkgs/archive/0243fb86a6f43e506b24b4c0533bd0b0de211c19.tar.gz](https://github.com/NixOS/nixpkgs/archive/0243fb86a6f43e506b24b4c0533bd0b0de211c19.tar.gz)&quot;;
    sha256 = &quot;1qvdbvdza7hsqhra0yg7xs252pr1q70nyrsdj6570qv66vq0fjnh&quot;;
  };
in {
  myHello = (import nixpkgs {
    overlays = [];
    config = {};
    inherit system;
  }).hello;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Override impure defaults for &lt;code&gt;overlays&lt;/code&gt;, &lt;code&gt;config&lt;/code&gt;, and make &lt;code&gt;system&lt;/code&gt; an
argument.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Building with a Specific System:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix-build -A myHello --argstr system x86_64-linux
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Pure Evaluation Mode in Traditional Nix:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix-instantiate --eval --pure-eval --expr &apos;fetchGit { url = ./.; rev = &quot;b4fe677e255c6f89c9a6fdd3ddd9319b0982b1ad&quot;; }&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Example of using &lt;code&gt;--pure-eval&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix-build --pure-eval --expr &apos;(import (fetchGit { url = ./.; rev = &quot;b4fe677e255c6f89c9a6fdd3ddd9319b0982b1ad&quot;; }) { system = &quot;x86_64-linux&quot;; }).myHello&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Building with a specific revision and system.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
  &lt;/details&gt;
&lt;h3&gt;Updating Nixpkgs&lt;/h3&gt;
&lt;details&gt;
&lt;summary&gt; Updating Nixpkgs with Flakes &lt;/summary&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix flake update
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;nix build .#myHello --override-input nixpkgs github:NixOS/nixpkgs/nixos-24.11
&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;
&lt;h3&gt;Updating Traditional Nix (using &lt;code&gt;niv&lt;/code&gt;)&lt;/h3&gt;
&lt;details&gt;
&lt;summary&gt; Updating with niv &lt;/summary&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;nix-shell -p niv
niv init
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# default.nix
{ system ? builtins.currentSystem,
  sources ? import nix/sources.nix,
  nixpkgs ? sources.nixpkgs,
  pkgs ? import nixpkgs {
    overlays = [ ];
    config = { };
    inherit system;
  }, }: {
  myHello = pkgs.hello;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And build it with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix-build -A myHello
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;niv update nixpkgs --branch=nixos-unstable
nix-build -A myHello
&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;
&lt;details&gt;
&lt;summary&gt; Adding Home-Manager with Flakes (click here) &lt;/summary&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# flake.nix
{
  inputs = {
    nixpkgs.url = &quot;github:NixOS/nixpkgs&quot;;
    flake-utils.url = &quot;github:numtide/flake-utils&quot;;
    home-manager.url = &quot;github:nix-community/home-manager&quot;;
  };

  outputs = { self, nixpkgs, flake-utils, home-manager, ... }:
    flake-utils.lib.eachDefaultSystem (system:
      let pkgs = nixpkgs.legacyPackages.${system};
      in {
        packages.myHello = pkgs.hello;
        packages.x86_64-linux.homeManagerDocs =
          home-manager.packages.x86_64-linux.docs-html;
      });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix flake update
nix flake show github:nix-community/home-manager
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;home-manager.inputs.follows = &quot;nixpkgs&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;
&lt;h4&gt;Adding Home-Manager with Traditional Nix&lt;/h4&gt;
&lt;details&gt;
&lt;summary&gt; Adding Home-Manager with Traditional Nix (click here) &lt;/summary&gt;
```nix
niv add nix-community/home-manager
```
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;nix repl
nix-repl&amp;gt; s = import ./nix/sources.nix
nix-repl&amp;gt; s.home-manager
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{ system ? builtins.currentSystem, sources ? import nix/sources.nix
  , nixpkgs ? sources.nixpkgs, pkgs ? import nixpkgs {
    overlays = [ ];
    config = { };
    inherit system;
  }, }: {
  homeManagerDocs = (import sources.home-manager { pkgs = pkgs; }).docs;

  myHello = pkgs.hello;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix-build -A homeManagerDocs
&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;
&lt;h4&gt;Conclusion&lt;/h4&gt;
&lt;p&gt;In this chapter, we’ve explored the key differences between traditional Nix and
Nix Flakes, particularly focusing on how each approach handles purity,
dependency management, and project structure. We’ve seen that while traditional
Nix can achieve purity with careful configuration, Flakes enforce it by default,
offering a more robust and standardized way to build reproducible environments.
Flakes also streamline dependency management and provide a more structured
project layout compared to the often ad-hoc nature of traditional Nix projects.&lt;/p&gt;
&lt;p&gt;However, regardless of whether you’re working with Flakes or traditional Nix,
understanding how to debug and trace issues within your Nix code is crucial.
When things go wrong, you’ll need tools and techniques to inspect the evaluation
process, identify the source of errors, and understand how your modules and
derivations are being constructed.&lt;/p&gt;
&lt;p&gt;In our next chapter,
&lt;a href=&quot;https://saylesss88.github.io/Debugging_and_Tracing_NixOS_Modules_9.html&quot;&gt;Debugging and Tracing Modules&lt;/a&gt;,
we will delve into the world of Nix debugging. We’ll explore various techniques
and tools that can help you understand the evaluation process, inspect the
values of expressions, and trace the execution of your Nix code, enabling you to
effectively troubleshoot and resolve issues in both Flake-based and traditional
Nix projects.&lt;/p&gt;
</content></entry><entry><title>My Chapter</title><id>https://saylesss88.github.io/installation/index.html</id><updated>2025-11-22T00:00:00+00:00</updated><author><name>saylesss87@proton.me (saylesss88)</name></author><link href="https://saylesss88.github.io/installation/index.html" rel="alternate"/><content type="html">&lt;h1&gt;Installation Guides&lt;/h1&gt;
&lt;p&gt;This section provides detailed guides for installing NixOS. You’ll choose
between an &lt;strong&gt;unencrypted&lt;/strong&gt; or &lt;strong&gt;encrypted&lt;/strong&gt; base setup. After your core
installation, you can explore adding optional features like &lt;code&gt;sops&lt;/code&gt; for encrypted
secrets, &lt;code&gt;lanzaboote&lt;/code&gt; for Secure Boot, or &lt;code&gt;impermanence&lt;/code&gt; for a stateless system.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;1. Unencrypted Disko Btrfs Subvol Installation&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Guide:&lt;/strong&gt;
&lt;a href=&quot;https://saylesss88.github.io/installation/unenc/unencrypted_setups.html&quot;&gt;Minimal Btrfs-Subvol Install with Disko and Flakes&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Users who want a straightforward and quick setup.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://saylesss88.github.io/installation/unenc/unenc_impermanence.html&quot;&gt;Unencrypted Impermanence&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You can still add Lanzaboote and sops secrets after the install for a more
secure system. To get the full benefits of Lanzaboote it is recommended to
use full disk encryption.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;2. Encrypted Disko Btrfs Subvol Installation&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Encrypted Install Guide:&lt;/strong&gt;
&lt;a href=&quot;https://saylesss88.github.io/installation/enc/enc_install.html&quot;&gt;Encrypted Install&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://saylesss88.github.io/installation/enc/encrypted_impermanence.html&quot;&gt;Encrypted Impermanence&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Important Considerations:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://saylesss88.github.io/installation/enc/lanzaboote.html&quot;&gt;Secure Boot with Lanzaboote&lt;/a&gt;
For the full benefit of Secure Boot (with Lanzaboote), it’s highly
recommended to have a second stage of protection, such as an encrypted disk.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://saylesss88.github.io/installation/enc/sops-nix.html&quot;&gt;Adding Sops&lt;/a&gt;
You can easily add &lt;code&gt;sops&lt;/code&gt; (for managing encrypted secrets) to your
configuration &lt;em&gt;after&lt;/em&gt; the initial encrypted installation and reboot. This
can simplify the initial setup process. However, always remember the core
goal of using encrypted secrets: &lt;strong&gt;never commit unencrypted or even hashed
sensitive data directly into your Git repository.&lt;/strong&gt; With modern equipment
brute force attacks are a real threat.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;3. Post-Installation Security &amp;amp; Features&lt;/h2&gt;
&lt;p&gt;Once your base NixOS system is installed, consider these powerful additions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;sops-nix&lt;/code&gt;:&lt;/strong&gt; For managing encrypted secrets directly within your NixOS
configuration, ensuring sensitive data is never stored in plain text.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;lanzaboote&lt;/code&gt;:&lt;/strong&gt; For enabling Secure Boot, verifying the integrity of your
boot chain (requires UEFI and custom keys).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;impermanence&lt;/code&gt;:&lt;/strong&gt; For setting up a stateless NixOS system, where the root
filesystem reverts to a clean state on every reboot.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content></entry><entry><title>Unencrypted Install</title><id>https://saylesss88.github.io/installation/unenc/unencrypted_setups.html</id><updated>2025-11-22T00:00:00+00:00</updated><author><name>saylesss87@proton.me (saylesss88)</name></author><link href="https://saylesss88.github.io/installation/unenc/unencrypted_setups.html" rel="alternate"/><content type="html">&lt;h1&gt;Minimal BTRFS-Subvol Install with Disko and Flakes&lt;/h1&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Click to Expand Table of Contents&lt;/summary&gt;
&lt;!-- toc --&gt;
&lt;/details&gt;
&lt;h1&gt;Unencrypted Setups&lt;/h1&gt;
&lt;p&gt;Figure 1: BTRFS Logo: Image of the BTRFS logo. Sourced from the BTRFS repo BTRFS
logo&lt;/p&gt;
&lt;p&gt;Why I Chose BTRFS I chose BTRFS because I was already familiar with it from
using it with Arch Linux and I found it to be very easy to use. From what I’ve
read, there are licensing issues between the Linux Kernel and ZFS which means
that ZFS is not part of the Linux Kernel; it’s maintained by the OpenZFS project
and available as a separate kernel module. This can cause issues and make you
think more about your filesystem than I personally want to at this point.&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;✔️ Click for BTRFS Subvolume Overview&lt;/summary&gt;
&lt;p&gt;A &lt;strong&gt;Btrfs subvolume&lt;/strong&gt; is essentially a distinct section within a Btrfs
filesystem that maintains its own set of files and directories, along with a
separate inode numbering system. Unlike block-level partitions (such as LVM
logical volumes), Btrfs subvolumes operate at the file level and are based on
file extents.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Extents&lt;/strong&gt; in Btrfs are contiguous blocks of data on disk that store the actual
contents of files. When files are created or modified, Btrfs manages these
extents efficiently, allowing features like deduplication and snapshots.
Multiple subvolumes can reference the same extents, meaning that identical data
is not duplicated on disk, which saves space and improves performance.&lt;/p&gt;
&lt;p&gt;A &lt;strong&gt;snapshot&lt;/strong&gt; in Btrfs is a special kind of subvolume that starts with the same
content as another subvolume at the time the snapshot is taken. Snapshots are
typically writable by default, so you can make changes in the snapshot without
affecting the original subvolume. This is possible because Btrfs tracks changes
at the extent level, only creating new extents when files are modified (a
technique called copy-on-write).&lt;/p&gt;
&lt;p&gt;Subvolumes in Btrfs behave much like regular directories from a user’s
perspective, but they support additional operations such as renaming, moving,
and nesting (placing subvolumes within other subvolumes). There are no
restrictions on nesting, though it can affect how snapshots are created and
managed. Each subvolume is assigned a unique and unchangeable numeric ID
(subvolid or rootid).&lt;/p&gt;
&lt;p&gt;You can access a Btrfs subvolume in two main ways:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;As a normal directory within the filesystem.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;By mounting it directly as if it were a separate filesystem, using the subvol
or subvolid mount options. When mounted this way, you only see the contents of
that subvolume, similar to how a bind mount works.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When a new Btrfs filesystem is created, it starts with a “top-level” subvolume
(with an internal ID of 5). This subvolume is always present and cannot be
deleted or replaced, and it is the default mount point unless changed with btrfs
subvolume set-default.&lt;/p&gt;
&lt;p&gt;Subvolumes can also have storage quotas set using Btrfs’s quota groups , but
otherwise, they all draw from the same underlying storage pool. Thanks to
features like deduplication and snapshots, subvolumes can share data efficiently
at the extent level.While ZFS is a solid choice and offers some benefits over
BTRFS, I recommend looking into it before making your own decision.&lt;/p&gt;
&lt;p&gt;If you have a ton of RAM you could most likely skip the minimal install and just
set your system up as needed or just use
&lt;a href=&quot;https://elis.nu/blog/2020/05/nixos-tmpfs-as-root/&quot;&gt;tmpfs as root&lt;/a&gt;&lt;/p&gt;
&lt;/details&gt;
&lt;h2&gt;Getting Started with Disko&lt;/h2&gt;
&lt;p&gt;Disko allows you to declaratively partition and format your disks, and then
mount them to your system. I recommend checking out the
&lt;a href=&quot;https://github.com/nix-community/disko/tree/master?tab=readme-ov-file&quot;&gt;README&lt;/a&gt;
as it is a disk destroyer if used incorrectly.&lt;/p&gt;
&lt;p&gt;We will mainly be following the
&lt;a href=&quot;https://github.com/nix-community/disko/blob/master/docs/quickstart.md&quot;&gt;disko quickstart guide&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Figure 2: &lt;strong&gt;Disko Logo&lt;/strong&gt;: Image of the logo for Disko, the NixOS declarative
disk partitioning tool. Sourced from the
&lt;a href=&quot;https://github.com/nix-community/disko&quot;&gt;Disko project&lt;/a&gt; disko logo&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Get the
&lt;a href=&quot;https://channels.nixos.org/nixos-25.05/latest-nixos-minimal-x86_64-linux.iso&quot;&gt;Nixos Minimal ISO&lt;/a&gt;
Get it on a usb stick, I use Ventoy with Ventoy2Disk.sh. The following is the
link to the
&lt;a href=&quot;https://sourceforge.net/projects/ventoy/files/v1.1.05/ventoy-1.1.05-linux.tar.gz/download&quot;&gt;Ventoy TarBall&lt;/a&gt;
download, untar it with &lt;code&gt;tar -xzf ventoy-1.1.05-linux.tar.gz&lt;/code&gt;, and make it
executable with &lt;code&gt;chmod +x Ventoy2Disk.sh&lt;/code&gt;, and finally execute it with
&lt;code&gt;sudo ./Ventoy2Disk.sh&lt;/code&gt; Follow the prompts to finish the install.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You’ll have to run it on for the USB drive you’re trying to use, you can do that
by unplugging the USB stick and running &lt;code&gt;lsblk&lt;/code&gt;, then plug it in again and run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;lsblk -f
NAME          FSTYPE      FSVER LABEL   UUID                                 FSAVAIL FSUSE% MOUNTPOINTS
sda
└─sda1        vfat        FAT32 MYUSB   46E8-9304
sdb           vfat        FAT12         F054-697D                               1.4M     0% /run/media/jr/F054-697D
nvme0n1
├─nvme0n1p1   vfat        FAT32         BCD8-8C51                               1.8G    12% /boot
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sdb&lt;/code&gt; is a USB plugin for a mouse. &lt;code&gt;sda&lt;/code&gt; is the USB stick that I want to
target here:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo ./Ventoy2Disk.sh -i /dev/sda
# Or to force overwrite an existing Ventoy entry
sudo ./Ventoy2Disk.sh -I /dev/sda
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;The minimal installer uses wpa_supplicant instead of NetworkManager, to
enable networking run the following:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo systemctl start wpa_supplicant
wpa_cli
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;&amp;gt; add_network
0

&amp;gt; set_network 0 ssid &quot;myhomenetwork&quot;
OK

&amp;gt; set_network 0 psk &quot;mypassword&quot;
OK

&amp;gt; enable_network 0
OK
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To exit type &lt;code&gt;quit&lt;/code&gt;, then check your connection with &lt;code&gt;ping google.com&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Another option is to do the following, so either the above method or the below
method after starting &lt;code&gt;wpa_supplicant&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Alternative for quick setup (less interactive, but often faster)
sudo wpa_passphrase &quot;myhomenetwork&quot; &quot;mypassword&quot; &amp;gt;&amp;gt; /etc/wpa_supplicant/wpa_supplicant-wlan0.conf
sudo systemctl restart wpa_supplicant@wlan0.service
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;Get your Disk Name with lsblk&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The output should be something like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;NAME        MAJ:MIN RM   SIZE RO TYPE MOUNTPOINTS
nvme0n1     259:0    0   1,8T  0 disk
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;Copy the disk configuration to your machine. You can choose one from the
examples directory.&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Option A&lt;/strong&gt;: (Simpler for new users) I also created a starter repo containing
much of what’s needed. If you choose this option follow the README.md included
with the repo.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd ~
git clone https://github.com/saylesss88/my-flake.git
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;Make sure to change line 7 in disk-config.nix to what you got from step 3
device = “/dev/nvme0n1”;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Option B&lt;/strong&gt;: (More flexible, more manual steps) Skip cloning the repo above
and for the btrfs-subvolume default layout, run the following:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd /tmp
curl https://raw.githubusercontent.com/nix-community/disko/refs/heads/master/example/btrfs-subvolumes.nix -o /tmp/disk-config.nix
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;5&quot;&gt;
&lt;li&gt;Make Necessary changes, I set mine up for impermanence with the following:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nano /tmp/disk-config.nix
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{
  disko.devices = {
    disk = {
      main = {
        type = &quot;disk&quot;;
        device = &quot;/dev/nvme0n1&quot;;
        content = {
          type = &quot;gpt&quot;;
          partitions = {
            ESP = {
              priority = 1;
              name = &quot;ESP&quot;;
              start = &quot;1M&quot;;
              end = &quot;512M&quot;;
              type = &quot;EF00&quot;;
              content = {
                type = &quot;filesystem&quot;;
                format = &quot;vfat&quot;;
                mountpoint = &quot;/boot&quot;;
                mountOptions = [&quot;umask=0077&quot;];
              };
            };
            root = {
              size = &quot;100%&quot;;
              content = {
                type = &quot;btrfs&quot;;
                extraArgs = [&quot;-f&quot;]; # Override existing partition
                # Subvolumes must set a mountpoint in order to be mounted,
                # unless their parent is mounted
                subvolumes = {
                  # Subvolume name is different from mountpoint
                  &quot;/root&quot; = {
                    mountpoint = &quot;/&quot;;
                    mountOptions = [&quot;subvol=root&quot; &quot;compress=zstd&quot; &quot;noatime&quot;];
                  };
                  # Subvolume name is the same as the mountpoint
                  &quot;/home&quot; = {
                    mountOptions = [&quot;subvol=home&quot; &quot;compress=zstd&quot; &quot;noatime&quot;];
                    mountpoint = &quot;/home&quot;;
                  };
                  # Sub(sub)volume doesn&apos;t need a mountpoint as its parent is mounted
                  &quot;/home/user&quot; = {};
                  # Parent is not mounted so the mountpoint must be set
                  &quot;/nix&quot; = {
                    mountOptions = [
                      &quot;subvol=nix&quot;
                      &quot;compress=zstd&quot;
                      &quot;noatime&quot;
                    ];
                    mountpoint = &quot;/nix&quot;;
                  };
                  &quot;/nix/persist&quot; = {
                    mountpoint = &quot;/nix/persist&quot;;
                    mountOptions = [&quot;subvol=persist&quot; &quot;compress=zstd&quot; &quot;noatime&quot;];
                  };
                  &quot;/log&quot; = {
                    mountpoint = &quot;/var/log&quot;;
                    mountOptions = [&quot;subvol=log&quot; &quot;compress=zstd&quot; &quot;noatime&quot;];
                  };
                  &quot;/lib&quot; = {
                    mountpoint = &quot;/var/lib&quot;;
                    mountOptions = [&quot;subvol=lib&quot; &quot;compress=zstd&quot; &quot;noatime&quot;];
                  };
                  # This subvolume will be created but not mounted
                  &quot;/test&quot; = {};
                };
              };
            };
          };
        };
      };
    };
  };
  fileSystems.&quot;/nix/persist&quot;.neededForBoot = true;
  fileSystems.&quot;/var/log&quot;.neededForBoot = true;
  fileSystems.&quot;/var/lib&quot;.neededForBoot = true;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;For &lt;code&gt;/tmp&lt;/code&gt; on RAM use something like the following. I’ve found that having
disko manage swaps causes unnecessary issues. Using zram follows the ephemeral
route:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{
  lib,
  config,
  ...
}: let
  cfg = config.custom.zram;
in {
  options.custom.zram = {
    enable = lib.mkEnableOption &quot;Enable utils module&quot;;
  };

  config = lib.mkIf cfg.enable {
    zramSwap = {
      enable = true;
      # one of &quot;lzo&quot;, &quot;lz4&quot;, &quot;zstd&quot;
      algorithm = &quot;zstd&quot;;
       priority = 5;
       memoryPercent = 50;
    };
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And in your &lt;code&gt;configuration.nix&lt;/code&gt; you would add:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# configuration.nix
custom = {
    zram.enable = true;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After adding the above module, you can see it with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;swapon --show
NAME       TYPE      SIZE USED PRIO
/dev/zram0 partition 7.5G   0B    5
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;6&quot;&gt;
&lt;li&gt;Run disko to partition, format and mount your disks. Warning this will wipe
EVERYTHING on your disk. Disko doesn’t work with dual boot.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo nix --experimental-features &quot;nix-command flakes&quot; run github:nix-community/disko/latest -- --mode destroy,format,mount /tmp/disk-config.nix
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Check it with the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mount | grep /mnt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The output for an nvme0n1 disk would be similar to the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#... snip ...
/dev/nvme0n1p2 on /mnt type btrfs (rw,noatime,compress=zstd:3,ssd,discard=async,space_cache=v2,subvolid=285,subvol=/root)
/dev/nvme0n1p2 on /mnt/persist type btrfs (rw,noatime,compress=zstd:3,ssd,discard=async,space_cache=v2,subvolid=261,subvol=/persist)
/dev/nvme0n1p2 on /mnt/etc type btrfs (rw,noatime,compress=zstd:3,ssd,discard=async,space_cache=v2,subvolid=261,subvol=/persist)
/dev/nvme0n1p2 on /mnt/nix type btrfs (rw,noatime,compress=zstd:3,ssd,discard=async,space_cache=v2,subvolid=260,subvol=/nix)
/dev/nvme0n1p2 on /mnt/var/lib type btrfs (rw,noatime,compress=zstd:3,ssd,discard=async,space_cache=v2,subvolid=258,subvol=/lib)
/dev/nvme0n1p2 on /mnt/var/log type btrfs (rw,noatime,compress=zstd:3,ssd,discard=async,space_cache=v2,subvolid=259,subvol=/log)
/dev/nvme0n1p2 on /mnt/nix/store type btrfs (ro,noatime,compress=zstd:3,ssd,discard=async,space_cache=v2,subvolid=260,subvol=/nix)
# ... snip ...
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;7&quot;&gt;
&lt;li&gt;Generate necessary files, here we use –no-filesystems because disko handles
the fileSystems attribute for us.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nixos-generate-config --no-filesystems --root /mnt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It may be helpful to add a couple things to your &lt;code&gt;configuration.nix&lt;/code&gt; now,
rebuild and then move on. Such as, your hostname, git, an editor of your choice.
After your additions run &lt;code&gt;sudo nixos-rebuild&lt;/code&gt; switch to apply the changes. If
you do this, you can skip the &lt;code&gt;nix-shell -p&lt;/code&gt; command coming up.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo mv /tmp/disk-config.nix /mnt/etc/nixos
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Setting a Flake for your minimal Install&lt;/h2&gt;
&lt;ol start=&quot;8&quot;&gt;
&lt;li&gt;Create the flake in your home directory, then move it to /mnt/etc/nixos. This
avoids needing to use sudo for every command while in the /mnt/etc/nixos
directory.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd ~
mkdir flake &amp;amp;&amp;amp; cd flake
nix-shell -p git yazi helix
export NIX_CONFIG=&apos;experimental-features = nix-command flakes&apos;
export EDITOR=&apos;hx&apos;
hx flake.nix
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;You’ll change hostname = nixpkgs.lib.nixosSystem to your chosen hostname,
(e.g. magic = nixpkgs.lib.nixosSystem). This will be the same as your
networking.hostName = “magic”; in your configuration.nix that we will set up
shortly.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# flake.nix
{
  description = &quot;NixOS configuration&quot;;

  inputs = {
    nixpkgs.url = &quot;github:nixos/nixpkgs/nixos-unstable&quot;;
    disko.url = &quot;github:nix-community/disko/latest&quot;;
    disko.inputs.nixpkgs.follows = &quot;nixpkgs&quot;;
    # impermanence.url = &quot;github:nix-community/impermanence&quot;;
  };

  outputs = inputs@{ nixpkgs, ... }: {
    nixosConfigurations = {
      # Change `my-hostname` to match `networking.hostName`
      my-hostname = nixpkgs.lib.nixosSystem {
        system = &quot;x86_64-linux&quot;;
        modules = [
          ./configuration.nix
          inputs.disko.nixosModules.disko
          # inputs.impermanence.nixosModules.impermanence
        ];
      };
    };
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Move all the files into your flake:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd /mnt/etc/nixos/
sudo mv disk-config.nix hardware-configuration.nix configuration.nix ~/flake
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;9&quot;&gt;
&lt;li&gt;Edit configuration.nix with what is required, the following is required, I
clone my original flake repo and move the pieces into place but it’s fairly
easy to just type it all out:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Bootloader, (e.g., boot.loader.systemd-boot.enable = true;)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;User, the example uses username change this to your chosen username. If you
don’t set your hostname it will be nixos.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Networking, networking.networkmanager.enable = true;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;hardware-configuration.nix&lt;/code&gt; &amp;amp; &lt;code&gt;disk-config.nix&lt;/code&gt; for this setup&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;initialHashedPassword&lt;/code&gt;: Run &lt;code&gt;mkpasswd --method=yescrypt&lt;/code&gt;, then enter your
desired password. Example output,&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mkpasswd --method=yescrypt &amp;gt; /tmp/pass.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;You can check the quality with pwscore:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix-shell -p libpwquality

pwscore
very-secure-password
100
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;read the hashed password into the file with :r /tmp/pass.txt and move it into
place.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# configuration.nix
{
  config,
  lib,
  pkgs,
  inputs,
  ...
}: {
  imports = [
    # Include the results of the hardware scan.
    ./hardware-configuration.nix
    ./disk-config.nix
  ];

  networking.hostName = &quot;my-hostname&quot;; # This will match the `hostname` of your flake

  networking.networkmanager.enable = true;

  boot.loader.systemd-boot.enable = true; # (for UEFI systems only)
  # List packages installed in system profile.
  # You can use https://search.nixos.org/ to find more packages (and options).
  environment.systemPackages = with pkgs; [
    vim # Do not forget to add an editor to edit configuration.nix! The Nano editor is also installed by default.
    #   wget
    git
  ];

  time.timeZone = &quot;America/New_York&quot;;

# Change `nixos` to your chosen username, change the group to match
  users.users.nixos = {
    isNormalUser = true;
    extraGroups = [ &quot;wheel&quot; &quot;networkmanager&quot; ]; # Add &quot;wheel&quot; for sudo access
    initialHashedPassword = &quot;COPY_YOUR_MKPASSWD_OUTPUT_HERE&quot;; # &amp;lt;-- This is where it goes!
    # home = &quot;/home/nixos&quot;; # Optional: Disko typically handles home subvolumes
  };
  # Create a matching group
  users.groups.nixos = {};

  console.keyMap = &quot;us&quot;;

  nixpkgs.config.allowUnfree = true;

  system.stateVersion = &quot;25.05&quot;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Shred pass.txt:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;shred /tmp/pass.txt
rm /tmp/pass.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;10&quot;&gt;
&lt;li&gt;Move the flake to /mnt/etc/nixos and run nixos-install:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo mv ~/flake /mnt/etc/nixos/
sudo nixos-install --flake /mnt/etc/nixos/flake .#hostname
# if the above command doesn&apos;t work try this:
sudo nixos-install --flake /mnt/etc/nixos/flake#hostname
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You will be prompted to enter a new password if everything succeeds.&lt;/p&gt;
&lt;p&gt;If everything checks out, reboot the system and you should be prompted to enter
your user and password to login to a shell to get started.&lt;/p&gt;
&lt;p&gt;The flake will be placed at &lt;code&gt;/etc/nixos/flake&lt;/code&gt;, I choose to move it to my home
directory. Since the file was first in &lt;code&gt;/etc&lt;/code&gt; you’ll need to adjust the
permissions with something like &lt;code&gt;sudo chown nixos:nixos ~/flake&lt;/code&gt;. This is based
off of the example above where we created both a nixos user and group.&lt;/p&gt;
&lt;p&gt;You can check the layout of your btrfs system with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo btrfs subvolume list /
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;You may notice some old_roots in the output, which are snapshots, which are
likely created before system upgrades or reboots for rollback purposes. They
can be deleted or rolled back as needed.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;https://btrfs.readthedocs.io/en/latest/Subvolumes.html&quot;&gt;BTRFS Subvolumes&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;To continue following along and set up impermanence
&lt;a href=&quot;https://saylesss88.github.io/installation/unencrypted/impermanence.html&quot;&gt;Click Here&lt;/a&gt;&lt;/p&gt;
</content></entry><entry><title>Encrypted Install (BTRFS)</title><id>https://saylesss88.github.io/installation/enc/enc_install.html</id><updated>2025-11-22T00:00:00+00:00</updated><author><name>saylesss87@proton.me (saylesss88)</name></author><link href="https://saylesss88.github.io/installation/enc/enc_install.html" rel="alternate"/><content type="html">&lt;h1&gt;Encrypted Setups&lt;/h1&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Click to Expand Table of Contents&lt;/summary&gt;
&lt;!-- toc --&gt;
&lt;/details&gt;
&lt;p&gt;NixOS supports file systems that are encrypted using LUKS (Linux Unified Key
Setup). This guide walks you through an encrypted NixOS installation using Disko
for disk management and Btrfs for subvolumes. It is designed for users who want
full disk encryption and a modern filesystem layout. If you prefer an
unencrypted setup, you can skip the LUKS and encryption steps, but this guide
focuses on security and flexibility.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;For Unencrypted layout
&lt;a href=&quot;https://saylesss88.github.io/installation/unencrypted/unencrypted.html&quot;&gt;Click Here&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you choose to set up impermanence, ensure it matches your install. Encrypted
Setup with Encrypted Impermanence and Unencrypted Setup with Unencrypted
Impermanence.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;❗ NOTE: This is a bit convoluted, there are a few paths you can follow. If
you choose to use the starter repo (&lt;a href=&quot;https://github.com/saylesss88/my-flake&quot;&gt;https://github.com/saylesss88/my-flake&lt;/a&gt;)
just follow the included README and use this for reference.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;What does LUKS Encryption Protect?&lt;/h2&gt;
&lt;p&gt;It’s important to understand what disk encryption protects and what it doesn’t
protect so you don’t have any misconceptions about how safe your data is.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://wiki.nixos.org/wiki/Full_Disk_Encryption&quot;&gt;NixOS Wiki FDE&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://wiki.archlinux.org/title/Data-at-rest_encryption&quot;&gt;Arch Wiki Data-at-rest encryption&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://0pointer.net/blog/authenticated-boot-and-disk-encryption-on-linux.html&quot;&gt;Authenticated Booot and DE on Linux&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://oddlama.org/blog/bypassing-disk-encryption-with-tpm2-unlock/&quot;&gt;Bypassing FDE with TPM2 Unlock&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;What LUKS Protects&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Data Confidentiality at Rest&lt;/strong&gt;: LUKS encrypts entire block devices (such as
disk partitions or whole drives), ensuring that all data stored on the
encrypted device is unreadable without the correct decryption key or
passphrase. This protects sensitive information from unauthorized access if
the device is lost, stolen, or physically accessed by an attacker.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Physical Security&lt;/strong&gt;: If someone gains physical possession of your device
(for example, by stealing your laptop or removing a hard drive), LUKS ensures
the data remains inaccessible and appears as random, meaningless bytes without
the correct credentials.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Protection Against Offline Attacks&lt;/strong&gt;: LUKS defends against attackers who
attempt to bypass the operating system by booting from another device or
removing the drive and mounting it elsewhere. Without the decryption key, the
data remains protected.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;What LUKS Does Not Protect&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Data in Use&lt;/strong&gt;: Once the system is booted and the encrypted device is
unlocked, the data becomes accessible to the operating system and any user or
process with the necessary permissions. LUKS does not protect against attacks
on a running system, such as malware, remote exploits, or unauthorized users
with access to an unlocked session.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;File-Level Access Control&lt;/strong&gt;: LUKS encrypts entire partitions or disks, not
individual files or directories. It does not provide granular file-level
encryption or access control within the operating system.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Network Attacks&lt;/strong&gt;: LUKS only protects data stored on disk. It does not
encrypt data transmitted over networks or protect against network-based
attacks.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Bootloader and EFI Partitions&lt;/strong&gt;: The initial bootloader or EFI system
partition cannot be encrypted with LUKS, so some parts of the boot process may
remain exposed unless additional measures are taken. (i.e., Secure Boot,
additional passwords, TPM2)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To Sum it Up: LUKS encryption protects the confidentiality of all data stored on
an encrypted block device by making it unreadable without the correct passphrase
or key. This ensures that, if your device is lost or stolen, your data remains
secure and inaccessible to unauthorized users. However, LUKS does not protect
data once the system is unlocked and running, nor does it provide file-level
encryption or protect against malware and network attacks. For comprehensive
security, LUKS should be combined with strong access controls and other security
best practices.&lt;/p&gt;
&lt;h2&gt;The Install&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Get the
&lt;a href=&quot;https://channels.nixos.org/nixos-25.05/latest-nixos-minimal-x86_64-linux.iso&quot;&gt;Nixos Minimal ISO&lt;/a&gt;
Get it on a usb stick, I use Ventoy with Ventoy2Disk.sh. The following is the
link to the
&lt;a href=&quot;https://sourceforge.net/projects/ventoy/files/v1.1.05/ventoy-1.1.05-linux.tar.gz/download&quot;&gt;Ventoy TarBall&lt;/a&gt;
download, untar it with &lt;code&gt;tar -xzf ventoy-1.1.05-linux.tar.gz&lt;/code&gt;, and make it
executable with &lt;code&gt;chmod +x Ventoy2Disk.sh&lt;/code&gt;, and finally execute it with
&lt;code&gt;sudo bash Ventoy2Disk.sh&lt;/code&gt; Follow the prompts to finish the install.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Configuring Networking&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The minimal installer uses &lt;code&gt;wpa_supplicant&lt;/code&gt; instead of NetworkManager. Choose
one of the following methods to enable networking:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo systemctl start wpa_supplicant
wpa_cli
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Option A: Interactive &lt;code&gt;wpa_cli&lt;/code&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;&amp;gt; add_network
0

&amp;gt; set_network 0 ssid &quot;myhomenetwork&quot;
OK

&amp;gt; set_network 0 psk &quot;mypassword&quot;
OK

&amp;gt; enable_network 0
OK
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To exit type &lt;code&gt;quit&lt;/code&gt;, then check your connection with &lt;code&gt;ping google.com&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Option B: Non-Interactive &lt;code&gt;wpa_passphrase&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;This method is quicker for known networks and persists the configuration for the
live environment.&lt;/p&gt;
&lt;p&gt;First, identify your wireless interface name (e.g., &lt;code&gt;wlan0&lt;/code&gt;) using &lt;code&gt;ip a&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo systemctl start wpa_supplicant # Ensure wpa_supplicant is running
# This command generates the config and appends it to a file specific to wlan0
sudo wpa_passphrase &quot;myhomenetwork&quot; &quot;mypassword&quot; | sudo tee /etc/wpa_supplicant/wpa_supplicant-wlan0.conf
sudo systemctl restart wpa_supplicant@wlan0.service
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After either method, exit &lt;code&gt;wpa_cli&lt;/code&gt; with &lt;code&gt;quit&lt;/code&gt;. Then test your connection:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ping 1.1.1.1
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;Get your Disk Name with &lt;code&gt;lsblk&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The output should be something like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;NAME        MAJ:MIN RM   SIZE RO TYPE MOUNTPOINTS
nvme0n1     259:0    0   1,8T  0 disk
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;❗ From here, you can either&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;Copy the disk configuration to your machine. You can choose one from the
&lt;a href=&quot;https://github.com/nix-community/disko/tree/master/example&quot;&gt;examples directory&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;There is still a starter repo that can save you some typing, make sure to
carefully review if you decide to use it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;export NIX_CONFIG=&apos;experimental-features = nix-command flakes&apos;
export EDITOR=&apos;hx&apos; # or &apos;vi&apos;
nix-shell -p git yazi helix mkpasswd
git config --global user.name &quot;gitUsername&quot;
git config --global user.email &quot;gitEmail&quot;
# OPTIONAL starter repo containing disk-config set up for impermanence
git clone https://github.com/saylesss88/my-flake.git
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I prefer &lt;code&gt;helix&lt;/code&gt; here as it’s defaults are great. (i.e., auto closing brackets
and much more)&lt;/p&gt;
&lt;p&gt;If you choose to use the starter repo you won’t need to run the next command as
it is already populated in the repo and should use the
&lt;a href=&quot;https://github.com/saylesss88/my-flake&quot;&gt;Starter Repo README&lt;/a&gt; most of the rest
of the guide is for manual disko without the starter repo.&lt;/p&gt;
&lt;p&gt;If you click on the layout you want then click the &lt;code&gt;Raw&lt;/code&gt; button near the top,
then copy the &lt;code&gt;url&lt;/code&gt; and use it in the following command:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd /tmp
curl https://raw.githubusercontent.com/nix-community/disko/refs/heads/master/example/luks-btrfs-subvolumes.nix -o /tmp/disk-config.nix
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The above curl command is to the &lt;code&gt;luks-btrfs-subvolumes.nix&lt;/code&gt; layout.&lt;/p&gt;
&lt;ol start=&quot;5&quot;&gt;
&lt;li&gt;Make Necessary changes, I prepared mine for impermanence with the following:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;hx /tmp/disk-config.nix
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Make sure you identify your system disk name with &lt;code&gt;lsblk&lt;/code&gt; and change the
&lt;code&gt;device&lt;/code&gt; attribute below to match your disk.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;lsblk
nvme0n1       259:0    0 476.9G  0 disk
├─nvme0n1p1   259:1    0   512M  0 part  /boot
└─nvme0n1p2   259:2    0 476.4G  0 part
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;My disk is &lt;code&gt;nvme0n1&lt;/code&gt;, change below to match yours:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{
  disko.devices = {
    disk = {
      nvme0n1 = {
        type = &quot;disk&quot;;
        # Make sure this is correct with `lsblk`
        device = &quot;/dev/nvme0n1&quot;;
        content = {
          type = &quot;gpt&quot;;
          partitions = {
            ESP = {
              label = &quot;boot&quot;;
              name = &quot;ESP&quot;;
              size = &quot;1G&quot;;
              type = &quot;EF00&quot;;
              content = {
                type = &quot;filesystem&quot;;
                format = &quot;vfat&quot;;
                mountpoint = &quot;/boot&quot;;
                mountOptions = [
                  &quot;defaults&quot;
                ];
              };
            };
            luks = {
              size = &quot;100%&quot;;
              label = &quot;luks&quot;;
              content = {
                type = &quot;luks&quot;;
                name = &quot;cryptroot&quot;;
                content = {
                  type = &quot;btrfs&quot;;
                  extraArgs = [&quot;-L&quot; &quot;nixos&quot; &quot;-f&quot;];
                  subvolumes = {
                    &quot;/root&quot; = {
                      mountpoint = &quot;/&quot;;
                      mountOptions = [&quot;subvol=root&quot; &quot;compress=zstd&quot; &quot;noatime&quot;];
                    };
                    &quot;/root-blank&quot; = {
                      mountOptions = [&quot;subvol=root-blank&quot; &quot;nodatacow&quot; &quot;noatime&quot;];
                    };
                    &quot;/home&quot; = {
                      mountpoint = &quot;/home&quot;;
                      mountOptions = [&quot;subvol=home&quot; &quot;compress=zstd&quot; &quot;noatime&quot;];
                    };
                    &quot;/nix&quot; = {
                      mountpoint = &quot;/nix&quot;;
                      mountOptions = [&quot;subvol=nix&quot; &quot;compress=zstd&quot; &quot;noatime&quot;];
                    };
                    &quot;/persist&quot; = {
                      mountpoint = &quot;/persist&quot;;
                      mountOptions = [&quot;subvol=persist&quot; &quot;compress=zstd&quot; &quot;noatime&quot;];
                    };
                    &quot;/log&quot; = {
                      mountpoint = &quot;/var/log&quot;;
                      mountOptions = [&quot;subvol=log&quot; &quot;compress=zstd&quot; &quot;noatime&quot;];
                    };
                    &quot;/lib&quot; = {
                      mountpoint = &quot;/var/lib&quot;;
                      mountOptions = [&quot;subvol=lib&quot; &quot;compress=zstd&quot; &quot;noatime&quot;];
                    };
                    &quot;/persist/swap&quot; = {
                      mountpoint = &quot;/persist/swap&quot;;
                      mountOptions = [&quot;subvol=swap&quot; &quot;noatime&quot; &quot;nodatacow&quot; &quot;compress=no&quot;];
                      swap.swapfile.size = &quot;18G&quot;;
                    };
                  };
                };
              };
            };
          };
        };
      };
    };
  };

  fileSystems.&quot;/persist&quot;.neededForBoot = true;
  fileSystems.&quot;/var/log&quot;.neededForBoot = true;
  fileSystems.&quot;/var/lib&quot;.neededForBoot = true;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I have 16G of RAM so to be safe for hibernation I chose to give it some extra
space. The boot partition is 1G, this extra space is for specialisations and
lanzaboote.&lt;/p&gt;
&lt;p&gt;or for a swapfile:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;swapDevices = [
  {
    device = &quot;/persist/swap/swapfile&quot;;
    size = 18 * 1024; # Size in MB (18GB)
    # or
    # size = 16384; # Size in MB (16G);
  }
];
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Setting up zram and /tmp on RAM&lt;/h2&gt;
&lt;p&gt;While &lt;code&gt;/tmp&lt;/code&gt; is handled by &lt;code&gt;tmpfs&lt;/code&gt; (as shown the below &lt;code&gt;configuration.nix&lt;/code&gt;), you
can further enhance memory efficiency with &lt;code&gt;zram&lt;/code&gt; for compressed swap, as shown
below.&lt;/p&gt;
&lt;blockquote&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{
  lib,
  config,
  ...
}: let
  cfg = config.custom.zram;
in {
  options.custom.zram = {
    enable = lib.mkEnableOption &quot;Enable utils module&quot;;
  };

  config = lib.mkIf cfg.enable {
    zramSwap = {
      enable = true;
      # one of &quot;lzo&quot;, &quot;lz4&quot;, &quot;zstd&quot;
      algorithm = &quot;zstd&quot;;
       priority = 5;
       memoryPercent = 50;
    };
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And in your &lt;code&gt;configuration.nix&lt;/code&gt; you would add:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# configuration.nix
custom = {
    zram.enable = true;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;p&gt;After adding the above module and rebuilding, you can see it with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;swapon --show
NAME       TYPE      SIZE USED PRIO
/dev/zram0 partition 7.5G   0B    5
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;6&quot;&gt;
&lt;li&gt;Run disko to partition, format and mount your disks. &lt;strong&gt;Warning&lt;/strong&gt; this will
wipe &lt;strong&gt;EVERYTHING&lt;/strong&gt; on your disk. Disko doesn’t work with dual boot.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo nix --experimental-features &quot;nix-command flakes&quot; run github:nix-community/disko/latest -- --mode destroy,format,mount /tmp/disk-config.nix
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Check it with the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mount | grep /mnt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The output for an &lt;code&gt;nvme0n1&lt;/code&gt; disk would be similar to the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#... snip ...
/dev/nvme0n1p2 on /mnt type btrfs (rw,noatime,compress=zstd:3,ssd,discard=async,space_cache=v2,subvolid=285,subvol=/root)
/dev/nvme0n1p2 on /mnt/persist type btrfs (rw,noatime,compress=zstd:3,ssd,discard=async,space_cache=v2,subvolid=261,subvol=/persist)
/dev/nvme0n1p2 on /mnt/etc type btrfs (rw,noatime,compress=zstd:3,ssd,discard=async,space_cache=v2,subvolid=261,subvol=/persist)
/dev/nvme0n1p2 on /mnt/nix type btrfs (rw,noatime,compress=zstd:3,ssd,discard=async,space_cache=v2,subvolid=260,subvol=/nix)
/dev/nvme0n1p2 on /mnt/var/lib type btrfs (rw,noatime,compress=zstd:3,ssd,discard=async,space_cache=v2,subvolid=258,subvol=/lib)
/dev/nvme0n1p2 on /mnt/var/log type btrfs (rw,noatime,compress=zstd:3,ssd,discard=async,space_cache=v2,subvolid=259,subvol=/log)
/dev/nvme0n1p2 on /mnt/nix/store type btrfs (ro,noatime,compress=zstd:3,ssd,discard=async,space_cache=v2,subvolid=260,subvol=/nix)
# ... snip ...
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;7&quot;&gt;
&lt;li&gt;Generate necessary files, here we use &lt;code&gt;--no-filesystems&lt;/code&gt; because disko
handles the &lt;code&gt;fileSystems&lt;/code&gt; attribute for us.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nixos-generate-config --no-filesystems --root /mnt
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;The above command will place a &lt;code&gt;configuration.nix&lt;/code&gt; and
&lt;code&gt;hardware-configuration.nix&lt;/code&gt; in &lt;code&gt;/mnt/etc/nixos/&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It may be helpful to add a couple things to your &lt;code&gt;configuration.nix&lt;/code&gt; now, while
it’s in its default location. You can just add what you want and rebuild once
with &lt;code&gt;sudo nixos-rebuild switch&lt;/code&gt; and move on. (i.e. &lt;code&gt;git&lt;/code&gt;, an editor, etc.).&lt;/p&gt;
&lt;h3&gt;Setting a Flake for your minimal Install&lt;/h3&gt;
&lt;ol start=&quot;8&quot;&gt;
&lt;li&gt;Create the flake in your home directory to avoid needing to use sudo for
every command:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd   # Move to home directory
mkdir flake
cd /mnt/etc/nixos/
sudo mv hardware-configuration.nix configuration.nix ~/flake/
sudo mv /tmp/disk-config.nix ~/flake/
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd flake
hx flake.nix
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;You’ll change &lt;code&gt;hostName = nixpkgs.lib.nixosSystem&lt;/code&gt; to your chosen hostname,
(e.g. &lt;code&gt;magic = nixpkgs.lib.nixosSystem&lt;/code&gt;). This will be the same as your
&lt;code&gt;networking.hostName = &quot;magic&quot;;&lt;/code&gt; in your &lt;code&gt;configuration.nix&lt;/code&gt; that we will set
up shortly.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# flake.nix
{
  description = &quot;NixOS configuration&quot;;

  inputs = {
    nixpkgs.url = &quot;github:nixos/nixpkgs/nixos-unstable&quot;;
    disko.url = &quot;github:nix-community/disko/latest&quot;;
    disko.inputs.nixpkgs.follows = &quot;nixpkgs&quot;;
    # impermanence.url = &quot;github:nix-community/impermanence&quot;;
  };

  outputs = inputs@{ nixpkgs, ... }: {
    nixosConfigurations = {
      # Change `hostName` to your chosen host name
      nixos = nixpkgs.lib.nixosSystem {
        system = &quot;x86_64-linux&quot;;
        modules = [
          ./configuration.nix
          inputs.disko.nixosModules.disko
          # inputs.impermanence.nixosModules.impermanence
        ];
      };
    };
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;9&quot;&gt;
&lt;li&gt;Edit &lt;code&gt;configuration.nix&lt;/code&gt; with what is required, the following are required, I
clone my original flake repo and move the pieces into place but it’s fairly
easy to just type it all out:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Bootloader, (e.g., &lt;code&gt;boot.loader.systemd-boot.enable = true;&lt;/code&gt;)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;User, the example uses &lt;code&gt;username&lt;/code&gt; change this to your chosen username. If you
don’t set your hostname it will be &lt;code&gt;nixos&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Networking, &lt;code&gt;networking.networkmanager.enable = true;&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;hardware-configuration.nix&lt;/code&gt; &amp;amp; &lt;code&gt;disk-config.nix&lt;/code&gt; for this setup&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If you type this out by hand and mess up a single character, you will have to
start over completely. A fairly safe way to do this is with &lt;code&gt;vim&lt;/code&gt; or &lt;code&gt;hx&lt;/code&gt; and
redirect the hashed pass to a &lt;code&gt;/tmp/pass.txt&lt;/code&gt;, you can then read it into your
&lt;code&gt;users.nix&lt;/code&gt;:&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mkpasswd --method=yescrypt &amp;gt; /tmp/pass.txt
# Enter your chosen password
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And then when inside &lt;code&gt;configuration.nix&lt;/code&gt;, move to the line where you want the
hashed password and type &lt;code&gt;:r /tmp/pass.txt&lt;/code&gt; to read the hash into your current
file.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# configuration.nix
{
  config,
  lib,
  pkgs,
  inputs,
  ...
}: {
  imports = [
    # Include the results of the hardware scan.
    ./hardware-configuration.nix
    ./disk-config.nix
  ];

  # systemd Stage 1: if enabled, it handles unlocking of LUKS-encrypted volumes during boot.
    boot.initrd.luks.devices = {
    cryptroot = {
      device = &quot;/dev/disk/by-partlabel/luks&quot;;
      allowDiscards = true;
    };
  };

  # This complements using zram, putting /tmp on RAM
    boot = {
    tmp = {
      useTmpfs = true;
      tmpfsSize = &quot;50%&quot;;
    };
  };

  # Enable autoScrub for btrfs
    services.btrfs.autoScrub = {
    enable = true;
    interval = &quot;weekly&quot;;
    fileSystems = [&quot;/&quot;];
  };


  # Change me!
  networking.hostName = &quot;nixos&quot;; # This will match the `hostname` of your flake

  networking.networkmanager.enable = true;

  boot.loader.systemd-boot.enable = true; # (for UEFI systems only)
  # List packages installed in system profile.
  # You can use https://search.nixos.org/ to find more packages (and options).
  environment.systemPackages = with pkgs; [
    vim # Do not forget to add an editor to edit configuration.nix! The Nano editor is also installed by default.
    #   wget
    git
  ];

  time.timeZone = &quot;America/New_York&quot;;

# Change me to your chosen username (i.e. change nixosUser to your username)
  users.users.nixosUser = {
    isNormalUser = true;
    extraGroups = [ &quot;wheel&quot; &quot;networkmanager&quot; ]; # Add &quot;wheel&quot; for sudo access
    initialHashedPassword = &quot;READ_MKPASSWD_OUTPUT_HERE&quot;; # &amp;lt;-- This is where it goes!
    # home = &quot;/home/nixos&quot;; # Optional: Disko typically handles home subvolumes
  };
  # Change me to match your chosen username
  users.group.nixosUser = {};

  console.keyMap = &quot;us&quot;;

  nixpkgs.config.allowUnfree = true;

  system.stateVersion = &quot;25.05&quot;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Although, just adding the &lt;code&gt;disk-config.nix&lt;/code&gt; works for prompting you for your
encryption passphrase adding the following is a more robust way of ensuring Nix
is aware of this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;    boot.initrd.luks.devices = {
    cryptroot = {
      device = &quot;/dev/disk/by-partlabel/luks&quot;;
      allowDiscards = true;
    };
  };
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;10&quot;&gt;
&lt;li&gt;Move the flake to &lt;code&gt;/mnt/etc/nixos&lt;/code&gt; and run &lt;code&gt;nixos-install&lt;/code&gt;:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo mv ~/flake /mnt/etc/nixos/
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Give everything a quick once over, insuring your host is set in both your
&lt;code&gt;flake.nix&lt;/code&gt;, and &lt;code&gt;configuration.nix&lt;/code&gt;. Ensure you changed the username in the
&lt;code&gt;configuration.nix&lt;/code&gt; from &lt;code&gt;nixos&lt;/code&gt; to your chosen name, this is the name you’ll
use to login after you enter your encryption passphrase.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The below command uses &lt;code&gt;#nixos&lt;/code&gt; because that’s what the defaults are, you’ll
change it to your chosen hostname.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo nixos-install --flake /mnt/etc/nixos/flake#nixos
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;You will be prompted to enter a new password if everything succeeds.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Create a Blank Snapshot of /root&lt;/h2&gt;
&lt;p&gt;This is essential if you plan on using impermanence with this encrypted setup.
We take a snapshot of &lt;code&gt;/root&lt;/code&gt; while it’s a clean slate, right after we run disko
to format the disk.&lt;/p&gt;
&lt;p&gt;To access all of the subvolumes, we have to mount the Btrfs partitions
top-level.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Unlock the LUKS device, if not already unlocked as it should be from running
disko:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo cryptsetup open /dev/disk/by-partlabel/luks cryptroot
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;Mount the Btrfs top-level (&lt;code&gt;subvolid=5&lt;/code&gt;):&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo mount -o subvolid=5 /dev/mapper/cryptroot /mnt
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;List the contents:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ls /mnt
# you should see something like
root   home  nix  persist  log  lib  ...
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;Now we can take a snapshot of the &lt;code&gt;root&lt;/code&gt; subvolume:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo btrfs subvolume snapshot -r /mnt/root /mnt/root-blank
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;5&quot;&gt;
&lt;li&gt;Verify Your Blank Snapshot:&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Before continuing, make sure your blank snapshot exists. This is crucial for
impermanence to work properly.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo btrfs subvolume list /mnt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You should see output containing both &lt;code&gt;root&lt;/code&gt; and &lt;code&gt;root-blank&lt;/code&gt; subvolumes:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ID 256 gen ... path root
ID 257 gen ... path root-blank
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Check that the snapshot is read only, this ensures that our snapshot will remain
the same as the day we took it. It was set &lt;code&gt;ro&lt;/code&gt; in disko but lets check anyways:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo btrfs property get -ts /mnt/root-blank
# output should be
ro=true
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;5&quot;&gt;
&lt;li&gt;Make sure to unmount:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo umount /mnt
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;If everything checks out, reboot the system and you should be prompted to
enter your &lt;code&gt;user&lt;/code&gt; and &lt;code&gt;password&lt;/code&gt; to login to a shell to get started.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The flake will be placed at &lt;code&gt;/etc/nixos/flake&lt;/code&gt; after the install and reboot, I
choose to move it to my home directory. Since the file was first in &lt;code&gt;/etc&lt;/code&gt;
you’ll need to adjust the permissions with something like
&lt;code&gt;sudo chown -R $USER:$USER ~/flake&lt;/code&gt; and then you can work on it without
privilege escalation. This requires that you create a group for your user as
done in the &lt;code&gt;configuration.nix&lt;/code&gt; above.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You can check the layout of your btrfs system with:&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo btrfs subvolume list /
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Persisting Critical System State&lt;/h2&gt;
&lt;p&gt;The following is a one time operation, we’re just getting it out of the way now.
This moves all of the important system state to a persistant location, further
preparing for impermanence.&lt;/p&gt;
&lt;p&gt;It’s essential that you have first run the &lt;code&gt;nixos-install&lt;/code&gt; command to populate
these directories before copying them over.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo mkdir -p /mnt/persist/etc
sudo mkdir -p /mnt/persist/var/lib
sudo mkdir -p /mnt/persist/var/log
sudo mkdir -p /mnt/persist/home
sudo mkdir -p /mnt/persist/root
sudo cp -a /mnt/etc/. /mnt/persist/etc/
sudo cp -a /mnt/var/lib/. /mnt/persist/var/lib
sudo cp -a /mnt/var/log/. /mnt/persist/var/log
sudo cp -a /mnt/home/. /mnt/persist/home/
sudo cp -a /mnt/root/. /mnt/persist/root/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since we are in a live environment, after the install and reboot the &lt;code&gt;/mnt&lt;/code&gt;
prefix will be removed.&lt;/p&gt;
&lt;h2&gt;Reboot&lt;/h2&gt;
&lt;p&gt;Now that everything is done, we can safely reboot and ensure that our LUKS
password/passphrase is accepted as well as our userlevel password and username.&lt;/p&gt;
&lt;p&gt;After reboot, you can continue to setup
&lt;a href=&quot;https://saylesss88.github.io/installation/enc/sops-nix.html&quot;&gt;Sops Encrypted Secrets&lt;/a&gt;
and
&lt;a href=&quot;https://saylesss88.github.io/installation/enc/lanzaboote.html&quot;&gt;Lanzaboote Secure Boot&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;To set up impermanence for this specific layout, follow the link
&lt;a href=&quot;https://saylesss88.github.io/installation/enc/encrypted_impermanence.html&quot;&gt;Encrypted Impermanence&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://btrfs.readthedocs.io/en/latest/Subvolumes.html&quot;&gt;BTRFS Subvolumes&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.freedesktop.org/software/systemd/man/latest/systemd-cryptenroll.html&quot;&gt;systemd-cryptenroll man page&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://uapi-group.org/specifications/specs/linux_tpm_pcr_registry/&quot;&gt;Linux TPM PCR Registry&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://oddlama.org/blog/bypassing-disk-encryption-with-tpm2-unlock/&quot;&gt;Bypassing FDE with TPM2&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content></entry><entry><title>USB Keyfile</title><id>https://saylesss88.github.io/installation/enc/USB_keyfile.html</id><updated>2025-11-22T00:00:00+00:00</updated><author><name>saylesss87@proton.me (saylesss88)</name></author><link href="https://saylesss88.github.io/installation/enc/USB_keyfile.html" rel="alternate"/><content type="html">&lt;h1&gt;USB Stick Keyfile&lt;/h1&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Click to Expand Table of Contents&lt;/summary&gt;
&lt;!-- toc --&gt;
&lt;/details&gt;
&lt;p&gt;This allows you to use a USB stick for your keyfile, with a backup in case you
want or need it. There is a setting &lt;code&gt;fallbackToPassword&lt;/code&gt; that protects you in
case something fails with the USB key.&lt;/p&gt;
&lt;p&gt;First, I’ll show how to set up a dedicated USB stick for a keyfile. (i.e., one
that is only used for this). After that I will show the process of adding the
keyfile to a USB stick with existing data on it that you don’t want to lose.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Generate the keyfile&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo dd if=/dev/urandom of=/root/usb-luks.key bs=4096 count=1
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Keyfile Enrollment Methods&lt;/h2&gt;
&lt;p&gt;This is for a dedicated USB stick that we will wipe first then add the key.&lt;/p&gt;
&lt;p&gt;Disko defaults to LUKS2&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# cryptsetup works for both LUKS1 and LUKS2 formats but doesn&apos;t work for
# TPM2, FIDO2, and smartcards
sudo cryptsetup luksAddKey /dev/disk/by-partlabel/luks /root/usb-luks.key
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;OR&lt;/strong&gt;&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Click to expand Experimental TPM2 auto-unlock for LUKS &lt;/summary&gt;
&lt;blockquote&gt;
&lt;p&gt;⚠️ WARNING: Security Implications of TPM2 Auto-Unlock&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Enabling TPM2 auto-unlock fundamentally changes your system’s security model.
While this feature protects against certain forms of malicious software
injection by tying the decryption key to the system’s boot state, it
eliminates the need for a user password at boot. This creates a significant
risk if your machine is stolen or seized, do not use this feature if the
physical security of your machine is a concern. This is still at a stage where
you can expect rough edges and workarounds.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;⚠️ WARNING: Do NOT use TPM auto-unlock if your CPU is vulnerable to faulTPM!
All AMD Zen2 and Zen3 Processors are known to be affected with AMD Zen1 likely
also affected and Zen4 unknown! Misconfigurations are also common, do your own
research!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://ieeexplore.ieee.org/document/10190531&quot;&gt;faulTPM:Exposing AMD fTPMs’ Deepest Secrets&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.techpowerup.com/308124/amd-faultpm-exploit-targets-zen-2-and-zen-3-processors&quot;&gt;AMD faulTPM Exploit Targets Zen 2 and Zen 3 Processors&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can add an additional layer by encrypting user data, such as individual home
folders, with a different mechanism, such as &lt;code&gt;fscrypt-experimental&lt;/code&gt; or
&lt;code&gt;systemd-homed&lt;/code&gt;. Or, you can use a TPM pin to benefit from the security
properties of the TPM, while avoiding completely unattended unlocking.
–&lt;a href=&quot;https://wiki.archlinux.org/title/Trusted_Platform_Module&quot;&gt;Arch Wiki&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I am reading that &lt;code&gt;fscrypt&lt;/code&gt; is no longer experimental.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;security.pam.enableFscrypt = true;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo fscrypt setup --all-users
sudo mv /home/&amp;lt;user&amp;gt; /home/old&amp;lt;user&amp;gt;
sudo mkdir /home/&amp;lt;user&amp;gt;
sudo chown &amp;lt;user&amp;gt;:users /home/&amp;lt;user&amp;gt;
sudo fscrypt encrypt --source pam_passphrase --user &amp;lt;user&amp;gt; --skip-unlock /home/&amp;lt;user&amp;gt;/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;–☝️&lt;a href=&quot;https://discourse.nixos.org/t/experienced-with-systemd-homed-or-other-encrypted-home/63516/2&quot;&gt;Discourse&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;It is fairly complex as to how TPM2 auto-unlock can improve security in some
ways, it has to do with how Linux distributions fail to authenticate the boot
process past the initrd.Even with encryption and Secure Boot enabled, the initrd
stage often remains unverified, meaning a tampered initrd could be substituted
without detection.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://0pointer.net/blog/brave-new-trusted-boot-world.html&quot;&gt;Brave New Trusted Boot World&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;TPMs protect secrets by releasing them only if the boot process can be
authenticated through “measurements.” During boot, each component involved
(firmware, bootloader, kernel, etc.) is hashed, and these hashes are extended
into special TPM registers called Platform Configuration Registers (PCRs). These
PCRs hold a cumulative, tamper-evident record of the boot process state.&lt;/p&gt;
&lt;p&gt;If any part of the boot sequence changes (even slightly), the PCR values will
differ from the expected, causing the TPM to refuse to release the bound secret
(such as a disk decryption key). This ensures that the system only boots or
unlocks secrets when its software stack is known and trusted, providing strong
protection against tampering or unauthorized modifications. The values aren’t
only protected by these PCRs but encrypted with a “seed key” that’s generated on
the TPM chip itself, and cannot leave the TPM.&lt;/p&gt;
&lt;p&gt;Check TPM support:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cat /sys/class/tpm/tpm0/device/description
TPM 2.0 Device
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Check for necessary software dependencies:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;systemd-analyze has-tpm2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Find your encrypted partition with &lt;code&gt;lsblk&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;lsblk
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;First, you need to use the &lt;code&gt;systemd-cryptenroll&lt;/code&gt; command to add a TPM2 key to
your encrypted LUKS partition. This process binds a key slot on your disk to the
state of your TPM2 chip’s PCRs (Platform Configuration Registers).&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# This command adds a new key to the LUKS volume, using a key generated by the TPM2 chip.
# It binds the key to PCRs 0,2,7,and 15 ensuring the key is only released if the firmware
# and Secure Boot state of your system is unchanged.
sudo systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs=0+2+7+15 /dev/disk/by-partlabel/luks
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There are quite a few options for the above command, some use the following with
less pcrs and a wipe feature:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo systemd-cryptenroll --wipe-slot=tpm2 --tpm2-device=auto --tpm2-pcrs=0+7 /dev/disk/by-partlabel/luks
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Using less pcrs could prevent breakage but reduces security. Check out the PCR
Definitions below and decide if you require additional PCRs or less.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;wipe-slot&lt;/code&gt; tells the system to delete any key associated with the TPM2 chip
from the LUKS volume’s keyslot before adding a new one.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can choose a more complex &lt;code&gt;--tpm2-pcrs&lt;/code&gt; for more security but it makes the
configuration more fragile because any legitimate system update altering any
measured component tied to these PCRs will prevent the TPM from releasing the
key and lock you out, unless you re-enroll the key with the updated PCR values.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://uapi-group.org/specifications/specs/linux_tpm_pcr_registry/&quot;&gt;PCR Definitions&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://0pointer.net/blog/authenticated-boot-and-disk-encryption-on-linux.html&quot;&gt;Authenticated Boot and FDE&lt;/a&gt;
This article explains the limitations and remedies very well.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That said, I do often see people mention a firmware update breaking their TPM2
auto-unlock functionality. Keep this in mind and have a backup plan. This is
also incompatible with the encrypted impermanence setup shared in this book, the
&lt;code&gt;boot.initrd.postDeviceCommands&lt;/code&gt; conflict.&lt;/p&gt;
&lt;p&gt;Change &lt;code&gt;YourUser&lt;/code&gt; to your username and ensure that &lt;code&gt;cryptroot&lt;/code&gt; is the name of
yours, if you followed this books encrypted disko install it should be:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;  # Adds your user to the &apos;tss&apos; group, allowing you to interact with the TPM
  users.users.YourUser.extraGroups = [ &quot;tss&quot; ];
  # Enables TPM2 services and tools on your system
  security.tpm2.enable = true;
  # Ensure the necessary kernel modules are in the initrd
  boot.initrd.kernelModules = [&quot;tpm_tis&quot;];
  # switches the initrd to a systemd-based environment, required for TPM2
  boot.initrd.systemd.enable = true;
  # ❗ Tell the initrd to use the TPM2 key for the encrypted root
  boot.initrd.luks.devices.cryptroot = {
    device = &quot;/dev/disk/by-partlabel/luks&quot;;
    # These options tell systemd-cryptsetup to automatically try to unlock the device
    # using the TPM2 key. &apos;tpm2-measure=yes&apos; ensures the PCRs are verified but only works if you use one disk
    crypttabExtraOpts = [&quot;tpm2-device=auto&quot; &quot;tpm2-measure=yes&quot;];
    fallbackToPassword = true;
  };
  environment.systemPackages = [ pkgs.tpm2-tss ];
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;❗ NOTE: &lt;code&gt;cryptroot&lt;/code&gt; needs to match what your encrypted partition is named, I
have seen quite a few different names here.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If you use this, you can’t also use the USB Keyfile or the included impermanence
guide.&lt;/p&gt;
&lt;/details&gt;
&lt;p&gt;&lt;strong&gt;Description&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;/dev/disk/by-partlabel/luks&lt;/code&gt; refers to your encrypted partition by its
partition label, which is stable and less likely to change than
&lt;code&gt;/dev/nvme0n1p2&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;/root/usb-luks.key&lt;/code&gt; is the keyfile we generated.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You’ll be prompted to enter your existing LUKS passphrase to authorize adding
the new key.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Now our LUKS volume will accept both our existing passphrase and the new
keyfile (from the USB stick) for unlocking.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Clear Data on USB stick and replace with 0’s&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;lsblk
NAME        MAJ:MIN RM   SIZE RO TYPE MOUNTPOINTS
sda           8:0    1   239M  0 disk
sdb           8:16   1   1.4M  0 disk  /run/media/jr/7CD1-149A # Example USB mount
zram0       253:0    0   7.5G  0 disk  [SWAP]
nvme0n1     259:0    0 476.9G  0 disk
├─nvme0n1p1 259:1    0   512M  0 part  /boot
└─nvme0n1p2 259:2    0 476.4G  0 part
  └─cryptroot 254:0  0 476.4G  0 crypt /persist  # Main Btrfs mount
                                               # (other subvolumes are within /persist and bind-mounted by impermanence)
# unplug the device and run lsblk again so your sure
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;Before wiping you must unmount any mounted partitions:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo umount /dev/sda1
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Overwrite with Zeros (fast, sufficient for most uses)
sudo dd if=/dev/zero of=/dev/sda bs=4M status=progress
# Or overwrite with Random Data (More Secure, Slower)
sudo dd if=/dev/urandom of=/dev/sda bs=4M status=progress
# Or for the most secure way run multiple passes of
sudo shred -v -n 3 /dev/sda
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;Create a New Partition and Format (Optional)&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo fdisk /dev/sda
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Press &lt;code&gt;o&lt;/code&gt; to create a new empty DOS partition table (if you are creating
partitions on a fresh disk or want to wipe existing partitions and start
over). Be very careful with this step as it will erase all existing
partition information on the disk.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Press &lt;code&gt;n&lt;/code&gt; to create a new partition.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;You will then be prompted for the partition type:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;p&lt;/code&gt; for a primary partition (you can have up to 4 primary partitions)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;e&lt;/code&gt; for an extended partition (which can contain logical partitions)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Next, you’ll be asked for the partition number (e.g., 1, 2, 3, 4).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Then, you’ll be asked for the first sector (press Enter to accept the default,
which is usually the first available sector after the previous partition or
the beginning of the disk).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Finally, you’ll be asked for the last sector or size (you can specify a size
like +10G for 10 Gigabytes, +512M for 512 Megabytes, or press Enter to use the
rest of the available space).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;Press &lt;code&gt;w&lt;/code&gt; to write the changes to the partition table and exit fdisk.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;After pressing &lt;code&gt;w&lt;/code&gt;, the kernel needs to be aware of the new partition table.
Sometimes this happens automatically, but if you encounter issues, a reboot or a
command like &lt;code&gt;partprobe&lt;/code&gt; (if available and needed) can help.&lt;/p&gt;
&lt;p&gt;Formats as FAT32:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo mkfs.vfat /dev/sda1
# or as ext4
sudo mkfs.ext4 /dev/sda1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I chose &lt;code&gt;vfat&lt;/code&gt; so I ran &lt;code&gt;sudo mkfs.vfat /dev/sda1&lt;/code&gt;. In my case this changed the
device path to &lt;code&gt;/run/media/jr/7CD1-149A&lt;/code&gt; so it’s important to find your own UUID
with the following command:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo blkid /dev/sda1
/dev/sda1: SEC_TYPE=&quot;msdos&quot; UUID=&quot;B7B4-863B&quot; BLOCK_SIZE=&quot;512&quot; TYPE=&quot;vfat&quot; PARTUUID=&quot;7d1f9d7f-01&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;As you can see the above UUID is &lt;code&gt;&quot;B7B4-863B&quot;&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Remove and re-insert the USB stick, this ensures the system recognizes the new
partition and filesystem.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;Copy the keyfile to your USB Stick&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo cp /root/usb-luks.key /run/media/jr/B7B4-863B/
sync
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;5&quot;&gt;
&lt;li&gt;Update your NixOS Configuration&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Note the output of &lt;code&gt;blkid /dev/sda1&lt;/code&gt; and if you have a backup device list that
also:&lt;/p&gt;
&lt;p&gt;The following is from the wiki edited for my setup, it was created by Tzanko
Matev:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;let
  PRIMARYUSBID = &quot;B7B4-863B&quot;;
  BACKUPUSBID = &quot;Ventoy&quot;;
in {

  boot.initrd.kernelModules = [
    &quot;uas&quot;
    &quot;usbcore&quot;
    &quot;usb_storage&quot;
    &quot;vfat&quot;
    &quot;nls_cp437&quot;
    &quot;nls_iso8859_1&quot;
  ];

  boot.initrd.postDeviceCommands = lib.mkBefore &apos;&apos;
    mkdir -p /key
    sleep 2
    mount -n -t vfat -o ro $(findfs UUID=${PRIMARYUSBID}) /key || \
    mount -n -t vfat -o ro $(findfs UUID=${BACKUPUSBID}) /key || echo &quot;No USB key found&quot;
  &apos;&apos;;

  boot.initrd.luks.devices.cryptroot = {
    device = &quot;/dev/disk/by-partlabel/luks&quot;;
    keyFile = &quot;/key/usb-luks.key&quot;;
    fallbackToPassword = true;
    allowDiscards = true;
    preLVM = false; # Crucial!
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you have issues or just want to remove the key take note of the path used to
add it so you don’t have to enter the whole key:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo cryptsetup luksRemoveKey /dev/disk/by-partlabel/luks --key-file /root/usb-luks.key
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;6&quot;&gt;
&lt;li&gt;Securely Remove the Keyfile from Your System:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo shred --remove --zero /root/usb-luks.key
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Instructions for Using a USB Stick with Existing Data&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Generate the Keyfile&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo dd if=/dev/urandom of=/root/usb-luks.key bs=4096 count=1
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;Add the Keyfile to your LUKS Volume&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo cryptsetup luksAddKey /dev/disk/by-partlabel/luks /root/usb-luks.key
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(enter your existing passphrase when prompted)&lt;/p&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;Copy the Keyfile to the USB Stick&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Plug in the USB Stick and note its mount point
(e.g.,&lt;code&gt;/run/media/$USER/YourLabel&lt;/code&gt;)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Copy the keyfile:&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo cp /root/usb-luks.key /run/media/$USER/YourLabel/
sync
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;You run the above as 2 commands, the second being &lt;code&gt;sync&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You can rename it if you wish (e.g., &lt;code&gt;luks.key&lt;/code&gt;)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;Securely Delete the Local Keyfile&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo shred --remove --zero /root/usb-luks.key
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;You need to ensure the keyfile is accessible in the initrd. Since automounting
(like &lt;code&gt;/run/media/...&lt;/code&gt;) does not happen in &lt;code&gt;initrd&lt;/code&gt;, you must manually mount
the USB in the &lt;code&gt;initrd&lt;/code&gt; using its &lt;code&gt;UUID&lt;/code&gt; or label.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Find the USB Partition UUID:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;lsblk -o NAME,UUID
# or
blkid /dev/sda1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Suppose the UUID is &lt;code&gt;B7B4-863B&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Add to your &lt;code&gt;configuration.nix&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;boot.initrd.kernelModules = [ &quot;usb_storage&quot; &quot;vfat&quot; &quot;nls_cp437&quot; &quot;nls_iso8859_1&quot; ];

boot.initrd.postDeviceCommands = lib.mkBefore &apos;&apos;
  mkdir -p /key
  sleep 1
  mount -n -t vfat -o ro $(findfs UUID=B7B4-863B) /key || echo &quot;USB not found&quot;
&apos;&apos;;

boot.initrd.luks.devices.cryptroot = {
  device = &quot;/dev/disk/by-partlabel/luks&quot;;
  keyFile = &quot;/key/usb-luks.key&quot;; # or whatever you named it
  fallbackToPassword = true;
  allowDiscards = true;
};
&lt;/code&gt;&lt;/pre&gt;
</content></entry><entry><title>Encrypted BTRFS Impermanence</title><id>https://saylesss88.github.io/installation/enc/encrypted_impermanence.html</id><updated>2025-11-22T00:00:00+00:00</updated><author><name>saylesss87@proton.me (saylesss88)</name></author><link href="https://saylesss88.github.io/installation/enc/encrypted_impermanence.html" rel="alternate"/><content type="html">&lt;h1&gt;Encrypted Impermanence&lt;/h1&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Click to Expand Table of Contents&lt;/summary&gt;
&lt;!-- toc --&gt;
&lt;/details&gt;
&lt;blockquote&gt;
&lt;p&gt;❗ Important Note: This guide details a setup involving encrypted partitions
and impermanent NixOS. While powerful, such configurations require careful
attention to detail. Incorrect steps, especially concerning encryption keys or
persistent data paths, can lead to &lt;strong&gt;permanent data loss&lt;/strong&gt;. Please read all
instructions thoroughly before proceeding and consider backing up any critical
data beforehand. This has only been tested with the disk layout described in
&lt;a href=&quot;https://saylesss88.github.io/installation/encrypted_manual.html&quot;&gt;Encrypted Setups&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;As a system operates, it gradually accumulates state on its root partition. This
state is stored in various directories such as &lt;code&gt;/etc&lt;/code&gt; and &lt;code&gt;/var&lt;/code&gt;, capturing all
the configuration changes, logs, and other modifications—whether they’re
well-documented or the result of ad-hoc adjustments made while setting up and
running services.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Impermanence&lt;/strong&gt;,in the context of operating systems, refers to a setup where
the majority of the system’s root filesystem (&lt;code&gt;/&lt;/code&gt;) is reset to a pristine state
on every reboot. This means any changes made to the system (e.g., installing new
packages, modifying system files outside of configuration management, creating
temporary files) are discarded upon shutdown or reboot.&lt;/p&gt;
&lt;p&gt;Having an impermanent root and &lt;code&gt;/tmp&lt;/code&gt; has some security benefits as well. By
reducing your persistent footprint you reduce your chance of leaving behind
sensitive activity or data. Since Nix can boot with only &lt;code&gt;/nix&lt;/code&gt; and &lt;code&gt;/boot&lt;/code&gt;,
experienced users familiar with “stateless” systems can take advantage of this
smaller attack surface.&lt;/p&gt;
&lt;p&gt;Although this setup does not use &lt;code&gt;/tmp&lt;/code&gt; as the root filesystem, the root itself
is restored to its original state upon each reboot, as it was at installation.
However, by configuring &lt;code&gt;/tmp&lt;/code&gt; to reside in RAM, you ensure that temporary files
including sensitive data like passwords are stored only in volatile memory and
are automatically cleared on shutdown or reboot. This significantly enhances the
security of temporary data by preventing it from ever being written to disk.&lt;/p&gt;
&lt;h3&gt;Getting Started&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Add impermanence to your &lt;code&gt;flake.nix&lt;/code&gt;. You will change the &lt;code&gt;hostname&lt;/code&gt; in the
flake to match your &lt;code&gt;networking.hostName&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# flake.nix
{
  description = &quot;NixOS configuration&quot;;

  inputs = {
    nixpkgs.url = &quot;github:nixos/nixpkgs/nixos-unstable&quot;;
    disko.url = &quot;github:nix-community/disko/latest&quot;;
    disko.inputs.nixpkgs.follows = &quot;nixpkgs&quot;;
    impermanence.url = &quot;github:nix-community/impermanence&quot;;
  };

  outputs = inputs@{ nixpkgs, ... }: {
    nixosConfigurations = {
      hostname = nixpkgs.lib.nixosSystem {
        system = &quot;x86_64-linux&quot;;
        modules = [
          ./configuration.nix
          inputs.disko.nixosModules.disko
          inputs.impermanence.nixosModules.impermanence
        ];
      };
    };
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;Discover where your root subvolume is located with &lt;code&gt;findmnt&lt;/code&gt;:&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If you followed the
&lt;a href=&quot;https://saylesss88.github.io/installation/encrypted_manual.html&quot;&gt;Encrypted Setups&lt;/a&gt;
guide, your encrypted subvolume should be located at:
&lt;code&gt;/dev/mapper/cryptroot /mnt&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Your encrypted Btrfs partition, once unlocked by LUKS, will be available at
&lt;code&gt;/dev/mapper/cryptroot&lt;/code&gt; as configured here in the &lt;code&gt;disk-config.nix&lt;/code&gt;:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# disk-config2.nix
# ... snip ...
            luks = {
              size = &quot;100%&quot;;
              label = &quot;luks&quot;;
              content = {
                type = &quot;luks&quot;;
                name = &quot;cryptroot&quot;;
                content = {
# ... snip ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Double check that the paths exist:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd /dev/mapper/crypt&amp;lt;TAB&amp;gt;  # autocomplete should fill out /dev/mapper/cryptroot
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;Create an &lt;code&gt;impermanence.nix&lt;/code&gt;:&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now, create a new file named &lt;code&gt;impermanence.nix&lt;/code&gt; in your configuration directory
(i.e. your flake directory). This file will contain all the specific settings
for your impermanent setup, including BTRFS subvolume management and persistent
data locations. Since this file is right next to your &lt;code&gt;configuration.nix&lt;/code&gt;,
you’ll just add an &lt;code&gt;imports = [ ./impermanence.nix ]&lt;/code&gt; to your
&lt;code&gt;configuration.nix&lt;/code&gt; apply it to your configuration.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{
  config,
  lib,
  ...
}: {
  boot.initrd.postDeviceCommands = lib.mkAfter &apos;&apos;
    echo &quot;Rollback running&quot; &amp;gt; /mnt/rollback.log
     mkdir -p /mnt
     mount -t btrfs /dev/mapper/cryptroot /mnt

     # Recursively delete all nested subvolumes inside /mnt/root
     btrfs subvolume list -o /mnt/root | cut -f9 -d&apos; &apos; | while read subvolume; do
       echo &quot;Deleting /$subvolume subvolume...&quot; &amp;gt;&amp;gt; /mnt/rollback.log
       btrfs subvolume delete &quot;/mnt/$subvolume&quot;
     done

     echo &quot;Deleting /root subvolume...&quot; &amp;gt;&amp;gt; /mnt/rollback.log
     btrfs subvolume delete /mnt/root

     echo &quot;Restoring blank /root subvolume...&quot; &amp;gt;&amp;gt; /mnt/rollback.log
     btrfs subvolume snapshot /mnt/root-blank /mnt/root

     umount /mnt
  &apos;&apos;;

  environment.persistence.&quot;/persist&quot; = {
    directories = [
      &quot;/etc&quot;
      &quot;/var/spool&quot;
      &quot;/srv&quot;
      &quot;/etc/NetworkManager/system-connections&quot;
      &quot;/var/lib/bluetooth&quot;
    ];
    files = [
      # &quot;/etc/machine-id&quot;
      # Add more files you want to persist
    ];
  };

# optional quality of life setting
  security.sudo.extraConfig = &apos;&apos;
    Defaults lecture = never
  &apos;&apos;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/mnt/rollback.log&lt;/code&gt;: this log will be available during the boot process for
debugging if the rollback fails, but won’t persist.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With the above impermanence script, the btrfs subvolumes are deleted recursively
and replaced with the &lt;code&gt;root-blank&lt;/code&gt; snapshot we took during the install.&lt;/p&gt;
&lt;p&gt;I have commented out &lt;code&gt;&quot;/etc/machine-id&quot;&lt;/code&gt; because we already copied over all of
the files to their persistent location and the above setting would work once and
then cause a conflict.&lt;/p&gt;
&lt;h2&gt;configuration.nix changes&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# configuration.nix
  boot.initrd.luks.devices = {
    cryptroot = {
      device = &quot;/dev/disk/by-partlabel/luks&quot;;
      allowDiscards = true;
      preLVM = true;
    };
  };
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;This defines how your system’s initial ramdisk (&lt;code&gt;initrd&lt;/code&gt;) should handle a
specific encrypted disk during the boot process. It helps with timing and is a
more robust way of telling Nix that we are using an encrypted disk.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The following is optional to enable &lt;code&gt;autoScrub&lt;/code&gt; for btrfs, the wiki shows
&lt;code&gt;interval = &quot;monthly&quot;;&lt;/code&gt; FYI.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# configuration.nix
  services.btrfs.autoScrub = {
    enable = true;
    interval = &quot;weekly&quot;;
    fileSystems = [&quot;/&quot;];
  };
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Remember to ensure that your &lt;code&gt;hostname&lt;/code&gt; in your &lt;code&gt;configuration.nix&lt;/code&gt; matches
the &lt;code&gt;hostname&lt;/code&gt; in your &lt;code&gt;flake.nix&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Applying Your Impermanence Configuration&lt;/h3&gt;
&lt;p&gt;Once you have completed all the steps and created or modified the necessary
files (&lt;code&gt;flake.nix&lt;/code&gt;, &lt;code&gt;impermanence.nix&lt;/code&gt;), you need to apply these changes to your
NixOS system.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Navigate to your NixOS configuration directory (where your &lt;code&gt;flake.nix&lt;/code&gt; is
located).&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd /path/to/your/flake
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;Rebuild and Switch: Execute the &lt;code&gt;nixos-rebuild switch&lt;/code&gt; command. This command
will:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Evaluate your &lt;code&gt;flake.nix&lt;/code&gt; and the modules it imports (including your new
&lt;code&gt;impermanence.nix&lt;/code&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Build a new NixOS system closure based on your updated configuration.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Activate the new system configuration, making it the current running system.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo nixos-rebuild switch --flake .#hostname # Replace &apos;hostname&apos; with your actual system hostname
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;Perform an Impermanence Test (Before Reboot):&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;Before you reboot, create a temporary directory and file in a non-persistent
location. Since you haven’t explicitly added &lt;code&gt;/imperm_test&lt;/code&gt; to your
&lt;code&gt;environment.persistence.&quot;/persist&quot;&lt;/code&gt; directories, this file should not survive
a reboot.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mkdir /imperm_test
echo &quot;This should be Gone after Reboot&quot; | sudo tee /imperm_test/testfile
ls -l /imperm_test/testfile # Verify the file exists
cat /imperm_test/testfile # Verify content
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;Reboot Your System: For the impermanence setup to take full effect and for
your root filesystem to be reset for the first time, you must reboot your
machine.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo reboot
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;5&quot;&gt;
&lt;li&gt;Verify Impermanence (After Reboot):&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;After the system has rebooted, check if the test directory and file still
exist:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ls -l /imperm_test/testfile
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You should see an output like &lt;code&gt;ls: cannot access &apos;/imperm_test/testfile&apos;&lt;/code&gt;: No
such file or directory. This confirms that the &lt;code&gt;/imperm_test&lt;/code&gt; directory and its
contents were indeed ephemeral and were removed during the reboot process,
indicating your impermanence setup is working correctly!&lt;/p&gt;
&lt;p&gt;Your system should now come up with a fresh root filesystem, and only the data
specified in your &lt;code&gt;environment.persistence.&quot;/persist&quot;&lt;/code&gt; configuration will be
persistent.&lt;/p&gt;
</content></entry><entry><title>Sops-Nix</title><id>https://saylesss88.github.io/installation/enc/sops-nix.html</id><updated>2025-11-22T00:00:00+00:00</updated><author><name>saylesss87@proton.me (saylesss88)</name></author><link href="https://saylesss88.github.io/installation/enc/sops-nix.html" rel="alternate"/><content type="html">&lt;h1&gt;Sops-Nix encrypted secrets&lt;/h1&gt;
&lt;details&gt;
&lt;summary&gt; Click to Expand Table of Contents&lt;/summary&gt;
&lt;!-- toc --&gt;
&lt;/details&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/getsops/sops?ref=blog.gitguardian.com&quot;&gt;SOPS&lt;/a&gt;, short for
&lt;strong&gt;S&lt;/strong&gt;ecrets&lt;strong&gt;OP&lt;/strong&gt;eration&lt;strong&gt;S&lt;/strong&gt;, is an editor of encrypted files that supports
quite a few BINARY formats and encrypts with AWS KMS, GCP KMS, Azure Key Vault,
age, and PGP.&lt;/p&gt;
&lt;p&gt;Managing secrets—like API keys, SSH deploy keys, and password hashes is a
critical part of system configuration, but it’s also one of the trickiest to do
securely and reproducibly. Traditionally, secrets might be stored in ad hoc
locations, referenced by absolute paths, or managed manually outside of version
control. This approach makes it hard to share, rebuild, or audit your
configuration, and increases the risk of accidental leaks or inconsistencies
between systems.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;sops-nix&lt;/code&gt; solves these problems by integrating Mozilla SOPS directly into your
NixOS configuration. Instead of relying on hardcoded file paths or copying
secrets around, you declare your secrets in your Nix code, encrypt them with
strong keys, and let &lt;code&gt;sops-nix&lt;/code&gt; handle decryption and placement at activation
time.&lt;/p&gt;
&lt;p&gt;Encryption with strong keys, as used by sops-nix, makes brute force attacks
computationally unfeasible with current technology—the time and resources
required to try every possible key would be astronomically high. However, this
protection relies on using strong, secret keys and good security practices;
advances in technology or poor key management can weaken this defense.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;❗ &lt;strong&gt;CRITICAL SECURITY NOTE:&lt;/strong&gt; While the encryption itself is robust, this
protection fundamentally relies on using &lt;strong&gt;strong, secret keys&lt;/strong&gt; and
&lt;strong&gt;diligent security practices&lt;/strong&gt;. If your PGP passphrase is weak, your Age
private key is easily guessable, or the cleartext secret itself is very short
and has low entropy (e.g., “12345”, “true”, “admin”), an attacker might be
able to compromise your secrets regardless of the encryption.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;Add sops to your &lt;code&gt;flake.nix&lt;/code&gt;:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{
  inputs.sops-nix.url = &quot;github:Mic92/sops-nix&quot;;
  inputs.sops-nix.inputs.nixpkgs.follows = &quot;nixpkgs&quot;;

  outputs = { self, nixpkgs, sops-nix }: {
    # change `yourhostname` to your actual hostname
    nixosConfigurations.yourhostname = nixpkgs.lib.nixosSystem {
      # customize to your system
      system = &quot;x86_64-linux&quot;;
      modules = [
        ./configuration.nix
        sops-nix.nixosModules.sops
      ];
    };
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;Add &lt;code&gt;sops&lt;/code&gt; and &lt;code&gt;age&lt;/code&gt; to your &lt;code&gt;environment.systemPackages&lt;/code&gt;:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;environment.systemPackages = [
    pkgs.sops
    pkgs.age
];
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;Generate a key (This is your &lt;strong&gt;private key&lt;/strong&gt; and &lt;strong&gt;MUST NEVER BE COMMITTED TO
GIT OR SHARED&lt;/strong&gt;):&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mkdir -p ~/.config/sops/age
age-keygen -o ~/.config/sops/age/keys.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To get the Public Keys Value, run the following command:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;age-keygen -y ~/.config/sops/age/keys.txt
age12zlz6lvcdk6eqaewfylg35w0syh58sm7gh53q5vvn7hd7c6nngyseftjxl
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Copy the &lt;code&gt;age&lt;/code&gt; value it gives you back.&lt;/p&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;Create a &lt;code&gt;.sops.yaml&lt;/code&gt; in the same directory as your &lt;code&gt;flake.nix&lt;/code&gt;:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;# .sops.yaml
keys:
  # Your personal age public key (from age-keygen -y ~/.config/sops/age/keys.txt)
  - &amp;amp;personal_age_key age12zlz6lvcdk6eqaewfylg35w0syh58sm7gh53q5vvn7hd7c6nngyseftjxl

  # You can also use PGP keys if you prefer, but age is often simpler
  # - &amp;amp;personal_pgp_key 0xDEADBEEFCAFE0123

creation_rules:
  # This rule applies to any file named &apos;secrets.yaml&apos; directly in the &apos;secrets/&apos; directory
  # or &apos;secrets/github-deploy-key.yaml&apos; etc.
  - path_regex: &quot;secrets/.*\\.yaml$&quot;
    key_groups:
      - age:
          - *personal_age_key
        # Add host keys for decryption on the target system
        # sops-nix will automatically pick up the system&apos;s SSH host keys
        # as decryption keys if enabled in your NixOS config.
        # So you typically don&apos;t list them explicitly here unless you
        # want to restrict it to specific fingerprints, which is rare.
        # This part ensures your *personal* key can decrypt it.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Save it and move on, this file and &lt;code&gt;sops.nix&lt;/code&gt; are safe to version control.&lt;/p&gt;
&lt;ol start=&quot;5&quot;&gt;
&lt;li&gt;sops-nix’s automatic decryption feature using system SSH host keys only works
with ed25519 host keys for deriving Age decryption keys. Therefore, for
system decryption, ensure your using ed25519 not rsa keys:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ssh-keygen -t ed25519 -C &quot;your_email@example.com&quot;
# for multiple keys run something like
ssh-keygen -t ed25519 -f ~/nix-book-deploy-key -C &quot;deploy-key-nix-book-repo&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;6&quot;&gt;
&lt;li&gt;Copy the &lt;strong&gt;PRIVATE&lt;/strong&gt; key for each and add them to your secrets directory:&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;While in your flake directory:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mkdir secrets
sops secrets/github-deploy-key.yaml  # For your github ssh key
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When you call a &lt;code&gt;sops&lt;/code&gt; command, it will handle the encryption/decryption
transparently and open the cleartext file in an editor.&lt;/p&gt;
&lt;p&gt;Editing will happen in the editor that &lt;code&gt;$SOPS_EDITOR&lt;/code&gt; or &lt;code&gt;$EDITOR&lt;/code&gt; is set to,
sops will wait for the editor to exit, and then try to reencrypt the file.&lt;/p&gt;
&lt;p&gt;The above command will open a default sops &lt;code&gt;github-deploy-key.yaml&lt;/code&gt; in your
&lt;code&gt;$EDITOR&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;Erase the default &lt;code&gt;sops&lt;/code&gt; filler and type &lt;code&gt;github_deploy_key_ed25519: |&lt;/code&gt;, move
your cursor 1 line down and type &lt;code&gt;:r ~/.ssh/id_ed25519&lt;/code&gt; to read the private key
into the file and repeat as needed.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;github_deploy_key_ed25519: |
  -----BEGIN OPENSSH PRIVATE KEY-----
  ...
  -----END OPENSSH PRIVATE KEY-----

github_deploy_key_ed25519_nix-book: |
  -----BEGIN OPENSSH PRIVATE KEY-----
  ...
  -----END OPENSSH PRIVATE KEY-----
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;-----BEGIN&lt;/code&gt; and the rest of the private key &lt;strong&gt;must&lt;/strong&gt; be indented 2 spaces&lt;/p&gt;
&lt;p&gt;Ensure sops can decrypt it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sops -d secrets/github-deploy-key.yaml
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;❗ WARNING: Only ever enter your private keys through the &lt;code&gt;sops&lt;/code&gt; command. If
you forget and paste them in without the &lt;code&gt;sops&lt;/code&gt; command then run &lt;code&gt;git add&lt;/code&gt; at
any point, your git history will have contained an unencrypted secret which is
a nono. Always use the &lt;code&gt;sops&lt;/code&gt; command when dealing with files in the &lt;code&gt;secrets&lt;/code&gt;
directory, save the file and inspect that it is encrypted on save. If not
something went wrong with the &lt;code&gt;sops&lt;/code&gt; process, &lt;strong&gt;do not add it to Git&lt;/strong&gt;. If you
do, you will be required to rewrite your entire history which can be bad if
you’re collaborating with others. &lt;code&gt;git-filter-repo&lt;/code&gt; is one such solution that
rewrites your history. Just keep this in mind. This happens because Git has a
protection that stops you from doing stupid things.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Generate an encrypted password hash with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mkpasswd --method=yescrypt &amp;gt; /tmp/password-hash.txt
# Enter your chosen password and copy the encrypted hash it gives you back
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sops secrets/password-hash.yaml      # For your `hashedPasswordFile`
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The above command will open your &lt;code&gt;$EDITOR&lt;/code&gt; with the file &lt;code&gt;password-hash.yaml&lt;/code&gt;,
add the following content to it. Replace &lt;code&gt;PasteEncryptedHashHere&lt;/code&gt; with the
output of the &lt;code&gt;mkpasswd&lt;/code&gt; command above:&lt;/p&gt;
&lt;p&gt;Delete the default &lt;code&gt;sops&lt;/code&gt; filler, type &lt;code&gt;password_hash:&lt;/code&gt; and leave your cursor
after the &lt;code&gt;:&lt;/code&gt; and type &lt;code&gt;:r /tmp/password-hash.txt&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;password_hash: PasteEncryptedHashHere
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ensure sops can decrypt it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sops -d secrets/password-hash.yaml
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;7&quot;&gt;
&lt;li&gt;Create a &lt;code&gt;sops.nix&lt;/code&gt; and import it or add this directly to your
&lt;code&gt;configuration.nix&lt;/code&gt;:&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;My &lt;code&gt;sops.nix&lt;/code&gt; is located at &lt;code&gt;~/flake/hosts/hostname/sops.nix&lt;/code&gt; and the secrets
directory is located at &lt;code&gt;~/flake/secrets&lt;/code&gt; so the path from &lt;code&gt;sops.nix&lt;/code&gt; to
&lt;code&gt;secrets/pasword-hash.yaml&lt;/code&gt; would be &lt;code&gt;../../secrets/password-hash.yaml&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Another step you can take is to copy your key to a persistent location,
preparing for impermanence:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo mkdir /persist/sops/age
sudo cp ~/.config/sops/age/keys.txt /persist/sops/age/keys.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then you would change the &lt;code&gt;age.keyFile = &quot;/persist/sops/age/keys.txt&quot;&lt;/code&gt; to match
this location below.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# ~/flake/hosts/magic/sops.nix  # magic is my hostname
# hosts/magic/ is also where my configuration.nix is
{...}: {
  sops = {
    defaultSopsFile = ../../.sops.yaml; # Or the correct path to your .sops.yaml
    # Don&apos;t mix sshKeyPaths and keyFile
    age.sshKeyPaths = [];
    age.keyFile = &quot;/persist/sops/age/keys.txt&quot;;

    secrets = {
      &quot;password_hash&quot; = {
        sopsFile = ../../secrets/password-hash.yaml; # &amp;lt;-- Points to your password hash file
        owner = &quot;root&quot;;
        group = &quot;root&quot;;
        mode = &quot;0400&quot;;
        neededForUsers = true;
      };
      &quot;github_deploy_key_ed25519_nix-book&quot; = {
        sopsFile = ../../secrets/github-deploy-key.yaml;
        key = &quot;github_deploy_key_ed25519_nix-book&quot;;
        owner = &quot;root&quot;;
        group = &quot;root&quot;;
        mode = &quot;0400&quot;;
      };
      &quot;github_deploy_key_ed25519&quot; = {
        sopsFile = ../../secrets/github-deploy-key.yaml;
        key = &quot;github_deploy_key_ed25519&quot;;
        owner = &quot;root&quot;;
        group = &quot;root&quot;;
        mode = &quot;0400&quot;;
      };
    };
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Import &lt;code&gt;sops.nix&lt;/code&gt; into your &lt;code&gt;configuration.nix&lt;/code&gt; or equivalent:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# configuration.nix
imports = [
  ./sops.nix # Assuming sops.nix is in the same directory as configuration.nix, adjust path as needed
  # ... other imports
];
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;❗ NOTE: You may see in the sops quickstart guide that if you’re using
impermanence, the key used for secret decryption (&lt;code&gt;sops.age.keyFile&lt;/code&gt;) must be
in a persistent directory, loaded early enough during the boot process. If you
are using the btrfs subvolume layout you don’t need to worry about this
because your home will be on its own partition when only the root partition is
wiped on reboot. Adding &lt;code&gt;neededForUsers = true;&lt;/code&gt; tells &lt;code&gt;sops-nix&lt;/code&gt; to decrypt
and make that secret available earlier in the boot process specifically,
before user and group accounts are created.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;You typically use &lt;code&gt;age.sshKeyPaths&lt;/code&gt; for &lt;strong&gt;system-level secrets&lt;/strong&gt; with a
persistent SSH host key&lt;/p&gt;
&lt;p&gt;For &lt;strong&gt;user-level secrets&lt;/strong&gt;, use &lt;code&gt;age.keyFile&lt;/code&gt; pointing to your Age private key,
stored in a safe persistent location.&lt;/p&gt;
&lt;p&gt;For reproducibility, keep your key files in a persistent, predictable path and
document which keys are used for which secrets in your &lt;code&gt;.sops.yaml&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If you don’t need both &lt;code&gt;age.keyFile&lt;/code&gt; and &lt;code&gt;age.sshKeyPaths&lt;/code&gt; it can reduce
complexity to use one or the other. Although most people may choose one, it’s
not bad to use both it just adds complexity.&lt;/p&gt;
&lt;p&gt;And finally use the password-hash for your &lt;code&gt;hashedPasswordFile&lt;/code&gt; for your user,
my user is &lt;code&gt;jr&lt;/code&gt; so I added this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# ... snip ...
    users.users = {
      # ${username} = {
      jr = {
        homeMode = &quot;755&quot;;
        isNormalUser = true;
        # description = userVars.gitUsername;
        hashedPasswordFile = config.sops.secrets.password_hash.path;
  # ...snip...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By integrating SOPS with NixOS through &lt;code&gt;sops-nix&lt;/code&gt;, you gain a modern, secure,
and reproducible way to manage sensitive secrets. Unlike traditional approaches
where secrets are often scattered in ad hoc locations, referenced by absolute
paths, or managed outside version control, &lt;code&gt;sops-nix&lt;/code&gt; keeps your secrets
encrypted, declarative, and version-control friendly.&lt;/p&gt;
</content></entry><entry><title>Readme1</title><id>https://saylesss88.github.io/nix/index.html</id><updated>2025-11-22T00:00:00+00:00</updated><author><name>saylesss87@proton.me (saylesss88)</name></author><link href="https://saylesss88.github.io/nix/index.html" rel="alternate"/><content type="html">&lt;h1&gt;Hardening README&lt;/h1&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Click to Expand Table of Contents&lt;/summary&gt;
&lt;!-- toc --&gt;
&lt;/details&gt;
&lt;p&gt;📌 &lt;strong&gt;How to Use This Guide&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Read warnings&lt;/strong&gt;: Advanced hardening can break compatibility or cause data
loss! Pause and research before enabling anything not listed above unless you
understand the consequences.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Hardening NixOS&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;🔷 Start Here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://saylesss88.github.io/nix/hardening_NixOS.html&quot;&gt;Hardening NixOS&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://saylesss88.github.io/nix/hardening_networking.html&quot;&gt;Hardening Networking&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;strong&gt;Additional security/hardening topics&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://saylesss88.github.io/nix/browsing_security.html&quot;&gt;Browser/Browsing Security/Privacy&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://saylesss88.github.io/nix/gpg-agent.html&quot;&gt;GnuPG gpg-agent&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://saylesss88.github.io/nix/whonix_kvm.html&quot;&gt;Whonix KVM on NixOS&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://saylesss88.github.io/nix/kvm.html&quot;&gt;Running NixOS in a secureblue VM&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Getting Started&lt;/h2&gt;
&lt;p&gt;There is a lot covered in this guide which can get overwhelming when trying to
decide what is worth implementing. Here, I will list some common recommendations
that most users should follow to harden their stance.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“The major problem with current systems is their inability to provide
effective isolation between various programs running on one machine. E.g. if
the user’s Web browser gets compromised (due to a bug exploited by a malicious
web site), the OS is usually unable to protect other user’s applications and
data from also being compromised.”–Qubes arch-spec&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Threat Modeling&lt;/h2&gt;
&lt;p&gt;You should always start by conducting a personal threat assesment to identify
potential threats and vulnerabilities that you need to develop strategies to
defend against.&lt;/p&gt;
&lt;p&gt;Threat modeling in computing involves evaluating the security risks to your
computer or network. It helps uncover possible threats and weaknesses so you can
create plans to safeguard your systems and data effectively. By examining
various attack scenarios, you can anticipate potential cyber threats and better
protect your digital resources.&lt;/p&gt;
&lt;p&gt;It’s not possible to protect yourself against every attack(er), focus on the
most probable threats to your specific situation.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://ssd.eff.org/playlist/want-security-starter-pack&quot;&gt;EFF Security Starter Pack&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://ssd.eff.org/module/your-security-plan&quot;&gt;EFF Your Security Plan&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.kicksecure.com/wiki/Threat_Modeling&quot;&gt;Kicksecure Computer Security Threat Modeling&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Baseline Hardening&lt;/h3&gt;
&lt;p&gt;Before diving into advanced or specialized hardening, apply these baseline
security measures suitable for all NixOS users. These settings help protect your
system with minimal risk of breaking workflows or causing admin headaches.&lt;/p&gt;
&lt;p&gt;There is something to be said about the window manager you use. GNOME, KDE
Plasma, and Sway secure privileged Wayland protocols like screencopy. This means
that on environments outside of GNOME, KDE, and Sway, applications can access
screen content of the entire desktop. This implicitly includes the content of
other applications. It’s primarily for this reason that Silverblue, Kinoite, and
Sericea images are recommended. COSMIC has plans to fix this.
–&lt;a href=&quot;https://secureblue.dev/images&quot;&gt;secureblue Images&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Secureblue recommends disabling Xwayland and finding alternatives for those apps
as well as disabling &lt;code&gt;xdg-desktop-portal-wlr&lt;/code&gt;, this is because the wlroots
desktop portal reintroduces the screencopy vulnerability.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Use Disk Encryption (LUKS) to protect your data at rest.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Keep your system up to date (update regularly).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Use strong, unique passwords. To generate one from the command-line, there is
&lt;code&gt;pkgs.diceware&lt;/code&gt;. Generate a password with: &lt;code&gt;diceware -n 12 -w en_eff&lt;/code&gt;, add
spaces between the words for higher entropy.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.kicksecure.com/wiki/Passwords#Password_Generation&quot;&gt;Kicksecure Password_Generation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Avoid reusing passwords, use a password manager.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Avoid storing files directly in the root home folder (i.e., &lt;code&gt;/home/user&lt;/code&gt;),
create sub-folders instead.(i.e., Instead of creating &lt;code&gt;~/notes.txt&lt;/code&gt;, create
&lt;code&gt;~/my-notes/notes.txt&lt;/code&gt; or &lt;code&gt;~/Documents/notes.txt&lt;/code&gt;).&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;If you are able to implement a Mandatory Access Control framework, there are
more sub-folders that should be avoided such as &lt;code&gt;~/Downloads&lt;/code&gt;. Another
reason to use non-default sub-dirs is to avoid typos deleting important
files.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Home-Manager has an option &lt;code&gt;xdg.userDirs.enable&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# home.nix or equivalent
{
  xdg.userDirs.enable = true;
  xdg.userDirs.createDirectories = true;
  # Optionally create non-default sub-dirs
  # xdg.userDirs.documents = &quot;/home/jr/my-documents&quot;;
  # xdg.userDirs.download = &quot;/home/jr/my-downloads&quot;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The XDG Base Directory Specification defines a consistent way for apps and
desktops to store and find files. It helps prevent “dotfile clutter” by
directing application files into clear, organized locations.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Only enable what you use, and actively disable what’s no longer in use.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Enable at least a basic firewall, a more complex firewall example that
utilizes nftables is shared in the
&lt;a href=&quot;https://saylesss88.github.io/nix/hardening_networking.html&quot;&gt;Hardening Networking Chapter&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Although the firewall is enabled by default on NixOS, let’s be explicit about
it, add the following to your &lt;code&gt;configuration.nix&lt;/code&gt; or equivalent:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# configuration.nix
# this denies incoming connections but allows outgoing and established connections
networking.firewall.enable = true;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Many services provide an option to open the required firewall ports
automatically. For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;services.tor.openFirewall = true;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This prevents you from having to manually open ports&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Audit and remove local user accounts that are no longer needed&lt;/strong&gt;: Regularly
review and remove unused or outdated accounts to reduce your system’s attack
surface, improve compliance, and ensure only authorized users have access. The
following setting ensures that user (and group) management is fully declarative:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# configuration.nix
# All users must be declared
users.mutableUsers = false;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With &lt;code&gt;users.mutableUsers = false;&lt;/code&gt;, all non-declaratively managed (imperative)
user management including creation, modification, or password changes will fail
or be reset on rebuild. User and group definitions become entirely controlled by
your system configuration for maximum reproducibility and security. If you need
to add, remove, or modify users, you must do so in your &lt;code&gt;configuration.nix&lt;/code&gt; and
rebuild the system.&lt;/p&gt;
&lt;p&gt;Don’t log in as &lt;code&gt;root&lt;/code&gt;, it’s unnecessary.&lt;/p&gt;
&lt;p&gt;Commands that require &lt;code&gt;root&lt;/code&gt; permissions should be run individually using &lt;code&gt;sudo&lt;/code&gt;
in all cases. Avoid logging in as &lt;code&gt;root&lt;/code&gt; &amp;amp; using &lt;code&gt;sudo su&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Never run GUI applications as &lt;code&gt;root&lt;/code&gt;. If there is a legitimate reason for doing
this, use &lt;code&gt;lxsudo&lt;/code&gt; instead.&lt;/p&gt;
&lt;hr /&gt;
&lt;blockquote&gt;
&lt;p&gt;NOTE: There is mention of making
&lt;a href=&quot;https://github.com/nikstur/userborn&quot;&gt;userborn&lt;/a&gt; the default for NixOS in the
future. It can be more secure by prohibiting UID/GID re-use and giving
warnings about insecure password hashing schemes.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I have personally had nothing but problems with &lt;code&gt;userborn&lt;/code&gt; and find the docs
extremely lacking, you need to read the source code to figure anything out which
is ridiculous. I don’t personally use this but if you figure it out, more power
to ya.&lt;/p&gt;
&lt;p&gt;To enable &lt;code&gt;userborn&lt;/code&gt;, just add the following to your &lt;code&gt;configuration.nix&lt;/code&gt; or
equivalent:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# users.nix
{pkgs,...}:{
services.userborn = {
    enable = true;
    # Only needed if `/etc` is immutable
    # passwordFilesLocation = &quot;/var/lib/nixos/userborn&quot;
};
    users.users = {
       &quot;newuser&quot; = {
         homeMode = &quot;755&quot;;
         uid = 1000;
         isNormalUser = true;
         description = &quot;New user account&quot;;
         extraGroups = [ &quot;networkmanager&quot; &quot;wheel&quot; &quot;libvirtd&quot; ];
         shell = pkgs.bash;
         ignoreShellProgramCheck = true;
         packages = with pkgs; [];
       };
    };
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With &lt;code&gt;userborn&lt;/code&gt;, you configure your users as you normally would declaratively
with NixOS with &lt;code&gt;users.users&lt;/code&gt;, change &lt;code&gt;&quot;newuser&quot;&lt;/code&gt; to your desired username.&lt;/p&gt;
&lt;p&gt;Explicitly setting &lt;code&gt;uid = 1000;&lt;/code&gt; is a best practice for compatibility and
predictability.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;strong&gt;Only install, enable, and run what is needed&lt;/strong&gt;: Disable or uninstall
unnecessary software and services to minimize potential vulnerabilities. Take
advantage of NixOS’s easy package management and minimalism to keep your system
lean and secure.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Avoid permanently installing temporary tools&lt;/strong&gt;: Use tools like &lt;code&gt;nix-shell&lt;/code&gt;,
&lt;code&gt;comma&lt;/code&gt;, &lt;code&gt;devShells&lt;/code&gt; and &lt;code&gt;nix-direnv&lt;/code&gt; to test or run software temporarily. This
prevents clutter and reduces potential risks from unused software lingering on
the system.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Update regularly&lt;/strong&gt;: Keep your system and software up to date to receive the
latest security patches. Delaying updates leaves known vulnerabilities open to
exploitation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Apply the Principle of Least Privilege&lt;/strong&gt;: Never run tools or services as root
unless absolutely necessary. Create dedicated users and groups with the minimum
required permissions to limit potential damage if compromised.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Use strong passwords and passphrases&lt;/strong&gt;: Aim for at least 14–16 characters by
combining several unrelated words, symbols, and numbers. For example:
&lt;code&gt;sunset-CoffeeHorse$guitar!&lt;/code&gt;. Strong passphrases are both memorable and secure.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Use a password manager and enable multi-factor authentication (MFA)&lt;/strong&gt;: Manage
unique, strong passwords effectively with a trusted manager and protect accounts
with MFA wherever possible for a second layer of defense.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Check logs regularly&lt;/strong&gt;: Reviewing your system logs helps you spot unusual
activity, errors, or failed login attempts that could indicate a security
problem. NixOS uses &lt;code&gt;journald&lt;/code&gt; by default, which makes this easy. For example,
to see the logs for your current boot session:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;journalctl -b
# for the previous session
journalctl -b -1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After establishing some standard best practices and a hardened base, it’s time
to dive deeper into system hardening, the process of adding layered safeguards
throughout your NixOS setup. This next section guides you through concrete steps
and options for hardening critical areas of your system: from encryption and
secure boot to managing secrets, tightening kernel security, and leveraging
platform-specific tools.
&lt;a href=&quot;https://saylesss88.github.io/nix/hardening_NixOS.html&quot;&gt;Hardening NixOS&lt;/a&gt;&lt;/p&gt;
</content></entry><entry><title>Lanzaboote</title><id>https://saylesss88.github.io/installation/enc/lanzaboote.html</id><updated>2025-11-22T00:00:00+00:00</updated><author><name>saylesss87@proton.me (saylesss88)</name></author><link href="https://saylesss88.github.io/installation/enc/lanzaboote.html" rel="alternate"/><content type="html">&lt;h1&gt;Secure Boot with Lanzaboote&lt;/h1&gt;
&lt;details&gt;
&lt;summary&gt; Click to Expand Table of Contents&lt;/summary&gt;
&lt;!-- toc --&gt;
&lt;/details&gt;
&lt;p&gt;⚠️ &lt;strong&gt;Warning: This can easily brick your system&lt;/strong&gt; ⚠️&lt;/p&gt;
&lt;p&gt;We will mainly follow the lanzaboote
&lt;a href=&quot;https://github.com/nix-community/lanzaboote/blob/master/docs/QUICK_START.md&quot;&gt;Quick Start Guide&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;For Windows dual-booters and BitLocker users, you should export your BitLocker
recovery keys and confirm that they are correct. Refer to this
&lt;a href=&quot;https://support.microsoft.com/en-us/windows/find-your-bitlocker-recovery-key-6b71ad27-0b89-ea08-f143-056f5ab347d6&quot;&gt;Microsoft support article&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;❗ NOTE: There are some serious limitations to this setup when used without
encryption, I’d say it could stop the average person. But an experienced
hacker could easily bypass this without encryption if they had access to your
computer. For more protection look into TPM2 Hardware Requirements, and full
disk encryption.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Important Considerations&lt;/h2&gt;
&lt;p&gt;I found
&lt;a href=&quot;https://0pointer.net/blog/authenticated-boot-and-disk-encryption-on-linux.html&quot;&gt;This Article&lt;/a&gt;
fairly enlightening as far as the state of Authenticated Boot and Disk
Encryption on Linux.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://0pointer.net/blog/brave-new-trusted-boot-world.html&quot;&gt;Brave New Trusted Boot World&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Lanzaboote only secures the boot chain. The userspace remains unverified (i.e.,
the nix store, etc.), to verify userspace you need to implement additional
integrity checks. It’s common to rely to disk encryption to prevent tampering
with and keep the Nix store safe but it’s not always desirable. (i.e.,
unattended boot)&lt;/p&gt;
&lt;h2&gt;Requirements&lt;/h2&gt;
&lt;p&gt;To be able to setup Secure Boot on your device, NixOS needs to be installed in
UEFI mode and systemd-boot must be used as a boot loader. This means if you wish
to install lanzaboote on a new machine, you need to follow the install
instruction for systemd-boot and then switch to lanzaboote after the first boot.&lt;/p&gt;
&lt;p&gt;Check these prerequisits with &lt;code&gt;bootctl status&lt;/code&gt;, this is an example output:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo bootctl status
System:
     Firmware: UEFI 2.70 (Lenovo 0.4720)
  Secure Boot: disabled (disabled)
 TPM2 Support: yes
 Boot into FW: supported

Current Boot Loader:
      Product: systemd-boot 251.7
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The firmware &lt;strong&gt;must&lt;/strong&gt; be &lt;code&gt;UEFI&lt;/code&gt; and the current bootloader needs to be
&lt;code&gt;systemd-boot&lt;/code&gt;. If you check these boxes, you’re good to go.&lt;/p&gt;
&lt;h2&gt;Security Requirements&lt;/h2&gt;
&lt;p&gt;To provide any security your system needs to defend against an attacker turning
UEFI Secure Boot off or being able to sign binaries with the keys we are going
to generate.&lt;/p&gt;
&lt;p&gt;The easiest way to achieve this is to:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Enable a BIOS password for your system, this will prevent someone from just
shutting off secure boot.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Use full disk encryption.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Preparation&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Finding the UEFI System Partition (ESP)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The UEFI boot process revolves around the ESP, the (U)EFI System Partition. This
partition is conventionally mounted at &lt;code&gt;/boot&lt;/code&gt; on NixOS.&lt;/p&gt;
&lt;p&gt;Verify this with the command &lt;code&gt;sudo bootctl status&lt;/code&gt;. Look for &lt;code&gt;ESP:&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Creating Your Keys&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;First you’ll need to install &lt;code&gt;sbctl&lt;/code&gt; which is available in &lt;code&gt;Nixpkgs&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# configuration.nix or equivalent
environment.systemPackages = [ pkgs.sbctl ];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create the keys:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ sudo sbctl create-keys
[sudo] password for julian:
Created Owner UUID 8ec4b2c3-dc7f-4362-b9a3-0cc17e5a34cd
Creating secure boot keys...✓
Secure boot keys created!
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you already have keys in &lt;code&gt;/etc/secureboot&lt;/code&gt; migrate these to &lt;code&gt;/var/lib/sbctl&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sbctl setup --migrate
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Configuring Lanzaboote With Flakes&lt;/h2&gt;
&lt;p&gt;Shown all in &lt;code&gt;flake.nix&lt;/code&gt; for brevity. Can easily be split up into a &lt;code&gt;boot.nix&lt;/code&gt;,
etc:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{
  description = &quot;A SecureBoot-enabled NixOS configurations&quot;;

  inputs = {
    nixpkgs.url = &quot;github:NixOS/nixpkgs/nixos-unstable&quot;;

    lanzaboote = {
      url = &quot;github:nix-community/lanzaboote/v0.4.2&quot;;

      # Optional but recommended to limit the size of your system closure.
      inputs.nixpkgs.follows = &quot;nixpkgs&quot;;
    };
  };

  outputs = { self, nixpkgs, lanzaboote, ...}: {
    nixosConfigurations = {
      yourHost = nixpkgs.lib.nixosSystem {
        system = &quot;x86_64-linux&quot;;

        modules = [
          # This is not a complete NixOS configuration and you need to reference
          # your normal configuration here.

          lanzaboote.nixosModules.lanzaboote

          ({ pkgs, lib, ... }: {

            environment.systemPackages = [
              # For debugging and troubleshooting Secure Boot.
              pkgs.sbctl
            ];

            # Lanzaboote currently replaces the systemd-boot module.
            # This setting is usually set to true in configuration.nix
            # generated at installation time. So we force it to false
            # for now.
            boot.loader.systemd-boot.enable = lib.mkForce false;

            boot.lanzaboote = {
              enable = true;
              pkiBundle = &quot;/var/lib/sbctl&quot;;
            };
          })
        ];
      };
    };
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Build it&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo nixos-rebuild switch --flake /path/to/flake
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Ensure Your Machine is Ready for Secure Boot enforcement&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ sudo sbctl verify
Verifying file database and EFI images in /boot...
✓ /boot/EFI/BOOT/BOOTX64.EFI is signed
✓ /boot/EFI/Linux/nixos-generation-355.efi is signed
✓ /boot/EFI/Linux/nixos-generation-356.efi is signed
✗ /boot/EFI/nixos/0n01vj3mq06pc31i2yhxndvhv4kwl2vp-linux-6.1.3-bzImage.efi is not signed
✓ /boot/EFI/systemd/systemd-bootx64.efi is signed
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Enabling Secure Boot and Entering Setup Mode&lt;/h3&gt;
&lt;p&gt;This is where things can get tricky because UEFI/BIOS are widely different and
use different conventions.&lt;/p&gt;
&lt;p&gt;You can see your BIOS from the output of &lt;code&gt;bootctl status&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo bootctl status
doas (jr@magic) password:
System:
      Firmware: UEFI 2.70 (American Megatrends)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;My UEFI is an American Megatrends, find yours and look up which key you have to
hit to enter the BIOS on reboot, mine is the delete key. So I reboot and
repeatedly hit delete until it brings up the BIOS settings.&lt;/p&gt;
&lt;p&gt;The lanzaboote guide shows a few systems and how to enter setup mode for them.&lt;/p&gt;
&lt;p&gt;For a ThinkPad the steps are:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Select the “Security” tab.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Select the “Secure Boot” entry.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set “Secure Boot” to enabled.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Select “Reset to Setup Mode”.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;p&gt;For my system, it would allow me to do the above steps but when I saved and
exited I got a red screen then blue screen and it said No Valid Keys or
something like that and eventually brought me to the MOK Manager where you can
manually register keys, this is NOT what you want to do.&lt;/p&gt;
&lt;p&gt;Even after this mistake I was able to re-enable secure boot and get back into
the system.&lt;/p&gt;
&lt;p&gt;After some tinkering, I found that I was able to enter “custom mode” without
enabling secure boot, which in turn allowed me to select the “Reset to Setup
Mode”&lt;/p&gt;
&lt;p&gt;It asks if you are sure you want to erase all of the variables to enter setup
mode? Hit “Yes”. Then it asks if you want to exit without saving, we want to
save our changes so hit “No” do not exit without saving.&lt;/p&gt;
&lt;p&gt;After this you should see all No Keys entries.&lt;/p&gt;
&lt;p&gt;Finally, Hit the setting to save and exit, some BIOS list an F4 or F9 keybind
that saves and exits.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;❗: For my system, choosing “save and reboot” would not work for some reason,
I had to choose “save and exit”.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;After hitting “save and exit”, the system boots into NixOS like normal but you
are in setup mode if everything worked correctly.&lt;/p&gt;
&lt;p&gt;Open a terminal and type:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo sbctl enroll-keys --microsoft
Enrolling keys to EFI variables...
With vendor keys from microsoft...✓
Enrolled keys to the EFI variables!
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;⚠️ If you used &lt;code&gt;--microsoft&lt;/code&gt; while enrolling the keys, you might want to check
that the Secure Boot Forbidden Signature Database (dbx) is not empty. A quick
and dirty way is by checking the file size of
&lt;code&gt;/sys/firmware/efi/efivars/dbx-\*&lt;/code&gt;. Keeping an up to date dbx reduces Secure
Boot bypasses, see for example:
&lt;a href=&quot;https://uefi.org/sites/default/files/resources/dbx_release_info.pdf&quot;&gt;https://uefi.org/sites/default/files/resources/dbx_release_info.pdf&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I then Rebooted into BIOS and enabled secure boot, saved and exited. This loads
NixOS as if you just rebooted.&lt;/p&gt;
&lt;p&gt;And finally check the output of &lt;code&gt;sbctl status&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo sbctl status
System:
      Firmware: UEFI 2.70 (American Megatrends)
 Firmware Arch: x64
   Secure Boot: enabled (user)
  TPM2 Support: yes
  Measured UKI: yes
  Boot into FW: supported
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can see the &lt;code&gt;Secure Boot: enabled (user)&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;What Lanzaboote (Secure Boot) Actually Secures on NixOS and Limitations&lt;/h2&gt;
&lt;p&gt;As mentioned earlier, this provides some basic protection that may be good
enough for your desktop in your bedroom but there are some serious limitations.
I want to be clear that this may stop an average person but an advanced threat
actor with resources could still fairly easily get in.&lt;/p&gt;
&lt;p&gt;Secure Boot (with Lanzaboote or any other tool) on NixOS primarily protects the
boot chain—the bootloader, kernel, and initrd—by ensuring only signed, trusted
binaries are executed at boot. This is a real and valuable security improvement,
especially for defending against “evil maid” attacks (where someone with
physical access tampers with your bootloader or kernel) and for preventing many
forms of persistent malware.&lt;/p&gt;
&lt;p&gt;Here are some of the caveats:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Userspace Remains Unverified&lt;/p&gt;
&lt;p&gt;Once the kernel and initrd have booted, NixOS (by default) does not
cryptographically verify the integrity of the rest of userspace (the programs
and libraries in the Nix store, your configs, etc.).&lt;/p&gt;
&lt;p&gt;This means an attacker who can modify userspace (e.g., by gaining root
access) can potentially install persistent malware, even if your boot chain
is protected&lt;/p&gt;
&lt;p&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Kernel Lockdown Is Not Enabled&lt;/p&gt;
&lt;p&gt;The Linux kernel’s [lockdown mode]&lt;/p&gt;
&lt;p&gt;is designed to prevent even root from tampering with the kernel at runtime
(e.g., by loading unsigned modules, using kexec, or accessing /dev/mem).&lt;/p&gt;
&lt;p&gt;NixOS does not enable kernel lockdown by default, and enabling it is
non-trivial, especially given how the Nix store works (modules and kernels
are built dynamically and not always signed at install time).&lt;/p&gt;
&lt;p&gt;Without lockdown, a root user (or malware with root) can still compromise the
kernel after boot.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Stage 2 Verification Is Lacking&lt;/p&gt;
&lt;p&gt;Some distributions (like Fedora Silverblue or systems using dm-verity)
cryptographically verify the entire userspace at boot, making it immutable
and much harder to tamper with. This is not the default on NixOS, though
there are experimental or appliance-focused solutions&lt;/p&gt;
&lt;p&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Disk Encryption Complements Secure Boot&lt;/p&gt;
&lt;p&gt;Full disk encryption (e.g., LUKS) is strongly recommended alongside Secure
Boot. Encryption protects your data at rest and ensures that even if someone
bypasses Secure Boot, they cannot read or modify your files without your
passphrase&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
</content></entry><entry><title>Local Nixpkgs</title><id>https://saylesss88.github.io/Working_with_Nixpkgs_Locally_10.html</id><updated>2025-11-22T00:00:00+00:00</updated><author><name>saylesss87@proton.me (saylesss88)</name></author><link href="https://saylesss88.github.io/Working_with_Nixpkgs_Locally_10.html" rel="alternate"/><content type="html">&lt;h1&gt;Chapter 10&lt;/h1&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Click to Expand Table of Contents&lt;/summary&gt;
&lt;!-- toc --&gt;
&lt;/details&gt;
&lt;p&gt;&lt;img src=&quot;https://saylesss88.github.io/images/server_rack.cleaned.png&quot; alt=&quot;server_rack&quot; /&gt;&lt;/p&gt;
&lt;!-- ![gruv18](images/gruv18.png) --&gt;
&lt;h2&gt;Working with Nixpkgs Locally: Benefits and Best Practices&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Nixpkgs, the package repository for NixOS, is a powerful resource for building
and customizing software.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Working with a local copy enhances development, debugging, and contribution
workflows.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;This post covers setting up a local Nixpkgs repository, searching for
dependencies, and leveraging its advantages, incorporating tips from the Nix
community.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;I. Why Work with Nixpkgs Locally?&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;A local Nixpkgs repository offers significant advantages for Nix developers:&lt;/p&gt;
&lt;h2&gt;A. Faster Development Cycle&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Local searches for packages and dependencies are significantly quicker than
querying remote repositories or channels.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;This speedup is crucial for efficient debugging and rapid prototyping of Nix
expressions.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;B. Enhanced Version Control&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;By pinning your local repository to specific commits or branches (e.g.,
&lt;code&gt;nixos-unstable&lt;/code&gt;), you ensure build reproducibility.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;This prevents unexpected issues arising from upstream changes in Nixpkgs.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;C. Flexible Debugging Capabilities&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;You can directly test and modify package derivations within your local copy.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;This allows for quick fixes to issues like missing dependencies without
waiting for upstream updates or releases.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;D. Streamlined Contribution Workflow&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Developing and testing new packages or patches locally is essential before
submitting them as pull requests to Nixpkgs.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A local setup provides an isolated environment for experimentation.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;E. Up-to-Date Documentation Source&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;The source code and comments within the Nixpkgs repository often contain the
most current information about packages.&lt;/li&gt;
&lt;li&gt;This can sometimes be more up-to-date than official, external documentation.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;F. Optimized Storage and Performance&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Employing efficient cloning strategies (e.g., shallow clones) and avoiding
unnecessary practices (like directly using Nixpkgs as a flake for local
development) minimizes disk usage and build times.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;II. Flake vs. Non-Flake Syntax for Local Nixpkgs&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;When working with Nixpkgs locally, the choice between Flake and non-Flake
syntax has implications for performance and storage:&lt;/p&gt;
&lt;h2&gt;A. Flake Syntax (&lt;code&gt;nix build .#&amp;lt;package&amp;gt;&lt;/code&gt;)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Treats the current directory as a flake, requiring evaluation of
&lt;code&gt;flake.nix&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;For local Nixpkgs, this evaluates the flake definition in the repository
root.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Performance and Storage Overhead:&lt;/strong&gt; Flakes copy the entire working
directory (including Git history if present) to &lt;code&gt;/nix/store&lt;/code&gt; for evaluation.
This can be slow and storage-intensive for large repositories like Nixpkgs.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;B. Non-Flake Syntax (&lt;code&gt;nix-build -f . &amp;lt;package&amp;gt;&lt;/code&gt; or &lt;code&gt;nix build -f . &amp;lt;package&amp;gt;&lt;/code&gt;)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;-f .&lt;/code&gt; specifies the Nix expression (e.g., &lt;code&gt;default.nix&lt;/code&gt; or a specific file)
in the current directory.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Efficiency:&lt;/strong&gt; Evaluates the Nix expression directly &lt;em&gt;without&lt;/em&gt; copying the
entire worktree to &lt;code&gt;/nix/store&lt;/code&gt;. This is significantly faster and more
storage-efficient for local development on large repositories.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;III. Setting Up a Local Nixpkgs Repository Efficiently&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Cloning Nixpkgs requires careful consideration due to its size.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;A.a Initial Clone: Shallow Cloning&lt;/h2&gt;
&lt;p&gt;It is common to place your local clone in the &lt;code&gt;/src&lt;/code&gt; directory:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mkdir src &amp;amp;&amp;amp; cd src
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;❗ Warning, A shallow clone (&lt;code&gt;--depth 1&lt;/code&gt;) is not recommended for general
development or contributing changes back to Nixpkgs via pull requests. It’s
primarily suitable for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Quick checks or builds: If you only need to verify a package’s current state
or build a specific version without needing historical context.&lt;/li&gt;
&lt;li&gt;CI/CD environments: Where disk space and clone time are critical, and only
the latest commit is needed for automated tests or builds.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;With that said, to avoid downloading the entire history, perform a shallow
clone:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git clone [https://github.com/NixOS/nixpkgs](https://github.com/NixOS/nixpkgs) --depth 1
cd nixpkgs
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;A.b A few Examples exploring Nixpkgs&lt;/h2&gt;
&lt;p&gt;While in the &lt;code&gt;nixpkgs&lt;/code&gt; directory, you can check the version of a package:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix-instantiate --eval -A openssl.version
&quot;3.4.1&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or to directly edit the file you can use &lt;code&gt;nix edit&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix edit nixpkgs#openssl
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It uses the nix registry and &lt;code&gt;openssl.meta.position&lt;/code&gt; to locate the file.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;man nix3 registry
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The above command will open the &lt;code&gt;openssl/default.nix&lt;/code&gt; in your &lt;code&gt;$EDITOR&lt;/code&gt;.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;A.1 Full Fork and Clone of Nixpkgs&lt;/h2&gt;
&lt;p&gt;If you want to contribute to Nixpkgs, you need to set up a local version
following the
&lt;a href=&quot;https://github.com/NixOS/nixpkgs/blob/master/CONTRIBUTING.md&quot;&gt;Contributing guide&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;You’ll need to, this is directly from the &lt;code&gt;Contributing.md&lt;/code&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/fork-a-repo#forking-a-repository&quot;&gt;Fork&lt;/a&gt;
the &lt;a href=&quot;https://github.com/nixos/nixpkgs/&quot;&gt;Nixpkgs repository&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/fork-a-repo#cloning-your-forked-repository&quot;&gt;Clone the forked repo&lt;/a&gt;
into a local &lt;code&gt;nixpkgs&lt;/code&gt; directory.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git clone git@github.com:your-user/nixpkgs.git
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/fork-a-repo#configuring-git-to-sync-your-fork-with-the-upstream-repository&quot;&gt;Configure the upstream Nixpkgs repo&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git remote add upstream git@github.com:NixOS/nixpkgs.git
# Check them out
git remove -v
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;The Three Master Branches&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Upstream Master&lt;/strong&gt; (upstream/master):&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Where it is&lt;/strong&gt;: On the official NixOS servers.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Role&lt;/strong&gt;: The absolute source of truth. Thousands of people are pushing to
this daily.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;&lt;strong&gt;Local Master&lt;/strong&gt; (master):&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Where it is&lt;/strong&gt;: On your physical computer.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Role&lt;/strong&gt;: Your working copy. This is the only one you can actually “rebase” or
“commit” to directly.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;&lt;strong&gt;Origin Master&lt;/strong&gt; (origin/master):&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Where it is&lt;/strong&gt;: On your GitHub fork (github.com/your-user/nixpkgs).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Role&lt;/strong&gt;: A personal backup and a place to host your code so you can open Pull
Requests.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Create a branch&lt;/h3&gt;
&lt;p&gt;In the nixpkgs ecosystem, the “cleanest” way to work is to &lt;strong&gt;never&lt;/strong&gt; add your
own commits to your local master.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Keep your &lt;code&gt;master&lt;/code&gt; as a “pure mirror” of &lt;code&gt;upstream/master&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Whenever you want to fix a package or add something new, create a feature
branch:&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git checkout -b fix-my-package master
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This way, syncing is as simple as a reset:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Total reset of your local master to match the official one
git fetch upstream
git checkout master
git reset --hard upstream/master
git push origin master --force
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Pushing a PR&lt;/h3&gt;
&lt;p&gt;When you’re ready to push changes you push to &lt;code&gt;origin/feature-branch&lt;/code&gt;, visit
your fork on github.com and submit the PR.&lt;/p&gt;
&lt;h2&gt;B. Managing Branches with Worktrees&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Use Git worktrees to manage different branches efficiently:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git fetch --all --prune --depth=1
git worktree add -b nixos-unstable nixos-unstable # For just unstable
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Explanation of &lt;code&gt;git worktree&lt;/code&gt;:&lt;/strong&gt; Allows multiple working directories
attached to the same &lt;code&gt;.git&lt;/code&gt; directory, sharing history and objects but with
separate checked-out files.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;git worktree add&lt;/code&gt;: Creates a new working directory for the specified branch
(&lt;code&gt;nixos-unstable&lt;/code&gt; in this case).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;IV. Debugging Missing Dependencies: A Practical Example&lt;/h1&gt;
&lt;details&gt;
&lt;summary&gt; Click to see icat Example &lt;/summary&gt;
&lt;ul&gt;
&lt;li&gt;Let’s say you’re trying to build &lt;code&gt;icat&lt;/code&gt; locally and encounter a missing
dependency error:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;nix-build -A icat
# ... (Error log showing &quot;fatal error: X11/Xlib.h: No such file or directory&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;The error &lt;code&gt;fatal error: X11/Xlib.h: No such file or directory&lt;/code&gt; indicates a
missing X11 dependency.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;A. Online Search with &lt;code&gt;search.nixos.org&lt;/code&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;The Nixpkgs package search website
(&lt;a href=&quot;https://search.nixos.org/packages&quot;&gt;https://search.nixos.org/packages&lt;/a&gt;) is a
valuable first step.&lt;/li&gt;
&lt;li&gt;However, broad terms like “x11” can yield many irrelevant results.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Utilize the left sidebar to filter by package sets (e.g., “xorg”).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;B. Local Source Code Search with &lt;code&gt;rg&lt;/code&gt; (ripgrep)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Familiarity with searching the Nixpkgs source code is crucial for finding
dependencies.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Navigate to your local &lt;code&gt;nixpkgs/&lt;/code&gt; directory and use &lt;code&gt;rg&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;rg &quot;x11 =&quot; pkgs # Case-sensitive search
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Output:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pkgs/tools/X11/primus/default.nix
21:  primus = if useNvidia then primusLib_ else primusLib_.override { nvidia_x11 = null; };
22:  primus_i686 = if useNvidia then primusLib_i686_ else primusLib_i686_.override { nvidia_x11 = null; };

pkgs/applications/graphics/imv/default.nix
38:    x11 = [ libGLU xorg.libxcb xorg.libX11 ];
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Refining the search (case-insensitive):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;rg -i &quot;libx11 =&quot; pkgs
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Output:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# ... (Output showing &quot;xorg.libX11&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The key result is &lt;code&gt;xorg.libX11&lt;/code&gt;, which is likely the missing dependency.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
 &lt;/details&gt;
&lt;h1&gt;V. Local Derivation Search with &lt;code&gt;nix-locate&lt;/code&gt;&lt;/h1&gt;
&lt;details&gt;
&lt;summary&gt; Click to Expand nix-locate command Example&lt;/summary&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;nix-locate&lt;/code&gt; (from the &lt;code&gt;nix-index&lt;/code&gt; package) allows searching for derivations
on the command line.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Install &lt;code&gt;nix-index&lt;/code&gt; and run &lt;code&gt;nix-index&lt;/code&gt; to create the initial
index.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix-locate libx11
# ... (Output showing paths related to libx11)
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Combining online and local search tools (&lt;code&gt;search.nixos.org&lt;/code&gt;, &lt;code&gt;rg&lt;/code&gt;,
&lt;code&gt;nix-locate&lt;/code&gt;) provides a comprehensive approach to finding dependencies.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/details&gt;
&lt;h1&gt;VI. Key Benefits of Working with Nixpkgs Locally (Recap)&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Speed:&lt;/strong&gt; Faster searches and builds compared to remote operations.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Control:&lt;/strong&gt; Full control over the Nixpkgs version.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Up-to-Date Information:&lt;/strong&gt; Repository source often has the latest details.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;VII. Best Practices and Tips from the Community&lt;/h1&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Click To Expand Best Practices and Tips from the community&lt;/summary&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Rebasing over Merging:&lt;/strong&gt; Never merge upstream changes into your local
branch. Always rebase your branch onto the upstream (e.g., &lt;code&gt;master&lt;/code&gt; or
&lt;code&gt;nixos-unstable&lt;/code&gt;) to avoid accidental large-scale pings in pull requests (Tip
from &lt;code&gt;soulsssx3&lt;/code&gt; on Reddit).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Tip from &lt;code&gt;ElvishJErrico&lt;/code&gt;:&lt;/strong&gt; Avoid using Nixpkgs directly as a flake for
local development due to slow copying to &lt;code&gt;/nix/store&lt;/code&gt; and performance issues
with garbage collection on large numbers of small files. Use
&lt;code&gt;nix build -f . &amp;lt;package&amp;gt;&lt;/code&gt; instead of &lt;code&gt;nix build .#&amp;lt;package&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Edge Cases for Flake Syntax:&lt;/strong&gt; Flake syntax might be necessary in specific
scenarios, such as NixOS installer tests where copying the Git history should
be avoided.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Base Changes on &lt;code&gt;nixos-unstable&lt;/code&gt;:&lt;/strong&gt; For better binary cache hits, base your
changes on the &lt;code&gt;nixos-unstable&lt;/code&gt; branch instead of &lt;code&gt;master&lt;/code&gt;. Consider the
merge-base for staging branches as well.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Consider &lt;code&gt;jujutsu&lt;/code&gt;:&lt;/strong&gt; Explore &lt;a href=&quot;https://github.com/jj-vcs/jj&quot;&gt;jj-vcs&lt;/a&gt;, a
Git-compatible alternative that can offer a more intuitive workflow,
especially for large monorepos like Nixpkgs. While it has a learning curve, it
can significantly improve parallel work and branch management.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://saylesss88.github.io/vcs/jujutsu.html&quot;&gt;Intro-To-Jujutsu&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/details&gt;
</content></entry><entry><title>Fork, Clone, Contribute</title><id>https://saylesss88.github.io/nixpkgs/fork_clone_contribute.html</id><updated>2025-11-22T00:00:00+00:00</updated><author><name>saylesss87@proton.me (saylesss88)</name></author><link href="https://saylesss88.github.io/nixpkgs/fork_clone_contribute.html" rel="alternate"/><content type="html">&lt;h1&gt;Fork, Clone, Contribute&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;In the &lt;a href=&quot;https://github.com/NixOS/nixpkgs&quot;&gt;Nixpkgs&lt;/a&gt; Repository.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Click Fork, then Create a new Fork.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Uncheck the box “Only fork the &lt;code&gt;master&lt;/code&gt; branch”, for development we will need
more branches.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If you only fork master, you won’t have the &lt;code&gt;nixos-XX.YY&lt;/code&gt; release branches
available on your fork when you later try to create a PR against them, or
when you want to create a feature branch from them on your fork.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Click &lt;code&gt;&amp;lt;&amp;gt; Code&lt;/code&gt; and Clone the Repo. &lt;code&gt;sayls8&lt;/code&gt; is the name of my GitHub, yours
will obviously be different.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git clone git@github.com:sayls8/nixpkgs.git
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Figure out the branch that should be used for this change by going through
&lt;a href=&quot;https://github.com/NixOS/nixpkgs/blob/master/CONTRIBUTING.md#branch-conventions&quot;&gt;this section&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;When in doubt use &lt;code&gt;master&lt;/code&gt;, that’s where most changes should go. This can be
changed later by
&lt;a href=&quot;https://github.com/NixOS/nixpkgs/blob/master/CONTRIBUTING.md#rebasing-between-branches-ie-from-master-to-staging&quot;&gt;rebasing&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Add &lt;a href=&quot;https://github.com/NixOS/nixpkgs&quot;&gt;Nixpkgs&lt;/a&gt; as your upstream:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd nixpkgs

git remote add upstream https://github.com/NixOS/nixpkgs.git
# Make sure you have the latest changes from upstream Nixpkgs
git fetch upstream
# Show currently configured remote repository
git remote -v
origin  git@github.com:sayls8/nixpkgs.git (fetch)
origin  git@github.com:sayls8/nixpkgs.git (push)
upstream        https://github.com/NixOS/nixpkgs.git (fetch)
upstream        https://github.com/NixOS/nixpkgs.git (push)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Understanding Your Remotes&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This output confirms that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;origin&lt;/code&gt; is your personal fork on GitHub (&lt;code&gt;sayls8/nixpkgs.git&lt;/code&gt;). When you
&lt;code&gt;git push origin ...&lt;/code&gt;, your changes go here.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;upstream&lt;/code&gt; is the official Nixpkgs repository (&lt;code&gt;NixOS/nixpkgs.git&lt;/code&gt;). When you
&lt;code&gt;git fetch upstream&lt;/code&gt;, you’re getting the latest updates from the main project.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This setup ensures you can easily pull updates from the original project and
push your contributions to your own fork.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Shows a ton of remote branches
git branch -r | grep upstream
# Narrow it down
git branch -r | grep upstream | grep nixos-
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next Steps for Contributing&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Ensure &lt;code&gt;master&lt;/code&gt; is up to date with &lt;code&gt;upstream&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git checkout master
git pull upstream master
git push origin master
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;git pull upstream master&lt;/code&gt; is equivalent to running &lt;code&gt;git fetch upstream&lt;/code&gt;
followed by &lt;code&gt;git merge upstream/master&lt;/code&gt; into your current branch (&lt;code&gt;master&lt;/code&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;git push origin master&lt;/code&gt; updates your forks remote with the fetched changes.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This keeps your fork in sync to avoid conflicts.&lt;/p&gt;
&lt;p&gt;If targeting another branch, replace &lt;code&gt;master&lt;/code&gt; with &lt;code&gt;nixos-24.11&lt;/code&gt; for example.&lt;/p&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;Create a Feature Branch&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git checkout master
git checkout -b my-feature-branch # name should represent the feature
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;Make and Test Changes&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/NixOS/nixpkgs/blob/master/pkgs/README.md#conventions&quot;&gt;Packaging Conventions&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;New package&lt;/strong&gt;: Add to
&lt;code&gt;pkgs/by-name/&amp;lt;first-two-letters&amp;gt;/&amp;lt;package-name&amp;gt;/default.nix&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example structure&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{ lib, stdenv, fetchFromGitHub }: stdenv.mkDerivation {
pname = &quot;xyz&quot;; version = &quot;1.2.3&quot;; src = fetchFromGitHub { ... }; ... }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Update package&lt;/strong&gt;: Edit version and &lt;code&gt;sha256&lt;/code&gt; in the package’s &lt;code&gt;default.nix&lt;/code&gt;.
Use &lt;code&gt;nix-prefetch-url&lt;/code&gt; to update hashes:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix-prefetch-url &amp;lt;source-url&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Fix a bug&lt;/strong&gt;: Modify files in &lt;code&gt;pkgs/&lt;/code&gt;, &lt;code&gt;nixos/modules/&lt;/code&gt;, or elsewhere.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Test locally&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;Build:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix-build -A &amp;lt;package-name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Test in a shell&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix-shell -p &amp;lt;package-name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For NixOS modules:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nixos-rebuild test
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Follow the Nixpkgs Contributing Guide.&lt;/p&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;&lt;strong&gt;Commit and Push&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Commit with a clear message, make sure to follow
&lt;a href=&quot;https://github.com/NixOS/nixpkgs/blob/master/CONTRIBUTING.md#commit-conventions&quot;&gt;commit conventions&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Commit Conventions&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Create a commit for each logical unit.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Check for unnecessary whitespace with &lt;code&gt;git diff --check&lt;/code&gt; before committing.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If you have commits &lt;code&gt;pkg-name: oh, forgot to insert whitespace&lt;/code&gt;: squash
commits in this case. Use &lt;code&gt;git rebase -i&lt;/code&gt;. See
&lt;a href=&quot;https://git-scm.com/book/en/v2/Git-Tools-Rewriting-History#_squashing&quot;&gt;Squashing Commits&lt;/a&gt;
for additional information.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;For consistency, there should not be a period at the end of the commit
message’s summary line (the first line of the commit message).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;When adding yourself as maintainer in the same pull request, make a separate
commit with the message maintainers: &lt;code&gt;add &amp;lt;handle&amp;gt;&lt;/code&gt;. Add the commit before
those making changes to the package or module. See
&lt;a href=&quot;https://github.com/NixOS/nixpkgs/blob/master/maintainers/README.md&quot;&gt;Nixpkgs Maintainers&lt;/a&gt;
for details.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Format the commit messages in the following way:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;(pkg-name): (from -&amp;gt; to | init at version | refactor | etc)

(Motivation for change. Link to release notes. Additional information.)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;a) For example, for the &lt;code&gt;airshipper&lt;/code&gt; package:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git add pkgs/by-name/ai/airshipper/
git commit -m &quot;airshipper: init at 0.1.0&quot;

Adds the airshipper tool for managing game assets.
Upstream homepage: https://github.com/someuser/airshipper&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;b) Updating &lt;code&gt;airshipper&lt;/code&gt; to a new version&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git add pkgs/by-name/ai/airshipper/
git commit -m &quot;airshipper: 0.1.0 -&amp;gt; 0.2.0

Updated airshipper to version 0.2.0. This release includes:
- Improved asset fetching logic
- Bug fixes for network errors

Release notes: https://github.com/someuser/airshipper/releases/tag/v0.2.0&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;c) Fixing a bug in &lt;code&gt;airshipper&lt;/code&gt;’s package definition&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git add pkgs/by-name/ai/airshipper/
git commit -m &quot;airshipper: fix: build with latest glibc

Resolved build failures on unstable channel due to changes in glibc.
Patched source to use updated API calls.
&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Examples:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;nginx: init at 2.0.1&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;firefox: 122.0 -&amp;gt; 123.0&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;vim: fix build with gcc13&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Push:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git push origin my-feature-branch
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When you push your feature branch, it will output a link that you can follow to
complete the PR on GitHub.&lt;/p&gt;
&lt;p&gt;If you have the &lt;code&gt;gh-cli&lt;/code&gt; set up you can also do this from the command line:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;gh pr create --repo NixOS/nixpkgs --base master --head sayls8:feat/my-package
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;5&quot;&gt;
&lt;li&gt;Create a Pull Request&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Go to &lt;a href=&quot;https://github.com/sayls8/nixpkgs&quot;&gt;https://github.com/sayls8/nixpkgs&lt;/a&gt;. (your fork) Click the PR prompt for
my-feature-branch. Set the base branch to &lt;code&gt;NixOS/nixpkgs:master&lt;/code&gt; (or
&lt;code&gt;nixos-24.11&lt;/code&gt;). Write a PR description: Purpose of the change. Related issues
(e.g., Fixes #1234). Testing steps (e.g., &lt;code&gt;nix-build -A &amp;lt;package-name&amp;gt;&lt;/code&gt;). Submit
and respond to feedback.&lt;/p&gt;
&lt;ol start=&quot;6&quot;&gt;
&lt;li&gt;Handle Updates&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For reviewer feedback or upstream changes:&lt;/p&gt;
&lt;p&gt;Edit, commit, and push:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git add . git commit -m &quot;&amp;lt;package-name&amp;gt;: address feedback&quot; git push origin my-feature-branch
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Rebase if needed:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git fetch upstream
git rebase upstream/master  # or upstream/nixos-24.11
git push origin my-feature-branch --force
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;7&quot;&gt;
&lt;li&gt;Cleanup&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;After PR merge:&lt;/p&gt;
&lt;p&gt;Delete branch:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git push origin --delete my-feature-branch
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Sync master:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git checkout master
git pull upstream master
git push origin master
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Addressing the Many Branches&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;No need to manage all branches: The &lt;code&gt;nixos-branches&lt;/code&gt; are just metadata from
upstream. You only check out the one you need (e.g., &lt;code&gt;master&lt;/code&gt; or
&lt;code&gt;nixos-24.11&lt;/code&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Focus on relevant branches: The filter (&lt;code&gt;grep nixos-&lt;/code&gt;) shows the key release
branches. Ignore -small branches and older releases unless specifically
required. Confirm latest stable: If you’re targeting a stable branch,
&lt;code&gt;nixos-24.11&lt;/code&gt; is likely the latest (or &lt;code&gt;nixos-25.05&lt;/code&gt; if it’s active). Verify
via NixOS status.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content></entry><entry><title>Local Nixpkgs</title><id>https://saylesss88.github.io/nixpkgs/local_package.html</id><updated>2025-11-22T00:00:00+00:00</updated><author><name>saylesss87@proton.me (saylesss88)</name></author><link href="https://saylesss88.github.io/nixpkgs/local_package.html" rel="alternate"/><content type="html">&lt;details&gt;
&lt;summary&gt; ✔️ Table of Contents&lt;/summary&gt;
&lt;!-- toc --&gt;
&lt;/details&gt;
&lt;h1&gt;Creating and Building a Local Package within a Nixpkgs Clone&lt;/h1&gt;
&lt;p&gt;This chapter demonstrates the fundamental pattern for creating a package. Every
package recipe is a file that declares a function. This function takes the
packages dependencies as argument.&lt;/p&gt;
&lt;p&gt;In this example we’ll make a simple package with &lt;code&gt;coreutils&lt;/code&gt; and build it.
Demonstrating the process of building and testing a local package.&lt;/p&gt;
&lt;p&gt;This chapter will assume you have already have a cloned fork of Nixpkgs. I
choose to clone mine to the &lt;code&gt;~/src/&lt;/code&gt; directory.&lt;/p&gt;
&lt;p&gt;You can check out the &lt;code&gt;nixpkgs/pkgs/README.md&lt;/code&gt;
&lt;a href=&quot;https://github.com/NixOS/nixpkgs/tree/master/pkgs&quot;&gt;Here&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The Nixpkgs Contributing Guide can be found
&lt;a href=&quot;https://github.com/NixOS/nixpkgs/blob/master/CONTRIBUTING.md&quot;&gt;Here&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Create your Package directory and a &lt;code&gt;default.nix&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;For this example, we’ll create a package called &lt;code&gt;testPackage&lt;/code&gt; and will place it
in the &lt;code&gt;nixpkgs/pkgs/misc&lt;/code&gt; directory.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd ~/src/nixpkgs/pkgs/misc
mkdir testPackage &amp;amp;&amp;amp; cd testPackage
hx default.nix
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# default.nix
{
  runCommand,
  coreutils,
}:
runCommand &quot;testPackage&quot; {
  nativeBuildInputs = [
    coreutils
  ];
} &apos;&apos;

  echo &apos;This is a Test&apos; &amp;gt; $out
&apos;&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we need to add our &lt;code&gt;testPackage&lt;/code&gt; to &lt;code&gt;all-packages.nix&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd pkgs/top-level
hx all-packages.nix
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;all-packages.nix&lt;/code&gt; is a centralized module that defines all available package
expressions.&lt;/p&gt;
&lt;p&gt;We’ll add our package in the list alphabetically:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# all-packages.nix
# `/msc` # editor search inside file
# Scroll down to t&apos;s
# snip ...
termusic = callPackage ../applications/autio/termusic { };

# we add our package here
testPackage = callPackage ../misc/testPackage { };

tfk8s = callPackage ../applications/misc/tfk8s { };
# snip ...
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;callPackage&lt;/code&gt; is a core utility in Nixpkgs. It takes a Nix expression (like
our &lt;code&gt;default.nix&lt;/code&gt; file, which defines a function) and automatically provides
the function with any arguments it declares, by looking them up within the
&lt;code&gt;pkgs&lt;/code&gt; set (or the scope where &lt;code&gt;callPackage&lt;/code&gt; is invoked). This means you only
need to list the dependencies your package needs in its &lt;code&gt;default.nix&lt;/code&gt; function
signature, and &lt;code&gt;callPackage&lt;/code&gt; will “inject” the correct versions of those
packages. This is what the &lt;code&gt;callPackage&lt;/code&gt; Nix Pill demonstrates at a lower
level.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Understanding &lt;code&gt;pkgs/by-name/&lt;/code&gt; and other locations&lt;/h2&gt;
&lt;p&gt;Nixpkgs uses different conventions for package placement:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Older categories (e.g., &lt;code&gt;pkgs/misc/&lt;/code&gt;, &lt;code&gt;pkgs/applications/&lt;/code&gt;):&lt;/strong&gt; Packages
within these directories typically use &lt;code&gt;default.nix&lt;/code&gt; as their definition file
(e.g., &lt;code&gt;pkgs/misc/testPackage/default.nix&lt;/code&gt;). &lt;strong&gt;These packages are NOT
automatically included&lt;/strong&gt; in the top-level &lt;code&gt;pkgs&lt;/code&gt; set; they &lt;em&gt;must&lt;/em&gt; be This
chapter will assume you have already have a cloned fork of Nixpkgs. explicitly
added via a &lt;code&gt;callPackage&lt;/code&gt; entry in &lt;code&gt;pkgs/top-level/all-packages.nix&lt;/code&gt;. This is
the method demonstrated in this chapter for our &lt;code&gt;testPackage&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The new &lt;code&gt;pkgs/by-name/&lt;/code&gt; convention:&lt;/strong&gt; This is the &lt;em&gt;preferred location for
new packages&lt;/em&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Packages here are placed in a directory structure like
&lt;code&gt;pkgs/by-name/&amp;lt;first-two-letters&amp;gt;/&amp;lt;package-name&amp;gt;/&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Crucially, their main definition file is named &lt;code&gt;package.nix&lt;/code&gt; (e.g.,
&lt;code&gt;pkgs/by-name/te/testPackage/package.nix&lt;/code&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Packages placed within &lt;code&gt;pkgs/by-name/&lt;/code&gt; are automatically discovered and
exposed&lt;/strong&gt; by Nixpkgs’ top-level &lt;code&gt;pkgs&lt;/code&gt; set. They &lt;strong&gt;do not&lt;/strong&gt; require a manual
&lt;code&gt;callPackage&lt;/code&gt; entry in &lt;code&gt;all-packages.nix&lt;/code&gt;. This results in a more modular
and scalable approach, reducing manual maintenance.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;❗ : While this example uses &lt;code&gt;pkgs/misc/&lt;/code&gt; to demonstrate explicit
&lt;code&gt;callPackage&lt;/code&gt; usage, when contributing a &lt;em&gt;new&lt;/em&gt; package to Nixpkgs, you should
nearly always place it within &lt;code&gt;pkgs/by-name/&lt;/code&gt; and name its definition file
&lt;code&gt;package.nix&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/NixOS/nixpkgs/blob/master/pkgs/by-name/README.md&quot;&gt;pkgs/by-name/README&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;There are some
&lt;a href=&quot;https://github.com/NixOS/nixpkgs/blob/master/pkgs/by-name/README.md#limitations&quot;&gt;Limitations&lt;/a&gt;
to this approach.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/NixOS/nixpkgs-vet&quot;&gt;nixpkgs-vet&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Previously, packages were manually added to &lt;code&gt;all-packages.nix&lt;/code&gt;. While this is no
longer needed in most cases, understanding the old method provides useful
context for troubleshooting legacy configurations or custom integrations.&lt;/p&gt;
&lt;h2&gt;Try Building the Package&lt;/h2&gt;
&lt;p&gt;Move to the root directory of Nixpkgs:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd ~/src/nixpkgs
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Try building it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix-build -A testPackage
this derivation will be built:
this derivation will be built:
  /nix/store/yrbjsxmgzkl24n75sqjfxbpv5cs3b9hc-testPackage.drv
building &apos;/nix/store/yrbjsxmgzkl24n75sqjfxbpv5cs3b9hc-testPackage.drv&apos;...
/nix/store/3012zlv30vn6ifihr1jxbg5z3ysw0hl3-testPackage
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;runCommand&lt;/code&gt; is a simple builder, it takes 3 arguments. The first is the package
name the second is the derivation attributes, and the third is the script to
run.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cat ~/src/nixpkgs/result
───────┬──────────────────────────────
       │ File: result
───────┼──────────────────────────────
   1   │ This is a Test
───────┴──────────────────────────────
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix-instantiate --eval -A testPackage.meta.position
&quot;/home/jr/src/nixpkgs/pkgs/misc/testPackage/default.nix:6&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Tools like &lt;code&gt;nix search&lt;/code&gt; and the Nixpkgs website use the &lt;code&gt;meta&lt;/code&gt; information for
documentation and discoverability. It can also be useful for debugging and helps
to provide better error messages. The above command shows that the
&lt;code&gt;meta.position&lt;/code&gt; attribute points to the file and line where the package
definition begins, which is very useful for debugging.&lt;/p&gt;
&lt;p&gt;Typically a file will have a &lt;code&gt;meta&lt;/code&gt; attribute that looks similar to the
following:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;meta = with lib; {
    homepage = &quot;https://www.openssl.org/&quot;;
    description = &quot;A cryptographic library that implements the SSL and TLS protocols&quot;;
    license = licenses.openssl;
    platforms = platforms.all;
} // extraMeta;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For example, the following shows how Nix is able to discover different parts of
your configuration:&lt;/p&gt;
&lt;p&gt;Launch the &lt;code&gt;nix repl&lt;/code&gt; and load your local flake:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd /src
nix repl
nix-repl&amp;gt; :lf nixpkgs
nix-repl&amp;gt; outputs.legacyPackages.x86_64-linux.openssl.meta.position
&quot;/nix/store/syvnmj3hhckkbncm94kfkbl76qsdqqj3-source/pkgs/development/libraries/openssl/default.nix:303&quot;
nix-repl&amp;gt; builtins.unsafeGetAttrPos &quot;description&quot; outputs.legacyPackages.x86_64-linux.openssl.meta
{
  column = 9;
  file = &quot;/nix/store/syvnmj3hhckkbncm94kfkbl76qsdqqj3-source/pkgs/development/libraries/openssl/default.nix&quot;;
  line = 303;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Lets create just the &lt;code&gt;meta.description&lt;/code&gt; for demonstration purposes.&lt;/p&gt;
&lt;h2&gt;Adding the meta attribute&lt;/h2&gt;
&lt;p&gt;Since we don’t have a &lt;code&gt;meta&lt;/code&gt; attribute this points to a default value that’s
incorrect.&lt;/p&gt;
&lt;p&gt;Let’s add the &lt;code&gt;meta&lt;/code&gt; attribute and try it again:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# default.nix
{
  runCommand,
  coreutils,
}:
runCommand &quot;testPackage&quot; {
  nativeBuildInputs = [
    coreutils
  ];

  meta = {
    description = &quot;test package&quot;;
};
} &apos;&apos;

  echo &apos;This is a Test&apos; &amp;gt; $out
&apos;&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;nix-instantiate --eval -A testPackage.meta.position
&quot;/home/jr/src/nixpkgs/pkgs/misc/testPackage/default.nix:11&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now it points us to the 11’th line, right where our &lt;code&gt;meta.description&lt;/code&gt; is.&lt;/p&gt;
&lt;p&gt;Let’s stage our package so nix recognises it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd ~/nixpkgs
git add pkgs/misc/testPackage/
nix edit .#testPackage
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I used &lt;code&gt;nix edit&lt;/code&gt; here to ensure it was picked up properly.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;default.nix&lt;/code&gt; that we’ve been working on should open in your &lt;code&gt;$EDITOR&lt;/code&gt;&lt;/p&gt;
</content></entry><entry><title>Nixpkgs Overlays</title><id>https://saylesss88.github.io/nixpkgs/overlay.html</id><updated>2025-11-22T00:00:00+00:00</updated><author><name>saylesss87@proton.me (saylesss88)</name></author><link href="https://saylesss88.github.io/nixpkgs/overlay.html" rel="alternate"/><content type="html">&lt;h1&gt;Nixpkgs Overlays&lt;/h1&gt;
&lt;p&gt;The following is done with a local clone of Nixpkgs located at &lt;code&gt;~/src/nixpkgs&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;In this example, we will create an overlay to override the version of
&lt;code&gt;btrfs-progs&lt;/code&gt;. In the root directory of our local clone of Nixpkgs
(i.e.&lt;code&gt;~/src/nixpkgs&lt;/code&gt;) we can run the following command to locate &lt;code&gt;btrfs-progs&lt;/code&gt;
within Nixpkgs:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;fd &apos;btrfs-progs&apos; .
./pkgs/by-name/bt/btrfs-progs/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Open the &lt;code&gt;package.nix&lt;/code&gt; in the above directory and copy the &lt;code&gt;src&lt;/code&gt; block within
the &lt;code&gt;stdenv.mkDerivation&lt;/code&gt; block like so:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# package.nix
  version = &quot;6.14&quot;;

  src = fetchurl {
    url = &quot;mirror://kernel/linux/kernel/people/kdave/btrfs-progs/btrfs-progs-v${version}.tar.xz&quot;;
    hash = &quot;sha256-31q4BPyzbikcQq2DYfgBrR4QJBtDvTBP5Qzj355+PaE=&quot;;
  };
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When we use the above &lt;code&gt;src&lt;/code&gt; block in our overlay we’ll need to add
&lt;code&gt;src = self.fetchurl&lt;/code&gt; for our overlay to have access to &lt;code&gt;fetchurl&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;We will replace the version with our desired version number. To find another
version that actually exists we need to check their github repos
&lt;a href=&quot;https://github.com/kdave/btrfs-progs/releases&quot;&gt;btrfs-progs Releases&lt;/a&gt;. I can see
that the previous version was &lt;code&gt;v6.13&lt;/code&gt;, lets try that.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd ~/src/nixpkgs
hx overlay.nix
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We will change the version to &lt;code&gt;6.13&lt;/code&gt; for demonstration purposes. All that is
really required is changing the version and 1 character in the &lt;code&gt;hash&lt;/code&gt; which
would cause a refetch and recalculation of the hash. We will use an empty string
to follow convention:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# overlay.nix
self: super: {
  btrfs-progs = super.btrfs-progs.overrideAttrs (old: rec {
      version = &quot;6.13&quot;;

      # Notice the `self` added here
      src = self.fetchurl {
        url = &quot;mirror://kernel/linux/kernel/people/kdave/btrfs-progs/btrfs-progs-v${version}.tar.xz&quot;;
        hash = &quot;&quot;;
      };
    };
  });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To build this with the file right from the root of the local Nixpkgs (i.e.
&lt;code&gt;~/src/nixpkgs&lt;/code&gt;) you could run the following. Running the command this way
avoids the impurity of looking it up in the &lt;code&gt;~/.config&lt;/code&gt; directory:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix-build -A btrfs-progs --arg overlays &apos;[ (import ./overlay.nix) ]&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The compiler will give you back the correct &lt;code&gt;hash&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;specified: sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
got:    sha256-ZbPyERellPgAE7QyYg7sxqfisMBeq5cTb/UGx01z7po=
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Replace the empty &lt;code&gt;hash&lt;/code&gt; with the new hash value we just got from the compiler
so the &lt;code&gt;overlay.nix&lt;/code&gt; would look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;self: super: {
  btrfs-progs = super.btrfs-progs.overrideAttrs (old: rec {
    version = &quot;6.13&quot;;

    src = self.fetchurl {
      url = &quot;mirror://kernel/linux/kernel/people/kdave/btrfs-progs/btrfs-progs-v${version}.tar.xz&quot;;
      hash = &quot;sha256-ZbPyERellPgAE7QyYg7sxqfisMBeq5cTb/UGx01z7po=&quot;;
    };
  });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Try building it again:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix-build -A btrfs-progs --arg overlays &apos;[ (import ./overlay.nix) ]&apos;
checking for references to /build/ in /nix/store/szd6lizahidjniz85a0g1wsrfknirhwb-btrfs-progs-6.13...
gzipping man pages under /nix/store/szd6lizahidjniz85a0g1wsrfknirhwb-btrfs-progs-6.13/share/man/
patching script interpreter paths in /nix/store/szd6lizahidjniz85a0g1wsrfknirhwb-btrfs-progs-6.13
/nix/store/szd6lizahidjniz85a0g1wsrfknirhwb-btrfs-progs-6.13/bin/fsck.btrfs: interpreter directive changed from &quot;#!/bin/sh -f&quot; to &quot;/nix/store/xy4jjgw87sbgwylm5kn047d9gkbhsr9x-bash-5.2p37/bin/sh -f&quot;
stripping (with command strip and flags -S -p) in  /nix/store/szd6lizahidjniz85a0g1wsrfknirhwb-btrfs-progs-6.13/lib /nix/store/szd6lizahidjniz85a0g1wsrfknirhwb-btrfs-progs-6.13/bin
/nix/store/szd6lizahidjniz85a0g1wsrfknirhwb-btrfs-progs-6.13
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can inspect it with the repl:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd ~/src/nixpkgs
nix repl
nix-repl&amp;gt; :a import ./. { overlays = [ (import ./overlay.nix) ]; }
nix-repl&amp;gt; btrfs-progs
«derivation /nix/store/6yxhj84cwcsnrd87rcxbd6w08l9ikc6p-btrfs-progs-6.13.drv»
nix-repl&amp;gt; btrfs-progs.drvAttrs.buildInputs
[
  «derivation /nix/store/yg4llzkcla5rppv8r1iikyamfxg3g4sg-acl-2.3.2.drv»
  «derivation /nix/store/vqczbcwjnid6bs4cv3skl7kyd6kkzcfx-attr-2.5.2.drv»
  «derivation /nix/store/xrvx0azszpdh2x0lnldakqx25vfxab19-e2fsprogs-1.47.2.drv»
  «derivation /nix/store/iil4b8adk615zhp6wmzjx16z1v2f8f4j-util-linux-minimal-2.41.drv»
  «derivation /nix/store/wwld8wp91m26wz69gp8vzh090sh5ygxd-lzo-2.10.drv»
  «derivation /nix/store/w4ncw24gdfkbx9779xpgjli5sagi506m-systemd-minimal-libs-257.5.drv»
  «derivation /nix/store/dmh4lvmq6n8hy56q93kplvnfnlwqzzv5-zlib-1.3.1.drv»
  «derivation /nix/store/h8iwhnr636dwb72qqcyzp111ajjxgzr2-zstd-1.5.7.drv»
]
nix-repl&amp;gt; btrfs-progs.drvAttrs.version
&quot;6.13&quot;
nix-repl&amp;gt; btrfs-progs.drvAttrs.src
«derivation /nix/store/y5nkz1xczxha4xl93qq3adndyc46dcvf-btrfs-progs-v6.13.tar.xz.drv»
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Using &lt;code&gt;:a&lt;/code&gt; adds the attributes from the resulting set into scope and avoids
bringing the entire &lt;code&gt;nixpkgs&lt;/code&gt; set into scope.&lt;/p&gt;
&lt;p&gt;To see whats available, you can for example type &lt;code&gt;btrfs-progs.drvAttrs.&lt;/code&gt; then
hit &lt;code&gt;TAB&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Another way to do this is to move our overlay to the
&lt;code&gt;~/.config/nixpkgs/overlays&lt;/code&gt; directory and rename the file like the following,
agian this adds an impurity because it relies on your &lt;code&gt;~/.config&lt;/code&gt; directory
which is different from user to user:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mv overlay.nix ~/.config/nixpkgs/overlays/btrfs-progs.nix
cd ~/src/nixpkgs
nix-build -A btrfs-progs
checking for references to /build/ in /nix/store/szd6lizahidjniz85a0g1wsrfknirhwb-btrfs-progs-6.13...
gzipping man pages under /nix/store/szd6lizahidjniz85a0g1wsrfknirhwb-btrfs-progs-6.13/share/man/
patching script interpreter paths in /nix/store/szd6lizahidjniz85a0g1wsrfknirhwb-btrfs-progs-6.13
/nix/store/szd6lizahidjniz85a0g1wsrfknirhwb-btrfs-progs-6.13/bin/fsck.btrfs: interpreter directive changed from &quot;#!/bin/sh -f&quot; to &quot;/nix/store/xy4jjgw87sbgwylm5kn047d9gkbhsr9x-bash-5.2p37/bin/sh -f&quot;
stripping (with command strip and flags -S -p) in  /nix/store/szd6lizahidjniz85a0g1wsrfknirhwb-btrfs-progs-6.13/lib /nix/store/szd6lizahidjniz85a0g1wsrfknirhwb-btrfs-progs-6.13/bin
/nix/store/szd6lizahidjniz85a0g1wsrfknirhwb-btrfs-progs-6.13
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Overlays with Flakes&lt;/h2&gt;
&lt;p&gt;In a flake, overlays are defined in the &lt;code&gt;outputs.overlays&lt;/code&gt; attribute set of the
&lt;code&gt;flake.nix&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;They are then applied to &lt;code&gt;nixpkgs&lt;/code&gt; inputs using
&lt;code&gt;inputs.nixpkgs.follows = &quot;nixpkgs&quot;;&lt;/code&gt; (or similar) and the overlays attribute on
the input.&lt;/p&gt;
&lt;p&gt;Example of flake usage:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# flake.nix
{
  description = &quot;My NixOS flake with custom overlays&quot;;

  inputs = {
    nixpkgs.url = &quot;github:NixOS/nixpkgs/nixos-unstable&quot;;
  };

  outputs = { self, nixpkgs, ... }: {

    overlays.myCustomOverlay = final: prev: {
      btrfs-progs = prev.btrfs-progs.overrideAttrs (old: rec {
        version = &quot;6.13&quot;;
        src = self.fetchurl {
          url = &quot;mirror://kernel/linux/kernel/people/kdave/btrfs-progs/btrfs-progs-v${version}.tar.xz&quot;;
          hash = &quot;sha256-ZbPyERellPgAE7QyYg7sxqfisMBeq5cTb/UGx01z7po=&quot;;
        };
      });
    };

    nixosConfigurations.my-system = nixpkgs.lib.nixosSystem {
      system = &quot;x86_64-linux&quot;;
      modules = [
        # Apply the overlay
        { nixpkgs.overlays = [ self.overlays.myCustomOverlay ]; }
        ./configuration.nix
      ];
    };
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix flake show
path:/home/jr/btrfs-progs?lastModified=1749655369&amp;amp;narHash=sha256-ln6dLiqo7TxStQSXgcIwfbdt7STGw4ZHftZRfWpY/JQ%3D
├───nixosConfigurations
│   └───my-system: NixOS configuration
└───overlays
    └───myCustomOverlay: Nixpkgs overlay
&lt;/code&gt;&lt;/pre&gt;
</content></entry><entry><title>Debugging NixOS modules</title><id>https://saylesss88.github.io/Debugging_and_Tracing_NixOS_Modules_9.html</id><updated>2025-11-22T00:00:00+00:00</updated><author><name>saylesss87@proton.me (saylesss88)</name></author><link href="https://saylesss88.github.io/Debugging_and_Tracing_NixOS_Modules_9.html" rel="alternate"/><content type="html">&lt;h1&gt;Chapter 11&lt;/h1&gt;
&lt;p&gt;This chapter covers debugging NixOS modules, focusing on tracing module options
and evaluating merges.&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Click to Expand Table of Contents&lt;/summary&gt;
&lt;!-- toc --&gt;
&lt;/details&gt;
&lt;h2&gt;Debugging and Tracing NixOS Modules&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://saylesss88.github.io/images/coding4.png&quot; alt=&quot;404&quot; /&gt;&lt;/p&gt;
&lt;!-- ![gruv17](images/gruv17.png) --&gt;
&lt;ul&gt;
&lt;li&gt;Other related post if you haven’t read my previous post on modules, that may
be helpful before reading this one:
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://saylesss88.github.io/posts/nix_modules_explained/&quot;&gt;nix-modules-explained&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;This post is my notes following Nix Hour 40. If it seems a little chaotic,
try watching one. They are hard to follow if you’re not extremely familiar
with the concepts.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=aLy8id4wr-M&amp;amp;t=2120s&quot;&gt;Nix Hour 40&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Nix Code is particularly hard to &lt;strong&gt;debug&lt;/strong&gt; because of (e.g. lazy evaluation,
declarative nature, layered modules)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The following simple Nix code snippet illustrates a basic NixOS module
definition and how options are declared and configured. We’ll use this example
to demonstrate fundamental debugging techniques using &lt;code&gt;nix-instantiate&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;let
  lib = import &amp;lt;nixpkgs/lib&amp;gt;;
in
lib.evalModules {
  modules = [
    ({ lib, ... }: {
      options.foo = lib.mkOption {
        # type = lib.types.raw;
        type = lib.types.anything;
        # default = pkgs;
      };
      config.foo = {
        bar = 10;
        list = [1 2 3 ];
        baz = lib.mkDefault &quot;baz&quot;;
      };
    })
    {
      foo.baz = &quot;bar&quot;;
    }
  ];
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;In the above code, adding &lt;code&gt;lib&lt;/code&gt; to the function arguments isn’t required but
if you were to move the module to another file it would fail without it
because &lt;code&gt;lib&lt;/code&gt; comes from outside of it. So it’s good practice to refer to
&lt;code&gt;lib&lt;/code&gt; in the modules themselves.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You should &lt;strong&gt;always&lt;/strong&gt; assign a type to your options, if you don’t know which
type to use you could use &lt;code&gt;raw&lt;/code&gt;. &lt;code&gt;raw&lt;/code&gt; is a type that doesn’t do any
processing. So if you were to assign the entire packages set to the option
e.g. &lt;code&gt;default = pkgs;&lt;/code&gt; it wouldn’t recurseinto all the packages and try to
evaluate them. There is also &lt;code&gt;anything&lt;/code&gt;, that is useful if you do want to
recurse into the values.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The following is an example of how you would run this inside vim/neovim, the
rest of the examples will be from the command line:&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-vim&quot;&gt;:!nix-instantiate --eval -A config.foo --strict
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Output&lt;/strong&gt;:&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt; Click to Expand the Output &lt;/summary&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;{ bar = 10; baz = &quot;bar&quot;; list = [ 1 2 3 ]; }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To show the difference you could uncomment the &lt;code&gt;raw&lt;/code&gt; type and comment the
&lt;code&gt;anything&lt;/code&gt; type and run the above command again you’ll see that you get an
error:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;error: The option &apos;foo&apos; is defined multiple times while it&apos;s expected to be
unique
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To execute this command on the command line:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix-instantiate --eval --strict -A config.foo
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It will show you the start of a trace. To get the full trace add:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix-instantiate --eval --strict -A config.foo --show-trace
&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;
&lt;h2&gt;Example 2&lt;/h2&gt;
&lt;details&gt;
&lt;summary&gt; Click to Expand Example 2 &lt;/summary&gt;
&lt;p&gt;In the previous example, we looked at a simplified module. Now, let’s examine a
more realistic scenario involving a basic NixOS configuration file
(&lt;code&gt;configuration.nix&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;This example will demonstrate how to use &lt;code&gt;nix-instantiate&lt;/code&gt; to evaluate an entire
system configuration and how &lt;code&gt;--show-trace&lt;/code&gt; helps in diagnosing errors within
this context.&lt;/p&gt;
&lt;p&gt;Consider the following &lt;code&gt;configuration.nix&lt;/code&gt; file:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# configuration.nix
{ lib, ... }: {
  boot.loader.grub.device = &quot;nodev&quot;;
  fileSystems.&quot;/&quot;.device = &quot;/devst&quot;;
  system.stateVersion = &quot;24.11&quot;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;This configuration snippet sets the GRUB bootloader device, defines a root
filesystem, and specifies the expected NixOS state version. To evaluate this
entire system configuration, you can use &lt;code&gt;nix-instantiate&lt;/code&gt; and point it to the
&lt;code&gt;&amp;lt;nixpkgs/nixos&amp;gt;&lt;/code&gt; entrypoint, providing our &lt;code&gt;configuration.nix&lt;/code&gt; file as an
argument. The &lt;code&gt;-A system&lt;/code&gt; flag selects the top-level &lt;code&gt;system&lt;/code&gt; attribute, which
represents the instantiated system configuration.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Run&lt;/strong&gt; it in with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix-instantiate &apos;&amp;lt;nixpkgs/nixos&amp;gt;&apos; --arg configuration ./configuration.nix -A system
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Output&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;/nix/store/kfcwvvpdbsb3xcks1s76id16i1mc3l5k-nixos-system-nixos-25.05pre-git.drv
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ok, we can see that this successfully &lt;em&gt;instantiates&lt;/em&gt;. Let’s introduce an error
to trace:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{ lib, ... }: {
  boot.loader.grub.device = &quot;nodev&quot;;
  fileSystems.&quot;/&quot;.device = &quot;/devst&quot;;
  system.stateVersion = builtins.genList &quot;24.11&quot; null;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Output&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;(stack trace truncated; use &apos;--show-trace&apos; to show the full, detailed trace)
error: expected an integer but found null: null
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Rerun the command with &lt;code&gt;--show-trace&lt;/code&gt; appended:&lt;/p&gt;
&lt;p&gt;Or on the command line&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix-instantiate &apos;&amp;lt;nixpkgs/nixos&amp;gt;&apos; --arg configuration ./configuration.nix -A system --show-trace
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;This outputs a much longer trace than the first example. It shows you the file
the error occured in and you can see that in this case they are a lot of
internal functions. (e.g.
&lt;code&gt;at /nix/store/ccfwxygjrarahgfv5865x2f828sjr5h0- source/lib/attrsets.nix:1529:14:&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To show your own error message you could do something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{lib, ...}: {
  boot.loader.grub.device = &quot;nodev&quot;;
  fileSystems.&quot;/&quot;.device = &quot;/devst&quot;;
  system.stateVersion = builtins.addErrorContext &quot;AAAAAAAAAAAAAAAAA&quot; (builtins.genList &quot;24.11&quot; null);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Run it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix-instantiate &apos;&amp;lt;nixpkgs/nixos&amp;gt;&apos; --arg configuration ./configuration.nix -A system --show-trace`
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Output&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt; … while evaluating the attribute &apos;value&apos;
     at /nix/store/ccfwxygjrarahgfv5865x2f828sjr5h0-source/lib/modules.nix:770:21:
      769|             inherit (module) file;
      770|             inherit value;
         |                     ^
      771|           }) module.config

   … AAAAAAAAAAAAAAAAA

   … while calling the &apos;genList&apos; builtin
     at /home/jr/tests/configuration.nix:4:71:
        3|   fileSystems.&quot;/&quot;.device = &quot;/devst&quot;;
        4|   system.stateVersion = builtins.addErrorContext &quot;AAAAAAAAAAAAAAAAA&quot;
         (builtins.genList &quot;24.11&quot; null);
         |                                                                       ^
        5| }

   … while evaluating the second argument passed to builtins.genList

   error: expected an integer but found null: null
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;In the latest nix they actually inverted the error messages so the most
relevant parts will be at the bottom.&lt;/li&gt;
&lt;/ul&gt;
&lt;/details&gt;
&lt;h2&gt;Example 3&lt;/h2&gt;
&lt;details&gt;
&lt;summary&gt; Click to Expand Example 3 &lt;/summary&gt;
&lt;p&gt;Let’s consider another example, this time demonstrating the definition of
configuration options using &lt;code&gt;lib.mkOption&lt;/code&gt; within a module structure.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# default.nix
let
  lib = import &amp;lt;nixpkgs/lib&amp;gt;;
in
lib.evalModules {
  modules = [
    ({ lib, ... }: {
      options.ints = lib.mkOption {
        type = lib.types.attrsOf lib.types.int;
      };
      options.strings = lib.mkOption {
        type = lib.types.string;
        # type = lib.types.attrsOf lib.types.string;
        default = &quot;foo&quot;;
      };
    })
  ];
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Instantiate&lt;/strong&gt; this with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix-instantiate --eval --strict -A config.strings
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Output&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;evaluation warning: The type `types.string` is deprecated.
See https://github.com/NixOS/nixpkgs/pull/66346 for better alternative types.
&quot;foo&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Unfortunately you won’t get the same depreciation warning from &lt;code&gt;lib.attrsOf&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Below is an interesting way to provide nixpkgs run it on the command line:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;export NIX_PATH=nixpkgs=channel:nixpkgs-unstable
echo $NIX_PATH
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Output&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nixpkgs=channel:nixpkgs-unstable
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The next two commands are to check that after using the above way to provide
&lt;code&gt;nixpkgs-unstable&lt;/code&gt; that they both point to the same store path, the following
command will fetch nixpkgs from the channel above:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix-instantiate --find-file nixpkgs
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Output&lt;/strong&gt; 1️⃣&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;/nix/store/ydrgwsibghsyx884qz97zbs1xs93yk11-source
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix-instantiate --eval channel:nixpkgs-unstable -A path
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Output&lt;/strong&gt;: 2️⃣&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;/nix/store/ydrgwsibghsyx884qz97zbs1xs93yk11-source
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;As you can see both commands produce the same store path&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Example 4&lt;/h2&gt;
&lt;p&gt;In our previous example, we encountered a deprecation warning for
&lt;code&gt;lib.types.string&lt;/code&gt;. This next example delves deeper into why that type was
deprecated and demonstrates the consequences of its behavior, along with the
recommended fix.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# default.nix
let
  lib = import &amp;lt;nixpkgs/lib&amp;gt;;
in
  lib.evalModules {
    modules = [
      ({lib, ...}: {
        options.ints = lib.mkOption {
          type = lib.types.attrsOf lib.types.int;
        };
        options.strings = lib.mkOption {
          # type = lib.types.string;
          type = lib.types.attrsOf lib.types.string;
          default = {
            x = &quot;foo&quot;;
          };
        };
        config = {
          strings = lib.mkOptionDefault {
            x = &quot;bar&quot;;
          };
        };
      })
    ];
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Evaluate it with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix-instantiate --eval --strict -A config.strings
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;types.string&lt;/code&gt; depricated because it silently concatenates strings&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The above command has two options with the same priority level and evaluates
to &lt;code&gt;{ x = &quot;foobar&quot;; }&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Output:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;evaluation warning: The type `types.string` is deprecated. See https://github.
com/NixOS/nixpkgs/pull/66346 for better alternative types.
{ x = &quot;foobar&quot;; }
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;types.str&lt;/code&gt; was the replacement for the depricated &lt;code&gt;types.string&lt;/code&gt;:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# default.nix
let
  lib = import &amp;lt;nixpkgs/lib&amp;gt;;
in
  lib.evalModules {
    modules = [
      ({lib, ...}: {
        options.ints = lib.mkOption {
          type = lib.types.attrsOf lib.types.int;
        };
        options.strings = lib.mkOption {
          # type = lib.types.string;
          type = lib.types.attrsOf lib.types.str;
          # Sets the value with a lower priority: lib.mkOptionDefault
          default = {
            x = &quot;foo&quot;;
          };
        };
        config = {
          strings = lib.mkOptionDefault {
            x = &quot;bar&quot;;
          };
        };
      })
    ];
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Output:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;error:
… while evaluating the attribute &apos;x&apos;

… while evaluating the attribute &apos;value&apos;
 at /nix/store/ydrgwsibghsyx884qz97zbs1xs93yk11-source/lib/modules.nix:1148:41:
 1147|
 1148|     optionalValue = if isDefined then { value = mergedValue; } else { };
     |                                         ^
 1149|   };

… while calling the &apos;foldl&apos;&apos; builtin
 at /nix/store/ydrgwsibghsyx884qz97zbs1xs93yk11-source/lib/options.nix:508:8:
  507|     else
  508|       (foldl&apos; (
     |        ^
  509|         first: def:

(stack trace truncated; use &apos;--show-trace&apos; to show the full, detailed trace)

error: The option `strings.x&apos; has conflicting definition values:
- In `&amp;lt;unknown-file&amp;gt;&apos;: &quot;foo&quot;
- In `&amp;lt;unknown-file&amp;gt;&apos;: &quot;bar&quot;
Use `lib.mkForce value` or `lib.mkDefault value` to change the priority on any of these definitions.

shell returned 1
&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;So types in the module system aren’t just types in the conventional sense but
they also specify the emerging behavior of these values.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If we switch the type in the above example to &lt;code&gt;types.lines&lt;/code&gt; you get this
returned, &lt;code&gt;{ x = &quot;foo\nbar&quot;; }&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;mkOptionDefault&lt;/code&gt; isn’t typically something you should generally use, instead
options have a &lt;code&gt;default&lt;/code&gt; setting&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If you want to make sure that you set a default but if the user specifies it,
it shouldn’t get overridden. You should not set it in the following:&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;options.strings = lib.mkOption {
  type = lib.types.attrsOf lib.types.lines;
  default = {
    x = &quot;foo&quot;;
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Because the above uses &lt;code&gt;mkOptionDefault&lt;/code&gt; but instead in under the &lt;code&gt;config&lt;/code&gt;
attribute like the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# ...snip...
options.strings = lib.mkOption {
  type = lib.types.attrsOf lib.types.lines;
  # default = {
    # x = &quot;foo&quot;;
  # };
};
config = {
  strings = {
    x = lib.mkDefault &quot;foo&quot;;
  };
};
# ...snip...
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;let
  lib = import &amp;lt;nixpkgs/lib&amp;gt;;
in
  lib.evalModules {
    modules = [
      ({lib, ...}: {
        options.ints = lib.mkOption {
          type = lib.types.attrsOf lib.types.int;
        };
        options.strings = lib.mkOption {
          # type = lib.types.string;
          type = lib.types.attrsOf lib.types.str;
          # Sets the value with a lower priority: lib.mkOptionDefault
          #default = {
          #  x = &quot;foo&quot;;
          #};
        };
        config.strings = {
          x = &quot;foo&quot;;
        };
      })
      {
        config.strings = {
          y = &quot;bar&quot;;
        };
      }
    ];
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Output&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;This works now because there’s no difference between &lt;code&gt;x&lt;/code&gt; and &lt;code&gt;y&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;{ x = &quot;foo&quot;; y = &quot;bar&quot;; }
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;More Functionality between modules&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;let
  lib = import &amp;lt;nixpkgs/lib&amp;gt;;
in
  lib.evalModules {
    modules = [
      ({lib, ...}: {
        options.ints = lib.mkOption {
          type = lib.types.attrsOf lib.types.int;
        };
        options.strings = lib.mkOption {
          # type = lib.types.string;
          type = lib.types.attrsOf lib.types.str;
          # Sets the value with a lower priority: lib.mkOptionDefault
          #default = {
          #  x = &quot;foo&quot;;
          #};
        };
        config.strings = {
          x = lib.mkDefault &quot;foo&quot;;
        };
      })
      {
        config.strings = {
          x = &quot;x&quot;;
          y = &quot;bar&quot;;
        };
      }
    ];
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;The above command would cause a conflict without the &lt;code&gt;x = lib.mkDefault foo&lt;/code&gt;
And this is typically what you want to do for defaults and modules in things
like nested configuration.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Output:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;{ x = &quot;x&quot;; y = &quot;bar&quot;; }
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Infinite recursion error&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;A common pitfall is to introduce a hard to debug error &lt;code&gt;infinite recursion&lt;/code&gt;
when shadowing a name. The simplest example for this is:&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;let a = 1; in rec { a = a; }
&lt;/code&gt;&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;💡&lt;strong&gt;TIP&lt;/strong&gt;: Avoid &lt;code&gt;rec&lt;/code&gt;. Use &lt;code&gt;let ... in&lt;/code&gt; Example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;let
 a = 1;
in {
 a = a;
 b = a + 2;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;details&gt;
&lt;summary&gt; Click to Expand a more involved infinite recursion error &lt;/summary&gt;
&lt;p&gt;We’ll separate the logic for this example, this will be the &lt;code&gt;default.nix&lt;/code&gt; this
is where having &lt;code&gt;lib&lt;/code&gt; defined in your inline modules is helpful because you can
just delete the section and paste it into your &lt;code&gt;modules.nix&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# default.nix
let
  lib = import &amp;lt;nixpkgs/lib&amp;gt;;
in
  lib.evalModules {
    modules = [
      ./module.nix
    ];
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And in the &lt;code&gt;module.nix&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# module.nix
{ lib, pkgs, ...}: {
  options.etc = lib.mkOption {
    type = lib.types.attrsOf lib.types.path;
    default = { };
    description = &apos;&apos;
      Specifies which paths are is /etc/
    &apos;&apos;;
  };

  config._module.args.pkgs = import &amp;lt;nixpkgs&amp;gt; {
    config = {};
    overlays = [];
  };
  config.etc.foo = pkgs.writeText &quot;foo&quot; &apos;&apos;
    foo configuration
  &apos;&apos;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;If you evaluate this with the following you will get an infinite recursion
error.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix-instantiate --eval --strict -A config.etc
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;This happens because &lt;code&gt;--strict&lt;/code&gt; evaluates the &lt;code&gt;etc&lt;/code&gt;, then it goes into the
&lt;code&gt;attrsOf&lt;/code&gt;, and the &lt;code&gt;path&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix repl
nix-repl&amp;gt; :l &amp;lt;nixpkgs&amp;gt;
nix-repl&amp;gt; hello.out.out.out
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;:l &amp;lt;nixpkgs&amp;gt;&lt;/code&gt; loads the Nixpkgs library into the repl environment, making its
definitions available.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;hello&lt;/code&gt; refers to the &lt;code&gt;hello&lt;/code&gt; package definition within Nixpkgs. Packages in
Nixpkgs are defined as &lt;em&gt;derivations&lt;/em&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;.out&lt;/code&gt; is a common attribute name for the &lt;em&gt;main output&lt;/em&gt; of a derivation (e.g.,
the installed package). Some packages, especially those with complex build
processes or multiple outputs, might have nested output attributes. In the
case of &lt;code&gt;hello&lt;/code&gt;, accessing &lt;code&gt;.out.out.out&lt;/code&gt; ultimately leads us to the
&lt;em&gt;derivation&lt;/em&gt; itself.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The key takeaway here is that when you evaluate a package in the &lt;code&gt;nix repl&lt;/code&gt;,
you’re often interacting with its derivation or one of its output paths in the
Nix store. The &lt;code&gt;«derivation ...»&lt;/code&gt; indicates that &lt;code&gt;hello.out.out.out&lt;/code&gt; evaluates
to a derivation – the blueprint for building the &lt;code&gt;hello&lt;/code&gt; package. This is in
contrast to &lt;code&gt;--eval --strict&lt;/code&gt;, which tries to fully evaluate values, potentially
leading to infinite recursion if it encounters a derivation that refers back to
itself indirectly during attribute evaluation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Output:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;«derivation /nix/store/b1vcpm321dwbwx6wj4n13l35f4y2wrfv-hello-2.12.1.drv»
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;So it recurses through the entire thing and tries to evaluate its string.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So we want to change the command from &lt;code&gt;--eval --strict&lt;/code&gt; which is only based on
evaluation to at least &lt;code&gt;nix-instantiate&lt;/code&gt; which is based on derivations:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix-instantiate -A config.etc
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Output:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;warning: you did not specify &apos;--add-root&apos;; the result might be removed by the garbage collector
/nix/store/abyfp1rxk73p0n5kfilv7pawxwvc7hsg-foo.drv
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;We don’t really have a derivation yet for example:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# module.nix
{
  lib,
  pkgs,
  ...
}: {
  options.etc = lib.mkOption {
    type = lib.types.attrsOf (lib.types.attrsOf lib.types.path);
    default = {};
    description = &apos;&apos;
      Specifies which paths are in /etc/
    &apos;&apos;;
  };

  config._module.args.pkgs = import &amp;lt;nixpkgs&amp;gt; {
    config = {};
    overlays = [];
  };
  config.etc.foo.bar = pkgs.writeText &quot;foo&quot; &apos;&apos;
    foo configuration
  &apos;&apos;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Try to evaluate the above command with &lt;code&gt;nix-instantiate -A config.etc&lt;/code&gt; and Nix
doesn’t even try to build it. With nested &lt;code&gt;attrsOf&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix repl -f default.nix
nix-repl&amp;gt; config.etc
{
  foo = { ... };
}
nix-repl&amp;gt; config.etc.foo
{
  bar = «derivation /nix/store/abyfp1rxk73p0n5kfilv7pawxwvc7hsg-foo.drv»;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;So &lt;code&gt;config.foo&lt;/code&gt; is an attribute set and &lt;code&gt;config.etc.foo&lt;/code&gt; is also an attribute
set but it’s not a derivation by itself. So &lt;code&gt;nix-instantiate&lt;/code&gt; does this one
level of recursion here and it would have built &lt;code&gt;foo&lt;/code&gt; value if it were a
derivation.&lt;/li&gt;
&lt;/ul&gt;
&lt;/details&gt;
&lt;h3&gt;Example 5&lt;/h3&gt;
&lt;details&gt;
&lt;summary&gt; Click to Expand Example 5 &lt;/summary&gt;
&lt;p&gt;We’ll use the same &lt;code&gt;module.nix&lt;/code&gt; and &lt;code&gt;default.nix&lt;/code&gt; from the previous example.&lt;/p&gt;
&lt;p&gt;Building More Complex Configurations with Modules In this next example, we’ll
focus on a common task in system configuration: managing files within the
&lt;code&gt;/etc/&lt;/code&gt; directory. We’ll define a module that allows us to specify the content
of arbitrary files in &lt;code&gt;/etc/&lt;/code&gt; and then use a special Nix function to combine
these individual file definitions into a single, manageable entity.&lt;/p&gt;
&lt;p&gt;We’ll introduce a new option, &lt;code&gt;options.etc&lt;/code&gt;, which will allow us to define the
content of files within &lt;code&gt;/etc/&lt;/code&gt;. Then, we’ll use &lt;code&gt;pkgs.linkFarm&lt;/code&gt; to create a
derivation that represents the entire &lt;code&gt;/etc/&lt;/code&gt; directory as a collection of
symbolic links pointing to the individual file contents we’ve defined. This
demonstrates how modules can abstract away the details of creating complex
system configurations, providing a declarative and reproducible way to manage
even fundamental aspects of the operating system.&lt;/p&gt;
&lt;p&gt;Let’s show how we can use Nix modules to declaratively manage the &lt;code&gt;/etc/&lt;/code&gt;
directory&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# default.nix
let
  lib = import &amp;lt;nixpkgs/lib&amp;gt;;
in
  lib.evalModules {
    modules = [
      ./module.nix
    ];
  }

&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# module.nix
{
  lib,
  pkgs,
  config,
  ...
}: {
  options.etc = lib.mkOption {
    type = lib.types.attrsOf (lib.types.attrsOf lib.types.path);
    default = {};
    description = &apos;&apos;
      Specifies which paths are in /etc/
    &apos;&apos;;
  };
  options.etcCombined = lib.mkOption {
    type = lib.types.package;
    default =
      pkgs.linkFarm &quot;etc&quot;
      (lib.mapAttrsToList (name: value: {
        name = name;
        path = value;
      }) config.etc);
  };

  config._module.args.pkgs = import &amp;lt;nixpkgs&amp;gt; {
    config = {};
    overlays = [];
  };
  config.etc.foo = pkgs.writeText &quot;foo&quot; &apos;&apos;
    foo configuration
  &apos;&apos;;
  config.etc.bar = pkgs.writeText &quot;bar&quot; &apos;&apos;
    bar configuration
  &apos;&apos;;
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Run it with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix-instantiate -A config.etcCombined
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Output&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;/nix/store/3da61nmfk546qn2zpxsm57mq6vz6fjx8-etc.drv
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;So we can see that it will instantiate, lets see if it will build:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix-build -A config.etcCombined
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Output&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;these 3 derivations will be built:
/nix/store/41yfxq4af1vrs0rrgfk5gc36kmjc7270-bar.drv
/nix/store/abyfp1rxk73p0n5kfilv7pawxwvc7hsg-foo.drv
/nix/store/3da61nmfk546qn2zpxsm57mq6vz6fjx8-etc.drv
building &apos;/nix/store/41yfxq4af1vrs0rrgfk5gc36kmjc7270-bar.drv&apos;...
building &apos;/nix/store/abyfp1rxk73p0n5kfilv7pawxwvc7hsg-foo.drv&apos;...
building &apos;/nix/store/3da61nmfk546qn2zpxsm57mq6vz6fjx8-etc.drv&apos;...
/nix/store/ca3wyk5m3qhy8n1nbn0181m29qvp1klp-etc
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix-build -A config.etcCombined &amp;amp;&amp;amp; ls result/ -laa
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Output&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;/nix/store/ca3wyk5m3qhy8n1nbn0181m29qvp1klp-etc
dr-xr-xr-x - root 31 Dec  1969  .
drwxrwxr-t - root 16 May 15:13  ..
lrwxrwxrwx - root 31 Dec  1969  bar -&amp;gt; /nix/store/1fsjyc2hmilab1qw6jfkf6cb767kz858-bar
lrwxrwxrwx - root 31 Dec  1969  foo -&amp;gt; /nix/store/wai5dycp0zx1lxg0rhpdxnydhiadpk05-foo
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;We can see that &lt;code&gt;foo&lt;/code&gt; and &lt;code&gt;bar&lt;/code&gt; link to different derivations&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;When trying to figure out which &lt;code&gt;default&lt;/code&gt; to use for &lt;code&gt;etcCombined&lt;/code&gt; infinisil
went to the Nixpkgs Reference Manual. Make sure to go to the correct version.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://nixos.org/manual/nixpkgs/stable/&quot;&gt;24.11pre-git&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://nixos.org/manual/nixpkgs/unstable/&quot;&gt;25.05pre-git&lt;/a&gt; (i.e. unstable)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Once at the website press &lt;code&gt;Ctrl+f&lt;/code&gt; and type &lt;code&gt;symlinkjoin&lt;/code&gt; and hit enter.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Or in your local copy of Nixpkgs you could go to
&lt;code&gt;nixpkgs/pkgs/build-support/ trivial-builders/default.nix&lt;/code&gt;. Then use your
editors search feature, with nvim and helix you press &lt;code&gt;/symlinkjoin&lt;/code&gt; or
&lt;code&gt;/linkFarm&lt;/code&gt; hit enter then press &lt;code&gt;n&lt;/code&gt; to cycle to the next match. It will bring
you to comments and up to date information.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# linkFarm &quot;myexample&quot; [ { name = &quot;hello-test&quot;; path = pkgs.hello; }
# { name = &quot;foobar&quot;; path = pkgs.stack; } ]
&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;
&lt;h3&gt;Tests&lt;/h3&gt;
&lt;details&gt;
&lt;summary&gt; Click to Expand Test Example &lt;/summary&gt;
&lt;ul&gt;
&lt;li&gt;How to create a Derivation with &lt;code&gt;passthru.tests&lt;/code&gt; outside of Nixpkgs and then
run tests available to your package set?&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mkdir passthru-tests &amp;amp;&amp;amp; cd passthru-tests
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create a &lt;code&gt;default.nix&lt;/code&gt; with the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# default.nix
let
  pkgs = import &amp;lt;nixpkgs&amp;gt; {};

  package = pkgs.runCommand &quot;foo&quot; {
    passthru.tests.simple = pkgs.runCommand &quot;foo-test&quot; {} &apos;&apos;
      if [[ &quot;$(cat ${package})&quot; != &quot;foo&quot; ]]; then
        echo &quot;Result is not foo&quot;
        exit 1
      fi
      touch $out
  &apos;&apos;;
  } &apos;&apos;
    echo foo &amp;gt; $out
  &apos;&apos;;
in
package
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;See if it will build:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix-build
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Try running the test:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix-build -A passthru.tests
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;this derivation will be built:
/nix/store/pqpqq9x1wnsabzbsb52z4g4y4zy6p7yx-foo-test.drv
building &apos;/nix/store/pqpqq9x1wnsabzbsb52z4g4y4zy6p7yx-foo-test.drv&apos;...
/nix/store/7bbw2ban0mgkh4d59yz3cnai4aavwvb6-foo-test
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Test 2&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;passthru.tests&lt;/code&gt; is the convention for defining tests associated with a
derivation. The attributes in &lt;code&gt;passthru&lt;/code&gt; are preserved and accessible after
the derivation is built.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;let
  pkgs = import &amp;lt;nixpkgs&amp;gt; {};

  package =
    pkgs.runCommand &quot;foo&quot; {
      passthru.tests.simple = pkgs.runCommand &quot;foo-test&quot; {} &apos;&apos;
        if [[ &quot;$(cat ${package})&quot; != &quot;foo&quot; ]]; then
          echo &quot;Result is not foo&quot;
          exit 1
        fi
        touch $out
      &apos;&apos;;

      passthru.tests.version = pkgs.testers.testVersion {
         package = package;
         version = &quot;1.2&quot;;
     };

      # pkgs.writeShellApplication
      script = &apos;&apos;
        #!${pkgs.runtimeShell}
        echo &quot;1.2&quot;
      &apos;&apos;;
      passAsFiles = [ &quot;script&quot; ];

    } &apos;&apos;
      cp &quot;$scriptPath&quot; &quot;$out&quot;
    &apos;&apos;;
in
  package
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Try to build it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix-build -A passthru.tests
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;testers.testVersion&lt;/code&gt; checks if an executable outputs a specific version
string.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;nix-build -A passthru.tests&lt;/code&gt; specifically targets the derivations defined
within the tests attribute of the main derivation.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;these 3 derivations will be built:
  /nix/store/lyz86bd78p7f3yjy1qky6annmggymcwd-foo.drv
  /nix/store/s4iawjy5zpv89dbkc3zz7z3ngz4jq2cv-foo-test.drv
  /nix/store/z3gi4pb8jn2h9rvk4dhba85fiphp5g4z-foo-test-version.drv
building &apos;/nix/store/lyz86bd78p7f3yjy1qky6annmggymcwd-foo.drv&apos;...
cp: cannot stat &apos;&apos;: No such file or directory
error: builder for &apos;/nix/store/lyz86bd78p7f3yjy1qky6annmggymcwd-foo.drv&apos;
 failed with exit code 1;
     last 1 log lines:
     &amp;gt; cp: cannot stat &apos;&apos;: No such file or directory
     For full logs, run:
       nix log /nix/store/lyz86bd78p7f3yjy1qky6annmggymcwd-foo.drv
error: 1 dependencies of derivation &apos;/nix/store/z3gi4pb8jn2h9rvk4dhba85fiphp5g4z
-foo-test-version.drv&apos; failed to build
error: build of &apos;/nix/store/s4iawjy5zpv89dbkc3zz7z3ngz4jq2cv-foo-test.drv&apos;,
 &apos;/nix/store/z3gi4pb8jn2h9rvk4dhba85fiphp5g4z-foo-test-version.drv&apos; failed
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Run &lt;code&gt;nix-build&lt;/code&gt; with no arguments:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix-build
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix derivation show /nix/store/lyz86bd78p7f3yjy1qky6annmggymcwd-foo.drv | jq &apos;.[].env&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Output&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;__structuredAttrs&quot;: &quot;&quot;,
  &quot;buildCommand&quot;: &quot;cp \&quot;$scriptPath\&quot; \&quot;$out\&quot;\n&quot;,
  &quot;buildInputs&quot;: &quot;&quot;,
  &quot;builder&quot;: &quot;/nix/store/xg75pc4yyfd5n2fimhb98ps910q5lm5n-bash-5.2p37/bin/bash&quot;,
  &quot;cmakeFlags&quot;: &quot;&quot;,
  &quot;configureFlags&quot;: &quot;&quot;,
  &quot;depsBuildBuild&quot;: &quot;&quot;,
  &quot;depsBuildBuildPropagated&quot;: &quot;&quot;,
  &quot;depsBuildTarget&quot;: &quot;&quot;,
  &quot;depsBuildTargetPropagated&quot;: &quot;&quot;,
  &quot;depsHostHost&quot;: &quot;&quot;,
  &quot;depsHostHostPropagated&quot;: &quot;&quot;,
  &quot;depsTargetTarget&quot;: &quot;&quot;,
  &quot;depsTargetTargetPropagated&quot;: &quot;&quot;,
  &quot;doCheck&quot;: &quot;&quot;,
  &quot;doInstallCheck&quot;: &quot;&quot;,
  &quot;enableParallelBuilding&quot;: &quot;1&quot;,
  &quot;enableParallelChecking&quot;: &quot;1&quot;,
  &quot;enableParallelInstalling&quot;: &quot;1&quot;,
  &quot;mesonFlags&quot;: &quot;&quot;,
  &quot;name&quot;: &quot;foo&quot;,
  &quot;nativeBuildInputs&quot;: &quot;&quot;,
  &quot;out&quot;: &quot;/nix/store/9mcrnddb6lf1md14v4lj6s089i99l5k7-foo&quot;,
  &quot;outputs&quot;: &quot;out&quot;,
  &quot;passAsFile&quot;: &quot;buildCommand&quot;,
  &quot;passAsFiles&quot;: &quot;script&quot;,
  &quot;patches&quot;: &quot;&quot;,
  &quot;propagatedBuildInputs&quot;: &quot;&quot;,
  &quot;propagatedNativeBuildInputs&quot;: &quot;&quot;,
  &quot;script&quot;: &quot;#!/nix/store/xg75pc4yyfd5n2fimhb98ps910q5lm5n-bash-5.2p37/bin/bash\necho \&quot;1.2\&quot;\n&quot;,
  &quot;stdenv&quot;: &quot;/nix/store/lgydi1gl5wqcw6k4gyjbaxx7b40zxrsp-stdenv-linux&quot;,
  &quot;strictDeps&quot;: &quot;&quot;,
  &quot;system&quot;: &quot;x86_64-linux&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix derivation show /nix/store/lyz86bd78p7f3yjy1qky6annmggymcwd-foo.drv | jq
 &apos;.[].env.buildCommand&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Output:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;&quot;cp \&quot;$scriptPath\&quot; \&quot;$out\&quot;\n&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;raw mode below&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix derivation show /nix/store/lyz86bd78p7f3yjy1qky6annmggymcwd-foo.drv | jq
 &apos;.[].env.buildCommand&apos; -r
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Output&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cp &quot;$scriptPath&quot; &quot;$out&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;It turns out the correct command was &lt;code&gt;passAsFile&lt;/code&gt; not &lt;code&gt;passAsFiles&lt;/code&gt; but that
change wasn’t enough to fix it. &lt;code&gt;passAsFiles&lt;/code&gt; expects a list of files, not a
single file path. Running &lt;code&gt;nix-build -A passthru.tests&lt;/code&gt; failed saying
&lt;code&gt;&amp;gt; foo --version returned a non-zero exit code.&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;let
  pkgs = import &amp;lt;nixpkgs&amp;gt; {};

  package =
    pkgs.runCommand &quot;foo&quot; {
      #passthru.tests.simple = pkgs.runCommand &quot;foo-test&quot; {} &apos;&apos;
      #  if [[ &quot;$(cat ${package})&quot; != &quot;foo&quot; ]]; then
      #    echo &quot;Result is not foo&quot;
      #    exit 1
      #  fi
      #  touch $out
      #&apos;&apos;;

      passthru.tests.version = pkgs.testers.testVersion {
        package = package;
        version = &quot;1.2&quot;;
      };

      # pkgs.writeShellApplication
      script = &apos;&apos;
        #!${pkgs.runtimeShell}
        echo &quot;1.2&quot;
      &apos;&apos;;
      passAsFile = [&quot;script&quot;];
    } &apos;&apos;
      mkdir -p &quot;$out/bin&quot;
      cp &quot;$scriptPath&quot; &quot;$out/bin/foo&quot;
      chmod +x &quot;$out/bin/foo&quot;
    &apos;&apos;;
in
  package
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Build it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix-build -A passthru.tests
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Output:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;these 2 derivations will be built:
  /nix/store/lqrlcd64dmpzkggcfzlnsnwjd339czd3-foo.drv
  /nix/store/c3kw4xbdlrig08jrdm5wis1dmv2gnqsd-foo-test-version.drv
building &apos;/nix/store/lqrlcd64dmpzkggcfzlnsnwjd339czd3-foo.drv&apos;...
building &apos;/nix/store/c3kw4xbdlrig08jrdm5wis1dmv2gnqsd-foo-test-version.drv&apos;...
1.2
/nix/store/zsbk5zawak68ailvkwi2gad2bqbqmdz9-foo-test-version
&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;
&lt;h3&gt;Key Takeaways for Debugging NixOS Modules&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;nix-instantiate&lt;/code&gt; is Your Friend:&lt;/strong&gt; Use &lt;code&gt;nix-instantiate&lt;/code&gt; to evaluate your
NixOS modules and pinpoint errors.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Unlock Details with &lt;code&gt;--show-trace&lt;/code&gt;:&lt;/strong&gt; When errors occur, always append
&lt;code&gt;--show-trace&lt;/code&gt; to get a comprehensive stack trace, revealing the origin of the
problem. Remember that in newer Nix versions, the most relevant parts of the
trace are often at the bottom.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Understand Option Types:&lt;/strong&gt; Nix option types (&lt;code&gt;raw&lt;/code&gt;, &lt;code&gt;anything&lt;/code&gt;,
&lt;code&gt;string&lt;/code&gt;/&lt;code&gt;str&lt;/code&gt;, &lt;code&gt;lines&lt;/code&gt;, &lt;code&gt;attrsOf&lt;/code&gt;) are not just about data types; they also
dictate how values are merged and processed within the module system.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Be Mindful of &lt;code&gt;mkOptionDefault&lt;/code&gt;:&lt;/strong&gt; While useful in specific scenarios,
&lt;code&gt;mkOptionDefault&lt;/code&gt; sets a lower priority default. For standard defaults that
can be overridden by user configuration, define them directly within the
&lt;code&gt;config&lt;/code&gt; attribute using &lt;code&gt;lib.mkDefault&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Use &lt;code&gt;builtins.addErrorContext&lt;/code&gt;:&lt;/strong&gt; Enhance your custom error messages by
providing specific context relevant to your module’s logic using
&lt;code&gt;builtins.addErrorContext&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Derivations vs. Evaluation:&lt;/strong&gt; Be aware of the difference between evaluating
expressions (&lt;code&gt;--eval --strict&lt;/code&gt;) and instantiating derivations
(&lt;code&gt;nix-instantiate&lt;/code&gt;). Strict evaluation can trigger infinite recursion if it
encounters unevaluated derivations with cyclic dependencies during attribute
access.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Explore with &lt;code&gt;nix repl&lt;/code&gt;:&lt;/strong&gt; The &lt;code&gt;nix repl&lt;/code&gt; allows you to interactively
explore Nix expressions and the outputs of derivations, providing insights
into the structure and values within Nixpkgs.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Conclusion&lt;/h4&gt;
&lt;p&gt;This chapter has equipped you with essential techniques for debugging and
tracing NixOS modules. We’ve explored how to use &lt;code&gt;nix-instantiate&lt;/code&gt; and
&lt;code&gt;--show-trace&lt;/code&gt; to pinpoint errors, how to interpret Nix’s often-verbose error
messages, and how to leverage the &lt;code&gt;nix repl&lt;/code&gt; for interactive exploration.
Understanding option types and the nuances of &lt;code&gt;mkOptionDefault&lt;/code&gt; is crucial for
writing robust and predictable modules. We’ve also touched upon the distinction
between evaluation and instantiation, and how that impacts debugging.&lt;/p&gt;
&lt;p&gt;While these tools and techniques are invaluable for understanding and
troubleshooting your own Nix configurations, they also become essential when you
want to contribute to or modify the vast collection of packages and modules
within &lt;strong&gt;Nixpkgs&lt;/strong&gt; itself. Nixpkgs is where the majority of Nix packages and
NixOS modules reside, and learning how to navigate and contribute to it opens up
a whole new level of control and customization within the Nix ecosystem.&lt;/p&gt;
&lt;p&gt;In the next chapter,
&lt;a href=&quot;https://saylesss88.github.io/Working_with_Nixpkgs_Locally_10.html&quot;&gt;Working with Nixpkgs Locally&lt;/a&gt;,
we’ll shift our focus to exploring and modifying Nixpkgs. We’ll cover how to
clone Nixpkgs, how to make changes to package definitions, and how to test those
changes locally before contributing them back upstream. This chapter will
empower you to not just use existing Nix packages, but also to customize and
extend them to fit your specific needs.&lt;/p&gt;
</content></entry><entry><title>Nix Module System Explained</title><id>https://saylesss88.github.io/NixOS_Modules_Explained_3.html</id><updated>2025-11-21T00:00:00+00:00</updated><author><name>saylesss87@proton.me (saylesss88)</name></author><link href="https://saylesss88.github.io/NixOS_Modules_Explained_3.html" rel="alternate"/><content type="html">&lt;h1&gt;Chapter 3&lt;/h1&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Click to Expand Table of Contents&lt;/summary&gt;
&lt;!-- toc --&gt;
&lt;/details&gt;
&lt;h2&gt;Nix Module System Explained&lt;/h2&gt;
&lt;!-- ![gruv3](images/gruv3.png) --&gt;
&lt;p&gt;&lt;img src=&quot;https://saylesss88.github.io/images/buildings1.png&quot; alt=&quot;buildings&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;: In this chapter, we will break down the Nix module system used by
both NixOS and Home-Manager. We will discuss using home-manager as a module and
the flexibility that modules give us. We will touch on options and break down
the &lt;code&gt;vim&lt;/code&gt; module from the Nixpkgs collection. Finally we will display how to
test modules with the repl.&lt;/p&gt;
&lt;p&gt;Your &lt;code&gt;configuration.nix&lt;/code&gt; is a module. For the Nixpkgs collection most modules
are in &lt;code&gt;nixos/modules&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The suggested way of using &lt;code&gt;home-manager&lt;/code&gt; according to their manual is as a
&lt;a href=&quot;https://nix-community.github.io/home-manager/index.xhtml#sec-install-nixos-module&quot;&gt;NixOS module&lt;/a&gt;.
Both home-manager and NixOS use the same module system.&lt;/p&gt;
&lt;h2&gt;Module Structure&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{
  imports = [
    # Paths to other modules.
    # Compose this module out of smaller ones.
  ];

  options = {
    # Option declarations.
    # Declare what settings a user of this module can set.
    # Usually this includes a global &quot;enable&quot; option which defaults to false.
  };

  config = {
    # Option definitions.
    # Define what other settings, services and resources should be active.
    # Usually these depend on whether a user of this module chose to &quot;enable&quot; it
    # using the &quot;option&quot; above.
    # Options for modules imported in &quot;imports&quot; can be set here.
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;imports&lt;/code&gt;, &lt;code&gt;options&lt;/code&gt;, and &lt;code&gt;config&lt;/code&gt; are the top-level attributes of a Nix module.
They are the primary, reserved keys that the Nix module system recognizes and
processes to combine different configurations into a single, cohesive system or
user environment. &lt;code&gt;config&lt;/code&gt; is the same &lt;code&gt;config&lt;/code&gt; you receive as a module argument
(e.g. &lt;code&gt;{ pkgs, config, ... }:&lt;/code&gt; at the top of your module function)&lt;/p&gt;
&lt;p&gt;Understanding &lt;code&gt;config&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;config&lt;/code&gt; is the big constantly updated blueprint of your entire system.&lt;/p&gt;
&lt;p&gt;Every time you bring in a new module, it adds its own settings and options to
this blueprint. So, when a module receives the &lt;code&gt;config&lt;/code&gt; argument, it’s getting
the complete picture of everything you’ve asked NixOS to set up so far.&lt;/p&gt;
&lt;p&gt;This allows the module to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;See what other parts of your system are doing.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Make smart decisions based on those settings.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add its own pieces to the overall plan, building on what’s already there.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Most modules are functions that take an attribute set and return an attribute
set.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To turn the above module into a function accepting an attribute set just add the
function arguments to the top, click the eye to see the whole module:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{ config, pkgs, ... }:
~ {
~   imports = [
~     # Paths to other modules.
~     # Compose this module out of smaller ones.
~   ];
~
~   options = {
~     # Option declarations.
~     # Declare what settings a user of this module can set.
~     # Usually this includes a global &quot;enable&quot; option which defaults to false.
~   };
~
~   config = {
~     # Option definitions.
~     # Define what other settings, services and resources should be active.
~     # Usually these depend on whether a user of this module chose to &quot;enable&quot; it
~     # using the &quot;option&quot; above.
~     # Options for modules imported in &quot;imports&quot; can be set here.
~   };
~ }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It may require the attribute set to contain:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;config&lt;/code&gt;: The configuration of the entire system.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;options&lt;/code&gt;: All option declarations refined with all definition and declaration
references.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;pkgs&lt;/code&gt;: The attribute set extracted from the Nix package collection and
enhanced with the &lt;code&gt;nixpkgs.config&lt;/code&gt; option.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;modulesPath&lt;/code&gt;: The location of the module directory of NixOS.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Modularize your configuration.nix&lt;/h2&gt;
&lt;p&gt;Many people start of using a single &lt;code&gt;configuration.nix&lt;/code&gt; and eventually their
single file configuration gets too large to search through and maintain
conveniently.&lt;/p&gt;
&lt;p&gt;This is where &lt;strong&gt;modules&lt;/strong&gt; come in allowing you to break up your configuration
into logical parts. Your &lt;code&gt;boot.nix&lt;/code&gt; will contain settings and options related to
the actual boot process. You’re &lt;code&gt;services.nix&lt;/code&gt; will only have services and so
on…&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;These modules are placed in a logical path relative to either your
&lt;code&gt;configuration.nix&lt;/code&gt; or equivalent or if you’re using flakes relative to your
&lt;code&gt;flake.nix&lt;/code&gt; or equivalent.
&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;imports&lt;/code&gt; mechanism takes paths to other modules as its argument and
combines them to be included in the evaluation of the system configuration.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{ ... }:
{
  imports = [
     # Paths to other modules

     # They can be relative paths
     ./otherModule.nix

     # Or absolute
     /path/to/otherModule.nix

     # Or to a directory
     ../modules/home/shells/nushell
  ];
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;❗: The &lt;strong&gt;imports&lt;/strong&gt; mechanism includes and evaluates the Nix expression found
at the given path &lt;em&gt;as a module&lt;/em&gt;. If that path is a directory, it will
automatically look for and evaluate a &lt;code&gt;default.nix&lt;/code&gt; file within that directory
&lt;em&gt;as a module&lt;/em&gt;. It is common to have that &lt;code&gt;default.nix&lt;/code&gt; be a function that only
imports and combines all the modules in said directory. Like the above
example, in the nushell directory would be a &lt;code&gt;default.nix&lt;/code&gt; that is
automatically imported and evaluated.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Crucial Distinction: &lt;code&gt;imports&lt;/code&gt; vs. &lt;code&gt;import&lt;/code&gt;&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;Beginners often confuse the modules attribute &lt;code&gt;imports = [./module.nix]&lt;/code&gt; here
with the Nix builtins function &lt;code&gt;import module.nix&lt;/code&gt;. The first expects a path to
a file containing a NixOS module (having the same specific structure we’re
describing here), while the second loads whatever Nix expression is in that file
(no expected structure). –NixOS Wiki.&lt;/p&gt;
&lt;p&gt;Considering &lt;code&gt;configuration.nix&lt;/code&gt; is a module, it can be imported like any other
module and this is exactly what you do when getting started with flakes.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# flake.nix
{
  description = &quot;NixOS configuration&quot;;

  inputs = {
    nixpkgs.url = &quot;github:nixos/nixpkgs/nixos-unstable&quot;;
    home-manager.url = &quot;github:nix-community/home-manager&quot;;
    home-manager.inputs.nixpkgs.follows = &quot;nixpkgs&quot;;
  };

  outputs = inputs@{ nixpkgs, home-manager, ... }: {
    nixosConfigurations = {
      hostname = nixpkgs.lib.nixosSystem {
        system = &quot;x86_64-linux&quot;;
        modules = [
          ./configuration.nix
          home-manager.nixosModules.home-manager
          {
            home-manager.useGlobalPkgs = true;
            home-manager.useUserPackages = true;
            home-manager.users.jdoe = ./home.nix;

            # Optionally, use home-manager.extraSpecialArgs to pass
            # arguments to home.nix
          }
        ];
      };
    };
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;modules = [...]&lt;/code&gt; in &lt;code&gt;flake.nix&lt;/code&gt;: This is effectively the initial &lt;code&gt;imports&lt;/code&gt; list
for your entire NixOS system or Home Manager user configuration. It tells the
Nix module system: “Start by collecting and merging the configurations defined
in these specific modules.”&lt;/p&gt;
&lt;p&gt;The above example is what you get from running:
&lt;code&gt;nix flake new /etc/nixos -t github:nix-community/home-manager#nixos&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;If you notice the &lt;code&gt;home-manager.nixosModules.home-manager&lt;/code&gt;, that is what imports
home-manager as a module.&lt;/p&gt;
&lt;p&gt;You could also make the actual home-manager module and import it like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# home-manager.nix
{ inputs, outputs, ... }: {
  imports = [
    # Import home-manager&apos;s NixOS module
    inputs.home-manager.nixosModules.home-manager
  ];

  home-manager = {
    extraSpecialArgs = { inherit inputs outputs; };
    users = {
      # Import your home-manager configuration
      your-username = import ../home-manager/home.nix;
    };
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This “module” isn’t much different from the one included in the &lt;code&gt;flake.nix&lt;/code&gt;
above, it is just shown here to show the flexibility of modules. They can be as
big and complex or as small and simple as you want. You can break up every
single program or component of your configuration into individual modules or
have modules that bundle similar programs the choice is yours.&lt;/p&gt;
&lt;p&gt;Then in your &lt;code&gt;configuration.nix&lt;/code&gt; or equivalent you would add &lt;code&gt;home-manager.nix&lt;/code&gt;
to your imports list and you would have home-manager as a NixOS module.&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;
✔️ Refresher (Click to Expand):
&lt;/summary&gt;
&lt;p&gt;An &lt;strong&gt;attribute set&lt;/strong&gt; is a collection of name-value pairs called &lt;em&gt;attributes&lt;/em&gt;:&lt;/p&gt;
&lt;p&gt;Attribute sets are written enclosed in curly braces &lt;code&gt;{}&lt;/code&gt;. Attribute names and
attribute values are separated by an equal sign &lt;code&gt;=&lt;/code&gt;. Each value can be an
arbitrary expression, terminated by a semicolon &lt;code&gt;;&lt;/code&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;:&lt;a href=&quot;https://nix.dev/manual/nix/2.24/language/syntax#attrs-literal&quot;&gt;nix.dev reference&lt;/a&gt;
This defines an attribute set with attributes named:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;x&lt;/code&gt; with the value &lt;code&gt;123&lt;/code&gt;, an integer&lt;/li&gt;
&lt;li&gt;&lt;code&gt;text&lt;/code&gt; with the value &lt;code&gt;&quot;Hello&quot;&lt;/code&gt;, a string&lt;/li&gt;
&lt;li&gt;&lt;code&gt;y&lt;/code&gt; where the value is the result of applying the function &lt;code&gt;f&lt;/code&gt; to the
attribute set &lt;code&gt;{bla = 456; }&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{
 x = 123;
 text = &quot;Hello&quot;;
 y = f { bla = 456; };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{ a = &quot;Foo&quot;; b = &quot;Bar&quot;}.a
~ &quot;Foo&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;p&gt;Attributes can appear in any order. An attribute name may only occur once in
each attribute set.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;❗ Remember &lt;code&gt;{}&lt;/code&gt; is a valid attribute set in Nix.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The following is a &lt;strong&gt;function&lt;/strong&gt; with an attribute set argument, remember that
anytime you see a &lt;code&gt;:&lt;/code&gt; in Nix code it means this is a function. To the left is
the &lt;strong&gt;function arguments&lt;/strong&gt; and to the right is the &lt;strong&gt;function body&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{ a, b }: a + b
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The simplest possible &lt;strong&gt;NixOS Module&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{ ... }:
{
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;
&lt;p&gt;NixOS produces a full system configuration by combining smaller, more isolated
and reusable components: &lt;strong&gt;Modules&lt;/strong&gt;. If you want to understand Nix and NixOS
make sure you grasp modules!&lt;/p&gt;
&lt;p&gt;A NixOS module defines configuration options and behaviors for system
components, allowing users to extend, customize, and compose configurations
declaratively.&lt;/p&gt;
&lt;p&gt;A &lt;strong&gt;module&lt;/strong&gt; is a file containing a Nix expression with a specific structure. It
&lt;em&gt;declares&lt;/em&gt; options for other modules to define (give a value). Modules were
introduced to allow extending NixOS without modifying its source code.&lt;/p&gt;
&lt;p&gt;To define any values, the module system first has to know which ones are
allowed. This is done by declaring options that specify which attributes can be
set and used elsewhere.&lt;/p&gt;
&lt;p&gt;If you want to write your own modules, I recommend setting up
&lt;a href=&quot;https://github.com/nix-community/nixd?tab=readme-ov-file&quot;&gt;nixd&lt;/a&gt; or
&lt;a href=&quot;https://github.com/oxalica/nil&quot;&gt;nil&lt;/a&gt; with your editor of choice. This will
allow your editor to warn you about missing arguments and dependencies as well
as syntax errors.&lt;/p&gt;
&lt;h3&gt;Declaring Options&lt;/h3&gt;
&lt;p&gt;Options are declared under the top-level &lt;code&gt;options&lt;/code&gt; attribute with
&lt;code&gt;lib.mkOption&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://nixos.org/manual/nixpkgs/stable/#function-library-lib.options.mkOption&quot;&gt;mkOption&lt;/a&gt;
Creates an Option attribute set. It accepts an attribute set with certain keys
such as, &lt;code&gt;default&lt;/code&gt;, &lt;code&gt;package&lt;/code&gt;, and &lt;code&gt;example&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# options.nix
{ lib, ... }:
{
  options = {
    name = lib.mkOption { type = lib.types.str; };
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;lib&lt;/code&gt; provides helper functions from &lt;code&gt;nixpkgs.lib&lt;/code&gt; and the ellipsis (&lt;code&gt;...&lt;/code&gt;) is
for arbitrary arguments which means that this function is prepared to accept
&lt;strong&gt;any additional arguments&lt;/strong&gt; that the caller might provide, even if those
arguments are not explicitly named or used within the module’s body. They make
the modules more flexible, without the &lt;code&gt;...&lt;/code&gt; each module would have to
explicitly list every possible argument it might receive, which would be
cumbersome and error-prone. So &lt;code&gt;{lib, ... }:&lt;/code&gt; means that “I need the &lt;code&gt;lib&lt;/code&gt;
argument” &lt;strong&gt;and&lt;/strong&gt; I acknowledge that the module system might pass other
arguments automatically (like &lt;code&gt;config&lt;/code&gt;, &lt;code&gt;pkgs&lt;/code&gt;, etc.) and I’m fine with them
being there, even if I don’t use them directly in this specific module file.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Defining Values&lt;/h3&gt;
&lt;p&gt;Options are &lt;strong&gt;set&lt;/strong&gt; or &lt;strong&gt;defined&lt;/strong&gt; under the top-level &lt;code&gt;config&lt;/code&gt; attribute:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# config.nix
{ ... }:
{
  config = {
    name = &quot;Slick Jones&quot;;
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this &lt;strong&gt;option declaration&lt;/strong&gt;, we created an option &lt;code&gt;name&lt;/code&gt; of type &lt;em&gt;string&lt;/em&gt; and
set that same option to a string.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Option Definitions&lt;/strong&gt; can be in a separate file than &lt;strong&gt;Option Declarations&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;Evaluating Modules&lt;/h3&gt;
&lt;p&gt;Modules are &lt;strong&gt;evaluated&lt;/strong&gt; with
&lt;a href=&quot;https://nixos.org/manual/nixpkgs/stable/#module-system-lib-evalModules&quot;&gt;lib.evalModules&lt;/a&gt;
&lt;code&gt;lib.evalModules&lt;/code&gt; evaluates a set of modules, typically once per application
(e.g. once for NixOS and once for Home-Manager).&lt;/p&gt;
&lt;h2&gt;Checking out the Vim module provided by Nixpkgs&lt;/h2&gt;
&lt;p&gt;The following is &lt;code&gt;nixpkgs/nixos/modules/programs/vim.nix&lt;/code&gt;, a module that is
included in the Nixpkgs collection:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{
  config,
  lib,
  pkgs,
  ...
}:

let
  cfg = config.programs.vim;
in
{
  options.programs.vim = {
    enable = lib.mkEnableOption &quot;Vi IMproved, an advanced text&quot;;

    defaultEditor = lib.mkEnableOption &quot;vim as the default editor&quot;;

    package = lib.mkPackageOption pkgs &quot;vim&quot; { example = &quot;vim-full&quot;; };
  };

  # TODO: convert it into assert after 24.11 release
  config = lib.mkIf (cfg.enable || cfg.defaultEditor) {
    warnings = lib.mkIf (cfg.defaultEditor &amp;amp;&amp;amp; !cfg.enable) [
      &quot;programs.vim.defaultEditor will only work if programs.vim.enable is
       enabled, which will be enforced after the 24.11 release&quot;
    ];
    environment = {
      systemPackages = [ cfg.package ];
      variables.EDITOR = lib.mkIf cfg.defaultEditor (lib.mkOverride 900 &quot;vim&quot;);
      pathsToLink = [ &quot;/share/vim-plugins&quot; ];
    };
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It provides options to enable Vim, set it as the default editor, and specify the
Vim package to use.&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Breakdown of the vim module.(Click to Expand)&lt;/summary&gt;
1. Module Inputs and Structure:
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{
  config,
  lib,
  pkgs,
  ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Inputs&lt;/strong&gt;: The module takes the above inputs and &lt;code&gt;...&lt;/code&gt; (catch-all for other
args)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;config&lt;/code&gt;: Allows the module to read option values (e.g.
&lt;code&gt;config.programs.vim.enable&lt;/code&gt;). It provides access to the evaluated
configuration.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;lib&lt;/code&gt;: The Nixpkgs library, giving us helper functions like &lt;code&gt;mkEnableOption&lt;/code&gt; ,
&lt;code&gt;mkIf&lt;/code&gt;, and &lt;code&gt;mkOverride&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;pkgs&lt;/code&gt;: The Nixpkgs package set, used to access packages like &lt;code&gt;pkgs.vim&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;...&lt;/code&gt;: Allows the module to accept additional arguments, making it flexible
for extension in the future.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;Key Takeaways: A NixOS module is typically a function that can include
&lt;code&gt;config&lt;/code&gt;, &lt;code&gt;lib&lt;/code&gt;, and &lt;code&gt;pkgs&lt;/code&gt;, but it doesn’t require them. The &lt;code&gt;...&lt;/code&gt; argument
ensures flexibility, allowing a module to accept extra inputs without breaking
future compatibility. Using &lt;code&gt;lib&lt;/code&gt; simplifies handling options (mkEnableOption,
mkIf, mkOverride) and helps follow best practices. Modules define options,
which users can set in their configuration, and &lt;code&gt;config&lt;/code&gt;, which applies
changes based on those options.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;Local Configuration Reference:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;let
  cfg = config.programs.vim;
in
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is a local alias. Instead of typing &lt;code&gt;config.programs.vim&lt;/code&gt; over and over,
the module uses &lt;code&gt;cfg&lt;/code&gt;.&lt;/p&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;Option Declaration&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;options.programs.vim = {
  enable = lib.mkEnableOption &quot;Vi IMproved, an advanced text&quot;;
  defaultEditor = lib.mkEnableOption &quot;vim as the default editor&quot;;
  package = lib.mkPackageOption pkgs &quot;vim&quot; { example = &quot;vim-full&quot;; };
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This defines three user-configurable options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;enable&lt;/code&gt;: Turns on Vim support system-wide.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;defaultEditor&lt;/code&gt;: Sets Vim as the system’s default &lt;code&gt;$EDITOR&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;package&lt;/code&gt;: lets the user override which Vim package is used.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;mkPackageOption&lt;/code&gt; is a helper that defines a package-typed option with a
default (&lt;code&gt;pkgs.vim&lt;/code&gt;) and provides docs + example. Using &lt;code&gt;lib.mkEnableOption&lt;/code&gt;
makes it clear exactly where this function is coming from. Same with
&lt;code&gt;lib.mkIf&lt;/code&gt; and as you can see they can be further down the configuration,
further from where you defined &lt;code&gt;with lib;&lt;/code&gt; making it less clear where they
come from. Explicitness is your friend when it comes to reproducability and
clarity.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;Conditional Configuration&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;config = lib.mkIf (cfg.enable || cfg.defaultEditor) {
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;This block is only activated if &lt;em&gt;either&lt;/em&gt; &lt;code&gt;programs.vim.enable&lt;/code&gt; or
&lt;code&gt;defaultEditor&lt;/code&gt; is set.&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;5&quot;&gt;
&lt;li&gt;Warnings&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;warnings = lib.mkIf (cfg.defaultEditor &amp;amp;&amp;amp; !cfg.enable) [
  &quot;programs.vim.defaultEditor will only work if programs.vim.enable is enabled,
   which will be enforced after the 24.11 release&quot;
];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Gives you a soft warning if you try to set &lt;code&gt;defaultEditor = true&lt;/code&gt; without also
enabling Vim.&lt;/p&gt;
&lt;ol start=&quot;6&quot;&gt;
&lt;li&gt;Actual System Config Changes&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;environment = {
  systemPackages = [ cfg.package ];
  variables.EDITOR = lib.mkIf cfg.defaultEditor (lib.mkOverride 900 &quot;vim&quot;);
  pathsToLink = [ &quot;/share/vim-plugins&quot; ];
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It adds Vim to your &lt;code&gt;systemPackages&lt;/code&gt;, sets &lt;code&gt;$EDITOR&lt;/code&gt; if &lt;code&gt;defaultEditor&lt;/code&gt; is true,
and makes &lt;code&gt;/share/vim-plugins&lt;/code&gt; available in the environment.&lt;/p&gt;
&lt;/details&gt;
&lt;p&gt;The following is a bat home-manager module that I wrote:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# bat.nix
{
  pkgs,
  config,
  lib,
  ...
}: let
  cfg = config.custom.batModule;
in {
  options.custom.batModule.enable = lib.mkOption {
    type = lib.types.bool;
    default = false;
    description = &quot;Enable bat module&quot;;
  };

  config = lib.mkIf cfg.enable {
    programs.bat = {
      enable = true;
      themes = {
        dracula = {
          src = pkgs.fetchFromGitHub {
            owner = &quot;dracula&quot;;
            repo = &quot;sublime&quot;; # Bat uses sublime syntax for its themes
            rev = &quot;26c57ec282abcaa76e57e055f38432bd827ac34e&quot;;
            sha256 = &quot;019hfl4zbn4vm4154hh3bwk6hm7bdxbr1hdww83nabxwjn99ndhv&quot;;
          };
          file = &quot;Dracula.tmTheme&quot;;
        };
      };
      extraPackages = with pkgs.bat-extras; [
        batdiff
        batman
        prettybat
        batgrep
      ];
    };
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now I could add this to my &lt;code&gt;home.nix&lt;/code&gt; to enable it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# home.nix
custom = {
  batModule.enable = true;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If I set this option to true the bat configuration is dropped in place. If it’s
not set to true, it won’t put the bat configuration in the system. Same as with
options defined in modules within the Nixpkgs repository.&lt;/p&gt;
&lt;p&gt;If I had set the default to &lt;code&gt;true&lt;/code&gt;, it would automatically enable the module
without requiring an explicit &lt;code&gt;custom.batModule.enable = true;&lt;/code&gt; call in my
&lt;code&gt;home.nix&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Module Composition&lt;/h3&gt;
&lt;p&gt;NixOS achieves its full system configuration by combining the configurations
defined in various modules. This composition is primarily handled through the
&lt;code&gt;imports&lt;/code&gt; mechanism.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;imports&lt;/code&gt;: This is a standard option within a NixOS or Home Manager
configuration (often found in your configuration.nix or home.nix). It takes a
list of paths to other Nix modules. When you include a module in the imports
list, the options and configurations defined in that module become part of your
overall system configuration.&lt;/p&gt;
&lt;p&gt;You declaratively state the desired state of your system by setting options
across various modules. The NixOS build system then evaluates and merges these
option settings. The culmination of this process, which includes building the
entire system closure, is represented by the derivation built by
&lt;code&gt;config.system.build.toplevel&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;NixOS Modules and Dependency Locking with npins&lt;/h3&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ npins example (Click to Expand)&lt;/summary&gt;
As our NixOS configurations grow in complexity, so too does the challenge of
managing the dependencies they rely on. Ensuring consistency and reproducibility
not only applies to individual packages but also to the versions of Nixpkgs and
other external resources our configurations depend upon.
&lt;p&gt;Traditionally, NixOS configurations often implicitly rely on the version of
Nixpkgs available when &lt;code&gt;nixos-rebuild&lt;/code&gt; is run. However, for more robust and
reproducible setups, especially in collaborative environments or when rolling
back to specific configurations, explicitly locking these dependencies to
specific versions becomes crucial.&lt;/p&gt;
&lt;p&gt;In the following example, we’ll explore how to use a tool called &lt;code&gt;npins&lt;/code&gt; to
manage and lock the dependencies of a NixOS configuration, ensuring a more
predictable and reproducible system. This will involve setting up a project
structure and using npins to pin the specific version of Nixpkgs our
configuration relies on.&lt;/p&gt;
&lt;p&gt;This is the file structure:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;❯ tree
.
├── configuration.nix
├── default.nix
├── desktop.nix
└── npins
    ├── default.nix
    └── sources.json
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This uses &lt;code&gt;npins&lt;/code&gt; for dependency locking. Install it and run this in the project&lt;/p&gt;
&lt;p&gt;directory:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npins init
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create a &lt;code&gt;default.nix&lt;/code&gt; with the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# default.nix
{ system ? builtins.currentSystem, sources ? import ./npins, }:
let
  pkgs = import sources.nixpkgs {
    config = { };
    overlays = [ ];
  };
  inherit (pkgs) lib;
in lib.makeScope pkgs.newScope (self: {

  shell = pkgs.mkShell { packages = [ pkgs.npins self.myPackage ]; };

    # inherit lib;

  nixosSystem = import (sources.nixpkgs + &quot;/nixos&quot;) {
    configuration = ./configuration.nix;
  };

  moduleEvale = lib.evalModules {
    modules = [
      # ...
    ];
  };
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A &lt;code&gt;configuration.nix&lt;/code&gt; with the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# configuration.nix
{
  boot.loader.grub.device = &quot;nodev&quot;;
  fileSystems.&quot;/&quot;.device = &quot;/devst&quot;;
  system.stateVersion = &quot;25.05&quot;;

  # declaring options means to declare a new option
  # defining options means to define a value of an option
  imports = [
    # ./main.nix
     ./desktop.nix # Files
    # ./minimal.nix
  ];

  # mine.desktop.enable = true;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And a &lt;code&gt;desktop.nix&lt;/code&gt; with the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# desktop.nix
{ pkgs, lib, config, ... }:

{
  imports = [];

  # Define an option to enable or disable desktop configuration
  options.mine.desktop.enable = lib.mkEnableOption &quot;desktop settings&quot;;

  # Configuration that applies when the option is enabled
  config = lib.mkIf config.mine.desktop.enable {
    environment.systemPackages = [ pkgs.git ];
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;mkEnableOption&lt;/code&gt; defaults to false. Now in your &lt;code&gt;configuration.nix&lt;/code&gt; you can
uncomment &lt;code&gt;mine.desktop.enable = true;&lt;/code&gt; to enable the desktop config and
vice-versa.&lt;/p&gt;
&lt;p&gt;You can test that this works by running:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix-instantiate -A nixosSystem.system
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;nix-instantiate&lt;/code&gt; performs only the evaluation phase of Nix expressions. During
this phase, Nix interprets the Nix code, resolves all dependencies, and
constructs derivations but does not execute any build actions. Useful for
testing.&lt;/p&gt;
&lt;p&gt;To check if this worked and &lt;code&gt;git&lt;/code&gt; is installed in systemPackages you can load it
into &lt;code&gt;nix repl&lt;/code&gt; but first you’ll want &lt;code&gt;lib&lt;/code&gt; to be available so uncomment this in
your &lt;code&gt;default.nix&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# default.nix
inherit lib;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Rerun &lt;code&gt;nix-instantiate -A nixosSystem.system&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Then load the repl and check that &lt;code&gt;git&lt;/code&gt; is in &lt;code&gt;systemPackages&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix repl -f .
nix-repl&amp;gt; builtins.filter (pkg: lib.hasPrefix &quot;git&quot; pkg.name) nixosSystem.config.environment.systemPackages
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This shows the path to the derivation&lt;/p&gt;
&lt;p&gt;Check that mine.desktop.enable is true&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;nix-repl&amp;gt; nixosSystem.config.mine.desktop.enable
true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As demonstrated with npins, explicitly managing the dependencies of your NixOS
modules is a powerful technique for ensuring the long-term stability and
reproducibility of your system configurations. By pinning specific versions of
Nixpkgs and other resources, you gain greater control over your environment and
reduce the risk of unexpected changes due to upstream updates.&lt;/p&gt;
&lt;/details&gt;
&lt;h3&gt;Best Practices&lt;/h3&gt;
&lt;p&gt;You’ll see the following all throughout Nix code and is convenient although it
doesn’t follow best practices. One reason is static analysis can’t reason about
the code (e.g. Because it implicitly brings all attributes into scope, tools
can’t verify which ones are actually being used), because it would have to
actually evaluate the files to see which names are in scope:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# utils.nix
{ pkgs, ... }: {
  environment.systemPackages = with pkgs; [
    rustup
    evcxr
    nix-prefetch-git
  ];
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Another reason the above expression is considered an “anti-pattern” is when more
then one &lt;code&gt;with&lt;/code&gt; is used, it’s no longer clear where the names are coming from.&lt;/p&gt;
&lt;p&gt;Scoping rules for &lt;code&gt;with&lt;/code&gt; are not intuitive, see
&lt;a href=&quot;https://github.com/NixOS/nix/issues/490&quot;&gt;issue&lt;/a&gt; –nix.dev This can make
debugging harder, as searching for variable origins becomes ambiguous (i.e. open
to more than one interpretation).&lt;/p&gt;
&lt;p&gt;The following follows best practices:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{pkgs, ... }: {
  environment.systemPackages = builtins.attrValues {
    inherit (pkgs)
      rustup
      evcxr
      nix-prefetch-git;
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://noogle.dev/f/builtins/attrValues&quot;&gt;Noogle builtins.attrValues&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Above Command Summary (Click to Expand) &lt;/summary&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{
  inherit (pkgs) rustup evcxr nix-prefetch-git;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;is equivalent to:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{
  rustup = pkgs.rustup;
  evcxr = pkgs.evcxr;
  nix-prefetch-git = pkgs.nix-prefetch-git;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Applying &lt;code&gt;builtins.attrValues&lt;/code&gt; produces:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;[ pkgs.evcxr pkgs.nix-prefetch-git pkgs.rustup ]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see only the values are included in the list, not the keys. This is
more explicit and declarative but can be more complicated, especially for a
beginner.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;builtins.attrValues&lt;/code&gt; returns the values of all attributes in the given set,
sorted by attribute name. The above expression turns into something like the
following avoiding bringing every attribute name from &lt;code&gt;nixpkgs&lt;/code&gt; into scope.&lt;/p&gt;
&lt;p&gt;A more straightforward example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;attrValues {c = 3; a = 1; b = 2;}
=&amp;gt; [1 2 3]
&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;
&lt;p&gt;This approach avoids unintended name clashes or confusion when debugging.&lt;/p&gt;
&lt;p&gt;Upon looking into this a bit further, most people use the following format to
avoid the “anti-pattern” from using &lt;code&gt;with pkgs;&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# utils.nix
{ pkgs, ... }: {
  environment.systemPackages = [
    pkgs.rustup
    pkgs.evcxr
    pkgs.nix-prefetch-git
  ];
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;While the performance differences might be negligible on modern computers,
adopting this best practice from the start is highly recommended. The above
approach is more explicit, it’s clear exactly where each package is coming from.&lt;/p&gt;
&lt;p&gt;If maintaining strict scope control matters, use &lt;code&gt;builtins.attrValues&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If readability and simplicity are more your priority, explicitly referencing
&lt;code&gt;pkgs.&amp;lt;packageName&amp;gt;&lt;/code&gt; might be better. Now you can choose for yourself.&lt;/p&gt;
&lt;h4&gt;Conclusion&lt;/h4&gt;
&lt;p&gt;As we have seen throughout this chapter, modules are the building blocks of your
NixOS system and are themselves often functions. There are a few different ways
to use these modules to build your system. In the next chapter,
&lt;a href=&quot;https://saylesss88.github.io/Nix_Flakes_Explained_4.html&quot;&gt;Nix Flakes Explained&lt;/a&gt;
we will learn about Nix Flakes as a more modern and comprehensive entrypoint for
managing your entire system and its dependencies.&lt;/p&gt;
&lt;p&gt;To further deepen your understanding of NixOS Modules and the broader ecosystem
of tools and best practices surrounding them, the following resources offer
valuable insights and information.&lt;/p&gt;
&lt;h4&gt;Resources on Modules&lt;/h4&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Resources (Click to Expand) &lt;/summary&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://nixos.org/manual/nixos/stable/#sec-writing-modules&quot;&gt;WritingNixOsModules&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://nixos.wiki/wiki/NixOS_modules&quot;&gt;NixWikiNixOSModules&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://nix.dev/tutorials/module-system/a-basic-module/index.html&quot;&gt;nix.dev A basic module&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://nix.dev/tutorials/module-system/deep-dive#module-system-deep-dive&quot;&gt;ModuleSystemDeepDive&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://xeiaso.net/talks/asg-2023-nixos/&quot;&gt;xeiaso Nixos Modules for fun &amp;amp; profit&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://nixos-and-flakes.thiscute.world/other-usage-of-flakes/module-system&quot;&gt;NixOS Flakes Book Module System&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Videos&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=N7hFP_40DJo&amp;amp;t=17s&quot;&gt;NixHour Writing NixOS modules&lt;/a&gt;
– This example is from this video
&lt;a href=&quot;https://infinisil.com/modules.mp4&quot;&gt;infinisilModules&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=cZjOzOHb2ow&quot;&gt;tweagModuleSystemRecursion&lt;/a&gt;&lt;/p&gt;
&lt;/details&gt;
</content></entry><entry><title>Nix Flakes Explained</title><id>https://saylesss88.github.io/Nix_Flakes_Explained_4.html</id><updated>2025-11-21T00:00:00+00:00</updated><author><name>saylesss87@proton.me (saylesss88)</name></author><link href="https://saylesss88.github.io/Nix_Flakes_Explained_4.html" rel="alternate"/><content type="html">&lt;h1&gt;Chapter 4&lt;/h1&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Click to Expand Table of Contents&lt;/summary&gt;
&lt;!-- toc --&gt;
&lt;/details&gt;
&lt;p&gt;&lt;img src=&quot;https://saylesss88.github.io/images/trees3.cleaned.png&quot; alt=&quot;trees3&quot; /&gt;&lt;/p&gt;
&lt;!-- &lt;img src=&quot;https://saylesss88.github.io/images/gruv15.png&quot; width=&quot;800&quot; height=&quot;600&quot;&gt; --&gt;
&lt;h2&gt;Nix Flakes Explained&lt;/h2&gt;
&lt;p&gt;If you’re completely new, take a look at
&lt;a href=&quot;https://nixos.wiki/wiki/flakes#Installing_flakes&quot;&gt;this&lt;/a&gt; to get flakes on your
system.&lt;/p&gt;
&lt;p&gt;For the Nix Flake man page type &lt;code&gt;man nix3 flake&lt;/code&gt; and for a specific feature,
type something like &lt;code&gt;man nix3 flake-lock&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Flakes replace stateful channels (which cause much confusion among novices) and
introduce a more intuitive and consistent CLI, making them a perfect opportunity
to start using Nix. – Alexander Bantyev
&lt;a href=&quot;https://serokell.io/blog/practical-nix-flakes&quot;&gt;Practical Nix Flakes&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The “state” being remembered and updated by channels is the specific revision of
the Nixpkgs repository that your local Nix installation considers “current” for
a given channel. When this state changes on your machine, your builds diverge
from others whose machines have a different, independently updated channel
state.&lt;/p&gt;
&lt;p&gt;Channels are also constantly updated on the remote servers. So, “nixos-unstable”
today refers to a different set of packages and versions than “nixos-unstable”
did yesterday or will tomorrow.&lt;/p&gt;
&lt;p&gt;Flakes solve this by making the exact revision of &lt;code&gt;nixpkgs&lt;/code&gt; (and other
dependencies) an explicit input within your &lt;code&gt;flake.nix&lt;/code&gt; file, pinned in the
&lt;code&gt;flake.lock&lt;/code&gt;. This means the state is explicitly defined in the configuration
itself, not implicitly managed by a global system setting.&lt;/p&gt;
&lt;p&gt;Evaluation time is notoriously slow on NixOS, the problem was that in the past
Nix evaluation wasn’t hermetic preventing effective evaluation caching. A &lt;code&gt;.nix&lt;/code&gt;
file can import other Nix files or by looking them up in the Nix search path
(&lt;code&gt;$NIX_PATH&lt;/code&gt;). This causes a cached result to be inconsistent unless every file
is perfectly kept track of. Flakes solve this problem by ensuring fully hermetic
evaluation.&lt;/p&gt;
&lt;p&gt;“Hermetic” means that the output of an evaluation (the derivation itself)
depends &lt;em&gt;only&lt;/em&gt; on the explicit inputs provided, not on anything external like
environment variables or pulling in files only on your system. This is the
problem that Nix solves and the problem that flakes are built around.&lt;/p&gt;
&lt;h2&gt;What is a Nix Flake?&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Nix flakes&lt;/strong&gt; are independent components in the Nix ecosystem. They define
their own &lt;strong&gt;dependencies&lt;/strong&gt; (inputs) and what they produce (outputs), which can
include &lt;strong&gt;packages&lt;/strong&gt;, &lt;strong&gt;deployment configurations&lt;/strong&gt;, or &lt;strong&gt;Nix functions&lt;/strong&gt; for
other flakes to use.&lt;/p&gt;
&lt;p&gt;Flakes provide a standardized framework for building and managing software,
making all project inputs explicit for greater reproducibility and
self-containment.&lt;/p&gt;
&lt;p&gt;At its core, a flake is a source tree (like a Git repository) that contains a
&lt;code&gt;flake.nix&lt;/code&gt; file in its root directory. This file provides a standardized way to
access Nix artifacts such as packages and modules.&lt;/p&gt;
&lt;p&gt;Flakes provide a standard way to write Nix expressions (and therefore packages)
whose dependencies are version-pinned in a lock file, improving reproducibility
of Nix installations. – NixOS Wiki&lt;/p&gt;
&lt;p&gt;Think of &lt;code&gt;flake.nix&lt;/code&gt; as the central entry point of a flake. It not only defines
what the flake produces but also declares its dependencies.&lt;/p&gt;
&lt;h3&gt;Key Concepts&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;flake.nix&lt;/code&gt;: &lt;strong&gt;The Heart of a Flake&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;flake.nix&lt;/code&gt; file is mandatory for any flake. It must contain an attribute
set with at least one required attribute: &lt;code&gt;outputs&lt;/code&gt;. It can also optionally
include &lt;code&gt;description&lt;/code&gt; and &lt;code&gt;inputs&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Basic Structure:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{
  description = &quot;Package description&quot;;
  inputs = { /* Dependencies go here */ };
  outputs = { /* What the flake produces */ };
  nixConfig = { /* Advanced configuration options */ };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I typically see &lt;code&gt;nixConfig&lt;/code&gt; used for extra-substituters for cachix. This is a
general-purpose way to define Nix configuration options that apply when this
flake is evaluated or built. It ties into your &lt;code&gt;/etc/nix/nix.conf&lt;/code&gt; or
&lt;code&gt;~/.config/nix/nix.conf&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;For example, create a directory and add a &lt;code&gt;flake.nix&lt;/code&gt; with the following
contents, yes this is a complete &lt;code&gt;flake.nix&lt;/code&gt; demonstrating &lt;em&gt;outputs&lt;/em&gt; being the
only required attribute:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# flake.nix
{
  outputs = _: { multiply = 2 * 2; };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now evaluate it with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix eval .#multiply
4
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the &lt;code&gt;outputs = _: { ... };&lt;/code&gt; line, the &lt;code&gt;_&lt;/code&gt; (underscore) is a placeholder
argument. It represents the inputs that the outputs function could receive (like
&lt;code&gt;inputs&lt;/code&gt;, &lt;code&gt;self&lt;/code&gt;, &lt;code&gt;pkgs&lt;/code&gt;, etc.), but in this specific case, we’re not using any
of them to define the multiply attribute. It’s a common convention in Nix to use
&lt;code&gt;_&lt;/code&gt; when an argument is required by a function but intentionally ignored.&lt;/p&gt;
&lt;p&gt;In the command &lt;code&gt;nix eval .#multiply&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;the &lt;code&gt;.&lt;/code&gt; signifies the current directory, indicating that Nix should look for a
&lt;code&gt;flake.nix&lt;/code&gt; file in the directory where you’re running the command.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The &lt;code&gt;#&lt;/code&gt; is used to select a specific attribute from the &lt;code&gt;outputs&lt;/code&gt; of the
flake. In this case, it’s telling Nix to evaluate the &lt;code&gt;multiply&lt;/code&gt; attribute.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In the next example we will create a &lt;code&gt;devShells&lt;/code&gt; output as well as a &lt;code&gt;packages&lt;/code&gt;
output.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;flake.lock&lt;/code&gt; auto-generated lock file&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;All flake inputs are pinned to specific revisions in a lockfile called
&lt;code&gt;flake.lock&lt;/code&gt; This file stores the revision info as JSON.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;flake.lock&lt;/code&gt; file ensures that Nix flakes have purely deterministic outputs.
A &lt;code&gt;flake.nix&lt;/code&gt; file without an accompanying &lt;code&gt;flake.lock&lt;/code&gt; should be considered
incomplete and a kind of proto-flake. Any Nix CLI command that is run against
the flake—like &lt;code&gt;nix build&lt;/code&gt;, &lt;code&gt;nix develop&lt;/code&gt;, or even &lt;code&gt;nix flake show&lt;/code&gt;—generates a
&lt;code&gt;flake.lock&lt;/code&gt; for you.&lt;/p&gt;
&lt;p&gt;Here’s an example section of a &lt;code&gt;flake.lock&lt;/code&gt; file that pins Nixpkgs to a specific
revision:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ cat flake.lock
{
  &quot;nodes&quot;: {
    &quot;nixpkgs&quot;: {
      &quot;info&quot;: {
        &quot;lastModified&quot;: 1587398327,
        &quot;narHash&quot;: &quot;sha256-mEKkeLgUrzAsdEaJ/1wdvYn0YZBAKEG3AN21koD2AgU=&quot;
      },
      &quot;locked&quot;: {
        &quot;owner&quot;: &quot;NixOS&quot;,
        &quot;repo&quot;: &quot;nixpkgs&quot;,
        &quot;rev&quot;: &quot;5272327b81ed355bbed5659b8d303cf2979b6953&quot;,
        &quot;type&quot;: &quot;github&quot;
      },
      &quot;original&quot;: {
        &quot;owner&quot;: &quot;NixOS&quot;,
        &quot;ref&quot;: &quot;nixos-20.03&quot;,
        &quot;repo&quot;: &quot;nixpkgs&quot;,
        &quot;type&quot;: &quot;github&quot;
      }
    },
    &quot;root&quot;: {
      &quot;inputs&quot;: {
        &quot;nixpkgs&quot;: &quot;nixpkgs&quot;
      }
    }
  },
  &quot;root&quot;: &quot;root&quot;,
  &quot;version&quot;: 5
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Any future build of this flake will use the version of &lt;code&gt;nixpkgs&lt;/code&gt; recorded in the
lock file. If you add new inputs, they will be automatically added when you run
a nix flake command like &lt;code&gt;nix flake show&lt;/code&gt;. But it won’t replace existing locks.&lt;/p&gt;
&lt;p&gt;If you need to update a locked input to the latest version:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix flake lock --update-input nixpkgs
nix build
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The above command allows you to update individual inputs, and &lt;code&gt;nix flake update&lt;/code&gt;
will update the whole lock file.&lt;/p&gt;
&lt;h3&gt;Helper functions that are good to know for working with Flakes&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;lib.genAttrs&lt;/code&gt;: A function, given the name of the attribute, returns the
attribute’s value&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;nix repl
nix-repl&amp;gt; :l &amp;lt;nixpkgs&amp;gt;
nix-repl&amp;gt; lib.genAttrs [ &quot;boom&quot; &quot;bash&quot; ] (name: &quot;sonic&quot; + name)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Output&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{
  bash = &quot;sonicbash&quot;;
  boom = &quot;sonicboom&quot;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You will often see the following:&lt;/p&gt;
&lt;p&gt;A common use for this with flakes is to have a list of different systems:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;     systems = [
       &quot;x86_64-linux&quot;
       &quot;aarch64-linux&quot;
       &quot;x86_64-darwin&quot;
       &quot;aarch64-darwin&quot;
     ];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And use it to generate an attribute set for each listed system:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;eachSystem = lib.genAttrs systems;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The above command creates an attribute set by mapping over a list of system
strings. If you notice, you provide it a list (i.e. [ 1 2 3 ]) and the function
returns a set (i.e. &lt;code&gt;{ ... }&lt;/code&gt;)&lt;/p&gt;
&lt;p&gt;Why &lt;code&gt;genAttrs&lt;/code&gt; is useful:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;It lets you define attributes (like &lt;code&gt;packages&lt;/code&gt;, &lt;code&gt;checks&lt;/code&gt;, &lt;code&gt;devShells&lt;/code&gt;) per
supported system in a DRY(don’t repeat yourself), structured way.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;lib.mapAttrs&lt;/code&gt;: A function, given an attribute’s name and value, returns a new
&lt;code&gt;nameValuePair&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;nix-repl&amp;gt; builtins.mapAttrs (name: value: name + &quot;-&quot; + value) { x = &quot;foo&quot;; y = &quot;bar&quot;; }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Output&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{
  x = &quot;x-foo&quot;;
  y = &quot;y-bar&quot;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;pkgs.mkShell&lt;/code&gt;: is a specialized &lt;code&gt;stdenv.mkDerivation&lt;/code&gt; that removes some
repetition when using it with &lt;code&gt;nix-shell&lt;/code&gt; (or &lt;code&gt;nix develop&lt;/code&gt;)&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{ pkgs ? import &amp;lt;nixpkgs&amp;gt; {} }:
pkgs.mkShell {
  packages = [ pkgs.gnumake ];

  inputsFrom = [ pkgs.hello pkgs.gnutar ];

  shellHook = &apos;&apos;
    export DEBUG=1
  &apos;&apos;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;A Simple flake that outputs a devshell and a package&lt;/h4&gt;
&lt;p&gt;In a new directory create a &lt;code&gt;flake.nix&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# flake.nix
{
  outputs = {
    self,
    nixpkgs,
  }: let
    pkgs = nixpkgs.legacyPackages.x86_64-linux;
  in {

    packages.x86_64-linux.default = pkgs.kakoune; # You could define a meta-package here

    devShells.x86_64-linux.default = pkgs.mkShell {
      packages = [
        pkgs.kakoune
        pkgs.git
        pkgs.ripgrep
        pkgs.fzf
      ];
    };
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;mkShell&lt;/code&gt; is a wrapper around &lt;code&gt;mkDerivation&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;This flake offers two main outputs for &lt;code&gt;x86_64-linux&lt;/code&gt; systems:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;A &lt;strong&gt;standard package&lt;/strong&gt; (&lt;code&gt;packages.x86_64-linux.default&lt;/code&gt;): This simple example
just re-exports &lt;code&gt;kakoune&lt;/code&gt; from &lt;code&gt;nixpkgs&lt;/code&gt;. You could build your own apps here.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A &lt;strong&gt;development shell&lt;/strong&gt; (&lt;code&gt;devShells.x86_64-linux.default&lt;/code&gt;): This provides a
convenient environment where you have specific tools available without
installing them globally on your system.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;To use this flake you have a few options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;nix run&lt;/code&gt; will launch kakoune&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;nix develop&lt;/code&gt; will activate the development environment providing all of the
pkgs listed under &lt;code&gt;mkShell&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Or more explicitly &lt;code&gt;nix develop .#devShells.x86_64-linux.default&lt;/code&gt;, does the
same thing as the command above.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Flake References&lt;/h4&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Flake References (Click to Expand) &lt;/summary&gt;
&lt;p&gt;&lt;strong&gt;Flake references&lt;/strong&gt; (flakerefs) are a way to specify the location of a flake.
They have two different formats:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Attribute set representation&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{
  type = &quot;github&quot;;
  owner = &quot;NixOS&quot;;
  repo = &quot;nixpkgs&quot;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or &lt;strong&gt;URL-like syntax&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;github:NixOS/nixpkgs
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These are used on the command line as a more convenient alternative to the
attribute set representation. For instance, in the command&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;nix build github:NixOS/nixpkgs#hello
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;github:NixOS/nixpkgs&lt;/code&gt; is a flake reference (while &lt;code&gt;hello&lt;/code&gt; is an output
attribute). They are also allowed in the &lt;code&gt;inputs&lt;/code&gt; attribute of a flake, e.g.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;inputs.nixpkgs.url = &quot;github:NixOS/nixpkgs&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;is equivalent to&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;inputs.nixpkgs = {
  type = &quot;github&quot;;
  owner = &quot;NixOS&quot;;
  repo = &quot;nixpkgs&quot;;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;–
&lt;a href=&quot;https://nix.dev/manual/nix/2.24/command-ref/new-cli/nix3-flake#flake-references&quot;&gt;nix.dev flake-references&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/details&gt;
&lt;h4&gt;Nix Flake Commands&lt;/h4&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Flake Commands (Click to Expand) &lt;/summary&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;nix flake&lt;/code&gt; provides subcommands for creating, modifying and querying &lt;em&gt;Nix
Flakes&lt;/em&gt;. Flakes are the unit for packaging Nix code in a reproducible and
discoverable way. They can have dependencies on other flakes, making it
possible to have multi-repository Nix projects.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;— From
&lt;a href=&quot;https://nix.dev/manual/nix/2.28/command-ref/new-cli/nix3-flake&quot;&gt;nix.dev Reference Manual&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The main thing to note here is that &lt;code&gt;nix flake&lt;/code&gt; is used to manage Nix flakes
and that Flake commands are whitespace separated rather than hyphen &lt;code&gt;-&lt;/code&gt;
separated.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Flakes do provide some advantages when it comes to discoverability of outputs.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;For Example, two helpful commands to inspect a flake are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://nix.dev/manual/nix/2.28/command-ref/new-cli/nix3-flake-show&quot;&gt;nix flake show&lt;/a&gt;
command: Show the outputs provided by a flake.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://nix.dev/manual/nix/2.28/command-ref/new-cli/nix3-flake-check&quot;&gt;nix flake check&lt;/a&gt;
command: check whether the flake evaluates and run its tests.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Any Nix CLI command that is run against a flake – like &lt;code&gt;nix build&lt;/code&gt;,
&lt;code&gt;nix develop&lt;/code&gt;, &lt;code&gt;nix flake show&lt;/code&gt; – generate a &lt;code&gt;flake.lock&lt;/code&gt; file for you.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;flake.lock&lt;/code&gt; file ensures that all flake inputs are pinned to specific
revisions and that Flakes have purely deterministic outputs.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix shell nixpkgs#ponysay --command ponysay &quot;Flakes Rock!&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This works because of the [flake registry] that maps symbolic identifiers like
&lt;code&gt;nixpkgs&lt;/code&gt; to actual locations such as &lt;code&gt;https://github.com/NixOS/nixpkgs&lt;/code&gt;. So the
following are equivalent:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix shell nixpkgs#ponysay --command ponysay Flakes Rock!
nix shell github:NixOS/nixpkgs#ponysay --command ponysay Flakes Rock!
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To override the &lt;code&gt;nixpkgs&lt;/code&gt; registry with your own local copy you could:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix registry add nixpkgs ~/src/local-nixpkgs
&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;
&lt;h3&gt;Attribute Sets: The Building Blocks&lt;/h3&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Attribute set Refresher (Click to Expand) &lt;/summary&gt;
&lt;p&gt;&lt;strong&gt;Attribute sets&lt;/strong&gt; are fundamental in Nix. They are simply collections of
name-value pairs wrapped in curly braces &lt;code&gt;{}&lt;/code&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Example, (click to see Output):&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;let
  my_attrset = { foo = &quot;bar&quot;; };
in
my_attrset.foo
~ &quot;bar&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Top-Level Attributes of a Flake&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;Flakes have specific &lt;strong&gt;top-level attributes&lt;/strong&gt; that can be accessed directly
(without dot notation). The most common ones are &lt;code&gt;inputs&lt;/code&gt;, &lt;code&gt;outputs&lt;/code&gt;, and
&lt;code&gt;nixConfig&lt;/code&gt;.&lt;/p&gt;
  &lt;/details&gt;
&lt;h3&gt;Deeper Dive into the Structure of &lt;code&gt;flake.nix&lt;/code&gt;&lt;/h3&gt;
&lt;!-- ![Flakes](images/Flakes.png) --&gt;
&lt;p&gt;&lt;code&gt;inputs&lt;/code&gt;: &lt;strong&gt;Declaring Dependencies&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;inputs&lt;/code&gt; attribute set specifies the other flakes that your current flake
depends on.&lt;/p&gt;
&lt;p&gt;Each key in the &lt;code&gt;inputs&lt;/code&gt; set is a name you choose for the dependency, and the
value is a reference to that flake (usually a URL or a Git Repo).&lt;/p&gt;
&lt;p&gt;To access something from a dependency, you generally go through the &lt;code&gt;inputs&lt;/code&gt;
attribute (e.g., &lt;code&gt;inputs.helix.packages&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;See
&lt;a href=&quot;https://saylesss88.github.io/flakes/flake_inputs_4.1.html&quot;&gt;Nix Flake inputs&lt;/a&gt;
for a flake inputs deep dive.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt; This declares dependencies on the &lt;code&gt;nixpkgs&lt;/code&gt; and &lt;code&gt;import-cargo&lt;/code&gt;
flakes:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;inputs = {
  import-cargo.url = &quot;github:edolstra/import-cargo&quot;;
  nixpkgs.url = &quot;nixpkgs&quot;;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When Nix evaluates your flake, it fetches and evaluates each input. These
evaluated inputs are then passed as an attribute set to the outputs function,
with the keys matching the names you gave them in the inputs set.&lt;/p&gt;
&lt;p&gt;The special input &lt;code&gt;self&lt;/code&gt; is a reference to the &lt;code&gt;outputs&lt;/code&gt; and the source tree of
the current flake itself.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;outputs&lt;/code&gt;: Defining What Your Flake Provides&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;&lt;code&gt;outputs&lt;/code&gt;&lt;/strong&gt; attribute defines what your flake makes available. This can
include packages, NixOS modules, development environments (&lt;code&gt;devShells&lt;/code&gt;) and
other Nix derivations.&lt;/p&gt;
&lt;p&gt;Flakes can output arbitrary Nix values. However, certain outputs have specific
meanings for Nix commands and must adhere to particular types (often
derivations, as described in the
&lt;a href=&quot;https://nixos.wiki/wiki/Flakes&quot;&gt;output schema&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;You can inspect the outputs of a flake using the command:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;nix flake show
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;This command takes a flake URI and displays its outputs in a tree structure,
showing the attribute paths and their corresponding types.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Understanding the &lt;code&gt;outputs&lt;/code&gt; Function&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Beginners often mistakenly think that self and nixpkgs within
&lt;code&gt;outputs = { self, nixpkgs, ... }: { ... }&lt;/code&gt; are the outputs themselves. Instead,
they are the &lt;em&gt;input arguments&lt;/em&gt; (often called &lt;em&gt;output arguments&lt;/em&gt;) to the outputs
function.&lt;/p&gt;
&lt;p&gt;The outputs function in &lt;code&gt;flake.nix&lt;/code&gt; always takes a single argument, which is an
attribute set. The syntax &lt;code&gt;{ self, nixpkgs, ... }&lt;/code&gt; is Nix’s way of destructuring
this single input attribute set to extract the values associated with the keys
&lt;code&gt;self&lt;/code&gt; and &lt;code&gt;nixpkgs&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Flakes output your whole system configuration, packages, as well as Nix
functions for use elsewhere.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;For example, the &lt;code&gt;nixpkgs&lt;/code&gt; repository has its own &lt;code&gt;flake.nix&lt;/code&gt; file that
outputs many helper functions via the &lt;code&gt;lib&lt;/code&gt; attribute.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;For a deep dive into flake outputs, see
&lt;a href=&quot;https://saylesss88.github.io/flakes/flake_outputs_4.2.html&quot;&gt;Nix Flake Outputs&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;The &lt;code&gt;lib&lt;/code&gt; convention The convention of using &lt;code&gt;lib&lt;/code&gt; to output functions is
observed not just by Nixpkgs but by many other Nix projects. You’re free,
however, to output functions via whichever attribute you prefer. –
&lt;a href=&quot;https://zero-to-nix.com/concepts/flakes/#inputs&quot;&gt;Zero to Nix Flakes&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Some flake outputs are required to be system specific (i.e. “x86_64-linux” for
(64-bit AMD/Intel Linux) including packages, development environments, and NixOS
configurations)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Variadic Attributes (…) and @-patterns&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;...&lt;/code&gt; syntax in the input arguments of the outputs function indicates
variadic attributes, meaning the input attribute set can contain more attributes
than just those explicitly listed (like &lt;code&gt;lib&lt;/code&gt; and &lt;code&gt;nixpkgs&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;mul = { a, b, ... }: a * b;
mul { a = 3; b = 4; c = 2; } # &apos;c&apos; is an extra attribute
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However, you cannot directly access these extra attributes within the function
body unless you use the @-pattern:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;(Click for Output)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;mul = s@{ a, b, ... }: a  b  s.c; # &apos;s&apos; now refers to the entire input set
mul { a = 3; b = 4; c = 2; } # Output: 24
~ 24
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When used in the outputs function argument list (e.g.,
&lt;code&gt;outputs = { pkgs, ... } @ inputs)&lt;/code&gt;, the @-pattern binds the entire input
attribute set to a name (in this case, &lt;code&gt;inputs&lt;/code&gt;) while also allowing you to
destructure specific attributes like pkgs.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What &lt;code&gt;outputs = { pkgs, ... } @ inputs: { ... };&lt;/code&gt; does:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Destructuring:&lt;/strong&gt; It tries to extract the value associated with the key
&lt;code&gt;pkgs&lt;/code&gt; from the input attribute set and binds it to the variable &lt;code&gt;pkgs&lt;/code&gt;. The
&lt;code&gt;...&lt;/code&gt; allows for other keys in the input attribute set to be ignored during
this direct destructuring.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Binding the Entire Set:&lt;/strong&gt; It binds the entire input attribute set to the
variable inputs.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Example &lt;code&gt;flake.nix&lt;/code&gt;:&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{
inputs.nixpkgs.url = &quot;github:NixOS/nixpkgs/nixos-unstable&quot;;
inputs.home-manager.url = &quot;github:nix-community/home-manager&quot;;

outputs = { self, nixpkgs, ... } @ attrs: { # A `packages` output for the x86_64-linux platform
packages.x86_64-linux.hello = nixpkgs.legacyPackages.x86_64-linux.hello;

    # A `nixosConfigurations` output (for a NixOS system named &quot;fnord&quot;)
    nixosConfigurations.fnord = nixpkgs.lib.nixosSystem {
      system = &quot;x86_64-linux&quot;;
      specialArgs = attrs;
      modules = [ ./configuration.nix ];
    };

};
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Platform Specificity in Outputs&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Flakes ensure that their outputs are consistent across different evaluation
environments. Therefore, any package-related output must explicitly specify the
target platform (a combination of architecture and OS, &lt;code&gt;x86_64-linux&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;legacyPackages Explained&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;legacyPackages&lt;/code&gt; is a way for flakes to interact with the traditional, less
structured package organization of nixpkgs. Instead of packages being directly
at the top level (e.g., &lt;code&gt;pkgs.hello&lt;/code&gt;), &lt;code&gt;legacyPackages&lt;/code&gt; provides a
platform-aware way to access them within the flake’s structured output format
(e.g., &lt;code&gt;nixpkgs.legacyPackages.x86_64-linux.hello&lt;/code&gt;). It acts as a bridge between
the flake’s expected output structure and nixpkgs’s historical organization.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Sole Argument of outputs&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;It’s crucial to remember that the outputs function accepts only one argument,
which is an attribute set. The &lt;code&gt;{ self, nixpkgs, ... }&lt;/code&gt; syntax is simply
destructuring that single input attribute set.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Outputs of the Flake (Return Value)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The outputs of the flake refer to the attribute set that is returned by the
&lt;code&gt;outputs&lt;/code&gt; function. This attribute set can contain various named outputs like
&lt;code&gt;packages&lt;/code&gt;, &lt;code&gt;nixosConfigurations&lt;/code&gt;, &lt;code&gt;devShells&lt;/code&gt;, etc.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Imports: Including Other Nix Expressions&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;import&lt;/code&gt; function in Nix is used to evaluate the Nix expression found at a
specified path (usually a file or directory) and return its value.&lt;/p&gt;
&lt;p&gt;Basic Usage: import &lt;code&gt;./path/to/file.nix&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Passing Arguments During Import&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;import &amp;lt;nixpkgs&amp;gt; {}&lt;/code&gt; is calling two functions, not one.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;import &amp;lt;nixpkgs&amp;gt;&lt;/code&gt;: The first function call&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;import&lt;/code&gt; is a built-in Nix function. Its job is to load and evaluate a Nix
expression from a specified path.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;nixpkgs&amp;gt;&lt;/code&gt; is a flake reference. When you use &lt;code&gt;import &amp;lt;nixpkgs&amp;gt;&lt;/code&gt;, Nix
evaluates the &lt;code&gt;default.nix&lt;/code&gt; file (or sometimes &lt;code&gt;lib/default.nix&lt;/code&gt;) found at
that location.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The &lt;code&gt;default.nix&lt;/code&gt; in &lt;code&gt;nixpkgs&lt;/code&gt; evaluates to a function. This function is
designed to be configurable, allowing you to pass arguments like &lt;code&gt;system&lt;/code&gt;,
&lt;code&gt;config&lt;/code&gt;, etc. to customize how &lt;code&gt;nixpkgs&lt;/code&gt; behaves and what packages it
provides.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;So, &lt;code&gt;import &amp;lt;nixpkgs&amp;gt;&lt;/code&gt; doesn’t give you the &lt;code&gt;nixpkgs&lt;/code&gt; package set directly; it
gives you the function that generates the &lt;code&gt;nixpkgs&lt;/code&gt; package set derivation.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;&lt;code&gt;{}&lt;/code&gt;: The second function call (and its argument)&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;{}&lt;/code&gt; denotes an empty attribute set&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;When an attribute set immediately follows a function, it means you are calling
that function and passing the attribute set as its single argument.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So, the &lt;code&gt;{}&lt;/code&gt; after &lt;code&gt;import &amp;lt;nixpkgs&amp;gt;&lt;/code&gt; is not part of the &lt;code&gt;import&lt;/code&gt; function
iteself. It’s the argument being passed to the function that &lt;code&gt;import &amp;lt;nixpkgs&amp;gt;&lt;/code&gt;
just returned.&lt;/p&gt;
&lt;p&gt;You can also pass an attribute set as an argument to the Nix expression being
imported:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;let
myHelpers = import ./lib/my-helpers.nix { pkgs = nixpkgs; };
in
# ... use myHelpers
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this case, the Nix expression in &lt;code&gt;./lib/my-helpers.nix&lt;/code&gt; is likely a function
that expects an argument (often named &lt;code&gt;pkgs&lt;/code&gt; by convention):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# ./lib/my-helpers.nix

{ pkgs }:
let
myPackage = pkgs.stdenv.mkDerivation {
name = &quot;my-package&quot;; # ...
};
in
myPackage
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By passing &lt;code&gt;{ pkgs = nixpkgs; }&lt;/code&gt; during the import, you are providing the
nixpkgs value from your current &lt;code&gt;flake.nix&lt;/code&gt; scope to the pkgs parameter expected
by the code in &lt;code&gt;./lib/my-helpers.nix&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Importing Directories (&lt;code&gt;default.nix&lt;/code&gt;)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;When you use import with a path that points to a directory, Nix automatically
looks for a file named &lt;code&gt;default.nix&lt;/code&gt; within that directory. If found, Nix
evaluates the expressions within &lt;code&gt;default.nix&lt;/code&gt; as if you had specified its path
directly in the import statement.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;For more advanced examples see
&lt;a href=&quot;https://saylesss88.github.io/flakes/flake_examples_4.3.html&quot;&gt;Nix Flake Examples&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;Conclusion: Unifying Your Nix Experience with Flakes&lt;/h5&gt;
&lt;p&gt;For some examples of more advanced outputs like &lt;code&gt;devShells&lt;/code&gt; and &lt;code&gt;checks&lt;/code&gt;, check
out this blog post that I wrote:
&lt;a href=&quot;https://tsawyer87.github.io/posts/nix_flakes_tips/&quot;&gt;Nix Flakes Tips and Tricks&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;In this chapter, we’ve explored Nix Flakes as a powerful and modern approach to
managing Nix projects, from development environments to entire system
configurations. We’ve seen how they provide structure, dependency management,
and reproducibility through well-defined inputs and outputs. Flakes offer a
cohesive way to organize your Nix code and share it with others.&lt;/p&gt;
&lt;p&gt;As we’ve worked with the flake.nix file, you’ve likely noticed its structure – a
top-level attribute set defining various outputs like devShells, packages,
nixosConfigurations, and more. These top-level attributes are not arbitrary;
they follow certain conventions and play specific roles within the Flake
ecosystem.&lt;/p&gt;
&lt;p&gt;In the next chapter,
&lt;a href=&quot;https://saylesss88.github.io/Understanding_Top-Level_Attributes_5.html&quot;&gt;Understanding Top-Level Attributes&lt;/a&gt;
we will delve deeper into the meaning and purpose of these common top-level
attributes. We’ll explore how they are structured, what kind of expressions they
typically contain, and how they contribute to the overall functionality and
organization of your Nix Flakes. Understanding these attributes is key to
effectively leveraging the full potential of Nix Flakes.&lt;/p&gt;
&lt;h5&gt;Further Resources&lt;/h5&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Resources (Click to Expand)&lt;/summary&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://serokell.io/blog/practical-nix-flakes&quot;&gt;practical-nix-flakes&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://xeiaso.net/blog/nix-flakes-1-2022-02-21/&quot;&gt;Nix Flakes an Introduction&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.tweag.io/blog/2020-07-31-nixos-flakes/&quot;&gt;tweag nix-flakes&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://nixos.wiki/wiki/Flakes&quot;&gt;NixOS-wiki Flakes&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://nix.dev/concepts/flakes.html&quot;&gt;nix.dev flakes&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://vtimofeenko.com/posts/practical-nix-flake-anatomy-a-guided-tour-of-flake.nix/&quot;&gt;anatomy-of-a-flake&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://jade.fyi/blog/flakes-arent-real/&quot;&gt;flakes-arent-real&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://mhwombat.codeberg.page/nix-book/#_attribute_set_operations&quot;&gt;wombats-book-of-nix&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://zero-to-nix.com/concepts/flakes/&quot;&gt;zero-to-nix flakes&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://nixos-and-flakes.thiscute.world/&quot;&gt;nixos-and-flakes-book&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://flakehub.com/&quot;&gt;FlakeHub&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://saylesss88.github.io/images/nixosnix.png&quot; alt=&quot;FlakeHub&quot; /&gt;&lt;/p&gt;
&lt;/details&gt;
</content></entry><entry><title>Package Definitions Explained</title><id>https://saylesss88.github.io/Package_Definitions_Explained_6.html</id><updated>2025-11-21T00:00:00+00:00</updated><author><name>saylesss87@proton.me (saylesss88)</name></author><link href="https://saylesss88.github.io/Package_Definitions_Explained_6.html" rel="alternate"/><content type="html">&lt;h1&gt;Chapter 8&lt;/h1&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Click to Expand Table of Contents&lt;/summary&gt;
&lt;!-- toc --&gt;
&lt;/details&gt;
&lt;p&gt;&lt;img src=&quot;https://saylesss88.github.io/images/coding2.png&quot; alt=&quot;coding2&quot; /&gt;&lt;/p&gt;
&lt;!-- ![gruv1](images/gruv1.png) --&gt;
&lt;h2&gt;Package Definitions Explained&lt;/h2&gt;
&lt;p&gt;In Nix, the concept of a &lt;strong&gt;package&lt;/strong&gt; can refer to two things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;A collection of files and data that constitute a piece of software or an
artifact.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A Nix &lt;strong&gt;expression&lt;/strong&gt; that describes how to create such a collection. This
expression acts as a blueprint before the package exists in a tangible form.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The process begins with writing a &lt;strong&gt;package definition&lt;/strong&gt; using the Nix language.
This definition contains the necessary instructions and metadata about the
software you intend to “package.”&lt;/p&gt;
&lt;h2&gt;The Journey from Definition to Package&lt;/h2&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Click to Expand&lt;/summary&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Package Definition:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;This is essentially a function written in the Nix language.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Nix language shares similarities with JSON but includes the crucial
addition of functions.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;It acts as the blueprint for creating a package.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Derivation:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;When the package definition is evaluated by Nix, it results in a
&lt;strong&gt;derivation&lt;/strong&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A derivation is a concrete and detailed build plan.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;It outlines the exact steps Nix needs to take: fetching source code,
building dependencies, compiling code, and ultimately producing the
desired output (the package).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Realization (Building the Package):&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;You don’t get a pre-built “package” directly from the definition or the
derivation.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The package comes into being when Nix &lt;strong&gt;executes&lt;/strong&gt; the derivation. This
process is often referred to as “realizing” the derivation.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Analogy:&lt;/strong&gt; Think of a package definition as an architectural blueprint, the
derivation as the detailed construction plan, and the realized package as the
finished building.&lt;/p&gt;
&lt;/details&gt;
## Skeleton of a Derivation
&lt;p&gt;The most basic derivation structure in Nix looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{ stdenv }:

stdenv.mkDerivation { }
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;This is a function that expects an attribute set containing &lt;code&gt;stdenv&lt;/code&gt; as its
argument.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;It then calls &lt;code&gt;stdenv.mkDerivation&lt;/code&gt; (a function provided by &lt;code&gt;stdenv&lt;/code&gt;) to
produce a derivation.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Currently, this derivation doesn’t specify any build steps or outputs.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Further Reading:&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://ryantm.github.io/nixpkgs/stdenv/stdenv/&quot;&gt;The Standard Environment&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://nixos.org/guides/nix-pills/19-fundamentals-of-stdenv.html&quot;&gt;Fundamentals of Stdenv&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Example: A Simple “Hello” Package Definition&lt;/h2&gt;
&lt;p&gt;Here’s a package definition for the classic “hello” program:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# hello.nix
{
  stdenv,
  fetchzip,
}:

stdenv.mkDerivation {
  pname = &quot;hello&quot;;
  version = &quot;2.12.1&quot;;

  src = fetchzip {
    url = &quot;[https://ftp.gnu.org/gnu/hello/hello-2.12.1.tar.gz](https://ftp.gnu.org/gnu/hello/hello-2.12.1.tar.gz)&quot;;
    sha256 = &quot;&quot;;
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;This is a Nix function that takes stdenv and fetchzip as arguments.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;It uses &lt;code&gt;stdenv.mkDerivation&lt;/code&gt; to define the build process for the “hello”
package.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;pname&lt;/code&gt;: The package name.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;version&lt;/code&gt;: The package version.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;src&lt;/code&gt;: Specifies how to fetch the source code using &lt;code&gt;fetchzip&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Handling Dependencies: Importing Nixpkgs&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;If you try to build &lt;code&gt;hello.nix&lt;/code&gt; directly with &lt;code&gt;nix-build hello.nix&lt;/code&gt;, it will
fail because &lt;code&gt;stdenv&lt;/code&gt; and &lt;code&gt;fetchzip&lt;/code&gt; are part of Nixpkgs, which isn’t included
in this isolated file.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;To make this package definition work, you need to pass the correct arguments
(&lt;code&gt;stdenv&lt;/code&gt;, &lt;code&gt;fetchzip&lt;/code&gt;) to the function.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The recommended approach is to create a &lt;code&gt;default.nix&lt;/code&gt; file in the same
directory:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# default.nix

let
  nixpkgs = fetchTarball &quot;[https://github.com/NixOS/nixpkgs/tarball/nixos-24.05](https://github.com/NixOS/nixpkgs/tarball/nixos-24.05)&quot;;
  pkgs = import nixpkgs { config = {}; overlays = []; };
in
{
  hello = pkgs.callPackage ./hello.nix { };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;This &lt;code&gt;default.nix&lt;/code&gt; imports Nixpkgs.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;It then uses &lt;code&gt;pkgs.callPackage&lt;/code&gt; to call the function in &lt;code&gt;hello.nix&lt;/code&gt;, passing
the necessary dependencies from Nixpkgs.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You can now build the “hello” package using: &lt;code&gt;nix-build -A hello&lt;/code&gt;. The &lt;code&gt;-A&lt;/code&gt;
flag tells Nix to build the attribute named hello from the top-level
expression in default.nix.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Realizing the Derivation and Handling sha256&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Evaluation vs. Realization&lt;/strong&gt;: While “evaluate” refers to Nix processing an
expression, “realize” often specifically means building a derivation and
producing its output in the Nix store.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;When you first run &lt;code&gt;nix-build -A hello&lt;/code&gt;, it will likely fail due to a missing
sha256 hash for the source file. Nix needs this hash for security and
reproducibility. The error message will provide the correct sha256 value.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Example Error&lt;/strong&gt;:&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;  nix-build -A hello
  error: hash mismatch in fixed-output derivation &apos;/nix/store/pd2kiyfa0c06giparlhd1k31bvllypbb-source.drv&apos;:
  specified: sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
  got: sha256-1kJjhtlsAkpNB7f6tZEs+dbKd8z7KoNHyDHEJ0tmhnc=
  error: 1 dependencies of derivation &apos;/nix/store/b4mjwlv73nmiqgkdabsdjc4zq9gnma1l-hello-2.12.1.drv&apos; failed to build
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Replace the empty &lt;code&gt;sha256 = &quot;&quot;;&lt;/code&gt; in &lt;code&gt;hello.nix&lt;/code&gt; with the provided correct
value: &lt;code&gt;sha256 = &quot;1kJjhtlsAkpNB7f6tZEs+dbKd8z7KoNHyDHEJ0tmhnc=&quot;;&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Building and Running the Result&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;After updating the &lt;code&gt;sha256&lt;/code&gt;, you can successfully build the package:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix-build -A hello
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The output will be a result symlink pointing to the built package in the Nix
store. You can then run the “hello” program:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;./result/bin/hello
Hello, world!
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Swaytools Package Definition&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Example: The swaytools Package Definition&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Let’s examine a more complex, real-world package definition from Nixpkgs:
&lt;code&gt;nixpkgs/pkgs/tools/wayland/swaytools/default.nix&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# default.nix
{
  lib,
  setuptools,
  buildPythonApplication,
  fetchFromGitHub,
  slurp,
}:

buildPythonApplication rec {
  pname = &quot;swaytools&quot;;
  version = &quot;0.1.2&quot;;

  format = &quot;pyproject&quot;;

  src = fetchFromGitHub {
    owner = &quot;tmccombs&quot;;
    repo = &quot;swaytools&quot;;
    rev = version;
    sha256 = &quot;sha256-UoWK53B1DNmKwNLFwJW1ZEm9dwMOvQeO03+RoMl6M0Q=&quot;;
  };

  nativeBuildInputs = [ setuptools ];

  propagatedBuildInputs = [ slurp ];

  meta = with lib; {
    homepage = &quot;https://github.com/tmccombs/swaytools&quot;;
    description = &quot;Collection of simple tools for sway (and i3)&quot;;
    license = licenses.gpl3Only;
    maintainers = with maintainers; [ atila ];
    platforms = platforms.linux;
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Breakdown of the Above default.nix&lt;/h3&gt;
&lt;details&gt;
&lt;summary&gt;Click to Expand&lt;/summary&gt;
&lt;p&gt;1 &lt;strong&gt;Function Structure&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;dl&gt;
&lt;dt&gt;The file starts with a function taking an attribute set of dependencies from
Nixpkgs: &lt;code&gt;{ lib, setuptools, buildPythonApplication, fetchFromGitHub, slurp }&lt;/code&gt;&lt;/dt&gt;
&lt;dd&gt;.&lt;/dd&gt;
&lt;/dl&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;&lt;strong&gt;Derivation Creation&lt;/strong&gt;:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;It calls &lt;code&gt;buildPythonApplication&lt;/code&gt;, a specialized helper for Python packages
(similar to &lt;code&gt;stdenv.mkDerivation&lt;/code&gt; but pre-configured for Python). The &lt;code&gt;rec&lt;/code&gt;
keyword allows attributes within the derivation to refer to each other.&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;&lt;strong&gt;Package Metadata&lt;/strong&gt;:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;pname&lt;/code&gt; and &lt;code&gt;version&lt;/code&gt; define the package’s name and version.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The &lt;code&gt;meta&lt;/code&gt; attribute provides standard package information like the homepage,
description, license, maintainers, and supported platforms.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;&lt;strong&gt;Source Specification&lt;/strong&gt;:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;src&lt;/code&gt; attribute uses &lt;code&gt;fetchFromGitHub&lt;/code&gt; to download the source code from
the specified repository and revision, along with its &lt;code&gt;sha256&lt;/code&gt; hash for
verification.&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;5&quot;&gt;
&lt;li&gt;&lt;strong&gt;Build and Runtime Dependencies&lt;/strong&gt;:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;nativeBuildInputs&lt;/code&gt;: Lists tools required during the build process (e.g.,
&lt;code&gt;setuptools&lt;/code&gt; for Python).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;propagatedBuildInputs&lt;/code&gt;: Lists dependencies needed at runtime (e.g., &lt;code&gt;slurp&lt;/code&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;6&quot;&gt;
&lt;li&gt;&lt;strong&gt;Build Format&lt;/strong&gt;:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;format = &quot;pyproject&quot;;&lt;/code&gt; indicates that the package uses a &lt;code&gt;pyproject.toml&lt;/code&gt;
file for its Python build configuration.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Integration within Nixpkgs&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Location&lt;/strong&gt;: The swaytools definition resides in
&lt;code&gt;pkgs/tools/wayland/swaytools/default.nix&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Top-Level Inclusion&lt;/strong&gt;: It’s made available as a top-level package in
&lt;code&gt;pkgs/top-level/all-packages.nix&lt;/code&gt; like this:&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# all-packages.nix
swaytools = python3Packages.callPackage ../tools/wayland/swaytools { };
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;python3Packages.callPackage&lt;/code&gt; is used here because &lt;code&gt;swaytools&lt;/code&gt; is a Python
package, and it ensures the necessary Python-related dependencies are correctly
passed to the &lt;code&gt;swaytools&lt;/code&gt; definition.&lt;/li&gt;
&lt;/ul&gt;
&lt;/details&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;In this chapter, we’ve journeyed through the fundamental concept of package
definitions in Nix. We’ve seen how these Nix expressions act as blueprints,
leading to the creation of derivations – the detailed plans for building
software. Finally, we touched upon the realization process where Nix executes
these derivations to produce tangible packages in the Nix store. Examining the
simple “hello” package and the more complex “swaytools” definition provided
practical insights into the structure and key attributes involved in defining
software within the Nix ecosystem.&lt;/p&gt;
&lt;p&gt;The crucial step in this process, the transformation from a package definition
to a concrete build plan, is embodied by the &lt;strong&gt;derivation&lt;/strong&gt;. This detailed
specification outlines every step Nix needs to take to fetch sources, build
dependencies, compile code, and produce the final package output. Understanding
the anatomy and lifecycle of a derivation is key to unlocking the full power and
flexibility of Nix.&lt;/p&gt;
&lt;p&gt;In the &lt;strong&gt;next chapter&lt;/strong&gt;,
&lt;a href=&quot;https://saylesss88.github.io/Intro_to_Nix_Derivations_7.html&quot;&gt;Introduction to Nix Derivations&lt;/a&gt;,
we will delve deeper into the structure and components of these derivations. We
will explore the attributes that define a build process, how dependencies are
managed within a derivation, and how Nix ensures the reproducibility and
isolation of your software builds through this fundamental concept.&lt;/p&gt;
&lt;h2&gt;Resources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://nix.dev/tutorials/packaging-existing-software.html&quot;&gt;Packaging Existing Software&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content></entry><entry><title>Introduction</title><id>https://saylesss88.github.io/README.html</id><updated>2000-01-01T00:00:00+00:00</updated><link href="https://saylesss88.github.io/README.html" rel="alternate"/><content type="html">&lt;p&gt;📚 Welcome to nix-book!&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/saylesss88/nix-book/actions/workflows/deploy-book.yml&quot;&gt;&lt;img src=&quot;https://github.com/saylesss88/nix-book/actions/workflows/deploy-book.yml/badge.svg?branch=main&quot; alt=&quot;Deploy mdBook to User GitHub Pages&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.buymeacoffee.com/saylesss88&quot;&gt;&lt;img src=&quot;https://img.shields.io/badge/Buy%20Me%20a%20Coffee-%23FFDD00?style=for-the-badge&amp;amp;logo=buy-me-a-coffee&amp;amp;logoColor=black&quot; alt=&quot;Buy Me A Coffee&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;🚀 If you find this guide helpful, please consider leaving a star ⭐ or
supporting the project by buying me a coffee ☕. Your support helps keep this
content updated and freely available.&lt;/p&gt;
&lt;p&gt;Follow nix-book with your preferred feed format for automatic notifications of
new content:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://saylesss88.github.io/rss.xml&quot;&gt;nix-book RSS&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://saylesss88.github.io/atom.xml&quot;&gt;nix-book Atom&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://saylesss88.github.io/feed.json&quot;&gt;nix-book JSON Feed&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Welcome to my personal journey and guide through the Nix ecosystem! This “book”
aims to be a practical and understandable resource for anyone looking to dive
into Nix, NixOS, and Home Manager. Whether you’re just getting started or
looking to deepen your understanding, I hope you find this resource helpful!&lt;/p&gt;
&lt;p&gt;I am a technologist with a wide range of interests that I’m passionate about,
NixOS being one of them. I am also a privacy advocate trying to spread the word.&lt;/p&gt;
&lt;p&gt;✨ What You’ll Find Here&lt;/p&gt;
&lt;p&gt;This book covers a range of topics to help you harness the power of Nix:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Getting Started with the Nix Ecosystem: Covers the Nix Language, Package
Manager, and a minimal Btrfs-Subvol install with Disko and Flakes, including
Btrfs Impermanence.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Hardening NixOS: A deep dive into security and hardening practices with NixOS&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Hardening Networking: Configure dnscrypt-proxy, firewalls, and more.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Gnupg and gpg-agent on NixOS&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Whonix KVM on NixOS&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;NixOS as a Guest VM with Secureblue as the Host&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Version Control with Git&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Version Control with JJ&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Understanding Nix Functions: Explores practical Nix functions and their role
with NixOS Modules.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;NixOS Modules Explained: A dedicated deep dive into NixOS’s modular
configuration system.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Nix Flakes Explained: Comprehensive coverage of Flake Inputs, Outputs,
Examples, and extending Flakes with Custom Packages using Overlays.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Understanding Top-Level Attributes&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Package Definitions Explained&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Intro to Nix Derivations: Including how builders and Autotools work.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Comparing Flakes and Traditional Nix&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Debugging and Tracing NixOS Modules&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Working with Nixpkgs Locally&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fork, Clone, Contribute to Nixpkgs&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Nix Pull Requests&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Intro to Nushell on NixOS&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;My goal is to share my learnings and provide clear examples to make your Nix
experience smoother and more enjoyable.&lt;/p&gt;
&lt;p&gt;📖 How to Read the Book&lt;/p&gt;
&lt;p&gt;You can read the book directly here on GitHub by navigating through the folders,
or for a more comfortable reading experience, check out the dedicated website. I
tried to write it in a way where you could jump to the chapter you’re interested
in and still be able to follow along:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://saylesss88.github.io/&quot;&gt;Start Here&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Browse the book chapters here on
&lt;a href=&quot;https://github.com/saylesss88/nix-book/tree/main/src&quot;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;🙏 Contributions &amp;amp; Feedback&lt;/p&gt;
&lt;p&gt;This book is a living document, and I welcome your input! If you find any
errors, have suggestions for improvements, or want to contribute a new section,
please feel free to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Reach out to me on Reddit &lt;code&gt;u/saylesss88&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Open an Issue: For bug reports, typos, or content suggestions.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Submit a Pull Request: If you have code changes or want to add content
directly.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Your feedback is invaluable and helps make this resource better for everyone!&lt;/p&gt;
&lt;p&gt;⚖️ License&lt;/p&gt;
&lt;p&gt;This “Nix Book” is open-source and licensed under the Apache License 2.0.&lt;/p&gt;
&lt;p&gt;This means you are free to use, modify, and distribute this work, provided you
adhere to the terms of the license. You can find the full text of the license in
the LICENSE file within this repository:&lt;/p&gt;
&lt;p&gt;To see a WIP book on privacy, checkout
&lt;a href=&quot;https://saylesss88.github.io/privacy-book/&quot;&gt;privacy-book&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;View the
&lt;a href=&quot;https://github.com/saylesss88/nix-book/tree/main?tab=Apache-2.0-1-ov-file&quot;&gt;Apache License 2.0&lt;/a&gt;&lt;/p&gt;
</content></entry><entry><title>Chapter1</title><id>https://saylesss88.github.io/Getting_Started_with_Nix_1.html</id><updated>2000-01-01T00:00:00+00:00</updated><link href="https://saylesss88.github.io/Getting_Started_with_Nix_1.html" rel="alternate"/><content type="html">&lt;h1&gt;Chapter1&lt;/h1&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Click to Expand Table of Contents&lt;/summary&gt;
&lt;!-- toc --&gt;
&lt;/details&gt;
&lt;!-- ![gruv13](images/gruv13.png) --&gt;
&lt;p&gt;&lt;img src=&quot;https://saylesss88.github.io/images/trees1.cleaned.png&quot; alt=&quot;trees&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Intro&lt;/h2&gt;
&lt;p&gt;Welcome to &lt;em&gt;nix-book&lt;/em&gt;, an introductory book about Nix. This book leans more
towards using Flakes but will contrast traditional Nix where beneficial.
Originally, this content started as a blog. I’m refining its flow to make it
more cohesive.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;In this chapter, I will touch on the different parts of the Nix ecosystem, give
a quick example of each and explain how they fit together.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Click &lt;a href=&quot;https://saylesss88.github.io/rss.xml&quot;&gt;Here&lt;/a&gt;, or the logo on the top
right, next to print for the RSS feed.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;details&gt;
&lt;summary&gt;
- ✔️: Will indicate an expandable section, click the little triangle to expand.
&lt;/summary&gt;
&lt;ul&gt;
&lt;li&gt;These sections are expandable!&lt;/li&gt;
&lt;/ul&gt;
&lt;/details&gt;
&lt;p&gt;The code blocks have an option to hide code, where I find it reasonable I will
hide the outputs of the expressions. Click the eye in the right corner of the
code block next to the copy clipboard.&lt;/p&gt;
&lt;p&gt;Example hover over top-right corner of code block and click the eye to see
hidden text:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{
  attrset = { a = 2; b = 4; };
~  hidden_set = { a = hidden; b = set; };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;❗ If you’re new to Nix, think of it as a recipe book for software: you
describe what you want (declarative), and Nix ensures it’s built the same way
every time (reproducible).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Why Learn Nix?&lt;/h3&gt;
&lt;p&gt;The main reason to learn Nix is that it allows you to write declarative scripts
for reproducible software builds. Rather than mutate the global state and
install packages to a global location such as &lt;code&gt;/usr/bin&lt;/code&gt; Nix stores packages in
the Nix store, usually the directory &lt;code&gt;/nix/store&lt;/code&gt;, where each package has its
own unique subdirectory. This paradigm gives you some powerful features, such
as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Allowing multiple versions or variants of the same package at the same time.
This prevents “DLL hell” from different applications having dependencies on
different versions of the same package.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Atomic upgrades: Upgrading or uninstalling an application cannot break other
applications and either succeed completely or fail completely preventing
partial upgrades breaking your system. The nix store is immutable preventing
package management operations from overwriting other packages. They wouldn’t
overwrite each other anyways because the hashing scheme ensures that new
versions or repeat packages end up at different paths.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Nix is designed to provide hermetic builds that aren’t affected by the
environment, this helps you make sure that when packaging software that the
dependencies are complete because they must be explicitly declared as inputs.
With other package managers it is more difficult to be sure that an
environment variable or something in your &lt;code&gt;$PATH&lt;/code&gt; isn’t affecting your build.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let’s dive into the key characteristics of Nix:&lt;/p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Concept&lt;/th&gt;&lt;th&gt;Description&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Pure&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Functions don’t cause side effects.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Functional&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Functions can be passed as arguments and returned as results.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Lazy&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Not evaluated until needed to complete a computation.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Declarative&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Describing a system outcome.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Reproducible&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Operations that are performed twice return same results&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;❗ Important: In Nix, everything is an expression, there are no statements.&lt;/p&gt;
&lt;p&gt;❗ Important: Values in Nix are immutable.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;The Nix Ecosystem&lt;/h3&gt;
&lt;p&gt;The &lt;strong&gt;Nix Language&lt;/strong&gt; is the foundation of the ecosystem and is used to write
&lt;strong&gt;Nix Expressions&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{ hello = &quot;world&quot;; }
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;or&lt;/h1&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;&quot;foo&quot; + &quot;bar&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;While the Nix language provides the foundation for writing expressions, it is
only part of the ecosystem. These expressions become powerful when used within
the Nix Package Manager, which evaluates and realizes them into tangible
software builds and system configurations. This is where Nixpkgs and NixOS come
into play.&lt;/p&gt;
&lt;h3&gt;The Nix Package Manager, Nixpkgs, and NixOS&lt;/h3&gt;
&lt;p&gt;At the heart of the Nix ecosystem is &lt;strong&gt;Nix Package Manager&lt;/strong&gt;. This powerful
engine is responsible for orchestrating the entire process: taking &lt;strong&gt;Nix
expressions&lt;/strong&gt; (like &lt;em&gt;package definitions&lt;/em&gt; and &lt;em&gt;configuration modules&lt;/em&gt;),
evaluating them into precise &lt;em&gt;derivations&lt;/em&gt;, executing their build steps (the
&lt;em&gt;realization phase&lt;/em&gt;), and meticulously managing the immutable Nix store.&lt;/p&gt;
&lt;p&gt;A cornerstone of the Nix ecosystem is &lt;strong&gt;Nixpkgs&lt;/strong&gt;. This vast collection
comprises tens of thousands of Nix expressions that describe how to build a wide
array of software packages from source. Nixpkgs is more than just a package
repository—it also contains &lt;strong&gt;NixOS Modules&lt;/strong&gt;, declarative configurations that
define system behavior, ensuring a structured and reproducible environment.
These modules enable users to declaratively describe a Linux system, with each
module contributing to the desired state of the overall system by leveraging
&lt;em&gt;package definitions&lt;/em&gt; and &lt;em&gt;derivations&lt;/em&gt;. This is how NixOS emerges: it is quite
simply the natural consequence of applying the Nix philosophy to building an
entire Linux operating system.&lt;/p&gt;
&lt;p&gt;We will further expand our understanding of modules in
&lt;a href=&quot;https://saylesss88.github.io/NixOS_Modules_Explained_3.html&quot;&gt;Chapter 3&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The following is an example of a NixOS module that is part of the &lt;code&gt;nixpkgs&lt;/code&gt;
collection:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# nixpkgs/nixos/modules/programs/zmap.nix
{
  pkgs,
  config,
  lib,
  ...
}:

let
  cfg = config.programs.zmap;
in
{
  options.programs.zmap = {
    enable = lib.mkEnableOption &quot;ZMap, a network scanner designed for Internet-wide network surveys&quot;;
  };

  config = lib.mkIf cfg.enable {
    environment.systemPackages = [ pkgs.zmap ];

    environment.etc.&quot;zmap/blacklist.conf&quot;.source = &quot;${pkgs.zmap}/etc/zmap/blacklist.conf&quot;;
    environment.etc.&quot;zmap/zmap.conf&quot;.source = &quot;${pkgs.zmap}/etc/zmap.conf&quot;;
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;This module, &lt;code&gt;programs.zmap.nix&lt;/code&gt;, demonstrates how NixOS configurations work.
It defines an enable option for the ZMap network scanner. If enabled by the
user in their system configuration, the module ensures the &lt;code&gt;zmap&lt;/code&gt; package is
installed and its default configuration files are placed in &lt;code&gt;/etc&lt;/code&gt;, allowing
ZMap to be managed declaratively as part of the operating system.
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;When &lt;code&gt;nixpkgs&lt;/code&gt; is imported (e.g., in a NixOS configuration), the
configuration options and settings defined by its modules (like
&lt;code&gt;programs.zmap.nix&lt;/code&gt;) become available for use, typically accessed via dot
notation (e.g., &lt;code&gt;config.programs.zmap.enable&lt;/code&gt;). This ability to make such a
huge set of modules and packages readily available without a significant
performance penalty is due to Nix’s &lt;strong&gt;lazy evaluation&lt;/strong&gt;; only the
expressions required for a particular build or configuration are actually
evaluated.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Most of the time you’ll simply &lt;a href=&quot;https://search.nixos.org/packages&quot;&gt;search&lt;/a&gt;
to see if the package is already included in &lt;code&gt;nixpkgs&lt;/code&gt; and follow the
instructions there to get it on your system. It is good practice to first
search for the &lt;a href=&quot;https://search.nixos.org/options?&quot;&gt;options&lt;/a&gt; to see what
configurable settings are available, and then proceed to search for the
package itself if you know it exists or if you need its specific package
definition. When you look up the options for Zmap, &lt;code&gt;programs.zmap.enable&lt;/code&gt; is
all that is listed in this example.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Home Manager uses the same underlying Nix module system as NixOS, and when
you do something like home.packages = with pkgs; you are referring to the
same package derivations from nixpkgs as you would with
&lt;code&gt;environment.systemPackages&lt;/code&gt;. However, Home Manager’s own configuration
modules (e.g., for &lt;code&gt;programs.zsh&lt;/code&gt; or &lt;code&gt;git&lt;/code&gt;) are distinct and reside in the
Home Manager repository, designed for user-specific configurations.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;One of the main differentiating aspects of Nix, as opposed to traditional
package managers, is this concept that package builds are treated as pure
functions. This functional paradigm ensures consistency and reproducibility,
which are core tenets of the Nix philosophy.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://saylesss88.github.io/images/nix_isnot_nixos.png&quot; alt=&quot;Nix is not&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Fig. X:&lt;/strong&gt; Conceptual diagram illustrating the distinction between Nix and
NixOS. Source: xeiaso, from the blog post “Making NixOS modules for fun and
(hopefully) profit”, &lt;a href=&quot;https://xeiaso.net/talks/asg-2023-nixos/&quot;&gt;https://xeiaso.net/talks/asg-2023-nixos/&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Nix expressions permeate the ecosystem—everything in Nix is an expression,
including the next key components: package definitions and derivations.&lt;/p&gt;
&lt;h3&gt;Package Definitions &amp;amp; Derivations&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Package Definitions&lt;/strong&gt; are specialized expressions that tell Nix how to build
software.&lt;/p&gt;
&lt;p&gt;Example package definition:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;# hello.nix
 {pkgs ? import &amp;lt;nixpkgs&amp;gt; {}}:
 pkgs.stdenv.mkDerivation {
  pname = &quot;hello&quot;;
  version = &quot;2.12.1&quot;;

  src = pkgs.fetchurl {
    url = &quot;https://ftp.gnu.org/gnu/hello/hello-2.12.1.tar.gz&quot;;
    sha256 = &quot;086vqwk2wl8zfs47sq2xpjc9k066ilmb8z6dn0q6ymwjzlm196cd&quot;;
  };

  nativeBuildInputs = [pkgs.autoconf pkgs.automake pkgs.gcc];

  configurePhase = &apos;&apos;
    ./configure --prefix=$out
  &apos;&apos;;

  buildPhase = &apos;&apos;
    make
  &apos;&apos;;

  installPhase = &apos;&apos;
    make install
  &apos;&apos;;
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Evaluation Phase&lt;/strong&gt;:&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now when you run something like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix-instantiate hello.nix
warning: you did not specify &apos;--add-root&apos;; the result might be removed by the garbage collector
/nix/store/p2hbg16a9kpqgx2nzcsq39wmnyxyq4jy-hello-2.12.1.drv
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Nix evaluates the expression and produces a &lt;code&gt;.drv&lt;/code&gt; file (the &lt;strong&gt;derivation&lt;/strong&gt;),
a precise JSON-like blueprint describing how the package will be built. It
does not contain the built software itself.&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;&lt;strong&gt;Realization Phase&lt;/strong&gt;:&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;When you run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix-build hello.nix
#...snip...
shrinking RPATHs of ELF executables and libraries in /nix/store/53hqyw72dijq3wb5kc0ln04g681gk6cp-hello-2.12.1
shrinking /nix/store/53hqyw72dijq3wb5kc0ln04g681gk6cp-hello-2.12.1/bin/hello
checking for references to /build/ in /nix/store/53hqyw72dijq3wb5kc0ln04g681gk6cp-hello-2.12.1...
gzipping man pages under /nix/store/53hqyw72dijq3wb5kc0ln04g681gk6cp-hello-2.12.1/share/man/
patching script interpreter paths in /nix/store/53hqyw72dijq3wb5kc0ln04g681gk6cp-hello-2.12.1
stripping (with command strip and flags -S -p) in  /nix/store/53hqyw72dijq3wb5kc0ln04g681gk6cp-hello-2.12.1/bin
/nix/store/53hqyw72dijq3wb5kc0ln04g681gk6cp-hello-2.12.1
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Nix realizes the derivation by actually executing the build steps, fetching
sources, compiling (if needed), and producing the final result (typically
stored in e.g. &lt;code&gt;/nix/store/53hqyw72dijq3wb5kc0ln04g681gk6cp-hello-2.12.1&lt;/code&gt;)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;nix-build&lt;/code&gt; also creates a symlink named &lt;code&gt;result&lt;/code&gt; in your current directory,
pointing to the final build output in the Nix store.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;Execute the program:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;./result/bin/hello
Hello, world!
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;result/bin/hello&lt;/code&gt; points to the executable inside the output of the
derivation.The derivation describes how the package is built, but does not
include the final binaries.&lt;/p&gt;
&lt;p&gt;To say that another way, the derivation is not the executable. The executable is
one of the derivations &lt;code&gt;outputs&lt;/code&gt;. When Nix “realizes” a derivation, it executes
those build instructions, and the result is the actual built software, which
gets placed into its own unique path in the Nix store.&lt;/p&gt;
&lt;p&gt;A single derivation can produce multiple outputs. The executable is typically
part of the &lt;code&gt;out&lt;/code&gt; output, specifically in its &lt;code&gt;bin&lt;/code&gt; directory.&lt;/p&gt;
&lt;p&gt;Here is a small snippet of what a &lt;code&gt;.drv&lt;/code&gt; file could look like, I got this from
building the hello derivation and running the following on the store path:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nix show-derivation /nix/store/9na8mwp5zaprikqaqw78v6cdn1rxac7i-hello-2.12.1
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{
  &quot;/nix/store/871398c9cbskmzy6bvfnynr8yrlh7nk0-hello-2.12.1.drv&quot;: {
    &quot;args&quot;: [
      &quot;-e&quot;,
      &quot;/nix/store/v6x3cs394jgqfbi0a42pam708flxaphh-default-builder.sh&quot;
    ],
    &quot;builder&quot;: &quot;/nix/store/1jzhbwq5rjjaqa75z88ws2b424vh7m53-bash-5.2p32/bin/bash&quot;,
    &quot;env&quot;: {
      &quot;__structuredAttrs&quot;: &quot;&quot;,
      &quot;buildInputs&quot;: &quot;&quot;,
      &quot;builder&quot;: &quot;/nix/store/1jzhbwq5rjjaqa75z88ws2b424vh7m53-bash-5.2p32/bin/bash&quot;,
      &quot;cmakeFlags&quot;: &quot;&quot;,
      &quot;configureFlags&quot;: &quot;&quot;,
      &quot;depsBuildBuild&quot;: &quot;&quot;,
      &quot;depsBuildBuildPropagated&quot;: &quot;&quot;,
      &quot;depsBuildTarget&quot;: &quot;&quot;,
      &quot;depsBuildTargetPropagated&quot;: &quot;&quot;,
      &quot;depsHostHost&quot;: &quot;&quot;,
      &quot;depsHostHostPropagated&quot;: &quot;&quot;,
      &quot;depsTargetTarget&quot;: &quot;&quot;,
      &quot;depsTargetTargetPropagated&quot;: &quot;&quot;,
      &quot;doCheck&quot;: &quot;&quot;,
      &quot;doInstallCheck&quot;: &quot;&quot;,
      &quot;mesonFlags&quot;: &quot;&quot;,
      &quot;name&quot;: &quot;hello-2.12.1&quot;,
      &quot;nativeBuildInputs&quot;: &quot;&quot;,
      &quot;out&quot;: &quot;/nix/store/9na8mwp5zaprikqaqw78v6cdn1rxac7i-hello-2.12.1&quot;,
      &quot;outputs&quot;: &quot;out&quot;,
      &quot;patches&quot;: &quot;&quot;,
      &quot;pname&quot;: &quot;hello&quot;,
      &quot;propagatedBuildInputs&quot;: &quot;&quot;,
      &quot;propagatedNativeBuildInputs&quot;: &quot;&quot;,
      &quot;src&quot;: &quot;/nix/store/pa10z4ngm0g83kx9mssrqzz30s84vq7k-hello-2.12.1.tar.gz&quot;,
      &quot;stdenv&quot;: &quot;/nix/store/80wijs24wjp619zmrasrh805bax02xjm-stdenv-linux&quot;,
      &quot;strictDeps&quot;: &quot;&quot;,
      &quot;system&quot;: &quot;x86_64-linux&quot;,
      &quot;version&quot;: &quot;2.12.1&quot;
    },
# ... snip ...
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Conclusion&lt;/h4&gt;
&lt;p&gt;In this introductory chapter, we’ve laid the groundwork for understanding the
powerful Nix ecosystem. We explored how the Nix Language forms the declarative
bedrock, enabling us to define desired system states and software builds as
expressions. You saw how the Nix Package Manager orchestrates this process,
transforming those expressions into precise derivations during the evaluation
phase, and then faithfully “realizing” them into reproducible, isolated
artifacts within the immutable &lt;code&gt;/nix/store&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;We also introduced the vast Nixpkgs collection, which provides tens of thousands
of package definitions and forms the foundation for NixOS — a fully declarative
operating system built on these principles—and even user-level configurations
like those managed by Home Manager. This unique functional approach, with its
emphasis on immutability and lazy evaluation, is what enables Nix’s promises of
consistency, atomic upgrades, and truly hermetic builds, fundamentally changing
how we think about software and system management.&lt;/p&gt;
&lt;h5&gt;Related Sub-Chapters&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The &lt;a href=&quot;https://saylesss88.github.io/nix/nix_language.html&quot;&gt;Nix Language&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://saylesss88.github.io/nix/nix_package_manager.html&quot;&gt;Nix Package Manager&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now that you have a foundational understanding of the Nix ecosystem and its core
operational cycle, we are ready to delve deeper into the building blocks of Nix
expressions. In the next chapter,
&lt;a href=&quot;https://saylesss88.github.io/Understanding_Nix_Functions_2.html&quot;&gt;Understanding Nix Functions&lt;/a&gt;,
we will peel back the layers and explore the intricacies of function arguments,
advanced patterns, scope, and how functions play a crucial role in building more
sophisticated Nix expressions and derivations.&lt;/p&gt;
&lt;p&gt;Here are some resources that are helpful for getting started:&lt;/p&gt;
&lt;h4&gt;Resources&lt;/h4&gt;
&lt;details&gt;
&lt;summary&gt; ✔️ Resources (Click to Expand)&lt;/summary&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://search.nixos.org/packages&quot;&gt;NixOS Search&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://search.nixos.org/options?&quot;&gt;NixOS Options&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://home-manager-options.extranix.com/?query=&amp;amp;release=master&quot;&gt;Extranix Home-Manager Option Search&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/nix-community/awesome-nix&quot;&gt;awesome-nix&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://wiki.nixos.org/wiki/Nix_ecosystem&quot;&gt;Nix Core Ecosystem&lt;/a&gt;, Nix, NixOS,
Nix Lang, Nixpkgs are all distinctly different; related things which can be
confusing for beginners this article explains them.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/nixos/nixpkgs&quot;&gt;nixpkgs&lt;/a&gt;: Vast package repository&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://nixos.org/guides/how-nix-works/&quot;&gt;How Nix Works&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://nix.dev/manual/nix/2.26/language/types#type-attrs&quot;&gt;Nix Reference Manual Data Types&lt;/a&gt;
The main Data Types you’ll come across in the Nix ecosystem&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://wiki.nixos.org/wiki/NixOS_Wiki&quot;&gt;NixOS Wiki&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://nix.dev/&quot;&gt;nix.dev&lt;/a&gt;: Has become the top respected source of
information in my opinion. There is a lot of great stuff in here, and they
actively update the information.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/details&gt;
&lt;pre&gt;&lt;code&gt;`````nix repl
(let a = &quot;2&quot;; in                   # Let expressions are a way to create variables
a + a + builtins.toString &quot;4&quot;)
`````
&lt;/code&gt;&lt;/pre&gt;
</content></entry></feed>