Different Rollback Methods in NixOS
Associated Youtube Video: https://youtu.be/HQmrQHmAMzg
Associated Odysee Video:
Previous Tutorial: Managing Your NixOS Config with Git
Next Tutorial: Coming soon!
This post showcases a few additional rollback methods for NixOS you may find useful and/or may not know about!
Table of Contents
The Obvious Rollback
The most obvious and readily accessible rollback method is the boot menu, in which you can select a previous, working configuration at boot time just in case your newest configuration is broken.
If you have a broken configuration, odds are you don’t want that to be the boot default. In this case, you can select your working configuration, and then navigate to /run/current-system/bin/
. In here, you should have a binary called switch-to-configuration
.
If you try calling this, you will notice it has the exact same syntax as nixos-rebuild
, therefore, you can make the old, working configuration (that you booted into) the boot default with either:
sudo /run/current-system/bin/switch-to-configuration switch
or even quicker:
sudo /run/current-system/bin/switch-to-configuration boot
Rollbacks on Non-flake Systems
If you are able to boot into a working configuration, but there are still minor problems with your setup, there are some other ways of rolling back your setup.
On a non-flake system, your system is built from:
- your configuration files (configuration of packages)
- your channels (versions of packages)
Configuration Rollbacks
If you accidentally switch into a crazy, messed up configuration, it’s possible to rollback really quickly with:
sudo nixos-rebuild switch --rollback
Problems with Committed Changes
For small configuration rollbacks, I like to use git. If you aren’t tracking your configuration using git already, check out my previous tutorial here.
The simplest way of “rolling back” something tracked in git is to use git-revert
. The syntax is something like:
git revert <hash>
You can always get a list of most recent commits via the command:
git log
git-revert
will make a new commit titled “Reverted: <blah-blah…>”, but you can avoid this using the -n
flag, which I like to do:
git revert <hash> -n
This way I can make commits manually.
Putting it all together as a full example, let’s say I run git log
and it gives me this:
commit 19ad54ed9e58a90fb02530ca08451a828d4cc7cb (HEAD -> main, gitlab/main, github/main, gitea/main, codeberg/main) Author: Emmet <emmet@librephoenix.com> Date: Sat May 4 11:57:50 2024 -0500 Updated system commit 67ec14a8ab354e4f9facc8a519018b645ee86d2d Author: Emmet <emmet@librephoenix.com> Date: Sun Apr 28 15:12:13 2024 -0500 Removed x's from magit-todos-keywords commit 2104e82401c67a3cc66c0b5731ff5633266ffbf4 Author: Emmet <emmet@librephoenix.com> Date: Sun Apr 28 15:07:53 2024 -0500 Fully fixes projectile-grep disable from doom
Then, let’s say that the commit “Fully fixes projectile-grep disable from doom” gave me a bunch of problems. I would copy the commit hash (2104e82…) and place it into the git-revert
command like so:
git revert 2104e82401c67a3cc66c0b5731ff5633266ffbf4 -n
Problems with Non-committed Changes (or Reversions)
Let’s say you run git-revert
(using the -n
flag for “no commit” of course), and then you try building the configuration but there are unforseen problems. Or alternatively, let’s say you’re tinkering with your configuration and trying out new stuff, and your changes are spaghetti and you can’t tell what’s wrong with it!
In these cases, it may be advisable to nuke all of your changes and reset the entire working area to a clean slate (to the last good working commit). In this case, you may want the git-reset
command.
To nuke all staged or unstaged changes (Warning: You will lose all staged and unstaged changes forever!), you can run:
git reset <branch-name> --hard
I usually name my branches main
, so the command would look like:
git reset main --hard
for me.
Channel Rollbacks
Another way your system can break is when you perform an update, and the updated packages break your system or prevent your configuration from being built. This is especially the case if you’re using nixos-unstable
for example.
Whenever you update your system, you’re usually calling (at some point):
sudo nix-channel --update
which will update the package set your system is building from.
If your system breaks due to an update, you should rollback your channels, as well as the configuration. If you only rollback the configuration to a working state, any subsequent rebuild of the system would still be using the broken packages from the update.
To do this, start by listing your channel generations via:
sudo nix-channel --list-generations
Next to each generation, there will be a number, for example:
sudo nix-channel --list-generations 2 2024-03-27 09:44:43 3 2024-04-02 08:12:41 (current)
To rollback your channels, now all you need to do is specify which previous generation to rollback to using:
sudo nix-channel --rollback <generation_number>
So in the above case, we would use:
sudo nix-channel --rollback 2
to rollback the channel.
This, by itself, will not rollback the system, but it will make subsequent rebuilds use the older set of packages.
Garbage Collection
At this point, it’s useful to mention garbage collection. As you use your NixOS system (or any Linux system, for that matter), you will accumulate a lot of old versions of packages. On other Linux systems, this may be in some kind of cache, but for NixOS, this would be the Nix store.
The Nix store can take up a lot of hard drive space over time, so it’s important to clean it up from time to time.
The basic way of doing this is:
sudo nix-collect-garbage --delete-older-than <time_period>
If you want to delete everything (that is unneeded) and older than 30 days, then you could use:
sudo nix-collect-garbage --delete-older-than 30d
This will delete old system generations and channels, at which point, it is impossible to rollback, unless…
Rollbacks on Flake Systems (Just Use Git)
If you are using flakes, there are ways of rolling back your system even if you delete older configurations. The only caveat on this is that you need to be able to boot into a working version of your system. As long as you are using flakes and you are tracking your configuration with git, you can rebuild any previous version of your system (with the exact versions of packages you previously used)!
Because the package version information is stored in the flake.lock
instead of channels, you have access to this information via your git history, and can undo as many changes as you want/need using git-revert
!
As an aside, if you want to revert some really old commits, it may be necessary to undo changes to specific files in order, by calling git revert
on multiple commits. Or, you may just need to manually clean up some git reversion conflicts in some cases.
Flake Systems and Garbage Collection
Since precise configuration and version information is stored with a flake configuration, you can rebuild previous working versions of your system even if you have garbage collected them. This type of rollback will take a while (since you might need to rebuild a lot of stuff) and it would also require you to be able to boot into a working setup, but the advantage is that you can be a little more trigger happy with nix-collect-garbage
.
After making sure you are able to boot into a new configuration (after a full reboot!), you can conserve even more storage space with:
sudo nix-collect-garbage -d
which will delete everything that you aren’t currently using.
It goes without saying that before doing this, you should make sure your most recent setup works and that you can boot from it. However, if your disk space is being utilized by a lot of other things (games, etc..), this can useful for reducing the size of the Nix store.
Back to the Future!
I hope this post has helped you turn your NixOS setup into a DeLorean time machine! Stay tuned for more NixOS and Linux stuff :)
Donation Links
If you have found my work to be helpful, please consider donating via one of the following links. Thank you very much!