My Nixos Configuration
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:
- Edit
.gitmodules
and change the URL of the nix-secrets submodule to point to your own private Git repo. - 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. - After commiting your secrets to your
nix-secrets
repo, get the branch name and hash of the latest commit 🔗 and paste it intonix-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 runnixos-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" |