<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Blog on Giovanni Bassi</title><link>https://giggio.net/en/blog/</link><image><url>https://giggio.net/images/base/logo-small.png</url><title>Blog on Giovanni Bassi</title><link>https://giggio.net/en/blog/</link></image><description>Blog no site do Giovanni Bassi</description><generator>Hugo</generator><language>en</language><managingEditor>giggio@giggio.net (Giovanni Bassi)</managingEditor><webMaster>giggio@giggio.net (Giovanni Bassi)</webMaster><copyright>© 2025 Giovanni Bassi</copyright><lastBuildDate>Mon, 01 Jun 2026 11:00:00 -0300</lastBuildDate><atom:link href="https://giggio.net/en/blog/index.xml" rel="self" type="application/rss+xml"/><item><title>NixOS: Installation Guide with RAID 1, encryption, and TPM unlock (part 7 - Mitigating the OS swap attack)</title><link>https://giggio.net/en/blog/nix-os-guia-de-instalacao-com-raid-1-criptografia-e-tpm-unlock-parte-7-mitigando-o-ataque-de-troca-de-so/</link><pubDate>Mon, 01 Jun 2026 11:00:00 -0300</pubDate><author>giggio@giggio.net (Giovanni Bassi)</author><guid>https://giggio.net/en/blog/nix-os-guia-de-instalacao-com-raid-1-criptografia-e-tpm-unlock-parte-7-mitigando-o-ataque-de-troca-de-so/</guid><category>infra</category><description>&lt;p&gt;There is still a loophole where one could obtain the LUKS volume encryption key using another operating system. Let&amp;rsquo;s
fix that.&lt;/p&gt;
&lt;p&gt;This is the seventh post in the series:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="https://giggio.net/en/blog/nix-os-guia-de-instalacao-com-raid-1-criptografia-e-tpm-unlock-parte-1/"&gt;Preparing the virtual machine and partitioning the disks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://giggio.net/en/blog/nix-os-guia-de-instalacao-com-raid-1-criptografia-e-tpm-unlock-parte-2-disko-luks-e-btrfs/"&gt;Disko, LUKS, and btrfs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://giggio.net/en/blog/nix-os-guia-de-instalacao-com-raid-1-criptografia-e-tpm-unlock-parte-3-instalando-o-so/"&gt;Installing the OS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://giggio.net/en/blog/nix-os-guia-de-instalacao-com-raid-1-criptografia-e-tpm-unlock-parte-4-secure-boot/"&gt;Enabling Secure Boot&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://giggio.net/en/blog/nix-os-guia-de-instalacao-com-raid-1-criptografia-e-tpm-unlock-parte-5-destrancando-o-disco-com-tpm/"&gt;Unlocking the disk with TPM&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://giggio.net/en/blog/nix-os-guia-de-instalacao-com-raid-1-criptografia-e-tpm-unlock-parte-6-mitigando-o-ataque-de-troca-de-volume/"&gt;Mitigating the volume swap attack&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mitigating the OS-switch attack (this post)&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If you are only using your own keys in Secure Boot, this post doesn&amp;rsquo;t apply to your scenario. If you are also using
Microsoft&amp;rsquo;s keys, you need to implement this mitigation.&lt;/p&gt;
&lt;h3 id="the-problem"&gt;
 &lt;a href="#the-problem" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;The Problem&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;So far, we have been linking disk decryption to PCR 7 (and PCR 15 for system loading, as described in the last post). As
seen in previous posts, PCR 7 depends exclusively on the Secure Boot state. This means that, regardless of which
operating system is loaded, PCR 7 will have the same values.&lt;/p&gt;
&lt;p&gt;If you also trust third-party keys (such as Microsoft&amp;rsquo;s or another manufacturer&amp;rsquo;s, like Canonical&amp;rsquo;s) in Secure Boot,
there is still an attack surface that allows booting another system signed by those authorities. Therefore, it&amp;rsquo;s
necessary to bind the protection to something beyond just PCR 7.&lt;/p&gt;
&lt;p&gt;With the operating system loaded and PCR 7 in the correct configuration, it&amp;rsquo;s possible to obtain the LUKS volume header
data and decrypt it using the TPM to get the LUKS master key, and then decrypt the volume. This could even be done using
a Live CD with a correctly signed boot loader.&lt;/p&gt;
&lt;h3 id="naively-sealing-the-luks-key-with-the-nixos-boot-loader"&gt;
 &lt;a href="#naively-sealing-the-luks-key-with-the-nixos-boot-loader" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;Naively sealing the LUKS key with the NixOS boot loader&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;Sealing the volume key only with PCR 7 is risky, so let&amp;rsquo;s also use another PCR that is closely tied to our operating
system. PCR 4 measures the boot loader and additional drivers — the boot loader/PE binary code executed in the boot path.
This means only an operating system that loads using the NixOS &lt;code&gt;.efi&lt;/code&gt; files we are using would be able to retrieve the
LUKS key via the TPM.&lt;/p&gt;
&lt;p&gt;Since these &lt;code&gt;.efi&lt;/code&gt; files are generated on-demand for our specific NixOS configuration and signed with Lanzaboote using
our private keys, this prevents another OS from decrypting the LUKS key. For instance, if you load Ubuntu, the boot
loader will be Ubuntu&amp;rsquo;s, with different &lt;code&gt;.efi&lt;/code&gt; files, and therefore the content extended into PCR 4 will also be
different. Because PCR 4 measures the EFI/PE binary actually executed at boot, a different boot path produces a
different measurement and cannot reuse the same TPM token.&lt;/p&gt;
&lt;p&gt;This would be simple if the boot loader didn&amp;rsquo;t change every time the Kernel, initrd, or any boot configuration is
updated, which ends up changing PCR 4. If that weren&amp;rsquo;t the case, we could simply run:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# remove previous token from disk 1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo systemd-cryptenroll --wipe-slot&lt;span class="o"&gt;=&lt;/span&gt;tpm2 /dev/disk/by-label/NIXLUKS1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# enroll new token using PCR 4 and PCR 7 on disk 1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo systemd-cryptenroll --tpm2-device&lt;span class="o"&gt;=&lt;/span&gt;auto --tpm2-pcrs&lt;span class="o"&gt;=&lt;/span&gt;4+7 /dev/disk/by-label/NIXLUKS1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# do the same for disk 2...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The commands above can be combined into one, which will wipe and enroll in a single operation.&lt;/p&gt;
&lt;p&gt;This will only work until the next time the boot loader files (the &lt;code&gt;.efi&lt;/code&gt; files) are changed. On the first boot after
such a change, the volume will no longer decrypt automatically, it will ask for the password, and you would need to run
the commands above again. If that&amp;rsquo;s acceptable to you, you don&amp;rsquo;t even need to read the rest of the post, but I can tell
you there is a better and equally secure way to solve this.&lt;/p&gt;
&lt;h3 id="sealing-the-luks-key-with-the-nixos-boot-loader-using-a-pcr-policy"&gt;
 &lt;a href="#sealing-the-luks-key-with-the-nixos-boot-loader-using-a-pcr-policy" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;Sealing the LUKS key with the NixOS boot loader using a PCR policy&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Important&lt;/strong&gt;: Before starting, verify that your virtual machine is configured to use only one of the disks as a boot
disk. I noticed that if both disks are marked as boot disks, PCR 4 is not correctly evaluated and the policy is not
created. In the virtual machine settings, you can see this in the &amp;ldquo;Boot Options&amp;rdquo; menu. This might be a quirk of Virtual
Machine Manager (and &lt;code&gt;virsh&lt;/code&gt;), the Firmware used, or something related to Secure Boot and TPM protocols. The problem
doesn&amp;rsquo;t occur on my physical machine, only on the virtual one.&lt;/p&gt;
&lt;p&gt;The secret to solving this problem is a system that can predict the values that will be in the PCR and create an
appropriate policy, and that is exactly what
&lt;a href="https://www.freedesktop.org/software/systemd/man/latest/systemd-pcrlock.html"&gt;systemd-pcrlock&lt;/a&gt; does. While the process
can be done manually, Lanzaboote recently implemented
&lt;a href="https://github.com/nix-community/lanzaboote/blob/master/docs/how-to-guides/enable-measured-boot.md"&gt;TPM measurement&lt;/a&gt;
alongside Secure Boot integration, using systemd-pcrlock. At the time of writing, this hasn&amp;rsquo;t made it into a release
yet, meaning it might contain bugs. By the time you read this, the functionality may have stabilized.&lt;/p&gt;
&lt;p&gt;To enable it, simply add this to your configuration:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-nix" data-lang="nix"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;boot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lanzaboote&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;enable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;pkiBundle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;/var/lib/sbctl&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;configurationLimit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;measuredBoot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;enable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;pcrs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Finally, you need to perform a &lt;code&gt;nixos-rebuild&lt;/code&gt;, but use &lt;code&gt;boot&lt;/code&gt; instead of &lt;code&gt;switch&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo nixos-rebuild boot
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This will prepare the configuration, but it will only be used on the next boot. This is important to ensure that the
boot loader files are correctly signed, in addition to preparing the PCR 7 files.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: &lt;code&gt;systemd-pcrlock&lt;/code&gt; has several subcommands that allow you to &amp;ldquo;lock&amp;rdquo; a specific PCR. &amp;ldquo;Locking,&amp;rdquo; as used here,
means inspecting the events of each PCR and generating &lt;code&gt;.pcrlock&lt;/code&gt; files in the &lt;code&gt;/var/lib/pcrlock.d&lt;/code&gt; directory. For
example, &lt;code&gt;systemd-pcrlock lock-firmware-code&lt;/code&gt; generates the file
&lt;code&gt;/var/lib/pcrlock.d/250-firmware-code-early.pcrlock.d/generated.pcrlock&lt;/code&gt; with PCR 0 and 2 measurements. Lanzaboote uses
this system when you enable PCR 7 by running &lt;code&gt;systemd-pcrlock lock-secureboot-authority&lt;/code&gt; and &lt;code&gt;systemd-pcrlock lock-secureboot-policy&lt;/code&gt;. This is done by creating two oneshot systemd services: &lt;code&gt;systemd-pcrlock-secureboot-authority&lt;/code&gt;
and &lt;code&gt;systemd-pcrlock-secureboot-policy&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Reboot the system. The LUKS key is still tied only to PCR 7, and the disks will be decrypted automatically. After the
reboot, the policy file will have been created. You can verify if both PCRs are present in the file by running:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; jq &lt;span class="s1"&gt;&amp;#39;.pcrValues.[].pcr&amp;#39;&lt;/span&gt; /var/lib/systemd/pcrlock.json
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;4
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;7
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If this is correct, you can now remove the token from the disk headers that was tied only to PCR 7 and create a new one
using the policy via the generated file:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# remove previous token and enroll new token using the policy on disk 1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo systemd-cryptenroll --tpm2-device&lt;span class="o"&gt;=&lt;/span&gt;auto --wipe-slot&lt;span class="o"&gt;=&lt;/span&gt;tpm2 --tpm2-pcrlock&lt;span class="o"&gt;=&lt;/span&gt;/var/lib/systemd/pcrlock.json /dev/disk/by-label/NIXLUKS1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# remove previous token and enroll new token using the policy on disk 2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo systemd-cryptenroll --tpm2-device&lt;span class="o"&gt;=&lt;/span&gt;auto --wipe-slot&lt;span class="o"&gt;=&lt;/span&gt;tpm2 --tpm2-pcrlock&lt;span class="o"&gt;=&lt;/span&gt;/var/lib/systemd/pcrlock.json /dev/disk/by-label/NIXLUKS2
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;With this, you can reboot again. The disk should continue to unlock automatically, but this time it is using the policy
file, and therefore PCRs 4 and 7.&lt;/p&gt;
&lt;p&gt;The functionality is now complete. Whenever the boot loader files are updated, the policy will also be updated, and the
disk will be decrypted automatically without requiring any manual interaction. In the following sections, I will dive
deeper into what is happening.&lt;/p&gt;
&lt;h3 id="inspecting-tpm-events"&gt;
 &lt;a href="#inspecting-tpm-events" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;Inspecting TPM events&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;You can check TPM events by running &lt;code&gt;sudo systemd-pcrlock log&lt;/code&gt;, or just &lt;code&gt;sudo systemd-pcrlock&lt;/code&gt;. Unfortunately, the
