(updated versions of this document, plus more, live here)
This will show you how to get Guix running on WSL2.
We're going to go as "minimal" as possible, without starting off one of the readily available WSL2 distros.
Parts of this guide should help with understanding how to set up any custom distro on WSL, not just Guix.
Disclaimer: I'm a Guix nOOb! (hence going through the trouble of installing it on WSL2)
WSL2 distros are, in fact, more like OS containers:
- A single instance of the Microsoft-shipped Linux Kernel is running at any given time (under Hyper-V).
- Running, from a command prompt,
wsl -d distroname
instructs the Kernel to create one such container, the root filesystem coming anext4.vhdx
disk image. - The Kernel executes an
/init
binary which, again, is supplied by Microsoft and cannot be changed/customized. Together with the Kernel itself,init
can be found in%systemroot%\System32\lxss\tools
. /init
executes a shell or the command supplied towsl.exe
/init
also takes care of mounting all the file systems, setting up/dev
, ...- A very useful side-effect of the "single Kernel for all distros" thing is that if, say, you've got a working Ubuntu WSL distro, and a Guix WSL distro that doesn't boot, you can run
dmesg
under Ubuntu and see what's wrong with Guix. The Kernel is shared, the Kernel ring buffer is also shared.
In this guide we'll use the wsl --import
command. Here's what that does:
- Takes a
.tar
or.tar.gz
rootfs archive. - Creates an Hyper-V disk image from it: the
ext4.vhdx
file we mentioned before. - Extracts the contents of the archive into the disk image.
- Adds a bunch of files/directories to the image, including
/init
. - Stopping the
LxssManager
service allows you to "mount"ext4.vhdx
in Windows. The disk image contents can be inspected with f.e. DiskInternals Linux Reader. - Not going to go into the details here, but
docker save
generates an archive which can then be fed towsl --import
. That's a great way to turn a Docker container into a WSL distro.
I'll prefix each command with the "place" it's been run from.
C:\
for a Windows command prompt.(ubuntu) $
for things that are run on an Ubuntu WSL distro. This is used only to create the rootfs. Doesn't have to be Ubuntu, any distro will do. You can also get creative and do everything from Windows.- If no "place" is specified, assume the command is run from the Guix WSL distro.
Let's start by adding busybox to the archive and nothing else.
C:\Users\Giuliano\Documents\WSL>mkdir guix
(ubuntu) /mnt/c/Users/Giuliano/Documents/WSL/guix $ mkdir rootfs
(ubuntu) /mnt/c/Users/Giuliano/Documents/WSL/guix $ cd rootfs
(ubuntu) /mnt/c/Users/Giuliano/Documents/WSL/guix/rootfs $ curl -LO https://busybox.net/downloads/binaries/1.31.0-i686-uclibc/busybox
(ubuntu) /mnt/c/Users/Giuliano/Documents/WSL/guix/rootfs $ cd ..
(ubuntu) /mnt/c/Users/Giuliano/Documents/WSL/guix $ tar -C rootfs -cvf rootfs.tar .
./
./busybox
Now we can try to wsl --import
.
Our main working folder contains:
C:\Users\Giuliano\Documents\WSL\guix>dir
05/01/2020 05:54 PM <DIR> .
05/01/2020 05:51 PM <DIR> ..
05/01/2020 05:53 PM <DIR> rootfs
05/01/2020 05:54 PM 983,040 rootfs.tar
And we only have one Ubuntu distro available:
C:\Users\Giuliano\Documents\WSL\guix>wsl -l
Windows Subsystem for Linux Distributions:
Ubuntu-18.04
--import
command causes ext4.vhdx
to be created:
C:\Users\Giuliano\Documents\WSL\guix>wsl --import guix . rootfs.tar
C:\Users\Giuliano\Documents\WSL\guix>dir
05/01/2020 05:58 PM <DIR> .
05/01/2020 05:51 PM <DIR> ..
05/01/2020 05:58 PM 66,060,288 ext4.vhdx
05/01/2020 05:53 PM <DIR> rootfs
05/01/2020 05:54 PM 983,040 rootfs.tar
...and registers the guix
distro:
C:\Users\Giuliano\Documents\WSL\guix>wsl -l
Windows Subsystem for Linux Distributions:
Ubuntu-18.04
guix
Can we actually run the guix
distro? Remember there's nothing but busybox
in there so we're going to try busybox's own sh
implementation:
C:\Users\Giuliano\Documents\WSL\guix>wsl -d guix /busybox sh
C:\Users\Giuliano\Documents\WSL\guix>
Doesn't work, we are immediately taken back to the Windows prompt. :( Let's try the dmesg
-from-Ubuntu trick explained above:
(ubuntu) $ dmesg
[..]
[29242.265446] EXT4-fs (sde): mounted filesystem with ordered data mode. Opts: discard,errors=remount-ro,data=ordered
[29242.270345] init: (1) ERROR: ConfigUpdateInformation:2623: creat /etc/hostname failed: 2
[29242.270349] init: (1) ERROR: ConfigUpdateInformation:2657: creat /etc/hosts failed 2
[29242.270494] init: (2) ERROR: UtilCreateProcessAndWait:635: /bin/mount failed with 2
[29242.270583] init: (1) ERROR: UtilCreateProcessAndWait:655: /bin/mount failed with status 0x
[29242.270585] ff00
[29242.270589] init: (1) ERROR: ConfigMountFsTab:2110: Processing fstab with mount -a failed.
[29242.271405] init: (3) ERROR: UtilCreateProcessAndWait:635: /bin/mount failed with 2
[29242.271506] init: (1) ERROR: UtilCreateProcessAndWait:655: /bin/mount failed with status 0x
[29242.271508] ff00
/etc/hostname
and /etc/hosts
don't exist. /bin/mount
seems to "fail".
According to wsl --help
, the "default shell" is used well, by default. Our rootfs doesn't have an /etc/passwd
yet, Microsoft's /init
can't check it to know what the root user shell should be. --exec
promises to run a command as is.
C:\Users\Giuliano>wsl --help
Usage: wsl.exe [Argument] [Options...] [CommandLine]
Arguments for running Linux binaries:
If no command line is provided, wsl.exe launches the default shell.
--exec, -e <CommandLine>
Execute the specified command without using the default Linux shell.
[..]
Does --exec
work?
C:\Users\Giuliano\Documents\WSL\guix>wsl -d guix --exec /busybox sh
/ #
It does! We've got shell access to the hopefully-soon-to-be guix
distro.
Now that we're in, we can check what WSL (--import
) added our bare rootfs.tar
. Notably, /root
, /etc
, /tmp
and /var
are missing...
/ # /busybox find / | /busybox grep -v '^.proc\|^.dev\|^.sys'
/
/busybox
/mnt
/mnt/wsl
/mnt/c
/sbin
/sbin/mount.drvfs
/bin
/bin/wslpath
/run
/run/WSL
/run/WSL/7_interop
/run/WSL/1_interop
/run/user
/run/shm
/run/lock
/init
/lost+found
/
Despite the mount
errors in dmesg
, things look OK:
/ # /busybox mount
/dev/sde on / type ext4 (rw,relatime,discard,errors=remount-ro,data=ordered)
tmpfs on /mnt/wsl type tmpfs (rw,relatime)
tools on /init type 9p (ro,dirsync,relatime,aname=tools;fmask=022,loose,access=client,trans=fd,rfd=6,wfd=6)
none on /dev type devtmpfs (rw,nosuid,relatime,size=3194532k,nr_inodes=798633,mode=755)
sysfs on /sys type sysfs (rw,nosuid,nodev,noexec,noatime)
proc on /proc type proc (rw,nosuid,nodev,noexec,noatime)
devpts on /dev/pts type devpts (rw,nosuid,noexec,noatime,gid=5,mode=620,ptmxmode=000)
none on /run type tmpfs (rw,nosuid,noexec,noatime,mode=755)
none on /run/lock type tmpfs (rw,nosuid,nodev,noexec,noatime)
none on /run/shm type tmpfs (rw,nosuid,nodev,noatime)
none on /run/user type tmpfs (rw,nosuid,nodev,noexec,noatime,mode=755)
[..]
Guix refuses to perform certain operations as root
so let's add the users/group it needs. groupadd
/useradd
are not available yet, so we're going to do it manually:
### Make sure you're running these from the Guix WSL distro!
# /busybox mkdir -p /root /etc /tmp /var/run /run /home
# /busybox chmod 1777 /tmp
# /busybox cat <<EOM >> /etc/passwd
root:x:0:0:root:/root:/bin/bash
guixbuilder01:x:999:999:Guix build user 01:/var/empty:/usr/sbin/nologin
guixbuilder02:x:998:999:Guix build user 02:/var/empty:/usr/sbin/nologin
guixbuilder03:x:997:999:Guix build user 03:/var/empty:/usr/sbin/nologin
guixbuilder04:x:996:999:Guix build user 04:/var/empty:/usr/sbin/nologin
guixbuilder05:x:995:999:Guix build user 05:/var/empty:/usr/sbin/nologin
guixbuilder06:x:994:999:Guix build user 06:/var/empty:/usr/sbin/nologin
guixbuilder07:x:993:999:Guix build user 07:/var/empty:/usr/sbin/nologin
guixbuilder08:x:992:999:Guix build user 08:/var/empty:/usr/sbin/nologin
guixbuilder09:x:991:999:Guix build user 09:/var/empty:/usr/sbin/nologin
guixbuilder10:x:990:999:Guix build user 10:/var/empty:/usr/sbin/nologin
EOM
# /busybox cat <<EOM >> /etc/group
root:x:0:
guixbuild:x:999:guixbuilder01,guixbuilder02,guixbuilder03,guixbuilder04,guixbuilder05,guixbuilder06,guixbuilder07,guixbuilder08,guixbuilder09,guixbuilder10
EOM
At this point /etc
contains:
/ # /busybox find /etc
/etc
/etc/passwd
/etc/group
WSL distros usually have the host C:\
drive automagically mounted on /mnt/c
(by the usual suspect: /init
), but we don't:
/tmp # /busybox mount | /busybox grep mnt
tmpfs on /mnt/wsl type tmpfs (rw,relatime)
/tmp #
Copy /busybox
to /bin/mount
:
/tmp # /busybox cp /busybox /bin/mount
Logout of the Guix WSL distro, terminate it, make sure it's stopped:
C:\Users\Giuliano>wsl -l -v
NAME STATE VERSION
Ubuntu-18.04 Running 2
guix Running 2
C:\Users\Giuliano>wsl -t guix
C:\Users\Giuliano>wsl -l -v
NAME STATE VERSION
Ubuntu-18.04 Running 2
guix Stopped 2
Log back in and notice how WSL (now that /etc
exists) created resolv.conf
, hosts
and hostname
for us:
C:\Users\Giuliano>wsl -d guix --exec /busybox sh
/ # /busybox find /etc
/etc
/etc/resolv.conf
/etc/hosts
/etc/passwd
/etc/hostname
/etc/group
The /busybox -> /bin/mount
trick caused /mnt/c
to appear:
/ # /busybox mount | /busybox grep mnt
tmpfs on /mnt/wsl type tmpfs (rw,relatime)
C:\134 on /mnt/c type 9p (rw,dirsync,noatime,aname=drvfs;path=C:\;uid=0;gid=0;symlinkroot=/mnt/,mmap,access=client,msize=65536,trans=fd,rfd=10,wfd=10)
/ #
Networking works now. If /busybox wget
properly supported HTTPS, we could use it to download the Guix binary tarball.
/tmp # /busybox wget https://ftp.gnu.org/gnu/guix/guix-binary-1.1.0.x86_64-linux.tar.xz
Connecting to ftp.gnu.org (209.51.188.20:443)
wget: note: TLS certificate validation not implemented
wget: TLS error from peer (alert code 40): handshake failure
wget: error getting response: Connection reset by peer
/tmp #
Except that it doesn't. Download the tarball with Windows and extract it from /mnt/c
instead.
/ # /busybox tar -C / -xvJf /mnt/c/Users/Giuliano/Downloads/guix-binary-1.1.0.x86_64-linux.tar.xz
/gnu
and /var/guix
are in place:
/ # /busybox ls -ld /gnu /var/guix
drwxr-xr-x 3 root root 4096 May 1 21:37 /gnu
drwxr-xr-x 5 root root 4096 May 1 21:37 /var/guix
/ #
As per the Binary Installation instructions, activate the profile included in the tarball:
/ # /busybox mkdir -p ~root/.config/guix
/ # /busybox ln -sf /var/guix/profiles/per-user/root/current-guix ~root/.config/guix/current
/ # GUIX_PROFILE="`echo ~root`/.config/guix/current"
/ # source $GUIX_PROFILE/etc/profile
And finally start the daemon:
/ # guix-daemon --build-users-group=guixbuild &
Great, in theory we can now start using Guix to install stuff!
Make sure to enable subtitutes, unless you want Guix to buid everything from source:
/ # guix archive --authorize < /var/guix/profiles/per-user/root/current-guix/share/guix/ci.guix.gnu.org.pub
But guix pull
doesn't work... :(
/ # guix pull
accepted connection from pid 23, user root
substitute: guix substitute: warning: ci.guix.gnu.org: host not found: Servname not supported for ai_socktype
[..]
build of /gnu/store/dd5mga544azygz2hhs3ksp1d8bgmi38j-isrgrootx1.pem.drv failed
View build log at '/var/log/guix/drvs/dd/5mga544azygz2hhs3ksp1d8bgmi38j-isrgrootx1.pem.drv.bz2'.
cannot build derivation `/gnu/store/j814i78jg0832akx856ha6za526rdvy6-le-certs-0.drv': 1 dependencies couldn't be built
guix pull: error: build of `/gnu/store/j814i78jg0832akx856ha6za526rdvy6-le-certs-0.drv' failed
The build log also mentions those Servname not supported for ai_socktype
errors. Googling tells us they are caused by a broken/missing /etc/services
which indeed does not exist. Let's populate it with the basics:
/ # /busybox cat <<EOM >> /etc/services
ftp-data 20/tcp
ftp 21/tcp
ssh 22/tcp # SSH Remote Login Protocol
domain 53/tcp # Domain Name Server
domain 53/udp
http 80/tcp www # WorldWideWeb HTTP
https 443/tcp # http protocol over TLS/SSL
ftps-data 989/tcp # FTP over SSL (data)
ftps 990/tcp
http-alt 8080/tcp webcache # WWW caching service
http-alt 8080/udp
EOM
guix pull
now works...
Now it'd be great if we could get our Guix WSL distro to be more like GuixSD. F.e., Guix itself should be able to make a "more proper" /etc/passwd
than the one we manually edited, fix /etc/services
, ...
To achieve that, we're going to try and put together a Guix System Configuration file. A WSL distro, unlike a VM or a bare metal server, doesn't need a lot of the stuff that Guix's operating-system declaration expects. In my configuration file I tried to convince Guix that it's fine to not care about the kernel/bootloader, deal with disks, ...
This is all very hacky/uneducated, but here goes the wsl-config.scm
I'm using:
;(use-modules (oop goops))
;(add-to-load-path "/root/.guix-profile/share/guile/site/3.0/")
;(use-modules (ice-9 readline))
;(activate-readline)
(use-modules
(gnu)
(guix profiles)
(guix packages)
(srfi srfi-1))
(use-service-modules networking ssh)
(use-package-modules screen vim)
(define os
(operating-system
(host-name "scarpa")
(timezone "Europe/London")
(locale "en_US.utf8")
(kernel hello) ; dummy package
(initrd (lambda* (. rest) (plain-file "dummyinitrd" "dummyinitrd")))
(initrd-modules '())
(firmware '())
(bootloader
(bootloader-configuration
(bootloader
(bootloader
(name 'dummybootloader)
(package hello)
(configuration-file "/dev/null")
(configuration-file-generator (lambda* (. rest) (computed-file "dummybootloader" #~(mkdir #$output))))
(installer #~(const #t))))))
(file-systems (list (file-system
(device "/dev/sdb")
(mount-point "/")
(type "ext4")
(mount? #t)))) ; saying #f here doesn't work :(
(users (cons (user-account
(name "giuliano")
(group "users")
(supplementary-groups '("wheel")))
%base-user-accounts))
(packages (append (list screen ; global packages to add
vim)
(remove
(lambda (x)
(member (package-name x)
(list "zile" ; global packages to not add
"nano"
"info-reader"
"pciutils"
"usbutils"
"util-linux-with-udev"
"kmod"
"eudev"
"isc-dhcp"
"iw"
"wireless-tools")))
%base-packages)))
(essential-services
(remove
(lambda (x)
(member (service-type-name (service-kind x))
(list 'firmware 'linux-bare-metal)))
(operating-system-default-essential-services this-operating-system)))
(services (list (service guix-service-type)
(service nscd-service-type)))))
; Hackish way to avoid building/including linux-module-database in the system,
(define hooks-modifier
(eval '(record-modifier <profile> 'hooks)
(resolve-module '(guix profiles))))
(define my-essential-services (operating-system-essential-services os))
(define system-service (car my-essential-services))
(unless (eq? 'system (service-type-name (service-kind system-service)))
(raise-exception "The first essential service is not 'system'"))
(define kernel-profile (car (cdr (car (service-value system-service)))))
(unless (string=? "hello" (manifest-entry-name (car (manifest-entries (profile-content kernel-profile)))))
(raise-exception "I was expecting 'hello' as the (dummy) kernel"))
(hooks-modifier kernel-profile '())
(define os
(operating-system
(inherit os)
(essential-services my-essential-services)))
os
Passing the file to guix system reconfigure
...
~ # guix system reconfigure --no-bootloader wsl-config.scm
...creates a new "instance" of the entire OS and switches to it (that's the beauty of GuixSD and NixOS!).
[..]
building /gnu/store/68wyiashnhc05wspcn26p3fw0vg2wn2l-switch-to-system.scm.drv...
making '/gnu/store/b544x90ncmxm95344m0c4cygz98w2azr-system' the current system...
setting up setuid programs in '/run/setuid-programs'...
populating /etc from /gnu/store/5s856h5r91xslm90ymm3gn179q6z4hmi-etc...
substitute: updating substitutes from 'https://ci.guix.gnu.org'... 100.0%
0.0 MB will be downloaded:
/gnu/store/q9wrh0rrhq3sy3n2i6m73ijql7a1m2nm-module-import-compiled
downloading from https://ci.guix.gnu.org/nar/lzip/q9wrh0rrhq3sy3n2i6m73ijql7a1m2nm-module-import-compiled ...
module-import-compiled 24KiB 1.3MiB/s 00:00 [##################] 100.0%
guix system: warning: while talking to shepherd: No such file or directory
~ #
[..]
After the previous step the system is functional. But, when the Guix WSL distro starts afresh (because it was stopped with wsl -t guix
, Windows was restarted or other), you'll notice that f.e. /run/setuid-programs
disappeared (sudo
stops working). Some important Guix things live in /run
, which is mounted by WSL on a tmpfs
(as per the FHS). Because WSL distros don't boot like normal VMs/servers, Guix doesn't have a chance to populate /run
at boot time.
The source and this thread help with understanding how to "boot GuixSD by hand":
export GUIX_NEW_SYSTEM=$(/busybox readlink -f /var/guix/profiles/system)
# $GUIX_NEW_SYSTEM/boot needs this to exist even though /run is expected to be empty.
# I installed GuixSD in a proper VM and /run is not on tmpfs, so I'm not sure.
/busybox ln -s none /run/current-system
/var/guix/profiles/system/profile/bin/guile --no-auto-compile $GUIX_NEW_SYSTEM/boot &
Running the above populates /run
and starts shepherd
.
~ # . /etc/profile
~ # pstree
init─┬─init───init───busybox─┬─pstree
│ └─shepherd─┬─guix-daemon
│ ├─nscd───7*[{nscd}]
│ └─4*[{shepherd}]
└─{init}
~ #
I don't know why but, after shepherd
is started with the recipe above, you can't open another "terminal" (I mean: open cmd.exe
, run wsl -d guix --exec /busybox sh
. The wsl
command returns immediately back to prompt. dmesg
contains:
[ 3541.851862] init: (48) ERROR: CreateProcessEntryCommon:600: initgroups failed 29
[ 3541.851865] init: (48) ERROR: CreateProcessEntryCommon:645: Create process not expected to return
This isn't really a problem for me, one terminal is enough and I use tmux
anyway.
I start Guix with wsl -d guix --exec /busybox sh /root/boot.sh
, where boot.sh
contains:
#!/busybox sh
export GUIX_NEW_SYSTEM=$(/busybox readlink -f /var/guix/profiles/system)
# $GUIX_NEW_SYSTEM/boot needs this to exist even though /run is expected to be empty.
# I installed GuixSD in a proper VM and /run is not on tmpfs, so I'm not sure.
/busybox ln -s none /run/current-system
/var/guix/profiles/system/profile/bin/guile --no-auto-compile $GUIX_NEW_SYSTEM/boot &
/busybox sleep 3
source /etc/profile
# why are these permissions not there in the first place?
for f in ping su sudo; do
chmod 4755 $(readlink -f $(which $f))
done
su -l giuliano -c tmux
Freshly installed:
bash-5.0# df -h
Filesystem Size Used Avail Use% Mounted on
/dev/sdb 251G 2.7G 236G 2% /
[email protected]