DEV Community

Ray Harris
Ray Harris

Posted on

Day 5: NixOS & Git - version control for config.nix

In my last post, I finally got NixOS dualbooting successfully on my Macbook Pro alongside macOS. In this post, I'll set up my desktop to dualboot NixOS and Windows 10. I'll also configure a github repository to track and sync my NixOS configuration for both of my devices.

My desktop PC is an aging mini-itx gaming machine with parts matching this pcpartpicker list. Needless to say, the config will be slightly different for this pc than my macbook pro. Let's see how the magic of Nix makes it easy to manage both with the same code.

I imagine the organization of my nix config will continue to evolve over time, but since it's all code and I'll be using version control, I feel pretty happy to experiment. The structure I'll be implementing today is pictured below and the actual configuration can be seen in the v0.2.0 tag on my github repo.

/home/ray/nix-config/
├── README.md
├── common-configuration.nix
└── hosts
    ├── nixos-mbp
    │   ├── configuration.nix
    │   ├── hardware-configuration.nix
    │   └── firmware
    └── nixos-desktop
        ├── configuration.nix
        └── hardware-configuration.nix
Enter fullscreen mode Exit fullscreen mode

Basic tools first

I use the Github desktop client. I admit it. I like it. I love it. I hate command line git. Sourcetree confuses me. I will grow one day. But today, I'm getting stuff done, not fighting for street cred.

I boot into the my Plasma NixOS environment that I set up for my Macbook Pro (as detailed in the previous post) and log in with my user account. I open Konsole from the start-like menu and run

sudo nano ../../etc/nixos/configuration.nix
Enter fullscreen mode Exit fullscreen mode

I scroll toward the bottom and add two system packages, so that my systemPackages config looks like this:

  environment.systemPackages = with pkgs; [
      git
      (vscode-with-extensions.override {
        vscodeExtensions = with vscode-extensions; [
          bbenoist.nix
        ];
      })
      github-desktop
  ];
Enter fullscreen mode Exit fullscreen mode

There's some explanation of how this works in my Day 2 Post and it's kind of easy to understand, so I won't go into details in this post. With that, I do a quick sudo nixos-rebuild switch and boom I've got VSCode with Nix syntax highlighting as well as Github desktop installed. I'm ready to do this.

Version control for Nix Config

One does not simply initialize a git repo in /etc/nixos. This is a protected area, where changes require root access. From what I can see there are a couple popular options for handling this:

  • Use sudo commands & git cli to manage repo in /etc/nixos
  • Grant your user ownership of /etc/nixos
  • Configure nix to load config from another location
  • Putting symlink(s) in /etc/nixos that point to another location

These are probably the best ways to do it right now. I also explored using multiple branches and worktrees but that got cumbersome fast. Ultimately, I just want something easy. To do that, I'll use one symlink and a repo in my home directory.

First, what is a symlink? A symlink is a symbolic link and differs from a hard link. Simply stated, a hard link is a reference to data and a symbolic link is a reference to a reference. It can get pretty technical if you look much deeper than that. But essentially, if I replace the file /etc/nixos/configuration.nix with a symlink of the same name which targets the config file in my home directory repo, Nix will follow that path and evaluate the config files in my repo instead of /etc/nixos.

Let's do it.

First, I cloned my existing repository to /home/ray/nix-config/ using Github desktop. Then I moved my current working files into that repo.

sudo cp -r /etc/nixos /home/ray/nix-config/
Enter fullscreen mode Exit fullscreen mode

Next, I deleted everything in /etc/nixos

