A Modern and Secure Desktop Setup

It took me quite a while to get to this setup so I thought I would write a guide to help others and also to discuss feedback and other desktop setups.

I’m currently running NixOS on my Surface Pro 6 intel tablet/laptop and an 8-9 years old custom gaming tower PC with intel + nvidia. I did a fresh install with btrfs on the tablet and I had an ext4 LUKS1 setup running on the tower for a while.

For those who want to jump ahead and just look at the setup, here it is. Everything is open, except for one encrypted file which I’m testing for keeping secrets and sensitive info. The encryption is done via git-crypt and once it’s setup it’s transparent when using git. Feel welcome to fork it.

The setup is LUKS2 encrypted Btrfs root with a swapfile, auto unlock with TPM2, secureboot and a BIOS password.

During the first-time installation if you’re inexperienced with partitioning you will probably select the auto option. It is better to manually set it up for btrfs, because the automatic setup will use ext4. If you’re ok with using ext4, that’s also perfectly fine, but btrfs has some nice features to avoid data corruption or back up your data and roll back.

If you already have a system installed with ext4, here’s an easy to follow guide to convert to btrfs.

During the installation you should select disk encryption which will set it up for you, however it will use LUKS1, which doesn’t work with the TPM unlock. This simple guide shows you how to upgrade it to LUKS2 and switch to a more secure algorithm after the installation while you’re still on the live USB.

After rebooting into the installed system, I would start a nix-shell with git and git-crypt, clone my repo to my home directory, and unlock git-crypt with a key that I exported from another machine. The repo has a top level flake.nix which contains configurations for both of my computers based on the hostname, my home manager setup, and even the shell setup for the repo, note that using nix-shell to clone works just as well.

Assuming that this is the first time I’m bringing up a new device, I would just create a folder for it within “devices” and add a conf.nix and a hw.nix. I would copy the contents of the auto generated /etc/nixos/hardware-configuration.nix into the local hw.nix. For the conf.nix, something like this:

