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!