--- layout: post.njk title: Using Git to Deploy NixOS Configurations tags: post date: 2024-01-20 ---

Lately I have been learning Nix+NixOS, which is configured using a declarative programming language. What is nice about that is that allows us to reproduce a system very easily. However, I googled around and tried to figure out how to easily deploy my config from a local machine to a Nix machine. I started off SSHing into the machine and making edits using nano in the config, but it’s tedious and I’d rather use my local machine to change the configurations. There is also little advantage to having a declarative config if I can’t reuse it elsewhere.

There are tools out there like deploy-rs and Nixops, however I found them a bit overkill for my need. There also seems to be a way to use nixos-rebuild to build a change on your local machine and deploy, which is intriguing but I don’t run nixos on my machine so it felt like a no-go. So in the end, I came up with a script to make the setup easy. It makes use of git, so it allows me to make my changes using a git flow similar to Heroku. Here is what the installation script looks like:

# Start of by adding git to our configuration.nix, we will levarage this to
# be able to easily make changes to our machine without SSH.
sed -i 's/^{$/{\n  programs.git.enable = true;/' /etc/nixos/configuration.nix

# Rebuild nix so we have git available
nixos-rebuild switch

# Set up a git repository where our nixos configuration lives
cd /etc/nixos
git init

# Change branch name to main instead of master
git branch -m main

# Add existing config and commit
git add .
git commit -m "Initial commit"

# Allow us to push changes to our machine and have those changes immediately reflected in the files
git config receive.denyCurrentBranch updateInstead

# Store a push-to-checkout script to variable $validate_script
read -r -d '' validate_script <<-EOF

#!/bin/sh
echo "Running push-to-checkout hook"

# Get the changes made
git read-tree -u -m HEAD "$1"

# Run a dry-build to see that it works
nixos-rebuild dry-build
result=$?
if [ $result -eq 1 ] ; then
  # In case of failure, we'll undo the changes applied from read-tree
  git stash --all --quiet
  exit 1
fi
exit 0
EOF

echo "$validate_script" > /etc/nixos/.git/hooks/push-to-checkout

# Make the hook executable
chmod +x /etc/nixos/.git/hooks/push-to-checkout

# Add a git hook to apply the changes afterward
echo -e '#!/bin/sh \nnixos-rebuild switch' > /etc/nixos/.git/hooks/post-receive

# Make the hook executable
chmod +x /etc/nixos/.git/hooks/post-receive

Once set up, you can clone the repo on your local computer

git clone root@your-machine:/etc/nixos

And if you’d like to set up a backup on Sourcehut, you can do so easily:

git remote add backup git@git.sr.ht:~username/reponame
git push backup main

This script can be ran on a new machine. I used Nixos-infect to setup NixOS on a VPC that I rent on Hetzner Cloud.

Edit: Thanks to Valentin Gagarin for pointing out the distinction between Nix and NixOS. I have updated the article to correctly call it NixOS instead of Nix.

Edit2: Thanks to James T on NixOS Discourse for pointing out some errors in the script. I've updated them accordingly.