My Nixos Configuration

3D illustration of an approach to a wall of a spaceship with complex metallic structure in blue and purple.
BlogLink to blog
Blog
6 min read

A walkthrough of my latest NixOS configuration using Flakes, Home Manager, and a poor man's secrets management solution. Photo by Alejandro Miranda from PxHere: https://pxhere.com/en/photo/1596629


Contents


Note: this blog was originally published March 2, 2024.

Introduction 

I’m officially a NixOS fanboy. I’ve seen the light, and it is declarative, idempotent, and reproducible. If you don’t know what I’m talking about, check out my earlier blog on what NixOS is and why it’s a big deal 🔗. The short answer is: NixOS lets you define your entire system’s configuration in plain text, so you can very easily rebuild any system from scratch.

In this blog post, I want to show off my configuration that I’ve built over almost a year of using NixOS full time. I’ve been gradually bolting on new features and functions as I’ve gotten more comfortable with Nix, and now that I’ve had time to refine my configuration, I’m excited to share it. A lot of this might look janky (and it is), but it’s worked well for me so far, and I hope it can give you some inspiration for your own Nix journey!

Pulling my configuration from GitHub 

My configuration is hosted publicly on GitHub. You can check it out here 🔗, or if you’re feeling brave, clone it:

git clone https://github.com/8bitbuddhist/nix-configuration.git

You should see four folders and three files:

aires@Khanda ~/Development/nix-configuration (main) $ ls --group-directories-first -gh
total 32K
drwxr-xr-x 1 users   84 Oct 18 11:29 bin
drwxr-xr-x 1 users  168 Oct  6 13:42 hosts
drwxr-xr-x 1 users  150 Oct 16 16:07 modules
drwxr-xr-x 1 users  180 Oct 17 10:51 packages
-rw-r--r-- 1 users  13K Oct 18 11:31 flake.lock
-rw-r--r-- 1 users 4.3K Oct 17 13:41 flake.nix
-rw-r--r-- 1 users 6.3K Oct 18 11:29 README.md

Let’s start with the folders:

Managing secrets 

Some of the data in my configuration is sensitive, but necessary. For example, SSH configurations, hashed user account passwords, domain names, etc. These are stored in the modules/secrets folder, and I’m using git-crypt 🔗 to encrypt them. When decrypted, this is just a regular nix file containing an option with default values (those default values being my secrets). You can drop in your own secrets module using whatever transparent encryption method you prefer. Just keep in mind that any secrets you define using this method will become world-readable in the Nix store.

I took this approach because I feel it’s a good balance between ease-of-use and security. I tried (and failed) to get Sops-nix 🔗 and Agenix running, and I wanted to do weird stuff like encrypt non-Nix files. Git-crypt makes this entire process transparent.

The downside to this approach is that it’s not 100% secure. When running nixos-rebuild, Nix handles my secrets files the same way it does any other file, which means storing it in /nix/store as a plain text, world-readable file. Anyone with access to my computers, including other users, can read my secrets files by browsing the Nix store. I’m the only one who admins these systems, so I don’t mind the risk, but be wary if you use this method on a shared system.

The entrypoint: flake.nix 

The main entrypoint to this configuration is flake.nix. Here’s where I import a bunch of external Flakes including Home-manager, Nix-flatpak (for declarative Flatpaks), and others. I use the stable NixOS branch (currently 24.05), but I also have the unstable repository available in case I want newer software. For stable packages, I can use pkgs.<packagename>, and for unstable packages, I can use pkgs.unstable.<packagename>. Check it out here 🔗.

nixpkgs.overlays = [
	(final: _prev: {
		# Allow packages from the unstable repo by using 'pkgs.unstable'
		unstable = import inputs.nixpkgs-unstable {
			system = final.system;
			config.allowUnfree = true;
		};
	})
];

Each host gets a separate folder under hosts/. Normally each host just has two files: default.nix and hardware-configuration.nix. In some cases, like with Shura 🔗, I have some extra files that get deployed specifically to that host.

