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
7 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


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 ~1 month of using NixOS full time. I’ve been gradually bolting on new features and functions as I’ve gotten more comfortable with Nix, and finally decided to share my config publicly. Keep in mind I’m still relatively new to Nix, so much of this might look janky (and it is). I’m still learning!

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 three files and three folders:

aires@Dimaga ~/Development/nix-configuration (main) $ ls --group-directories-first -gh
total 24K
drwxr-xr-x 1 users   78 Mar  1 16:29 hosts
drwxr-xr-x 1 users   94 Mar  1 16:29 modules
drwxr-xr-x 1 users   30 Mar  2 12:56 nix-secrets
-rw-r--r-- 1 users 8.3K Mar  2 11:51 flake.lock
-rw-r--r-- 1 users 2.6K Mar  2 12:50 flake.nix
-rw-r--r-- 1 users 3.3K Mar  1 16:29 README.md

hosts contains host-specific configuration files, as well as common configurations shared across hosts. Everything else is defined in modules, from applications to services to user files. When creating this folder, I tried to follow the Nix Options syntax. In other words, every module is imported, but you enable or disable specific modules in the host config. For example, there’s an entire module for installing and configuring Gnome 🔗, but to actually enable Gnome on a host, you need to add:

host.ui.gnome.enable = true;

Managing secrets 

nix-secrets isn’t just a folder: it’s a submodule. It’s where I store parts of my config that I’m not comfortable sharing. It points to a private repo that requires an SSH key, so it will likely fail if you try to run nixos-rebuild. You can remove the reference to this repo by editing hosts/common/default.nix and removing or commenting out line 13, or point to your own secrets submodule:

{ lib, ... }: 
let
  # Fetch secrets
  # IMPORTANT: Make sure this repo exists on the filesystem first!
  nix-secrets = builtins.fetchGit {
  	url = "/path/to/secrets/folder";
    ref = "main";
    rev = "55fc814d477d956ab885e157f24c2d43f433dc7a";
  };
in{
  imports = [
    ../../modules
    #"${nix-secrets}/default.nix"
  ];
}

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 🔗 running, then found out I can pull in files from other folders as long as the folder is a Git repo.

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. Since it’s just me using these systems, I’m not worried about it, but be wary if you use this method on a shared system.

The upside is that it’s very easy to manage secrets, even compared to more through options like Agenix 🔗 or Sops-nix 🔗. The tradeoff is less security.

Still, if you want to use this method yourself, there’s three things you need to do:

  1. Edit .gitmodules and change the URL of the nix-secrets submodule to point to your own private Git repo.
  2. Edit hosts/common/default.nix and change the URL to point to the submodule’s path on your filesystem. As far as I know, the URL must be an absolute path, so you can’t just use ../../nix-secrets. Trust me, I tried.
  3. After commiting your secrets to your nix-secrets repo, get the branch name and hash of the latest commit 🔗 and paste it into nix-secrets.rev. Nix Flakes requires you to reference a specific commit to ensure it uses the exact same files each time. Otherwise, you could end up with two different outputs if you run nixos-rebuild twice in between updating your secrets. It’s extra work and kinda annoying, but it makes sense from a reproducibility standpoint.

If you do swap out the submodule, make sure you initialize it and pull down the files by running:

git submodule update --init --recursive

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 like to live dangerously have the latest and greatest software, so I follow the nix-unstable package repo by default.

# Configure nixpkgs
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";

Each of my hosts gets a separate folder under hosts/. Normally each host just has two files: hostname.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 hosts/common/default.nix. This single file is the linchpin linking hosts to modules. It imports modules/default.nix, which in turn imports everything under modules/. It also imports nix-secrets, the submodule where my secrets are stored. Like I mentioned earlier, you can comment this line out (or remove it entirely) if you want to try building this config yourself.

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.

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.

# Example configuration for my Surface Laptop. This installs a bunch of app categories, Gnome and Flatpak, and adds the aires user with Syncthing enabled.
host = {
	role = "workstation";
	apps = {
		development.enable = true;
		hugo.enable = true;
		media.enable = true;
		office.enable = true;
		pandoc.enable = true;
	};
	ui = {
		flatpak.enable = true;
		gnome.enable = true;
	};
	users = {
		aires = {
			enable = true;
			autologin = true;
			services = {
				syncthing = {
					enable = true;
					autostart = true;
					enableTray = true;
				};
			};
		};
	};
};

Host roles 

The eagle-eyed among you might’ve noticed the host.role attribute. This is something I came up with to try and create sane defaults for different hosts depending on how I use them. The two options right now are workstation and server. Workstations are devices I use for day-to-day work (my Lenovo Legion and Surface laptops), and servers are…well, servers. These don’t have GUIs, have more verbose logging, and have some shared background services. Right now the difference is pretty much negligible, but at some point I might add more.

Additional thoughts 

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.

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 statix 🔗. Using statix is simple: just install it and run it using:

statix check /path/to/nix-configuration

It’ll recursively look for any .nix files and print out a report of its findings to the terminal. If you’re feeling brave, you can have statix make edits directly to your files using:

statix fix /path/to/nix-configuration

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.

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: "Installing a Custom Package With Nix"Next: "The Antidepressant That Changed My Life"
atmospheric breaks breakbeat buddhism chicago code disco 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