Overview

We want to back up the list of packages we want installed on our system, and diff it with the list that is actually installed at present.

This can be easily achieved in two text files and some basic bash scripting, all of which can be trivially stored in a git repository.

packages.sh

Create a packages.sh which, when run, will simply echo the list of all the packages you want.

# packages.sh
echo anki
echo asciinema
echo base
echo git
echo p7zip
...

To save you writing the starting config all by hand, you can just use your distro's command to list explicitly installed packages. On Arch Linux you can use:

pacman -Qqe | while read line; do echo "echo $line"; done > packages.sh

If you have a different distro/package manager, you will obviously need to change the pacman command to something else.

pkgdiff.sh (v1)

In future, we want to see which packages we have installed/uninstalled since the last time we updated the configuration file. We can use a simple bash script which pipes into the comm command the output from our script and from the system package manager.

#!/bin/bash
comm --total <(
    sh packages.sh | sort
) <({
    pacman -Qqe | sort # Change this if you have a different package manager
})

When we run ./pkgdiff.sh, it will print a columnar diff indicating the difference between the package manager's list and your own.

$ ./pkgdiff.sh
CFG
    SYS
anki
p7zip
    podman
1   0   297 total

Here I can see that my configuration specifies anki and p7zip to be installed, but the system is missing them. Conversely, the system has podman installed, but it isn't specified in the configuration

pkgdiff.sh (v2)

It would be nice to pipe the list of config/system packages into the package manager, to bulk install/uninstall them. We can easily add this feature to our script.

#!/bin/bash

# Look for the arguments `cfg` or `sys`
case "$1" in
"cfg")
    suppress=2
    ;;
"sys")
    suppress=1
    ;;
*)
    suppress=
    ;;
esac

# Build the options to pass to `comm`
opts=("-3$suppress")
if [ ! "$suppress" ]; then
    comm <(echo CFG) <(echo SYS)
    opts+=(--total)
fi

# Pass the options via an array expansion
comm "${opts[@]}" <(
    sh packages.sh | sort
) <({
    pacman -Qqe | sort # Change this if you have a different package manager
})

Now I can run ./pkgdiff.sh cfg and ./pkgdiff.sh sys respectively to see just one or other column.

$ ./pkgdiff.sh cfg
anki
p7zip
$ ./pkgdiff.sh sys
podman

This can be easily piped to the system package manager for bulk install/uninstall, e.g.

$ ./pkgdiff.sh cfg | sudo pacman -S -

Conclusion

Periodically run the pkgdiff.sh script and see if your configuration needs updating. Manually add/delete the relevant lines from your packages.sh file.

Store everything in git. It is just two text files!