Each host imports a set of default modules defined in flake.nix 🔗. This set includes autoimport.nix, which is responsible for automatically importing every Nix file in the modules folder.

The modules 

Each aspect of my systems are split out into different modules. For the most part, these modules are self-contained, but some reference (or require) others. For example, enabling Gnome indirectly enables both audio and Flatpaks 🔗. This is mostly to make sure I don’t forget something critical when enabling/disabling certain features.

Module options follow a proposed template created for Auxolotl 🔗, a sort of spin-off distribution of NixOS. The top-level namespace for options is aux.system, and the modules form a hierarchy beneath that. For example, here’s the configuration for my Microsoft Surface Pro laptop:

aux.system = {
    # Enable to allow unfree (e.g. closed source) packages.
    # Some settings may override this (e.g. enabling Nvidia GPU support).
    # https://nixos.org/manual/nixpkgs/stable/#sec-allow-unfree
    allowUnfree = true;

    apps = {
      development.enable = true;
      media.enable = true;
      office.enable = true;
      recording.enable = true;
      social.enable = true;
      writing.enable = true;
    };

    # Enable Secure Boot support.
    bootloader = {
      enable = true;
      secureboot.enable = true;
      tpm2.enable = true;
    };

    # Change the default text editor. Options are "emacs", "nano", or "vim".
    editor = "nano";

    # Enable GPU support.
    gpu.intel.enable = true;

    # Change how long old generations are kept for.
    retentionPeriod = "14d";

    services = {
      autoUpgrade = {
        enable = true;
        configDir = config.secrets.nixConfigFolder;
        onCalendar = "weekly";
        user = config.users.users.aires.name;
      };
      virtualization.enable = true;
    };

    ui = {
      desktops.gnome = {
        enable = true;
        experimental = {
          tripleBuffering.enable = true;
          vrr.enable = true;
        };
      };
      flatpak = {
        # Enable Flatpak support.
        enable = true;

        # Define Flatpak packages to install.
        packages = [
          "com.github.tchx84.Flatseal"
          "com.github.wwmm.easyeffects"
          "md.obsidian.Obsidian"
          "org.keepassxc.KeePassXC"
          "org.mozilla.firefox"
        ];

        useBindFS = true;
      };
    };

I tried to follow the NixOS options 🔗 syntax when making these. Like I mentioned, all modules are imported by default, and you selectively enable them for each host. Some modules have additional options, like Duplicacy Web 🔗, which requires a directory for storing its own configuration. Otherwise, it’s a simple true or false.

Beautifying the code 

The last thing I did before making this public is to try and clean up my Nix code and follow standard formatting. For that, I used a linter called nixfmt 🔗. Using nixfmt is simple: just specify the linting style you want to use in your flake.nix file, then run:

nix fmt

It’ll recursively look for any .nix files and adjust their formatting automatically. This will edit and save your files, so I’d only recommend doing this if you have a backup of your config files, or you’ve checked them into your repository already! You can also add it as a Git hook 🔗 so it runs whenever you commit files to your repo.

Building your Nix skills 

Sometimes the best way to learn about Nix is to poke around other people’s configurations and see how they do things. That’s how I got this far, and I appreciate everyone in the community brave enough to post their configurations online for others to read.

Want to check out other people’s configurations, or learn more about how to write your own? My NixOS 🔗 is a great resource, but so is simply searching “nixos configuration github” in DuckDuckGo or your preferred search engine. You can also check out the nixos-configuration topic in GitHub 🔗 to browse repos.

Most importantly, have fun! There’s a lot to learn with Nix, so try not to feel too overwhelmed. Take your time, make iterative changes, and remember to commit and back up your files frequently! One of NixOS’ strengths is that you can always open a previous generation, so even if you completely mess up your config files, you can revert back just by rebooting (assuming you didn’t delete your past generations).

Previous: "Why is enabling automatic updates in NixOS so hard?"
art atmospheric breaks breakbeat buddhism chicago code disco fediverse fiction funk furry house house music kubernetes lgbt linux logseq mastodon mental health movies music nixos obsidian personal philosophy pkm poetry prompt second life social software soul technology writing