Guekka's blog
C++, Nix, Linux, Self-hosting, and more.
Zola
2023-05-17T00:00:00+00:00
https://guekka.github.io/atom.xml
NixOS as a server, part 2: Flake, tailscale
2023-05-17T00:00:00+00:00
2023-05-17T00:00:00+00:00
Unknown
https://guekka.github.io/nixos-server-2/
<p>In the <a rel="noopener" target="_blank" href="https://guekka.github.io/nixos-server-1/">previous part</a>, we configured our NixOS server to use impermanence. I have made a few changes since, most notably moving to a proper VM in Proxmox.</p>
<p>The following instructions might lack some details, but you can follow <a rel="noopener" target="_blank" href="https://github.com/Guekka/nixos-server/commits/2-tailscale">the GitHub repo</a> to see the full code.</p>
<h1 id="moving-to-flakes">Moving to flakes</h1>
<blockquote>
<p>If you already know about flakes, you can safely ignore this part.</p>
</blockquote>
<p>Have you heard about Nix flakes? If you have been in the Nix ecosystem for more than a few days, most likely. They’re the shiny new way to write Nix code, still experimental but used everywhere.
Their main advantage over traditional Nix is <em>purity</em>, mainly with their defined <code>inputs</code> and <code>outputs</code>. </p>
<p>Remember when I told you Nix was reproducible? It was a lie. Let me explain myself: when writing Nix code, we always have some kind of input. For example, <code>nixpkgs</code> will be required almost all the time. There are two ways to obtain it.</p>
<ul>
<li>fetch it: <code>import builtins.fetchTarball "https://github.com/nixos/nixpkgs/archive/nixos-22.11.tar.gz";</code></li>
<li>or more commonly, use <em>channels</em>: <code>import <nixpkgs> {}</code></li>
</ul>
<p>This second way uses a globally-defined configuration, which can change externally to our Nix files. We thus lose complete reproducibility. Instead, flakes allow us to avoid channels by specifying inputs alongside Nix configuration, as well as blocking some actions that could hinder reproducibility.</p>
<p>For a more in depth introduction, have a look at <a rel="noopener" target="_blank" href="https://nixos.wiki/wiki/Flakes#See_also">the wiki</a>.</p>
<p>Now, why do we want to migrate to flakes? We do not have external requirements, do we?
Well, yes, we do. Apart from the obvious <code>nixpkgs</code> dependency, which is configured system-wide, the impermanence module is being imported:</p>
<pre data-lang="nix" class="language-nix z-code"><code class="language-nix" data-lang="nix"><span class="z-source z-nix"> <span class="z-variable z-parameter z-name z-nix">impermanence</span> <span class="z-invalid z-illegal">=</span> <span class="z-constant z-language z-nix">builtins</span><span class="z-keyword z-operator z-nix">.</span><span class="z-variable z-parameter z-name z-nix">fetchTarball</span> <span class="z-string z-quoted z-double z-nix"><span class="z-punctuation z-definition z-string z-double z-start z-nix">"</span>https://github.com/Nix-community/impermanence/archive/master.tar.gz<span class="z-punctuation z-definition z-string z-double z-end z-nix">"</span></span><span class="z-invalid z-illegal">;</span>
</span></code></pre>
<p>This <code>fetchTarball</code> call is bad, by the way, as we do not specify the expected hash. We could be a victim of a man-in-the-middle attack and not notice it.</p>
<p>Now that I intend to add more modules, and possibly use the <code>unstable</code> channel, it is better to migrate. Let’s see what our entry point would look like:</p>
<pre data-lang="nix" class="language-nix z-code"><code class="language-nix" data-lang="nix"><span class="z-source z-nix"><span class="z-punctuation z-definition z-attrset-or-function z-nix">{</span>
</span><span class="z-source z-nix"> <span class="z-comment z-line z-number-sign z-nix"># what is consumed (previously provided by channels and fetchTarball)</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">inputs</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-punctuation z-definition z-attrset-or-function z-nix">{</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">nixpkgs</span>.<span class="z-entity z-other z-attribute-name z-multipart z-nix">url</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-string z-quoted z-double z-nix"><span class="z-punctuation z-definition z-string z-double z-start z-nix">"</span>github:NixOS/nixpkgs/nixos-22.11<span class="z-punctuation z-definition z-string z-double z-end z-nix">"</span></span><span class="z-punctuation z-terminator z-bind z-nix">;</span> <span class="z-comment z-line z-number-sign z-nix"># (1)</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">impermanence</span>.<span class="z-entity z-other z-attribute-name z-multipart z-nix">url</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-string z-quoted z-double z-nix"><span class="z-punctuation z-definition z-string z-double z-start z-nix">"</span>github:Nix-community/impermanence<span class="z-punctuation z-definition z-string z-double z-end z-nix">"</span></span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"> <span class="z-punctuation z-definition z-attrset z-nix">}</span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix">
</span><span class="z-source z-nix"> <span class="z-comment z-line z-number-sign z-nix"># what will be produced (i.e. the build)</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">outputs</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-punctuation z-definition z-entity z-function z-2 z-nix">{</span> <span class="z-variable z-parameter z-function z-1 z-nix">nixpkgs</span><span class="z-keyword z-operator z-nix">,</span> <span class="z-keyword z-operator z-nix">... </span><span class="z-punctuation z-definition z-entity z-function z-nix">}</span>@<span class="z-variable z-parameter z-function z-3 z-nix">inputs</span><span class="z-punctuation z-definition z-function z-nix">:</span> <span class="z-punctuation z-definition z-attrset-or-function z-nix">{</span> <span class="z-comment z-line z-number-sign z-nix"># (2)</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">nixosConfigurations</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-punctuation z-definition z-attrset-or-function z-nix">{</span> <span class="z-comment z-line z-number-sign z-nix"># (3)</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">server</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-variable z-parameter z-name z-nix">nixpkgs</span><span class="z-keyword z-operator z-nix">.</span><span class="z-variable z-parameter z-name z-nix">lib</span><span class="z-keyword z-operator z-nix">.</span><span class="z-variable z-parameter z-name z-nix">nixosSystem</span> <span class="z-punctuation z-definition z-attrset-or-function z-nix">{</span> <span class="z-comment z-line z-number-sign z-nix"># (4)</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">packages</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-variable z-parameter z-name z-nix">nixpkgs</span><span class="z-keyword z-operator z-nix">.</span><span class="z-variable z-parameter z-name z-nix">legacyPackages</span><span class="z-keyword z-operator z-nix">.</span><span class="z-variable z-parameter z-name z-nix">x86_64-linux</span><span class="z-punctuation z-terminator z-bind z-nix">;</span> <span class="z-comment z-line z-number-sign z-nix"># (5)</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">specialArgs</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-variable z-parameter z-name z-nix">inputs</span><span class="z-punctuation z-terminator z-bind z-nix">;</span> <span class="z-comment z-line z-number-sign z-nix"># forward inputs to modules</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">modules</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-punctuation z-definition z-list z-nix">[</span>
</span><span class="z-source z-nix"> <span class="z-string z-unquoted z-path z-nix">./configuration.Nix</span>
</span><span class="z-source z-nix"> <span class="z-punctuation z-definition z-list z-nix">]</span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"> <span class="z-punctuation z-definition z-attrset z-nix">}</span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"> <span class="z-punctuation z-definition z-attrset z-nix">}</span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"> <span class="z-punctuation z-definition z-attrset z-nix">}</span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"><span class="z-punctuation z-definition z-attrset z-nix">}</span>
</span></code></pre>
<p>That’s a lot to understand at once. Let’s study it one line at a time.
Firstly, we have to understand this file is simply describing <code>outputs</code> as a function taking <code>inputs</code>. Like in mathematics, we create the same output given the same input: a <em>pure</em> function, would say functional programmers.</p>
<p><code>(1)</code> is defining an input: we simply give it an url. That line can be translated as *Grab the <code>nixos-22.11</code> branch from the GitHub repo <code>nixpkgs</code> owned by <code>NixOS</code>.</p>
<p><code>(2)</code> is defining the <code>outputs</code> function. Most complicated things are defined using functions in Nix. It takes a <em>named attribute set</em> argument as an input. So this syntax:</p>
<pre data-lang="nix" class="language-nix z-code"><code class="language-nix" data-lang="nix"><span class="z-source z-nix"><span class="z-punctuation z-definition z-entity z-function z-2 z-nix">{</span> <span class="z-variable z-parameter z-function z-1 z-nix">nixpkgs</span><span class="z-keyword z-operator z-nix">,</span> <span class="z-keyword z-operator z-nix">... </span><span class="z-punctuation z-definition z-entity z-function z-nix">}</span>@<span class="z-variable z-parameter z-function z-3 z-nix">inputs</span><span class="z-punctuation z-definition z-function z-nix">:</span> <span class="z-variable z-parameter z-name z-nix">nixpkgs</span>
</span></code></pre>
<p>is the same as:</p>
<pre data-lang="nix" class="language-nix z-code"><code class="language-nix" data-lang="nix"><span class="z-source z-nix"><span class="z-variable z-parameter z-function z-4 z-nix">inputs</span><span class="z-punctuation z-definition z-function z-nix">:</span> <span class="z-variable z-parameter z-name z-nix">inputs</span><span class="z-keyword z-operator z-nix">.</span><span class="z-variable z-parameter z-name z-nix">nixpkgs</span>
</span></code></pre>
<p>In both cases, we’re accessing the <code>nixpkgs</code> property of the <code>inputs</code> set.</p>
<p>But in the first case, we don’t have to repeat <code>inputs.</code> everywhere. In JS, you would call that <em>destructuring</em>: it is just making inner elements easier to access. If you have troubles understanding the Nix syntax, I personally like <a rel="noopener" target="_blank" href="https://fasterthanli.me/series/building-a-rust-service-with-Nix/part-9">FasterThanLime article</a>.</p>
<p><code>(3)</code>: NixOS configuration have to be placed specifically in the <code>nixosConfigurations</code> set.</p>
<p><code>(4)</code> is the place where we actually define the system. We call the <code>nixosSystem</code> function and pass it some arguments. Yes, the whole system is an <code>output</code> too!</p>
<p><code>(5)</code>: we give the packages instance to our system. In our case, we are passing the default packages, but we might want to modify them before. We also have to specify our architecture (<code>x86_64</code>).</p>
<p>That’s pretty much it. With that <code>flake.Nix</code> in <code>/etc/nixos</code>, <code>nixos-rebuild</code> will work as before. However, if you’re using git, beware that your files all need to be under version control or Nix will not see them.</p>
<h1 id="secrets-with-sops">Secrets with Sops</h1>
<p>In order to set up Tailscale, we will use a pre-auth key. This allows us to connect to our server without interaction. However, we must hide this key, or other people could join our Tailscale network, which could obviously have dangerous consequences.</p>
<p>There are 2 well-known solutions : agenix and sops-nix. I’ve chosen sops for no particular reason.
The first step will be to add it to our flake. See, we already get a use for it!</p>
<h2 id="importing-sops-nix">Importing <code>sops-nix</code></h2>
<p>Let’s change our <code>inputs</code>:</p>
<pre data-lang="nix" class="language-nix z-code"><code class="language-nix" data-lang="nix"><span class="z-source z-nix"> <span class="z-variable z-parameter z-name z-nix">inputs</span> <span class="z-invalid z-illegal">=</span> <span class="z-punctuation z-definition z-attrset-or-function z-nix">{</span>
</span><span class="z-source z-nix"> <span class="z-comment z-line z-number-sign z-nix"># ...</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">sops-nix</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-punctuation z-definition z-attrset-or-function z-nix">{</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">url</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-string z-quoted z-double z-nix"><span class="z-punctuation z-definition z-string z-double z-start z-nix">"</span>github:mic92/sops-nix<span class="z-punctuation z-definition z-string z-double z-end z-nix">"</span></span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">inputs</span>.<span class="z-entity z-other z-attribute-name z-multipart z-nix">nixpkgs</span>.<span class="z-entity z-other z-attribute-name z-multipart z-nix">follows</span> <span class="z-keyword z-operator z-bind z-nix">=</span><span class="z-string z-quoted z-double z-nix"><span class="z-punctuation z-definition z-string z-double z-start z-nix">"</span>nixpkgs<span class="z-punctuation z-definition z-string z-double z-end z-nix">"</span></span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"> <span class="z-punctuation z-definition z-attrset z-nix">}</span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"> <span class="z-punctuation z-definition z-attrset z-nix">}</span><span class="z-invalid z-illegal">;</span>
</span></code></pre>
<h3 id="follows"><code>Follows</code></h3>
<p>What’s up with this <code>follows</code>? <code>sops-nix</code> already depends on <code>nixpkgs</code>, but it might use a different revision than ours. Making it use our own has several advantages:</p>
<ul>
<li>improve consistency</li>
<li>reduce the number of evaluations required</li>
</ul>
<p>And how do we know if a package has inputs that need to be redirected? That’s the neat thing, we don’t. Either we have to look at the upstream <code>flake.nix</code>, or we can call <code>nix flake info</code> and get a graph like so:</p>
<pre class="z-code"><code><span class="z-text z-plain">Resolved URL: git+file:///etc/nixos
</span><span class="z-text z-plain">Locked URL: git+file:///etc/nixos
</span><span class="z-text z-plain">Path: /Nix/store/4b14z6ki7av3kid69sp5vgf50wzd3a73-source
</span><span class="z-text z-plain">Last modified: 2023-04-17 14:04:13
</span><span class="z-text z-plain">Inputs:
</span><span class="z-text z-plain">├───impermanence: github:Nix-community/impermanence/6138e
</span><span class="z-text z-plain">├───nixpkgs: github:NixOS/nixpkgs/39fa0
</span><span class="z-text z-plain">└───sops-Nix: github:mic92/sops-nix/de651
</span><span class="z-text z-plain"> ├───nixpkgs follows input 'nixpkgs'
</span><span class="z-text z-plain"> └───nixpkgs-stable: github:NixOS/nixpkgs/1040c
</span></code></pre>
<p>We can notice <code>sops-nix</code> also has a <code>nixpkgs-stable</code> input, that we might as well redirect.</p>
<h2 id="generating-a-key">Generating a key</h2>
<p><code>sops-nix</code> works by encrypting our secrets with private keys.
We thus need to provide it with the keys we will use. We can generate an <code>age</code> key, or get one from our <code>SSH</code> host key.
Each secrets group can have different allowed keys, so that one user cannot access another’s secrets.
I will use the SSH host key for my server:</p>
<pre class="z-code"><code><span class="z-text z-plain">$ nix-shell -p ssh-to-age --run 'cat /etc/ssh/ssh_host_ed25519_key.pub | ssh-to-age'
</span><span class="z-text z-plain">age1dt24qetqhy2ng53fyj69yq9hg8rdsg4ep0lvvhdg69xw9v4l0asqj6xzkh
</span></code></pre>
<p>We now have to write <code>.sops.yaml</code> file in order to configure which keys can access which secrets.</p>
<pre data-lang="yaml" class="language-yaml z-code"><code class="language-yaml" data-lang="yaml"><span class="z-source z-yaml"><span class="z-string z-unquoted z-plain z-out z-yaml"><span class="z-entity z-name z-tag z-yaml">keys</span></span><span class="z-punctuation z-separator z-key-value z-mapping z-yaml">:</span>
</span><span class="z-source z-yaml"> <span class="z-punctuation z-definition z-block z-sequence z-item z-yaml">-</span> <span class="z-meta z-property z-yaml"><span class="z-keyword z-control z-property z-anchor z-yaml"><span class="z-punctuation z-definition z-anchor z-yaml">&</span></span><span class="z-entity z-name z-other z-anchor z-yaml">horus</span></span> <span class="z-string z-unquoted z-plain z-out z-yaml">age1dt24qetqhy2ng53fyj69yq9hg8rdsg4ep0lvvhdg69xw9v4l0asqj6xzkh</span>
</span><span class="z-source z-yaml"><span class="z-string z-unquoted z-plain z-out z-yaml"><span class="z-entity z-name z-tag z-yaml">creation_rules</span></span><span class="z-punctuation z-separator z-key-value z-mapping z-yaml">:</span>
</span><span class="z-source z-yaml"> <span class="z-punctuation z-definition z-block z-sequence z-item z-yaml">-</span> <span class="z-string z-unquoted z-plain z-out z-yaml"><span class="z-entity z-name z-tag z-yaml">path_regex</span></span><span class="z-punctuation z-separator z-key-value z-mapping z-yaml">:</span> <span class="z-string z-unquoted z-plain z-out z-yaml">hosts/horus/secrets.yaml$</span>
</span><span class="z-source z-yaml"> <span class="z-string z-unquoted z-plain z-out z-yaml"><span class="z-entity z-name z-tag z-yaml">key_groups</span></span><span class="z-punctuation z-separator z-key-value z-mapping z-yaml">:</span>
</span><span class="z-source z-yaml"> <span class="z-punctuation z-definition z-block z-sequence z-item z-yaml">-</span> <span class="z-string z-unquoted z-plain z-out z-yaml"><span class="z-entity z-name z-tag z-yaml">age</span></span><span class="z-punctuation z-separator z-key-value z-mapping z-yaml">:</span>
</span><span class="z-source z-yaml"> <span class="z-punctuation z-definition z-block z-sequence z-item z-yaml">-</span> <span class="z-keyword z-control z-flow z-alias z-yaml"><span class="z-punctuation z-definition z-alias z-yaml">*</span></span><span class="z-variable z-other z-alias z-yaml">horus</span>
</span><span class="z-source z-yaml"> <span class="z-punctuation z-definition z-block z-sequence z-item z-yaml">-</span> <span class="z-string z-unquoted z-plain z-out z-yaml"><span class="z-entity z-name z-tag z-yaml">path_regex</span></span><span class="z-punctuation z-separator z-key-value z-mapping z-yaml">:</span> <span class="z-string z-unquoted z-plain z-out z-yaml">hosts/common/secrets.yaml$</span>
</span><span class="z-source z-yaml"> <span class="z-string z-unquoted z-plain z-out z-yaml"><span class="z-entity z-name z-tag z-yaml">key_groups</span></span><span class="z-punctuation z-separator z-key-value z-mapping z-yaml">:</span>
</span><span class="z-source z-yaml"> <span class="z-punctuation z-definition z-block z-sequence z-item z-yaml">-</span> <span class="z-string z-unquoted z-plain z-out z-yaml"><span class="z-entity z-name z-tag z-yaml">age</span></span><span class="z-punctuation z-separator z-key-value z-mapping z-yaml">:</span>
</span><span class="z-source z-yaml"> <span class="z-punctuation z-definition z-block z-sequence z-item z-yaml">-</span> <span class="z-keyword z-control z-flow z-alias z-yaml"><span class="z-punctuation z-definition z-alias z-yaml">*</span></span><span class="z-variable z-other z-alias z-yaml">horus</span>
</span></code></pre>
<p>That’s it for decryption. However, we need to write secrets too. For that, we can get the corresponding private key:</p>
<pre data-lang="sh" class="language-sh z-code"><code class="language-sh" data-lang="sh"><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">nix-shell</span></span><span class="z-meta z-function-call z-arguments z-shell"><span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> -</span>p</span> ssh-to-age<span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> --</span>run</span> <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span>sudo ssh-to-age -private-key -i /etc/ssh/ssh_host_ed25519_key | install -D -m 400 /dev/stdin ~/.config/sops/age/keys.txt<span class="z-punctuation z-definition z-string z-end z-shell">"</span></span></span>
</span></code></pre>
<p>That <code>install</code> bit is here to create the directory if it doesn’t exist and set the right permissions.</p>
<blockquote>
<p>Isn’t this insecure? The key is not password-locked.</p>
</blockquote>
<p>Indeed, if someone has access to our user account, they can read that key and decrypt the secrets. However, we can probably assume our user already has access to the local secrets, so it doesn’t matter much. Our goal is to be able to put these secrets on a public GitHub, not to protect them locally.</p>
<h2 id="configuring-sops-nix">Configuring <code>sops-nix</code></h2>
<p>Our last step is to configure <code>sops</code>.
We’re going to get fancy here, as I’m <del>stealing</del> borrowing a module from Misterio’s config. In the future, this will often happen, as his config happens to be a great resource. Let’s have a look at <code>sops.nix</code>:</p>
<pre data-lang="nix" class="language-nix z-code"><code class="language-nix" data-lang="nix"><span class="z-source z-nix"><span class="z-punctuation z-definition z-entity z-function z-2 z-nix">{</span> <span class="z-variable z-parameter z-function z-1 z-nix">sops-nix</span><span class="z-keyword z-operator z-nix">,</span> <span class="z-variable z-parameter z-function z-1 z-nix">lib</span><span class="z-keyword z-operator z-nix">,</span> <span class="z-variable z-parameter z-function z-1 z-nix">config</span><span class="z-keyword z-operator z-nix">,</span> <span class="z-keyword z-operator z-nix">... </span><span class="z-punctuation z-definition z-entity z-function z-nix">}</span><span class="z-punctuation z-definition z-function z-nix">:</span>
</span><span class="z-source z-nix"><span class="z-keyword z-other z-nix">let</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">isEd25519</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-variable z-parameter z-function z-4 z-nix">k</span><span class="z-punctuation z-definition z-function z-nix">:</span> <span class="z-variable z-parameter z-name z-nix">k</span><span class="z-keyword z-operator z-nix">.</span><span class="z-variable z-parameter z-name z-nix">type</span> <span class="z-keyword z-operator z-nix">==</span> <span class="z-string z-quoted z-double z-nix"><span class="z-punctuation z-definition z-string z-double z-start z-nix">"</span>ed25519<span class="z-punctuation z-definition z-string z-double z-end z-nix">"</span></span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">getKeyPath</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-variable z-parameter z-function z-4 z-nix">k</span><span class="z-punctuation z-definition z-function z-nix">:</span> <span class="z-variable z-parameter z-name z-nix">k</span><span class="z-keyword z-operator z-nix">.</span><span class="z-variable z-parameter z-name z-nix">path</span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">keys</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-constant z-language z-nix">builtins</span><span class="z-keyword z-operator z-nix">.</span><span class="z-variable z-parameter z-name z-nix">filter</span> <span class="z-variable z-parameter z-name z-nix">isEd25519</span> <span class="z-variable z-parameter z-name z-nix">config</span><span class="z-keyword z-operator z-nix">.</span><span class="z-variable z-parameter z-name z-nix">services</span><span class="z-keyword z-operator z-nix">.</span><span class="z-variable z-parameter z-name z-nix">openssh</span><span class="z-keyword z-operator z-nix">.</span><span class="z-variable z-parameter z-name z-nix">hostKeys</span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"><span class="z-keyword z-other z-nix">in</span>
</span><span class="z-source z-nix"><span class="z-punctuation z-definition z-attrset-or-function z-nix">{</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">imports</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-punctuation z-definition z-list z-nix">[</span>
</span><span class="z-source z-nix"> <span class="z-variable z-parameter z-name z-nix">sops-nix</span><span class="z-keyword z-operator z-nix">.</span><span class="z-variable z-parameter z-name z-nix">nixosModules</span><span class="z-keyword z-operator z-nix">.</span><span class="z-variable z-parameter z-name z-nix">sops</span>
</span><span class="z-source z-nix"> <span class="z-punctuation z-definition z-list z-nix">]</span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix">
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">sops</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-punctuation z-definition z-attrset-or-function z-nix">{</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">age</span>.<span class="z-entity z-other z-attribute-name z-multipart z-nix">sshKeyPaths</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-support z-function z-nix">map</span> <span class="z-variable z-parameter z-name z-nix">getKeyPath</span> <span class="z-variable z-parameter z-name z-nix">keys</span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"> <span class="z-punctuation z-definition z-attrset z-nix">}</span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"><span class="z-punctuation z-definition z-attrset z-nix">}</span>
</span></code></pre>
<p>This looks complicated, but it is not. First, we are declaring some functions in the <code>let</code> block.</p>
<ul>
<li><code>isEd25519</code> simply tells if an SSH key uses <code>ed25519</code></li>
<li><code>getKeyPath</code> gets the path of an SSH key</li>
<li><code>keys</code> is the list of <code>ed25519</code> keys, taken from <code>openssh</code></li>
</ul>
<p>Then we import <code>sops</code>. Finally, we give it the keys we collected earlier. This avoids hardcoding keys, which is great!</p>
<p>We can now import this module in our config:</p>
<pre data-lang="nix" class="language-nix z-code"><code class="language-nix" data-lang="nix"><span class="z-source z-nix"> <span class="z-variable z-parameter z-name z-nix">imports</span> <span class="z-invalid z-illegal">=</span>
</span><span class="z-source z-nix"> <span class="z-punctuation z-definition z-list z-nix">[</span>
</span><span class="z-source z-nix"> <span class="z-variable z-parameter z-name z-nix">impermanence</span><span class="z-keyword z-operator z-nix">.</span><span class="z-variable z-parameter z-name z-nix">nixosModule</span>
</span><span class="z-source z-nix"> <span class="z-string z-unquoted z-path z-nix">./hardware-configuration.Nix</span>
</span><span class="z-source z-nix"> <span class="z-string z-unquoted z-path z-nix">../../modules/sops.nix</span>
</span><span class="z-source z-nix"> <span class="z-punctuation z-definition z-list z-nix">]</span><span class="z-invalid z-illegal">;</span>
</span></code></pre>
<p><code>sops-nix</code> is now ready to use. Do not forget to rebuild the config.</p>
<h2 id="our-first-secret">Our first secret</h2>
<p>Let’s write a secret:</p>
<pre data-lang="sh" class="language-sh z-code"><code class="language-sh" data-lang="sh"><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">mkdir</span></span><span class="z-meta z-function-call z-arguments z-shell"><span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> -</span>p</span> hosts/horus</span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">nix-shell</span></span><span class="z-meta z-function-call z-arguments z-shell"><span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> -</span>p</span> sops<span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> --</span>run</span> <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span>sops hosts/horus/secrets.yaml<span class="z-punctuation z-definition z-string z-end z-shell">"</span></span></span>
</span></code></pre>
<p>An editor should open. We can now write secrets, using yaml. Once we’re done, we can save the file. Example content:</p>
<pre data-lang="yaml" class="language-yaml z-code"><code class="language-yaml" data-lang="yaml"><span class="z-source z-yaml"><span class="z-string z-unquoted z-plain z-out z-yaml"><span class="z-entity z-name z-tag z-yaml">tailscale_key</span></span><span class="z-punctuation z-separator z-key-value z-mapping z-yaml">:</span> <span class="z-string z-unquoted z-plain z-out z-yaml">e2b6595884993e001da58d2995af65df489582a702e3a2f3</span>
</span></code></pre>
<p>We now have to tell <code>sops</code> this secret exists. So we declare it somewhere in our configuration:</p>
<pre class="z-code"><code><span class="z-text z-plain">sops.secrets.tailscale_key = {
</span><span class="z-text z-plain"> sopsFile = ./secrets.yaml;
</span><span class="z-text z-plain">};
</span></code></pre>
<p>And that’s all! To use it, we simply have to use <code>config.sops.secrets.tailscale_key.path</code> where we need it. Beware that this will not give you the secret, but a path to a file containing the raw secret, for security reasons. Otherwise, the secret would be in the Nix store, and thus accessible to any user on the system.</p>
<h2 id="note-adding-a-new-host">Note: adding a new host</h2>
<p>If you ever need to add a new host, you will need to update your secrets with <code>sops updatekeys your_secret</code>. This command has to be on a system with already authorized keys.</p>
<h1 id="tailscale">Tailscale</h1>
<p>We can finally get to a real feature, setting up Tailscale.
For those of you who haven’t heard of it, Tailscale is a private meshed network, allowing you to connect to your machines privately and securely through Wireguard, a VPN protocol, without exposing them to the world.
This means being able to close port 22, while still being able to SSH into your computer.</p>
<p>Furthermore, Tailscale offers some additional features, such as a fancy file sending tool or hole punching, which allows you to connect to your computer even if it is behind a NAT. I won’t go into details here, but you can read more about it on <a rel="noopener" target="_blank" href="https://tailscale.com/">their website</a>.</p>
<p>I’ve chosen to write a full-fledged NixOS module for Tailscale, as it is a service that needs to be configured and started. This is a good example of a module that can be reused in other configurations, so it’s worth writing it. Let’s get started!</p>
<h2 id="boilerplate-for-nixos-modules">Boilerplate for NixOS modules</h2>
<p>We’re going to write a module, so we need to create a directory for it:</p>
<pre data-lang="sh" class="language-sh z-code"><code class="language-sh" data-lang="sh"><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">mkdir</span></span><span class="z-meta z-function-call z-arguments z-shell"><span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> -</span>p</span> modules/nixos</span>
</span></code></pre>
<p>Inside, we’ll need a <code>default.nix</code> file:</p>
<pre data-lang="nix" class="language-nix z-code"><code class="language-nix" data-lang="nix"><span class="z-source z-nix"><span class="z-punctuation z-definition z-attrset-or-function z-nix">{</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">tailscale-autoconnect</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-support z-function z-nix">import</span> <span class="z-string z-unquoted z-path z-nix">./tailscale-autoconnect.nix</span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"><span class="z-punctuation z-definition z-attrset z-nix">}</span>
</span></code></pre>
<p>By convention, this file is automatically imported when you import a directory. Then, in our <code>flake.nix</code>, we can import our module:</p>
<pre data-lang="nix" class="language-nix z-code"><code class="language-nix" data-lang="nix"><span class="z-source z-nix"><span class="z-variable z-parameter z-name z-nix">outputs</span> <span class="z-invalid z-illegal">=</span> <span class="z-comment z-line z-number-sign z-nix"># ...</span>
</span><span class="z-source z-nix"><span class="z-punctuation z-definition z-attrset-or-function z-nix">{</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">nixosModules</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-support z-function z-nix">import</span> <span class="z-string z-unquoted z-path z-nix">./modules/nixos</span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"><span class="z-punctuation z-definition z-attrset z-nix">}</span>
</span></code></pre>
<p>As before, the <code>nixosModules</code> attribute has a special meaning.</p>
<p>Finally, we have to import the module we’re writing in our configuration:</p>
<pre data-lang="nix" class="language-nix z-code"><code class="language-nix" data-lang="nix"><span class="z-source z-nix"> <span class="z-variable z-parameter z-name z-nix">imports</span> <span class="z-invalid z-illegal">=</span>
</span><span class="z-source z-nix"> <span class="z-punctuation z-definition z-list z-nix">[</span>
</span><span class="z-source z-nix"> <span class="z-comment z-line z-number-sign z-nix"># ...</span>
</span><span class="z-source z-nix"> <span class="z-variable z-parameter z-name z-nix">outputs</span><span class="z-keyword z-operator z-nix">.</span><span class="z-variable z-parameter z-name z-nix">nixosModules</span><span class="z-keyword z-operator z-nix">.</span><span class="z-variable z-parameter z-name z-nix">tailscale-autoconnect</span>
</span><span class="z-source z-nix"> <span class="z-punctuation z-definition z-list z-nix">]</span><span class="z-invalid z-illegal">;</span>
</span></code></pre>
<h2 id="writing-the-module">Writing the module</h2>
<p>We’re going to write a module to start Tailscale and connect to it automatically. This is a good example of a module that can be reused in other configurations, so it’s worth writing it. Let’s get started!</p>
<p>First, we need to create a <code>tailscale-autoconnect.nix</code> file in our <code>modules/nixos</code> directory. We’ll start with the boilerplate:</p>
<pre data-lang="nix" class="language-nix z-code"><code class="language-nix" data-lang="nix"><span class="z-source z-nix"><span class="z-punctuation z-definition z-entity z-function z-2 z-nix">{</span> <span class="z-variable z-parameter z-function z-1 z-nix">config</span><span class="z-keyword z-operator z-nix">,</span> <span class="z-variable z-parameter z-function z-1 z-nix">lib</span><span class="z-keyword z-operator z-nix">,</span> <span class="z-variable z-parameter z-function z-1 z-nix">pkgs</span><span class="z-keyword z-operator z-nix">,</span> <span class="z-keyword z-operator z-nix">... </span><span class="z-punctuation z-definition z-entity z-function z-nix">}</span><span class="z-punctuation z-definition z-function z-nix">:</span>
</span><span class="z-source z-nix"><span class="z-punctuation z-definition z-attrset-or-function z-nix">{</span>
</span><span class="z-source z-nix"> <span class="z-invalid z-illegal z-reserved z-nix">with</span> <span class="z-variable z-parameter z-function z-maybe z-nix">lib</span><span class="z-invalid z-illegal">;</span> <span class="z-keyword z-other z-nix">let</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">cfg</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-variable z-parameter z-name z-nix">config</span><span class="z-keyword z-operator z-nix">.</span><span class="z-variable z-parameter z-name z-nix">services</span><span class="z-keyword z-operator z-nix">.</span><span class="z-variable z-parameter z-name z-nix">tailscaleAutoconnect</span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"> <span class="z-keyword z-other z-nix">in</span> <span class="z-punctuation z-definition z-attrset-or-function z-nix">{</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">options</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-punctuation z-definition z-attrset-or-function z-nix">{</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">services</span>.<span class="z-entity z-other z-attribute-name z-multipart z-nix">tailscaleAutoconnect</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-punctuation z-definition z-attrset-or-function z-nix">{</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">enable</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-variable z-parameter z-name z-nix">mkEnableOption</span> <span class="z-string z-quoted z-double z-nix"><span class="z-punctuation z-definition z-string z-double z-start z-nix">"</span>tailscaleAutoconnect<span class="z-punctuation z-definition z-string z-double z-end z-nix">"</span></span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"> <span class="z-punctuation z-definition z-attrset z-nix">}</span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"> <span class="z-punctuation z-definition z-attrset z-nix">}</span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix">
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">config</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-variable z-parameter z-name z-nix">mkIf</span> <span class="z-variable z-parameter z-name z-nix">cfg</span><span class="z-keyword z-operator z-nix">.</span><span class="z-variable z-parameter z-name z-nix">services</span><span class="z-keyword z-operator z-nix">.</span><span class="z-variable z-parameter z-name z-nix">tailscaleAutoconnect</span><span class="z-keyword z-operator z-nix">.</span><span class="z-variable z-parameter z-name z-nix">enable</span> <span class="z-punctuation z-definition z-attrset-or-function z-nix">{</span>
</span><span class="z-source z-nix"> <span class="z-comment z-line z-number-sign z-nix"># ...</span>
</span><span class="z-source z-nix"> <span class="z-punctuation z-definition z-attrset z-nix">}</span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"> <span class="z-punctuation z-definition z-attrset z-nix">}</span><span class="z-invalid z-illegal">;</span>
</span><span class="z-source z-nix"><span class="z-invalid z-illegal">}</span>
</span></code></pre>
<p>This is the basic structure of a module. We declare an option, and then we use it to conditionally change the configuration. So:</p>
<ul>
<li>What we write in <code>options</code> is the option declaration</li>
<li>What we write in <code>config</code> is the consequence of the option being enabled, the configuration change</li>
</ul>
<p>Let’s declare all the options first.</p>
<pre data-lang="nix" class="language-nix z-code"><code class="language-nix" data-lang="nix"><span class="z-source z-nix"> <span class="z-variable z-parameter z-name z-nix">options</span><span class="z-keyword z-operator z-nix">.</span><span class="z-variable z-parameter z-name z-nix">services</span><span class="z-keyword z-operator z-nix">.</span><span class="z-variable z-parameter z-name z-nix">tailscaleAutoconnect</span> <span class="z-invalid z-illegal">=</span> <span class="z-punctuation z-definition z-attrset-or-function z-nix">{</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">enable</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-variable z-parameter z-name z-nix">mkEnableOption</span> <span class="z-string z-quoted z-double z-nix"><span class="z-punctuation z-definition z-string z-double z-start z-nix">"</span>tailscaleAutoconnect<span class="z-punctuation z-definition z-string z-double z-end z-nix">"</span></span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">authkeyFile</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-variable z-parameter z-name z-nix">mkOption</span> <span class="z-punctuation z-definition z-attrset-or-function z-nix">{</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">type</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-variable z-parameter z-name z-nix">types</span><span class="z-keyword z-operator z-nix">.</span><span class="z-variable z-parameter z-name z-nix">str</span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">description</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-string z-quoted z-double z-nix"><span class="z-punctuation z-definition z-string z-double z-start z-nix">"</span>The authkey to use for authentication with Tailscale<span class="z-punctuation z-definition z-string z-double z-end z-nix">"</span></span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"> <span class="z-punctuation z-definition z-attrset z-nix">}</span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix">
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">loginServer</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-variable z-parameter z-name z-nix">mkOption</span> <span class="z-punctuation z-definition z-attrset-or-function z-nix">{</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">type</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-variable z-parameter z-name z-nix">types</span><span class="z-keyword z-operator z-nix">.</span><span class="z-variable z-parameter z-name z-nix">str</span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">default</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-string z-quoted z-double z-nix"><span class="z-punctuation z-definition z-string z-double z-start z-nix">"</span><span class="z-punctuation z-definition z-string z-double z-end z-nix">"</span></span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">description</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-string z-quoted z-double z-nix"><span class="z-punctuation z-definition z-string z-double z-start z-nix">"</span>The login server to use for authentication with Tailscale<span class="z-punctuation z-definition z-string z-double z-end z-nix">"</span></span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"> <span class="z-punctuation z-definition z-attrset z-nix">}</span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix">
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">advertiseExitNode</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-variable z-parameter z-name z-nix">mkOption</span> <span class="z-punctuation z-definition z-attrset-or-function z-nix">{</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">type</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-variable z-parameter z-name z-nix">types</span><span class="z-keyword z-operator z-nix">.</span><span class="z-variable z-parameter z-name z-nix">bool</span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">default</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-constant z-language z-nix">false</span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">description</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-string z-quoted z-double z-nix"><span class="z-punctuation z-definition z-string z-double z-start z-nix">"</span>Whether to advertise this node as an exit node<span class="z-punctuation z-definition z-string z-double z-end z-nix">"</span></span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"> <span class="z-punctuation z-definition z-attrset z-nix">}</span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix">
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">exitNode</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-variable z-parameter z-name z-nix">mkOption</span> <span class="z-punctuation z-definition z-attrset-or-function z-nix">{</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">type</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-variable z-parameter z-name z-nix">types</span><span class="z-keyword z-operator z-nix">.</span><span class="z-variable z-parameter z-name z-nix">str</span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">default</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-string z-quoted z-double z-nix"><span class="z-punctuation z-definition z-string z-double z-start z-nix">"</span><span class="z-punctuation z-definition z-string z-double z-end z-nix">"</span></span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">description</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-string z-quoted z-double z-nix"><span class="z-punctuation z-definition z-string z-double z-start z-nix">"</span>The exit node to use for this node<span class="z-punctuation z-definition z-string z-double z-end z-nix">"</span></span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"> <span class="z-punctuation z-definition z-attrset z-nix">}</span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix">
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">exitNodeAllowLanAccess</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-variable z-parameter z-name z-nix">mkOption</span> <span class="z-punctuation z-definition z-attrset-or-function z-nix">{</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">type</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-variable z-parameter z-name z-nix">types</span><span class="z-keyword z-operator z-nix">.</span><span class="z-variable z-parameter z-name z-nix">bool</span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">default</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-constant z-language z-nix">false</span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">description</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-string z-quoted z-double z-nix"><span class="z-punctuation z-definition z-string z-double z-start z-nix">"</span>Whether to allow LAN access to this node<span class="z-punctuation z-definition z-string z-double z-end z-nix">"</span></span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"> <span class="z-punctuation z-definition z-attrset z-nix">}</span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"> <span class="z-punctuation z-definition z-attrset z-nix">}</span><span class="z-invalid z-illegal">;</span>
</span></code></pre>
<p>This looks like a lot of code, but we’re simply declaring options. We need to give them a type, and we can also give a default value and a description.
Now, the actually useful code:</p>
<pre data-lang="nix" class="language-nix z-code"><code class="language-nix" data-lang="nix"><span class="z-source z-nix"> <span class="z-variable z-parameter z-name z-nix">config</span> <span class="z-invalid z-illegal">=</span> <span class="z-variable z-parameter z-name z-nix">mkIf</span> <span class="z-variable z-parameter z-name z-nix">cfg</span><span class="z-keyword z-operator z-nix">.</span><span class="z-variable z-parameter z-name z-nix">enable</span> <span class="z-punctuation z-definition z-attrset-or-function z-nix">{</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">assertions</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-punctuation z-definition z-list z-nix">[</span>
</span><span class="z-source z-nix"> <span class="z-punctuation z-definition z-attrset-or-function z-nix">{</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">assertion</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-variable z-parameter z-name z-nix">cfg</span><span class="z-keyword z-operator z-nix">.</span><span class="z-variable z-parameter z-name z-nix">authkeyFile</span> <span class="z-keyword z-operator z-nix">!=</span> <span class="z-string z-quoted z-double z-nix"><span class="z-punctuation z-definition z-string z-double z-start z-nix">"</span><span class="z-punctuation z-definition z-string z-double z-end z-nix">"</span></span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">message</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-string z-quoted z-double z-nix"><span class="z-punctuation z-definition z-string z-double z-start z-nix">"</span>authkeyFile must be set<span class="z-punctuation z-definition z-string z-double z-end z-nix">"</span></span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"> <span class="z-punctuation z-definition z-attrset z-nix">}</span>
</span><span class="z-source z-nix"> <span class="z-punctuation z-definition z-attrset-or-function z-nix">{</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">assertion</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-variable z-parameter z-name z-nix">cfg</span><span class="z-keyword z-operator z-nix">.</span><span class="z-variable z-parameter z-name z-nix">exitNodeAllowLanAccess</span> <span class="z-keyword z-operator z-nix">-></span> <span class="z-variable z-parameter z-name z-nix">cfg</span><span class="z-keyword z-operator z-nix">.</span><span class="z-variable z-parameter z-name z-nix">exitNode</span> <span class="z-keyword z-operator z-nix">!=</span> <span class="z-string z-quoted z-double z-nix"><span class="z-punctuation z-definition z-string z-double z-start z-nix">"</span><span class="z-punctuation z-definition z-string z-double z-end z-nix">"</span></span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">message</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-string z-quoted z-double z-nix"><span class="z-punctuation z-definition z-string z-double z-start z-nix">"</span>exitNodeAllowLanAccess must be false if exitNode is not set<span class="z-punctuation z-definition z-string z-double z-end z-nix">"</span></span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"> <span class="z-punctuation z-definition z-attrset z-nix">}</span>
</span><span class="z-source z-nix"> <span class="z-punctuation z-definition z-attrset-or-function z-nix">{</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">assertion</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-variable z-parameter z-name z-nix">cfg</span><span class="z-keyword z-operator z-nix">.</span><span class="z-variable z-parameter z-name z-nix">advertiseExitNode</span> <span class="z-keyword z-operator z-nix">-></span> <span class="z-variable z-parameter z-name z-nix">cfg</span><span class="z-keyword z-operator z-nix">.</span><span class="z-variable z-parameter z-name z-nix">exitNode</span> <span class="z-keyword z-operator z-nix">==</span> <span class="z-string z-quoted z-double z-nix"><span class="z-punctuation z-definition z-string z-double z-start z-nix">"</span><span class="z-punctuation z-definition z-string z-double z-end z-nix">"</span></span><span class="z-punctuation z-terminator z-bind z-nix">;</span><span class="z-entity z-other z-attribute-name z-multipart z-nix">d</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">message</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-string z-quoted z-double z-nix"><span class="z-punctuation z-definition z-string z-double z-start z-nix">"</span>advertiseExitNode must be false if exitNode is set<span class="z-punctuation z-definition z-string z-double z-end z-nix">"</span></span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"> <span class="z-punctuation z-definition z-attrset z-nix">}</span>
</span><span class="z-source z-nix"> <span class="z-punctuation z-definition z-list z-nix">]</span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix">
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">systemd</span>.<span class="z-entity z-other z-attribute-name z-multipart z-nix">services</span>.<span class="z-entity z-other z-attribute-name z-multipart z-nix">tailscale-autoconnect</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-punctuation z-definition z-attrset-or-function z-nix">{</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">description</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-string z-quoted z-double z-nix"><span class="z-punctuation z-definition z-string z-double z-start z-nix">"</span>Automatic connection to Tailscale<span class="z-punctuation z-definition z-string z-double z-end z-nix">"</span></span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix">
</span><span class="z-source z-nix"> <span class="z-comment z-line z-number-sign z-nix"># make sure tailscale is running before trying to connect to tailscale</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">after</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-punctuation z-definition z-list z-nix">[</span><span class="z-string z-quoted z-double z-nix"><span class="z-punctuation z-definition z-string z-double z-start z-nix">"</span>network-pre.target<span class="z-punctuation z-definition z-string z-double z-end z-nix">"</span></span> <span class="z-string z-quoted z-double z-nix"><span class="z-punctuation z-definition z-string z-double z-start z-nix">"</span>tailscale.service<span class="z-punctuation z-definition z-string z-double z-end z-nix">"</span></span><span class="z-punctuation z-definition z-list z-nix">]</span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">wants</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-punctuation z-definition z-list z-nix">[</span><span class="z-string z-quoted z-double z-nix"><span class="z-punctuation z-definition z-string z-double z-start z-nix">"</span>network-pre.target<span class="z-punctuation z-definition z-string z-double z-end z-nix">"</span></span> <span class="z-string z-quoted z-double z-nix"><span class="z-punctuation z-definition z-string z-double z-start z-nix">"</span>tailscale.service<span class="z-punctuation z-definition z-string z-double z-end z-nix">"</span></span><span class="z-punctuation z-definition z-list z-nix">]</span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">wantedBy</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-punctuation z-definition z-list z-nix">[</span><span class="z-string z-quoted z-double z-nix"><span class="z-punctuation z-definition z-string z-double z-start z-nix">"</span>multi-user.target<span class="z-punctuation z-definition z-string z-double z-end z-nix">"</span></span><span class="z-punctuation z-definition z-list z-nix">]</span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix">
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">serviceConfig</span>.<span class="z-entity z-other z-attribute-name z-multipart z-nix">Type</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-string z-quoted z-double z-nix"><span class="z-punctuation z-definition z-string z-double z-start z-nix">"</span>oneshot<span class="z-punctuation z-definition z-string z-double z-end z-nix">"</span></span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix">
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">script</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-keyword z-other z-nix">with</span> <span class="z-variable z-parameter z-name z-nix">pkgs</span>; <span class="z-string z-quoted z-other z-nix"><span class="z-punctuation z-definition z-string z-other z-start z-nix">''</span>
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix"> # wait for tailscaled to settle
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix"> sleep 2
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix">
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix"> # check if we are already authenticated to tailscale
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix"> status="$(<span class="z-markup z-italic"><span class="z-punctuation z-section z-embedded z-begin z-nix">${</span><span class="z-variable z-parameter z-name z-nix">tailscale</span><span class="z-punctuation z-section z-embedded z-end z-nix">}</span></span>/bin/tailscale status -json | <span class="z-markup z-italic"><span class="z-punctuation z-section z-embedded z-begin z-nix">${</span><span class="z-variable z-parameter z-name z-nix">jq</span><span class="z-punctuation z-section z-embedded z-end z-nix">}</span></span>/bin/jq -r .BackendState)"
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix"> # if status is not null, then we are already authenticated
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix"> echo "tailscale status: $status"
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix"> if [ "$status" != "NeedsLogin" ]; then
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix"> exit 0
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix"> fi
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix">
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix"> # otherwise authenticate with tailscale
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix"> # timeout after 10 seconds to avoid hanging the boot process
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix"> <span class="z-markup z-italic"><span class="z-punctuation z-section z-embedded z-begin z-nix">${</span><span class="z-variable z-parameter z-name z-nix">coreutils</span><span class="z-punctuation z-section z-embedded z-end z-nix">}</span></span>/bin/timeout 10 <span class="z-markup z-italic"><span class="z-punctuation z-section z-embedded z-begin z-nix">${</span><span class="z-variable z-parameter z-name z-nix">tailscale</span><span class="z-punctuation z-section z-embedded z-end z-nix">}</span></span>/bin/tailscale up \
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix"> <span class="z-markup z-italic"><span class="z-punctuation z-section z-embedded z-begin z-nix">${</span><span class="z-variable z-parameter z-name z-nix">lib</span><span class="z-keyword z-operator z-nix">.</span><span class="z-variable z-parameter z-name z-nix">optionalString</span> <span class="z-punctuation z-definition z-expression z-nix">(</span><span class="z-variable z-parameter z-name z-nix">cfg</span><span class="z-keyword z-operator z-nix">.</span><span class="z-variable z-parameter z-name z-nix">loginServer</span> <span class="z-keyword z-operator z-nix">!=</span> <span class="z-string z-quoted z-double z-nix"><span class="z-punctuation z-definition z-string z-double z-start z-nix">"</span><span class="z-punctuation z-definition z-string z-double z-end z-nix">"</span></span><span class="z-punctuation z-definition z-expression z-nix">)</span> <span class="z-string z-quoted z-double z-nix"><span class="z-punctuation z-definition z-string z-double z-start z-nix">"</span>--login-server=<span class="z-markup z-italic"><span class="z-punctuation z-section z-embedded z-begin z-nix">${</span><span class="z-variable z-parameter z-name z-nix">cfg</span><span class="z-keyword z-operator z-nix">.</span><span class="z-variable z-parameter z-name z-nix">loginServer</span><span class="z-punctuation z-section z-embedded z-end z-nix">}</span></span><span class="z-punctuation z-definition z-string z-double z-end z-nix">"</span></span><span class="z-punctuation z-section z-embedded z-end z-nix">}</span></span> \
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix"> --authkey=$(cat "<span class="z-markup z-italic"><span class="z-punctuation z-section z-embedded z-begin z-nix">${</span><span class="z-variable z-parameter z-name z-nix">cfg</span><span class="z-keyword z-operator z-nix">.</span><span class="z-variable z-parameter z-name z-nix">authkeyFile</span><span class="z-punctuation z-section z-embedded z-end z-nix">}</span></span>")
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix">
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix"> # we have to proceed in two steps because some options are only available
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix"> # after authentication
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix"> <span class="z-markup z-italic"><span class="z-punctuation z-section z-embedded z-begin z-nix">${</span><span class="z-variable z-parameter z-name z-nix">coreutils</span><span class="z-punctuation z-section z-embedded z-end z-nix">}</span></span>/bin/timeout 10 <span class="z-markup z-italic"><span class="z-punctuation z-section z-embedded z-begin z-nix">${</span><span class="z-variable z-parameter z-name z-nix">tailscale</span><span class="z-punctuation z-section z-embedded z-end z-nix">}</span></span>/bin/tailscale up \
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix"> <span class="z-markup z-italic"><span class="z-punctuation z-section z-embedded z-begin z-nix">${</span><span class="z-variable z-parameter z-name z-nix">lib</span><span class="z-keyword z-operator z-nix">.</span><span class="z-variable z-parameter z-name z-nix">optionalString</span> <span class="z-punctuation z-definition z-expression z-nix">(</span><span class="z-variable z-parameter z-name z-nix">cfg</span><span class="z-keyword z-operator z-nix">.</span><span class="z-variable z-parameter z-name z-nix">loginServer</span> <span class="z-keyword z-operator z-nix">!=</span> <span class="z-string z-quoted z-double z-nix"><span class="z-punctuation z-definition z-string z-double z-start z-nix">"</span><span class="z-punctuation z-definition z-string z-double z-end z-nix">"</span></span><span class="z-punctuation z-definition z-expression z-nix">)</span> <span class="z-string z-quoted z-double z-nix"><span class="z-punctuation z-definition z-string z-double z-start z-nix">"</span>--login-server=<span class="z-markup z-italic"><span class="z-punctuation z-section z-embedded z-begin z-nix">${</span><span class="z-variable z-parameter z-name z-nix">cfg</span><span class="z-keyword z-operator z-nix">.</span><span class="z-variable z-parameter z-name z-nix">loginServer</span><span class="z-punctuation z-section z-embedded z-end z-nix">}</span></span><span class="z-punctuation z-definition z-string z-double z-end z-nix">"</span></span><span class="z-punctuation z-section z-embedded z-end z-nix">}</span></span> \
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix"> <span class="z-markup z-italic"><span class="z-punctuation z-section z-embedded z-begin z-nix">${</span><span class="z-variable z-parameter z-name z-nix">lib</span><span class="z-keyword z-operator z-nix">.</span><span class="z-variable z-parameter z-name z-nix">optionalString</span> <span class="z-punctuation z-definition z-expression z-nix">(</span><span class="z-variable z-parameter z-name z-nix">cfg</span><span class="z-keyword z-operator z-nix">.</span><span class="z-variable z-parameter z-name z-nix">advertiseExitNode</span><span class="z-punctuation z-definition z-expression z-nix">)</span> <span class="z-string z-quoted z-double z-nix"><span class="z-punctuation z-definition z-string z-double z-start z-nix">"</span>--advertise-exit-node<span class="z-punctuation z-definition z-string z-double z-end z-nix">"</span></span><span class="z-punctuation z-section z-embedded z-end z-nix">}</span></span> \
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix"> <span class="z-markup z-italic"><span class="z-punctuation z-section z-embedded z-begin z-nix">${</span><span class="z-variable z-parameter z-name z-nix">lib</span><span class="z-keyword z-operator z-nix">.</span><span class="z-variable z-parameter z-name z-nix">optionalString</span> <span class="z-punctuation z-definition z-expression z-nix">(</span><span class="z-variable z-parameter z-name z-nix">cfg</span><span class="z-keyword z-operator z-nix">.</span><span class="z-variable z-parameter z-name z-nix">exitNode</span> <span class="z-keyword z-operator z-nix">!=</span> <span class="z-string z-quoted z-double z-nix"><span class="z-punctuation z-definition z-string z-double z-start z-nix">"</span><span class="z-punctuation z-definition z-string z-double z-end z-nix">"</span></span><span class="z-punctuation z-definition z-expression z-nix">)</span> <span class="z-string z-quoted z-double z-nix"><span class="z-punctuation z-definition z-string z-double z-start z-nix">"</span>--exit-node=<span class="z-markup z-italic"><span class="z-punctuation z-section z-embedded z-begin z-nix">${</span><span class="z-variable z-parameter z-name z-nix">cfg</span><span class="z-keyword z-operator z-nix">.</span><span class="z-variable z-parameter z-name z-nix">exitNode</span><span class="z-punctuation z-section z-embedded z-end z-nix">}</span></span><span class="z-punctuation z-definition z-string z-double z-end z-nix">"</span></span><span class="z-punctuation z-section z-embedded z-end z-nix">}</span></span> \
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix"> <span class="z-markup z-italic"><span class="z-punctuation z-section z-embedded z-begin z-nix">${</span><span class="z-variable z-parameter z-name z-nix">lib</span><span class="z-keyword z-operator z-nix">.</span><span class="z-variable z-parameter z-name z-nix">optionalString</span> <span class="z-punctuation z-definition z-expression z-nix">(</span><span class="z-variable z-parameter z-name z-nix">cfg</span><span class="z-keyword z-operator z-nix">.</span><span class="z-variable z-parameter z-name z-nix">exitNodeAllowLanAccess</span><span class="z-punctuation z-definition z-expression z-nix">)</span> <span class="z-string z-quoted z-double z-nix"><span class="z-punctuation z-definition z-string z-double z-start z-nix">"</span>--exit-node-allow-lan-access<span class="z-punctuation z-definition z-string z-double z-end z-nix">"</span></span><span class="z-punctuation z-section z-embedded z-end z-nix">}</span></span>
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix"> <span class="z-punctuation z-definition z-string z-other z-end z-nix">''</span></span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"> <span class="z-punctuation z-definition z-attrset z-nix">}</span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix">
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">networking</span>.<span class="z-entity z-other z-attribute-name z-multipart z-nix">firewall</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-punctuation z-definition z-attrset-or-function z-nix">{</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">trustedInterfaces</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-punctuation z-definition z-list z-nix">[</span> <span class="z-string z-quoted z-double z-nix"><span class="z-punctuation z-definition z-string z-double z-start z-nix">"</span>tailscale0<span class="z-punctuation z-definition z-string z-double z-end z-nix">"</span></span> <span class="z-punctuation z-definition z-list z-nix">]</span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">allowedUDPPorts</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-punctuation z-definition z-list z-nix">[</span> <span class="z-variable z-parameter z-name z-nix">config</span><span class="z-keyword z-operator z-nix">.</span><span class="z-variable z-parameter z-name z-nix">services</span><span class="z-keyword z-operator z-nix">.</span><span class="z-variable z-parameter z-name z-nix">tailscale</span><span class="z-keyword z-operator z-nix">.</span><span class="z-variable z-parameter z-name z-nix">port</span> <span class="z-punctuation z-definition z-list z-nix">]</span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"> <span class="z-punctuation z-definition z-attrset z-nix">}</span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix">
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">services</span>.<span class="z-entity z-other z-attribute-name z-multipart z-nix">tailscale</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-punctuation z-definition z-attrset-or-function z-nix">{</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">enable</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-constant z-language z-nix">true</span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">useRoutingFeatures</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-keyword z-other z-nix">if</span> <span class="z-variable z-parameter z-name z-nix">cfg</span><span class="z-keyword z-operator z-nix">.</span><span class="z-variable z-parameter z-name z-nix">advertiseExitNode</span> <span class="z-keyword z-other z-nix">th</span><span class="z-keyword z-other z-nix">en</span> <span class="z-string z-quoted z-double z-nix"><span class="z-punctuation z-definition z-string z-double z-start z-nix">"</span>server<span class="z-punctuation z-definition z-string z-double z-end z-nix">"</span></span> <span class="z-keyword z-other z-nix">el</span><span class="z-keyword z-other z-nix">se</span> <span class="z-string z-quoted z-double z-nix"><span class="z-punctuation z-definition z-string z-double z-start z-nix">"</span>client<span class="z-punctuation z-definition z-string z-double z-end z-nix">"</span></span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"> <span class="z-punctuation z-definition z-attrset z-nix">}</span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"> <span class="z-punctuation z-definition z-attrset z-nix">}</span><span class="z-invalid z-illegal">;</span>
</span></code></pre>
<p>First, the assertions. They’re here to make sure that the user doesn’t make any mistake when configuring the module. For example, a user cannot both advertise an exit node and set an exit node.
Then, the service. We’re using systemd to run a script that will connect to Tailscale. The <code>after</code>, <code>wants</code> and <code>wantedBy</code> options make the script run after the network is up and after Tailscale daemon is started. The <code>Type</code> option is here to make sure that the script is run only once. The script itself is a bit long, but it’s just a bunch of bash commands. It’s pretty straightforward. First, we wait for the Tailscale daemon to settle. Then, we check if we’re already authenticated. If we are, we exit. Otherwise, we authenticate. Finally, we connect to Tailscale. We have to do it in two steps because some options are only available after authentication.</p>
<p>At the end, we configure the firewall to allow Tailscale traffic, and we enable the Tailscale service.</p>
<p>Now, an example of how to use this module:</p>
<pre data-lang="nix" class="language-nix z-code"><code class="language-nix" data-lang="nix"><span class="z-source z-nix"><span class="z-punctuation z-definition z-entity z-function z-2 z-nix">{</span> <span class="z-variable z-parameter z-function z-1 z-nix">outputs</span><span class="z-keyword z-operator z-nix">,</span> <span class="z-keyword z-operator z-nix">...</span><span class="z-punctuation z-definition z-entity z-function z-nix">}</span><span class="z-punctuation z-definition z-function z-nix">:</span>
</span><span class="z-source z-nix"><span class="z-punctuation z-definition z-attrset-or-function z-nix">{</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">imports</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-punctuation z-definition z-list z-nix">[</span>
</span><span class="z-source z-nix"> <span class="z-variable z-parameter z-name z-nix">outputs</span><span class="z-keyword z-operator z-nix">.</span><span class="z-variable z-parameter z-name z-nix">nixosModules</span><span class="z-keyword z-operator z-nix">.</span><span class="z-variable z-parameter z-name z-nix">tailscale-autoconnect</span>
</span><span class="z-source z-nix"> <span class="z-punctuation z-definition z-list z-nix">]</span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix">
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">services</span>.<span class="z-entity z-other z-attribute-name z-multipart z-nix">tailscaleAutoconnect</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-punctuation z-definition z-attrset-or-function z-nix">{</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">enable</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-constant z-language z-nix">true</span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">authkeyFile</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-variable z-parameter z-name z-nix">config</span><span class="z-keyword z-operator z-nix">.</span><span class="z-variable z-parameter z-name z-nix">sops</span><span class="z-keyword z-operator z-nix">.</span><span class="z-variable z-parameter z-name z-nix">secrets</span><span class="z-keyword z-operator z-nix">.</span><span class="z-variable z-parameter z-name z-nix">tailscale_key</span><span class="z-keyword z-operator z-nix">.</span><span class="z-variable z-parameter z-name z-nix">path</span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">loginServer</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-string z-quoted z-double z-nix"><span class="z-punctuation z-definition z-string z-double z-start z-nix">"</span>https://login.tailscale.com<span class="z-punctuation z-definition z-string z-double z-end z-nix">"</span></span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">exitNode</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-string z-quoted z-double z-nix"><span class="z-punctuation z-definition z-string z-double z-start z-nix">"</span>some-node-id<span class="z-punctuation z-definition z-string z-double z-end z-nix">"</span></span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">exitNodeAllowLanAccess</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-constant z-language z-nix">true</span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"> <span class="z-punctuation z-definition z-attrset z-nix">}</span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix">
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">sops</span>.<span class="z-entity z-other z-attribute-name z-multipart z-nix">secrets</span>.<span class="z-entity z-other z-attribute-name z-multipart z-nix">tailscale_key</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-punctuation z-definition z-attrset-or-function z-nix">{</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">sopsFile</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-string z-unquoted z-path z-nix">./secrets.yaml</span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"> <span class="z-punctuation z-definition z-attrset z-nix">}</span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix">
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">environment</span>.<span class="z-entity z-other z-attribute-name z-multipart z-nix">persistence</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-punctuation z-definition z-attrset-or-function z-nix">{</span>
</span><span class="z-source z-nix"> <span class="z-string z-quoted z-double z-nix"><span class="z-punctuation z-definition z-string z-double z-start z-nix">"</span>/persist<span class="z-punctuation z-definition z-string z-double z-end z-nix">"</span></span>.<span class="z-entity z-other z-attribute-name z-multipart z-nix">directories</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-punctuation z-definition z-list z-nix">[</span><span class="z-string z-quoted z-double z-nix"><span class="z-punctuation z-definition z-string z-double z-start z-nix">"</span>/var/lib/tailscale<span class="z-punctuation z-definition z-string z-double z-end z-nix">"</span></span><span class="z-punctuation z-definition z-list z-nix">]</span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"> <span class="z-punctuation z-definition z-attrset z-nix">}</span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"><span class="z-punctuation z-definition z-attrset z-nix">}</span>
</span></code></pre>
<p>The module is imported and configured. We also use the <code>sops</code> secret we created earlier. Finally, we persist the Tailscale state, so that we don’t have to authenticate again after a reboot. This is especially important if the authkey can expire.</p>
<hr />
<p>That’s all for this post. Thanks for reading! If you have any question, feel free to ask in the comments. The final code can be found <a rel="noopener" target="_blank" href="https://github.com/Guekka/nixos-server/tree/2-tailscale">here</a>.</p>
<!--
# Our first service : a simple file server
Our first service will be a basic web server. For that, we will use nginx. `nginx` is, obviously, a web server. However, it is often also used as a reverse proxy, forwarding the requests it gets to the internal services. `nginx.Nix`:
```nix
{
services.nginx = {
enable = true;
recommendedTlsSettings = true;
recommendedProxySettings = true;
recommendedGzipSettings = true;
recommendedOptimisation = true;
clientMaxBodySize = "300m";
};
networking.firewall.allowedTCPPorts = [ 80 443 ];
}
```
`web-server.Nix`:
```nix
{
services.nginx.virtualHosts."e1.oze.li" = {
addSSL = true;
enableACME = true;
root = "/var/www/files";
};
environment.persistence = {
"/persist".directories = [ "/var/www/files" ];
};
}
```
We are enabling the `nginx` service. For now, it only serves `e1.oze.li`, our web server. You can notice the `addSSL` parameter, together with `enableACME`: thanks to them, we are getting a HTTPS certificate for free, provided by LetsEncrypt. We need to accept its terms. `acme.Nix`:
```nix
# Enable acme for usage with nginx vhosts
security.acme = {
defaults.email = "some@email.com";
acceptTerms = true;
};
environment.persistence = {
"/persist".directories = [ "/var/lib/acme" ];
};
}
```
That's all! That is so simple and straightforward. I couldn't imagine myself using only Docker now.
-->
NixOS as a server, part 1: Impermanence
2023-02-20T00:00:00+00:00
2023-02-20T00:00:00+00:00
Unknown
https://guekka.github.io/nixos-server-1/
<p>A few months ago, I woke up with the idea of hosting my own services. I went through a lot of tries. LXC, Debian, Alpine, (rootless or not) Docker, podman, portainer…</p>
<p>But no solution felt perfect. Until I decided to have a try at hosting using NixOS.</p>
<p>I’m going to assume you know about NixOS and have some prior experience. However, for a small summary: NixOS is a Linux distribution revolving around the Nix package manager. Its main advantage is having a reproducible environment through a declarative configuration. This means that you can copy an entire computer configuration easily: if it works somewhere, it will work anywhere.</p>
<p>My main focus point is reproducibility, so that’s why we’ll start with configuring <em>impermanence</em>.</p>
<h2 id="what-s-impermanence">What’s impermanence?</h2>
<p>Originally, a philosophic concept. But in our case, impermanence means erasing the <code>/</code> drive at each reboot. You read that right, erasing <em>almost</em> everything at each reboot. This part stands on the shoulders of those who did it before me:</p>
<ul>
<li><a rel="noopener" target="_blank" href="https://grahamc.com/blog/erase-your-darlings">Erase your darlings: immutable infrastructure for mutable systems - Graham Christensen</a></li>
<li><a rel="noopener" target="_blank" href="https://elis.nu/blog/2020/05/nixos-tmpfs-as-root/">NixOS ❄: tmpfs as root</a></li>
<li><a rel="noopener" target="_blank" href="https://mt-caret.github.io/blog/posts/2020-06-29-optin-state.html#fn6">Encypted Btrfs Root with Opt-in State on NixOS</a></li>
<li><a rel="noopener" target="_blank" href="https://xeiaso.net/blog/paranoid-nixos-2021-07-18">Paranoid NixOS Setup - Xe Iaso</a></li>
<li><a rel="noopener" target="_blank" href="https://github.com/nix-community/impermanence">nix-community/impermanence: NixOS module</a></li>
</ul>
<p>The goal is the following: over years, configuration files accumulate. Sometimes editing <code>/etc</code> is required, because of a bug or an obscure configuration. NixOS allows us to avoid this manual file editing, but it does not <em>force</em> us to do so. We can still have a lot of important state, breaking the reproducibility promise.</p>
<p>So what can we do instead? Erase everything, at each reboot. This way, we’ll be sure the only source of truth is our configuration.</p>
<h2 id="installing-the-system">Installing the system</h2>
<p>I’m currently using a <a rel="noopener" target="_blank" href="https://github.com/quickemu-project/quickemu">quickemu</a> VM. This is not a recommenced setup and is only done for testing. Configuration file:</p>
<pre data-lang="conf" class="language-conf z-code"><code class="language-conf" data-lang="conf"><span class="z-source z-genconfig"><span class="z-meta z-comment z-genconfig"><span class="z-comment z-line z-number-sign z-genconfig">#!/usr/bin/quickemu --vm
</span></span></span><span class="z-source z-genconfig"><span class="z-meta z-param z-genconfig"><span class="z-variable z-parameter z-genconfig">guest_os</span><span class="z-keyword z-operator z-genconfig">=</span></span><span class="z-string z-quoted z-double z-genconfig">"linux"</span>
</span><span class="z-source z-genconfig"><span class="z-meta z-param z-genconfig"><span class="z-variable z-parameter z-genconfig">disk_img</span><span class="z-keyword z-operator z-genconfig">=</span></span><span class="z-string z-quoted z-double z-genconfig">"nixos-22.11-minimal/disk.qcow2"</span>
</span><span class="z-source z-genconfig"><span class="z-meta z-param z-genconfig"><span class="z-variable z-parameter z-genconfig">iso</span><span class="z-keyword z-operator z-genconfig">=</span></span><span class="z-string z-quoted z-double z-genconfig">"nixos-22.11-minimal/latest-nixos-minimal-x86_64-linux.iso"</span>
</span><span class="z-source z-genconfig"><span class="z-meta z-param z-genconfig"><span class="z-variable z-parameter z-genconfig">disk_size</span><span class="z-keyword z-operator z-genconfig">=</span></span><span class="z-string z-quoted z-double z-genconfig">"50G"</span>
</span><span class="z-source z-genconfig"><span class="z-meta z-param z-genconfig"><span class="z-variable z-parameter z-genconfig">ram</span><span class="z-keyword z-operator z-genconfig">=</span></span><span class="z-string z-quoted z-double z-genconfig">"4G"</span>
</span></code></pre>
<p>Let’s first format it:</p>
<pre data-lang="sh" class="language-sh z-code"><code class="language-sh" data-lang="sh"><span class="z-source z-shell z-bash"><span class="z-variable z-other z-readwrite z-assignment z-shell">DISK</span><span class="z-keyword z-operator z-assignment z-shell">=</span><span class="z-string z-unquoted z-shell">/dev/vda</span>
</span><span class="z-source z-shell z-bash">
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">parted</span></span><span class="z-meta z-function-call z-arguments z-shell"> <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span><span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-variable z-other z-readwrite z-shell">DISK</span></span><span class="z-punctuation z-definition z-string z-end z-shell">"</span></span><span class="z-keyword z-operator z-end-of-options z-shell"> --</span></span><span class="z-meta z-function-call z-arguments z-shell"> mklabel gpt</span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">parted</span></span><span class="z-meta z-function-call z-arguments z-shell"> <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span><span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-variable z-other z-readwrite z-shell">DISK</span></span><span class="z-punctuation z-definition z-string z-end z-shell">"</span></span><span class="z-keyword z-operator z-end-of-options z-shell"> --</span></span><span class="z-meta z-function-call z-arguments z-shell"> mkpart ESP fat32 1MiB 1GiB</span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">parted</span></span><span class="z-meta z-function-call z-arguments z-shell"> <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span><span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-variable z-other z-readwrite z-shell">DISK</span></span><span class="z-punctuation z-definition z-string z-end z-shell">"</span></span><span class="z-keyword z-operator z-end-of-options z-shell"> --</span></span><span class="z-meta z-function-call z-arguments z-shell"> set 1 boot on</span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">mkfs.vfat</span></span><span class="z-meta z-function-call z-arguments z-shell"> <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span><span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-variable z-other z-readwrite z-shell">DISK</span></span><span class="z-punctuation z-definition z-string z-end z-shell">"</span></span>1</span>
</span></code></pre>
<pre data-lang="sh" class="language-sh z-code"><code class="language-sh" data-lang="sh"><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">parted</span></span><span class="z-meta z-function-call z-arguments z-shell"> <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span><span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-variable z-other z-readwrite z-shell">DISK</span></span><span class="z-punctuation z-definition z-string z-end z-shell">"</span></span><span class="z-keyword z-operator z-end-of-options z-shell"> --</span></span><span class="z-meta z-function-call z-arguments z-shell"> mkpart Swap linux-swap 1GiB 9GiB</span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">mkswap</span></span><span class="z-meta z-function-call z-arguments z-shell"><span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> -</span>L</span> Swap <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span><span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-variable z-other z-readwrite z-shell">DISK</span></span><span class="z-punctuation z-definition z-string z-end z-shell">"</span></span>2</span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">swapon</span></span><span class="z-meta z-function-call z-arguments z-shell"> <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span><span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-variable z-other z-readwrite z-shell">DISK</span></span><span class="z-punctuation z-definition z-string z-end z-shell">"</span></span>2</span>
</span></code></pre>
<blockquote>
<p>Using swap in 2023!?</p>
</blockquote>
<p><a rel="noopener" target="_blank" href="https://chrisdown.name/2018/01/02/in-defence-of-swap.html">Yes</a>.</p>
<pre data-lang="sh" class="language-sh z-code"><code class="language-sh" data-lang="sh"><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">parted</span></span><span class="z-meta z-function-call z-arguments z-shell"> <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span><span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-variable z-other z-readwrite z-shell">DISK</span></span><span class="z-punctuation z-definition z-string z-end z-shell">"</span></span><span class="z-keyword z-operator z-end-of-options z-shell"> --</span></span><span class="z-meta z-function-call z-arguments z-shell"> mkpart primary 9GiB 100<span class="z-meta z-group z-expansion z-job z-shell"><span class="z-punctuation z-definition z-variable z-job z-shell">%</span></span></span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">mkfs.btrfs</span></span><span class="z-meta z-function-call z-arguments z-shell"><span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> -</span>L</span> Butter <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span><span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-variable z-other z-readwrite z-shell">DISK</span></span><span class="z-punctuation z-definition z-string z-end z-shell">"</span></span>3</span>
</span></code></pre>
<p>While the <code>impermanence</code> module recommends using <code>tmpfs</code> for <code>/</code>, I chose to use <code>btrfs</code>: I do not have RAM to waste. Furthermore, this will allow us to use a nice script we’ll see later on.</p>
<p>Let’s create <code>btrfs</code> subvolumes:</p>
<pre data-lang="sh" class="language-sh z-code"><code class="language-sh" data-lang="sh"><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">mount</span></span><span class="z-meta z-function-call z-arguments z-shell"> <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span><span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-variable z-other z-readwrite z-shell">DISK</span></span><span class="z-punctuation z-definition z-string z-end z-shell">"</span></span>3 /mnt</span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">btrfs</span></span><span class="z-meta z-function-call z-arguments z-shell"> subvolume create /mnt/root</span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">btrfs</span></span><span class="z-meta z-function-call z-arguments z-shell"> subvolume create /mnt/home</span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">btrfs</span></span><span class="z-meta z-function-call z-arguments z-shell"> subvolume create /mnt/nix</span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">btrfs</span></span><span class="z-meta z-function-call z-arguments z-shell"> subvolume create /mnt/persist</span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">btrfs</span></span><span class="z-meta z-function-call z-arguments z-shell"> subvolume create /mnt/log</span>
</span></code></pre>
<p>And now, the crucial part:</p>
<pre data-lang="sh" class="language-sh z-code"><code class="language-sh" data-lang="sh"><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">btrfs</span></span><span class="z-meta z-function-call z-arguments z-shell"> subvolume snapshot<span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> -</span>r</span> /mnt/root /mnt/root-blank</span>
</span></code></pre>
<p>We just took a snapshot of that empty volume. We will restore it at each reboot.
We can now mount the subvolumes and let <code>nixos-generate-config</code> do its job</p>
<pre data-lang="sh" class="language-sh z-code"><code class="language-sh" data-lang="sh"><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">mount</span></span><span class="z-meta z-function-call z-arguments z-shell"><span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> -</span>o</span> subvol=root,compress=zstd,noatime <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span><span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-variable z-other z-readwrite z-shell">DISK</span></span><span class="z-punctuation z-definition z-string z-end z-shell">"</span></span>3 /mnt</span>
</span><span class="z-source z-shell z-bash">
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">mkdir</span></span><span class="z-meta z-function-call z-arguments z-shell"> /mnt/home</span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">mount</span></span><span class="z-meta z-function-call z-arguments z-shell"><span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> -</span>o</span> subvol=home,compress=zstd,noatime <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span><span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-variable z-other z-readwrite z-shell">DISK</span></span><span class="z-punctuation z-definition z-string z-end z-shell">"</span></span>3 /mnt/home</span>
</span><span class="z-source z-shell z-bash">
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">mkdir</span></span><span class="z-meta z-function-call z-arguments z-shell"> /mnt/nix</span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">mount</span></span><span class="z-meta z-function-call z-arguments z-shell"><span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> -</span>o</span> subvol=nix,compress=zstd,noatime <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span><span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-variable z-other z-readwrite z-shell">DISK</span></span><span class="z-punctuation z-definition z-string z-end z-shell">"</span></span>3 /mnt/nix</span>
</span><span class="z-source z-shell z-bash">
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">mkdir</span></span><span class="z-meta z-function-call z-arguments z-shell"> /mnt/persist</span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">mount</span></span><span class="z-meta z-function-call z-arguments z-shell"><span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> -</span>o</span> subvol=persist,compress=zstd,noatime <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span><span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-variable z-other z-readwrite z-shell">DISK</span></span><span class="z-punctuation z-definition z-string z-end z-shell">"</span></span>3 /mnt/persist</span>
</span><span class="z-source z-shell z-bash">
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">mkdir</span></span><span class="z-meta z-function-call z-arguments z-shell"><span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> -</span>p</span> /mnt/var/log</span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">mount</span></span><span class="z-meta z-function-call z-arguments z-shell"><span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> -</span>o</span> subvol=log,compress=zstd,noatime <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span><span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-variable z-other z-readwrite z-shell">DISK</span></span><span class="z-punctuation z-definition z-string z-end z-shell">"</span></span>3 /mnt/var/log</span>
</span><span class="z-source z-shell z-bash">
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">mkdir</span></span><span class="z-meta z-function-call z-arguments z-shell"> /mnt/boot</span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">mount</span></span><span class="z-meta z-function-call z-arguments z-shell"> <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span><span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-variable z-other z-readwrite z-shell">DISK</span></span><span class="z-punctuation z-definition z-string z-end z-shell">"</span></span>1 /mnt/boot</span>
</span><span class="z-source z-shell z-bash">
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">nixos-generate-config</span></span><span class="z-meta z-function-call z-arguments z-shell"><span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> --</span>root</span> /mnt</span>
</span></code></pre>
<p>Lastly, we only have to edit the generated configuration files at <code>/mnt/etc/nixos</code>.</p>
<p>My final configuration is available <a rel="noopener" target="_blank" href="https://github.com/Guekka/nixos-server/tree/1-impermanence">here</a>. You can follow all the steps by looking at the <a rel="noopener" target="_blank" href="https://github.com/Guekka/nixos-server/commits/1-impermanence">commits</a>.</p>
<h2 id="configuring-the-system">Configuring the system</h2>
<ul>
<li>Checking that we have the correct mount options in <code>/mnt/etc/nixos/hardware-configuration.nix</code>.</li>
</ul>
<p>I’ve added <code>"compress=zstd" "noatime"</code> to all filesystems. We also need to add <code>neededForBoot</code> to <code>/var/log</code> and <code>/persist</code>.</p>
<ul>
<li>Replacing default values in <code>configuration.nix</code></li>
</ul>
<p>I’ve enabled <code>networkmanager</code>, removed most suggested options and enabled <code>system.copySystemConfiguration</code>.</p>
<p>This last option copies the current configuration to <code>/run/current_system/configuration.nix</code>. You should not rely on it: keep your configuration in a git repository. But it can serve as some kind of last chance.</p>
<ul>
<li>Declaring a user, including ssh</li>
</ul>
<pre data-lang="nix" class="language-nix z-code"><code class="language-nix" data-lang="nix"><span class="z-source z-nix"><span class="z-variable z-parameter z-name z-nix">users</span><span class="z-keyword z-operator z-nix">.</span><span class="z-variable z-parameter z-name z-nix">mutableUsers</span> <span class="z-invalid z-illegal">=</span> <span class="z-constant z-language z-nix">false</span><span class="z-invalid z-illegal">;</span>
</span><span class="z-source z-nix"><span class="z-variable z-parameter z-name z-nix">users</span><span class="z-keyword z-operator z-nix">.</span><span class="z-variable z-parameter z-name z-nix">users</span><span class="z-keyword z-operator z-nix">.</span><span class="z-variable z-parameter z-name z-nix">user</span> <span class="z-invalid z-illegal">=</span> <span class="z-punctuation z-definition z-attrset-or-function z-nix">{</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">isNormalUser</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-constant z-language z-nix">true</span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">extraGroups</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-punctuation z-definition z-list z-nix">[</span> <span class="z-string z-quoted z-double z-nix"><span class="z-punctuation z-definition z-string z-double z-start z-nix">"</span>wheel<span class="z-punctuation z-definition z-string z-double z-end z-nix">"</span></span> <span class="z-punctuation z-definition z-list z-nix">]</span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix">
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">openssh</span>.<span class="z-entity z-other z-attribute-name z-multipart z-nix">authorizedKeys</span>.<span class="z-entity z-other z-attribute-name z-multipart z-nix">keys</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-punctuation z-definition z-list z-nix">[</span> <span class="z-string z-quoted z-double z-nix"><span class="z-punctuation z-definition z-string z-double z-start z-nix">"</span>ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICWVNch9BcjkMqS/Xwep+GN4HwqyRIjr3Cuw7mHpqsKr nixos<span class="z-punctuation z-definition z-string z-double z-end z-nix">"</span></span> <span class="z-punctuation z-definition z-list z-nix">]</span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix">
</span><span class="z-source z-nix"> <span class="z-comment z-line z-number-sign z-nix"># passwordFile needs to be in a volume marked with `neededForBoot = true`</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">passwordFile</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-string z-quoted z-double z-nix"><span class="z-punctuation z-definition z-string z-double z-start z-nix">"</span>/persist/passwords/user<span class="z-punctuation z-definition z-string z-double z-end z-nix">"</span></span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"><span class="z-punctuation z-definition z-attrset z-nix">}</span><span class="z-invalid z-illegal">;</span>
</span></code></pre>
<p>Here we have completely disabled imperative user modification. This does not matter much, as imperative changes would be erased anyway at start.
We thus need to provide a password. We’re using <code>passwordFile</code> for that: a path to a file containing the hashed password.</p>
<p>Here’s how to generate that file: <code>sudo mkpasswd -m sha-512 "hunter2" > /mnt/persist/passwords/user</code>.</p>
<p>The SSH key was generated using `ssh-keygen -t ed25519 -C “nixos”.</p>
<ul>
<li>Enabling openSSH
We’re going to use Xe’s configuration:</li>
</ul>
<pre data-lang="nix" class="language-nix z-code"><code class="language-nix" data-lang="nix"><span class="z-source z-nix"> <span class="z-variable z-parameter z-name z-nix">services</span><span class="z-keyword z-operator z-nix">.</span><span class="z-variable z-parameter z-name z-nix">openssh</span> <span class="z-invalid z-illegal">=</span> <span class="z-punctuation z-definition z-attrset-or-function z-nix">{</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">enable</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-constant z-language z-nix">true</span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">passwordAuthentication</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-constant z-language z-nix">false</span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">allowSFTP</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-constant z-language z-nix">false</span><span class="z-punctuation z-terminator z-bind z-nix">;</span> <span class="z-comment z-line z-number-sign z-nix"># Don't set this if you need sftp</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">challengeResponseAuthentication</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-constant z-language z-nix">false</span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">extraConfig</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-string z-quoted z-other z-nix"><span class="z-punctuation z-definition z-string z-other z-start z-nix">''</span>
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix"> AllowTcpForwarding yes
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix"> X11Forwarding no
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix"> AllowAgentForwarding no
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix"> AllowStreamLocalForwarding no
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix"> AuthenticationMethods publickey
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix"> <span class="z-punctuation z-definition z-string z-other z-end z-nix">''</span></span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"> <span class="z-punctuation z-definition z-attrset z-nix">}</span><span class="z-invalid z-illegal">;</span>
</span></code></pre>
<p>This reduces attack surface, for example by disabling stream-local forwarding and disabling password authentification.</p>
<hr />
<p>This will be enough for now. Let’s install the system before going to the next step: <code>sudo nixos-install --root /mnt && sudo reboot</code>. You should be able to connect by SSH using the previously defined key, or login using the password you defined in <code>/persist/passwords/user</code>.</p>
<h2 id="configuring-impermanence">Configuring impermanence</h2>
<p>We’ve created our volumes, we’ve configured the system… But I promised we would reset our system at each reboot. Let’s do that now!
We’re going to use the following script, credit of mt-caret. Do not forget to replace <code>vda3</code> with your data partition.</p>
<p><strong>16/07/23 update</strong>: it was brought to my attention that <a rel="noopener" target="_blank" href="https://discourse.nixos.org/t/what-does-impermanence-add-over-built-in-functionality/27939/16">postDeviceCommands can cause data loss</a>.
While I did not experience any issue, I have updated the script to use a safer alternative.</p>
<pre data-lang="nix" class="language-nix z-code"><code class="language-nix" data-lang="nix"><span class="z-source z-nix"> <span class="z-variable z-parameter z-name z-nix">boot</span><span class="z-keyword z-operator z-nix">.</span><span class="z-variable z-parameter z-name z-nix">initrd</span> <span class="z-invalid z-illegal">=</span> <span class="z-punctuation z-definition z-attrset-or-function z-nix">{</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">enable</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-constant z-language z-nix">true</span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">supportedFilesystems</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-punctuation z-definition z-list z-nix">[</span> <span class="z-string z-quoted z-double z-nix"><span class="z-punctuation z-definition z-string z-double z-start z-nix">"</span>btrfs<span class="z-punctuation z-definition z-string z-double z-end z-nix">"</span></span> <span class="z-punctuation z-definition z-list z-nix">]</span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix">
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">systemd</span>.<span class="z-entity z-other z-attribute-name z-multipart z-nix">services</span>.<span class="z-entity z-other z-attribute-name z-multipart z-nix">restore-root</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-punctuation z-definition z-attrset-or-function z-nix">{</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">description</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-string z-quoted z-double z-nix"><span class="z-punctuation z-definition z-string z-double z-start z-nix">"</span>Rollback btrfs rootfs<span class="z-punctuation z-definition z-string z-double z-end z-nix">"</span></span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">wantedBy</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-punctuation z-definition z-list z-nix">[</span> <span class="z-string z-quoted z-double z-nix"><span class="z-punctuation z-definition z-string z-double z-start z-nix">"</span>initrd.target<span class="z-punctuation z-definition z-string z-double z-end z-nix">"</span></span> <span class="z-punctuation z-definition z-list z-nix">]</span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">requires</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-punctuation z-definition z-list z-nix">[</span>
</span><span class="z-source z-nix"> <span class="z-string z-quoted z-double z-nix"><span class="z-punctuation z-definition z-string z-double z-start z-nix">"</span>dev-vda3<span class="z-punctuation z-definition z-string z-double z-end z-nix">"</span></span>
</span><span class="z-source z-nix"> <span class="z-punctuation z-definition z-list z-nix">]</span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">after</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-punctuation z-definition z-list z-nix">[</span>
</span><span class="z-source z-nix"> <span class="z-string z-quoted z-double z-nix"><span class="z-punctuation z-definition z-string z-double z-start z-nix">"</span>dev-vda3<span class="z-punctuation z-definition z-string z-double z-end z-nix">"</span></span>
</span><span class="z-source z-nix"> <span class="z-comment z-line z-number-sign z-nix"># for luks</span>
</span><span class="z-source z-nix"> <span class="z-string z-quoted z-double z-nix"><span class="z-punctuation z-definition z-string z-double z-start z-nix">"</span>systemd-cryptsetup@<span class="z-markup z-italic"><span class="z-punctuation z-section z-embedded z-begin z-nix">${</span><span class="z-variable z-parameter z-name z-nix">config</span><span class="z-keyword z-operator z-nix">.</span><span class="z-variable z-parameter z-name z-nix">networking</span><span class="z-keyword z-operator z-nix">.</span><span class="z-variable z-parameter z-name z-nix">hostName</span><span class="z-punctuation z-section z-embedded z-end z-nix">}</span></span>.service<span class="z-punctuation z-definition z-string z-double z-end z-nix">"</span></span>
</span><span class="z-source z-nix"> <span class="z-punctuation z-definition z-list z-nix">]</span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">before</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-punctuation z-definition z-list z-nix">[</span> <span class="z-string z-quoted z-double z-nix"><span class="z-punctuation z-definition z-string z-double z-start z-nix">"</span>sysroot.mount<span class="z-punctuation z-definition z-string z-double z-end z-nix">"</span></span> <span class="z-punctuation z-definition z-list z-nix">]</span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">unitConfig</span>.<span class="z-entity z-other z-attribute-name z-multipart z-nix">DefaultDependencies</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-string z-quoted z-double z-nix"><span class="z-punctuation z-definition z-string z-double z-start z-nix">"</span>no<span class="z-punctuation z-definition z-string z-double z-end z-nix">"</span></span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">serviceConfig</span>.<span class="z-entity z-other z-attribute-name z-multipart z-nix">Type</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-string z-quoted z-double z-nix"><span class="z-punctuation z-definition z-string z-double z-start z-nix">"</span>oneshot<span class="z-punctuation z-definition z-string z-double z-end z-nix">"</span></span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">script</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-string z-quoted z-other z-nix"><span class="z-punctuation z-definition z-string z-other z-start z-nix">''</span>
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix"> mkdir -p /mnt
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix">
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix"> # We first mount the btrfs root to /mnt
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix"> # so we can manipulate btrfs subvolumes.
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix"> mount -o subvol=/ /dev/vda3 /mnt
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix">
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix"> # While we're tempted to just delete /root and create
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix"> # a new snapshot from /root-blank, /root is already
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix"> # populated at this point with a number of subvolumes,
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix"> # which makes `btrfs subvolume delete` fail.
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix"> # So, we remove them first.
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix"> #
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix"> # /root contains subvolumes:
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix"> # - /root/var/lib/portables
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix"> # - /root/var/lib/machines
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix"> #
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix"> # I suspect these are related to systemd-nspawn, but
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix"> # since I don't use it I'm not 100% sure.
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix"> # Anyhow, deleting these subvolumes hasn't resulted
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix"> # in any issues so far, except for fairly
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix"> # benign-looking errors from systemd-tmpfiles.
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix"> btrfs subvolume list -o /mnt/root |
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix"> cut -f9 -d' ' |
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix"> while read subvolume; do
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix"> echo "deleting /$subvolume subvolume..."
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix"> btrfs subvolume delete "/mnt/$subvolume"
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix"> done &&
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix"> echo "deleting /root subvolume..." &&
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix"> btrfs subvolume delete /mnt/root
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix">
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix"> echo "restoring blank /root subvolume..."
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix"> btrfs subvolume snapshot /mnt/root-blank /mnt/root
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix">
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix"> # Once we're done rolling back to a blank snapshot,
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix"> # we can unmount /mnt and continue on the boot process.
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix"> umount /mnt
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix"> <span class="z-punctuation z-definition z-string z-other z-end z-nix">''</span></span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"> <span class="z-punctuation z-definition z-attrset z-nix">}</span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"> <span class="z-punctuation z-definition z-attrset z-nix">}</span><span class="z-invalid z-illegal">;</span>
</span></code></pre>
<p>We can then specify the files we want to keep.</p>
<p>But which files do we want to keep? Let’s find out. Thanks to another useful script of mt-caret, we can list the differences between our current <code>/</code> and the blank state:</p>
<pre data-lang="sh" class="language-sh z-code"><code class="language-sh" data-lang="sh"><span class="z-source z-shell z-bash"><span class="z-comment z-line z-number-sign z-shell"><span class="z-punctuation z-definition z-comment z-begin z-shell">#</span></span><span class="z-comment z-line z-number-sign z-shell">!/usr/bin/env bash</span><span class="z-comment z-line z-number-sign z-shell">
</span></span><span class="z-source z-shell z-bash"><span class="z-comment z-line z-number-sign z-shell"><span class="z-punctuation z-definition z-comment z-begin z-shell">#</span></span><span class="z-comment z-line z-number-sign z-shell"> fs-diff.sh</span><span class="z-comment z-line z-number-sign z-shell">
</span></span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-support z-function z-set z-shell">set</span></span><span class="z-meta z-function-call z-arguments z-shell"> -euo pipefail</span>
</span><span class="z-source z-shell z-bash">
</span><span class="z-source z-shell z-bash"><span class="z-variable z-other z-readwrite z-assignment z-shell">OLD_TRANSID</span><span class="z-keyword z-operator z-assignment z-shell">=</span><span class="z-string z-unquoted z-shell"><span class="z-meta z-group z-expansion z-command z-parens z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-punctuation z-section z-parens z-begin z-shell">(</span><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">sudo</span></span><span class="z-meta z-function-call z-arguments z-shell"> btrfs subvolume find-new /mnt/root-blank 9999999</span><span class="z-punctuation z-section z-parens z-end z-shell">)</span></span></span>
</span><span class="z-source z-shell z-bash"><span class="z-variable z-other z-readwrite z-assignment z-shell">OLD_TRANSID</span><span class="z-keyword z-operator z-assignment z-shell">=</span><span class="z-string z-unquoted z-shell"><span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-punctuation z-section z-expansion z-parameter z-begin z-shell">{</span></span><span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-variable z-other z-readwrite z-shell">OLD_TRANSID<span class="z-keyword z-operator z-expansion z-shell">#</span></span></span><span class="z-meta z-group z-expansion z-parameter z-shell">transid marker was </span><span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-section z-expansion z-parameter z-end z-shell">}</span></span></span>
</span><span class="z-source z-shell z-bash">
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">sudo</span></span><span class="z-meta z-function-call z-arguments z-shell"> btrfs subvolume find-new <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span>/mnt/root<span class="z-punctuation z-definition z-string z-end z-shell">"</span></span> <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span><span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-variable z-other z-readwrite z-shell">OLD_TRANSID</span></span><span class="z-punctuation z-definition z-string z-end z-shell">"</span></span></span> <span class="z-keyword z-operator z-logical z-pipe z-shell">|</span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">sed</span></span><span class="z-meta z-function-call z-arguments z-shell"> <span class="z-string z-quoted z-single z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">'</span>$d<span class="z-punctuation z-definition z-string z-end z-shell">'</span></span></span> <span class="z-keyword z-operator z-logical z-pipe z-shell">|</span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">cut</span></span><span class="z-meta z-function-call z-arguments z-shell"><span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> -</span>f17-</span><span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> -</span>d</span><span class="z-string z-quoted z-single z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">'</span> <span class="z-punctuation z-definition z-string z-end z-shell">'</span></span></span> <span class="z-keyword z-operator z-logical z-pipe z-shell">|</span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">sort</span></span> <span class="z-keyword z-operator z-logical z-pipe z-shell">|</span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">uniq</span></span> <span class="z-keyword z-operator z-logical z-pipe z-shell">|</span>
</span><span class="z-source z-shell z-bash"><span class="z-keyword z-control z-loop z-while z-shell">while</span> <span class="z-meta z-function-call z-shell"><span class="z-support z-function z-read z-shell">read</span></span><span class="z-meta z-function-call z-arguments z-shell"> path</span><span class="z-keyword z-operator z-logical z-continue z-shell">;</span> <span class="z-keyword z-control z-loop z-do z-shell">do</span>
</span><span class="z-source z-shell z-bash"> <span class="z-variable z-other z-readwrite z-assignment z-shell">path</span><span class="z-keyword z-operator z-assignment z-shell">=</span><span class="z-string z-unquoted z-shell"><span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span>/<span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-variable z-other z-readwrite z-shell">path</span></span><span class="z-punctuation z-definition z-string z-end z-shell">"</span></span></span>
</span><span class="z-source z-shell z-bash"> <span class="z-keyword z-control z-conditional z-if z-shell">if</span> <span class="z-support z-function z-test z-begin z-shell">[</span><span class="z-meta z-function-call z-arguments z-shell"> <span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell">-</span>L</span> <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span><span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-variable z-other z-readwrite z-shell">path</span></span><span class="z-punctuation z-definition z-string z-end z-shell">"</span></span> <span class="z-support z-function z-test z-end z-shell">]</span></span><span class="z-keyword z-operator z-logical z-continue z-shell">;</span> <span class="z-keyword z-control z-conditional z-then z-shell">then</span>
</span><span class="z-source z-shell z-bash"> <span class="z-meta z-function-call z-shell"><span class="z-support z-function z-colon z-shell">:</span></span> <span class="z-comment z-line z-number-sign z-shell"><span class="z-punctuation z-definition z-comment z-begin z-shell">#</span></span><span class="z-comment z-line z-number-sign z-shell"> The path is a symbolic link, so is probably handled by NixOS already</span><span class="z-comment z-line z-number-sign z-shell">
</span></span><span class="z-source z-shell z-bash"> <span class="z-keyword z-control z-conditional z-elseif z-shell">elif</span> <span class="z-support z-function z-test z-begin z-shell">[</span><span class="z-meta z-function-call z-arguments z-shell"> <span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell">-</span>d</span> <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span><span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-variable z-other z-readwrite z-shell">path</span></span><span class="z-punctuation z-definition z-string z-end z-shell">"</span></span> <span class="z-support z-function z-test z-end z-shell">]</span></span><span class="z-keyword z-operator z-logical z-continue z-shell">;</span> <span class="z-keyword z-control z-conditional z-then z-shell">then</span>
</span><span class="z-source z-shell z-bash"> <span class="z-meta z-function-call z-shell"><span class="z-support z-function z-colon z-shell">:</span></span> <span class="z-comment z-line z-number-sign z-shell"><span class="z-punctuation z-definition z-comment z-begin z-shell">#</span></span><span class="z-comment z-line z-number-sign z-shell"> The path is a directory, ignore</span><span class="z-comment z-line z-number-sign z-shell">
</span></span><span class="z-source z-shell z-bash"> <span class="z-keyword z-control z-conditional z-else z-shell">else</span>
</span><span class="z-source z-shell z-bash"> <span class="z-meta z-function-call z-shell"><span class="z-support z-function z-echo z-shell">echo</span></span><span class="z-meta z-function-call z-arguments z-shell"> <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span><span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-variable z-other z-readwrite z-shell">path</span></span><span class="z-punctuation z-definition z-string z-end z-shell">"</span></span></span>
</span><span class="z-source z-shell z-bash"> <span class="z-keyword z-control z-conditional z-end z-shell">fi</span>
</span><span class="z-source z-shell z-bash"><span class="z-keyword z-control z-loop z-end z-shell">done</span>
</span></code></pre>
<p>Used like this:</p>
<pre data-lang="sh" class="language-sh z-code"><code class="language-sh" data-lang="sh"><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">sudo</span></span><span class="z-meta z-function-call z-arguments z-shell"> mkdir /mnt</span> <span class="z-keyword z-operator z-logical z-continue z-shell">;</span> <span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">sudo</span></span><span class="z-meta z-function-call z-arguments z-shell"> mount<span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> -</span>o</span> subvol=/ /dev/vda3 /mnt</span> <span class="z-keyword z-operator z-logical z-continue z-shell">;</span> <span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">./fs-diff.sh</span></span>
</span></code></pre>
<p>Here’s the result of mine:</p>
<pre class="z-code"><code><span class="z-text z-plain">/etc/.clean
</span><span class="z-text z-plain">/etc/group
</span><span class="z-text z-plain">/etc/machine-id
</span><span class="z-text z-plain">/etc/nixos/configuration.nix
</span><span class="z-text z-plain">/etc/nixos/hardware-configuration.nix
</span><span class="z-text z-plain">/etc/passwd
</span><span class="z-text z-plain">/etc/resolv.conf
</span><span class="z-text z-plain">/etc/shadow
</span><span class="z-text z-plain">/etc/ssh/authorized_keys.d/user
</span><span class="z-text z-plain">/etc/ssh/ssh_host_ed25519_key
</span><span class="z-text z-plain">/etc/ssh/ssh_host_ed25519_key.pub
</span><span class="z-text z-plain">/etc/ssh/ssh_host_rsa_key
</span><span class="z-text z-plain">/etc/ssh/ssh_host_rsa_key.pub
</span><span class="z-text z-plain">/etc/subgid
</span><span class="z-text z-plain">/etc/subuid
</span><span class="z-text z-plain">/etc/sudoers
</span><span class="z-text z-plain">/etc/.updated
</span><span class="z-text z-plain">/root/.nix-channels
</span><span class="z-text z-plain">/root/.nix-defexpr/channels
</span><span class="z-text z-plain">/var/lib/NetworkManager/internal-84e273c2-b91a-3a96-b341-8234a339bdc7-enp0s8.lease
</span><span class="z-text z-plain">/var/lib/NetworkManager/internal-84e273c2-b91a-3a96-b341-8234a339bdc7-enp0s9.lease
</span><span class="z-text z-plain">/var/lib/NetworkManager/NetworkManager-intern.conf
</span><span class="z-text z-plain">/var/lib/NetworkManager/secret_key
</span><span class="z-text z-plain">/var/lib/NetworkManager/timestamps
</span><span class="z-text z-plain">/var/lib/nixos/auto-subuid-map
</span><span class="z-text z-plain">/var/lib/nixos/declarative-groups
</span><span class="z-text z-plain">/var/lib/nixos/declarative-users
</span><span class="z-text z-plain">/var/lib/nixos/gid-map
</span><span class="z-text z-plain">/var/lib/nixos/uid-map
</span><span class="z-text z-plain">/var/lib/systemd/catalog/database
</span><span class="z-text z-plain">/var/lib/systemd/random-seed
</span><span class="z-text z-plain">/var/.updated
</span></code></pre>
<p>That’s not too bad!</p>
<p>Out of these, there’s almost nothing I want to preserve.</p>
<p>Let’s make use of the <code>impermanence</code> module. We need to download it:</p>
<pre data-lang="nix" class="language-nix z-code"><code class="language-nix" data-lang="nix"><span class="z-source z-nix"><span class="z-keyword z-other z-nix">let</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">impermanence</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-constant z-language z-nix">builtins</span><span class="z-keyword z-operator z-nix">.</span><span class="z-variable z-parameter z-name z-nix">fetchTarball</span> <span class="z-string z-quoted z-double z-nix"><span class="z-punctuation z-definition z-string z-double z-start z-nix">"</span>https://github.com/nix-community/impermanence/archive/master.tar.gz<span class="z-punctuation z-definition z-string z-double z-end z-nix">"</span></span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"><span class="z-keyword z-other z-nix">in</span>
</span><span class="z-source z-nix"><span class="z-punctuation z-definition z-attrset-or-function z-nix">{</span>
</span><span class="z-source z-nix"><span class="z-entity z-other z-attribute-name z-multipart z-nix">imports</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-punctuation z-definition z-list z-nix">[</span> <span class="z-string z-quoted z-double z-nix"><span class="z-punctuation z-definition z-string z-double z-start z-nix">"</span><span class="z-markup z-italic"><span class="z-punctuation z-section z-embedded z-begin z-nix">${</span><span class="z-variable z-parameter z-name z-nix">impermanence</span><span class="z-punctuation z-section z-embedded z-end z-nix">}</span></span>/nixos.nix<span class="z-punctuation z-definition z-string z-double z-end z-nix">"</span></span> <span class="z-string z-unquoted z-path z-nix">./hardware-configuration.nix</span> <span class="z-punctuation z-definition z-list z-nix">]</span>
</span><span class="z-source z-nix"><span class="z-keyword z-operator z-nix">//</span> <span class="z-variable z-parameter z-name z-nix">the</span> <span class="z-variable z-parameter z-name z-nix">whole</span> <span class="z-variable z-parameter z-name z-nix">configuration</span>
</span><span class="z-source z-nix"><span class="z-invalid z-illegal">}</span>
</span></code></pre>
<p>And now, we can just tell it the files and directories that we want:</p>
<pre data-lang="nix" class="language-nix z-code"><code class="language-nix" data-lang="nix"><span class="z-source z-nix"> <span class="z-comment z-line z-number-sign z-nix"># configure impermanence</span>
</span><span class="z-source z-nix"> <span class="z-variable z-parameter z-name z-nix">environment</span><span class="z-keyword z-operator z-nix">.</span><span class="z-variable z-parameter z-name z-nix">persistence</span><span class="z-keyword z-operator z-nix">.</span><span class="z-string z-quoted z-double z-nix"><span class="z-punctuation z-definition z-string z-double z-start z-nix">"</span>/persist<span class="z-punctuation z-definition z-string z-double z-end z-nix">"</span></span> <span class="z-invalid z-illegal">=</span> <span class="z-punctuation z-definition z-attrset-or-function z-nix">{</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">directories</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-punctuation z-definition z-list z-nix">[</span>
</span><span class="z-source z-nix"> <span class="z-string z-quoted z-double z-nix"><span class="z-punctuation z-definition z-string z-double z-start z-nix">"</span>/etc/nixos<span class="z-punctuation z-definition z-string z-double z-end z-nix">"</span></span>
</span><span class="z-source z-nix"> <span class="z-punctuation z-definition z-list z-nix">]</span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">files</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-punctuation z-definition z-list z-nix">[</span>
</span><span class="z-source z-nix"> <span class="z-string z-quoted z-double z-nix"><span class="z-punctuation z-definition z-string z-double z-start z-nix">"</span>/etc/machine-id<span class="z-punctuation z-definition z-string z-double z-end z-nix">"</span></span>
</span><span class="z-source z-nix"> <span class="z-string z-quoted z-double z-nix"><span class="z-punctuation z-definition z-string z-double z-start z-nix">"</span>/etc/ssh/ssh_host_ed25519_key<span class="z-punctuation z-definition z-string z-double z-end z-nix">"</span></span>
</span><span class="z-source z-nix"> <span class="z-string z-quoted z-double z-nix"><span class="z-punctuation z-definition z-string z-double z-start z-nix">"</span>/etc/ssh/ssh_host_ed25519_key.pub<span class="z-punctuation z-definition z-string z-double z-end z-nix">"</span></span>
</span><span class="z-source z-nix"> <span class="z-string z-quoted z-double z-nix"><span class="z-punctuation z-definition z-string z-double z-start z-nix">"</span>/etc/ssh/ssh_host_rsa_key<span class="z-punctuation z-definition z-string z-double z-end z-nix">"</span></span>
</span><span class="z-source z-nix"> <span class="z-string z-quoted z-double z-nix"><span class="z-punctuation z-definition z-string z-double z-start z-nix">"</span>/etc/ssh/ssh_host_rsa_key.pub<span class="z-punctuation z-definition z-string z-double z-end z-nix">"</span></span>
</span><span class="z-source z-nix"> <span class="z-invalid z-illegal">}</span><span class="z-invalid z-illegal">;</span>
</span><span class="z-source z-nix">
</span><span class="z-source z-nix"> <span class="z-variable z-parameter z-name z-nix">security</span><span class="z-keyword z-operator z-nix">.</span><span class="z-variable z-parameter z-name z-nix">sudo</span><span class="z-keyword z-operator z-nix">.</span><span class="z-variable z-parameter z-name z-nix">extraConfig</span> <span class="z-invalid z-illegal">=</span> <span class="z-string z-quoted z-other z-nix"><span class="z-punctuation z-definition z-string z-other z-start z-nix">''</span>
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix"> # rollback results in sudo lectures after each reboot
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix"> Defaults lecture = never
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix"> <span class="z-punctuation z-definition z-string z-other z-end z-nix">''</span></span><span class="z-invalid z-illegal">;</span>
</span></code></pre>
<p>What an ergonomic interface.</p>
<blockquote>
<p>Wait, did you just say Nix was ergonomic?</p>
</blockquote>
<p>Well, yes. Sometimes.</p>
<hr />
<p>I have not saved my network manager configuration, but you may need to.</p>
<p>When new files are set to be preserved, <strong>it is necessary to copy them manually to <code>/persist</code></strong>:</p>
<pre data-lang="sh" class="language-sh z-code"><code class="language-sh" data-lang="sh"><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">sudo</span></span><span class="z-meta z-function-call z-arguments z-shell"> nixos-rebuild boot</span>
</span><span class="z-source z-shell z-bash">
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">sudo</span></span><span class="z-meta z-function-call z-arguments z-shell"> mkdir /persist/etc</span>
</span><span class="z-source z-shell z-bash">
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">sudo</span></span><span class="z-meta z-function-call z-arguments z-shell"> cp<span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> -</span>r</span> <span class="z-meta z-group z-expansion z-brace z-shell"><span class="z-punctuation z-section z-expansion z-brace z-begin z-shell">{</span><span class="z-punctuation z-separator z-shell">,</span>/persist<span class="z-punctuation z-section z-expansion z-brace z-end z-shell">}</span></span>/etc/nixos</span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">sudo</span></span><span class="z-meta z-function-call z-arguments z-shell"> cp <span class="z-meta z-group z-expansion z-brace z-shell"><span class="z-punctuation z-section z-expansion z-brace z-begin z-shell">{</span><span class="z-punctuation z-separator z-shell">,</span>/persist<span class="z-punctuation z-section z-expansion z-brace z-end z-shell">}</span></span>/etc/machine-id</span>
</span><span class="z-source z-shell z-bash">
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">sudo</span></span><span class="z-meta z-function-call z-arguments z-shell"> mkdir /persist/etc/ssh</span>
</span><span class="z-source z-shell z-bash">
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">sudo</span></span><span class="z-meta z-function-call z-arguments z-shell"> cp <span class="z-meta z-group z-expansion z-brace z-shell"><span class="z-punctuation z-section z-expansion z-brace z-begin z-shell">{</span><span class="z-punctuation z-separator z-shell">,</span>/persist<span class="z-punctuation z-section z-expansion z-brace z-end z-shell">}</span></span>/etc/ssh/ssh_host_ed25519_key</span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">sudo</span></span><span class="z-meta z-function-call z-arguments z-shell"> cp <span class="z-meta z-group z-expansion z-brace z-shell"><span class="z-punctuation z-section z-expansion z-brace z-begin z-shell">{</span><span class="z-punctuation z-separator z-shell">,</span>/persist<span class="z-punctuation z-section z-expansion z-brace z-end z-shell">}</span></span>/etc/ssh/ssh_host_ed25519_key.pub</span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">sudo</span></span><span class="z-meta z-function-call z-arguments z-shell"> cp <span class="z-meta z-group z-expansion z-brace z-shell"><span class="z-punctuation z-section z-expansion z-brace z-begin z-shell">{</span><span class="z-punctuation z-separator z-shell">,</span>/persist<span class="z-punctuation z-section z-expansion z-brace z-end z-shell">}</span></span>/etc/ssh/ssh_host_rsa_key</span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">sudo</span></span><span class="z-meta z-function-call z-arguments z-shell"> cp <span class="z-meta z-group z-expansion z-brace z-shell"><span class="z-punctuation z-section z-expansion z-brace z-begin z-shell">{</span><span class="z-punctuation z-separator z-shell">,</span>/persist<span class="z-punctuation z-section z-expansion z-brace z-end z-shell">}</span></span>/etc/ssh/ssh_host_rsa_key.pub</span>
</span></code></pre>
<p>Now, if we reboot and list files again:</p>
<pre class="z-code"><code><span class="z-text z-plain">/etc/.clean
</span><span class="z-text z-plain">/etc/group
</span><span class="z-text z-plain">/etc/passwd
</span><span class="z-text z-plain">/etc/resolv.conf
</span><span class="z-text z-plain">/etc/shadow
</span><span class="z-text z-plain">/etc/ssh/authorized_keys.d/user
</span><span class="z-text z-plain">/etc/subgid
</span><span class="z-text z-plain">/etc/subuid
</span><span class="z-text z-plain">/etc/sudoers
</span><span class="z-text z-plain">/etc/.updated
</span><span class="z-text z-plain">/root/.nix-channels
</span><span class="z-text z-plain">/var/lib/NetworkManager/internal-84e273c2-b91a-3a96-b341-8234a339bdc7-enp0s9.lease
</span><span class="z-text z-plain">/var/lib/NetworkManager/NetworkManager-intern.conf
</span><span class="z-text z-plain">/var/lib/NetworkManager/secret_key
</span><span class="z-text z-plain">/var/lib/NetworkManager/timestamps
</span><span class="z-text z-plain">/var/lib/nixos/auto-subuid-map
</span><span class="z-text z-plain">/var/lib/nixos/declarative-groups
</span><span class="z-text z-plain">/var/lib/nixos/declarative-users
</span><span class="z-text z-plain">/var/lib/nixos/gid-map
</span><span class="z-text z-plain">/var/lib/nixos/uid-map
</span><span class="z-text z-plain">/var/lib/systemd/catalog/database
</span><span class="z-text z-plain">/var/lib/systemd/random-seed
</span><span class="z-text z-plain">/var/.updated
</span></code></pre>
<p>Success! The files we persisted are no longer showing up.</p>
<h3 id="what-about-our-home-directory">What about our home directory?</h3>
<p>It is possible to setup the impermanence module for our home directory. However, I did not want to go through <code>home-manager</code> installation. Furthermore, a home directory is meant to be stateful.</p>
<p>In our case, we are creating a server, so it would still make sense to configure it. If you are interested, have a look at <a rel="noopener" target="_blank" href="https://elis.nu/blog/2020/06/nixos-tmpfs-as-home/">tmpfs at home</a>.</p>
<h2 id="next-steps">Next steps</h2>
<p>In the next part, we will make our server more secure by making it only available through Tailscale. We will also setup our first service.</p>
<hr />
<p>I hope you’ve enjoyed this article! Thanks for reading to the end!</p>