Using manifests to maintain our Guix applications

The most efficient way to manage packages in Guix is by maintaining a list of packages in a file (a manifest). As the manifest is a file it's easy to keep under under version control. With all our favourite applications in a manifest it's simple to deploy them onto any machine and into any environment. We can split the package list into different subsets representing different use-cases (e.g. development applications) and install just that subset into an environment.

Now, if by some accident you're here to learn how to manifest anything your heart desires through the law of attraction you're in the wrong place: unless your hearts desire is how to manage applications efficiently on Linux! Which frankly, would be a better use of your time than reading that other link.

If you've been following along in the series I hope your hearts desire (!) has been fulfilled and you're installing, upgrading and managing applications using Guix as a package manager on top of your 'foreign distribution' (Ubuntu for me). While it's simple enough to manually manage a few applications this way, it's difficult to keep track of everything that is installed. Fear not - we don't have to hope the universe will rescue us - manifests are a better way.

If you'd like to read the rest of the series:

Transactions with Manifests

The first thing to cover is that manifests act differently from the way we use the Guix command line tool to manage packages. When we use the Guix CLI commands the transaction is additive to the state of the default profile. If we add a package, it adds it to the existing set we have installed. Here's an example to illustrate:

$ guix install glibc-locales nnn vifm htop
$ guix package --list-installed
glibc-locales           2.33            out     /gnu/store/ixzmi6614baf4w37qfjgqrv8hwsl8jcv-glibc-locales-2.33
nnn                     4.6             out     /gnu/store/byap6zn0g5gbv43wg67cdkvlnl24hhlg-nnn-4.6
vifm                    0.12.1          out     /gnu/store/754z5pxivcaw2y2ill9lf50lfcq5qj9x-vifm-0.12.1
htop                    3.2.1           out     /gnu/store/h0yd0v23n645nzykxfpxayixslfg1mfr-htop-3.2.1

$ guix install hello glances
$ guix package --list-installed
glibc-locales           2.33            out     /gnu/store/ixzmi6614baf4w37qfjgqrv8hwsl8jcv-glibc-locales-2.33
nnn                     4.6             out     /gnu/store/byap6zn0g5gbv43wg67cdkvlnl24hhlg-nnn-4.6
vifm                    0.12.1          out     /gnu/store/754z5pxivcaw2y2ill9lf50lfcq5qj9x-vifm-0.12.1
htop                    3.2.1           out     /gnu/store/h0yd0v23n645nzykxfpxayixslfg1mfr-htop-3.2.1
hello                   2.12.1          out     /gnu/store/s5pd3rnzymliafb4la5sca63j86xs0y0-hello-2.12.1
glances                 3.1.7           out     /gnu/store/rhsa9lxzxfgbampx0vkx859xfizz07rp-glances-3.1.7

As we can see we initially install four packages, and then install another two. After the second transaction when we list the packages that are installed, it shows us all six packages are there in the default profile.

It's exactly the same if we remove some applications:

$ guix remove vifm glances
$ guix package --list-installed
glibc-locales           2.33            out     /gnu/store/ixzmi6614baf4w37qfjgqrv8hwsl8jcv-glibc-locales-2.33
nnn                     4.6             out     /gnu/store/byap6zn0g5gbv43wg67cdkvlnl24hhlg-nnn-4.6
htop                    3.2.1           out     /gnu/store/h0yd0v23n645nzykxfpxayixslfg1mfr-htop-3.2.1
hello                   2.12.1          out     /gnu/store/s5pd3rnzymliafb4la5sca63j86xs0y0-hello-2.12.1

After removing a couple of packages we still have all the ones we've previously installed. I imagine at this point you're thinking "so,what?" as this is how package managers normally work from the command line - it's how we're used to using apt and yup.

When using a manifest it's a bit different as we declare the final state that we want. What this means is that the completed transaction will have all the packages specified installed - but only those packages.

As an illustration, lets say we have a manifest file that has vifm and hello packages in it, we'd install it like this:

$ guix package --manifest=base-shell.scm

The following packages will be installed:
   hello 2.12.1
   vifm  0.12.1

The following derivation will be built:
    /gnu/store/p82cyw5k6paln5rdwyd9s13gf0946jgx-profile.drv

[lots more output]

Now, when we check which packages are installed:

$ guix package --list-installed

hello   2.12.1  out     /gnu/store/s5pd3rnzymliafb4la5sca63j86xs0y0-hello-2.12.1
vifm    0.12.1  out     /gnu/store/754z5pxivcaw2y2ill9lf50lfcq5qj9x-vifm-0.12.1

As we can see we now only have the hello and vifm packages, the transaction has removed glibc-locales, nnn and htop.

What's happening is that a new generation is being created in the default profile. When we use a manifest we declare the final end-state that we want - in this case we told Guix we wanted two packages, so it removed everything else to achieve the requested end state.

When we understand this behaviour manifests are easy to use.

Defining a Manifest

It's time to start writing manifests. Lets have a look at what one looks like:

;;;; glibc-locales is always required so Guix can find the locale
;;;; everything else I do in alphabetical order making it easy to maintain
(specifications->manifest
    '( "glibc-locales"
       "git"
       ;; we also want the gui
       "git:gui"
       "nnn"
       "tmux"
       "vim"))