command doesn&amp;rsquo;t allow showing events for a specific PCR, so you can do it using JSON and &lt;code&gt;jq&lt;/code&gt;. To see only PCR 4 events,
run:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo systemd-pcrlock log --json&lt;span class="o"&gt;=&lt;/span&gt;short 2&amp;gt;/dev/null &lt;span class="p"&gt;|&lt;/span&gt; jq &lt;span class="s1"&gt;&amp;#39;[.log[] | select(.pcr == 4)]&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To see the components registered in &lt;code&gt;systemd-pcrlock&lt;/code&gt;, use the &lt;code&gt;list-components&lt;/code&gt; command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; sudo systemd-pcrlock list-components
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;ID VARIANTS
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;240-secureboot-policy /var/lib/pcrlock.d/240-secureboot-policy.pcrlock.d/generated.pcrlock
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;350-action-efi-application /nix/store/f4s218swc4kw35apcqg9al8d7s287x7y-pcrlock.d/350-action-efi-application.pcrlock
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;400-secureboot-separator /nix/store/f4s218swc4kw35apcqg9al8d7s287x7y-pcrlock.d/400-secureboot-separator.pcrlock.d/300-0x00000000.pcrlock
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;500-separator /nix/store/f4s218swc4kw35apcqg9al8d7s287x7y-pcrlock.d/500-separator.pcrlock.d/300-0x00000000.pcrlock
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;620-secureboot-authority /var/lib/pcrlock.d/620-secureboot-authority.pcrlock.d/generated.pcrlock
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;630-bootloader /var/lib/pcrlock.d/630-bootloader.pcrlock.d/current.pcrlock
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;635-lanzaboote /var/lib/pcrlock.d/635-lanzaboote.pcrlock.d/7.pcrlock
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;These components are used to predict the values of each PCR. They are what &amp;ldquo;lock&amp;rdquo; the TPM. The idea is that
&lt;code&gt;systemd-pcrlock lock-*&lt;/code&gt; commands check TPM events and generate these components, along with Lanzaboote generating the
PCR 4 (boot loader) ones at every &lt;code&gt;nixos-rebuild&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Finally, you can check the measurements being used by running &lt;code&gt;systemd-pcrlock predict&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; sudo systemd-pcrlock predict --pcr&lt;span class="o"&gt;=&lt;/span&gt;4,7
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Event log record 0 (PCR 0, &amp;#34;Raw: 2\0008\000\000\000&amp;#34;) not matching any component.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Event log record 9 (PCR 1, &amp;#34;Raw: ACPI DATA&amp;#34;) not matching any component.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Event log record 13 (PCR 2, &amp;#34;Raw: \030\377\004\000&amp;#34;) not matching any component.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Event log record 27 (PCR 5, &amp;#34;GPT: disk 8118a150-5781-4514-94f0-517c327f4ca7&amp;#34;) not matching any component.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Event log record 40 (PCR 9, &amp;#34;Linux: kernel command line&amp;#34;) not matching any component.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Event log record 31 (PCR 11, &amp;#34;String: .linux&amp;#34;) not matching any component.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Event log record 39 (PCR 12, &amp;#34;String: Global credentials initrd&amp;#34;) not matching any component.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Event log record 44 (PCR 15, &amp;#34;cryptsetup:crypt_disk1:5a2876f5-9220-4040-8b56-7bc226c9d649&amp;#34;) not matching any component.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;3 combinations of components.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;PCR 4 (boot-loader-code) matches event log and fully consists of recognized measurements. Including in set of PCRs.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;PCR 7 (secure-boot-policy) matches event log and fully consists of recognized measurements. Including in set of PCRs.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;PCRs in protection mask: 4 (boot-loader-code), 7 (secure-boot-policy)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Results for PCR 4 (boot-loader-code):
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; sha256: 19d6c3ffa28c30062e84d8e90a357a6319ce921dfa15ee6618a5f9c38a5e9890
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; sha256: 58d68b2aa60262d9831fdd1b8b0f50fd30610162bf3ae7f19d4722e21ba24277
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; sha256: 480319eadf7ad52de18d44758204505cf34a4def787c762e1da3024a69b2ec8e
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Results for PCR 7 (secure-boot-policy):
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; sha256: cc1dc9cbd11e3ad0a695744ab152362d388e2e97f165117e3e45b8248f4a5078
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The initial lines show event records that were not recognized. Since &lt;code&gt;predict&lt;/code&gt; only generates predictions for PCRs whose
component coverage is complete, those PCRs are excluded from this specific policy. If we wanted to use them, some could
have components created by running &lt;code&gt;systemd-pcrlock lock-*&lt;/code&gt; commands, while others would need manual component creation.&lt;/p&gt;
&lt;p&gt;Lastly, let&amp;rsquo;s compare the PCR 7 logs used in the
&lt;a href="https://giggio.net/en/blog/nix-os-guia-de-instalacao-com-raid-1-criptografia-e-tpm-unlock-parte-5-destrancando-o-disco-com-tpm/#understanding-the-tpm"&gt;fifth post&lt;/a&gt;.
The only difference is the inclusion of the component field (which &amp;ldquo;locks&amp;rdquo; the PCR), highlighted below:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo systemd-pcrlock log --json&lt;span class="o"&gt;=&lt;/span&gt;short 2&amp;gt;/dev/null &lt;span class="p"&gt;|&lt;/span&gt; jq &lt;span class="s1"&gt;&amp;#39;[.log[] | select(.pcr == 7)]&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;pcr&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;pcrname&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;secure-boot-policy&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;event&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;efi-variable-driver-config&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;match&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;sha256&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;9f75b6823bff6af1024a4e2036719cdd548d3cbc2bf1de8e7ef4d0ed01f94bf9&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;phase&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;F&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;component&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;240-secureboot-policy&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;description&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Variable: dbx-d719b2cb-3d3a-4596-a3bc-dad00e67656f&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;pcr&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;pcrname&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;secure-boot-policy&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;event&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;efi-variable-authority&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;match&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;sha256&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;f35567246a92b2fcdd2901e4ad2febdfd80173f25527c5a73033bf100a8eb980&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;phase&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;F&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;component&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;620-secureboot-authority&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;description&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Authority: db-d719b2cb-3d3a-4596-a3bc-dad00e67656f&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="evaluating-the-luks-header-token"&gt;
 &lt;a href="#evaluating-the-luks-header-token" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;Evaluating the LUKS header token&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;We can also compare the LUKS header, which was updated when we ran &lt;code&gt;systemd-cryptenroll&lt;/code&gt; with the &lt;code&gt;--wipe-slot&lt;/code&gt; argument
and then &lt;code&gt;--tpm2-pcrlock&lt;/code&gt;. In the
&lt;a href="https://giggio.net/en/blog/nix-os-guia-de-instalacao-com-raid-1-criptografia-e-tpm-unlock-parte-5-destrancando-o-disco-com-tpm/#unlocking-the-disk-during-boot"&gt;fifth post&lt;/a&gt;,
the policy wasn&amp;rsquo;t used, so the &lt;code&gt;tpm-pcrs&lt;/code&gt; field (a list) had the value &lt;code&gt;7&lt;/code&gt;, and now it&amp;rsquo;s empty. The &lt;code&gt;tpm2-pcr-bank&lt;/code&gt;
field is no longer used, and the &lt;code&gt;tpm2_pcrlock_nv&lt;/code&gt; field now has a hash:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo cryptsetup luksDump --dump-json-metadata /dev/disk/by-label/NIXLUKS1 &lt;span class="p"&gt;|&lt;/span&gt; jq -r &lt;span class="s1"&gt;&amp;#39;.tokens.&amp;#34;0&amp;#34;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;type&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;systemd-tpm2&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;keyslots&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;1&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;tpm2-blob&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;lt;a base64 value&amp;gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;tpm2-pcrs&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;tpm2-policy-hash&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;lt;a hash&amp;gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;tpm2_pcrlock&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;tpm2_srk&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;lt;another base64 value&amp;gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;tpm2_pcrlock_nv&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;lt;yet another base64 value&amp;gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This last field, ending in &lt;code&gt;_nv&lt;/code&gt;, is especially important. It points to a non-volatile area of the TPM, which is not
erased when the computer turns off. The pcrlock policy data is stored there, and if the TPM is cleared, it forces the
recreation of the policy (more on this below).&lt;/p&gt;
&lt;h3 id="removing-the-tpm-policy"&gt;
 &lt;a href="#removing-the-tpm-policy" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;Removing the TPM policy&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;If you ever remove the NixOS TPM measurement settings (&lt;code&gt;boot.lanzaboote.measuredBoot&lt;/code&gt;), you will need to remove the
policy manually. It is registered in the aforementioned policy file, but also in the TPM&amp;rsquo;s non-volatile memory. This
should be automated by Lanzaboote (I opened &lt;a href="https://github.com/nix-community/lanzaboote/issues/600"&gt;an issue&lt;/a&gt;), but
from what I&amp;rsquo;ve seen, it isn&amp;rsquo;t yet. After running &lt;code&gt;nixos-rebuild switch&lt;/code&gt;, run:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# remove the policy from the TPM&amp;#39;s non-volatile memory and also the files:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# /var/lib/systemd/pcrlock.json and&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# /boot/loader/credentials/pcrlock.nixos.cred&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo systemd-pcrlock remove-policy
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Don&amp;rsquo;t forget to remove the TPM keyslot from the LUKS volume headers:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo systemd-cryptenroll --wipe-slot&lt;span class="o"&gt;=&lt;/span&gt;tpm2 /dev/disk/by-label/NIXLUKS1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo systemd-cryptenroll --wipe-slot&lt;span class="o"&gt;=&lt;/span&gt;tpm2 /dev/disk/by-label/NIXLUKS2
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="dealing-with-a-clearedwiped-tpm"&gt;
 &lt;a href="#dealing-with-a-clearedwiped-tpm" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;Dealing with a cleared/wiped TPM&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;If the TPM&amp;rsquo;s non-volatile memory is wiped, the reference to the TPM policy registered in the LUKS volume header will
become outdated, pointing to a record that no longer exists. This will force us to re-enroll the policy into the LUKS
header. You can test this safely by clearing the TPM directly from Linux. Run the following commands and reboot:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;nix-shell -p tpm2-tools
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo tpm2 clear
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;exit&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;As a result, the operation to decrypt the disk using the TPM during boot will fail, and you will need to decrypt using
your password. To re-enable automatic TPM decryption, you first need to remove the policy from the disk (the
&lt;code&gt;/var/lib/systemd/pcrlock.json&lt;/code&gt; and &lt;code&gt;/boot/loader/credentials/pcrlock.nixos.cred&lt;/code&gt; files—explained in the previous
section), run &lt;code&gt;nixos-rebuild boot&lt;/code&gt;, and reboot to recreate the wiped policy. Then, redo the LUKS header with the
&lt;code&gt;systemd-cryptenroll --wipe-slot&lt;/code&gt; and &lt;code&gt;systemd-cryptenroll --tpm2-pcrlock&lt;/code&gt; commands:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo systemd-pcrlock remove-policy
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo systemd-cryptenroll --wipe-slot&lt;span class="o"&gt;=&lt;/span&gt;tpm2 /dev/disk/by-label/NIXLUKS1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo systemd-cryptenroll --wipe-slot&lt;span class="o"&gt;=&lt;/span&gt;tpm2 /dev/disk/by-label/NIXLUKS2
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo nixos-rebuild boot
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# reboot, enter LUKS password&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;jq &lt;span class="s1"&gt;&amp;#39;.pcrValues.[].pcr&amp;#39;&lt;/span&gt; /var/lib/systemd/pcrlock.json &lt;span class="c1"&gt;# validate that the result is: 4 7&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo systemd-cryptenroll --tpm2-device&lt;span class="o"&gt;=&lt;/span&gt;auto --tpm2-pcrlock&lt;span class="o"&gt;=&lt;/span&gt;/var/lib/systemd/pcrlock.json /dev/disk/by-label/NIXLUKS1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo systemd-cryptenroll --tpm2-device&lt;span class="o"&gt;=&lt;/span&gt;auto --tpm2-pcrlock&lt;span class="o"&gt;=&lt;/span&gt;/var/lib/systemd/pcrlock.json /dev/disk/by-label/NIXLUKS2
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Reboot once more, and you should no longer be prompted for a password.&lt;/p&gt;
&lt;p&gt;You&amp;rsquo;ve already seen these commands in this article, so I won&amp;rsquo;t detail them further.&lt;/p&gt;
&lt;h3 id="conclusion"&gt;
 &lt;a href="#conclusion" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;Conclusion&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;With this, your operating system is now protected, and you have the convenience of a system that starts without
constantly asking for a decryption password. Better yet, since everything was done using NixOS, the configuration is
ready to be reused: if it worked once, it will work forever.&lt;/p&gt;
&lt;p&gt;The series was supposed to end here, but since I wrote this post, a few days have passed and I&amp;rsquo;ve used everything I
wrote here to migrate my own machine from Ubuntu to NixOS. I learned a lot and have a few more things to share, so the
series will have at least one more post.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;ve followed along this far, ran a proof of concept with the examples, or have ideas or criticisms, leave a
comment so I know!&lt;/p&gt;</description></item><item><title>NixOS: Installation Guide with RAID 1, encryption, and TPM Unlock (part 6 - Mitigating the volume swap attack)</title><link>https://giggio.net/en/blog/nix-os-guia-de-instalacao-com-raid-1-criptografia-e-tpm-unlock-parte-6-mitigando-o-ataque-de-troca-de-volume/</link><pubDate>Tue, 26 May 2026 10:00:00 -0300</pubDate><author>giggio@giggio.net (Giovanni Bassi)</author><guid>https://giggio.net/en/blog/nix-os-guia-de-instalacao-com-raid-1-criptografia-e-tpm-unlock-parte-6-mitigando-o-ataque-de-troca-de-volume/</guid><category>infra</category><description>&lt;p&gt;The NixOS disk is encrypted, but a careful LUKS volume swap attack can still be used to obtain the encryption master key.&lt;/p&gt;
&lt;p&gt;In this post, I show how to mitigate it!&lt;/p&gt;
&lt;p&gt;This is the sixth post in the series:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="https://giggio.net/en/blog/nix-os-guia-de-instalacao-com-raid-1-criptografia-e-tpm-unlock-parte-1/"&gt;Preparing the virtual machine and partitioning the disks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://giggio.net/en/blog/nix-os-guia-de-instalacao-com-raid-1-criptografia-e-tpm-unlock-parte-2-disko-luks-e-btrfs/"&gt;Disko, LUKS, and btrfs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://giggio.net/en/blog/nix-os-guia-de-instalacao-com-raid-1-criptografia-e-tpm-unlock-parte-3-instalando-o-so/"&gt;Installing the OS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://giggio.net/en/blog/nix-os-guia-de-instalacao-com-raid-1-criptografia-e-tpm-unlock-parte-4-secure-boot/"&gt;Enabling Secure Boot&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://giggio.net/en/blog/nix-os-guia-de-instalacao-com-raid-1-criptografia-e-tpm-unlock-parte-5-destrancando-o-disco-com-tpm/"&gt;Unlocking the disk with TPM&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mitigating the volume swap attack (this post)&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;As I said in the previous post, this kind of attack would not be carried out by an average person. The computer shop
technician who is repairing your laptop probably will not be able to read your LUKS encrypted disk and with the key
sealed with the help of PCR 7. But someone who knows what they are doing will find the weakness. This post and the next
one are for those who want to be fully protected, both from the technician at the shop around the corner and from a
hostile government or corporate agent. By the end of these two posts, the attacks described here will be mitigated,
significantly increasing security (keeping in mind that it is always prudent to consider other attack surfaces outside
these scenarios).&lt;/p&gt;
&lt;h3 id="the-problem"&gt;
 &lt;a href="#the-problem" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;The problem&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;The attack
&lt;a href="https://oddlama.org/blog/bypassing-disk-encryption-with-tpm2-unlock/"&gt;was described by Oddlama&lt;/a&gt;, and it basically
consists of removing the disk from the original computer, creating a fake encrypted partition with the same
identification data as the original partition, and then using that to obtain the key. He even describes how to do this
against NixOS.&lt;/p&gt;
&lt;p&gt;This is possible because the UKI (Unified Kernel Image — the binary loaded by UEFI that contains the kernel and the
initrd) is not encrypted — if it were, UEFI would not be able to load it. The LUKS volume header is also not encrypted,
since it must be read by the system in initrd so that the LUKS volume can be decrypted. It is possible to inspect what
will be loaded during boot and replace the LUKS volume with another one carefully prepared to steal the master key from
the original LUKS volume.&lt;/p&gt;
&lt;p&gt;I will not go into more detail because the explanation is long. You can read Oddlama’s post; it is quite interesting.
The important point is that this attack is only possible because the UKI does not validate the identity of the LUKS
volume, which allows it to be replaced.&lt;/p&gt;
&lt;h3 id="closing-the-breach"&gt;
 &lt;a href="#closing-the-breach" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;Closing the breach&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;To solve the problem, we just need to start validating the decrypted volume before proceeding, something that is not
being done yet and will be addressed using PCR 15 (&lt;code&gt;system-identity&lt;/code&gt;). In the previous post, I showed how to list the
hashes of all PCRs with &lt;code&gt;systemd-analyze pcrs&lt;/code&gt;. Notice that PCR 15 was zeroed out, meaning it had not been extended.
systemd can start writing to it by simply enabling &lt;code&gt;tpm2-measure-pcr=yes&lt;/code&gt; for &lt;code&gt;systemd-cryptsetup&lt;/code&gt; during the boot
process.&lt;/p&gt;
&lt;p&gt;To start writing to PCR 15 in the NixOS way, we need a Nix configuration. Fortunately, Oddlama himself provided the
solution, pointing to
&lt;a href="https://forge.lal.lol/patrick/nix-config/src/branch/master/modules/ensure-pcr.nix"&gt;a Forgejo repository by PatrickDaG&lt;/a&gt;,
with the file &lt;code&gt;ensure-pcr.nix&lt;/code&gt;, which
&lt;a href="https://codeberg.org/giggio/nixos_raid_btrfs_luks_tpm/src/branch/main/ensure-pcr.nix"&gt;has already been adapted in the repository&lt;/a&gt;
cloned into &lt;code&gt;/etc/nixos&lt;/code&gt;, and which makes it possible to read this PCR through a NixOS module.&lt;/p&gt;
&lt;p&gt;I incorporated this module into my solution, which made it very easy to apply. The example commit is
&lt;a href="https://codeberg.org/giggio/nixos_raid_btrfs_luks_tpm/commit/f690b8882df0993f05c3ad849fe42ebc7676e8d1"&gt;f690b88&lt;/a&gt;,
described as &amp;ldquo;Enable PCR15&amp;rdquo;, but we do not need to check it out; it is enough to update the configuration in
&lt;code&gt;/etc/nixos/configuration.nix&lt;/code&gt; (it is already in the code, just remove the comment):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-nix" data-lang="nix"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;systemIdentity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;enable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then apply it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;nixos-rebuild switch
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Restart the virtual machine, and after the reboot, confirm that the value of PCR 15 is present (the value below is just
an example — replaced by a sequence from 0 to 9):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; systemd-analyze pcrs &lt;span class="m"&gt;15&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;NR NAME SHA256
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;15 system-identity 0123456789012345678901234567890123456789012345678901234567890123
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That is the hash of the system identity (derived from the activated LUKS volume key); it is what will ensure that the
unlocked volume is the expected one, reducing the volume swap attack.&lt;/p&gt;
&lt;p&gt;The value found must be copied into the &lt;code&gt;pcr15&lt;/code&gt; field in the configuration. For example, using the fictitious sequence
above from 0 to 9:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-nix" data-lang="nix"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;systemIdentity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;enable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;pcr15&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;0123456789012345678901234567890123456789012345678901234567890123&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Change this value, reboot once more, and everything should continue working.&lt;/p&gt;
&lt;h3 id="how-this-solution-prevents-volume-swapping"&gt;
 &lt;a href="#how-this-solution-prevents-volume-swapping" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;How this solution prevents volume swapping&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;The explanation is in the Nix module defined in the file
&lt;a href="https://codeberg.org/giggio/nixos_raid_btrfs_luks_tpm/src/branch/main/ensure-pcr.nix"&gt;ensure-pcr.nix&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It creates a systemd service that starts extending PCR 15 when &lt;code&gt;systemIdentity.enable&lt;/code&gt; is enabled, running:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;systemd-cryptsetup attach crypt_disk1 /dev/disk/by-partlabel/NIXLUKS1 - &lt;span class="s1"&gt;&amp;#39;tpm2-device=auto,tpm2-measure-pcr=yes,discard&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And when the hash of PCR 15 has been defined in &lt;code&gt;systemIdentity.pcr15&lt;/code&gt;, it creates a &lt;code&gt;check-pcrs&lt;/code&gt; service that runs in
initrd and, if it fails, prevents boot from continuing. This service is very simple: it just compares the hash stored in
the configuration with the value found in PCR 15:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="k"&gt;$(&lt;/span&gt;systemd-analyze pcrs &lt;span class="m"&gt;15&lt;/span&gt; --json&lt;span class="o"&gt;=&lt;/span&gt;short &lt;span class="p"&gt;|&lt;/span&gt; jq -r &lt;span class="s2"&gt;&amp;#34;.[0].sha256&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt; !&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.systemIdentity.pcr15&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;PCR 15 check failed&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;PCR 15 check succeed&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;As this service is required by &lt;code&gt;sysroot.mount&lt;/code&gt;, if &lt;code&gt;check-pcrs&lt;/code&gt; fails the root disk cannot be mounted and the entire
boot fails, entering emergency mode.&lt;/p&gt;
&lt;h3 id="what-if-the-boot-fails"&gt;
 &lt;a href="#what-if-the-boot-fails" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;What if the boot fails?&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;If the value of PCR 15 changes for any reason, the boot will fail and enter emergency mode. In that case, you will need
to log in with the emergency password configured in initrd and run:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;systemctl disable check-pcrs
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;systemctl default
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: see the &lt;code&gt;boot.initrd.systemd.emergencyAccess&lt;/code&gt; attribute in &lt;code&gt;configuration.nix&lt;/code&gt;, that is what defines the
emergency initrd password (using a hash of the password). To create the password as &lt;code&gt;test&lt;/code&gt;, run (replace the salt,
&lt;code&gt;xyz&lt;/code&gt;, and the password):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;openssl passwd -6 -salt xyz &lt;span class="nb"&gt;test&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Tip&lt;/strong&gt;: I recommend that you test this by changing the configuration value to the wrong hash, running &lt;code&gt;switch&lt;/code&gt;, and
confirming that the boot failed. Then test the commands above: you will need to provide the initrd emergency password,
disable the &lt;code&gt;check-pcrs&lt;/code&gt; service, and continue the boot. In the worst-case scenario, if you cannot do that, you can
choose the previous configuration from the boot menu. After logging in again, set the PCR 15 value in the configuration
back to the correct one, run a new &lt;code&gt;switch&lt;/code&gt; and reboot, making sure that boot works again without issues.&lt;/p&gt;
&lt;h3 id="is-it-secure"&gt;
 &lt;a href="#is-it-secure" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;Is it secure?&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;Again: not yet.&lt;/p&gt;
&lt;p&gt;The attack described by Oddlama no longer works, but there is still another one: since we only bind the disk encryption
to PCRs 7 and 15, it is still possible to replace the operating system with another one and use the TPM to decrypt the
disk. We will solve that in the next post.&lt;/p&gt;</description></item><item><title>NixOS: Installation Guide with RAID 1, encryption, and TPM Unlock (part 5 - unlocking the disk with TPM)</title><link>https://giggio.net/en/blog/nix-os-guia-de-instalacao-com-raid-1-criptografia-e-tpm-unlock-parte-5-destrancando-o-disco-com-tpm/</link><pubDate>Mon, 18 May 2026 11:00:00 -0300</pubDate><author>giggio@giggio.net (Giovanni Bassi)</author><guid>https://giggio.net/en/blog/nix-os-guia-de-instalacao-com-raid-1-criptografia-e-tpm-unlock-parte-5-destrancando-o-disco-com-tpm/</guid><category>infra</category><description>&lt;p&gt;At last, we are going to automatically decrypt the NixOS disk using the TPM!&lt;/p&gt;
&lt;p&gt;This is the fifth post in the series:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="https://giggio.net/en/blog/nix-os-guia-de-instalacao-com-raid-1-criptografia-e-tpm-unlock-parte-1/"&gt;Preparing the virtual machine and partitioning the disks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://giggio.net/en/blog/nix-os-guia-de-instalacao-com-raid-1-criptografia-e-tpm-unlock-parte-2-disko-luks-e-btrfs/"&gt;Disko, LUKS, and btrfs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://giggio.net/en/blog/nix-os-guia-de-instalacao-com-raid-1-criptografia-e-tpm-unlock-parte-3-instalando-o-so/"&gt;Installing the OS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://giggio.net/en/blog/nix-os-guia-de-instalacao-com-raid-1-criptografia-e-tpm-unlock-parte-4-secure-boot/"&gt;Enabling Secure Boot&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Unlocking the disk with TPM (this post)&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Up to now, every boot has required the LUKS password to decrypt the disk. But if the machine’s integrity has not been
compromised, there is no problem in doing this automatically. But how?&lt;/p&gt;
&lt;p&gt;Now it is time to orchestrate everything we have put together so far, connecting LUKS, Secure Boot, and the TPM.&lt;/p&gt;
&lt;h3 id="why-this-is-safe"&gt;
 &lt;a href="#why-this-is-safe" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;Why this is safe&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;Assuming the machine has not been compromised, when the operating system comes up, the only safe way to access it is
with valid credentials — assuming the system has no exploitable vulnerability. That means logging in through the
console, SSH, the graphical interface, or a serial connection. In practice, that means that without a login password
there is no access to the computer, and no privilege is gained.&lt;/p&gt;
&lt;p&gt;Everything works based on this principle: an uncompromised and intact system protects itself, and if it is compromised
(firmware modification, disk removal, etc), encryption protects the data.&lt;/p&gt;
&lt;h3 id="understanding-the-tpm"&gt;
 &lt;a href="#understanding-the-tpm" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;Understanding the TPM&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;The TPM (Trusted Platform Module) is a dedicated processor that stores, in an inviolable way, measurements taken of the
hardware and software running on the computer. These measurements are stored in &lt;strong&gt;PCRs&lt;/strong&gt; (Platform Configuration
Registers), whose values can be extended, but never set directly. Every value sent to the TPM extends the previous
value and produces a new one. If we were to model this as a formula, it would be something like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;PCR = HASH(PCR || new_measurement)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In other words, the PCR hash is used together with the new measured value to create the new hash.&lt;/p&gt;
&lt;p&gt;You can see the hashes of each PCR on your running TPM (fictitious values):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; systemd-analyze pcrs
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;NR NAME SHA256
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; 0 platform-code 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; 1 platform-config 123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; 2 external-code 23456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef01
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; 3 external-config 3456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef012
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; 4 boot-loader-code 456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; 5 boot-loader-config 56789abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; 6 host-platform 6789abcdef0123456789abcdef0123456789abcdef0123456789abcdef012345
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; 7 secure-boot-policy 789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; 8 - 89abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; 9 kernel-initrd 9abcdef0123456789abcdef0123456789abcdef0123456789abcdef012345678
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;10 ima abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;11 kernel-boot 0000000000000000000000000000000000000000000000000000000000000000
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;12 kernel-config 0000000000000000000000000000000000000000000000000000000000000000
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;13 sysexts 0000000000000000000000000000000000000000000000000000000000000000
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;14 shim-policy bcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789a
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;15 system-identity 0000000000000000000000000000000000000000000000000000000000000000
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;16 debug 0000000000000000000000000000000000000000000000000000000000000000
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;17 - ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;18 - ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;19 - ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;20 - ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;21 - ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;22 - ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;23 application-support 0000000000000000000000000000000000000000000000000000000000000000
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Each PCR is independent and is extended freely, without depending on the others. On compatible TPMs there are 24 PCRs;
the &lt;a href="https://uapi-group.org/"&gt;UAPI (Linux Userspace API Group)&lt;/a&gt; in
&lt;a href="https://uapi-group.org/specifications/specs/linux_tpm_pcr_registry/"&gt;its specifications&lt;/a&gt; considers 0–7 to be firmware
and 8–15 to be the range available to the operating system, although the systemd ecosystem also uses specific PCRs for
additional measurements, and PCRs 16–23 are also usable.&lt;/p&gt;
&lt;p&gt;When the computer powers on, all PCRs start out with no value. The firmware then begins extending the PCRs. For example,
PCR 0 depends on the software running in the firmware — what we normally call the BIOS. When you “update the BIOS”,
PCR 0 changes. PCR 1 may change if you replace a hardware component. PCR 4 is tied to the operating system; it measures
the boot loader and additional drivers. And so on.&lt;/p&gt;
&lt;p&gt;PCR 7 depends on the Secure Boot state. Its values are commonly extended using the keys enrolled in Secure Boot, as well
as the Secure Boot Authority. It is this PCR that we will use to unlock the disk, at this moment.&lt;/p&gt;
&lt;p&gt;I will come back to PCRs in the next posts, but it is important to understand what they are for. If you decide to use
what I am proposing in these posts, this knowledge may be useful if you run into trouble.&lt;/p&gt;
&lt;p&gt;You can see all the events that led to PCR extensions by running &lt;code&gt;sudo systemd-pcrlock log&lt;/code&gt;. Using &lt;code&gt;jq&lt;/code&gt;, you can show
only the events that led to PCR 7:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo /run/current-system/systemd/lib/systemd/systemd-pcrlock log --json&lt;span class="o"&gt;=&lt;/span&gt;short 2&amp;gt;/dev/null &lt;span class="p"&gt;|&lt;/span&gt; jq &lt;span class="s1"&gt;&amp;#39;[.log[] | select(.pcr == 7)]&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The result, simplified so it does not get too long, is this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;pcr&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;pcrname&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;secure-boot-policy&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;event&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;efi-variable-driver-config&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;match&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;sha256&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;9f75b6823bff6af1024a4e2036719cdd548d3cbc2bf1de8e7ef4d0ed01f94bf9&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;phase&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;F&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;component&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;description&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Variable: dbx-d719b2cb-3d3a-4596-a3bc-dad00e67656f&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;pcr&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;pcrname&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;secure-boot-policy&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;event&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;efi-variable-authority&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;match&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;sha256&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;f35567246a92b2fcdd2901e4ad2febdfd80173f25527c5a73033bf100a8eb980&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;phase&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;F&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;component&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;description&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Authority: db-d719b2cb-3d3a-4596-a3bc-dad00e67656f&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Note that the hash value, stored in the &lt;code&gt;sha256&lt;/code&gt; field, changes with each event. When the TPM receives a request to
decrypt a value, its state must match exactly what was used when the value was encrypted (this is done through TPM
policies). That means a value encrypted early in the boot process may no longer be decryptable at the end of the boot
process, if the PCR has been extended again. This is especially useful for this very reason: allowing a value to be
accessible only at a specific point in the boot sequence, such as during &lt;code&gt;initrd&lt;/code&gt; startup (PCR 11 has measurements that
make this possible).&lt;/p&gt;
&lt;p&gt;Systemd &lt;a href="https://systemd.io/TPM2_PCR_MEASUREMENTS/"&gt;documents&lt;/a&gt; how it uses PCRs, and since nowadays there is practically
no Linux without systemd, it is worth understanding. This document will be useful later, when we look more closely at
PCR 15 (system identity).&lt;/p&gt;
&lt;h3 id="unlocking-the-disk-during-boot"&gt;
 &lt;a href="#unlocking-the-disk-during-boot" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;Unlocking the disk during boot&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;Only one command is needed to make everything happen. In our case, since we have two disks and two partitions, we run
the command once for each of them (you will need to enter your LUKS password for the process to complete):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo systemd-cryptenroll --tpm2-device&lt;span class="o"&gt;=&lt;/span&gt;auto --tpm2-pcrs&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;7&lt;/span&gt; /dev/disk/by-label/NIXLUKS1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo systemd-cryptenroll --tpm2-device&lt;span class="o"&gt;=&lt;/span&gt;auto --tpm2-pcrs&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;7&lt;/span&gt; /dev/disk/by-label/NIXLUKS2
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;When this command runs, the volume master key is obtained using your password. It is then encrypted (sealed) using the
TPM, and this sealed value is stored in the LUKS volume header. In this case, only PCR 7 is being used to seal the
value, meaning only the Secure Boot state. That means if the Secure Boot state changes (if it is reset, or if a new key
is removed or added), the TPM’s PCR 7 state will also change, and the disk will no longer be decrypted during boot. In
this post, the goal is to tie automatic disk decryption to the Secure Boot state, and that is what we have done.&lt;/p&gt;
&lt;p&gt;You can see the details stored in the LUKS volume header by running the following command after &lt;code&gt;systemd-cryptenroll&lt;/code&gt;
has been executed:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo cryptsetup luksDump /dev/disk/by-label/NIXLUKS1
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Notice the &lt;code&gt;Tokens&lt;/code&gt; field, with the first one being &lt;code&gt;systemd-tpm2&lt;/code&gt;, with a &lt;code&gt;Keyslot&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Tokens:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 0: systemd-tpm2
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Keyslot: 1
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To see more details about the sealed value, run:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo cryptsetup luksDump --dump-json-metadata /dev/disk/by-label/NIXLUKS1 &lt;span class="p"&gt;|&lt;/span&gt; jq -r &lt;span class="s1"&gt;&amp;#39;.tokens.&amp;#34;0&amp;#34;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The result will look something like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;type&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;systemd-tpm2&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;keyslots&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;1&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;tpm2-blob&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;lt;a base64 value&amp;gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;tpm2-pcrs&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;tpm2-pcr-bank&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;sha256&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;tpm2-policy-hash&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;lt;a hash&amp;gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;tpm2_srk&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;lt;another base64 value&amp;gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;tpm2-blob&lt;/code&gt; contains the value encrypted by the TPM (plus metadata). Using the tools from the
&lt;a href="https://github.com/tpm2-software/tpm2-tools"&gt;tpm2-tools&lt;/a&gt; package and root access, you could recover the master password
from this value. That is not a security problem: the system is already running and the disk is already decrypted. The
purpose of TPM protection is not to protect a healthy running system from its administrator, but to protect against
integrity violations that happen before, during, and after boot.&lt;/p&gt;
&lt;p&gt;With just these two commands, the TPM will start unlocking both LUKS volumes. Reboot to confirm that it no longer asks
for the password. If it does, inspect the boot logs with &lt;code&gt;sudo journalctl --boot&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="is-it-secure"&gt;
 &lt;a href="#is-it-secure" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;Is it secure?&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;Not yet.&lt;/p&gt;
&lt;p&gt;There are two vulnerabilities, and I will address them in the next posts. Neither is trivial, but someone who
understands how all of this works could gain access to the data in a short amount of time. None of the work done so far
will be lost; we will build on the current solution. The good news is that both issues are easy to fix.&lt;/p&gt;
&lt;p&gt;That said, it is fair assume that most people, including technicians who are not systems penetration experts, would no
longer be able to read the disk of a computer encrypted this way. In other words, sending a laptop for repair with this
configuration would probably not present a risk. But that is still not good enough.&lt;/p&gt;
&lt;p&gt;In the next post we will start closing the gaps.&lt;/p&gt;</description></item><item><title>NixOS: Installation Guide with RAID 1, encryption, and TPM Unlock (part 4 - Secure Boot)</title><link>https://giggio.net/en/blog/nix-os-guia-de-instalacao-com-raid-1-criptografia-e-tpm-unlock-parte-4-secure-boot/</link><pubDate>Thu, 14 May 2026 11:00:00 -0300</pubDate><author>giggio@giggio.net (Giovanni Bassi)</author><guid>https://giggio.net/en/blog/nix-os-guia-de-instalacao-com-raid-1-criptografia-e-tpm-unlock-parte-4-secure-boot/</guid><category>infra</category><description>&lt;p&gt;In the fourth post of our series, we are going to configure Secure Boot to ensure that only trusted operating
systems can be executed.&lt;/p&gt;
&lt;p&gt;This is the fourth post of the series:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="https://giggio.net/en/blog/nix-os-guia-de-instalacao-com-raid-1-criptografia-e-tpm-unlock-parte-1/"&gt;Preparing the virtual machine and partitioning the disks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://giggio.net/en/blog/nix-os-guia-de-instalacao-com-raid-1-criptografia-e-tpm-unlock-parte-2-disko-luks-e-btrfs/"&gt;Disko, LUKS, and btrfs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://giggio.net/en/blog/nix-os-guia-de-instalacao-com-raid-1-criptografia-e-tpm-unlock-parte-3-instalando-o-so/"&gt;Installing the OS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Enabling Secure Boot (this post)&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Secure Boot is a fundamental component for tightening the security, especially when using the TPM to automatically
decrypt the disk. I will explain what it is, what it’s for, and how to implement it in NixOS using Lanzaboote.&lt;/p&gt;
&lt;h3 id="understanding-secure-boot"&gt;
 &lt;a href="#understanding-secure-boot" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;Understanding Secure Boot&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;Secure Boot is a protocol that is part of the UEFI (Unified Extensible Firmware Interface). When enabled, it verifies if
the operating system&amp;rsquo;s bootloader has a valid digital signature recognized by the keys registered in the motherboard&amp;rsquo;s
firmware.&lt;/p&gt;
&lt;p&gt;This prevents boot-level malware (rootkits) or unauthorized operating systems from loading. Additionally, the TPM uses
the Secure Boot state as one of the metrics to decide whether or not to release the disk encryption keys.&lt;/p&gt;
&lt;p&gt;There are three main ways to manage Secure Boot keys:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Factory default keys:&lt;/strong&gt; Usually the Microsoft keys that come with almost every computer;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Custom keys (User Mode):&lt;/strong&gt; You remove the Microsoft keys and install only your own;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hybrid Mode:&lt;/strong&gt; You keep the Microsoft keys (for dual-booting with Windows) and add your own.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If you don&amp;rsquo;t intend to run Windows, the second option is the most restrictive and secure. If you need dual boot, the
third is mandatory. It&amp;rsquo;s worth noting that NixOS does not have a bootloader signed by Microsoft (unlike Ubuntu or
Fedora), so we always need to generate our own keys.&lt;/p&gt;
&lt;p&gt;And why Microsoft keys, specifically? Because they were the first company to push Secure Boot as mandatory, and they
hold the largest share of the PC market.
&lt;a href="https://en.wikipedia.org/wiki/UEFI#Secure_Boot_criticism"&gt;There is quite a bit of controversy regarding this&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The important thing for our setup is knowing that once you define how Secure Boot will be used, it cannot be changed
without breaking the automatic decryption process (which will be done with the help of the TPM). In our case, we will
use the third option (mixing Microsoft keys with our own). If Secure Boot is turned off on the machine or a new platform
key is loaded, the TPM will refuse to decrypt the disk. I’ll explain this in future posts, so for now, what matters is
enabling Secure Boot.&lt;/p&gt;
&lt;h3 id="putting-the-firmware-into-setup-mode"&gt;
 &lt;a href="#putting-the-firmware-into-setup-mode" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;Putting the Firmware into Setup Mode&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;In the first post, we cleared the keys in the VM&amp;rsquo;s boot menu to enter Setup Mode. This is essential: for us to write
our keys via software, the firmware must be &amp;ldquo;open&amp;rdquo; to new signatures.&lt;/p&gt;
&lt;p&gt;Confirm the status by running:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; sudo bootctl status
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Secure Boot: disabled (setup)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If &lt;code&gt;disabled (setup)&lt;/code&gt; appears, we are ready to proceed.&lt;/p&gt;
&lt;h3 id="generating-keys-with-sbctl"&gt;
 &lt;a href="#generating-keys-with-sbctl" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;Generating Keys with sbctl&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;We will use &lt;a href="https://github.com/Foxboron/sbctl/"&gt;sbctl&lt;/a&gt; to create and register our keys. Since it isn&amp;rsquo;t installed by
default, let&amp;rsquo;s use &lt;code&gt;nix-shell&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;nix-shell -p sbctl
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: In NixOS, you can bring any application into your terminal&amp;rsquo;s context using &lt;code&gt;nix-shell&lt;/code&gt;. With this, the &lt;code&gt;sbctl&lt;/code&gt;
command becomes available in a subshell — to exit, just run &lt;code&gt;exit&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;To generate the keys, we will run the &lt;code&gt;create-keys&lt;/code&gt; subcommand:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo sbctl create-keys
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The keys will be generated in &lt;code&gt;/var/lib/sbctl/&lt;/code&gt;. You can see the structure with the &lt;code&gt;tree&lt;/code&gt; command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; tree /var/lib/sbctl/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;/var/lib/sbctl/
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;├── GUID
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;└── keys
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; ├── db
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; │   ├── db.key
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; │   └── db.pem
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; ├── KEK
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; │   ├── KEK.key
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; │   └── KEK.pem
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; └── PK
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; ├── PK.key
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; └── PK.pem
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Install the keys into the machine&amp;rsquo;s firmware with &lt;code&gt;enroll-keys&lt;/code&gt;. This will use the keys we created earlier with &lt;code&gt;sbctl&lt;/code&gt;.
With the &lt;code&gt;--microsoft&lt;/code&gt; argument, Microsoft&amp;rsquo;s platform keys will also be installed:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo sbctl enroll-keys --microsoft
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Verify that Secure Boot has been re-enabled with:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo bootctl status
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It should show &lt;code&gt;Secure Boot: enabled (user)&lt;/code&gt;. It might not show as enabled yet until the next boot, showing up as
&lt;code&gt;Secure Boot: disabled&lt;/code&gt;, but without &lt;code&gt;setup&lt;/code&gt;. But don&amp;rsquo;t reboot just yet!&lt;/p&gt;
&lt;h3 id="signing-the-boot-with-lanzaboote"&gt;
 &lt;a href="#signing-the-boot-with-lanzaboote" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;Signing the Boot with Lanzaboote&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;At this point, if you restart the machine, NixOS will no longer boot, as the NixOS bootloader is not yet signed with the
Secure Boot keys we created in the previous step. That’s where &lt;a href="https://github.com/nix-community/lanzaboote"&gt;Lanzaboote&lt;/a&gt;
comes in. It automates the signing of NixOS generations. It replaces the default &lt;code&gt;systemd-boot&lt;/code&gt; manager with one that
handles the keys generated by &lt;code&gt;sbctl&lt;/code&gt;. Once enabled, whenever a new bootloader is created in the &lt;code&gt;/boot&lt;/code&gt; directory, it
will be signed with the keys located in &lt;code&gt;/var/lib/sbctl&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;To enable Lanzaboote and then sign the files, first go to the configuration directory that was copied in previous posts
to &lt;code&gt;/etc/nixos&lt;/code&gt;, and check out the correct commit. Be careful not to forget the &lt;code&gt;git checkout&lt;/code&gt;, or it won&amp;rsquo;t work:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /etc/nixos
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# ensuring the current user owns the files:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo chown -R giggio:users .
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# checkout with the message &amp;#34;Enable lanzaboote&amp;#34;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git checkout dab0fd4
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The change in your &lt;code&gt;configuration.nix&lt;/code&gt; disables the native &lt;code&gt;systemd-boot&lt;/code&gt; and activates &lt;code&gt;lanzaboote&lt;/code&gt;, pointing to the
keys directory:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-nix" data-lang="nix"&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;boot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loader&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;systemd-boot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;enable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;boot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lanzaboote&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;enable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;pkiBundle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;/var/lib/sbctl&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The Lanzaboote configuration was already present; it was just disabled. And with it enabled, &lt;code&gt;systemd-boot&lt;/code&gt; can be
disabled. Note also that the keys directory, defined in the &lt;code&gt;pkiBundle&lt;/code&gt; attribute, is already correctly pointed to
&lt;code&gt;/var/lib/sbctl&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s all; now just install the new configurations, which take effect immediately and will also be effective on the
next boot:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo nixos-rebuild switch
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="validating-the-signature"&gt;
 &lt;a href="#validating-the-signature" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;Validating the Signature&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;To ensure Lanzaboote did its job and signed the &lt;code&gt;.efi&lt;/code&gt; binaries, run:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; sudo sbctl verify
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Verifying file database and EFI images in /boot...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;✓ /boot/EFI/BOOT/BOOTX64.EFI is signed
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;✓ /boot/EFI/Linux/nixos-generation-1-xxx.efi is signed
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;✓ /boot/EFI/Linux/nixos-generation-2-yyy.efi is signed
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;✗ /boot/EFI/nixos/kernel-7.0-zzz.efi is not signed
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;✓ /boot/EFI/systemd/systemd-bootx64.efi is signed
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Note about the Kernel:&lt;/strong&gt; It is normal to see an &lt;code&gt;✗&lt;/code&gt; on the isolated kernel file. What matters are the files inside
&lt;code&gt;/boot/EFI/&lt;/code&gt; and the boot managers. Lanzaboote creates a &lt;strong&gt;Unified Kernel Image (UKI)&lt;/strong&gt;, which bundles the kernel and
the initrd into a single signed executable that the UEFI can run directly.&lt;/p&gt;
&lt;p&gt;Now you can reboot. If everything went well, the system will come up normally and &lt;code&gt;bootctl status&lt;/code&gt; will confirm: &lt;code&gt;Secure Boot: enabled (user)&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The machine still asks for the password to decrypt the disk. This is expected, as we haven&amp;rsquo;t yet implemented the
structure for automatic decryption with the TPM.&lt;/p&gt;
&lt;h3 id="troubleshooting-secure-boot"&gt;
 &lt;a href="#troubleshooting-secure-boot" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;Troubleshooting Secure Boot&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;If something goes wrong and the VM won&amp;rsquo;t start, you can reset the NVRAM (the VM&amp;rsquo;s &amp;ldquo;BIOS&amp;rdquo;). On your host, turn off the VM
and run:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# on the host:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;virsh start &amp;lt;vm_name&amp;gt; --reset-nvram
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This will start the virtual machine normally, but with all the firmware boot settings lost, including Secure Boot
settings.&lt;/p&gt;
&lt;p&gt;The following commands will re-apply the boot settings to the firmware&amp;rsquo;s non-volatile memory and the disk:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# on the vm:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# registering platform keys in the firmware again:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo sbctl enroll-keys --microsoft
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# installing the boot menu option:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo bootctl install
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# re-signing the UKIs:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo nixos-rebuild switch
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Why run switch again&lt;/strong&gt;: When running &lt;code&gt;bootctl install&lt;/code&gt;, the files &lt;code&gt;/boot/EFI/systemd/systemd-bootx64.efi&lt;/code&gt; and
&lt;code&gt;/boot/EFI/BOOT/BOOTX64.EFI&lt;/code&gt; will not be signed, so it is necessary to run &lt;code&gt;switch&lt;/code&gt; so that these two files become
signed again.&lt;/p&gt;
&lt;h3 id="declarative-approach"&gt;
 &lt;a href="#declarative-approach" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;Declarative Approach&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;Lanzaboote allows generating platform keys automatically via Nix config:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-nix" data-lang="nix"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;boot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lanzaboote&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;autoGenerateKeys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;enable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;autoEnrollKeys&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;enable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;autoReboot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is described in more detail in the
&lt;a href="https://github.com/nix-community/lanzaboote/blob/079c608988c2747db3902c9de033572cd50e8656/docs/explanation/automatic-provisioning.md"&gt;docs on automatic provisioning&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;While practical for testing, I don&amp;rsquo;t recommend using this in production systems. The ideal approach is to generate keys
manually (as I demonstrated here) and store them securely somewhere. A good option is to use
&lt;a href="https://github.com/Mic92/sops-nix/"&gt;sops-nix&lt;/a&gt;, keeping the keys encrypted within the repository itself. The
inconvenience would be during application, where you would always need the keys or devices to decrypt the &lt;code&gt;sops&lt;/code&gt; secrets
at every &lt;code&gt;nixos-rebuild&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="next-up-using-tpm-to-decrypt-the-disk"&gt;
 &lt;a href="#next-up-using-tpm-to-decrypt-the-disk" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;Next Up: Using TPM to Decrypt the Disk&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;Secure Boot is active. The disk is encrypted. But we still have to type the LUKS password at every boot.&lt;/p&gt;
&lt;p&gt;If someone tries to start another operating system, it will need to be signed with your platform key or Microsoft&amp;rsquo;s. You
can make the system more secure by removing the &lt;code&gt;--microsoft&lt;/code&gt; option from the &lt;code&gt;enroll-keys&lt;/code&gt; command and not allowing any
other operating system.&lt;/p&gt;
&lt;p&gt;In the next post, we will configure the &lt;strong&gt;TPM (Trusted Platform Module)&lt;/strong&gt; so that it &amp;ldquo;measures&amp;rdquo; the Secure Boot state
and releases the encryption key automatically if the system hasn&amp;rsquo;t been tampered with. Maximum security with
convenience.&lt;/p&gt;
&lt;p&gt;See you then!&lt;/p&gt;</description></item><item><title>NixOS: Installation Guide with RAID 1, encryption, and TPM Unlock (part 3 - Installing the OS)</title><link>https://giggio.net/en/blog/nix-os-guia-de-instalacao-com-raid-1-criptografia-e-tpm-unlock-parte-3-instalando-o-so/</link><pubDate>Mon, 11 May 2026 11:00:00 -0300</pubDate><author>giggio@giggio.net (Giovanni Bassi)</author><guid>https://giggio.net/en/blog/nix-os-guia-de-instalacao-com-raid-1-criptografia-e-tpm-unlock-parte-3-instalando-o-so/</guid><category>infra</category><description>&lt;p&gt;I’m continuing my journey of setting up a NixOS machine with secure and redundant storage. In this post, we’re going to
perform the actual OS installation!&lt;/p&gt;
&lt;p&gt;This is the third post in the series:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="https://giggio.net/en/blog/nix-os-guia-de-instalacao-com-raid-1-criptografia-e-tpm-unlock-parte-1/"&gt;Preparing the virtual machine and partitioning the disks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://giggio.net/en/blog/nix-os-guia-de-instalacao-com-raid-1-criptografia-e-tpm-unlock-parte-2-disko-luks-e-btrfs/"&gt;Disko, LUKS, and btrfs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Installing NixOS (this post)&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;At this point, we have a virtual machine with formatted disks and the file system configured and mounted, ready to
receive the NixOS binaries.&lt;/p&gt;
&lt;h3 id="why-arent-we-using-disko-install"&gt;
 &lt;a href="#why-arent-we-using-disko-install" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;Why aren&amp;rsquo;t we using disko-install?&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;The Disko project includes a tool called
&lt;a href="https://github.com/nix-community/disko/blob/32f4236bfc141ae930b5ba2fb604f561fed5219d/docs/disko-install.md"&gt;disko-install&lt;/a&gt;
that allows you to format partitions and install NixOS in a single step. The tool is excellent and even allows for disk
parameterization via the command line.&lt;/p&gt;
&lt;p&gt;However, &lt;a href="https://github.com/nix-community/disko/issues/947#issuecomment-4297662288"&gt;a bug&lt;/a&gt; prevents its use when the
machine&amp;rsquo;s RAM is smaller than the size of the system being created. Until this is resolved, we will follow the process
in two stages: disk preparation (previous post) and the manual installation of the operating system (this post).&lt;/p&gt;
&lt;p&gt;But&amp;hellip; if that bug is ever fixed, the command would look like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo nix --experimental-features &lt;span class="s2"&gt;&amp;#34;nix-command flakes&amp;#34;&lt;/span&gt; run &lt;span class="s1"&gt;&amp;#39;github:nix-community/disko/latest#disko-install&amp;#39;&lt;/span&gt; -- &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --flake ~/nixos#nixos3 --disk disk1 /dev/vda --disk disk2 /dev/vdb
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="installing-the-operating-system"&gt;
 &lt;a href="#installing-the-operating-system" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;Installing the operating system&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;We are starting from the final state of the previous post, with the system mounted at &lt;code&gt;/mnt&lt;/code&gt;. If you restarted the
virtual machine, just run the following command to mount them again without needing to reformat everything:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo nix --experimental-features &lt;span class="s2"&gt;&amp;#34;nix-command flakes&amp;#34;&lt;/span&gt; run github:nix-community/disko/latest -- &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --mode mount ~/nixos/disko.nix
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;With everything mounted, we start the NixOS installation by pointing to our flake:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo nixos-install --root /mnt --no-root-passwd --flake ~/nixos#nixos3
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This command will take a few minutes. NixOS will download and build the entire system from scratch within the &lt;code&gt;/mnt&lt;/code&gt;
directory.&lt;/p&gt;
&lt;h4 id="configuration-persistence"&gt;
 &lt;a href="#configuration-persistence" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;Configuration Persistence&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h4&gt;
&lt;p&gt;To manage your system in the future (using &lt;code&gt;nixos-rebuild&lt;/code&gt;), we need to move our configuration files into the new system
by copying the repository to your &lt;code&gt;/etc/nixos&lt;/code&gt; directory, which is mounted at &lt;code&gt;/mnt/etc/nixos&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo cp -R ~/nixos /mnt/etc/nixos
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="setting-the-user-password"&gt;
 &lt;a href="#setting-the-user-password" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;Setting the User Password&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h4&gt;
&lt;p&gt;Although I left a default password in the settings (&lt;code&gt;luks&lt;/code&gt;) to make the lab easier, let&amp;rsquo;s define the password manually.
We will use &lt;code&gt;nixos-enter&lt;/code&gt;, which creates a chroot environment in the new system:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo nixos-enter --root /mnt -c &lt;span class="s2"&gt;&amp;#34;passwd giggio&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: &lt;code&gt;nixos-enter&lt;/code&gt; is extremely useful for maintenance. It allows you to &amp;ldquo;enter&amp;rdquo; the installed system even before
the first boot to fix configurations or reset passwords.&lt;/p&gt;
&lt;p&gt;NixOS is installed! At this stage, we have partitioned and encrypted disks with a password entered during boot, the
btrfs file system is formatted, and the operating system is installed. Everything should work; shut down the VM, remove
the ISO from your settings, and start the VM again.&lt;/p&gt;
&lt;h3 id="first-access-and-ssh"&gt;
 &lt;a href="#first-access-and-ssh" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;First Access and SSH&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;After booting, log in with the user &lt;code&gt;giggio&lt;/code&gt; and the password defined via &lt;code&gt;passwd&lt;/code&gt; in the previous step.&lt;/p&gt;
&lt;p&gt;To access via SSH from your host machine, remember to clear the old key (from the installation environment) from your
&lt;code&gt;known_hosts&lt;/code&gt; file (created during the first access to the VM):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ssh-keygen -R &amp;lt;ip&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now, connect with the new user:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ssh giggio@&amp;lt;ip&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="what-do-we-have-so-far"&gt;
 &lt;a href="#what-do-we-have-so-far" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;What do we have so far?&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;If you&amp;rsquo;ve never used NixOS, now is the time to experiment. In practice, it’s a modern Linux with GNOME. You’ll see that
it runs with GNOME 49 (with 50 about to be integrated into nixpkgs), and it’s on the latest Kernel version - currently
version 7.0.0.&lt;/p&gt;
&lt;div class="lightbox-gallery text-center"&gt;
 
&lt;a
 href="https://giggio.net/blog/nix-os-guia-de-instalacao-com-raid-1-criptografia-e-tpm-unlock-parte-3-instalando-o-so/images/nixos_about_hu_5751f340ec6b37f6.webp"
 data-pswp-width="1504"
 data-pswp-height="1135"
 target="_blank"
 class="lightbox "
 data-alt="NixOS &amp;#39;About&amp;#39; window displaying hardware information, Kernel 7.0, and system version."
 data-title="NixOS &amp;#39;About&amp;#39; window displaying hardware information, Kernel 7.0, and system version."
 data-pswp-srcset="https://giggio.net/blog/nix-os-guia-de-instalacao-com-raid-1-criptografia-e-tpm-unlock-parte-3-instalando-o-so/images/nixos_about_hu_5f6e0fc542a4cb6e.webp 400w, https://giggio.net/blog/nix-os-guia-de-instalacao-com-raid-1-criptografia-e-tpm-unlock-parte-3-instalando-o-so/images/nixos_about_hu_3232df680cba8cfa.webp 575w, https://giggio.net/blog/nix-os-guia-de-instalacao-com-raid-1-criptografia-e-tpm-unlock-parte-3-instalando-o-so/images/nixos_about_hu_6dc64499ff2bc03a.webp 767w, https://giggio.net/blog/nix-os-guia-de-instalacao-com-raid-1-criptografia-e-tpm-unlock-parte-3-instalando-o-so/images/nixos_about_hu_a0e7f14176c848f0.webp 991w, https://giggio.net/blog/nix-os-guia-de-instalacao-com-raid-1-criptografia-e-tpm-unlock-parte-3-instalando-o-so/images/nixos_about_hu_add9e5087f60aea6.webp 1200w, https://giggio.net/blog/nix-os-guia-de-instalacao-com-raid-1-criptografia-e-tpm-unlock-parte-3-instalando-o-so/images/nixos_about_hu_661febbe4c129883.webp 1400w, https://giggio.net/blog/nix-os-guia-de-instalacao-com-raid-1-criptografia-e-tpm-unlock-parte-3-instalando-o-so/images/nixos_about_hu_b05f1e9f70b6c5b5.webp 1504w"
&gt;
 
&lt;img
 src="https://giggio.net/blog/nix-os-guia-de-instalacao-com-raid-1-criptografia-e-tpm-unlock-parte-3-instalando-o-so/images/nixos_about_hu_5751f340ec6b37f6.webp"
 alt="NixOS &amp;#39;About&amp;#39; window displaying hardware information, Kernel 7.0, and system version."
 srcset="https://giggio.net/blog/nix-os-guia-de-instalacao-com-raid-1-criptografia-e-tpm-unlock-parte-3-instalando-o-so/images/nixos_about_hu_5f6e0fc542a4cb6e.webp 400w, https://giggio.net/blog/nix-os-guia-de-instalacao-com-raid-1-criptografia-e-tpm-unlock-parte-3-instalando-o-so/images/nixos_about_hu_3232df680cba8cfa.webp 575w, https://giggio.net/blog/nix-os-guia-de-instalacao-com-raid-1-criptografia-e-tpm-unlock-parte-3-instalando-o-so/images/nixos_about_hu_6dc64499ff2bc03a.webp 767w, https://giggio.net/blog/nix-os-guia-de-instalacao-com-raid-1-criptografia-e-tpm-unlock-parte-3-instalando-o-so/images/nixos_about_hu_a0e7f14176c848f0.webp 991w, https://giggio.net/blog/nix-os-guia-de-instalacao-com-raid-1-criptografia-e-tpm-unlock-parte-3-instalando-o-so/images/nixos_about_hu_add9e5087f60aea6.webp 1200w, https://giggio.net/blog/nix-os-guia-de-instalacao-com-raid-1-criptografia-e-tpm-unlock-parte-3-instalando-o-so/images/nixos_about_hu_661febbe4c129883.webp 1400w, https://giggio.net/blog/nix-os-guia-de-instalacao-com-raid-1-criptografia-e-tpm-unlock-parte-3-instalando-o-so/images/nixos_about_hu_b05f1e9f70b6c5b5.webp 1504w"
 sizes="400px, 575px, 767px, 991px, 1200px, 1400px, 1504px"
 
 
 
 
/&gt;

&lt;/a&gt;


&lt;/div&gt;

&lt;p&gt;The system already features RAID 1 via btrfs and LUKS encryption. However, you’ll notice that it still asks for the
encryption password manually at every boot.&lt;/p&gt;
&lt;p&gt;In the next post, we’ll dive deep into Secure Boot, so I’ll stop here to keep the next one from getting too long. It’s
coming soon! See you then!&lt;/p&gt;</description></item><item><title>NixOS: Installation Guide with RAID 1, encryption, and TPM unlock (part 2 - Disko, LUKS, and btrfs)</title><link>https://giggio.net/en/blog/nix-os-guia-de-instalacao-com-raid-1-criptografia-e-tpm-unlock-parte-2-disko-luks-e-btrfs/</link><pubDate>Wed, 06 May 2026 11:00:00 -0300</pubDate><author>giggio@giggio.net (Giovanni Bassi)</author><guid>https://giggio.net/en/blog/nix-os-guia-de-instalacao-com-raid-1-criptografia-e-tpm-unlock-parte-2-disko-luks-e-btrfs/</guid><category>infra</category><description>&lt;p&gt;In this post I keep on building a NixOS setup with secure storage, now going deeper into Disko, LUKS, and btrfs.&lt;/p&gt;
&lt;p&gt;This is the second post in the series.
&lt;a href="https://giggio.net/en/blog/nix-os-guia-de-instalacao-com-raid-1-criptografia-e-tpm-unlock-parte-1/"&gt;Read the first one&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In the previous post I showed how to create the virtual machine and partition the disks using
&lt;a href="https://github.com/nix-community/disko/"&gt;Disko&lt;/a&gt;.
The main command was:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo nix --experimental-features &lt;span class="s2"&gt;&amp;#34;nix-command flakes&amp;#34;&lt;/span&gt; run github:nix-community/disko/latest -- &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --mode destroy,format,mount ~/nixos/disko.nix
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Although the file
&lt;a href="https://codeberg.org/giggio/nixos_raid_btrfs_luks_tpm/src/branch/main/disko.nix"&gt;disko.nix&lt;/a&gt;
is part of a
&lt;a href="https://nixos.wiki/wiki/flakes"&gt;flake&lt;/a&gt;
(a versioned Nix configuration), in this case I ran only the file on its own with &lt;code&gt;nix run&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;It is also possible to run only the &lt;code&gt;mount&lt;/code&gt; step, after the disk has already been partitioned and formatted, with
&lt;code&gt;--mode mount&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="what-disko-is"&gt;
 &lt;a href="#what-disko-is" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;What Disko is&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;Disko is a project that brings Nix a declarative way to partition and format disks. In Nix, everything is done
declaratively, except disk partitioning and formatting. Disko solves that. Partitioning and formatting disks is done
only at the start of an installation, and that is exactly where the project is useful.&lt;/p&gt;
&lt;p&gt;In addition, Disko provides a NixOS module that lets you use what was declared to define NixOS file systems, which are
normally declared in the file
&lt;a href="https://codeberg.org/giggio/nixos_raid_btrfs_luks_tpm/src/branch/main/hardware-configuration.nix"&gt;hardware-configuration.nix&lt;/a&gt;.
I will talk more about that at the end.&lt;/p&gt;
&lt;p&gt;Disko can also be used in the installation process imperatively, through the command line. That is what I did when
installing
&lt;a href="https://codeberg.org/giggio/nixos_serverbase/src/commit/8e8a356183698c248daa22c1a7be66e900996d7c/modules/lib.nix#L282"&gt;my servers&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="configuring-the-disk-and-its-first-partition"&gt;
 &lt;a href="#configuring-the-disk-and-its-first-partition" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;Configuring the disk and its first partition&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;I am working with two disks. The second disk is where all the file system declarations live, so I will focus on it. The
first disk ends up being a bit simpler, since it will be mirrored from the first one
(I will talk about that below, in the &lt;a href="https://giggio.net/en/blog/nix-os-guia-de-instalacao-com-raid-1-criptografia-e-tpm-unlock-parte-2-disko-luks-e-btrfs/#raid-1-on-btrfs"&gt;RAID section&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;At the beginning I declare the disk, the device I am referring to (&lt;code&gt;/dev/vdb&lt;/code&gt; — that is, the second virtual disk), and
the first partition, which will be the EFI system partition, or &lt;code&gt;ESP&lt;/code&gt;
(&lt;a href="https://en.wikipedia.org/wiki/EFI_system_partition"&gt;EFI System Partition&lt;/a&gt;):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-nix" data-lang="nix"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;disko&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;devices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;disk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# disk1 omitted&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;disk2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;disk&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;device&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;/dev/vdb&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;gpt&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;partitions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ESP&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;1G&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;EF00&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;EFI2&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;filesystem&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;extraArgs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;-n&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;EFI2&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;format&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;vfat&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;mountpoint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;/boot&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;mountOptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;umask=0077&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# crypt_disk2 partition omitted&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Note that the mount options are included, and are used both by Disko&amp;rsquo;s &lt;code&gt;mount&lt;/code&gt; command and by the Disko module when
defining file systems.&lt;/p&gt;
&lt;p&gt;In this case, an ESP partition needs to have type &lt;code&gt;EF00&lt;/code&gt; and be formatted as &lt;code&gt;vfat&lt;/code&gt;. I also set the label of the
partition to &lt;code&gt;EFI2&lt;/code&gt;, and the file system label to the same value (with &lt;code&gt;-n EFI&lt;/code&gt;, an argument passed to &lt;code&gt;mkfs.vfat&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;This part will generate something roughly like these commands:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;parted /dev/vdb mklabel gpt
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;parted /dev/vdb mkpart primary 1MiB 1025MiB
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;parted /dev/vdb &lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; esp on
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mkfs.fat -F &lt;span class="m"&gt;32&lt;/span&gt; -n EFI2 /dev/vdb1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# and, for mount:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mount /dev/disk/by-label/EFI2 /boot
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="second-partition-luks--btrfs"&gt;
 &lt;a href="#second-partition-luks--btrfs" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;Second partition: LUKS + btrfs&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;Next, I need to create the Linux partition, which I want to be encrypted. The encryption will be handled by
&lt;a href="https://gitlab.com/cryptsetup/cryptsetup/"&gt;LUKS&lt;/a&gt;,
so the &lt;code&gt;type&lt;/code&gt; is defined as &lt;code&gt;luks&lt;/code&gt; (in the ESP it was defined as &lt;code&gt;filesystem&lt;/code&gt;). That means the command used to set up the
partition will be &lt;code&gt;cryptsetup luksFormat&lt;/code&gt;, and it is what receives the partition label parameter &lt;code&gt;--label NIXLUKS2&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-nix" data-lang="nix"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;disko&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;devices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;disk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;disk2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;partitions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# ESP partition omitted&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;crypt_disk2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;100%&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;NIXLUKS2&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;luks&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;crypt_disk2&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;extraFormatArgs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;--label&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;NIXLUKS2&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;passwordFile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="sr"&gt;./luks-password.txt&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;settings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;allowDiscards&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt; &lt;span class="c1"&gt;# btrfs details omitted&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;};&lt;/span&gt; &lt;span class="c1"&gt;# other curly braces omitted&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;On a real NixOS system the password coming from the &lt;code&gt;luks-password.txt&lt;/code&gt; file would be encrypted. I usually use
&lt;a href="https://github.com/Mic92/sops-nix/"&gt;sops-nix&lt;/a&gt; for that.&lt;/p&gt;
&lt;p&gt;That previous part will generate something roughly like these commands:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;parted /dev/vdb mkpart primary 1025MiB 100%
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cryptsetup --label&lt;span class="o"&gt;=&lt;/span&gt;NIXLUKS2 luksFormat --type luks2 /dev/vdb2
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# and, for mount:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cryptsetup open /dev/vdb2 crypt_disk2
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: The password is entered interactively in the commands above.&lt;/p&gt;
&lt;p&gt;Then, in the content part, we define what goes inside the LUKS encrypted volume, in this case, a btrfs file system.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-nix" data-lang="nix"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# beginning omitted&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;partitions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# ESP partition omitted&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;crypt_disk2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# LUKS details omitted&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;btrfs&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;extraArgs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;--label&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;NIXOS&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;-d&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;raid1&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;-m&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;raid1&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;/dev/mapper/crypt_disk1&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;subvolumes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;/@&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;mountpoint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;/&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;/@home&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;mountpoint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;/home&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;/@root&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;mountpoint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;/root&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;/@nix&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;mountpoint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;/nix&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This part will generate something roughly like these commands:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cryptsetup open /dev/vdb2 crypt_disk2
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mkfs.btrfs --label NIXOS -d raid1 -m raid1 /dev/mapper/crypt_disk1 /dev/mapper/crypt_disk2
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mkdir -p /mnt
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mount /dev/mapper/crypt_disk2 /mnt
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;btrfs subvolume create /mnt/@
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;btrfs subvolume create /mnt/@home
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;btrfs subvolume create /mnt/@root
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;btrfs subvolume create /mnt/@nix
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;umount /mnt
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# and, for mount:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mount -o &lt;span class="nv"&gt;subvol&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;@ /dev/mapper/crypt_disk2 /
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mkdir /mnt/&lt;span class="o"&gt;{&lt;/span&gt;home,nix,root,boot&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mount -o &lt;span class="nv"&gt;subvol&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;@home /dev/mapper/crypt_disk2 /home
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mount -o &lt;span class="nv"&gt;subvol&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;@nix /dev/mapper/crypt_disk2 /nix
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mount -o &lt;span class="nv"&gt;subvol&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;@root /dev/mapper/crypt_disk2 /root
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;At the end of the operation, Disko will mount btrfs at &lt;code&gt;/mnt&lt;/code&gt;.
You can verify everything by running:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# list the partitions and file systems&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;lsblk -f
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# list the btrfs subvolumes&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo btrfs subvolume list /mnt
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="raid-1-on-btrfs"&gt;
 &lt;a href="#raid-1-on-btrfs" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;RAID 1 on btrfs&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;Notice that I passed the parameters &lt;code&gt;-d raid1 -m raid1 /dev/mapper/crypt_disk1&lt;/code&gt;, which means btrfs will be running in
RAID 1 for both metadata and data, and in parity with the first disk, which has also already been configured for
encryption with LUKS. The reason for defining everything on the second disk is because Disko operates in the order
defined, so when disk 1 is being prepared disk 2 still has not been partitioned.&lt;/p&gt;
&lt;p&gt;In RAID 1, everything written to one disk is immediately copied (mirrored) to the other disks. In this case, we have a
RAID 1 with two disks, controlled directly by the btrfs file system. With Linux, it is very common to do this with
&lt;a href="https://docs.kernel.org/admin-guide/md.html"&gt;mdadm&lt;/a&gt;, but in this case mdadm is unnecessary because btrfs
&lt;a href="https://btrfs.readthedocs.io/en/latest/Volume-management.html"&gt;has native RAID support&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You can check the RAID status by running:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;btrfs device stats /mnt
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="subvolumes-on-btrfs"&gt;
 &lt;a href="#subvolumes-on-btrfs" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;Subvolumes on btrfs&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;I defined several &lt;a href="https://btrfs.readthedocs.io/en/latest/Subvolumes.html"&gt;btrfs subvolumes&lt;/a&gt;. They are useful because
they let us manage them independently. For example, we can save everything written to a volume using snapshots, as well
as define several settings that on other file systems are available only for partitions, such as quotas, for example.
They can also be nested, inheriting the settings and actions performed within the hierarchy.&lt;/p&gt;
&lt;p&gt;Subvolumes can also be mounted wherever we want, and that is exactly what we did, for example by mounting &lt;code&gt;/home&lt;/code&gt; from
the &lt;code&gt;@home&lt;/code&gt; volume.&lt;/p&gt;
&lt;p&gt;On an already prepared system, this is the list of subvolumes:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; sudo btrfs subvolume list /
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;ID 256 gen 1103 top level 5 path @
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;ID 257 gen 923 top level 5 path @home
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;ID 258 gen 923 top level 5 path @nix
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;ID 259 gen 695 top level 5 path @root
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;ID 260 gen 19 top level 256 path srv
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;ID 261 gen 19 top level 256 path var/lib/portables
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;ID 262 gen 19 top level 256 path var/lib/machines
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;ID 263 gen 1080 top level 256 path tmp
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;ID 264 gen 1080 top level 256 path var/tmp
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The root volume is always &lt;code&gt;5&lt;/code&gt;, which is why the four subvolumes created point to it as the parent subvolume.&lt;/p&gt;
&lt;p&gt;It is still possible to create subvolumes with &lt;code&gt;btrfs subvolume create&lt;/code&gt; and make snapshots with
&lt;code&gt;btrfs subvolume snapshot&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="using-disko-as-a-module"&gt;
 &lt;a href="#using-disko-as-a-module" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;Using Disko as a module&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;As I mentioned at the start of the post, Disko lets you use it as a module that declares file systems, without needing
to declare them in the &lt;code&gt;hardware-configuration.nix&lt;/code&gt; file.&lt;/p&gt;
&lt;p&gt;To use it, just add &lt;code&gt;disko.nixosModules.disko&lt;/code&gt; to the system modules, along with your Disko configuration file, which in
this example is &lt;code&gt;disko.nix&lt;/code&gt;, the same one that was used on its own to create the file system. It looks like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-nix" data-lang="nix"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;inputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;nixpkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;github:NixOS/nixpkgs/nixos-unstable&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;disko&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;github:nix-community/disko/latest&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;inputs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nixpkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;follows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;nixpkgs&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;outputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nixpkgs&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;disko&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;inputs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;nixosConfigurations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;nixos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nixpkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nixosSystem&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;system&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;x86_64-linux&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;modules&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="sr"&gt;./configuration.nix&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt; &lt;span class="sr"&gt;./disko.nix&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;disko&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nixosModules&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;disko&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Note that my
&lt;a href="https://codeberg.org/giggio/nixos_raid_btrfs_luks_tpm/src/branch/main/hardware-configuration.nix"&gt;hardware-configuration.nix&lt;/a&gt;
file does not declare any file systems. See
&lt;a href="https://wiki.nixos.org/wiki/Filesystems"&gt;NixOS docs about file systems&lt;/a&gt;
for how this is normally declared, and, because I used Disko, I did not need to do it.&lt;/p&gt;
&lt;p&gt;If I had to do it manually, it would be more or less like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-nix" data-lang="nix"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;fileSystems&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;/&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;device&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;/dev/mapper/crypt_disk1&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;fsType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;btrfs&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;subvol=@&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;/home&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;device&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;/dev/mapper/crypt_disk1&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;fsType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;btrfs&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;subvol=@home&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;/nix&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;device&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;/dev/mapper/crypt_disk1&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;fsType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;btrfs&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;subvol=@nix&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;/root&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;device&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;/dev/mapper/crypt_disk1&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;fsType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;btrfs&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;subvol=@root&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;/boot&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;device&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;/dev/disk/by-label/EFI1&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;fsType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;vfat&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;fmask=0022&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;dmask=0022&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And that only after manually partitioning the main disks with LUKS and formatting them with btrfs.&lt;/p&gt;
&lt;p&gt;It becomes obvious how much Disko simplifies the whole process: we use its definitions to configure the disks during
installation, and later to bring up the operating system itself.&lt;/p&gt;
&lt;h3 id="conclusion"&gt;
 &lt;a href="#conclusion" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;Conclusion&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;With a disk and file system declaration that is later used to configure it, it is possible to prepare all the storage
for a future Linux operating system using just one command line.&lt;/p&gt;
&lt;p&gt;Instead of having to keep commands in documentation or build a shell script to prepare everything, we have it all
versioned in executable files.&lt;/p&gt;
&lt;p&gt;In the next post I will show how to finally install NixOS on the file system that was mounted. In the following ones I
will demonstrate how to use TPM to obtain an encryption key that will be used to unlock LUKS without human interaction,
directly at boot.&lt;/p&gt;</description></item><item><title>NixOS: Installation Guide with RAID 1, encryption, and TPM unlock (part 1)</title><link>https://giggio.net/en/blog/nix-os-guia-de-instalacao-com-raid-1-criptografia-e-tpm-unlock-parte-1/</link><pubDate>Tue, 28 Apr 2026 16:34:00 -0300</pubDate><author>giggio@giggio.net (Giovanni Bassi)</author><guid>https://giggio.net/en/blog/nix-os-guia-de-instalacao-com-raid-1-criptografia-e-tpm-unlock-parte-1/</guid><category>infra</category><description>&lt;p&gt;It is time to migrate from Ubuntu to NixOS!&lt;/p&gt;
&lt;p&gt;For a long time, I have been using Nix on Ubuntu through
&lt;a href="https://nix-community.github.io/home-manager/"&gt;Home Manager&lt;/a&gt;,
and I have been automating my
&lt;a href="https://codeberg.org/giggio/dotfiles"&gt;dotfiles&lt;/a&gt;
more and more, steadily removing my custom setup and using Nix instead. Recently, I adopted the new
&lt;a href="https://github.com/numtide/system-manager"&gt;System Manager&lt;/a&gt;
project, whose goal is to bring system-level configuration to operating systems other than NixOS, where it was previously
only available.&lt;/p&gt;
&lt;p&gt;After quite a while on that journey, and after already migrating
&lt;a href="https://codeberg.org/giggio/nixos_serverbase"&gt;my home servers&lt;/a&gt;,
it is time to move to NixOS and leave Ubuntu behind.&lt;/p&gt;
&lt;p&gt;I want to live on the edge of what is new and configure my Linux exactly the way I want, and NixOS will let me do that.
I have also come to really like the way it keeps configuration in versioned file systems that I can control in detail.&lt;/p&gt;
&lt;p&gt;This post will show how to meet what I consider the bare minimum for security: I am going to create a NixOS system with
an encrypted disk, mirrored with RAID 1, using the most modern file system available right now (btrfs), and use the TPM
to decrypt the system without human interaction.&lt;/p&gt;
&lt;p&gt;Already know NixOS, TPM, btrfs, and the rest, and just want the code? It is free and available
&lt;a href="https://codeberg.org/giggio/nixos_raid_btrfs_luks_tpm"&gt;in a repository on my Codeberg&lt;/a&gt;.
The README explains what is going on, but I will go into more detail here on the blog, explaining the concepts in more
depth. This will be a series of posts, where I intend to explain each piece.&lt;/p&gt;
&lt;p&gt;The plan is to do something like this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Creating the VM and the initial partitions (this post)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://giggio.net/en/blog/nix-os-guia-de-instalacao-com-raid-1-criptografia-e-tpm-unlock-parte-2-disko-luks-e-btrfs/"&gt;More information about Disko, LUKS and btrfs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://giggio.net/en/blog/nix-os-guia-de-instalacao-com-raid-1-criptografia-e-tpm-unlock-parte-3-instalando-o-so/"&gt;Installing NixOS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://giggio.net/en/blog/nix-os-guia-de-instalacao-com-raid-1-criptografia-e-tpm-unlock-parte-4-secure-boot/"&gt;Preparing Secure Boot&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://giggio.net/en/blog/nix-os-guia-de-instalacao-com-raid-1-criptografia-e-tpm-unlock-parte-5-destrancando-o-disco-com-tpm/"&gt;Using the TPM to decrypt the disks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://giggio.net/en/blog/nix-os-guia-de-instalacao-com-raid-1-criptografia-e-tpm-unlock-parte-6-mitigando-o-ataque-de-troca-de-volume/"&gt;Mitigating the volume-swapping attack&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://giggio.net/en/blog/nix-os-guia-de-instalacao-com-raid-1-criptografia-e-tpm-unlock-parte-7-mitigando-o-ataque-de-troca-de-so/"&gt;Mitigating the OS-swapping attack&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Review and conclusions&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I am sure the plan will fall apart, but it gives you an idea of what I intend to cover.&lt;/p&gt;
&lt;p&gt;In this first part, we will prepare the VM and the initial partitions.&lt;/p&gt;
&lt;h3 id="preparing-the-virtual-machine"&gt;
 &lt;a href="#preparing-the-virtual-machine" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;Preparing the virtual machine&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;I am on Ubuntu 24, using
&lt;a href="https://virt-manager.org/"&gt;Virtual Machine Manager&lt;/a&gt;
5.1, installed through NixOS. Adapt this to your own OS setup.&lt;/p&gt;
&lt;p&gt;Download the
&lt;a href="https://nixos.org/download/#nixos-iso"&gt;NixOS ISO&lt;/a&gt;. I used the graphical ISO, but I imagine the minimal image should work
just fine too.&lt;/p&gt;
&lt;p&gt;I chose to create the disks ahead of time with &lt;code&gt;qemu-img&lt;/code&gt;, because it creates disks that do not consume all the reserved
space right away; they grow as needed (sparse files). I noticed that when creating the disks through the VMM UI, it
creates them with the full size by default, unless you go through the custom storage option, where there is a
checkbox for &amp;ldquo;Allocate entire volume now&amp;rdquo;. In any case, I prefer the command line, so I will leave it here for
reference. In my case, I will keep the VM files under
&lt;code&gt;/mnt/data/vms/vmm&lt;/code&gt;, so adapt that to whatever directory you prefer.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /mnt/data/vms/vmm
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;qemu-img create -f qcow2 nixos1-1.qcow2 200G
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;qemu-img create -f qcow2 nixos1-2.qcow2 200G
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Copy the downloaded &lt;code&gt;.iso&lt;/code&gt; into that same directory.&lt;/p&gt;
&lt;p&gt;Create the virtual machine with those two disks and with the NixOS ISO. Configure the TPM for version 2. Enable the boot
menu. Configure the network as NAT.
If you want to see my configuration, it is available in &lt;a href="https://giggio.net/blog/nix-os-guia-de-instalacao-com-raid-1-criptografia-e-tpm-unlock-parte-1/files/nixos.xml"&gt;nixos.xml&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Delete all Secure Boot keys and disable Secure Boot in the VM, because the installer does not have a signed &lt;code&gt;.efi&lt;/code&gt;
file and will not boot with Secure Boot enabled.&lt;/p&gt;
&lt;p&gt;To do that: when the VM starts, press &lt;code&gt;ESC&lt;/code&gt; to enter the boot menu. Then choose &amp;ldquo;Device Manager&amp;rdquo; and
&amp;ldquo;Secure Boot Configuration&amp;rdquo;. Select &amp;ldquo;Reset Secure Boot Keys&amp;rdquo; and confirm. That will disable Secure Boot as well. Go
back to the main menu and choose &amp;ldquo;Reset&amp;rdquo;, which will restart the boot process. It is possible that during this process
it will eject the virtual installation CD (&lt;code&gt;.iso&lt;/code&gt;); add it back in the VM settings and restart it.&lt;/p&gt;
&lt;h3 id="starting-the-installation"&gt;
 &lt;a href="#starting-the-installation" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;Starting the installation&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;Choose one of the installation options. I went with Gnome and the latest Kernel, but I imagine it will make little
difference; all we need is a shell.
When the installer opens, close it and open a terminal.&lt;/p&gt;
&lt;p&gt;From that point on, you can continue from that terminal.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tip&lt;/strong&gt;: I preferred to connect via SSH, since that makes it easier to copy and paste commands into the terminal. To do
that, create a password with &lt;code&gt;passwd&lt;/code&gt;. Find the VM IP with &lt;code&gt;ip a&lt;/code&gt; and connect to it from the host with
&lt;code&gt;ssh nixos@&amp;lt;ip&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="declarative-partitioning-with-disko"&gt;
 &lt;a href="#declarative-partitioning-with-disko" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;Declarative partitioning with Disko&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;First, check the current partition status with &lt;code&gt;lsblk -f&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I am using &lt;a href="https://github.com/nix-community/disko/"&gt;Disko&lt;/a&gt; to create the partitions and file systems.&lt;/p&gt;
&lt;p&gt;Clone the repository into &lt;code&gt;~/nixos&lt;/code&gt;: &lt;a href="https://codeberg.org/giggio/nixos_raid_btrfs_luks_tpm"&gt;https://codeberg.org/giggio/nixos_raid_btrfs_luks_tpm&lt;/a&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git clone https://codeberg.org/giggio/nixos_raid_btrfs_luks_tpm.git ~/nixos
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ~/nixos
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git checkout 397fb28 &lt;span class="c1"&gt;# checkout the commit with message &amp;#34;Updated config&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# create the partitions&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo nix --experimental-features &lt;span class="s2"&gt;&amp;#34;nix-command flakes&amp;#34;&lt;/span&gt; run github:nix-community/disko/latest -- --mode destroy,format,mount ~/nixos/disko.nix
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# check the results&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;lsblk -f
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo fdisk -l /dev/vd&lt;span class="o"&gt;{&lt;/span&gt;a,b&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# or&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo parted /dev/vda print all
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;See the file used by Disko:
&lt;a href="https://codeberg.org/giggio/nixos_raid_btrfs_luks_tpm/src/branch/main/disko.nix"&gt;disko.nix&lt;/a&gt;.
Note that the password is &lt;code&gt;luks&lt;/code&gt;, and it comes from the file
&lt;a href="https://codeberg.org/giggio/nixos_raid_btrfs_luks_tpm/src/branch/main/luks-password.txt"&gt;luks-password.txt&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In the next post, I will explain in more detail what Disko did. For now, I can say that it already created two
partitions on each disk: one partition for EFI (ESP) and another for Linux, which will be encrypted using LUKS, and will
internally use btrfs, with subvolumes already created. I will explain all of that later.&lt;/p&gt;
&lt;p&gt;For now, enjoy figuring out how Disko managed to do all that with just one file. That is the beauty of NixOS: no
endless command typing in the terminal, everything is versioned and declarative.&lt;/p&gt;
&lt;p&gt;See you in the next post.&lt;/p&gt;</description></item><item><title>MVP: End of a Cycle, Beginning of Another</title><link>https://giggio.net/en/blog/mvp-fim-de-um-ciclo-comeco-de-outro/</link><pubDate>Thu, 10 Jul 2025 21:01:36 -0300</pubDate><author>giggio@giggio.net (Giovanni Bassi)</author><guid>https://giggio.net/en/blog/mvp-fim-de-um-ciclo-comeco-de-outro/</guid><category>community</category><description>&lt;p&gt;&lt;a href="https://web.archive.org/web/20090403184527/http://unplugged.giggio.net/unplugged/post/Sou-agora-um-Microsoft-MVP.aspx"&gt;In April
2009&lt;/a&gt;,
16 years ago, I received the Microsoft MVP award for the first time, and I renewed it every year. Until today.&lt;/p&gt;
&lt;p&gt;I’m leaving the program with a good feeling. I joined amazing communities, met so many great people, and the MVP program
opened countless doors for me. It was fantastic for my career, and I still say that this and similar programs are
invaluable for anyone who, like me, enjoys contributing to the various software communities in Brazil and around the
world. I have lots of friends who are MVPs, GDEs, GitHub Stars, Java Champions, etc.—truly wonderful people whom I’ve
been privileged to know. Some became so close that we founded a successful company together, where we’ve helped train
many people who also went on to win the award. At one point, we were one of the companies with the highest number of
awarded professionals globally—with ten awardees!&lt;/p&gt;
&lt;p&gt;The program renews every 12 months, in July, and lately my desire to participate by speaking, writing articles, and
producing Microsoft‑related content has all but vanished. That was reflected in how little content I produced in that
period, which, if not zero, was pretty close to it. My decision to step away from the program is justified, and I saw it
coming.&lt;/p&gt;
&lt;p&gt;I thought I’d feel upset when it happened. When the farewell email arrived, I analyzed my feelings, and now—ten hours
later—I’m still at peace. I see this as the end of a cycle. It was time.&lt;/p&gt;
&lt;p&gt;I won’t lose the connections I’ve made along the way—those remain, and that makes me happy. And I’ve been making many
other connections outside the Microsoft community for years, and I’m very happy about that too.&lt;/p&gt;
&lt;p&gt;At the same time, it’s the start of a new cycle. Actually, it confirms a cycle that began some years ago. I’ve been
contributing more and more to technologies completely unrelated to Microsoft, like Rust, Linux, and all sorts of
infrastructure and DevOps tools I know. Also, since I’m on sabbatical, my demand for C# is very low, and I’ve had more
opportunities to explore other areas. C# remains one of my favorite languages—it just keeps getting better—but I know it
inside and out, and anyone who knows me knows I love learning constantly. So, for now, my main focus is Rust.&lt;/p&gt;
&lt;p&gt;Communities have changed a lot too. Nowadays, we meet mostly online, many events are virtual, and much of the
interaction isn’t face‑to‑face anymore—which I do miss sometimes. One of my earliest community involvements—a study
group called .NET Architects—started in person but quickly gained a strong online presence through an (almost)
still‑alive email group that’s still &lt;a href="https://groups.google.com/g/dotnetarchitects"&gt;available&lt;/a&gt; for anyone who wants to
read our old conversations. Our in‑person meetups were important and took place in several Brazilian cities.&lt;/p&gt;
&lt;p&gt;The MVP program itself has changed dramatically. When I joined, one of the biggest perks was access to confidential info
about upcoming products. For example, I learned about the .NET Core project in 2014, long before it went public, and I
was able to use that intel strategically in the businesses I was involved in. Even though I couldn’t share details, that
knowledge was crucial for making major decisions that I took—and helped my business partners take. Nowadays, most of
Microsoft’s software development is open source, and planning happens openly, with meetings and minutes posted on
GitHub. It’s wonderful to see open source win, but conversely that perk was watered down. There were also cases of
people breaking NDAs and leaking confidential info, which led some teams to share less than they’d planned. A pity.&lt;/p&gt;
&lt;p&gt;When I joined, the program still offered a $100 voucher to buy anything in the MVP store, shipped to Brazil tax‑free. I
got one and gifted a bunch of stuff—it was really cool. That benefit ended in 2010, so I only got it once.&lt;/p&gt;
&lt;p&gt;Another huge benefit was the annual event at Microsoft headquarters—the MVP Summit in Redmond—where I had the chance to
meet and spend a few days with the people working day in and day out on the products I used. It was incredible to meet
some of the top designers of languages, frameworks, tools, and operating systems in the world, and even challenge them a
bit. How many times did I ask the C# team to add more metaprogramming features, like macros? Many—pretty much every
year. I even made a public request for the main DevOps tool (now Azure DevOps) to support Git, which caused quite a stir
and put me in a tricky spot with some Microsoft internal teams. At that event, I met the team working on WSL (Windows
Subsystem for Linux) and spent an hour in the hallway listening to the engineering lead talk about their technical and
political challenges—a Linux guy inside Microsoft!&lt;/p&gt;
&lt;p&gt;During the pandemic, the event became fully online, and with its return it adopted a hybrid model—but they cut the free
lodging; before, we only had to cover transport costs. That reduced participation—dozens of Brazilians used to go; in
recent years, fewer than ten. I haven’t gone. The event overall seems to have shrunk, and many prefer to stay online.
Another pity.&lt;/p&gt;
&lt;p&gt;The recognition the award brings, however, remains strong. But after nearly 30 years of experience, and more than half
that time as a continuous awardee, I already have plenty of recognition. I think it’s good to contribute now without
that affiliation, and at the same time free up space for the newer folks coming up.&lt;/p&gt;
&lt;p&gt;Thanks to everyone who was close on this journey—you know who you are. And I’m not disappearing from tech communities.
Nothing changes in that regard. Only now I’ll be Giovanni, not “MVP.”&lt;/p&gt;
&lt;p&gt;Some photos from MVP Summits over the years:&lt;/p&gt;
&lt;div class="lightbox-gallery "&gt;
 
 &lt;a
 href="https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_brasileiros_hu_a863748d3643e77a.webp"
 data-pswp-width="2048"
 data-pswp-height="1365"
 target="_blank"
 class="lightbox "
 data-alt="Brazilians at the Summit"
 data-title="Brazilians at the Summit"
 data-pswp-srcset="https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_brasileiros_hu_885eabfc9ccf16c7.webp 400w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_brasileiros_hu_3edcc8c62da2e409.webp 575w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_brasileiros_hu_e8d28506c50dae28.webp 767w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_brasileiros_hu_8fe4e4c460b97d68.webp 991w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_brasileiros_hu_58a1097dbb1f4d33.webp 1200w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_brasileiros_hu_501e735f3c3d25e5.webp 1400w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_brasileiros_hu_4b70cdbbbf7b5d0b.webp 2000w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_brasileiros_hu_5a26112c0835f45.webp 2048w"
 &gt;
 
 &lt;img
 src="https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_brasileiros_hu_a863748d3643e77a.webp"
 alt="Brazilians at the Summit"
 srcset="https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_brasileiros_hu_885eabfc9ccf16c7.webp 400w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_brasileiros_hu_3edcc8c62da2e409.webp 575w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_brasileiros_hu_e8d28506c50dae28.webp 767w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_brasileiros_hu_8fe4e4c460b97d68.webp 991w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_brasileiros_hu_58a1097dbb1f4d33.webp 1200w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_brasileiros_hu_501e735f3c3d25e5.webp 1400w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_brasileiros_hu_4b70cdbbbf7b5d0b.webp 2000w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_brasileiros_hu_5a26112c0835f45.webp 2048w"
 sizes="400px, 575px, 767px, 991px, 1200px, 1400px, 2000px, 2048px"
 
 
 
 
 /&gt;
 
 &lt;/a&gt;

 &lt;!-- markdownlint-disable MD013 --&gt;
 &lt;a
 href="https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_microsoft_lambda3_hu_39cc67547b179396.webp"
 data-pswp-width="4032"
 data-pswp-height="3024"
 target="_blank"
 class="lightbox "
 data-alt="Lambda3 team in front of Microsoft HQ"
 data-title="Lambda3 team in front of Microsoft HQ: Giovanni, Emmanuel Brandão, Wennder dos Santos, Vinicius Quaiato, Evilázaro Álves, Igor Abade, Victor Cavalcante and Diego Nogare"
 data-pswp-srcset="https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_microsoft_lambda3_hu_91698ba314f7d3d5.webp 400w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_microsoft_lambda3_hu_3d430605b3bacc0e.webp 575w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_microsoft_lambda3_hu_2d018c3bff4db785.webp 767w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_microsoft_lambda3_hu_5a1e7ded49081f8c.webp 991w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_microsoft_lambda3_hu_3192705460acd5c1.webp 1200w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_microsoft_lambda3_hu_c7c8d12c4c45096a.webp 1400w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_microsoft_lambda3_hu_6064936e4605bfea.webp 2000w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_microsoft_lambda3_hu_1bdb97adb26a8022.webp 4032w"
 &gt;
 
 &lt;img
 src="https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_microsoft_lambda3_hu_39cc67547b179396.webp"
 alt="Lambda3 team in front of Microsoft HQ"
 srcset="https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_microsoft_lambda3_hu_91698ba314f7d3d5.webp 400w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_microsoft_lambda3_hu_3d430605b3bacc0e.webp 575w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_microsoft_lambda3_hu_2d018c3bff4db785.webp 767w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_microsoft_lambda3_hu_5a1e7ded49081f8c.webp 991w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_microsoft_lambda3_hu_3192705460acd5c1.webp 1200w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_microsoft_lambda3_hu_c7c8d12c4c45096a.webp 1400w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_microsoft_lambda3_hu_6064936e4605bfea.webp 2000w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_microsoft_lambda3_hu_1bdb97adb26a8022.webp 4032w"
 sizes="400px, 575px, 767px, 991px, 1200px, 1400px, 2000px, 4032px"
 
 
 
 
 /&gt;
 
 &lt;/a&gt;

 &lt;a
 href="https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_gravacao_video_codecracker_hu_71ef3a7845832fa2.webp"
 data-pswp-width="5376"
 data-pswp-height="3024"
 target="_blank"
 class="lightbox "
 data-alt="CodeCracker video recording with Elemar Jr, Phil Haack, Giovanni Bassi and Carlos dos Santos"
 data-title="CodeCracker video recording with Elemar Jr, Phil Haack, Giovanni Bassi and Carlos dos Santos"
 data-pswp-srcset="https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_gravacao_video_codecracker_hu_c09db1421ce91c5.webp 400w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_gravacao_video_codecracker_hu_5c5ba8d151e457a7.webp 575w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_gravacao_video_codecracker_hu_f983559ac6e7b5da.webp 767w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_gravacao_video_codecracker_hu_a5bf75893864870c.webp 991w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_gravacao_video_codecracker_hu_49311e464d323372.webp 1200w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_gravacao_video_codecracker_hu_59e8ae4e5d9244db.webp 1400w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_gravacao_video_codecracker_hu_d75bcbe16edab182.webp 2000w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_gravacao_video_codecracker_hu_67b65a4eb67eacc3.webp 5376w"
 &gt;
 
 &lt;img
 src="https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_gravacao_video_codecracker_hu_71ef3a7845832fa2.webp"
 alt="CodeCracker video recording with Elemar Jr, Phil Haack, Giovanni Bassi and Carlos dos Santos"
 srcset="https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_gravacao_video_codecracker_hu_c09db1421ce91c5.webp 400w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_gravacao_video_codecracker_hu_5c5ba8d151e457a7.webp 575w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_gravacao_video_codecracker_hu_f983559ac6e7b5da.webp 767w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_gravacao_video_codecracker_hu_a5bf75893864870c.webp 991w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_gravacao_video_codecracker_hu_49311e464d323372.webp 1200w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_gravacao_video_codecracker_hu_59e8ae4e5d9244db.webp 1400w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_gravacao_video_codecracker_hu_d75bcbe16edab182.webp 2000w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_gravacao_video_codecracker_hu_67b65a4eb67eacc3.webp 5376w"
 sizes="400px, 575px, 767px, 991px, 1200px, 1400px, 2000px, 5376px"
 
 
 
 
 /&gt;
 
 &lt;/a&gt;

 &lt;a
 href="https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_hotel_lambda3_hu_5697be51ecc7c5a5.webp"
 data-pswp-width="4032"
 data-pswp-height="1960"
 target="_blank"
 class="lightbox "
 data-alt="Lambda3 team at the hotel: Emmanuel Brandão, Vinicius Quaiato, Wennder dos Santos, Evilázaro Álves, Victor Cavalcante and Giovanni Bassi"
 data-title="Lambda3 team at the hotel: Emmanuel Brandão, Vinicius Quaiato, Wennder dos Santos, Evilázaro Álves, Victor Cavalcante and Giovanni Bassi"
 data-pswp-srcset="https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_hotel_lambda3_hu_daf2c8f1e846ff9f.webp 400w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_hotel_lambda3_hu_1d22989606f02d5a.webp 575w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_hotel_lambda3_hu_cf2e8f65fc55a2fb.webp 767w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_hotel_lambda3_hu_9c3a6024c11bcbaa.webp 991w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_hotel_lambda3_hu_83eaee4c9f4e4d4.webp 1200w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_hotel_lambda3_hu_a2f978ccee6a96d2.webp 1400w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_hotel_lambda3_hu_56131a312795075d.webp 2000w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_hotel_lambda3_hu_742513ca7969a4a1.webp 4032w"
 &gt;
 
 &lt;img
 src="https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_hotel_lambda3_hu_5697be51ecc7c5a5.webp"
 alt="Lambda3 team at the hotel: Emmanuel Brandão, Vinicius Quaiato, Wennder dos Santos, Evilázaro Álves, Victor Cavalcante and Giovanni Bassi"
 srcset="https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_hotel_lambda3_hu_daf2c8f1e846ff9f.webp 400w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_hotel_lambda3_hu_1d22989606f02d5a.webp 575w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_hotel_lambda3_hu_cf2e8f65fc55a2fb.webp 767w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_hotel_lambda3_hu_9c3a6024c11bcbaa.webp 991w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_hotel_lambda3_hu_83eaee4c9f4e4d4.webp 1200w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_hotel_lambda3_hu_a2f978ccee6a96d2.webp 1400w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_hotel_lambda3_hu_56131a312795075d.webp 2000w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_hotel_lambda3_hu_742513ca7969a4a1.webp 4032w"
 sizes="400px, 575px, 767px, 991px, 1200px, 1400px, 2000px, 4032px"
 
 
 
 
 /&gt;
 
 &lt;/a&gt;

 &lt;a
 href="https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_vc_e_gb_careta_hu_e5f19c4efbdf442b.webp"
 data-pswp-width="1064"
 data-pswp-height="1064"
 target="_blank"
 class="lightbox "
 data-alt="Victor Cavalcante and Giovanni Bassi making faces in front of an MVP‑logo backdrop"
 data-title="Victor Cavalcante and Giovanni Bassi"
 data-pswp-srcset="https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_vc_e_gb_careta_hu_93ddf671dec60a3b.webp 400w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_vc_e_gb_careta_hu_d92f57f06c8c6ebd.webp 575w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_vc_e_gb_careta_hu_a38bd5c41a2d7c92.webp 767w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_vc_e_gb_careta_hu_9fee84aede9aa437.webp 991w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_vc_e_gb_careta_hu_377ae2e7552571d0.webp 1064w"
 &gt;
 
 &lt;img
 src="https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_vc_e_gb_careta_hu_e5f19c4efbdf442b.webp"
 alt="Victor Cavalcante and Giovanni Bassi making faces in front of an MVP‑logo backdrop"
 srcset="https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_vc_e_gb_careta_hu_93ddf671dec60a3b.webp 400w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_vc_e_gb_careta_hu_d92f57f06c8c6ebd.webp 575w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_vc_e_gb_careta_hu_a38bd5c41a2d7c92.webp 767w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_vc_e_gb_careta_hu_9fee84aede9aa437.webp 991w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_vc_e_gb_careta_hu_377ae2e7552571d0.webp 1064w"
 sizes="400px, 575px, 767px, 991px, 1064px"
 
 
 
 
 /&gt;
 
 &lt;/a&gt;

 &lt;a
 href="https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_gb_chapeu_hu_2a984934f8e60822.webp"
 data-pswp-width="1872"
 data-pswp-height="1248"
 target="_blank"
 class="lightbox "
 data-alt="Giovanni Bassi in front of an MVP‑logo backdrop, wearing a top hat and scarf"
 data-title="Giovanni Bassi"
 data-pswp-srcset="https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_gb_chapeu_hu_a97e8d060385795b.webp 400w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_gb_chapeu_hu_32e9a8615c136b4b.webp 575w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_gb_chapeu_hu_477f0fcc976343dd.webp 767w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_gb_chapeu_hu_aa4a045382b34a82.webp 991w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_gb_chapeu_hu_3eebff642d755f3e.webp 1200w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_gb_chapeu_hu_b2551daf955e2dbb.webp 1400w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_gb_chapeu_hu_d680dbed32c519b0.webp 1872w"
 &gt;
 
 &lt;img
 src="https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_gb_chapeu_hu_2a984934f8e60822.webp"
 alt="Giovanni Bassi in front of an MVP‑logo backdrop, wearing a top hat and scarf"
 srcset="https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_gb_chapeu_hu_a97e8d060385795b.webp 400w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_gb_chapeu_hu_32e9a8615c136b4b.webp 575w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_gb_chapeu_hu_477f0fcc976343dd.webp 767w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_gb_chapeu_hu_aa4a045382b34a82.webp 991w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_gb_chapeu_hu_3eebff642d755f3e.webp 1200w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_gb_chapeu_hu_b2551daf955e2dbb.webp 1400w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_gb_chapeu_hu_d680dbed32c519b0.webp 1872w"
 sizes="400px, 575px, 767px, 991px, 1200px, 1400px, 1872px"
 
 
 
 
 /&gt;
 
 &lt;/a&gt;

 &lt;a
 href="https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_vc_e_gb_king_crab_hu_7f29bb3b195aed86.webp"
 data-pswp-width="800"
 data-pswp-height="600"
 target="_blank"
 class="lightbox "
 data-alt="Giovanni Bassi and Victor Cavalcante eating King Crab, seated, wearing bibs, holding a large crab leg"
 data-title="Giovanni Bassi and Victor Cavalcante"
 data-pswp-srcset="https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_vc_e_gb_king_crab_hu_62c457044fdc9cdb.webp 400w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_vc_e_gb_king_crab_hu_1b36e50ad67f75e7.webp 575w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_vc_e_gb_king_crab_hu_6dfe59a535153eec.webp 767w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_vc_e_gb_king_crab_hu_876a7841ab1a1762.webp 991w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_vc_e_gb_king_crab_hu_bad54e24f554bf2c.webp 1200w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_vc_e_gb_king_crab_hu_61297bc22e3f623c.webp 1400w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_vc_e_gb_king_crab_hu_34e828ac71158011.webp 2000w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_vc_e_gb_king_crab_hu_3295b4190eb2187.webp 2592w"
 &gt;
 
 &lt;img
 src="https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_vc_e_gb_king_crab_hu_7f29bb3b195aed86.webp"
 alt="Giovanni Bassi and Victor Cavalcante eating King Crab, seated, wearing bibs, holding a large crab leg"
 srcset="https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_vc_e_gb_king_crab_hu_62c457044fdc9cdb.webp 400w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_vc_e_gb_king_crab_hu_1b36e50ad67f75e7.webp 575w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_vc_e_gb_king_crab_hu_6dfe59a535153eec.webp 767w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_vc_e_gb_king_crab_hu_876a7841ab1a1762.webp 991w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_vc_e_gb_king_crab_hu_bad54e24f554bf2c.webp 1200w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_vc_e_gb_king_crab_hu_61297bc22e3f623c.webp 1400w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_vc_e_gb_king_crab_hu_34e828ac71158011.webp 2000w, https://giggio.net/blog/mvp-fim-de-um-ciclo-comeco-de-outro/images/mvp_summit_vc_e_gb_king_crab_hu_3295b4190eb2187.webp 2592w"
 sizes="400px, 575px, 767px, 991px, 1200px, 1400px, 2000px, 2592px"
 
 
 
 
 /&gt;
 
 &lt;/a&gt;

 &lt;!-- markdownlint-enable MD013 --&gt;

&lt;/div&gt;</description></item><item><title>Data Centers and Digital Sovereignty</title><link>https://giggio.net/en/blog/data-centers-and-digital-sovereignty/</link><pubDate>Wed, 02 Jul 2025 15:00:00 -0300</pubDate><author>giggio@giggio.net (Giovanni Bassi)</author><guid>https://giggio.net/en/blog/data-centers-and-digital-sovereignty/</guid><category>politics</category><description>&lt;p&gt;The Brazilian government, in May 2025, was in the USA to meet with big techs and discuss investments in data
centers in Brazil. I will argue why the proposals presented by the government are bad for the Brazilian people and
for the national economy, as well as point to a possible alternative path for an initiative of this kind.&lt;/p&gt;
&lt;h3 id="federal-government-proposals"&gt;
 &lt;a href="#federal-government-proposals" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;Federal government proposals&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;The federal government&amp;rsquo;s project can be summarized in five main points:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Exemptions from IPI, PIS/Cofins, and Import taxes for 5 years
&lt;blockquote&gt;
&lt;p&gt;Named Redata, the program provides exemptions from IPI, PIS/Cofins, and import tax on equipment for
five years. According to estimates from the Ministry of Finance, the framework can boost up to R$ 2 trillion in
investments over the next decade. &lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;No study presented
&lt;blockquote&gt;
&lt;p&gt;The departments limited themselves to saying that &amp;ldquo;the national data center policy is being developed, still in
study phase, by the federal government based on the joint work of various sectors and ministries&amp;rdquo; and that
&amp;ldquo;technical meetings are being held with all involved sectors and there is no conclusion at the moment.&amp;rdquo; &lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;Expectation of R$ 2 trillion investment in 10 years
&lt;blockquote&gt;
&lt;p&gt;The department led by Haddad has repeatedly stated that this new policy can unlock about R$ 2 trillion in
investments over the next ten years. &lt;sup id="fnref1:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;Transfers of gross revenue
&lt;blockquote&gt;
&lt;p&gt;As anticipated counterparts in the policy, the government plans to require data center companies to transfer
2% to 8% of their gross revenue to the National Fund for Industrial and Technological Development (FNDIT). The
fund aims to foster domestic technological development. &lt;sup id="fnref2:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;10% capacity reserved for the domestic market
&lt;blockquote&gt;
&lt;p&gt;Another counterpart would be the requirement that 10% of the data centers&amp;rsquo; processing capacity be reserved for
the domestic market. The intention is to prevent foreign companies from coming to Brazil only to take advantage
of the benefits while continuing to serve international demand. But, according to experts, verifying whether
this requirement is being met is technically difficult. &lt;sup id="fnref3:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Official sources:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Haddad details Data Center Policy at conference in the USA (Agência gov) &lt;sup id="fnref:3"&gt;&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref"&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;li&gt;Haddad presents sustainable growth plan and announces Data Center Policy at conference in the USA
(Ministry of Finance) &lt;sup id="fnref:4"&gt;&lt;a href="#fn:4" class="footnote-ref" role="doc-noteref"&gt;4&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;At first glance, the initiative sounds great: trillions of reais in investments in a cutting-edge industry. But the
proposal inflates the numbers — far from business reality and Brazil’s actual needs — and ignores the fact that, if
implemented as-is, it would result in yet another case of capital outflow, exploiting our natural resources and labor,
with no real compensation in return.&lt;/p&gt;
&lt;h3 id="unrealistic-promises"&gt;
 &lt;a href="#unrealistic-promises" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;Unrealistic promises&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h3&gt;
&lt;h4 id="r-2-trillion-investment"&gt;
 &lt;a href="#r-2-trillion-investment" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;R$ 2 trillion investment&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h4&gt;
&lt;p&gt;The figure of R$ 2 trillion over ten years (roughly US$ 350 billion), or R$ 200 billion per year (US$ 35 billion),
is unrealistic. Here’s why:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A modern data center costs between US$ 500 million and US$ 1.5 billion. I’ll use the average of US$ 1 billion in
this article for cost and output estimates.&lt;/li&gt;
&lt;li&gt;Combined, the three largest cloud providers operate over 500 data centers (&lt;a href="https://web.archive.org/web/20250613020146/https://aws.amazon.com/about-aws/global-infrastructure/"&gt;117
AWS&lt;/a&gt;, &lt;a href="https://web.archive.org/web/20250618000637/https://news.microsoft.com/datacenters/"&gt;300
Azure&lt;/a&gt;, &lt;a href="https://web.archive.org/web/20250615205748/https://cloud.google.com/about/locations"&gt;127
GCP&lt;/a&gt;), and Brazil has 163 (MDIC
&lt;sup id="fnref:5"&gt;&lt;a href="#fn:5" class="footnote-ref" role="doc-noteref"&gt;5&lt;/a&gt;&lt;/sup&gt;).&lt;/li&gt;
&lt;li&gt;To reach R$ 2 trillion, Brazil would need 350 new data centers in ten years — a 64% increase over the global
infrastructure of the top three clouds, and 214% more than the number of data centers currently in Brazil.&lt;/li&gt;
&lt;li&gt;If we considered smaller data centers at US$ 100 million each, we’d need 3,500 of them.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If such an investment were to happen, the project’s assumptions wouldn’t hold: sustainability, allocating part of the
processing power to Brazil, job creation, and so on. I’ll dig deeper into these and other aspects of the project,
showing that the numbers are unachievable, and that even if we got close, the result would be dysfunctional — in some
cases going in the opposite direction of what the project claims to want. I’ll work with 50% of the proposed figure —
175 data centers in ten years — an optimistic scenario, but already strategically unfeasible based on the arguments that
follow. Even far below the proposed value, the negative impact would already be substantial.&lt;/p&gt;
&lt;h4 id="job-creation"&gt;
 &lt;a href="#job-creation" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;Job creation&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h4&gt;
&lt;p&gt;The government hasn’t shared job creation estimates, but the Secretary of the Ministry of Development, Industry,
Commerce, and Services said that &amp;ldquo;on average, each data center creates 166 direct jobs,&amp;rdquo; according to Neofeed
&lt;sup id="fnref1:5"&gt;&lt;a href="#fn:5" class="footnote-ref" role="doc-noteref"&gt;5&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;They already know the impact on this front is small. And it’s true: modern data centers require modest staffing. They
are industrially operated, need little constant intervention, and a large part of operations can be done remotely.&lt;/p&gt;
&lt;p&gt;This is confirmed by countries more advanced than Brazil in this area:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;In the United States, for example, employment numbers have shown that data centers don&amp;rsquo;t generate as many jobs as
expected. While a larger workforce is needed to build these structures, the number shrinks when it comes to the
operation of data centers. In Abilene, Texas, around 1,500 people are building Stargate&amp;rsquo;s first data center. But after
it is completed, only 100 full-time employees will work at the facility, according to a Wall Street Journal report
from February. &lt;sup id="fnref:6"&gt;&lt;a href="#fn:6" class="footnote-ref" role="doc-noteref"&gt;6&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So maybe Brazilians will participate in the technological development of these data centers, right? Not really. That
investment is globalized, managed by teams built up over the past decades. We might get some peripheral demand, but
nothing central to the operation.&lt;/p&gt;
&lt;p&gt;A large data center might generate up to 300 direct jobs — an optimistic figure — which I’ll use in the estimates below.
I’ll disregard indirect jobs, such as in R&amp;amp;D, for reasons already discussed in the previous paragraph.&lt;/p&gt;
&lt;p&gt;Even if 175 data centers were built (they won’t be), assuming twice the number estimated by the MDIC (300 jobs per data
center), only 53,000 direct jobs would be created. With around 110 million economically active people in Brazil (PEA -
Dec/24) &lt;sup id="fnref:7"&gt;&lt;a href="#fn:7" class="footnote-ref" role="doc-noteref"&gt;7&lt;/a&gt;&lt;/sup&gt;, that represents a measly 0.05% growth in the current workforce. Unemployment in Brazil is
currently low (historically speaking), but even if it were high, the impact would be negligible.&lt;/p&gt;
&lt;p&gt;It’s important to remember that it’s demand — not companies — that creates jobs. The demand for data center labor
already exists, as the world needs more of them, but under the current project, even if the center is built in Brazil,
that demand will remain concentrated abroad and highly mechanized (even there). For the demand to appear in Brazil, we’d
need to develop and run the high-tech needs of these data centers ourselves. I’ll expand on this in the alternative
proposals at the end, but it’s important to remember that not all job demand is necessarily good. If we started a
project to cut down the entire Amazon, we’d employ thousands of people — but that’s not work that benefits the Brazilian
people. It’s not about generating demand, but about what kind of demand we want to encourage.&lt;/p&gt;
&lt;h4 id="technology-transfer"&gt;
 &lt;a href="#technology-transfer" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;Technology transfer&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h4&gt;
&lt;p&gt;We shouldn’t expect access to cutting-edge technologies, which are closely guarded secrets by data center operators.
These companies don’t share their industrial secrets with potential competitors — let alone with a foreign government.&lt;/p&gt;
&lt;p&gt;If we want to develop our own data centers, it won’t be using the technology of the Silicon Valley companies the
government spoke with.&lt;/p&gt;
&lt;p&gt;Brazil also doesn’t develop advanced logic or memory chips, the kind required by this type of data center — they would
all be imported tax-free. Developing an industry in this area would take decades anyway, and it’s not on the agenda of
any influential politician today.&lt;/p&gt;
&lt;h4 id="tax-revenue-gains"&gt;
 &lt;a href="#tax-revenue-gains" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;Tax revenue gains&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h4&gt;
&lt;p&gt;We’re also not going to collect much in taxes from these foreign-owned data centers. The government is
proposing exemptions from IPI, PIS, Cofins, and import taxes. It’s set for five years, but we know how these benefits
tend to get renewed indefinitely — the government is currently facing a crisis on this front, trying to revoke past
benefits, without success.&lt;/p&gt;
&lt;p&gt;Even the purchase of equipment — servers, switches, etc. — could be made abroad, with funds transferred from one
multinational to another, never passing through Brazil, never paying IOF or any other tax. And import taxes would be
exempt (but not when you buy a processor for your home or business).&lt;/p&gt;
&lt;p&gt;BNDES is even planning to finance part of the effort. So not only would we fail to collect taxes, Brazilian society
would actually be financing foreign companies in their extractive efforts with billions of reais &lt;sup id="fnref:8"&gt;&lt;a href="#fn:8" class="footnote-ref" role="doc-noteref"&gt;8&lt;/a&gt;&lt;/sup&gt;
&lt;sup id="fnref:9"&gt;&lt;a href="#fn:9" class="footnote-ref" role="doc-noteref"&gt;9&lt;/a&gt;&lt;/sup&gt;, and even donating half a billion reais to build data centers and other infrastructure for AI initiatives
&lt;sup id="fnref:10"&gt;&lt;a href="#fn:10" class="footnote-ref" role="doc-noteref"&gt;10&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;The proposed 2% to 8% revenue transfer to FNDIT is a good idea and perhaps the only real counterpart offered so far, but
it’s clearly not enough.&lt;/p&gt;
&lt;h4 id="reserving-10-for-domestic-processing"&gt;
 &lt;a href="#reserving-10-for-domestic-processing" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;Reserving 10% for domestic processing&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h4&gt;
&lt;p&gt;Today, there are only a few public cloud regions in Brazil: two from Azure, and one each from AWS and GCP — all launched
years ago, with a good portion of their processing already targeting Brazil and South America.&lt;/p&gt;
&lt;p&gt;We also have other data center companies operating here. Numbers vary, but there are around 175 publicly offered and
another 100 or so private &lt;sup id="fnref:11"&gt;&lt;a href="#fn:11" class="footnote-ref" role="doc-noteref"&gt;11&lt;/a&gt;&lt;/sup&gt; &lt;sup id="fnref:12"&gt;&lt;a href="#fn:12" class="footnote-ref" role="doc-noteref"&gt;12&lt;/a&gt;&lt;/sup&gt;
&lt;sup id="fnref:13"&gt;&lt;a href="#fn:13" class="footnote-ref" role="doc-noteref"&gt;13&lt;/a&gt;&lt;/sup&gt;. MDIC estimates 163 data centers in Brazil &lt;sup id="fnref2:5"&gt;&lt;a href="#fn:5" class="footnote-ref" role="doc-noteref"&gt;5&lt;/a&gt;&lt;/sup&gt;. The number
varies because the methodology for counting a data center varies too (private, public, size, etc.).&lt;/p&gt;
&lt;p&gt;This is already a mature market. New data centers operating here, at the proposed scale, would have almost all of their
processing aimed abroad. To be blunt: exported.&lt;/p&gt;
&lt;h3 id="environmental-impacts"&gt;
 &lt;a href="#environmental-impacts" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;Environmental impacts&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;If there’s one glaring omission in this project, it’s the Ministry of the Environment. As we’ll see next, the
environmental impacts are significant, and without careful attention to the sustainability of these initiatives, we put
the future of Brazilians at risk. We’re talking about the exploitation of natural resources — and the ministry
responsible for overseeing this area is absent from the project &lt;sup id="fnref:14"&gt;&lt;a href="#fn:14" class="footnote-ref" role="doc-noteref"&gt;14&lt;/a&gt;&lt;/sup&gt;. Why?&lt;/p&gt;
&lt;h4 id="electricity-demand"&gt;
 &lt;a href="#electricity-demand" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;Electricity demand&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h4&gt;
&lt;p&gt;According to Empresa de Pesquisa Energética (EPE – a state-owned company), in its 2024 national energy report
&lt;sup id="fnref:15"&gt;&lt;a href="#fn:15" class="footnote-ref" role="doc-noteref"&gt;15&lt;/a&gt;&lt;/sup&gt; and executive summary &lt;sup id="fnref:16"&gt;&lt;a href="#fn:16" class="footnote-ref" role="doc-noteref"&gt;16&lt;/a&gt;&lt;/sup&gt;, Brazil generated 708 TWh (terawatt-hours)
of electricity in 2023. A cloud data center from one of the three major providers consumes on average 100 MW, which adds
up to about 876 GWh per year. In other words, just 808 of these data centers would consume the entirety of Brazil’s
electricity production. The (unrealistic) government projection of building about 350 data centers would require a 43%
increase in electricity generation — and that’s not even considering the needs of the population or other industries.
Between 2014 and 2023 (ten years), growth was only 18%, or 112 TWh, while GDP growth in the same period was 5.7%. So,
while energy production has outpaced economic growth, it still falls far short of what would be needed to meet such
demand.&lt;/p&gt;
&lt;p&gt;Even if only half of the government’s proposed investment were realized, production would still need to increase by
21.7% — in a country that already frequently faces blackout threats. We’re talking about an additional annual demand of
153 TWh, which is 36% more than the growth of the last decade — and again, that’s without factoring in other sectors or
the data centers already in the pipeline.&lt;/p&gt;
&lt;p&gt;The National Electric System Operator (ONS) also identified data centers as major upcoming consumers in its medium-term
energy studies for 2025–2029 &lt;sup id="fnref:17"&gt;&lt;a href="#fn:17" class="footnote-ref" role="doc-noteref"&gt;17&lt;/a&gt;&lt;/sup&gt;. According to the report:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Forecasts from the Ministry of Mines and Energy (MME) project 10.5 GW of electricity demand from data centers by 2037,
in the Southeast, Northeast, and South regions.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This forecast doesn’t even include the new projects that would be spurred by the government’s proposed policy. Again, if
175 new data centers were built, the added demand would reach 17.5 GW, or 153.3 TWh per year. That’s 166% beyond the
forecast for the next 12 years — squeezed into just 10 — and current demand is already heavily concentrated in São
Paulo, which hosts nearly 70% of existing data centers. Meanwhile, half of the forecasted electricity demand is
concentrated in Rio Grande do Sul, creating bottleneck challenges (data from EPE &lt;sup id="fnref:18"&gt;&lt;a href="#fn:18" class="footnote-ref" role="doc-noteref"&gt;18&lt;/a&gt;&lt;/sup&gt;).&lt;/p&gt;
&lt;p&gt;We also need to consider that building a data center — even when starting from the planning phase — typically takes
significantly less time than building the required energy infrastructure. A large data center usually takes around
three years to complete, while smaller ones can be ready in under a year. In contrast, building power transmission lines
— from planning to deployment — takes at least four and a half years (58 months), and can stretch to nearly nine years
(104 months), again according to EPE &lt;sup id="fnref1:18"&gt;&lt;a href="#fn:18" class="footnote-ref" role="doc-noteref"&gt;18&lt;/a&gt;&lt;/sup&gt;. The uncertainty around this potential growth is one of
the biggest planning challenges, according to EPE, and it puts both residential and industrial consumers at risk — as
well as the viability of the data center projects themselves.&lt;/p&gt;
&lt;h4 id="water-demand"&gt;
 &lt;a href="#water-demand" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;Water demand&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h4&gt;
&lt;p&gt;Microsoft claims to be highly focused on minimizing environmental impact. On its page about sustainability and data
center efficiency &lt;sup id="fnref:19"&gt;&lt;a href="#fn:19" class="footnote-ref" role="doc-noteref"&gt;19&lt;/a&gt;&lt;/sup&gt;, it publishes a table stating that it uses, on average, 0.3325 liters
of water per kWh.&lt;/p&gt;
&lt;p&gt;Given that a modern data center consumes around 876 GWh per year, this translates to 291 million liters of water
annually (876 million kWh times 0.3325). According to a UOL article &lt;sup id="fnref:20"&gt;&lt;a href="#fn:20" class="footnote-ref" role="doc-noteref"&gt;20&lt;/a&gt;&lt;/sup&gt;, Sabesp produced 815 million liters
of water in the first quarter of 2025. Extrapolating, that’s 3.2 billion liters per year, supplying 29 million people
across 375 municipalities &lt;sup id="fnref:21"&gt;&lt;a href="#fn:21" class="footnote-ref" role="doc-noteref"&gt;21&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;A &lt;em&gt;single&lt;/em&gt; data center would require almost 9% of that total. Half of the government’s goal — 175 data centers — would
be devastating. There simply isn’t enough water.&lt;/p&gt;
&lt;p&gt;There are lower-impact water recirculation technologies available — but they are more expensive. In a public program
with no incentives for investment, in a peripheral country exploited by multinationals seeking deregulation, it’s highly
unlikely these would be adopted.&lt;/p&gt;
&lt;h3 id="who-benefits"&gt;
 &lt;a href="#who-benefits" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;Who benefits?&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;An analogy illustrates well what’s being proposed. Imagine the government owns a vast rural property, suitable for
agricultural and livestock investments, but currently inactive. Seeing all this potential going to waste, it decides to
stimulate the economy. It talks to foreign investors and offers all sorts of incentives for them to occupy this idle
land. They agree and, in return, offer to pay 2% to 8% of the revenue generated from using the property — and all
production will be mechanized. Our land would be used to feed the world, our natural resources — which belong to all
Brazilians — would be handed over to foreign nations, enriching only a few intermediaries along the way.&lt;/p&gt;
&lt;p&gt;Back to the real scenario: with no significant job creation, no technology transfer, and no meaningful tax revenue, but
enormous pressure on our natural resources, it becomes clear that the only real winners would be the foreign companies
that take the deal — along with those who profit from the commercialization of natural resources, like companies trading
in energy and water. There would be some secondary, non-strategic gains — for example, low-value suppliers of products
and services. But it’s not as if selling and maintaining air conditioners is going to change Brazil’s situation in any
meaningful way.&lt;/p&gt;
&lt;p&gt;So once again we return to the core question: What is Brazil’s strategic interest in this project?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;This is a debate about the exploitation of our natural resources.&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Around 12% of the planet’s freshwater is in Brazilian territory. While the North region holds roughly 80% of the
country’s available water, regions near the Atlantic Ocean hold less than 3% of Brazil’s water resources. &lt;sup id="fnref:22"&gt;&lt;a href="#fn:22" class="footnote-ref" role="doc-noteref"&gt;22&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Our energy matrix is mostly renewable, lowering the environmental compensation costs for the companies that own the data
centers. But the destruction caused by hydroelectric plants — and all other externalities — is borne by the Brazilian
people. If we’re going to exploit this potential, the question is: why? What does Brazil gain?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;This is a debate about capital outflow.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;These companies are looking for cheap water and energy in Brazil to export processing capacity — a modern version of
soybean exports, with far greater demands and no real benefits in return.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;So you process the data here and send it abroad. Folks, this is nothing more than exporting energy. Right now we have
a 20% energy surplus in an industry that&amp;rsquo;s only just starting to recover, and still slowly. This is our chance to grow
our energy consumption by exporting clean energy, which is exactly what this industry demands. &lt;sup id="fnref2:18"&gt;&lt;a href="#fn:18" class="footnote-ref" role="doc-noteref"&gt;18&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Gustavo Estrella - CEO of CPFL Energia&lt;/p&gt;
&lt;h3 id="real-need"&gt;
 &lt;a href="#real-need" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;Real need&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;As we’ve already seen in the section on electricity demand, the Ministry of Mines and Energy (MME) is forecasting a 10.5
GW increase over the next 12 years — without &lt;em&gt;any&lt;/em&gt; additional incentives. Assuming 100 MW per data center, that comes
out to just over 100 new data centers in the period — or roughly ten per year. This already represents a 64% growth,
based on the current number of 163 data centers, according to the Ministry of Development, Industry, Trade and Services
(MDIC) &lt;sup id="fnref3:5"&gt;&lt;a href="#fn:5" class="footnote-ref" role="doc-noteref"&gt;5&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;The numbers vary, and it’s hard to be precise, but other sources confirm &lt;sup id="fnref:23"&gt;&lt;a href="#fn:23" class="footnote-ref" role="doc-noteref"&gt;23&lt;/a&gt;&lt;/sup&gt; this level of growth.
While there may be some shortage in this market, it’s already being addressed. There’s already immense interest in
Brazil’s data center market, with concrete investments underway, such as Scala Data Centers’ project in Rio Grande do
Sul &lt;sup id="fnref:24"&gt;&lt;a href="#fn:24" class="footnote-ref" role="doc-noteref"&gt;24&lt;/a&gt;&lt;/sup&gt;, currently one of the largest in the country.&lt;/p&gt;
&lt;p&gt;Industry players claim processing costs are high in Brazil, arguing it’s due to an imbalance between supply and demand.
In reality, the imbalance comes mainly from other causes — the infamous “Brazil cost” also affects the tech sector. It’s
no surprise that the country with the highest real interest rate in the world also faces high operational costs. The
real issue isn’t a lack of incentives for data centers — it’s how Brazilian society is structured, choosing to reward
rent-seekers rather than labor and innovation.&lt;/p&gt;
&lt;p&gt;There is national demand, and we already have projects underway to meet it. What’s really at stake in the new federal
proposal is whether Brazil wants to serve foreign demand by exporting capital while exploiting its own natural
resources.&lt;/p&gt;
&lt;h3 id="railroading-through"&gt;
 &lt;a href="#railroading-through" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;Railroading through&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;When data centers in Brazil became a topic of public debate, a range of actors began lobbying to influence related
matters. And it’s showing results — some authorities are already echoing those interests.&lt;/p&gt;
&lt;p&gt;One example is the discussion around artificial intelligence regulation — or any sort of regulation on technology at
all. The data center sector is actively pushing for no regulation &lt;sup id="fnref:25"&gt;&lt;a href="#fn:25" class="footnote-ref" role="doc-noteref"&gt;25&lt;/a&gt;&lt;/sup&gt;, claiming that a deregulated
environment is essential for the installation of these extractive data centers.&lt;/p&gt;
&lt;p&gt;Some relevant statements from these areas:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;In December 2024, while the Senate was debating a bill to regulate artificial intelligence, the data center sector
published a letter requesting the removal of an article that would have mandated payment of royalties to content
owners for material used to train AI. &lt;sup id="fnref1:14"&gt;&lt;a href="#fn:14" class="footnote-ref" role="doc-noteref"&gt;14&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;According to Alckmin, the main bottleneck for AI worldwide
is energy, and Brazil, he said, “has abundant and renewable energy”. &lt;sup id="fnref:26"&gt;&lt;a href="#fn:26" class="footnote-ref" role="doc-noteref"&gt;26&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Technology and digital
infrastructure are a win-win agenda for Brazil and the US. Our role is to help ensure the regulatory environment keeps
pace with that ambition. &lt;sup id="fnref1:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Fabrizio Panzini, Director of Public Policy at Amcham&lt;/p&gt;
&lt;p&gt;This kind of initiative is opportunistic — it has no real connection to building data centers in Brazil. If the
government’s proposal only requires 10% of processing to stay local, then the vast majority — the other 90% — would be
exported and not subject to Brazilian regulation.&lt;/p&gt;
&lt;p&gt;The processing of Brazilian citizens’ data should be governed by strict legal standards and aligned with the values of
Brazilian society — which doesn’t want to see its data mined by foreign companies. The demand for data centers in Brazil
already exists under current regulations, and the argument that deregulation is necessary to attract interest is
simply false. The interest is already here.&lt;/p&gt;
&lt;h3 id="brazilian-ai"&gt;
 &lt;a href="#brazilian-ai" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;Brazilian AI&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;The federal government is already planning the creation and promotion of a Brazilian artificial intelligence initiative
&lt;sup id="fnref1:10"&gt;&lt;a href="#fn:10" class="footnote-ref" role="doc-noteref"&gt;10&lt;/a&gt;&lt;/sup&gt;. While such an effort does require computing resources, it has no direct connection to the proposal of
bringing in foreign companies to exploit our resources through a data center project.&lt;/p&gt;
&lt;p&gt;First, it’s important to distinguish between training an AI model and running it. Running a model scales like any
other computing service — it grows with demand. Training, on the other hand, requires a large upfront investment before
any result can be seen. And as companies like China’s DeepSeek have already demonstrated, it’s possible to train models
more cheaply using distilled (derived) models, and there are also already-pretrained models available.&lt;/p&gt;
&lt;p&gt;But the bigger issue here is something else — and I’ll only touch on it briefly: building an AI isn’t something you pay
for once and then it’s done. It needs constant updates; it’s a continuous investment project. The Brazilian state is
currently struggling to maintain fiscal balance, and it needs to seriously consider whether this should be a priority.
The way this topic is being discussed, it gives the impression that it would be a one-time expense. It’s not.&lt;/p&gt;
&lt;p&gt;Some are concerned that Brazil will miss the opportunity to join the AI race. But if Brazilian companies or the
government decide to enter this market and compete on equal footing with global leaders, the main challenge won’t be the
current or future supply of processing power. The current market is already mature.&lt;/p&gt;
&lt;h3 id="alternative-proposals"&gt;
 &lt;a href="#alternative-proposals" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;Alternative proposals&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;There are several ways to invest in data centers in Brazil. Let’s go through the alternatives, from worst to best.&lt;/p&gt;
&lt;h4 id="national-champions"&gt;
 &lt;a href="#national-champions" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;“National champions”&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h4&gt;
&lt;p&gt;This is the model China has adopted. Unsurprisingly, they now compete with Western cloud providers on equal footing.
This model involves incentivizing private companies, within a strategic framework. There are serious distortions in this
kind of setup, as it creates a new tech oligarchy — or reinforces an existing one — and Brazil has been ruled by the
same oligarchies since the time of slavery.&lt;/p&gt;
&lt;p&gt;But there’s a problem with copying the Chinese model: the Brazilian state does not steer the economy strategically like
China does. There’s a high risk that such investment would fail and end up enriching a few capitalists in another
chapter of accumulation and patrimonialism (&lt;em&gt;br: patrimonialismo&lt;/em&gt;). There’s a world of difference between a country that
plans 25 years ahead and one that fights every week to push through basic state policies. So we’d have to accept the
creation of large, functional companies that would be corrupt and uninterested in improving life for the Brazilian
people — though at least the investments would remain here. Until, of course, a new government came along and allowed
those companies to be sold to foreign capitalists — exactly what happened to our aerospace industry
&lt;sup id="fnref:27"&gt;&lt;a href="#fn:27" class="footnote-ref" role="doc-noteref"&gt;27&lt;/a&gt;&lt;/sup&gt;, once considered strategic for national defense and sovereignty, until it wasn’t anymore.&lt;/p&gt;
&lt;p&gt;This is a bad model — one we’ve tried before and that is doomed to fail because it relies on the good will of oligarchs
and monopolists.&lt;/p&gt;
&lt;h4 id="foreign-companies--real-counterparts"&gt;
 &lt;a href="#foreign-companies--real-counterparts" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;Foreign companies + real counterparts&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h4&gt;
&lt;p&gt;Assuming we are willing to hand over our natural resources to foreign companies, we can still demand something in return
that isn’t pure surrender and could actually benefit the Brazilian people. Let’s remember: we are under no obligation to
give up our resources. If there’s external interest, we have the sovereign right to negotiate.&lt;/p&gt;
&lt;p&gt;The first steps are obvious: undo the harmful proposals already outlined — reinstate taxes, enforce regulation, require
technology transfer. But we can go further. For example, to offset water and energy consumption, we could require that
data centers be built alongside sustainable infrastructure, such as solar panels and closed-loop water cooling systems —
more expensive, yes, but far less wasteful.&lt;/p&gt;
&lt;p&gt;We could also require partnerships with national companies, just as China does. Foreign firms could operate here, but at
least 51% of the capital and operation would need to be held by a company with domestic ownership. This would also help
drive technology transfer. But it’s important to recall what I pointed out earlier: large cloud providers are not
interested in sharing their technology or forming joint ventures. Measures like this would push them away. Just look at
how many don’t operate in China for precisely this reason — not because of so-called censorship. We know by now that the
West is willing to work with any (so called) authoritarian regime that makes strategic sense — but sharing technology
never does.&lt;/p&gt;
&lt;p&gt;This model might be beneficial, but it doesn’t move the country forward strategically. Brazil would act like a landlord
renting property to an entrepreneur at a fair rate: we wouldn’t be the entrepreneurs ourselves, and we wouldn’t advance
in terms of sovereignty or national development. Still a bad deal.&lt;/p&gt;
&lt;h4 id="public-investment-in-data-centers"&gt;
 &lt;a href="#public-investment-in-data-centers" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;Public investment in data centers&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h4&gt;
&lt;p&gt;This could be done with or without private partnerships, and it’s the model we adopted with Petrobras. Drawing that
parallel, it’s important to remember that Petrobras is constantly under attack — politicians often want to privatize it,
and both domestic and foreign oligarchies dream of owning this market. We saw the consequences of that recently with the
sale of refineries, which led to higher fuel prices (and increased inflation), and the sell-off of BR Distribuidora,
which stripped the state of its ability to pass on international oil price reductions to consumers. As a result, lower
oil prices benefited only a few, rather than the majority — and once again, the government lost an important tool of
monetary policy.&lt;/p&gt;
&lt;p&gt;Now imagine what would have happened if Petrobras and its predecessors had not been created. Brazil would never have
found oil and would now be completely dependent on imports, unbalancing its already fragile trade balance. Or, if the
oil had been found, its exploration would be in foreign hands, and we’d receive little from the exploitation of our own
natural resources — the outcome would have been purely financial.&lt;/p&gt;
&lt;p&gt;The parallels are obvious. This is the only option that truly makes sense. The creation of a competent, national,
state-owned tech industry — just like we did with oil — would ensure that the benefits of exploiting Brazilian natural
resources with technology actually stay in Brazil. It would face the same challenges as Petrobras: corruption,
inefficiency, and so on. But it’s still better than the alternatives.&lt;/p&gt;
&lt;p&gt;That said, the oil-data center analogy has its limits. The data center market is very different. Petrobras is a
monopolist in a protected market, while a state-owned data center initiative would face fierce competition from
established domestic and foreign players. Any such project would need to be designed with that reality in mind.&lt;/p&gt;
&lt;p&gt;This is the best alternative if Brazil’s long-term interests are taken seriously — but it’s not an easy path. It’s made
harder by the fact that such a project would only bear fruit in decades. It would require commitment from successive
governments — often from opposing parties and ideologies. How do you ensure a 25-year plan in a country facing constant
political instability? This kind of initiative requires a state policy, not a government policy.&lt;/p&gt;
&lt;h3 id="sovereignty-and-cutting-edge-technological-development"&gt;
 &lt;a href="#sovereignty-and-cutting-edge-technological-development" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;Sovereignty and cutting-edge technological development&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;Brazil must decide whether it wants to be part of cutting-edge technological development — in areas like logic
processors, memory, and software — or remain dependent on foreign partners. Today, such development happens in countries
that do not see us as partners, but as consumers. The current hardware production network is concentrated in just a few
countries, like the Netherlands, Taiwan, the U.S., and South Korea, with China pushing ahead and showing it may become
the leader in this field within a decade, more or less — and doing so with less reliance on foreign suppliers. China
took 40 years to reach this position, always through centralized planning, where companies are expected to execute
what’s laid out in national strategic plans that serve the needs of the country. Without a similar strategic direction,
any effort on our part is doomed to fail.&lt;/p&gt;
&lt;p&gt;Forget about any partnership for strategic technology transfer with ASML, TSMC, Samsung, Intel, AMD, Microsoft, Google,
or any other similar company — such initiatives are simply not in their interest. But there is the possibility of
discussing a partnership with China. Perhaps the BRICS framework can also open space for such a relationship, and this
kind of alliance could allow Brazil to take a leading role in just over a decade. That is the only viable option I see
in the current global context, and it depends entirely on China’s willingness — which, it’s worth noting, has shown
signs of interest &lt;sup id="fnref:28"&gt;&lt;a href="#fn:28" class="footnote-ref" role="doc-noteref"&gt;28&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;Such an initiative would allow the creation of a national hardware and software market that could support the
development of sovereign data centers in Brazil, while also advancing the country’s overall sovereignty. Any other
effort might yield short-term economic benefits, but would lock us into dependence, with no real autonomy, since all the
technology would come from abroad. And if we’re going to accept such dependence, it makes no sense to do it with
countries that don’t consider us part of their bloc (e.g., the U.S., Europe, Japan, etc.).&lt;/p&gt;
&lt;p&gt;Meanwhile, the Brazilian government has already launched a plan to build a sovereign cloud and has invested R$1.2
billion in it &lt;sup id="fnref:29"&gt;&lt;a href="#fn:29" class="footnote-ref" role="doc-noteref"&gt;29&lt;/a&gt;&lt;/sup&gt;. That initiative could be positive — if it weren’t relying on foreign software
&lt;sup id="fnref:30"&gt;&lt;a href="#fn:30" class="footnote-ref" role="doc-noteref"&gt;30&lt;/a&gt;&lt;/sup&gt; to manage the data centers. Leaving aside the (unnecessary) costs of such a project, one thing must be
made clear: there is no such thing as sovereignty when the brain of the so-called sovereign cloud is built with foreign
technology, which may contain strategically placed vulnerabilities — usable to extract data from Brazilians or even shut
down the entire operation in a scenario of conflict or sanctions. This kind of infrastructure must be treated as a
matter of national security, like military technology.&lt;/p&gt;
&lt;p&gt;Relying on foreign proprietary software for something this strategic is lazy. There are open-source-based technologies
available to build a national solution, and Brazil has enough talent to make that work — as demonstrated by Magazine
Luiza when it repatriated several Brazilians to build its own cloud infrastructure.&lt;/p&gt;
&lt;p&gt;Another topic that should be central to this conversation about technology and sovereignty is education — yet it&amp;rsquo;s
absent from all the plans mentioned by those involved in the federal government’s data center project. Brazil already
suffers from a serious shortage of tech professionals and a significant brain drain. Not only do we fail to train enough
people for high-tech fields, we also lose a big chunk of those we do train to foreign countries — yet another form of
capital export. There is no future for technological sovereignty without brains, and yet this issue is shockingly left
out of the conversation. The Ministry of Education is just as absent from this debate as the Ministry of the
Environment.&lt;/p&gt;
&lt;h3 id="conclusion"&gt;
 &lt;a href="#conclusion" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;Conclusion&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;Brazil has had a mature data center market for decades. It is a competitive industry with a large number of new projects
currently underway — many of which pay taxes and are operated by companies with Brazilian capital.&lt;/p&gt;
&lt;p&gt;The current proposal is a disaster for Brazil. The government needs to listen to our experts and build a long-term
strategy that guarantees sovereignty and sustainability — rather than behaving like a servant to foreign interests. We
are a globally valued market; we must develop our potential based on our sovereign interests, generating positive
outcomes for the entire Brazilian population — not at their expense.&lt;/p&gt;
&lt;h3 id="to-learn-more"&gt;
 &lt;a href="#to-learn-more" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;To learn more&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://youtu.be/lHYyRY1Zm4Y?si=df-aF6MHYK-IbO-M"&gt;Why do data centers consume so much water and energy? (Intercept Brasil)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.bcg.com/publications/2025/breaking-barriers-data-center-growth"&gt;Breaking Barriers to Data Center Growth (Boston Consulting Group)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://diplomatique.org.br/data-centers-no-nordeste-minerios-na-africa-lucros-no-vale-do-silicio/"&gt;Data centers in the Northeast, minerals in Africa, profits in Silicon Valley (Le Monde Diplomatique Brasil)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www12.senado.leg.br/noticias/materias/2025/05/30/especialistas-alertam-congresso-sobre-impasse-ambiental-com-avanco-da-ia"&gt;Experts warn Congress about environmental deadlock with AI advances (Agência Senado)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://open.spotify.com/episode/6rGtPxoWhb5qx0vOsgZTbO"&gt;The Strategic Role of EPE Amidst the Data Center Boom (EPE)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://youtu.be/zOR9l5XMPgE?si=_laSvOTLhd5MLWf7"&gt;“AI City”: Billion-dollar Data Center Project Advances in Eldorado do Sul (Intercept Brasil)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.theguardian.com/global-development/2025/may/22/datacentre-drought-chinese-social-media-supercomputers-brazil-latin-america"&gt;Draining cities dry: the giant tech companies queueing up to build data centers in drought-hit Latin America (The Guardian)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.theguardian.com/global-development/2025/mar/04/brazil-power-electricity-energy-poverty-datacentre-boom"&gt;Power struggle: will Brazil’s booming data center industry leave ordinary people in the dark? (The Guardian)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/live/iAKpxrvXgq4?si=cAE1zpQO5oCIcRIO"&gt;Reducing the energy footprint of data centres: sustainable solutions for a global challenge (European Sustainable Energy Week - EUSEW)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://youtu.be/nGid6Om96ZM?si=qVp-tJmY2gve2KmW"&gt;Conversation about the data center project (Tecnologia e Classe - TeClas)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://youtu.be/LXRrzAKNtpk?si=_I4vz82E8e2spGyl"&gt;Panel on the data center project (Inimigos do hAIpe)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="references"&gt;
 &lt;a href="#references" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;References&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h3&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;&lt;a href="https://archive.ph/BaRYR"&gt;Haddad presents proposal to attract data centers to the USA (CNN)&lt;/a&gt;&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&amp;#160;&lt;a href="#fnref1:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;&lt;a href="https://archive.ph/tE5KN"&gt;Haddad went to the USA to attract data center investments without showing what Brazil gains (Intercept Brasil)&lt;/a&gt;&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&amp;#160;&lt;a href="#fnref1:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&amp;#160;&lt;a href="#fnref2:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&amp;#160;&lt;a href="#fnref3:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;&lt;a href="https://agenciagov.ebc.com.br/noticias/202505/haddad-apresenta-plano-de-crescimento-sustentavel-e-anuncia-politica-de-data-centers-em-conferencia-nos-eua"&gt;Haddad details Data Center Policy at conference in the USA (Agência gov)&lt;/a&gt;&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:4"&gt;
&lt;p&gt;&lt;a href="https://www.gov.br/fazenda/pt-br/assuntos/noticias/2025/Maio/haddad-apresenta-plano-de-crescimento-sustentavel-e-anuncia-politica-de-data-centers-em-conferencia-nos-eua"&gt;Haddad presents sustainable growth plan and announces Data Center Policy at conference in the USA (Ministry of Finance)&lt;/a&gt;&amp;#160;&lt;a href="#fnref:4" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:5"&gt;
&lt;p&gt;&lt;a href="https://archive.ph/B7RQV"&gt;Government prepares plan to make Brazil a “data center powerhouse” in 10 years (Neofeed)&lt;/a&gt;&amp;#160;&lt;a href="#fnref:5" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&amp;#160;&lt;a href="#fnref1:5" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&amp;#160;&lt;a href="#fnref2:5" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&amp;#160;&lt;a href="#fnref3:5" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:6"&gt;
&lt;p&gt;&lt;a href="https://web.archive.org/web/20250522234821/https://www.techpolicy.press/brazil-is-handing-out-generous-incentives-for-data-centers-but-what-it-stands-to-gain-from-it-is-still-unclear/"&gt;Brazil is Handing Out Generous Incentives for Data Centers, But What it Stands to Gain is Still Unclear (Tech Policy Press)&lt;/a&gt;&amp;#160;&lt;a href="#fnref:6" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:7"&gt;
&lt;p&gt;&lt;a href="https://web.archive.org/web/20250625152321/https://www.gov.br/fazenda/pt-br/central-de-conteudo/publicacoes/conjuntura-economica/panorama-macroeconomico/2025/panmacro_spe_slides_-fevereiro2025.pdf"&gt;Macroeconomic Overview (Ministry of Finance - Feb/25)&lt;/a&gt;&amp;#160;&lt;a href="#fnref:7" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:8"&gt;
&lt;p&gt;&lt;a href="https://www.bndes.gov.br/wps/portal/site/home/financiamento/produto/fust-datacenter"&gt;FUST BNDES – Telecommunication Services Universalization Fund – Data Center and Core Network (BNDES)&lt;/a&gt;&amp;#160;&lt;a href="#fnref:8" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:9"&gt;
&lt;p&gt;&lt;a href="https://www.gov.br/mcom/pt-br/noticias/2024/setembro/nova-linha-de-credito-para-data-centers-contara-com-recursos-do-fust-e-bndes"&gt;New credit line for data centers will include FUST and BNDES resources (Ministry of Communications)&lt;/a&gt;&amp;#160;&lt;a href="#fnref:9" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:10"&gt;
&lt;p&gt;&lt;a href="https://web.archive.org/web/20250201140149/https://www.gov.br/mcti/pt-br/acompanhe-o-mcti/noticias/2024/07/plano-brasileiro-de-ia-tera-supercomputador-e-investimento-de-r-23-bilhoes-em-quatro-anos/ia_para_o_bem_de_todos.pdf/view"&gt;AI for the Good of All (Ministry of Science, Technology and Innovation)&lt;/a&gt;&amp;#160;&lt;a href="#fnref:10" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&amp;#160;&lt;a href="#fnref1:10" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:11"&gt;
&lt;p&gt;&lt;a href="https://web.archive.org/web/20250419115457/https://www.arizton.com/market-reports/brazil-data-center-portfolio"&gt;Brazil Existing &amp;amp; Upcoming Data Center Portfolio (Arizton)&lt;/a&gt;&amp;#160;&lt;a href="#fnref:11" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:12"&gt;
&lt;p&gt;&lt;a href="https://archive.ph/Mfoc3"&gt;Data centers in Brazil (Cloudscene)&lt;/a&gt;&amp;#160;&lt;a href="#fnref:12" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:13"&gt;
&lt;p&gt;&lt;a href="https://web.archive.org/web/20250327140431/https://www.globenewswire.com/news-release/2025/03/27/3050435/0/en/Brazil-Data-Center-Market-Growth-Analysis-Report-2025-2030-5-95-Bn-Investment-Opportunities-in-IT-Electrical-Mechanical-Infrastructure-General-Construction-and-Tier-Standards.html"&gt;Brazil Data Center Market Growth Analysis Report 2025-2030 (Research and markets)&lt;/a&gt;&amp;#160;&lt;a href="#fnref:13" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:14"&gt;
&lt;p&gt;&lt;a href="https://archive.ph/OIc6a"&gt;The AI push-through (Intercept Brasil)&lt;/a&gt;&amp;#160;&lt;a href="#fnref:14" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&amp;#160;&lt;a href="#fnref1:14" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:15"&gt;
&lt;p&gt;&lt;a href="https://web.archive.org/web/20250602182652/https://www.epe.gov.br/pt/publicacoes-dados-abertos/publicacoes/balanco-energetico-nacional-2024"&gt;National Energy Balance 2024 (EPE)&lt;/a&gt;&amp;#160;&lt;a href="#fnref:15" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:16"&gt;
&lt;p&gt;&lt;a href="https://web.archive.org/web/20250619213633/https://www.epe.gov.br/sites-pt/publicacoes-dados-abertos/publicacoes/PublicacoesArquivos/publicacao-819/topico-715/BEN_S%C3%ADntese_2024_PT.pdf"&gt;Summary Report of the National Energy Balance 2024 (EPE)&lt;/a&gt;&amp;#160;&lt;a href="#fnref:16" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:17"&gt;
&lt;p&gt;&lt;a href="https://web.archive.org/web/20250625152946/https://www.ons.org.br/AcervoDigitalDocumentosEPublicacoes/Apresenta%C3%A7%C3%A3o%20Sum%C3%A1rio%20Executivo%20PARPEL%202024.pdf"&gt;Executive Summary PAR/PEL 2024 - Cycle 2025-2029 (ONS)&lt;/a&gt;&amp;#160;&lt;a href="#fnref:17" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:18"&gt;
&lt;p&gt;&lt;a href="https://youtu.be/X_yfhHr45kI?si=77E5gqq9ZFqJYhMT"&gt;Webinar - Data Center Energy Planning (EPE)&lt;/a&gt;&amp;#160;&lt;a href="#fnref:18" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&amp;#160;&lt;a href="#fnref1:18" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&amp;#160;&lt;a href="#fnref2:18" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:19"&gt;
&lt;p&gt;&lt;a href="https://datacenters.microsoft.com/sustainability/efficiency/"&gt;Measuring energy and water efficiency for Microsoft data centers (Microsoft)&lt;/a&gt;&amp;#160;&lt;a href="#fnref:19" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:20"&gt;
&lt;p&gt;&lt;a href="https://archive.ph/mwd3E"&gt;Sabesp’s net accounting profit grows 80% in 1Q (UOL)&lt;/a&gt;&amp;#160;&lt;a href="#fnref:20" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:21"&gt;
&lt;p&gt;&lt;a href="https://web.archive.org/web/20250426202805/https://ri.sabesp.com.br/a-companhia/perfil/"&gt;Profile (Sabesp)&lt;/a&gt;&amp;#160;&lt;a href="#fnref:21" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:22"&gt;
&lt;p&gt;&lt;a href="https://www.ebc.com.br/especiais-agua/agua-no-brasil/"&gt;Where is the water in Brazil? (EBC)&lt;/a&gt;&amp;#160;&lt;a href="#fnref:22" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:23"&gt;
&lt;p&gt;&lt;a href="https://web.archive.org/web/20250624225321/https://www.researchandmarkets.com/reports/5983079/brazil-existing-and-upcoming-data-center-portfolio"&gt;Brazil Existing &amp;amp; Upcoming Data Center Portfolio (Research and markets)&lt;/a&gt;&amp;#160;&lt;a href="#fnref:23" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:24"&gt;
&lt;p&gt;&lt;a href="https://web.archive.org/web/20250503134116/https://www.estado.rs.gov.br/com-investimento-inicial-de-r-3-bilhoes-governo-do-rs-e-scala-data-centers-assinam-acordo-para-o-maior-projeto-de-infraestrutu"&gt;With R$ 3 billion investment, State and Scala Data Centers sign agreement for the largest digital infrastructure project in RS&lt;/a&gt;&amp;#160;&lt;a href="#fnref:24" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:25"&gt;
&lt;p&gt;&lt;a href="https://web.archive.org/web/20250515194246/https://www1.folha.uol.com.br/tec/2024/12/data-centers-pedem-retirada-de-protecao-de-direitos-autorais-da-regulacao-de-ia-no-brasil.shtml"&gt;Data centers request removal of copyright protections in AI regulation in Brazil (Folha)&lt;/a&gt;&amp;#160;&lt;a href="#fnref:25" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:26"&gt;
&lt;p&gt;&lt;a href="https://archive.ph/ZlbsS"&gt;Alckmin says government will launch Redata soon, policy to attract data centers and AI (Valor)&lt;/a&gt;&amp;#160;&lt;a href="#fnref:26" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:27"&gt;
&lt;p&gt;&lt;a href="https://www.gov.br/turismo/pt-br/assuntos/ultimas-noticia/governo-edita-mp-para-abertura-total-do-capital-estrangeiro-em-aereas-brasileiras"&gt;Government authorizes 100% foreign capital in Brazilian airlines (Ministry of Tourism)&lt;/a&gt;&amp;#160;&lt;a href="#fnref:27" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:28"&gt;
&lt;p&gt;&lt;a href="https://web.archive.org/web/20250529073914/https://www.chinadaily.com.cn/a/202505/28/WS683661cba310a04af22c1e31.html"&gt;Brazil looks to China for green push (China Daily)&lt;/a&gt;&amp;#160;&lt;a href="#fnref:28" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:29"&gt;
&lt;p&gt;&lt;a href="https://archive.ph/q9Txi"&gt;Lula government has already spent R$ 1.2 billion to repatriate Brazilian data and build a “sovereign cloud” (Folha de São Paulo)&lt;/a&gt;&amp;#160;&lt;a href="#fnref:29" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:30"&gt;
&lt;p&gt;&lt;a href="https://web.archive.org/web/20250202172125/https://www.baguete.com.br/noticias/nuvem-soberana-do-serpro-usa-google-cloud"&gt;Serpro’s sovereign cloud uses Google Cloud (Baguete)&lt;/a&gt;&amp;#160;&lt;a href="#fnref:30" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description></item><item><title>Modernizing the website using Bootstrap 5</title><link>https://giggio.net/en/blog/modernizando-o-site-a-partir-do-bootstrap-5/</link><pubDate>Wed, 02 Apr 2025 13:00:00 -0300</pubDate><author>giggio@giggio.net (Giovanni Bassi)</author><guid>https://giggio.net/en/blog/modernizando-o-site-a-partir-do-bootstrap-5/</guid><category>web</category><description>&lt;p&gt;In the last few days, I updated this site to &lt;a href="https://blog.getbootstrap.com/2021/05/05/bootstrap-5/"&gt;Bootstrap 5&lt;/a&gt;. I
took the opportunity to make several improvements and I want to share how that process went.&lt;/p&gt;
&lt;p&gt;More than just an update, I used this chance to enhance various parts of the site, including where it touches
&lt;a href="https://gohugo.io/"&gt;Hugo&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="bootstrap-5"&gt;
 &lt;a href="#bootstrap-5" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;Bootstrap 5&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;Migrating to Bootstrap 5 turned out to be much smoother than I expected. I thought it would be an extremely laborious
process, but in practice, it was pretty straightforward. There are countless articles and even the
&lt;a href="https://getbootstrap.com/docs/5.3/migration/"&gt;official documentation&lt;/a&gt; that explain every step, so I won&amp;rsquo;t dive too deep
into the technical details of the update itself. During the process, I realized I could improve other parts of the site,
so I decided to work on them as well.&lt;/p&gt;
&lt;p&gt;If you want to check out the update, the commit is
&lt;a href="https://github.com/giggio/giggionet/commit/484dd97a07340bfcda0d77bd5e7ccc899b3e1bad"&gt;on Github&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="ecmascript-modules"&gt;
 &lt;a href="#ecmascript-modules" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;EcmaScript modules&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;Bootstrap 5, released in mid-2021 (three and a half years after version 4.0), brought full support for ES Modules,
which are widely supported by 2025. Previously, my code loaded scripts and styles via CDN. That took advantage of users’
caches but ended up downloading the entire library, including unnecessary code and styles since not everything was used.
Plus, it didn’t use ESM.&lt;/p&gt;
&lt;p&gt;With ESM, I was able to apply tree shaking – the removal of unused code. Luckily, Hugo already offers an option to
perform this process, making everything extremely simple.&lt;/p&gt;
&lt;p&gt;I created a file called
&lt;a href="https://github.com/giggio/giggionet/blob/789839f6a6e14c4a14cd72c2ab5d2a1d48ad5d64/assets/js/vendor.mjs"&gt;vendor.mjs&lt;/a&gt; to
list only the dependencies I actually use. This is the file that gets compiled. It basically exports the libraries, like
this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Popover&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Tooltip&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;from&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;bootstrap&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Using Hugo’s &lt;a href="https://gohugo.io/functions/js/build/"&gt;js.Build&lt;/a&gt; method, the bundle is generated automatically. In short,
it looks like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go-html-template" data-lang="go-html-template"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;with&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resources&lt;/span&gt;&lt;span class="na"&gt;.Get&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;/js/vendor.mjs&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;$opts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dict&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;minify&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;hugo&lt;/span&gt;&lt;span class="na"&gt;.IsProduction&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;sourceMap&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;linked&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;format&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;esm&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;printf&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`&amp;lt;!-- Vendor file: %q --&amp;gt;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;js&lt;/span&gt;&lt;span class="na"&gt;.Build&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;$opts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fingerprint&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;sha256&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="na"&gt;.RelPermalink&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;safeHTML&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This file is then imported into my main script, the
&lt;a href="https://github.com/giggio/giggionet/blob/789839f6a6e14c4a14cd72c2ab5d2a1d48ad5d64/assets/js/script.mjs"&gt;script.js&lt;/a&gt;,
with a simple &lt;code&gt;import&lt;/code&gt;. It starts off like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Popover&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Tooltip&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;from&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;./vendor.mjs&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I simply import this file, without building it, which means the vendor file isn’t inlined. The challenge was that the
file name changes (because of the fingerprint), but I solved that with an
&lt;a href="https://developer.mozilla.org/docs/Web/HTML/Element/script/type/importmap"&gt;importmap script&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I started using Bootstrap installed via Npm, which made the process a lot easier because Hugo’s &lt;code&gt;js.Build&lt;/code&gt; uses
&lt;a href="https://esbuild.github.io/"&gt;esbuild&lt;/a&gt; to automatically search within the &lt;code&gt;node_modules&lt;/code&gt; directory.&lt;/p&gt;
&lt;p&gt;Another advantage is that the vendor file remains static, which allows browsers to leverage their cache extensively. It
only changes when I update the dependencies. When that happens, the compiled file name changes, solving browser cache
issues. The same goes for &lt;code&gt;script.mjs&lt;/code&gt;, though that one tends to change more often as new features are added.&lt;/p&gt;
&lt;p&gt;Fun fact: rewriting this script was the only part that Github Copilot managed to automate during this migration – and it
generated a bug that I fixed right away.&lt;/p&gt;
&lt;h3 id="sass-compilation"&gt;
 &lt;a href="#sass-compilation" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;Sass compilation&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;I had already been performing tree shaking and analyzing all of my Sass/CSS, but the Bootstrap CSS was loaded entirely
via CDN. Since Bootstrap’s Sass is already available in &lt;code&gt;node_modules&lt;/code&gt;, I started integrating it directly, eliminating
all of the &lt;strong&gt;unused Bootstrap CSS&lt;/strong&gt;. I lost the potential cache benefits from the CDN, but I believe the vendor file
will make up for that.&lt;/p&gt;
&lt;p&gt;The challenge was tweaking my unused CSS analysis function, as much of the CSS remaining in the final file (before tree
shaking) wasn’t written by me. This script cross-references the generated HTML with the CSS and points out unused
selectors, allowing me to remove them manually afterward. With a bit of Bash and open-source tools, I managed to sort it
out. If you&amp;rsquo;re interested, check out the
&lt;a href="https://github.com/giggio/giggionet/blob/789839f6a6e14c4a14cd72c2ab5d2a1d48ad5d64/package.json#L12"&gt;unused-css&lt;/a&gt;
script in the &lt;code&gt;package.json&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="goodbye-jquery"&gt;
 &lt;a href="#goodbye-jquery" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;Goodbye jQuery&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;I’m incredibly grateful to jQuery – it paid many of my bills and was a great companion – but by 2025, it just doesn’t
make sense anymore. While Bootstrap 4 depended on jQuery, Bootstrap 5 does not. So, I removed it.&lt;/p&gt;
&lt;p&gt;The problem was that the lightbox library I had chosen depended on jQuery, and I didn’t want to keep it around just for
that reason. I decided to replace it with &lt;a href="https://photoswipe.com/"&gt;Photoswipe&lt;/a&gt;, which already supports ESM, making
implementation easier. To import it, I used another script with &lt;code&gt;importmap&lt;/code&gt;, pointing directly to the CDN. And with
that, I also eliminated the extra files from the previous library, which required me to also deliver its images. Now,
everything comes from the CDN.&lt;/p&gt;
&lt;h3 id="switching-fontawesome-to-js--svg"&gt;
 &lt;a href="#switching-fontawesome-to-js--svg" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;Switching FontAwesome to JS + SVG&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;After optimizing Bootstrap’s JavaScript and CSS, I decided to tackle other areas of the site. I noticed that the web
font files for &lt;a href="https://fontawesome.com/"&gt;FontAwesome&lt;/a&gt; were quite large – with three groups of free fonts (solid,
regular, and brands), totaling over 300KB. On the other hand, FontAwesome offers an option to use JavaScript + SVG
instead of fonts, without changing the HTML. This approach even allows for tree shaking, as described in the
&lt;a href="https://docs.fontawesome.com/apis/javascript/tree-shaking"&gt;documentation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The biggest challenge was the documentation, which ended up being confusing, especially with the constant promotion of
premium icons. I&amp;rsquo;m considering switching to &lt;a href="https://icons.getbootstrap.com/"&gt;Bootstrap’s own icons&lt;/a&gt; or at least mixing
some of them in, but I&amp;rsquo;m still evaluating. Either way, figuring out how to use FontAwesome’s JavaScript APIs wasn’t
simple since there isn’t a complete example available.&lt;/p&gt;
&lt;p&gt;I used the same technique with the &lt;code&gt;vendor.mjs&lt;/code&gt; file, including only the icons I need. Creating this file correctly was
challenging, but with some Bash-fu, I managed to generate a file whose sole purpose is to import the icons I’m using –
the
&lt;a href="https://github.com/giggio/giggionet/blob/789839f6a6e14c4a14cd72c2ab5d2a1d48ad5d64/assets/js/fontawesome.generated.mjs"&gt;fontawesome.generated.mjs&lt;/a&gt;.
If you want to see how it was done, all of the project’s Bash code is concentrated in one file (and I promise I’ll write
a detailed post about it soon):
&lt;a href="https://github.com/giggio/giggionet/blob/789839f6a6e14c4a14cd72c2ab5d2a1d48ad5d64/nix/h#L492-L556"&gt;find it here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The generated file, in simplified form, looks like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;dom&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;library&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;from&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;@fortawesome/fontawesome-svg-core&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;faMastodon&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;@fortawesome/free-brands-svg-icons&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;faCheck&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;@fortawesome/free-solid-svg-icons&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;library&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;faMastodon&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;faCheck&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;dom&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;library&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In the actual file, lines 2 and 3 contain all the icons.&lt;/p&gt;
&lt;p&gt;Using it is super simple: I created another file called &lt;code&gt;fontawesome.mjs&lt;/code&gt;, which imports the generated file and contains
a single function responsible for initializing the icons:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;dom&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;from&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;./fontawesome.generated.mjs&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;initFontawesome&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// essa chamada inicializa os ícones da página inteira:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;dom&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;i2svg&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// essa chamada inicializa apenas os ícones dos
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// elementos recebidos e é usada quando um elemento
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// com ícone é adicionado dinamicamente à página:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;dom&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;i2svg&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Finally, this file is imported and exported in &lt;code&gt;vendor.mjs&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;You might have noticed that the &lt;code&gt;initFontawesome&lt;/code&gt; function optionally accepts a list of nodes. To avoid having to call
it manually everywhere, I created a
&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver"&gt;MutationObserver&lt;/a&gt; that triggers it whenever an &lt;code&gt;&amp;lt;i&amp;gt;&lt;/code&gt;
element is added to the DOM. So when that happens, the &lt;code&gt;&amp;lt;i&amp;gt;&lt;/code&gt; is removed by FontAwesome’s code and replaced with an
&lt;code&gt;&amp;lt;svg&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="dark-mode"&gt;
 &lt;a href="#dark-mode" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;Dark Mode&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;One of the reasons for updating to Bootstrap was its new implementation of light and dark themes, something essential
for me as a dark mode fan. My site’s all-white look was really bugging me, so this change was a top priority!&lt;/p&gt;
&lt;h4 id="moving-sass-variables-to-css"&gt;
 &lt;a href="#moving-sass-variables-to-css" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;Moving Sass variables to CSS&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h4&gt;
&lt;p&gt;The &lt;a href="https://getbootstrap.com/docs/5.3/customize/color-modes/"&gt;documentation&lt;/a&gt; for Bootstrap explains the process well,
and the implementation was very straightforward. The challenge was that my code, inherited from a theme, made heavy use
of &lt;a href="https://sass-lang.com/documentation/variables/"&gt;Sass variables&lt;/a&gt;. For an effective dark mode implementation, I needed
to migrate to
&lt;a href="https://developer.mozilla.org/docs/Web/CSS/CSS_cascading_variables/Using_CSS_custom_properties"&gt;CSS variables&lt;/a&gt;
(or, more accurately, &lt;strong&gt;CSS custom properties&lt;/strong&gt;). I carried out this migration
&lt;a href="https://github.com/giggio/giggionet/commit/26ca930e5169eb9b6c457cbab67afbce966023e0"&gt;in this commit&lt;/a&gt; –
and it was super easy.&lt;/p&gt;
&lt;h4 id="implementing-dark-mode"&gt;
 &lt;a href="#implementing-dark-mode" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;Implementing dark mode&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h4&gt;
&lt;p&gt;The implementation was super straightforward. Preferably, Bootstrap doesn’t rely solely on the
&lt;a href="https://developer.mozilla.org/docs/Web/CSS/@media/prefers-color-scheme"&gt;prefers-color-scheme&lt;/a&gt;
media query, since that would make it hard for
users to switch themes without altering their browser or OS settings. Instead, it uses the &lt;code&gt;data-bs-theme-value&lt;/code&gt;
attribute, which you can place anywhere (typically on the &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;With that done, you just need to create a section for default CSS variables (which in my case became the light theme),
and another for the alternative (dark) theme, something like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-scss" data-lang="scss"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nd"&gt;:root&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;--body-color&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="ni"&gt;white&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;--text-color&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="no"&gt;black&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;@include&lt;/span&gt;&lt;span class="nd"&gt; bootstrap&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;color-mode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ni"&gt;dark&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;--body-color&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="no"&gt;black&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;--text-color&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="ni"&gt;white&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The importance of migrating to CSS variables beforehand becomes clear here.&lt;/p&gt;
&lt;p&gt;The Sass &lt;code&gt;color-mode&lt;/code&gt; mixin basically inserts the content inside the mentioned attribute, like so:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-scss" data-lang="scss"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;@mixin&lt;/span&gt;&lt;span class="nf"&gt; color-mode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$mode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;data-bs-theme&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="nv"&gt;$mode&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;@content&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And the above code compiles to:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-css" data-lang="css"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nd"&gt;root&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;--body-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;white&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;--text-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;black&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;data-bs-theme&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;dark&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;--body-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;black&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;--text-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;white&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Since the &lt;code&gt;[data-bs-theme]&lt;/code&gt; attribute is defined on the &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt;, it has higher specificity than &lt;code&gt;:root&lt;/code&gt;, altering the
CSS variables and consequently, all the site’s colors.&lt;/p&gt;
&lt;p&gt;Bootstrap’s documentation provides a base code for this functionality, which I used and tweaked slightly. It only uses
the media query until the user opts to switch themes, ensuring a very flexible system.&lt;/p&gt;
&lt;p&gt;The biggest challenge wasn’t implementing dark mode, but finding a color scheme that I liked while still respecting the
visual identity of the light theme, especially for blog post readability. For that, the predominant background needed to
be black – for me, dark mode is truly black. On OLED screens, every turned-off pixel counts! No dark gray. In the end, I
liked the result, but I still need to live with it a bit longer to see if it really works for me.&lt;/p&gt;
&lt;p&gt;So, did you like dark mode? Any suggestions for improvement? Leave your comment down below!&lt;/p&gt;
&lt;h4 id="adapting-generated-themes-from-tools"&gt;
 &lt;a href="#adapting-generated-themes-from-tools" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;Adapting generated themes from tools&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h4&gt;
&lt;p&gt;Two widgets didn’t automatically adapt: the &lt;a href="https://giscus.app/"&gt;Giscus&lt;/a&gt; comments and Hugo’s &lt;a href="https://gohugo.io/content-management/syntax-highlighting/"&gt;syntax
highlighting&lt;/a&gt;. Both used fixed themes, but luckily they offer
customization options with just a few lines of JavaScript. Giscus provides an API to adjust the theme, while Hugo’s
syntax highlighting allows using external CSS – you just need to change the &lt;code&gt;href&lt;/code&gt; of the &lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt; that loads the
highlight CSS.&lt;/p&gt;
&lt;p&gt;I’m even thinking about offering users the option to change just the syntax highlighting theme, with a subtle screen
element that allows for this change. It’s the kind of customization that’s only possible when you have complete control
over the HTML, something that would be quite labor-intensive in WordPress.&lt;/p&gt;
&lt;h3 id="conclusion"&gt;
 &lt;a href="#conclusion" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;Conclusion&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;This journey took me about 30 hours, with a good portion of that time dedicated to research. If I had to do it again, it
would definitely be faster now that I’ve learned the parts of the work I didn’t know before. It was an extremely
enjoyable experience getting back into frontend development after a few years, without relying on SPA
libraries/frameworks or even TypeScript. Seeing how modern and accessible CSS and JavaScript have become was refreshing.
Perhaps soon, Sass won’t even be necessary. JavaScript handles a codebase of this size very well, especially with the
TypeScript compiler constantly checking the code, which ends up being a huge help.&lt;/p&gt;
&lt;p&gt;I still have other stories from this update to tell, but since they aren’t directly related to adopting Bootstrap 5,
I’ll save them for another post.&lt;/p&gt;
&lt;p&gt;So, did you know you could do all this with a static site generator? Were you aware of these new JavaScript and CSS
innovations? Drop a comment below and let me know what you think!&lt;/p&gt;
&lt;p&gt;It’s so much fun working with Hugo, Bootstrap, modern JavaScript, and CSS!&lt;/p&gt;</description></item><item><title>Under the hood: creating a site with Hugo</title><link>https://giggio.net/en/blog/under-the-hood-creating-a-site-with-hugo/</link><pubDate>Mon, 24 Mar 2025 15:00:00 -0300</pubDate><author>giggio@giggio.net (Giovanni Bassi)</author><guid>https://giggio.net/en/blog/under-the-hood-creating-a-site-with-hugo/</guid><category>web</category><description>&lt;h3 id="evaluating-the-options"&gt;
 &lt;a href="#evaluating-the-options" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;Evaluating the Options&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;When I decided to create a new website, the first option that came to mind was WordPress. I’ve been using it for many
years and I like it. Since I’m also well-versed in infrastructure, setting up a service in the cloud and running it is
very simple. It’s tempting.&lt;/p&gt;
&lt;p&gt;But there are performance challenges. While I maintained the Lambda3 site, we faced several problems. Initially, we
solved them with cache plugins, but over time, even that wasn’t enough, and we started using Cloudflare’s cache, which
in fact solved the problem definitively.&lt;/p&gt;
&lt;p&gt;It was a bit much: PHP + MariaDB + Cloudflare, all just to serve a static site. And it still needed plugins to write in
Markdown, which was what I wanted from the start. Wasn’t there a better solution?&lt;/p&gt;
&lt;p&gt;My main argument in favor of WordPress is its ecosystem of plugins, which is unbeatable. But for a static site, that
seemed excessive. I just wanted a site with a blog and a few sections. Did I really need all of that?&lt;/p&gt;
&lt;p&gt;Giving up WordPress meant having to deliver everything it and its plugins do. My minimum requirements included static
pages, a blog, and RSS. Along the way, other needs emerged: posts categorization and tagging, blocking generative AI
robots, image optimization, among others. Not to mention the common frontend needs, like compiling styles written in
Sass.&lt;/p&gt;
&lt;h3 id="choosing-hugo"&gt;
 &lt;a href="#choosing-hugo" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;Choosing Hugo&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;Decided to use a static site generator, I went to evaluate options. I wanted fast software, under constant development,
mature and free. It needed to meet my needs and offer flexibility for the future. &lt;a href="https://gohugo.io/"&gt;Hugo&lt;/a&gt; won, but
other good options were &lt;a href="https://astro.build/"&gt;Astro&lt;/a&gt;, &lt;a href="https://jekyllrb.com/"&gt;Jekyll&lt;/a&gt;, and
&lt;a href="https://www.getzola.org/"&gt;Zola&lt;/a&gt;. I evaluated others, but these were the ones I considered the most. It’s been a while
since I made the decision for Hugo; I started the project months ago, and it was shelved for a while.&lt;/p&gt;
&lt;p&gt;Hugo met everything I needed. With almost &lt;a href="https://github.com/gohugoio/hugo"&gt;80 thousand stars on GitHub&lt;/a&gt;, it is a tool
in constant update, with almost 9 thousand commits. Made in Golang, it has good documentation and an active forum for
questions.&lt;/p&gt;
&lt;p&gt;Moreover, it features &lt;a href="https://themes.gohugo.io/"&gt;hundreds of themes&lt;/a&gt; distributed across the internet. Since I’m not a
designer, that was essential. I chose the &lt;a href="https://github.com/StaticMania/roxo-hugo"&gt;Roxo&lt;/a&gt; theme, which I ended up
completely customizing. The only thing that will still give me trouble is updating Bootstrap 4 to version 5, something
that isn’t so trivial.&lt;/p&gt;
&lt;h3 id="whats-good-about-hugo"&gt;
 &lt;a href="#whats-good-about-hugo" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;What’s Good About Hugo&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h3&gt;
&lt;h4 id="development-experience"&gt;
 &lt;a href="#development-experience" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;Development Experience&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h4&gt;
&lt;p&gt;The development experience with Hugo is excellent. The built-in server, started with &lt;code&gt;hugo server&lt;/code&gt;, runs the site and
hot reloads whenever a file is changed. Changes in CSS don’t even reload the page; they are applied dynamically. On my
machine, a Markdown post takes 22ms to process, and the entire site half a second. On GitHub Actions, the build takes
2.5s, so YMMV. Template development is equally fast; you save and immediately see the result, without delay. The
experience is the same as developing a dynamic site with server-side rendering, and with immediate feedback, somewhat
better than working on an Angular or React SPA.&lt;/p&gt;
&lt;h4 id="flexibility"&gt;
 &lt;a href="#flexibility" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;Flexibility&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h4&gt;
&lt;p&gt;With Hugo, you have full control over HTML, CSS, and JavaScript. You don’t depend on whoever wrote a plugin to adapt it
to get to the result you want. There are plugins for Hugo (called modules), but the most common is to
find code snippets to customize as needed. Since it is very extensible, that’s enough. I really liked this approach,
which allows for enormous freedom.&lt;/p&gt;
&lt;h4 id="template-system"&gt;
 &lt;a href="#template-system" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;Template System&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h4&gt;
&lt;p&gt;Hugo uses HTML templates to structure the site and Markdown for content. This is useful because metadata remains in the
&lt;a href="https://gohugo.io/content-management/front-matter/"&gt;front matter&lt;/a&gt;, facilitating translations, RSS, and other features.&lt;/p&gt;
&lt;p&gt;Its template system is powerful. The templates are divided into &lt;a href="https://gohugo.io/templates/types/"&gt;types&lt;/a&gt;, such as
sections (lists of pages) and individual pages. There is support for partials, allowing for the componentization of
reusable elements like header and footer.&lt;/p&gt;
&lt;p&gt;The templates can generate any format, such as JSON, XML, or others. This is essential for features like
&lt;a href="https://giggio.net/pt-br/sitemap.xml"&gt;sitemap.xml&lt;/a&gt; and RSS.&lt;/p&gt;
&lt;h4 id="data-features"&gt;
 &lt;a href="#data-features" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;Data Features&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h4&gt;
&lt;p&gt;At the footer there is a section of podcasts I listen to and blogs I read. Both were built using a Hugo feature to
reference &lt;a href="https://gohugo.io/content-management/data-sources/"&gt;data sources&lt;/a&gt;. You can create CSV, JSON, TOML, YAML, and
XML files (I used YAML) and use them as a source.&lt;/p&gt;
&lt;p&gt;For example, creating a YAML data file for podcasts, like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Medo e delírio em Brasília&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;https://medoedelirioembrasilia.com.br/&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Hipsters.tech&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;https://www.hipsters.tech/&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And then using it, like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go-html-template" data-lang="go-html-template"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;with&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;hugo&lt;/span&gt;&lt;span class="na"&gt;.Data.podcasts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;.url&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;.name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="shortcodes-and-code-fences"&gt;
 &lt;a href="#shortcodes-and-code-fences" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;Shortcodes and Code Fences&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h4&gt;
&lt;p&gt;There are also &lt;a href="https://gohugo.io/content-management/shortcodes/"&gt;shortcodes&lt;/a&gt;, which are ways to easily create HTML,
like embedding a YouTube video, or creating code with &lt;em&gt;syntax highlighting&lt;/em&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-markdown" data-lang="markdown"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{{&amp;lt; youtube EFAe8W3n2ks &amp;gt;}}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The codes above were generated solely with &lt;a href="https://gohugo.io/content-management/syntax-highlighting/"&gt;&lt;em&gt;code fences&lt;/em&gt;&lt;/a&gt; —
those codes with three or four backticks in the markdown — and they use a shortcode under the hood. This is an example
of code fences with &lt;code&gt;yaml&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-markdown" data-lang="markdown"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;```yaml
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;```&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="taxonomy"&gt;
 &lt;a href="#taxonomy" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;Taxonomy&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h4&gt;
&lt;p&gt;Hugo has a taxonomy system, which is a way to categorize your pages. The most common, and what I’m using, are categories
and tags for blog posts, but you could use it to organize anything. I also used it to build pages for types of
contributions (for example, &lt;a href="http://giggio.net/participations/kinds/videos/"&gt;videos&lt;/a&gt; – check its code on
&lt;a href="https://github.com/giggio/giggionet/tree/main/content/participationkinds"&gt;Github&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;The example they use in the &lt;a href="https://gohugo.io/content-management/taxonomies/"&gt;documentation&lt;/a&gt; is with movies and actors.
Thus, a movie page can list its actors. Actor is the &lt;code&gt;taxonomy&lt;/code&gt;, the actor’s name becomes the &lt;code&gt;term&lt;/code&gt;. The cool thing is
that you can create pages for these classifications, both for the taxonomy and for the term. That’s how I have here the
&lt;a href="https://giggio.net/blog/categories/"&gt;categories&lt;/a&gt; page, &lt;a href="https://giggio.net/blog/tags/"&gt;tags&lt;/a&gt;, and each of their terms,
such as the tag &lt;a href="https://giggio.net/blog/tags/blog/"&gt;blog&lt;/a&gt;. The pages, the taxonomies, and the terms are data that you
can manipulate.&lt;/p&gt;
&lt;h4 id="build"&gt;
 &lt;a href="#build" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;Build&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h4&gt;
&lt;p&gt;During the build, all pages are generated, the entire site. With the dev server running you can choose whether to
rebuild everything or just the page that changed. It builds the lists of pages, the taxonomies, everything. It also
caches everything it did, such as image manipulations and files it may have downloaded.&lt;/p&gt;
&lt;p&gt;Hugo supports environments. In dev, it doesn’t optimize anything (and this is configurable). The prod build, on the
other hand, can do a series of things. Just look at what happens on my site:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Sass compilation, concatenation of all CSS, tree shaking (with PostCSS), minification, and fingerprinting.&lt;/li&gt;
&lt;li&gt;Optimization of all images, which are converted to webp and in some cases resized.&lt;/li&gt;
&lt;li&gt;HTML minification&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I also perform critical CSS analysis with the &lt;a href="https://github.com/addyosmani/critical"&gt;critical&lt;/a&gt; tool, which is run for
each individual page. I also generate search with &lt;a href="http://pagefind.app/"&gt;pagefind&lt;/a&gt; (you can try it
&lt;a href="https://giggio.net/en/search/"&gt;at /search&lt;/a&gt; – and I admit I spent little time on it, but it’s working).&lt;/p&gt;
&lt;p&gt;This happens locally, if I want to test, and also using the
&lt;a href="https://github.com/giggio/giggionet/blob/main/.github/workflows/build-hugo.yaml"&gt;workflow&lt;/a&gt; from GitHub Actions, which
also performs linting and spell checking (with &lt;a href="https://cspell.org/"&gt;cspell&lt;/a&gt;).&lt;/p&gt;
&lt;h4 id="other-features"&gt;
 &lt;a href="#other-features" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;Other Features&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Hugo supports multiple languages, and I’m using that on this site (click the little flag at the top in the header).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The index, at the beginning of this post, was also built using a page metadata, the
&lt;a href="https://gohugo.io/methods/page/tableofcontents/"&gt;.TableOfContents&lt;/a&gt; (check out
&lt;a href="https://github.com/giggio/giggionet/blob/b7be46d67eeb539459c4d51d5a16f15468f086d9/layouts/blog/single.html#L37"&gt;the source code&lt;/a&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You can use external resources that can be downloaded, such as an image, or a CSS file, or JSON, and then process them
and generate content from them. That’s how I’m generating my &lt;a href="http://giggio.net/robots.txt"&gt;robots.txt&lt;/a&gt; file (see the
source &lt;a href="https://github.com/giggio/giggionet/blob/b7be46d67eeb539459c4d51d5a16f15468f086d9/layouts/robots.txt"&gt;on Github&lt;/a&gt;),
like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go-html-template" data-lang="go-html-template"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;{{-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;with&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resources&lt;/span&gt;&lt;span class="na"&gt;.GetRemote&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;https://link/para/robots.json&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;-}}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;(notice the &lt;code&gt;try&lt;/code&gt;?)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The integration with Node.js resources is excellent, thanks to the ability to mount directories and files. You can
specify that any directory should be available for the site, and when accessing the resource, it copies the necessary
files. That’s how I’m doing it with the files from &lt;a href="https://fontawesome.com/search"&gt;Font Awesome&lt;/a&gt;, check out how
the
&lt;a href="https://github.com/giggio/giggionet/blob/b7be46d67eeb539459c4d51d5a16f15468f086d9/config/_default/hugo.yaml#L147C3-L155"&gt;configuration file&lt;/a&gt;
looks:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;module&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;mounts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;node_modules/@fortawesome/fontawesome-free&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;assets/css/fontawesome&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="what-could-improve"&gt;
 &lt;a href="#what-could-improve" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;What Could Improve&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;Hugo’s main problem is the lack of debugging. When you’re deep into template developing and something doesn’t work
as you’d like, ideally you would be able to debug the problem while the site is being generated, but the tool does not
offer that.&lt;/p&gt;
&lt;p&gt;We’re stuck with good and old writing to HTML approach, using the &lt;code&gt;debug.Dump&lt;/code&gt; instruction, when the result appears
directly in the HTML, or the &lt;code&gt;warnidf&lt;/code&gt; function, which generates a log in the terminal. It helps, but it’s an experience
that could be improved. I struggled a bit until I discovered that these existed, so here’s a tip for those who are
starting.&lt;/p&gt;
&lt;h3 id="conclusion"&gt;
 &lt;a href="#conclusion" class="site-blog-post-header"&gt;
 &lt;span class="site-blog-post-header-text"&gt;Conclusion&lt;/span&gt;
 &lt;i class="fa-solid fa-link site-blog-post-header-paragraph"&gt;&lt;/i&gt;
 &lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;I still have more to say about Hugo, but this post is already long. The main point is that Hugo is an excellent tool for
static sites. It perfectly solved my problem, it’s a pleasure to use, and from what I see, it will continue to serve me
for a long time.&lt;/p&gt;
&lt;p&gt;I still have more to talk about regarding the entire structure around it, such as how I used GitHub Pages, the use of
Nix to structure the tools, and much more, and that will also be saved for future posts.&lt;/p&gt;
&lt;p&gt;What do you think of Hugo? Comment below!&lt;/p&gt;</description></item><item><title>Returning to the World</title><link>https://giggio.net/en/blog/returning-to-the-world/</link><pubDate>Fri, 21 Mar 2025 11:00:00 -0300</pubDate><author>giggio@giggio.net (Giovanni Bassi)</author><guid>https://giggio.net/en/blog/returning-to-the-world/</guid><category>misc</category><description>&lt;p&gt;Fifteen years ago, I shut down my personal website and blog and redirected the domain &lt;code&gt;giggio.net&lt;/code&gt; to the website of the
company I had created, Lambda3. My blog,
&lt;a href="https://web.archive.org/web/20101014051650/http://unplugged.giggio.net/"&gt;.NET Unplugged&lt;/a&gt;, was transferred
to the company&amp;rsquo;s blog, with the posts integrated there.&lt;/p&gt;
&lt;p&gt;Today, one year and three months after leaving the company, I’m revisiting that — perhaps slightly outdated — idea of
writing on the internet autonomously, without depending on a platform or a billionaire’s social network.&lt;/p&gt;
&lt;p&gt;The first time I did this was about 25 years ago. Just like back then, this site has an &lt;a href="https://giggio.net/blog/index.xml"&gt;RSS feed&lt;/a&gt;,
reflecting my insistence on keeping the online world more anarchic and free of intermediaries. I still use RSS to access
a variety of content, and I believe everyone should give it a try: less doom scrolling and more deep reading. At the
bottom, in the footer, there’s a list of some of these sources, which I plan to expand into a “friends’ blogs” section
(another idea over 25 years old) and include other blogs I follow. There’s also a section listing some of the countless
podcasts I listen to. This initial version of the site is an MVP, and much more is coming.&lt;/p&gt;
&lt;p&gt;(For those who don&amp;rsquo;t know what RSS is: it&amp;rsquo;s a way to distribute content without depending on any platform. I use a free
RSS reader called &lt;a href="https://www.inoreader.com/"&gt;Inoreader&lt;/a&gt;. Try creating an account there and putting &lt;code&gt;giggio.net/en&lt;/code&gt;
there as your first feed to see how it works.)&lt;/p&gt;
&lt;p&gt;I’m creating this space to express my ideas in a more structured way. Besides blogging, I’ve been writing long posts on
BlueSky and Twitter for many years — a spontaneous form of expression. Over the last year, I felt the need for a space
to develop something more elaborate than a 15-minute thought. And that’s exactly what I’m doing here.&lt;/p&gt;
&lt;p&gt;If you’re here expecting me to talk only about technology, rest assured, I will. But I also plan to reflect on politics,
philosophy, and other topics that, frankly, I can’t yet predict. I’m already organizing the posts into categories, so if
you prefer to follow just one subject, that’s perfectly fine. And, of course, everything is available via RSS. I’ll also
use tags more freely — also with RSS support. I’ll always let you know on social media whenever I write something I find
interesting, so it’s worth following me on one of those platforms. Right now, I’m most active on BlueSky 🦋, where I go
by &lt;a href="https://bsky.app/profile/giggio.net"&gt;giggio.net&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For the first time, I’m also publishing in English. The whole site was built with support for this language, and I
plan to translate much of what I publish in Brazilian. Now you can share my posts with your friends and colleagues who
don’t speak our language.&lt;/p&gt;
&lt;p&gt;Between my personal blog and the one at Lambda3, I’ve published over 700 posts. I’m proud to have written most of them,
but unfortunately, they’re currently unavailable for reading, since the company’s old site is blocked and the posts
weren’t republished. On the other hand, many of them need to be reviewed — they’ve become outdated, or my opinions have
changed. I plan to review each one and re-edit the best ones, updating them to reflect what I think today and the
current state of technology. It’ll be an interesting process, both to have fun with my own youthful naivety and to see
where I got it right or wrong in my predictions. In any case, some content remains very relevant and deserves to see the
light of day again. And for those who didn’t know me many years ago, I believe many will enjoy reading these ideas.
Let’s see how it goes.&lt;/p&gt;
&lt;p&gt;This space starts off simple, with a few sections. I have an area where I list some of my most recent contributions to
the software community and related fields (with some older gems). There, you’ll find videos, interviews, podcasts,
lectures, and other content—useful for those who want to access material they might have missed. And, again, everything
is available via RSS. There’s also a section where I introduce myself, a contact page, a search function, and that’s it
for now.&lt;/p&gt;
&lt;p&gt;Soon, I’ll write a post explaining in detail how this site was built. I can already share the basics: all the content is
free and available on GitHub at &lt;a href="https://github.com/giggio/giggionet/"&gt;giggio/giggionet&lt;/a&gt;. The site was built
using a static site generator called &lt;a href="https://gohugo.io/"&gt;Hugo&lt;/a&gt;, which is absurdly fast and loaded with features. I’m
seriously impressed by the tool’s capabilities — it sometimes comes close to what a dynamic site can deliver. This site,
with around 200 files, is generated in 539 milliseconds, while the development server rebuilds this page in 21
milliseconds, with hot reload. An excellent user experience.&lt;/p&gt;
&lt;p&gt;I built the entire site with a focus on web standards and accessibility. It scores a perfect 100 on
&lt;a href="https://developer.chrome.com/docs/lighthouse/"&gt;Lighthouse&lt;/a&gt;, both on mobile and desktop. I’m not an accessibility
expert, but I had help from
&lt;a href="https://twitter.com/msales"&gt;Marcelo Sales&lt;/a&gt; from &lt;a href="https://tudoeacessibilidade.com.br/"&gt;tudoeacessibilidade.com.br&lt;/a&gt;, who
gave me some valuable tips on what to check. I’ll talk a bit more about that when I post about how the site was built.
If you’re blind or have low vision and notice any difficulty in reading or navigating the site, please let me know —
I’ll make sure to fix whatever isn’t working.&lt;/p&gt;
&lt;p&gt;Since the code is freely hosted on GitHub, the site is obviously hosted on GitHub Pages, which, when the site is public,
offers hosting, a custom domain and HTTPS certificate for free. This is so powerful that I plan to recommend it to many
people and organizations, like NGOs etc. In 2025, all you need to have a public site is a domain — the rest is free.
What a wonderful moment!&lt;/p&gt;
&lt;p&gt;And, since everything is on GitHub, I’m open to corrections, suggestions, and the like. Soon, I’ll add a link to make
this easier, something like an “edit this on GitHub” link on each page. The backlog will also be moved there soon, using
Github Issues.&lt;/p&gt;
&lt;p&gt;I’ll start by getting out of my head those posts I’ve been meaning to write for over a year. Besides diving deeper into
some technologies, I plan to elaborate on topics like home automation, Hugo, the PC I built last year, politics, and my
current state.&lt;/p&gt;
&lt;p&gt;Welcome to this (re)start. I hope you enjoy it. At the end of each post, there’s a comments section — feel free to join
in. It’s also based on GitHub (discussions), which helps avoid spam and, therefore, is free.&lt;/p&gt;
&lt;p&gt;See you in the next posts.&lt;/p&gt;</description></item></channel></rss>