During my holidays some rare conditions were met: I had some free time and
access to a Windows machine. Time to review this
patch from Alex Griffin adding WSL
support to the guix system image
command.
I'm not exactly sure how WSL works, but it looks like some kind of emulation layer allowing to run GNU/Linux distributions on Windows. To be frank, I hope that I won't have to use this WSL thing in the future. However, some less fortunate people are forced to work in a Windows environment so being able to run GNU Guix could be a lifeline.
Merge the patch
Let's have a short look to the patches proposed by Alex to better understand what adding WSL support means.
First stop is to add tarball
support to guix system image
.
It means that if you were to run:
guix system image --image-type=tarball my-image.scm
you would obtain a tarball archive containing the Guix System image declared
in my-image.scm
. Handy and pretty straightforward.
Then, Alex added some plumbing to declare that a WSL image is basically a
tarball image. My only concern here is that generating tar.gz
archives
compressed at level 9 takes quite some times. I wonder if Windows would accept
zstd
archives or so. This question is left open for early adopters.
The last required patch, that I tweaked a little bit adds a (gnu system images wsl2)
module. In this module, the wsl-os
variable is declared.
(define-public wsl-os
(operating-system
(host-name "gnu")
(timezone "Etc/UTC")
(bootloader
(bootloader-configuration
(bootloader dummy-bootloader)))
(kernel dummy-kernel)
(initrd dummy-initrd)
(initrd-modules '())
(firmware '())
(file-systems '())
...))
This operating-system is meant to be the foundation for any future WSL image. It is a bit special because several fields are set to dummy packages. That's because the WSL environment already provides the Linux kernel and possibly the initrd, mounts the file-systems and so on.
The strategy of defining a dummy-kernel
, dummy-initrd
and
dummy-bootloader
for this specific use-case works but is not perfectly
satisfactory to me. Maybe we should not define those dummy packages directly
in (gnu system images wsl2)
but rather in (gnu system)
or so.
Another point worth noticing is how Alex managed to start the Shepherd, the init system used by Guix System. WSL is taking care of starting the Linux kernel but it cannot know how to start the Shepherd because that's the role of the Guix System initrd.
To work around that, a wsl-boot-program
script is defined. This script takes
care of starting the Shepherd and spawning a shell. It is registered as the
login program of the root account to trick WSL into running it.
(users (cons* (user-account
(name "guest")
(group "users")
(supplementary-groups '("wheel")) ; allow use of sudo
(password "")
(comment "Guest of GNU"))
(user-account
(inherit %root-account)
(shell (wsl-boot-program "guest")))
%base-user-accounts))
OK, so this is a good start and all those patches are now merged.
Generate images
Now let's generate some images. We can first try to generate a basic image
that does nothing more than instantiating the wsl-os
operating-system and
produce a WSL bootable image out of it. From a Guix checkout, we can run:
guix system image gnu/system/images/wsl2.scm
/gnu/store/cgbd1yl5ab12pn7a103pixld868lcslf-image.tar.gz
that's it! The resulting image can be imported in Windows using the following commands:
wsl --import Guix ./guix cgbd1yl5ab12pn7a103pixld868lcslf-image.tar.gz
wsl -d Guix
From within the WSL environment we can stop holding our breath and enjoy a classic Guix System distribution.
That's nice but we can go a step further. Let's say we want to deploy a WSL
image which role is to run a particular service, say tailon
a web server for
browsing some system logs. We can define our operating-system this way
(use-modules (gnu system images wsl2)
(gnu services web))
(operating-system
(inherit wsl-os)
(services
(cons
(service tailon-service-type)
(operating-system-user-services wsl-os))))
run,
guix system image --image-type=wsl2 wsl-tailon.scm
import the image, like the first one, and from a Windows browser we can
directly use the tailon
service.
We can also choose a more complex service: the Nginx web server.
(use-modules (gnu system images wsl2)
(gnu packages web)
(gnu services web)
(srfi srfi-1))
(operating-system
(inherit wsl-os)
(services
(cons*
(service dummy-service-type)
(simple-service
'index
activation-service-type
(with-imported-modules '((guix build utils))
#~(begin
(use-modules (guix build utils))
(mkdir-p "/srv/http/www.example.com")
(copy-file
#$(file-append nginx "/share/nginx/html/index.html")
"/srv/http/www.example.com/index.html"))))
(service nginx-service-type
(nginx-configuration
(server-blocks
(list (nginx-server-configuration
(server-name '("www.example.com"))
(listen '("8080"))
(root "/srv/http/www.example.com"))))))
(operating-system-user-services wsl-os))))
This way, nginx
will serve the index.html
file at the
http://localhost:8080
location. Sadly this won't work because the nginx
Shepherd service requires the loopback
service. If we add the loopback
service however, we will get a run time issue because WSL already took care of
creating this interface and Shepherd will fail to start it and refuse to start
nginx
which depends of the loopback
service.
As a quick hack, I defined a dummy-service
which is conveniently called
loopback
and does nothing. The operating-system declaration now looks like:
(use-modules (gnu system images wsl2)
(guix gexp)
(gnu packages web)
(gnu services shepherd)
(gnu services web)
(srfi srfi-1))
(define (dummy-service _)
(list
(shepherd-service
(documentation "")
(provision '(loopback))
(start #~(const #t))
(stop #~(const #t))
(respawn? #f))))
(define dummy-service-type
(service-type
(name 'loopback)
(extensions
(list (service-extension shepherd-root-service-type
dummy-service)))
(default-value '())
(description "")))
(operating-system
(inherit wsl-os)
(services
(cons*
(service dummy-service-type)
(simple-service
'index
activation-service-type
(with-imported-modules '((guix build utils))
#~(begin
(use-modules (guix build utils))
(mkdir-p "/srv/http/www.example.com")
(copy-file
#$(file-append nginx "/share/nginx/html/index.html")
"/srv/http/www.example.com/index.html"))))
(service nginx-service-type
(nginx-configuration
(server-blocks
(list (nginx-server-configuration
(server-name '("www.example.com"))
(listen '("8080"))
(root "/srv/http/www.example.com"))))))
(operating-system-user-services wsl-os))))
And just like before, we can generate an image, import it, and open a Windows web browser to check that is works correctly.
All in all that's a pretty good start that still needs to be polished a bit by future users. It is also a good demonstration that generating new kind of system images is quite easy with the Guix System image API. Thank you Alex for working on that topic!