Managing your NixOS configuration
The nix learning curve is steep, so many guides have been made about it to help you get started. I shall not reiterate what others have said many times before, but a lot of these tutorials leave out a critical detail:
ChatGPT is really good at nix.
You don’t need to know all the details of the nix language to manage your system with it. And after half a year of fairly intense nixing, I feel qualified to give you a quick, top-down, handwavy overview of how to structure a nix repo to set you and ChatGPT up for success.
Words you have to know
- flake: A nix project, containing all your nix code for managing your system. You can manage multiple machines and users in one flake.
- home-manager: Manages your user configuration like per-user software, and everything that would go into your home directory. It’s not built into nixOS but it’s the de facto standard and everyone uses it. Your home-manager configuration is part of your flake.
- nixpkgs: The nix package repository.
You will want to use home-manager for everything you can and keep the systemwide configuration to a minimum. A tricky problem you will encounter is that while systemwide configuration usually doesn’t care about users, your user config often cares about what machine it runs on. Thankfully we have ways to solve this :)
Flake structure
Let’s assume you have two machines, “SKYNET” and “OUTPOST”, and two users, “alice” and “bob” that have accounts on either machine, that you would like to manage with your flake.
Here is how I would go about structuring the flake:
├── assets
│ └── wallpapers
│ ├── wallpaper1.jpg
│ └── wallpaper2.jpg
├── config
│ ├── themes
│ │ └── mytheme
│ │ └── default.nix
│ └── types
│ ├── default.nix
│ ├── hostconfig.nix
│ └── theme.nix
├── home
│ ├── modules
│ │ ├── google-chrome
│ │ ├── git
│ │ ├── neovim
│ │ ├── vscode
│ │ └── zsh
│ ├── users
│ │ ├── alice.nix
│ │ └── bob.nix
│ ├── alice-OUTPOST.nix
│ ├── alice-SKYNET.nix
│ ├── bob-OUTPOST.nix
│ └── bob-SKYNET.nix
├── hosts
│ ├── OUTPOST
│ │ ├── hardware.nix
│ │ ├── hostconfig.nix
│ │ └── default.nix
│ └── SKYNET
│ ├── hardware.nix
│ ├── hostconfig.nix
│ └── default.nix
├── flake.nix
└── README.md
Folder: assets
Keep any large, static files in here, for example wallpapers.
Folder: hosts
This folder contains systemwide configuration for each host.
When you first install nixOS on a machine, it will generate a short
hardware.nix
file for you that contains low-level configuration
like file system partitioning. You are not supposed to edit this
file, so just move it unchanged into your flake.
hostconfig.nix
contains all information you want to share with your
user configuration, more on this later.
default.nix
contains all the other systemwide configuration that
you set yourself, and it imports hardware.nix
and hostconfig.nix
.
Folder: home/modules
This folder contains reusable modules for your user configuration.
For example, here is a minimal module for git
:
# home/modules/git/default.nix
{ pkgs, config, lib, ... }:
{
programs.git = {
enable = true;
delta.enable = true;
extraConfig = {
core.askPass = "";
rerere.enabled = true;
push.autosetupRemote = true;
fetch.prune = true;
pull.rebase = true;
diff.algorithm = "histogram";
};
};
}
Folder: home/users
This folder contains per-user configuration. Usually files in here
are just big import statements, refrencing all the modules you
defined in home/modules
, and perhaps simple installs that don’t
justify a whole module.
For example, here is a minimal user configuration for “alice”:
# home/users/alice.nix
{ pkgs, config, lib, ... }:
{
imports = [
../modules/git
../modules/neovim
../modules/vscode
../modules/zsh
];
home.packages = with pkgs; [
ripgrep
python3
];
}
home/*.nix
These files contain per-user, per-host configuration. They will
import a user from home/users
and host-specific configuration
from hosts/<hostname>/hostconfig.nix
.
# home/alice-SKYNET.nix
{
pkgs,
inputs,
...
}:
{
imports = [
../config/types
../config/themes/mytheme
../hosts/SKYNET/hostconfig.nix
./users/alice.nix
];
programs.zsh.shellAliases.rebuild = "home-manager switch --flake ~/dotfiles#flo-schnitzelwirt";
programs.zsh.shellAliases.rebuildsystem = "sudo nixos-rebuild switch --flake ~/dotfiles#schnitzelwirt";
}
Folder: config/types
This folder contains type definitions for user-defined data you want to pass around your project; for example theme definitions, or data you want to share between systemwide and user configuration.
Here is an example of this shared data; I’m using it to define how large my primary monitor is:
# config/types/hostconfig.nix
{ lib, ... }:
let
inherit (lib) mkOption types;
in
{
options.hostconfig = {
primaryMonitor = {
width = mkOption {
type = types.int;
};
height = mkOption {
type = types.int;
};
hz = mkOption {
type = types.int;
default = 60;
};
};
};
}
Note that you define types on options.foo
; after you have done so,
you can store data in config.foo
if you import the type definition.
Your machine configuration can then set these values:
# hosts/SKYNET/hostconfig.nix
{ ... }:
{
config.hostconfig = {
primaryMonitor = {
width = 1920;
height = 1080;
hz = 144;
};
};
}
Folder: config/themes
This folder contains theme definitions. Completely optional but I feel like theming is integral to most nix setups so I’ve included it here. I store a couple of color palettes in there aswell as which wallpaper to use.
flake.nix
This is the entrypoint to your flake. It will define your inputs (nixpkgs, home-manager, etc) and outputs (your hosts and users).
I will not show you a flake.nix here because instead I will link to my dotfiles; it has a flake.nix you can check out to see how it looks like in practice.
So I invite you to scroll back up once more, and look at the folder structure; if you understand how all the files interact, you should be able to understand my flake.nix, and most system configuration flakes you will encounter in the wild.
If you’re entertaining the idea of dumping your current setup
to switch to nixOS, I highly recommend you do so. After working with
it for half a year I have no doubt that nixOS is the future of
linux distributions, especially in the LLM era. Having your entire
configuration of everything in a git repo is just so much superior
to cobbling together an unsatisfying ubuntu with 50 bash scripts
that get lost after each OS upgrade. Gone are the days of node version
manager and installing 15 versions of the java JDK too, or downlading
AppImages.. I have not missed running apt install
either, as all
the packages on apt are outdated and most software is missing.
The nix ecosystem is HUGE on the other hand and it’s extremely
rare that I can’t find a package for something I want to install.
Setting up nixOS will take you a weekend or three but it is oh so worth it; it will take a crazy value proposition to get me to switch to any other distro in coming years. The nix evangelists tend to go a bit overboard with theming and all that but you can really just use nixOS and rock the default GNOME desktop and be perfectly happy. You will reap the benefits of nixOS regardless!
Anyway. I hope this guide was helpful to you; and see you on the other side!