Pretty simple right, we're listing the packages that we want inside double quotes. A manifest is written in GNU Guile which is a dialect of Scheme, that's why the file has the .scm extension. The whole of Guix is written in Guile under the hood. The manifest uses a function called specifications->manifest which accepts a string and returns a package object. This is done as a Guile list which is why it is inside those brackets (). To prevent evaluating the list rather than passing it to the function we quote it which is why there's a quote mark in front of those brackets - the ` thing.

To use this we save it to a manifest file - lets call it base-shell.scm. We can install it with:

$ guix package --manifest=base-shell.scm

After we do this we'll have a new generation which only has the applications the manifest specified:

$ guix package --list-installed

vim     9.0.0954        out     /gnu/store/zayyazzxbv6qhchbl8lzr4pbf11312gs-vim-9.0.0954
tmux    3.3a            out     /gnu/store/ynvhs65sh4nq5rd6b1pnlh2h7d94jk1c-tmux-3.3a
nnn     4.6             out     /gnu/store/byap6zn0g5gbv43wg67cdkvlnl24hhlg-nnn-4.6
git     2.38.1          out     /gnu/store/f9b4ckya79hfwdlb4sd7w1f619la8lgf-git-2.38.1
git     2.38.1          gui     /gnu/store/r6hknlbii5xpj37wwlzsqq7j5rlzcrbq-git-2.38.1-gui

$ guix package --list-generations

Generation 42   Dec 21 2022 13:04:26
+ tmux           3.3a          out     /gnu/store/ynvhs65sh4nq5rd6b1pnlh2h7d94jk1c-tmux-3.3a
+ vim          9.0.0954        out     /gnu/store/zayyazzxbv6qhchbl8lzr4pbf11312gs-vim-9.0.0954
- vifm         0.12.1          out     /gnu/store/754z5pxivcaw2y2ill9lf50lfcq5qj9x-vifm-0.12.1
- hello        2.12.1          out     /gnu/store/s5pd3rnzymliafb4la5sca63j86xs0y0-hello-2.12.1

Generation 43   Dec 21 2022 13:06:34    (current)
+ git  2.38.1  out     /gnu/store/f9b4ckya79hfwdlb4sd7w1f619la8lgf-git-2.38.1
+ git  2.38.1  gui     /gnu/store/r6hknlbii5xpj37wwlzsqq7j5rlzcrbq-git-2.38.1-gui

We can use all the same capabilities we have on the command line in a manifest. We can install different parts of a package which is what we've done above to install the Git GUI with gui, and install a version of a package if more than one is available in the repository (GTK+ is a good example of this).

Using Multiple Manifests

Rather than keeping one long manifest with all my packages specified I split my manifests to suit different use-cases. I use multiple manifests in an environment, combining their packages. The advantages of this approach are:

  • Shorter manifests are faster for guix to upgrade: if we do an upgrade having one giant manifest file means we have to wait for everything to install. It's often useful to do a pull/upgrade when we login to an environment - a big manifest makes this particularly slow and annoying.
  • Multiple manifests are easier to maintain: it's easier to maintain package lists that are split into different use-cases. We can then think about what we'll want available in each situation.
  • Focus on the required applications: it's sometimes useful to reduce the set of applications to only those that are required or useful: for example X Windows applications only need to be available if we're in a graphical environment.
  • Project specific requirements when developing: if we're doing some development we might find a project has different and even conflicting package requirements. There's some quite neat ways we can make reproducible environments for development using Guixs' feature set.

We can combine two or more manifests on the command line. Lets say we've created another manifest called util-shell.scm which has in it:

(specifications->manifest
    '(
        "exa"
        "glances"
        "hello"
        "ncdu"
     )
) ;; almost certainly will be feathered and tared by the lisperati for aligning my brackets

To install these manifests at the same time we do:

$ guix package --manifest=base-shell.scm --manifest=util-shell.scm

During the transaction Guix combines multiple manifest files and processes them in a single transaction. Technically, the combination is being applied to the default profile, but this could be used on any profile. We'll cover profiles and environments in future posts.

It would be rather boring if we had to retype all the applications that we've manually installed into a manifest. Guix gives us a simple way to export our current generation to a manifest:

$ guix package --export-manifest > example-export.scm

If we look in the file it has:

;; This "manifest" file can be passed to 'guix package -m' to reproduce
;; the content of your profile.  This is "symbolic": it only specifies
;; package names.  To reproduce the exact same profile, you also need to
;; capture the channels being used, as returned by "guix describe".
;; See the "Replicating Guix" section in the manual.

(specifications->manifest
  (list "exa"
        "glances"
        "hello"
        "ncdu"
        "tmux"
        "git:gui"
        "git"
        "nnn"
        "tmux"
        "vim"))

We can also manually add and remove packages on the command line and then use the --export-manifest capability to recreate an up to date manifest.

Manifest resources

There aren't that many posts or pages about manifests, the ones I found useful are:

Onward with Manifests

That's really all there is to say about manifests, they're very useful, simple to use and maintain. In my case I maintain multiple machines and by using the same manifests I can keep the user-experience the same across all of them. It's possible to associate a manifest with a particular profile or environment so we'll cover that topic in our next post. We can also combine manifests during login but this requires us to combine them into a profile.


Posted in Tech Monday 12 December 2022
Tagged with tech ubuntu guix