Guix common workflows and concepts

This post covers all the common workflows and commands needed to use Guix as a package manager on top of any Linux distribution. We'll cover how to search for packages, find out details about them, install packages and remove them. Introducing some of the concepts that make Guix so capable - including how Guix stores packages and supports transactions - but we'll try not to over-complicate, keeping the focus on how to use it. If we can use Guix with just the commands covered here, as a set of recipes or a cheatsheet, then we've been successful.

I'm using Ubuntu (22.04 LTS) as my Linux distribution, and installing additional applications from Guix. This means I'm using Guix as a package manager. In the Guix manual, this is called using Guix on a foreign distribution. The alternative would be to use Guix as a full Linux distribution. Guix operates as a package manager in the same way when you run it on top of any Linux distribution - so if you're using Fedora, SuSE or Arch it's going to be the same commands.

If you read part 1 then you have Guix installed in a system container (LXD) so we're ready to start using it.

Searching for a package

To search for applications to install we can either use the command line or the Packages area of the Guix Web site. The best command to start with is guix package --list-available as it returns a nice table format that's easy to scan, it's similar to apt search:

$ guix package --list-available=vim
editorconfig-vim        1.1.1                   out     gnu/packages/vim.scm:619:2
emacs-vimrc-mode        0.3.1-1.13bc150         out     gnu/packages/emacs-xyz.scm:27440:4
eovim                   0.2.0                   out     gnu/packages/vim.scm:793:2
[additional packages listed]

This returns all packages that have vim in the package information. As you'll have noticed Vim itself is not at the top of the list, it's the eleventh item for me. We can tighten a search by using regular expressions in the search pattern: for example to see packages where vim both starts and ends the line we can do:

guix package --list-available=^vim$
vim     9.0.0954        out     gnu/packages/vim.scm:79:2

The guix search command is similar but provides the details of each package, it's equivalent to apt show:

$ guix search git

The search command is an alias for guix package -s. I tend to use the aliases as they're easier to remember.

Showing information about a package

We can see information about a particular package using the guix show command, it's the same output as guix search:

$ guix show tmux

We'll get something like this:

name: tmux
version: 3.3a
outputs:
+ out: everything
systems: x86_64-linux i686-linux
dependencies: libevent@2.1.12 ncurses@6.2.20210619
location: gnu/packages/tmux.scm:45:2
homepage: https://github.com/tmux/tmux/wiki
license: ISC
synopsis: Terminal multiplexer
description: tmux is a terminal multiplexer: it enables a number of
+ terminals (or windows), each running a separate program, to be
+ created, accessed, and controlled from a single screen.  tmux may be
+ detached from a screen and continue running in the background, then
+ later reattached.

We can also see the size of the package with its dependencies with guix size:

$ guix size tmux
store item                                                       total    self
/gnu/store/5h2w4qi9hk1qzzgi1w83220ydslinr4s-glibc-2.33              38.3    36.6  45.4%
/gnu/store/094bbaq6glba86h1d4cj16xhdi6fk2jl-gcc-10.3.0-lib          71.7    33.4  41.4%
/gnu/store/9rrnm5hdjw7cy96a2a9rfgh6y08wsbmf-ncurses-6.2.20210619    77.6     5.9   7.4%
/gnu/store/n0sd9hghs18pjsj72023r1spa9wxccc2-libevent-2.1.12         73.8     2.1   2.6%
/gnu/store/ynvhs65sh4nq5rd6b1pnlh2h7d94jk1c-tmux-3.3a               80.6     0.9   1.1%
total: 80.6 MiB

This is showing the specific packages that have been installed under /gnu/store.

It sometimes useful to see what packages and libraries an application needs - and it's always looks cool! To show the dependency graph:

# we need dot and a pdf viewer
$ guix install graphviz
$ guix graph tmux | dot -Tsvg > tmux-dependency-graph.svg
$ xdg-open tmux-dependency-graph.svg

# if you want to see which packages depend on this one do a reverse
# dependency graph
$ guix graph --type=reverse-package tmux | dot -Tsvg >tmux-reverse-dependencies.svg

Finally, we can actually see the way the package definition:

$ guix edit tmux
[opens an editor with the package definition]

