Tyblog | How to Preview System Updates on NixOS

If you’re a NixOS user, knowing what will change on your system when performing updates can be tricky. We can make the experience better!


This is a companion discussion topic for the original entry at https://blog.tjll.net/previewing-nixos-system-updates/

Hi Tyler,

First of all: Great and very interesting article!

I also want a “pending updates” notification in my status bar (swaybar with i3blocks).
How often do you run the script?
I’m a bit hesitant to run it too often, due to performance impact during build.
What is your experience? / What do you think is a good practice?

Thanks in advance!

@UNGUEtiN thank you, I’m glad this is useful!

This operation is indeed kind of a heavy command. Currently, I run this on a regular timer but I ensure that my laptop is connected to a power source before invoking the command. When I do run the script, I cache the results to a file in ~/.cache to have a last-run result in case I want to check things quickly.

Building a system derivation kind of depends on how heavily customized your configuration is. For example, a stock nixos system on the nixos-23.05 channel can probably check without too much overhead, but if you (like me) rely on something like emacs-overlay then you may end up compiling a lot of programs.

I brought this up in the nvd project and got some helpful feedback from the author. You may want to take a look at that issue; the tl;dr is that you have the option of tweaking this example slightly to look at the derivations themselves rather than the built derivations to save on compile cycles. There haven’t been any changes in nvd yet, but it seems like there are options for a more lightweight system diff.

Hi Tyler,

Thank you for your answer, in the meantime I’ve come up with the following solution:

  1. Configured NixOS automatic upgrades through configuration.nix, but without doing a switch:
  # Enable automatic upgrades.
  system.autoUpgrade = {
    enable = true;
    operation = "boot";
    dates = "daily";
    persistent = true;
    allowReboot = false;
  };

This config causes the build to run once a day (and also run immediately, if the task was missed), so impact will be pretty low I think.

  1. Added NixOS specific code -heavily inspired by your post- to my already existing ‘updates.sh’ script, which I use with i3blocks:
case "$distro" in
    arch)
        # check for pending updates
        if ping -q -c 1 -W 1 1.1.1.1 >/dev/null 2>&1; then
            pending_updates="$(checkupdates)"
        else
            exit 0
        fi
        # check if a reboot is required
        if [ "$(pacman -Q linux | sed 's/^linux //')" = "$(uname -r | sed 's/-arch/.arch/')" ]; then
            reboot_required="false"
        else
            reboot_required="true"
        fi
        ;;
    nixos)
        # check for pending updates
        # - heavily inspired by this post: https://blog.tjll.net/previewing-nixos-system-updates/
        nixos_current_system="/run/current-system"
        nixos_system="/nix/var/nix/profiles/system"
        if [ -d "$nixos_current_system" ] && [ -d "$nixos_system" ]; then
            nixos_current_system_hash="$(basename $(readlink -f "$nixos_current_system") | cut -d- -f1)"
            nixos_system_hash="$(basename $(readlink -f "$nixos_system") | cut -d- -f1)"
            if [ "$nixos_current_system_hash" != "$nixos_system_hash" ]; then
                nvd_output_file="/tmp/.nvd-${nixos_current_system_hash}-${nixos_system_hash}"
                if [ ! -f "$nvd_output_file" ] ; then
                    nvd_command="nvd diff "$nixos_current_system" "$nixos_system""
                    if [ -x "$(command -v nvd)" ] >/dev/null 2>&1; then
                        nvd_diff="$(eval "$nvd_command")"
                    else
                        nvd_diff="$(nix-shell --quiet -p nvd --command "$nvd_command")"
                    fi
                    echo "$nvd_diff" > "$nvd_output_file"
                fi
                if [ -r "$nvd_output_file" ] ; then
                    pending_updates="$(cat "$nvd_output_file" | tail -n +3 | grep -E "^\[(U|D|C).\]" | sed -e 's/\\/\\\\/g' -e 's/&/\&amp;/g' -e 's/</\&lt;/g' -e 's/>/\&gt;/g')"
                else
                    exit 0
                fi
            else
                pending_updates=""
            fi
        else
            exit 0
        fi
        # check if a reboot is required
        if diff <(readlink /run/booted-system/{initrd,kernel,kernel-modules}) <(readlink /nix/var/nix/profiles/system/{initrd,kernel,kernel-modules}) >/dev/null 2>&1; then
            reboot_required="false"
        else
            reboot_required="true"
        fi
        ;;
esac

The script reuses the cached output of nvd if the hashes (of the current system and the system that will be activated on a switch or on the next reboot) have not (yet) changed.

Perhaps there’s a smarter way and/or some parts of the code can be simplified, but for me at the moment this seems to work nicely.

Thanks for pointing out the nvd issue, looks interesting I’ll be checking this out soon.

Still learning NixOS bit by bit!