sudo rm -rf /etc/nixos/*
Enter fullscreen mode Exit fullscreen mode

I created the symlink

sudo ln -s /home/ray/nix-config/configuration.nix /etc/nixos/configuration.nix
Enter fullscreen mode Exit fullscreen mode

And finally, tested it out

sudo nixos-rebuild dry-activate
Enter fullscreen mode Exit fullscreen mode

That created a new result file (actually another symlink!) which I added to my gitignore.

Since I'm not collaborating with anyone and I hope to be careful about my changes, I'll be working directly out of the main branch w/o pull requests. This is a little iffy because it means it's very easy to make quick changes to my device's active config. Ideally I could have the ease of Github desktop's git action UX and diff highlighting as well as some kind of confirmation for when I update the config that's actually in use by the computer. Perhaps someone can comment with advice! In any case, it's time to move on.

Refactoring config layout

My current config is essentially the NixOS default. The contents for my laptop are pinned in a release here.:

/etc/nixos/
├── configuration.nix
├── hardware-configuration.nix
Enter fullscreen mode Exit fullscreen mode

To adapt to the new structure, I'll create a common-configuration.nix file which is accessed by each device as well as a dedicated directory for configuration.nix and hardware-configuration.nix for each device. For each device, 1 symlink will be created pointing /etc/nixos/configuration.nix to /home/ray/nix-config/HOSTNAME/configuration.nix. I'll try to keep as much config in the common file as possible.

First, I'm creating the new file structure and leaving the common file empty. Nothing changing besides locations. That means removing the symlink I just built and creating a new one with

sudo rm /etc/nixos/configuration.nix
Enter fullscreen mode Exit fullscreen mode

and

sudo ln -s /home/ray/nix-config/hosts/nixos-mbp/configuration.nix /etc/nixos/configuration.nix
Enter fullscreen mode Exit fullscreen mode

Once I get that working, I create the common file as an "empty" config:

# NixOS Configuration common to both of my machines

{ config, pkgs, ... }:
{}
Enter fullscreen mode Exit fullscreen mode

Next I update my imports in nixos-mbp/configuration.nix

imports =
  [ # Common configuration
    ../../common-configuration.nix
    # Include the results of the hardware scan.
    ./hardware-configuration.nix
    # Firmware for keyboard, trackpad, etc
   "${builtins.fetchGit { url = "https://github.com/kekrby/nixos-hardware.git"; }}/apple/t2"
  ];
Enter fullscreen mode Exit fullscreen mode

The reference to the common file goes up to the root of my git repo and the reference to the hardware config looks in the same host directory. Lastly, the third part pulls in some firmware stuff we need for the macbook.

Now I'm going through the rest of the config file and moving anything that I think should be in the common config file. I end up with just 4 "uncommon" things:

  1. Hostname
  2. Imports
  3. Broadcom firmware
  4. Touchpad support

Next, I'm going to duplicate this file and add it to a new directory for my desktop computer. Normally I'd do this with the GUI, but here's the CLI command:

cp -r hosts/nixos-mbp/configuration.nix hosts/nixos-desktop/
Enter fullscreen mode Exit fullscreen mode

Maybe I'm learning linux after all. Anyway, I open that new file in VSCode and remove the firmware import, the wifi firmware, and the touchpad entry. I change the hostname and my file looks like this:

# Config for nixos-mbp device

{ config, pkgs, ... }:

{
  networking.hostName = "nixos-desktop"; # Define your hostname.

  imports =
    [ # Common configuration
      ../../common-configuration.nix
      # Include the results of the hardware scan.
      ./hardware-configuration.nix
    ];

}
Enter fullscreen mode Exit fullscreen mode

Now I think I'm ready to install on the PC!

Installing NixOS on PC

Now there's no reason to make this difficult. I downloaded the GNOME 64-bit graphical installer from the Nix site, flashed an 8GB drive with Balena Etcher I downloaded onto my Windows 10 system and spammed F12 when booting back up.

I clicked Next 3 times, entered a name and password, clicked next 2 more times, checked Allow unfree software, clicked next, chose my 12 year old SSD, selected Erase Disk with Swap (with Hibernate), clicked next, clicked install, waited a bit, checked a box to reboot, and hit done.

Booting up, I smashed F12 again and chose "Linux Boot Manager (P2: OCZ Agility3) and there I was, staring down the login screen with my name on it.

Configuring desktop with the repo config

Now I need to do a few things to get in sync with my laptop environment.

  1. Clone my nix-config repo
  2. Move my hardware-configuration.nix into it
  3. Replace my configuration.nix with a symlink to the repo file

To do that, I'll temporarily install git with

nix-env -i git
Enter fullscreen mode Exit fullscreen mode

and I'll clone the repo into place:

git clone https://github.com/raymondgh/nix-config /home/ray/nix-config
Enter fullscreen mode Exit fullscreen mode

Moving the hardware config into the repo:

mv /etc/nixos/hardware-configuration.nix /home/ray/nix-config/hosts/nixos-desktop
Enter fullscreen mode Exit fullscreen mode

Removing the current config.nix file from etc/nixos

rm mnt/etc/nixos/configuration.nix
Enter fullscreen mode Exit fullscreen mode

And then replacing it with the symlink:

ln -s /home/ray/nix-config/hosts/nixos-desktop/configuration.nix /etc/nixos/configuration.nix
Enter fullscreen mode Exit fullscreen mode

One last thing to do that I learned the hard way -- grant my user ownership of the directory I cloned with root

sudo chown -R ray /home/ray/nix-config
Enter fullscreen mode Exit fullscreen mode

And we should be good to rebuild!

sudo nixos-rebuild switch
Enter fullscreen mode Exit fullscreen mode

and with that done, I ran:

reboot
Enter fullscreen mode Exit fullscreen mode

Pushing back to Github

Now that our desktop has everything in place, it's time to give back. Opening Github desktop, I choose "add local repository" and select my /ray/nix-config directory. It's instantly set up and all I need to do is commit and push to origin.

And with that, I've got two machines running nearly identical operating systems with a single codebase under version control managing their features.

Whenever I install something, I'll ask myself "Is this for both of my machines, or just one?" and put that new or changed nix expression in either the common-configuration.nix file or the HOSTNAME/configuration.nix file accordingly. Then when I log on to the other machine, I'll pull the latest from the repo, do a nixos-rebuild switch and enjoy being totally in sync. Pretty snazzy!

What's next

Now I've really got my foundation landed. I should try to get something productive done. My next updates might be more quality of life (like getting sound to work on the laptop or making my desktop nicer) or functional (getting a software dev environment set up). At least from what I've read, once I get around to nix flakes or home manager, I'll have more challenges to figure out. As always, thanks for reading and let me know what you think!

Top comments (0)