Skip to content

Lesson 04 — Ian's Config Tour

Source: vendor/dotfiles-main/github.com/igray/dotfiles
Who: Ian Gray (@igray) — Marcus's friend
Machine: Framework 13 7040 AMD laptop
Channel: nixos-25.05 stable

Why Start Here?

Ian's config is the right place to start. It's a single machine, single user, using stable NixOS with a clear modular structure. Every concept is visible without being buried under abstraction.

Top-Level Structure

dotfiles-main/
├── flake.nix           ← entry point: defines inputs and outputs
├── flake.lock          ← pins every input to an exact commit
├── nixos/              ← system config (runs as root)
│   ├── configuration.nix
│   ├── hardware-configuration.nix
│   ├── audio.nix
│   ├── desktop.nix
│   ├── fonts.nix
│   ├── laptop.nix
│   ├── locale.nix
│   ├── restic.nix
│   └── wallpaper.nix
├── home-manager/       ← user config (runs as igray)
│   ├── home.nix        ← imports all the modules below
│   ├── alacritty.nix
│   ├── browser.nix
│   ├── colors.nix
│   ├── desktop.nix
│   ├── ghostty.nix     ← terminal emulator
│   ├── git.nix
│   ├── lf.nix          ← terminal file manager
│   ├── nixvim.nix      ← neovim via nixvim module
│   ├── packages.nix
│   ├── sh.nix          ← fish shell + starship
│   ├── starship.nix
│   ├── theme.nix
│   ├── tmux.nix
│   ├── wallpaper.nix
│   └── zk.nix          ← zettelkasten notes
└── bin/
    ├── restic-backups-nightly.sh
    └── restic-backups-work.sh

The Flake — Key Points

vendor/dotfiles-main/flake.nix

let
  vars = {
    username = "igray";
    terminal = "ghostty";
  };
  system = "x86_64-linux";
  unstable = import nixpkgs-unstable {
    inherit system;
    config.allowUnfree = true;
  };

Pattern: shared vars attribute set. Defining username and terminal once here avoids hardcoding them throughout all modules. Any module that receives vars can use vars.username or vars.terminal.

Pattern: pre-instantiated unstable. Rather than passing the raw nixpkgs-unstable input and letting each module instantiate it, Ian creates one unstable pkgs set and passes it. This is clean and efficient.

nixosConfigurations."nixos" = nixpkgs.lib.nixosSystem {
  specialArgs = { inherit inputs vars system; };
  modules = [
    nixos-hardware.nixosModules.framework-13-7040-amd
    ./nixos/configuration.nix
  ];
};

Pattern: nixos-hardware. The Framework 13 7040 AMD module from nixos-hardware sets hardware-specific options (power management, kernel params, firmware, etc.) so Ian doesn't have to figure them out manually. This is the right way to handle known hardware.

Pattern: specialArgs. This is how you pass things to modules that aren't in the standard module arguments (config, pkgs, lib). Here inputs, vars, and system are made available to every NixOS module.

Interesting Modules to Study

nixos/configuration.nix

The main system config. Worth reading in full. It handles: - Boot (systemd-boot with secure boot support) - Networking (NetworkManager, hostname) - Locale and timezone - Users - Imports of the other nixos/*.nix modules

home-manager/sh.nix

Fish shell configuration. Good example of how programs.* works in Home Manager. Compare this to a traditional ~/.config/fish/config.fish — the Nix version generates that file declaratively.

home-manager/git.nix

Git config via Home Manager's programs.git module. Generates ~/.gitconfig from Nix options. Cleaner than a manually maintained dotfile because it's version-controlled and reproducible.

home-manager/nixvim.nix

This pulls in the nixvim module from the flake input, which gives a huge set of Nix options for configuring Neovim. Ian also imports a second flake (nixvim-config) with his actual Neovim configuration. This is an example of using external flakes as composable config layers.

Hardware Configuration

nixos/hardware-configuration.nix is auto-generated by the NixOS installer. It contains: - Filesystem mount points and UUIDs - Kernel modules for your specific hardware - Detected file system types

Never hand-edit this file. If you reinstall or change disks, regenerate it with:

sudo nixos-generate-config --show-hardware-config

Backup Setup

Ian uses Restic for backups, managed via: - nixos/restic.nix — the NixOS restic service configuration - bin/restic-backups-nightly.sh and bin/restic-backups-work.sh — shell scripts called by the service

This shows that not everything has to be pure Nix — shell scripts still have a place, especially for operational tasks. The Nix module just wires the script into a systemd timer.

Caching

nix.settings = {
  substituters = [
    "https://cosmic.cachix.org/"
    "https://nix-community.cachix.org"
    "https://cache.nixos.org"
  ];
  trusted-public-keys = [ ... ];
};

Substituters are binary caches. Instead of compiling nixvim from source (which takes a long time), Nix downloads pre-built binaries from nix-community.cachix.org. The trusted-public-keys verify the cache is authentic.

cache.nixos.org is the official cache for everything in nixpkgs. The others are community caches for packages not in nixpkgs yet (like COSMIC desktop, nix-community projects).

Key Takeaways from Ian's Config

  1. One file per concern — each app or feature gets its own .nix file. Don't put everything in one giant file.
  2. vars for shared values — username, terminal, etc. defined once in flake.nix and passed down.
  3. nixos-hardware for known hardware — don't reinvent hardware quirks, use the community module.
  4. Standalone Home Manager — user config updated independently from system config.
  5. Two nixpkgs channels — stable for reliability, unstable instantiated separately for bleeding-edge packages.