Day 5: NixOS & Git - version control for config.nix
Tags: nixos, git, configuration
Published: 2023-05-17 03:46:49

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](https://pcpartpicker.com/user/wudf/saved/#view=TJgJxr). 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](https://github.com/raymondgh/nix-config/tree/v0.2.0).
```
/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
```
### 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](https://dev.to/raymondgh/day-4-reinstalling-nixos-on-my-apfs-t2-intel-macbook-pro-265n )) and log in with my user account. I open `Konsole` from the start-like menu and run
```
sudo nano ../../etc/nixos/configuration.nix
```
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
  ];
```
There's some explanation of how this works in my [Day 2 Post](https://dev.to/raymondgh/day-2-configurationnix-5cj3) 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](https://github.com/raymondgh/nix-config) 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/
```
Next, I deleted everything in `/etc/nixos`
```
sudo rm -rf /etc/nixos/*
```
I created the symlink
```
sudo ln -s /home/ray/nix-config/configuration.nix /etc/nixos/configuration.nix
```
And finally, tested it out
```
sudo nixos-rebuild dry-activate
```
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](https://github.com/raymondgh/nix-config/releases/tag/v0.1.1).:
```
/etc/nixos/
├── configuration.nix
├── hardware-configuration.nix
```

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
```
and
```
sudo ln -s /home/ray/nix-config/hosts/nixos-mbp/configuration.nix /etc/nixos/configuration.nix
```
Once I get that working, I create the common file as an "empty" config:
```
# NixOS Configuration common to both of my machines

{ config, pkgs, ... }:
{}
```
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"
  ];
```
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/
```
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
    ];

}
```

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](https://nixos.org/download.html#nixos-iso), flashed an 8GB drive with [Balena Etcher](https://www.balena.io/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
```
and I'll clone the repo into place:
```
git clone https://github.com/raymondgh/nix-config /home/ray/nix-config
```
Moving the hardware config into the repo:
```
mv /etc/nixos/hardware-configuration.nix /home/ray/nix-config/hosts/nixos-desktop
```
Removing the current config.nix file from etc/nixos
```
rm mnt/etc/nixos/configuration.nix
```
And then replacing it with the symlink:
```
ln -s /home/ray/nix-config/hosts/nixos-desktop/configuration.nix /etc/nixos/configuration.nix
```

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
```


And we should be good to rebuild!
```
sudo nixos-rebuild switch
```

and with that done, I ran:
```
reboot
```

### 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!