For me the package definition is this:

(define-public tmux
    (package
        (name "tmux")
        (version "3.3a")
        (source (origin
            (method url-fetch)
                (uri (string-append
                        "https://github.com/tmux/tmux/releases/download/"
                        version "/tmux-" version ".tar.gz"))
                        (sha256
                            (base32
                                "0gzrrm6imhcp3sr5vw8g71x9n40bbdidwvcdyk2741xx8dw39zg4"))))
        (build-system gnu-build-system)
            (inputs
                (list libevent ncurses))
            (home-page "https://github.com/tmux/tmux/wiki")
            (synopsis "Terminal multiplexer")
            (description
                "tmux is a terminal multiplexer: it enables a number of terminals (or
                 windows), each running a separate program, to be created, accessed, and
                 controlled from a single screen.  tmux may be detached from a screen and
                 continue running in the background, then later reattached.")
            (license license:isc)))

Now you're either looking at that and thinking, "Wow, how amazingly concise and elegant for a complex package", or "What the hell are all those brackets, aaaah my eyes are burning!". And, that reaction will determine whether you'll be interested in a later post on packaging in Guix!

Updating the package list to the most recent versions

Guix keeps all the package definitions and versions in a git repository. The guix pull command downloads the definitions for a user. This is equivalent on Ubuntu to apt update.

$ guix pull
Updating channel 'guix' from Git repository 'https://git.savannah.gnu.org/git/guix.git'
Authenticating channel 'guix', commits 9eb3f6 to e6dd58c (1,432 new commits)...
Building from these channels:
    guix https://git.savannah.gnu.org/git/guix.git e6dd58c
substitute: updating substitutes from 'https://ci.guix.gnu.org' ... 100.0%
[a lot more similar output]

The following derivations will be built:
/gnu/store/3g0jiiwa8f258vliz8h54879n7z6hmh8-profile.drv
[a lot more output like that]

