The Guix System image API

October 22, 2020

First, I would first like to thank people that are supporting me via Librepay, your help is much appreciated.

Some history

When I started hacking on GNU Guix a few years ago, the image generation mechanism directly caught my attention. Turning a Scheme configuration file into a disk-image by running one command seemed futuristic to me.

At that time, I was working as an embedded software developer using Yocto and Buildroot, and constantly disappointed.

Turning Guix into an alternative quickly became a priority to me. The task wasn't easy. Guix System was only used as an x86_64 distribution, the only supported bootloader was Grub and the image generation used a complex mechanism spawning a virtual machine to produce a disk-image.

Thanks to pending patches from David Craven, I added support for extlinux and u-boot bootloaders. Then, some developments were necessary to support image generation, system reconfiguration and installation on ARMv7.

Soon, I was able to create a disk-image for my BeagleBone Black and wrote an article about it.

Guix System on BeagleBone Black

You can also have a look to the talk I gave at Fosdem 2020.

Further developments were required to make this first hack viable:

Those developments kept me busy for the last two years, but were recently sped up thanks to Jan (janneke) Nieuwenhuizen and his tremendous work on the Hurd. Porting Guix System to the Hurd is achieved by cross-compiling a Guix System image for the i586-pc-gnu architecture. His work is detailed in this article.

Now, let's dive into this Guix System image API.

Guix System image API

Historically, Guix System is centered around an operating-system structure. This structure contains various fields ranging from the bootloader and kernel declaration to the services to install.

Turning this structure into a disk-image requires additional information such as the image label, size and partitioning. That's the purpose of the new image record.

(define-record-type* <image>
  image make-image
  image?
  (name               image-name ;symbol
                      (default #f))
  (format             image-format) ;symbol
  (target             image-target
                      (default #f))
  (size               image-size  ;size in bytes as integer
                      (default 'guess))
  (operating-system   image-operating-system  ;<operating-system>
                      (default #f))
  (partitions         image-partitions ;list of <partition>
                      (default '()))
  (compression?       image-compression? ;boolean
                      (default #t))
  (volatile-root?     image-volatile-root? ;boolean
                      (default #t))
  (substitutable?     image-substitutable? ;boolean
                      (default #t)))

This record also contains the operating-system to instantiate. The format field defines the image type and can be disk-image, compressed-qcow2 or iso9660. In the future, it could be extended to docker or other image types.

A new directory in the Guix sources is dedicated to images definition. For now there are two files:

Let's have a look to pine64.scm. It contains the pine64-barebones-os variable which is a minimal definition of an operating-system dedicated to the Pine A64 LTS board.

(define pine64-barebones-os
  (operating-system
    (host-name "vignemale")
    (timezone "Europe/Paris")
    (locale "en_US.utf8")
    (bootloader (bootloader-configuration
                 (bootloader u-boot-pine64-lts-bootloader)
                 (target "/dev/vda")))
    (initrd-modules '())
    (kernel linux-libre-arm64-generic)
    (file-systems (cons (file-system
                          (device (file-system-label "my-root"))
                          (mount-point "/")
                          (type "ext4"))
                        %base-file-systems))
    (services (cons (service agetty-service-type
                             (agetty-configuration
                              (extra-options '("-L")) ; no carrier detect
                              (baud-rate "115200")
                              (term "vt100")
                              (tty "ttyS0")))
                    %base-services))))

The kernel and bootloader fields are pointing to packages dedicated to this board.

Right below, the pine64-image-type variable is also defined.

(define pine64-image-type
  (image-type
   (name 'pine64-raw)
   (constructor (cut image-with-os arm64-disk-image <>))))

It's using a record we haven't talked about yet, the image-type record, defined this way:

(define-record-type* <image-type>
  image-type make-image-type
  image-type?
  (name           image-type-name) ;symbol
  (constructor    image-type-constructor)) ;<operating-system> -> <image>

The main purpose of this record is to associate a name to a procedure transforming an operating-system to an image. To understand why it is necessary, let's have a look to the command producing a disk-image from an operating-system configuration file:

guix system disk-image my-os.scm

This command expects an operating-system configuration but how should we indicate that we want an image targeting a Pine64 board? We need to provide an extra information, the image-type, by passing the --image-type or -t flag, this way:

guix system disk-image --image-type=pine64-raw my-os.scm

This image-type parameter points to the pine64-image-type defined above. Hence, the operating-system declared in my-os.scm will be applied the (cut image-with-os arm64-disk-image <>) procedure to turn it into an image.

The resulting image looks like:

(image
   (format 'disk-image)
   (target "aarch64-linux-gnu")
   (operating-system my-os)
   (partitions
    (list (partition
           (inherit root-partition)
           (offset root-offset)))))

which is the aggregation of the operating-system defined in my-os.scm to the arm64-disk-image record.

But enough Scheme madness. What does this image API bring to the Guix user?

Image API usage

One can run:

mathieu@cervin:~$ guix system --list-image-types
The available image types are:

   - pine64-raw
   - hurd-raw
   - hurd-qcow2
   - iso9660
   - uncompressed-iso9660
   - raw
   - qcow2

and by writing an operating-system file based on pine64-barebones-os or hurd-barebones-os run:

guix system --image-type=pine64-raw my-pine-os.scm

or,

guix system --image-type=hurd-raw my-hurd-os.scm

to get a disk-image that can directly be written to a support and booted from.

Without changing anything to my-hurd-os.scm, calling:

guix system --image-type=hurd-qcow2 my-hurd-os.scm

will instead produce a Hurd QEMU image.

This image API brings some flexibility to the image generation but there's still room for some improvements.

Future improvements

The first improvement would be to add support for new image-types so that Guix System can be used on other machines such as Novena, Pinebook Pro or MNT Reform. This task should now be relatively straightforward.

Some images built periodically by the CI are made available here. Adding images targeting new supported platforms, such as Pine64 LTS to this page would be nice.

Finally, images targeting foreign architectures are cross-compiled. As Guix cross-compilation support is not optimal yet, improving transparent emulation support could also help.

Now you are more familiar with the image API, feel free to join the party and help porting Guix System to your favourite machine!