{ config, pkgs, pconf, nixos-hardware, ... }: {
  imports = [
  networking.hostName = "${pconf.user}-my-device";
  # Swap
  swapDevices = [ { device = "/var/swapfile"; size = 10*1024; } ];  # 10 = 8 GB RAM + 2 buffer
  boot.resumeDevice = "/dev/dm-0";  # the unlocked drive mapping
  boot.kernelParams = [
    "resume_offset=#########"  # for hibernate resume
  system.stateVersion = "23.11";  # Do not change

The swapfile will be automatically created based on the config, the resume device can be found with ll /dev/mapper/luks* and there is a command in my README in the linked repo for finding the resume offset.

Then add a new machine config in the flake which imports this conf.nix, like I did for the other 2 devices. If the device already had a working config and needs to be brought up after a fresh clean reinstall, then there is no need to recreate these 2 config files or to re-add it to the flake.

Then I would remove both files from /etc/nixos/ and create a symlink of my flake to /etc/nixos/flake.nix. There is also a symlink for my home manager configuration. The commands for both are in my README.

At this point we’re still missing the secure boot signing keys, so the setup won’t build. Here’s a short guide to set up secureboot. First I would open a nix shell with “sbctl” and run the key generation command, then the build should already work. If you set up the symlinks, using the standard sudo nixos-rebuild switch works.

Enrolling the secureboot key was a different process for my two machines. On the surface I just ran the enroll command and it just worked after that. On the tower I had to turn ON the Windows 10 WHQL settings in BIOS, switch a newly appeared setting from CSM to UEFI and then put secureboot in a setup state, then reboot and run the enroll command. After enabling secureboot I put a BIOS password on both machines, but I’m not sure if that significantly improves security.

After enrolling the keys and enabling secureboot, make sure to reboot and verify that it works according to the guide.

Finally I enrolled the disk decryption key in the TPM chip using pins 0, 1, 2, 3, 7. The cryptenroll command and another command to find the name of your encrypted partition are both in my README.

At this point, you should be able to reboot with secureboot enabled and only have to enter your login password, not encryption password. Depending on your hardware, this setup is fairly secure. As long as your nix configuration doesn’t install malicious software of course. If an attacker tampers with your unencrypted boot, secureboot will fail, and if they disable secureboot or load a live USB the TPM won’t decrypt your root partition, so they won’t be able to access your data. If they steal your laptop, modify your boot partition, get past your BIOS password, disable secure boot, and return your laptop without you noticing, then you unknowingly turn it on, not notice that secure boot is off, then enter the encryption key to unlock your drive, then they could gain access to your data. Or using the cold boot attack…

After you confirmed that everything works, nix-collect-garbage -d can free up some space and make your boot menu cleaner by erasing old boot generations and unused packages.

Note, the declarative email/calendar in the config doesn’t work, hopefully in the future it will. I would also like to add impermanence to this setup, but I’m not there yet.


I’ve had many a rant about git-crypt in NixOS land, but it sounds like you’ve thought this through. Do you consider the fact that your secrets end up with o+rwx unproblematic?

Is it worth at least caveating for people following along that you shouldn’t use remote builds/caches with this kind of setup? That you should not store secrets that shouldn’t cross the UID barrier with git-crypt?

I still think agenix/sops-nix/scalpel should always be preferred over git-crypt for secrets in NixOS configurations…

Hey, thanks for the feedback. What you said makes a lot of sense, and I agree, git crypt should not be used for real secrets. In this case I just used it for privacy for my email address, name, username, etc since I wouldn’t want necessarily everyone who looks at my repo to see which email provider I use, though it was more of an exercise, since my name and email are visible here or on GitHub anyway.

It’s a good point to avoid pushing to a public cache when your build contains plain text secrets and if there’s someone else using your system they could potentially find it in the nix store, though to be fair the latter scenario sounds a bit unlikely.

The options you linked here definitely look like a better alternative for servers and similar but you can’t use them for everything, eg the host name. I don’t use SSH keys that much on my machines apart from GitHub and I’m not sure if I care enough about that to include it in my build.

I also thought about using Bitwarden CLI for secrets since I use it anyway for my passwords. That way the secrets don’t get into the builds at all, but I imagine there can be downsides to that as well. It would at least make a lot of sense for my email configuration, since it’s better for those to always be up to date, it doesn’t make sense to roll it back.

You absolutely can, though it takes some custom module writing and runtime evaluation here and there. Especially scalpel is specifically designed to overcome those limitations.

That said, if the information is just confidential rather than secret, yep, git-crypt makes more sense!

You can alternatively use gpg or age keys. But yeah, I appreciate that there’s a population of users for whom any encryption is a significant barrier to entry. I’ve previously talked about perhaps setting up a secret management standard that doesn’t require it, but just formalizes how secrets are stored outside the nix store.

To be clear, we’re still talking about email addresses and other sensitive information, rather than encryption keys, passwords and other secrets in the security sense of the word here?

The home-manager modules and such simply are not designed to take that information from anything but plaintext NixOS modules, because their authors don’t consider that information confidential.

Git-crypt is an ok solution for this. The other alternative I prefer is to keep a separate flake in a private (perhaps even offline) repo that you depend on in your public flake which just holds that data - no encryption required. You can also just choose not to make your repositories public in the first place.

The “correct” solution would be to write your own modules that do treat the data confidentially, so that users can supply it any way they like (such as through bitwarden at runtime), and perhaps upstream it as an alternative.

In the last case I meant using Bitwarden in the email configuration password command instead of adding the password to the build in any form.

Also, do you know if it is possible to make the home manager email/calendar system work with the gnome apps?

I learned about TPM2 :+1: Thanks for the guide! Quite useful to have a kind of checklist of all elements that go into a secure desktop.

1 Like

Never experimented with that, sorry. There’s probably a way, but may not be implemented.

1 Like