38.9MB will be downloaded
substitute: updating substitutes from 'https://ci.guix.gnu.org'... 100.0%
guix-manual  6.2MiB                         1.8MiB/s 00:04 [###############] 100.0%
[a lot more like this for each package]

building /gnu/store/b2mkpibr3kcj5nzy5h08vp32z4s4kfxj-config.scm.drv
[more like this]
building profile with 2 package ...

We can read the news from the repository with the following:

$ guix pull --news

If we want to see a list of every updated package we can do:

$ guix pull --news --details
New in this revision:
    359 new packages: aerc, aiger, appmenu-gtk-module, atelier, avr-gdb ....
    [ lots more output ]
967 packages upgraded: akonadi-calendar@22.08.1, akonadi-contacts@22.08.1, ...
    [ lots more output ]

Installing a package

We can install packages using guix install. This is the equivalent of apt install under Ubuntu:

$ guix install tmux

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

When Guix installs a package it puts it into the package store which is under /gnu/store. Each build of a package is put into its own separate directory which is why we see those long hashes.

We can either download a binary package that has been built by the distributions build farm, or we can build the package locally ourselves. A particular version of the package should build locally to exactly the same hash as the one we would get from the build farm (see guix challenge if you're interested in this). It would be too time consuming to build all applications ourselves, the default is to use the binary build provided by the Guix build farm, this is called a binary substitution.

Technically, the guix install command is an alias for guix package -i <package name> and some of the old documentation has this.

Sometimes packages have multiple outputs. For example, if we look at git with guix show git we see:

outputs:
    + send-email: [description missing]
    + svn: [description missing]
    + credential-netrc: [description missing]
    + credential-libsecret: [description missing]
    + subtree: [description missing]
    + gui: [description missing]
    + out: everything else

The default is to install the out part, so if we run guix install git this is what we'll get. If we want a specific part of the package we do:

guix install git:gui

Seeing installed packages

To see the packages that we have installed:

$ guix package --list-installed
glibc-locales   2.33    out     /gnu/store/ixzmi6614baf4w37qfjgqrv8hwsl8jcv-glibc-locales-2.33
firefox         106.0.4 out     /gnu/store/laf85klx1n5fva5bwhfyxhl7k70grhp5-firefox-106.0.4
rust            1.61.0  out     /gnu/store/7pffjglzk699x6pi8q2qzxssfq

This is showing us the package name, version, the part of the package installed (all the default out) and the location in the package store that it's soft linked from.

Removing a package

To remove a package we use guix remove:

$ guix remove hello

The following package will be removed:
   hello 2.12.1

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

building CA certificate bundle...
listing Emacs sub-directories...
building fonts directory...
generating GdkPixbuf loaders cache...
generating GLib schema cache...
creating GTK+ icon theme cache...
building cache files for GTK+ input methods...
building directory of Info manuals...
building XDG desktop file cache...
building XDG MIME database...
building profile with 4 packages...

Package Transactions (Generations)

A key advantage of Guix is that each package operation is a transaction which increases the reliability of the system. This is a notable advantage over standard package managers like deb/apt and rpm.

The challenge for these package managers is that they are imperative and change the system in-place. But, what happens if we lose power during a large package installation - it's possible that half a package is written to the file system, but the other half is not and the system is now in a bad state. A long running challenge of imperative package managers is that installation/removal cannot be guaranteed to be clean. For the most part it works, but it's particularly problematic when you have complex services. Most users will rarely see a package manager problem, but when you consider that there are hundreds of millions of active Linux systems it only takes a small percentage to have issues for this to be serious.

Package management is complex and various attempts have been made to solve these problems. One approach is that with advanced file systems we can implement snaphots, such as apt-btrfs-snapper, if there's a problem we revert to an earlier snapshot. This works, but it depends on the file system (and layout) and it's quite messy to use. To provide a proper user-experience we'd have to tightly couple the file system and the package manager which isn't good.

Another approach, which started in mobile phones, is to use image-based updating - for example Flatpak and Snap. The advantage is that you can lay down a new image and if there's a problem revert to the old one. There are lots of advantages to this approach including the user-experience, security enhancements and the fact that it's cross-distribution. But, in practise, it litters the system with weird mounts and the indirection makes it slow. Security wise application confinement is an improvement, but that's not really related to the approach. The image-based format is very opaque, what you're getting is a set of bits - there's no way to get from source to binary or to attest that what you're installing is what the distribution intended to ship.

Guix's solution to the problem is to use transactions, and packages are all locally reproducible from source. Lets install the cowsay package, remove it and then look at it from a transaction perspective:

$ guix install cowsay

$ guix remove cowsay
[ lots of output]
Generation 19   Nov 29 2022 10:41:56
 + cowsay       3.7.0   out     /gnu/store/zqjfh5r8zmagcl097szfg13f7pykc76g-cowsay-3.7.0

 Generation 20   Nov 29 2022 10:20:59    (current)
  - cowsay       3.7.0   out     /gnu/store/zqjfh5r8zmagcl097szfg13f7pykc76g-cowsay-3.7.0

As we can see there are Generations, each one represents a specific transaction that the Guix package manager undertook. We can use this to undo any action, for example, lets say we want to undo the removal of cowsay - because everyone needs cowsay on their system - we do this as follows:

$ guix package --roll-back
switched from generation 20 to 19

$ guix package --list-generations
[lots of output]

Generation 19   Nov 29 2022 10:41:56    (current)
+ cowsay       3.7.0   out     /gnu/store/zqjfh5r8zmagcl097szfg13f7pykc76g-cowsay-3.7.0

Generation 20   Nov 29 2022 10:20:59
- cowsay       3.7.0   out     /gnu/store/zqjfh5r8zmagcl097szfg13f7pykc76g-cowsay-3.7.0

As we can see we've moved back to the state we were in at Generation 19, which is that we have cowsay installed.

In fact, more generally we can move to any generation:

guix package --switch-generation=4

When we move to a generation we can then take actions from there, so we can now install new packages. But, bear in mind that we're now rewriting the generations: so if we install a package we'll get a new Generation 5.

This is pretty revolutionary, we can install and remove packages without any risk. Package installations can't mess up our system, and if a new version is buggy or there's an issue we can easily revert to an earlier version.

Deleting Generations

A profile has a set of generations (transactions). Each generation is a set of soft links to the packages installed under /gnu/store. Each package installation (whether a local build, or downloading a binary substitution) creates a new directory under /gnu/store. A soft link is created from the generations directory, to the package installation directories - multiple links as each application is built in its own area of the Store. Each generation creates new soft links that point to the all the locations in the Store for the packages that are active.

We'll explore profiles more another time. For now be aware that when we run the Guix command it's applying the actions to the default profile. The default profile is stored in /home/<user>/.guix-profile. To see how profiles are created look in /var/guix/profiles/per-user/<user>/. What you'll see is a set of links, something like this:

lrwxrwxrwx 1 steve steve 20 Nov 29 16:40 guix-profile -> guix-profile-17-link/
lrwxrwxrwx 1 steve steve 51 Nov 29 10:20 guix-profile-17-link -> /gnu/store/gs2fsghzz5bm2cmznzg4b7cj1zs8r0sb-profile/

What this is telling us is that the default profile (guix-profile) is pointing to the 17th generation which is a specific location in the store. If we look at that location in the store we'll find the bin directory with the applications that were installed for that particular generation.

When we complete a transaction all that Guix is doing is moving the soft link for guix-profile to point to a different location in the store. This is how it can guarantee the transaction.

The more generations that we have, the more space used in /gnu/store: each unique package and version of a package installed takes up its own space. Guix will keep the package installed for as long as any generation points to it. Consequently, it's useful to remove old generations that we no longer need. For an individual user this is done with the guix package --delete-generations command:

# remove a specific generation
$ guix package --delete-generations=1
deleting /var/guix/profiles/per-user/steve/guix-profile-1-link

# remove a range of generations
$ guix package --delete-generations=4..7
deleting /var/guix/profiles/per-user/steve/guix-profile-4-link
deleting /var/guix/profiles/per-user/steve/guix-profile-5-link
deleting /var/guix/profiles/per-user/steve/guix-profile-6-link
deleting /var/guix/profiles/per-user/steve/guix-profile-7-link

# remove generations for a time period
# this can be d(ay), w(eek), m(onth)
$ guix package --delete-generations=2w

# remove all generations except the last
$ guix package --delete-generations

There's actually two default profiles for each user: if we look at /var/guix/profiles/per-user/<user> there are also links for current-guix. These are for the /home/<user>/.config/guix profile which stores the Guix binary itself. So each time we upgrade the Guix command itself, it is writing to this profile:

lrwxrwxrwx 1 steve steve   19 Nov 28 09:20 current-guix -> current-guix-8-link/
lrwxrwxrwx 1 steve steve   51 Oct  5 09:55 current-guix-1-link -> /gnu/store/8k2pdhsi3w8b0qyq5jbmr511wxmbby5q-profile/
lrwxrwxrwx 1 steve steve   51 Oct  5 10:26 current-guix-2-link -> /gnu/store/91bka2zxbk5z8zzgzfikr5wgmnk9l6bz-profile/
[lots more like this]

As we can see this means I've updated the Guix command 8 times, and it's pointing to a build of the guix binary in the Store. If we dig into that directory we'll find a bin directory with the guix and guix-daemon binaries that were installed for that particular generation.

This is useful to know as it makes it easy to under the Guix upgrade procedure.

Managing the store

Every time we install a package, or we install a version of a package, a new hashed directory is created under /gnu/store. This approach means Guix uses more space than a traditional package manager, but the benefit is that transactions make it safer.

The package manager's garbage collection command (guix gc) can optimise how much space is used. It can remove a store item when it's free: this is when no Generation points to that store item.

Consequently, to reduce how much space is used we remove any generations that are no longer required for all users. Then we tell Guix to remove unneeded packages.

We could do this for each individual user, but Guix can optimise all users and all profiles with one command. This will remove any generations specified for all users: this is fine for me as I'm the only actual user on my laptop.

To tell Guix to optimise all user profiles we do this:

# this shows all the profiles that Guix knows about
$ guix gc --list-roots

# remove all generations except for the last
$ guix gc --delete-generations

This will remove all the generations except for that last one. Like the other commands we can also specify a pattern:

# this can be d(ay), w(eek), m(onth)
$ guix gc --delete-generations=2d

# remove a range of generations
$ guix gc --delete-generations=1..7

Each of these commands will also optimise the store by removing any unneeded space.

The alternative is to edit the profiles for each user (root and main user) by hand. If nothing has been changed, there will be two profiles. The /root/.guix-profile profile contains any applications that have been installed for the root user. The /root/.config/guix/current profile contains the guix and guix-daemon binary that the guix service runs.

For each of these we check what's in them and delete any generation we don't want:

# list the generations for the 'app' profile
$ sudo --login guix package --list-generations --profile=/root/.guix-profile

Generation 1    Oct 05 2022 10:02:43    (current)
glibc-locales 2.33    out     /gnu/store/ixzmi6614baf4w37qfjgqrv8hwsl8jcv-glibc-locales-2.33

The only package we have installed is glibc-locales so that the guix daemon doesn't show an error in the logs.

# list the generations for the 'guix' profile
$ sudo --login guix package --list-generations --profile=/root/.config/guix/current
[output showing 8 generations]

# remove some unused generations
$ sudo --login guix package --delete-generations=2w --profile=/root/.config/guix/current
deleting /var/guix/profiles/per-user/root/current-guix-1-link
deleting /var/guix/profiles/per-user/root/current-guix-2-link
deleting /var/guix/profiles/per-user/root/current-guix-3-link
deleting /var/guix/profiles/per-user/root/current-guix-4-link

Having done the same action for our normal user the package manager can now remove any packages that are not needed in the Store.

$ guix gc

See Invoking guix gc in the manual for more information about this topic.

Upgrading an installation

We've covered all of the concepts to understand how upgrading our Guix installation works. The summary is that we have to update the guix build daemon which is done under the root users profile, and then update each of the individual users profiles.

We know that Guix runs a build daemon which either builds the software package itself, or downloads a binary substitution. This runs as a service and we can see the status with:

user@guix-host1:$ sudo systemctl status guix-daemon.service

This binary that is being run is coming from the root users profile. As we know the default is that each user has two profiles:

  • The /root/.guix-profile profile contains any applications that have been installed for the root user.
  • The /root/.config/guix/current profile contains the guix and guix-daemon binary that the guix service runs.

For each of these we check what's in them:

# list the generations for the 'app' profile
$ sudo --login guix package --list-installed --profile=/root/.guix-profile
glibc-locales  2.33  out /gnu/store/blahblah

The only package we have installed is glibc-locales so that the guix daemon doesn't show an error in the logs. We can see when we last did updated the installation with:

$ sudo --login guix pull --list-generations

To update the root users guix daemon we do the following:

$ sudo -i guix pull
Updating channel 'guix' from Git repository at 'https://git.savannah.gnu.org/git/guix.git'...
[ lots more output ]

# restart the daemon service
$ sudo systemctl restart guix-daemon.service
$ sudo systemctl status guix-daemon.service

On my main system it takes about a 2-5 minutes to download the updated git repository, and then a couple of minutes to figure out the derivatives required and download them.

For the root user we're generally not installing any packages other than glibc-locales, so we don't have to do guix upgrade as there isn't anything else in the profile.

Then for each normal user on the system we do the following:

# pull down the update repository and guix command
$ guix pull

The guix pull command itself downloads an updated git repository with the package definitions within it for any channel. It also updates the guix command. After a pull new packages and package versions are available, there might also be new guix commands and Scheme modules available.

We then upgrade the default profile:

# see some news about what's been updated
$ guix pull --news

$ guix upgrade

If we're just running the default profile then this will be sufficient. We'll cover profiles in more depth in future.

Summary

We've explored all the core workflows that are needed to use Guix day-to-day: searching for packages, installing, removing, using transactions and updating/upgrading the installation. We've also looked at the main concepts that are useful to understand for general use: the package store, binary substitutions, generations and a little on profiles. I've been able to use Guix as my main package manager with just this set of recipes.

In the first post I covered the advantages and trade-offs of using Guix as a package manager rather than using something like Flatpak or Snap. The main critical issue that we haven't dealt with this time is how to install proprietary applications like Chromium and Zoom, so we'll deal with that in another post.


Posted in Tech Friday 09 September 2022
Tagged with tech ubuntu guix