diff mbox series

[bug#41011] gnu: grub: Support for network boot via tftp/nfs.

Message ID CA441935-DB65-4DF8-BB7F-844B541BB554@vodafonemail.de
State Accepted
Headers show
Series [bug#41011] gnu: grub: Support for network boot via tftp/nfs. | expand

Checks

Context Check Description
cbaines/applying patch fail View Laminar job

Commit Message

Stefan June 6, 2020, 1:33 p.m. UTC
* gnu/bootloader/grub.scm (grub-efi-net-bootloader): New efi bootloader for
network booting via tftp/nfs.
(install-grub-efi-net): New bootloader installer for tftp.
(grub-root-search): Adding support for tftp root.
(eye-candy): Use 'gfxterm' for all systems if selected via 'terminal-outputs'.
* gnu/system.scm (read-boot-parameters): Prevent devices with ":/" from being
treated as a file system label.
* gnu/build/linux-boot.scm (device-string->file-system-device): Prevent devices
with ":/" from being treated as a file system label.
---
 gnu/bootloader/grub.scm  | 127 +++++++++++++++++++++++++++------------
 gnu/build/linux-boot.scm |   1 +
 gnu/system.scm           |   3 +-
 3 files changed, 93 insertions(+), 38 deletions(-)

Comments

Danny Milosavljevic June 6, 2020, 5:37 p.m. UTC | #1
Hi Stefan,

thanks for the patch!

Could you create a new record type (something like <nfs-mount>)
and use that instead of magical ":/" where possible (i.e. everywhere except
gnu/build/linux-boot.scm)?

See also ./gnu/system/file-systems.scm file-system-label for how that is done
(just copy the record and the serializer for the initrd parameter and adapt it).

I know that previously we did magical string parsing, but I'd like not to add
to it.

Also, is the eye-candy change necessary?  I'm not opposed to it, but it
would be nicer in an extra patch (separation of concerns), if necessary in
this patch series.
Danny Milosavljevic June 9, 2020, 1:44 p.m. UTC | #2
Hi Stefan,

On Tue, 9 Jun 2020 14:16:18 +0200
Stefan <stefan-guix@vodafonemail.de> wrote:

> I made your requested change (using <nfs-share>), but when trying a 'guix system reconfigure …' I only get this error:
> 
> guix system: error: #<nfs-share ":/volume5/guix-system">: invalid G-expression input
> 
> There is no backtrace, no nothing. I can’t figure out, which part of the code tries to read this serialisation. Do you have a clue?

I think it's a problem in the transfer of the record from host side to build
side, in this case mostly via Linux kernel command line arguments, which are
strings.

The host side code can use these records, but eventually the build side code
has to get a string and reconstruct it.

I don't know how to debug it except for running the translation in my head.
The error reporting facility could be improved a lot :(

gnu/build/linux-boot.scm is build side.  It runs when the system boots.

In there it has 

  (define (device-string->file-system-device device-string)
    ;; The "--root=SPEC" kernel command-line option always provides a
    ;; string, but the string can represent a device, a UUID, a
    ;; label or a NFS spec.  So check for all three.
    (cond ((string-prefix? "/" device-string) device-string)
          ((uuid device-string) => identity)
          (else (file-system-label device-string))))

But looking at the condition (uuid device-string) I have no idea what that means,
or is bound to!

It was introduced by:

commit 281d80d8e547fe663aaacb3226119166dd3100f9
Author: Maxim Cournoyer <maxim.cournoyer@gmail.com>
Date:   Tue Feb 11 14:00:06 2020 -0500

    linux-boot: Refactor boot-system.
    
    The --root option can now be omitted, and inferred from the root file system
    declaration instead.
    
    * gnu/build/file-systems.scm (canonicalize-device-spec): Extend to support NFS
    directly, and...
    * gnu/build/linux-boot.scm (boot-system): ...remove NFS special casing from
    here.  Remove nested definitions for root-fs-type, root-fs-flags and
    root-fs-options, and bind those inside the let* instead.  Make "--root" take
    precedence over the device field string representation of the root file
    system.
    * doc/guix.texi (Initial RAM Disk): Document that "--root" can be left
    unspecified.

If "--root" is not specified, it should then pick up the root from the root
file system declaration. 

@Maxim: How can we use your commit to boot from NFS?  I see no way to leave
"--root" off in the first place for a regular user.  (Stefan's patch adds
support for network boot via tftp/nfs via grub efi net) 

Cheers,
   Danny
Stefan June 9, 2020, 2:25 p.m. UTC | #3
Hi!

> Am 09.06.2020 um 15:44 schrieb Danny Milosavljevic <dannym@scratchpost.org>:
> 
> gnu/build/linux-boot.scm is build side.  It runs when the system boots.
> 
> In there it has 
> 
>  (define (device-string->file-system-device device-string)
>    ;; The "--root=SPEC" kernel command-line option always provides a
>    ;; string, but the string can represent a device, a UUID, a
>    ;; label or a NFS spec.  So check for all three.
>    (cond ((string-prefix? "/" device-string) device-string)
>          ((uuid device-string) => identity)
>          (else (file-system-label device-string))))

Yes, that code runs when the system boots. But in my case the error is thrown already during the reconfigure itself. The trouble must be way before. 


Bye

Stefan
Maxim Cournoyer June 11, 2020, 4:21 a.m. UTC | #4
Hello Danny,

Danny Milosavljevic <dannym@scratchpost.org> writes:

> Hi Stefan,
>
> On Tue, 9 Jun 2020 14:16:18 +0200
> Stefan <stefan-guix@vodafonemail.de> wrote:
>
>> I made your requested change (using <nfs-share>), but when trying a 'guix system reconfigure …' I only get this error:
>> 
>> guix system: error: #<nfs-share ":/volume5/guix-system">: invalid G-expression input
>> 
>> There is no backtrace, no nothing. I can’t figure out, which part of the code tries to read this serialisation. Do you have a clue?
>
> I think it's a problem in the transfer of the record from host side to build
> side, in this case mostly via Linux kernel command line arguments, which are
> strings.
>
> The host side code can use these records, but eventually the build side code
> has to get a string and reconstruct it.
>
> I don't know how to debug it except for running the translation in my head.
> The error reporting facility could be improved a lot :(
>
> gnu/build/linux-boot.scm is build side.  It runs when the system boots.
>
> In there it has 
>
>   (define (device-string->file-system-device device-string)
>     ;; The "--root=SPEC" kernel command-line option always provides a
>     ;; string, but the string can represent a device, a UUID, a
>     ;; label or a NFS spec.  So check for all three.
>     (cond ((string-prefix? "/" device-string) device-string)
>           ((uuid device-string) => identity)
>           (else (file-system-label device-string))))
>
> But looking at the condition (uuid device-string) I have no idea what that means,
> or is bound to!

It means that if the device-string (a string as its name imply) contains
something that represent a UUID, return its corresponding UUID object.
`uuid' comes from (gnu system uuid).  Does that answer your question?

> It was introduced by:
>
> commit 281d80d8e547fe663aaacb3226119166dd3100f9
> Author: Maxim Cournoyer <maxim.cournoyer@gmail.com>
> Date:   Tue Feb 11 14:00:06 2020 -0500
>
>     linux-boot: Refactor boot-system.
>     
>     The --root option can now be omitted, and inferred from the root file system
>     declaration instead.
>     
>     * gnu/build/file-systems.scm (canonicalize-device-spec): Extend to support NFS
>     directly, and...
>     * gnu/build/linux-boot.scm (boot-system): ...remove NFS special casing from
>     here.  Remove nested definitions for root-fs-type, root-fs-flags and
>     root-fs-options, and bind those inside the let* instead.  Make "--root" take
>     precedence over the device field string representation of the root file
>     system.
>     * doc/guix.texi (Initial RAM Disk): Document that "--root" can be left
>     unspecified.
>
> If "--root" is not specified, it should then pick up the root from the root
> file system declaration. 
>
> @Maxim: How can we use your commit to boot from NFS?  I see no way to leave
> "--root" off in the first place for a regular user.  (Stefan's patch adds
> support for network boot via tftp/nfs via grub efi net) 

This commit just makes it so that if --root was ever removed from the
generated GRUB configuration, it'd still be able to find it
automatically.  It was added to be consistent with the fact that mount
options are automatically taken from the root file system without any
user option (and I initially had a rootflags user option but this was
deemed unnecessary at the time).  I'll probably add it back soon now
that I've found a valid use case for it (passing the 'degraded' mount
option for booting from a degraded Btrfs RAID is one).

Does it cause a problem for the NFS boot via 'grub efi net' (I know
nothing about it -- any link for a recommended reading?)  When booting
from NFS using the nfsroot Linux option, it's possible to specify a
'/dev/nfs' as the root kernel parameter.  /dev/nfs is not a real block
device, it's just a stub hinting the kernel that its root file system is
on NFS.  Perhaps that can be used?

Maxim
Stefan June 11, 2020, 11:36 a.m. UTC | #5
Hi!

I found the root-cause of my problem: I missed a conversion in gnu/system.scm (device-sexp). Sorry for the noise.


Bye

Stefan
Maxim Cournoyer June 11, 2020, 1:07 p.m. UTC | #6
Hi Stefan,

Stefan <stefan-guix@vodafonemail.de> writes:

> Hi!
>
> I found the root-cause of my problem: I missed a conversion in gnu/system.scm (device-sexp). Sorry for the noise.

No worries; thanks for working on bettering NFS support!

Maxim
Danny Milosavljevic June 11, 2020, 1:19 p.m. UTC | #7
Hi Maxim,
Hi Stefan,

On Thu, 11 Jun 2020 00:21:11 -0400
Maxim Cournoyer <maxim.cournoyer@gmail.com> wrote:

> >   (define (device-string->file-system-device device-string)
> >     ;; The "--root=SPEC" kernel command-line option always provides a
> >     ;; string, but the string can represent a device, a UUID, a
> >     ;; label or a NFS spec.  So check for all three.
> >     (cond ((string-prefix? "/" device-string) device-string)
> >           ((uuid device-string) => identity)
> >           (else (file-system-label device-string))))
> >
> > But looking at the condition (uuid device-string) I have no idea what that means,
> > or is bound to!  
> 
> It means that if the device-string (a string as its name imply) contains
> something that represent a UUID, return its corresponding UUID object.
> `uuid' comes from (gnu system uuid).  Does that answer your question?

Oh!  I've looked at now it but I still don't get it.

How can that be a cond condition?

Or asked differently, what if device-string does not represent an uuid, how
come device-string->file-system-device does not enter the branch "=> identity" ?

> Does it cause a problem for the NFS boot via 'grub efi net' (I know
> nothing about it -- any link for a recommended reading?)

https://manpages.debian.org/testing/grub-common/grub-mknetdir.1.en.html

Stefan has written a patch supporting it for Guix.

(canonicalize-device-spec seems to expect a nfs share reference to be a
string, too.  Is that on purpose?  No <nfs-share> record?  That's kinda
weird when we even have records for device labels and uuids--but we don't
have them for something that's actually complicated to specify? [1] :) )

>  When booting
> from NFS using the nfsroot Linux option, it's possible to specify a
> '/dev/nfs' as the root kernel parameter.  /dev/nfs is not a real block
> device, it's just a stub hinting the kernel that its root file system is
> on NFS.  Perhaps that can be used?

Hmm maybe.  @Stefan?

Also, could we have a system test testing this stuff?  I can write the
actual test--but could you tell me how to use the functionality introduced
in this patch?

[1] Reference docs for nfsroot: https://www.kernel.org/doc/Documentation/filesystems/nfs/nfsroot.txt
Stefan June 11, 2020, 11:43 p.m. UTC | #8
Hi!

Now that the new record <nfs-share> is working and seeing the amount of changes to make this working, I get the impression that this is unnecessarily complicated.

In the end there will be the "--root=" option for the kernel, which is only a plain string. And most of the device record related functions in the end target to produce a string.

And then there is (device-string->file-system-device) in gnu/build/linux-boot.scm to convert this string back into a device record.

As long as this conversion to and from string is necessary, there is no real benefit in having different record types for (file-system (device …)), it could just be a string. Then there would only be the need for a simple parser function like (device-string->device-type) to determine the type of device to be used in places where the type matters.


Bye

Stefan
Stefan June 12, 2020, 12:06 a.m. UTC | #9
Hi Maxim!

> Does it cause a problem for the NFS boot via 'grub efi net' (I know
> nothing about it -- any link for a recommended reading?)

No, that’s no problem.

Regarding ‘grub efi net’: Just take a look at the GRUB manual. Actually GRUB is not dealing with NFS at all, it is using TFTP to load files. Only Linux uses NFS to mount its root file system. 

> When booting
> from NFS using the nfsroot Linux option, it's possible to specify a
> '/dev/nfs' as the root kernel parameter.  /dev/nfs is not a real block
> device, it's just a stub hinting the kernel that its root file system is
> on NFS.  Perhaps that can be used?

These “root=/dev/nfs rootfstype=nfs nfsroot=… ip=…” kernel arguments only make sense if an initrd can be omitted. Either the initrd or such a root-nfs becomes the root file system at startup. As the guix system is currently relying on an initrd, this is not an option.

Further you would need to ensure that certain CONFIG_NFS…, CONFIG_IP… and even more options for your network interface are set to ‘=y’ to ensure that Linux can make use of an nfs-root.


Bye

Stefan
Stefan June 12, 2020, 2:41 p.m. UTC | #10
Hi Danny!

> (canonicalize-device-spec seems to expect a nfs share reference to be a
> string, too.  Is that on purpose?  No <nfs-share> record?  That's kinda
> weird when we even have records for device labels and uuids--but we don't
> have them for something that's actually complicated to specify? [1] :) )

I think you are already able today to specify a (file-system (type "nfs") (device "my-server:/srv/nfs/music") …). It is just not sufficient yet for an NFS root file system. And using this style is still possible.

Therefore I started to use a simple string for an NFS share, too. That canonicalize-device-spec expects a string is due to an older patch from me in (boot-system), which Maxim moved from there.

Actually I see a difference between an NFS share and a file system label and an UUID, and this is (file-system (type …) …). If you specify for example "ext4" or "btrfs" the ‘device’ field can still have multiple representations, whereas for the type "nfs" the device field can only have one representation.

>> When booting
>> from NFS using the nfsroot Linux option, it's possible to specify a
>> '/dev/nfs' as the root kernel parameter.  /dev/nfs is not a real block
>> device, it's just a stub hinting the kernel that its root file system is
>> on NFS.  Perhaps that can be used?
> 
> Hmm maybe.  @Stefan?

As explained in another e-mail, no, that’s not an option.

> Also, could we have a system test testing this stuff?  I can write the
> actual test--but could you tell me how to use the functionality introduced
> in this patch?

A system test would be great!

You need a TFTP server serving the content of some /boot… folder as its root, which gets filled by the new grub-efi-net bootloader. This folder name is specified by (bootloader-configuration (target …) …). So lets assume you will use /boot-tftp for this.

The bootloader installer will create a link /boot-tftp/gnu pointing to ../gnu, and a link /boot-tftp/efi/boot/grub.cfg pointing to ../../../boot/grub/grub.cfg. So the TFTP server needs to have access to the store and the grub.cfg through these links as well.

You need a DHCP server pointing the guix machine to the TFTP server and to the file to boot. I use dnsmasq for this. My relevant configuration lines are the “TFTP server name” option number 66 and the “Bootfile name” option 67:

dhcp-option-force=66,10.10.10.10
dhcp-option-force=67,efi/boot/bootaa64.efi

The dnsmasq program can be configured with 

enable-tftp
tftp-root=/srv/tftp/

to act as a TFTP server, too. This is probably preferable.

You need an NFS server serving the root file system.

And you need a guix machine, capable to boot via network. Possibly any UEFI system could work, I think using qemu is possible as well.

I’m using a Raspberry Pi 3b. It is not PXE compliant, but capable to boot via network. For example it can only handle an IP address but no hostname inside the “TFTP server name” option. After specific firmware blobs have been loaded, it is loading the U-Boot, which then acts as an UEFI implementation and uses PXE. However, I do not use special PXE functionality, but finally the U-Boot will try to load /efi/boot/bootaa64.efi via TFTP, which in fact is GRUB.

GRUB then loads more files via TFTP from /efi/boot, most interestingly /efi/boot/grub.cfg, which is a link to ../../../boot/grub/grub.cfg. And that file points GRUB to the store.

Up to here no NFS is involved at all. GRUB will load the initrd and start Linux. Then the guix system is running and will mount its root file system via NFS.

If it is possible to start qemu with just an initrd, a kernel, and kernel arguments, then it would be possible to avoid DHCP, TFTP, and GRUB. This might be a much simpler first step for a test, only requiring an NFS server.


Bye

Stefan
Maxim Cournoyer June 14, 2020, 6:56 p.m. UTC | #11
Hello Danny,

Danny Milosavljevic <dannym@scratchpost.org> writes:

> Hi Maxim,
> Hi Stefan,
>
> On Thu, 11 Jun 2020 00:21:11 -0400
> Maxim Cournoyer <maxim.cournoyer@gmail.com> wrote:
>
>> >   (define (device-string->file-system-device device-string)
>> >     ;; The "--root=SPEC" kernel command-line option always provides a
>> >     ;; string, but the string can represent a device, a UUID, a
>> >     ;; label or a NFS spec.  So check for all three.
>> >     (cond ((string-prefix? "/" device-string) device-string)
>> >           ((uuid device-string) => identity)
>> >           (else (file-system-label device-string))))
>> >
>> > But looking at the condition (uuid device-string) I have no idea what that means,
>> > or is bound to!  
>> 
>> It means that if the device-string (a string as its name imply) contains
>> something that represent a UUID, return its corresponding UUID object.
>> `uuid' comes from (gnu system uuid).  Does that answer your question?
>
> Oh!  I've looked at now it but I still don't get it.

Perhaps the '=>' syntax is the reason why? The the Guile Reference info
manual defines it as such:

     For the ‘=>’ clause type, EXPRESSION is
     evaluated and the resulting procedure is applied to the value of
     TEST.  The result of this procedure application is then the result
     of the ‘cond’-expression.

(uuid device-string) returns either #f or a uuid object.  So applying
the identity function to a uuid object yields that same object.

HTH!

Maxim
Maxim Cournoyer June 14, 2020, 7:09 p.m. UTC | #12
Hello Stefan!

Stefan <stefan-guix@vodafonemail.de> writes:

[...]

> Regarding ‘grub efi net’: Just take a look at the GRUB
> manual. Actually GRUB is not dealing with NFS at all, it is using TFTP
> to load files. Only Linux uses NFS to mount its root file system.

I see. Then we're talking about TFTP support in GRUB, and it doesn't
seem to depend on EFI at all (which is good!).

>> When booting
>> from NFS using the nfsroot Linux option, it's possible to specify a
>> '/dev/nfs' as the root kernel parameter.  /dev/nfs is not a real block
>> device, it's just a stub hinting the kernel that its root file system is
>> on NFS.  Perhaps that can be used?
>
> These “root=/dev/nfs rootfstype=nfs nfsroot=… ip=…” kernel arguments
> only make sense if an initrd can be omitted. Either the initrd or such
> a root-nfs becomes the root file system at startup. As the guix system
> is currently relying on an initrd, this is not an option.

I see. Thanks for explaining, I understand the plan better now.

> Further you would need to ensure that certain CONFIG_NFS…, CONFIG_IP…
> and even more options for your network interface are set to ‘=y’ to
> ensure that Linux can make use of an nfs-root.

Yes. For having done it recently, enabling the NFS support in the kernel
doesn't require much change, but then supporting a vast array of network
cards directly in the kernel doesn't sound too appealing. IIUC, going
through an initrd allows dynamically loading kernel modules instead of
having them statically built in the kernel (so that a user could add a
network module of their choice to their OS declaration without having to
rebuild the default kernel), which is why it's better to go that route
in Guix.

Am I understanding things correctly?

Thanks!

Maxim
Stefan June 17, 2020, 1:12 p.m. UTC | #13
Hi Maxim!

> I see. Then we're talking about TFTP support in GRUB, and it doesn't
> seem to depend on EFI at all (which is good!).

Well, the data transfer in GRUB is using TFTP, but the actual network driver, the facilities to start a kernel and so on, happens by GRUB via the EFI-API in my case, which is provided by the U-Boot.

It may be a runtime detection, if the EFI-API or the legacy BIOS functions are used. I don’t know.

In that case I should probably rename grub-efi-net to grub-net.

> Yes. For having done it recently, enabling the NFS support in the kernel
> doesn't require much change, but then supporting a vast array of network
> cards directly in the kernel doesn't sound too appealing. IIUC, going
> through an initrd allows dynamically loading kernel modules instead of
> having them statically built in the kernel (so that a user could add a
> network module of their choice to their OS declaration without having to
> rebuild the default kernel), which is why it's better to go that route
> in Guix.
> 
> Am I understanding things correctly?

Yes. And additionally as a beginner I simply don’t want to try to remove the initrd, I can’t even imagine the effort to do so. :-)


Bye

Stefan
Stefan June 20, 2020, 1:52 p.m. UTC | #14
Hi!

A friendly ping.

What about this patch? This is what has been requested. Can it be pushed or does someone have further requests?


Bye

Stefan
diff mbox series

Patch

diff --git a/gnu/bootloader/grub.scm b/gnu/bootloader/grub.scm
index 2d9a39afc3..60b3a12037 100644
--- a/gnu/bootloader/grub.scm
+++ b/gnu/bootloader/grub.scm
@@ -23,7 +23,7 @@ 
 
 (define-module (gnu bootloader grub)
   #:use-module (guix records)
-  #:use-module ((guix utils) #:select (%current-system))
+  #:use-module ((guix utils) #:select (%current-system %current-target-system))
   #:use-module (guix gexp)
   #:use-module (gnu artwork)
   #:use-module (gnu bootloader)
@@ -47,6 +47,8 @@ 
 
             grub-bootloader
             grub-efi-bootloader
+            make-grub-efi-net-bootloader
+            grub-efi-net-bootloader
             grub-mkrescue-bootloader
             grub-minimal-bootloader
 
@@ -135,41 +137,25 @@  file with the resolution provided in CONFIG."
            (_ #f)))))
 
 (define* (eye-candy config store-device store-mount-point
-                    #:key store-directory-prefix system port)
+                    #:key store-directory-prefix port)
   "Return a gexp that writes to PORT (a port-valued gexp) the 'grub.cfg' part
 concerned with graphics mode, background images, colors, and all that.
 STORE-DEVICE designates the device holding the store, and STORE-MOUNT-POINT is
 its mount point; these are used to determine where the background image and
-fonts must be searched for.  SYSTEM must be the target system string---e.g.,
-\"x86_64-linux\".  STORE-DIRECTORY-PREFIX is a directory prefix to prepend to
-any store file name."
-  (define setup-gfxterm-body
-    (let ((gfxmode
-           (or (and-let* ((theme (bootloader-configuration-theme config))
-                          (gfxmode (grub-theme-gfxmode theme)))
-                 (string-join gfxmode ";"))
-               "auto")))
-
-      ;; Intel and EFI systems need to be switched into graphics mode, whereas
-      ;; most other modern architectures have no other mode and therefore
-      ;; don't need to be switched.
-
-      ;; XXX: Do we really need to restrict to x86 systems?  We could imitate
-      ;; what the GRUB default configuration does and decide based on whether
-      ;; a user provided 'gfxterm' in the terminal-outputs field of their
-      ;; bootloader-configuration record.
-      (if (string-match "^(x86_64|i[3-6]86)-" system)
-          (format #f "
-  set gfxmode=~a
-  insmod all_video
-  insmod gfxterm~%" gfxmode)
-          "")))
-
+fonts must be searched for.  STORE-DIRECTORY-PREFIX is a directory prefix to
+prepend to any store file name."
   (define (setup-gfxterm config font-file)
     (if (memq 'gfxterm (bootloader-configuration-terminal-outputs config))
-        #~(format #f "if loadfont ~a; then
-  setup_gfxterm
-fi~%" #+font-file)
+        #~(format #f "
+if loadfont ~a; then
+  set gfxmode=~a
+  insmod all_video
+  insmod gfxterm
+fi~%"
+                  #$font-file
+                  #$(string-join
+                      (grub-theme-gfxmode (bootloader-theme config))
+                      ";"))
         ""))
 
   (define (theme-colors type)
@@ -190,8 +176,6 @@  fi~%" #+font-file)
 
   (and image
        #~(format #$port "
-function setup_gfxterm {~a}
-
 # Set 'root' to the partition that contains /gnu/store.
 ~a
 
@@ -206,7 +190,6 @@  else
   set menu_color_normal=cyan/blue
   set menu_color_highlight=white/blue
 fi~%"
-                 #$setup-gfxterm-body
                  #$(grub-root-search store-device font-file)
                  #$(setup-gfxterm config font-file)
                  #$(grub-setup-io config)
@@ -313,6 +296,9 @@  code."
         ((? file-system-label? label)
          (format #f "search --label --set ~a"
                  (file-system-label->string label)))
+        ((? (lambda (device)
+              (and (string? device) (string-contains device ":/"))) nfs-uri)
+         "set root=(tftp)")
         ((or #f (? string?))
          #~(format #f "search --file --set ~a" #$file)))))
 
@@ -358,7 +344,6 @@  when booting a root file system on a Btrfs subvolume."
                (menu-entry-device (first all-entries))
                (menu-entry-device-mount-point (first all-entries))
                #:store-directory-prefix store-directory-prefix
-               #:system system
                #:port #~port))
 
   (define keyboard-layout-config
@@ -498,11 +483,73 @@  fi~%"))))
                       "--bootloader-id=Guix"
                       "--efi-directory" target-esp))))
 
+(define (install-grub-efi-net subdir)
+  "Define a grub-efi bootloader installer for installation in SUBDIR,
+which is usually \"efi/boot\" or \"efi/Guix\"."
+  (let* ((arch (car (string-split (or (%current-target-system)
+                                      (%current-system))
+                                  #\-)))
+         (efi-bootloader-link (string-append "/boot"
+                                             (match arch
+                                               ("i686" "ia32")
+                                               ("x86_64" "x64")
+                                               ("arm" "arm")
+                                               ("armhf" "arm")
+                                               ("aarch64" "aa64")
+                                               ("riscv" "riscv32")
+                                               ("riscv64" "riscv64"))
+                                             ".efi"))
+         (efi-bootloader (string-append (match arch
+                                          ("i686" "i386")
+                                          ("x86_64" "x86_64")
+                                          ("arm" "arm")
+                                          ("armhf" "arm")
+                                          ("aarch64" "arm64")
+                                          ("riscv" "riscv32")
+                                          ("riscv64" "riscv64"))
+                                        "-efi/core.efi")))
+    #~(lambda (bootloader target mount-point)
+        "Install GRUB as e.g. \"bootx64.efi\" or \"bootarm64.efi\" \"into
+SUBDIR, which is usually \"efi/boot\" or \"efi/Guix\" below the directory TARGET
+for the system whose root is mounted at MOUNT-POINT."
+        (let* (;; Use target-depth and subdir-depth to construct links to
+               ;; "../gnu" and "../../../boot/grub/grub.cfg" with the correct
+               ;; number of "../". Note: This doesn't consider ".." or ".",
+               ;; which may appear inside target or subdir.
+               (target-depth (length (delete "" (string-split target #\/))))
+               (subdir-depth (length (delete "" (string-split #$subdir #\/))))
+               (up1 (string-join (make-list target-depth "..") "/" 'suffix))
+               (up2 (string-join (make-list subdir-depth "..") "/" 'suffix))
+               (net-dir (string-append mount-point target "/"))
+               (store-name (car (delete "" (string-split bootloader #\/))))
+               (store (string-append up1 store-name))
+               (store-link (string-append net-dir store-name))
+               (grub-cfg (string-append up1 up2 "boot/grub/grub.cfg"))
+               (grub-cfg-link (string-append net-dir #$subdir "/grub.cfg"))
+               (efi-bootloader-link
+                (string-append net-dir #$subdir #$efi-bootloader-link)))
+          ;; Tell 'grub-install' that there might be a LUKS-encrypted /boot or
+          ;; root partition.
+          (setenv "GRUB_ENABLE_CRYPTODISK" "y")
+          (invoke/quiet (string-append bootloader "/bin/grub-mknetdir")
+                        (string-append "--net-directory=" net-dir)
+                        (string-append "--subdir=" #$subdir))
+          (false-if-exception (delete-file store-link))
+          (symlink store store-link)
+          (false-if-exception (delete-file grub-cfg-link))
+          (symlink grub-cfg grub-cfg-link)
+          (false-if-exception (delete-file efi-bootloader-link))
+          (symlink #$efi-bootloader efi-bootloader-link)))))
+
 ^L
 
 ;;;
 ;;; Bootloader definitions.
 ;;;
+;;; For all these grub-bootloader variables the path to /boot/grub/grub.cfg
+;;; is fixed.  Inheriting and overwriting the field 'configuration-file' will
+;;; break 'guix system delete-generations', 'guix system switch-generation',
+;;; and 'guix system roll-back'.
 
 (define grub-bootloader
   (bootloader
@@ -513,12 +560,12 @@  fi~%"))))
    (configuration-file "/boot/grub/grub.cfg")
    (configuration-file-generator grub-configuration-file)))
 
-(define* grub-minimal-bootloader
+(define grub-minimal-bootloader
   (bootloader
    (inherit grub-bootloader)
    (package grub-minimal)))
 
-(define* grub-efi-bootloader
+(define grub-efi-bootloader
   (bootloader
    (inherit grub-bootloader)
    (installer install-grub-efi)
@@ -526,7 +573,13 @@  fi~%"))))
    (name 'grub-efi)
    (package grub-efi)))
 
-(define* grub-mkrescue-bootloader
+(define grub-efi-net-bootloader
+  (bootloader
+   (inherit grub-efi-bootloader)
+   (name 'grub-efi-net-bootloader)
+   (installer (install-grub-efi-net "efi/boot"))))
+
+(define grub-mkrescue-bootloader
   (bootloader
    (inherit grub-efi-bootloader)
    (package grub-hybrid)))
diff --git a/gnu/build/linux-boot.scm b/gnu/build/linux-boot.scm
index f08bb11514..3a79cfd461 100644
--- a/gnu/build/linux-boot.scm
+++ b/gnu/build/linux-boot.scm
@@ -503,6 +503,7 @@  upon error."
     ;; string, but the string can represent a device, a UUID, or a
     ;; label.  So check for all three.
     (cond ((string-prefix? "/" device-string) device-string)
+          ((string-contains device-string ":/") device-string)
           ((uuid device-string) => identity)
           (else (file-system-label device-string))))
 
diff --git a/gnu/system.scm b/gnu/system.scm
index ac8bbd1d16..91caba7012 100644
--- a/gnu/system.scm
+++ b/gnu/system.scm
@@ -301,7 +301,8 @@  file system labels."
       ((? string? device)
        ;; It used to be that we would not distinguish between labels and
        ;; device names.  Try to infer the right thing here.
-       (if (string-prefix? "/dev/" device)
+       (if (or (string-prefix? "/dev/" device)
+               (string-contains device ":/")) ; nfs
            device
            (file-system-label device)))))