diff mbox series

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

Message ID 9AAFEFF4-8ACE-4C95-975F-67C3F4FDAF81@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 May 1, 2020, 8:32 p.m. UTC
* gnu/bootloader/grub.scm (grub-efi-net-bootloader): New efi bootloader for
network booting via tftp/nfs and possibly images, prepared for chain loading.
(install-grub-efi-net): New bootloader installer for tftp and possibly images,
does not need root rights.
(grub-root-search): Adding support for tftp root.
(eye-candy): Enable gfxterm support for all systems.
* gnu/system.scm (read-boot-parameters): Prevent devices with ":/" from being
treated as a file system label.
---
 gnu/bootloader/grub.scm | 107 +++++++++++++++++++++++++++++++---------
 gnu/system.scm          |   3 +-
 2 files changed, 86 insertions(+), 24 deletions(-)

Comments

Mathieu Othacehe May 10, 2020, 8:20 a.m. UTC | #1
Hello Stefan,

This patch does not apply here. Could you rebase it on top of master?

> -
> -      ;; 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 "
> +    (format #f "
>    set gfxmode=~a
>    insmod all_video
> -  insmod gfxterm~%" gfxmode)
> -          "")))
> +  insmod gfxterm~%"
> +            (string-join
> +             (grub-gfxmode (bootloader-theme config))
> +             ";")))

Why not enable graphic mode only if 'gfxterm' is provided in
terminal-outputs fields, like suggested by the comment?

> +         (efi-bootloader-link (string-append "boot"
> +                                       (match arch
> +                                         ("i686" "ia32")
> +                                         ("x86_64" "x64")
> +                                         ("armhf" "arm")

If cross-building for "arm-linux-gnueabihf", arch will be "arm" and
won't match anything here.

> +          (catch 'system-error
> +            (lambda () (delete-file efi-bootloader-link))
> +            (lambda _ #f))

You can use "false-if-exception" here I think.

> +          (symlink #$efi-bootloader
> +                   efi-bootloader-link)
> +          (catch 'system-error
> +            (lambda () (delete-file store-link))
> +            (lambda _ #f))

Same here.

> +(define* (grub-efi-net-bootloader #:key (target #f) (efi-subdir #f))

#f if implicit if omitted.

> +  (let ((target (or target "boot"))
> +        (efi-subdir (or efi-subdir "efi/boot")))

It would be better to keep grub-efi-net-bootloader as a variable, like
all other bootloaders. You could default configuration-file to
"boot/efi/boot/grub.cfg" instead?

Thanks,

Mathieu
Stefan May 10, 2020, 9:13 p.m. UTC | #2
Hi Mathieu!

Thanks for your reply again! :-)

> Am 10.05.2020 um 10:20 schrieb Mathieu Othacehe <m.othacehe@gmail.com>:
> 
> This patch does not apply here. Could you rebase it on top of master?

I’ll try.

>> -      ;; 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 "
>> +    (format #f "
>>   set gfxmode=~a
>>   insmod all_video
>> -  insmod gfxterm~%" gfxmode)
>> -          "")))
>> +  insmod gfxterm~%"
>> +            (string-join
>> +             (grub-gfxmode (bootloader-theme config))
>> +             ";")))
> 
> Why not enable graphic mode only if 'gfxterm' is provided in
> terminal-outputs fields, like suggested by the comment?

Good point.

Looking into this topic it seem's questionable to me that the default of (bootloader-configuration (terminal-output …)) with '(gfxterm) is grub-specific. This doesn't make sense for other boot-loaders, e.g. U-Boot. I expect this to be changed in future. ;-)

>> +         (efi-bootloader-link (string-append "boot"
>> +                                       (match arch
>> +                                         ("i686" "ia32")
>> +                                         ("x86_64" "x64")
>> +                                         ("armhf" "arm")
> 
> If cross-building for "arm-linux-gnueabihf", arch will be "arm" and
> won't match anything here.

Good catch!

>> +          (catch 'system-error
>> +            (lambda () (delete-file efi-bootloader-link))
>> +            (lambda _ #f))
> 
> You can use "false-if-exception" here I think.

Nice trick.

>> +          (symlink #$efi-bootloader
>> +                   efi-bootloader-link)
>> +          (catch 'system-error
>> +            (lambda () (delete-file store-link))
>> +            (lambda _ #f))
> 
> Same here.

Sure.

>> +(define* (grub-efi-net-bootloader #:key (target #f) (efi-subdir #f))
> 
> #f if implicit if omitted.

I wasn’t aware of this.

>> +  (let ((target (or target "boot"))
>> +        (efi-subdir (or efi-subdir "efi/boot")))
> 
> It would be better to keep grub-efi-net-bootloader as a variable, like
> all other bootloaders. You could default configuration-file to
> "boot/efi/boot/grub.cfg" instead?

Actually there is a problem with all this in guix: There is (bootloader (target …)), which gives the impression that one is able to freely chose a folder for the bootloader files. However, the path “/boot/grub.cfg” is kind of hard coded.

Yes, it’s kind of possible to inherit from grub-efi-bootloader and overwrite the (configuration-file) field. In a first step this seems to work. But when e.g. deleting a system generation, in guix/scripts/system.scm (reinstall-bootloader) there is this code:

    ;; Use the detected bootloader with default configuration.
    ;; It will be enough to allow the system to boot.
    (bootloader-config (bootloader-configuration
                        (bootloader bootloader))) 

It reads this information form /var/guix/profiles/system/parameters: (bootloader-name grub-efi-bootloader).
With this again the hard-coded path “/boot/grub.cfg” of is used, ignoring any value overwritten in (configuration-file).

Another issue is (install-dir (string-append mount-point "/boot")) in (install-grub-efi), which ignores any (configuration-file) setting, too – well, it has no access to that setting –, and implies at least “/boot” to be the prefix of (bootloader (target …)). 

Beside the wish to avoid this hard-coded “/boot“ assumption, I made this a function with two parameters for two more reasons. 

One is simply to suite my needs. The folder for my tftp server is not “boot” but “boot-nfs”. For my SBC I’m using different operating systems from time to time, e.g. LibreELEC. As I have bad experiences with unreliable micro SD cards and as an nfs root file system is nice for tinkering, I copy such operating systems onto my tftp/nfs server. This includes of corse their “boot” folder. The build-in update functionalities overwrite stuff inside there. But I need to modify stuff for network booting. To not loose these modifications during updates and for later comparisons I keep such modifications in a copy as “boot-nfs”.

The other reason is that I’m not sure, if the efi-dir for network booting should be “efi/Guix” instead of “efi/boot” in other constellations. U-Boot expects “efi/boot“ over tftp like for a removable media by default. I guess this can be changed with DNS options. Also for real UEFI firmware this might be configurable. I don’t know, so I don’t want these paths to be hard-coded.

However, digging up all this and now re-trying to delete a system generation, I get this error with my new boot-efi-net-bootloader as a function:

stefan@guix ~/development/guix$ sudo guix system delete-generations 151
/var/guix/profiles/system-151-link wird gelöscht
guix system: error: grub-efi-net-bootloader: no such bootloader

So thanks for your hint, it can’t be a function! (Not now …)


Bye

Stefan
Mathieu Othacehe May 23, 2020, 8:02 a.m. UTC | #3
Hello Stefan,

> Yes, it’s kind of possible to inherit from grub-efi-bootloader and overwrite the (configuration-file) field. In a first step this seems to work. But when e.g. deleting a system generation, in guix/scripts/system.scm (reinstall-bootloader) there is this code:
>
>     ;; Use the detected bootloader with default configuration.
>     ;; It will be enough to allow the system to boot.
>     (bootloader-config (bootloader-configuration
>                         (bootloader bootloader))) 
>
> It reads this information form /var/guix/profiles/system/parameters: (bootloader-name grub-efi-bootloader).
> With this again the hard-coded path “/boot/grub.cfg” of is used, ignoring any value overwritten in (configuration-file).

Oh, we need to fix that! It means that we would need to add a
"bootloader-configuration-file" field to <boot-parameters> record, is
that correct?

> Another issue is (install-dir (string-append mount-point "/boot")) in (install-grub-efi), which ignores any (configuration-file) setting, too – well, it has no access to that setting –, and implies at least “/boot” to be the prefix of (bootloader (target …)). 
>
> Beside the wish to avoid this hard-coded “/boot“ assumption, I made this a function with two parameters for two more reasons. 

Could it be an option to infer the bootloader installation directory and
the efi subdir from the install-grub-efi/install-grub-efi-net functions?
If TARGET is /boot-nfs/efi/Guix", could we suppose that the
boot-directory is "/boot-nfs" and the efi-subdir is "efi/Guix"?

The make-grub-efi-net-bootloader macro is a nice hack, but I fear that
it makes the bootloader configuration (even more) difficult.

Thanks,

Mathieu
Stefan May 24, 2020, 10:18 a.m. UTC | #4
Hi Mathieu!


> Am 23.05.2020 um 10:02 schrieb Mathieu Othacehe <othacehe@gnu.org>:
> 
>> It reads this information form /var/guix/profiles/system/parameters: (bootloader-name grub-efi-bootloader).
>> With this again the hard-coded path “/boot/grub.cfg” is used, ignoring any value overwritten in (configuration-file).
> 
> Oh, we need to fix that! It means that we would need to add a
> "bootloader-configuration-file" field to <boot-parameters> record, is
> that correct?

Yes, I would guess so. But I’m not sure, if the field bootloader-name can be dropped then from <boot-parameters>. But if, then we could probably also drop the field name from the <bootloader> record. 

>> Another issue is (install-dir (string-append mount-point "/boot")) in (install-grub-efi), which ignores any (configuration-file) setting, too – well, it has no access to that setting –, and implies at least “/boot” to be the prefix of (bootloader (target …)). 
>> 
>> Beside the wish to avoid this hard-coded “/boot“ assumption, I made this a function with two parameters for two more reasons. 
> 
> Could it be an option to infer the bootloader installation directory and
> the efi subdir from the install-grub-efi/install-grub-efi-net functions?
> If TARGET is /boot-nfs/efi/Guix", could we suppose that the
> boot-directory is "/boot-nfs" and the efi-subdir is "efi/Guix"?

For the new install-grub-efi-net I see first of all no issue in keeping it a function. This gives the needed flexibility.

For the existing grub-efi-bootloader the assumption seems to be that there will always be a /boot/grub.cfg. This is just not stated in the documentation. But it gave me the impression that there is some control about it with (bootloader (target …) …). But this is not the case. For the legacy grub-bootlouder the (bootloader (target …) …) needs to be a device, and the /boot/grub.cfg is implied and hard coded as well.

Actually thinking about it again, mounting the efi partition at e.g. /foo/efi, doesn't brake anything in the first place. Then GRUB will be installed at the target /foo/efi, basically into the efi partition it belongs. It will just read its configuration from /boot/grub.cfg, from a different partition.

The actual difference to the new grub-efi-net-bootloader is that it has only TFTP access to its files and its configuration file; there is only one place to lookup both, instead of two partitions in case of the grub-efi-bootloader.

For deleting system generations the path to the always present, not configurable /boot/grub.cfg is looked up. This works for the existing grub-efi-bootloader and grub-bootloader. But it does not work for the grub-efi-net-bootloader, because its configuration file does not live at /boot/grub.cfg, as its path is now implicitly configurable via (bootloader (target …) …). In addition for my setup I used a /boot-nfs folder instead of a /boot folder, and saw no benefit then in keeping the /boot folder.

I think there is a second possibility. It may be possible to create a symlink from /boot-nfs/efi/boot/grub.cfg to ../../../boot/grub.cfg. Then the assumption that there will always be a /boot/grub.cfg file stays valid. 

But personally I do not like this idea.

In <boot-parameters> it seems – but I’m not sure yet – that we only keep a symbol name to figure out the path to the grub.cfg, although it is possible to just put that path directly there instead. And using the symbol makes it hard do get a configurable bootloader: A new bootloader has to be a variable, tricks with macros come up. Also inheriting from a bootloader and overwriting the configuration-file field – for whatever reason – is problematic: It seems to work at the beginning, but only fails badly when removing a system generation. It seems to have subtle bugs. It’s not usable like other parts in guix. It’s not hackable. I’d really prefer to change this.

> The make-grub-efi-net-bootloader macro is a nice hack, but I fear that
> it makes the bootloader configuration (even more) difficult.

At least it gives me the flexibility which was missing so far. I suggest to keep it for the moment and do a different patch once we are clear which way to go. If we add a bootloader-configuration-file field to the <boot-parameters> record, than the macro can be removed anyway.


Bye

Stefan
Danny Milosavljevic May 24, 2020, 11 a.m. UTC | #5
Hi Stefan,

On Sun, 24 May 2020 12:18:38 +0200
Stefan <stefan-guix@vodafonemail.de> wrote:

> But I’m not sure, if the field bootloader-name can be dropped then[after adding bootloader-configuration-file to boot-parameters] from <boot-parameters>. But if, then we could probably also drop the field name from the <bootloader> record. 

We definitely can't drop it.  The name is required in order to know which
bootloader to restore when deleting system generations.  After all you could be
deleting the generation that switched from extlinux to grub.  How to boot then?

(see lookup-bootloader-by-name call site)

Since we are trying to have the bootloader to use part of the Guix system
configuration (for better or for worse), we have to be really careful to
pick the right bootloader and generate the configuration for the right
bootloader, otherwise the computer won't boot anymore *and* you couldn't
select the before-fuckup generation anymore either.

> Yes, it’s kind of possible to inherit from grub-efi-bootloader and overwrite
the (configuration-file) field. In a first step this seems to work. But when
e.g. deleting a system generation, in guix/scripts/system.scm
(reinstall-bootloader) there is this code:
>
>     ;; Use the detected bootloader with default configuration.
>     ;; It will be enough to allow the system to boot.
>     (bootloader-config (bootloader-configuration
>                         (bootloader bootloader))) 

>In <boot-parameters> it seems – but I’m not sure yet – that we only keep a symbol name to figure out the path to the grub.cfg, although it is possible to just put that path directly there instead. And using the symbol makes it hard do get a configurable bootloader: A new bootloader has to be a variable, tricks with macros come up. Also inheriting from a bootloader and overwriting the configuration-file field – for whatever reason – is problematic: It seems to work at the beginning, but only fails badly when removing a system generation. It seems to have subtle bugs. It’s not usable like other parts in guix. It’s not hackable. I’d really prefer to change this.

Yeah, well...  that is the only way we could think of to make sure it actually
boots in all cases, as it is right now.

(Though if the user had custom entries, that would nuke all of them--but
that's still better than not being able to boot at all)

Using a symbol was to make it clear that this is supposed to be a reference
to an actual variable and not some weird mini-programming language inside a
string or whatever.

It would be better to have some kind of abstract representation of *all*
the bootloader things one could want, in Guix in config.scm.
That would make Guix system a lot more complicated, though (chainloading
bootloaders for one--I saw you working on this, too).

Just to be clear, I'm fine with changing boot-parameters, but be very very
careful that all old versions of Guix and new versions of Guix can handle
all the boot-parameters--at least falling back to something.  It's not fun
if you can't boot anymore.  Source: I modified a lot of that stuff and
wasn't able to boot quite often until I finally stopped overcomplicating
the boot-parameters.

>Actually there is a problem with all this in guix: There is (bootloader (target …)),
>which gives the impression that one is able to freely chose a folder for the
>bootloader files. However, the path “/boot/grub.cfg” is kind of hard coded.

Could you elaborate why having that hard-coded file path is bad?

It makes booting a lot more resilient if it's hard-coded as opposed to having
basic stuff like that configurable--and being careful all the time it's
actually configured correctly for all the parties to it, some of them maybe
not even inside Guix.
Stefan May 24, 2020, 1:09 p.m. UTC | #6
Hi Danny!

> Am 24.05.2020 um 13:00 schrieb Danny Milosavljevic <dannym@scratchpost.org>:
> 
>> But I’m not sure, if the field bootloader-name can be dropped then[after adding bootloader-configuration-file to boot-parameters] from <boot-parameters>. But if, then we could probably also drop the field name from the <bootloader> record. 
> 
> We definitely can't drop it.  The name is required in order to know which
> bootloader to restore when deleting system generations.  After all you could be
> deleting the generation that switched from extlinux to grub.  How to boot then?
> 
> (see lookup-bootloader-by-name call site)

OK, I understand. I will take a look at it.

> Since we are trying to have the bootloader to use part of the Guix system
> configuration (for better or for worse), we have to be really careful to
> pick the right bootloader and generate the configuration for the right
> bootloader, otherwise the computer won't boot anymore *and* you couldn't
> select the before-fuckup generation anymore either.

Hm, if I select an older system generation in GRUB, than that older one is booted. But this doesn't change the bootloader.
If I then delete some system generations – as I’ve seen so far, but I might be wrong – the bootloader is not reinstalled either. Only the grub.cfg is regenerated to remove the deleted generations. If I reboot, then I'm still using the latest generation GRUB, but boot some older system generation, which would not be able by itself to install this very recent GRUB in use.

If I then reconfigure the system, only then another GRUB - or even a different bootloader, depending on my etc/config.scm – will be installed and the according configuration file will be generated as well. Then again all will fit. In the worst case the (bootloader (bootloader-configuration …) …) in my etc/config.scm is still newer than this older guix system in use is able to handle. 

Oh, by the way, does booting an older system generation also change the guix version in use from the latest 'guix pull'? I don't think it does.

And does booting an older generation change the config.scm? I don’t think so either.

Actually, I don’t really understand what you mean. Are there circumstances beside a 'guix system reconfigure' in which the bootloader gets reinstalled? And with reinstall I don’t mean to only regenerate the grub.cfg, but calling /sbin/grub-install.

Isn’t the actual problem for an older running system generation to know which bootloder is currently in use? I think this can't be inferred by the currently running system generation. It may happen, that you use a brand new bootloader which is not known by the older system generation you just switched to.

But still then, if you invoke a 'sudo -E guix system delete-generations' or a 'sudo -E guix system reconfigure' I think you still use the very latest guix version that you 'guix pull'-ed last. And that guix version should still know all brand new bootloader. The problem may “only” be to know for 'sudo -E guix system delete-generations' which one to use. But actually the bootloader-name field in /var/guix/profiles/system/parameters can't tell either, as it must be an older bootloader than the brand new one.

Maybe the information about the bootloader version in use needs to reside with the installed bootloader somewhere below /boot/efi/…? But this may be impossible for the legacy grub-bootloader.

>> Yes, it’s kind of possible to inherit from grub-efi-bootloader and overwrite
> the (configuration-file) field. In a first step this seems to work. But when
> e.g. deleting a system generation, in guix/scripts/system.scm
> (reinstall-bootloader) there is this code:
>> 
>>    ;; Use the detected bootloader with default configuration.
>>    ;; It will be enough to allow the system to boot.
>>    (bootloader-config (bootloader-configuration
>>                        (bootloader bootloader))) 
> 
>> In <boot-parameters> it seems – but I’m not sure yet – that we only keep a symbol name to figure out the path to the grub.cfg, although it is possible to just put that path directly there instead. And using the symbol makes it hard do get a configurable bootloader: A new bootloader has to be a variable, tricks with macros come up. Also inheriting from a bootloader and overwriting the configuration-file field – for whatever reason – is problematic: It seems to work at the beginning, but only fails badly when removing a system generation. It seems to have subtle bugs. It’s not usable like other parts in guix. It’s not hackable. I’d really prefer to change this.
> 
> Yeah, well...  that is the only way we could think of to make sure it actually
> boots in all cases, as it is right now.

I think after switching to an older system generation this is not true, as explained above. Am I wrong about this?

> Just to be clear, I'm fine with changing boot-parameters, but be very very
> careful that all old versions of Guix and new versions of Guix can handle
> all the boot-parameters--at least falling back to something.

I see.

> Could you elaborate why having that hard-coded file path is bad?
> 
> It makes booting a lot more resilient if it's hard-coded as opposed to having
> basic stuff like that configurable--and being careful all the time it's
> actually configured correctly for all the parties to it, some of them maybe
> not even inside Guix.

As I wrote a bit below: I think there is a second possibility. It may be possible to create a symlink from /boot-nfs/efi/boot/grub.cfg to ../../../boot/grub.cfg. Then the assumption that there will always be a /boot/grub.cfg file stays valid. But Im still unsure about this.


Bye

Stefan
Danny Milosavljevic May 24, 2020, 1:42 p.m. UTC | #7
Hi Stefan,

On Sun, 24 May 2020 15:09:02 +0200
Stefan <stefan-guix@vodafonemail.de> wrote:

> Hm, if I select an older system generation in GRUB, than that older one is booted. But this doesn't change the bootloader.

Correct.  It doesn't need to since it just changes which Linux kernel will be booted temporarily, including what system and so on.

"System" here excludes $HOME.

Since the guix package manager itself is in $HOME, I think that that doesn't revert though.

> If I then delete some system generations – as I’ve seen so far, but I might be wrong – the bootloader is not reinstalled either.
> Only the grub.cfg is regenerated to remove the deleted generations.

You are totally right O_O

reinstall-bootloader says:

>          ;; Only install bootloader configuration file.

What happened here?  Why?!

(I think we should document these bootloader Guix intricacies in some better place than the mailing list archives; can't keep the thing straight otherwise :P)

>If I reboot, then I'm still using the latest generation GRUB, but boot some older system generation, which would not be able by itself to install this very recent GRUB in use.

It should be able to since guix package manager including package definitions is in $HOME, which is not rolled back, I think.  The only way to install the very recent GRUB is guix system reconfigure, and that will totally be able to see the newest guix packages.

> If I then reconfigure the system, only then another GRUB - or even a different bootloader, depending on my etc/config.scm – will be installed and the according configuration file will be generated as well. Then again all will fit.

Yes, that case is fine.

> In the worst case the (bootloader (bootloader-configuration …) …) in my etc/config.scm is still newer than this older guix system in use is able to handle.

I think guix-the-package-manager is not reverted, so it will be able to see the newest installable stuff. 

> Oh, by the way, does booting an older system generation also change the guix version in use from the latest 'guix pull'? I don't think it does.

No, that is per-user and not per-system.

> And does booting an older generation change the config.scm? I don’t think so either.

No.

> Actually, I don’t really understand what you mean. Are there circumstances beside a 'guix system reconfigure' in which the bootloader gets reinstalled? And with reinstall I don’t mean to only regenerate the grub.cfg, but calling /sbin/grub-install.

I think the actual bootloader (any of them those worked before) should be reinstalled by guix system delete-generations too, but apparently it doesn't do it right now.
Sounds very dangerous.  Doesn't that mean if one changed bootloaders in the past and then keeps using guix system delete-generations, that one eventually couldn't boot anymore?  O_O

> Isn’t the actual problem for an older running system generation to know which bootloder is currently in use? I think this can't be inferred by the currently running system generation. It may happen, that you use a brand new bootloader which is not known by the older system generation you just switched to.

I think guix-the-package-manager is still the newest one even after selecting an older system generation.

So the "brand new bootloader" case should be fine.
But the delete-generation case basically would have had to do the actual bootloader installation too.  Like it is now, it totally has a huge problem.

A possible way around having to know which bootloader is in use would be to just always install the configurations for all the known bootloaders.

> But still then, if you invoke a 'sudo -E guix system delete-generations' or a 'sudo -E guix system reconfigure' I think you still use the very latest guix version that you 'guix pull'-ed last.

Yes.

> And that guix version should still know all brand new bootloader. The problem may “only” be to know for 'sudo -E guix system delete-generations' which one to use.

Yep.

> But actually the bootloader-name field in /var/guix/profiles/system/parameters can't tell either, as it must be an older bootloader than the brand new one.

Correct.

> Maybe the information about the bootloader version in use needs to reside with the installed bootloader somewhere below /boot/efi/…? But this may be impossible for the legacy grub-bootloader.

That sounds like a huge can of worms to open.  Better would be some kind of bootloader detector (can package "os-prober" do it maybe?)--or better yet, just also install the bootloader each time a system generation is deleted and/or system in reconfigured.  That was the original plan.
Danny Milosavljevic May 24, 2020, 1:58 p.m. UTC | #8
> > But actually the bootloader-name field in /var/guix/profiles/system/parameters can't tell either, as it must be an older bootloader than the brand new one.  

Yes, but as the comment in the source says, the goal is to be able to boot at all, not be perfect.

So basically we could assume that if a previous system generation has used a bootloader [name] (and we can still apparently run guix commands) then that bootloader actually could boot that system, and presumably can boot it again if we installed it now.

Might make sense to use the newest generation possible for that.  (I think even if you used the boot menu or guix system switch-generation, the newest generation would still be present, just not current; so if that used the bootloader name of the newest generation that exists we might be safe)

All this complication is just because we don't have the file name of the os configuration (/etc/config.scm) in guix system delete-generations.  What about us just requiring it from the user and then installing the bootloader&config that is specified there?  That would be a lot less magical--it basically would do the same that guix system reconfigure does now, then.

(What about guix system switch-generation?  What does it do regarding bootloader installation? *mumbles* *checks*  Aha, it also only installs the bootloader configuration)

guix/scripts/system.scm says:

>         (bootloader (lookup-bootloader-by-name (system-bootloader-name)))

What is that (system-bootloader-name) magical parameterless call?  Which generation does that use?  *shakes head*

Our source code there totally could use some more comments...
Stefan May 24, 2020, 4:47 p.m. UTC | #9
Hi Danny!

> Am 24.05.2020 um 15:42 schrieb Danny Milosavljevic <dannym@scratchpost.org>:
> 
> You are totally right O_O
> 
> reinstall-bootloader says:
> 
>>         ;; Only install bootloader configuration file.
> 
> What happened here?  Why?!

From my point of o newbie view nothing, this has ever been so. :-)

> I think the actual bootloader (any of them those worked before) should be reinstalled by guix system delete-generations too, but apparently it doesn't do it right now.
> Sounds very dangerous.  Doesn't that mean if one changed bootloaders in the past and then keeps using guix system delete-generations, that one eventually couldn't boot anymore?  O_O

I think this may not be needed. The task of the bootloader is to parse the generated configuration file and load the initrd and kernel. If the current latest bootloader is able do this, than all is fine. The loaded system generation does not matter for this.

Why should 'guix system switch-generation' or 'guix system delete-generations' brake anything here, as long as the generated configuration file is still readable by the bootloader in use?

Ah, now I got your point! An older generation now potentially has a different value in the bootloader-name field of the <boot-parameters> record. Then it will generate the wrong bootloader configuration file, e.g. for extlinux instead of for grub-efi. 

Well, the bootloader-name information of an older system generation does not help.

> So the "brand new bootloader" case should be fine.
> But the delete-generation case basically would have had to do the actual bootloader installation too.  Like it is now, it totally has a huge problem.
> 
> A possible way around having to know which bootloader is in use would be to just always install the configurations for all the known bootloaders.

Hm, not sure.

>> Maybe the information about the bootloader version in use needs to reside with the installed bootloader somewhere below /boot/efi/…? But this may be impossible for the legacy grub-bootloader.
> 
> That sounds like a huge can of worms to open.  Better would be some kind of bootloader detector (can package "os-prober" do it maybe?)--or better yet, just also install the bootloader each time a system generation is deleted and/or system is reconfigured.  That was the original plan.

Some information from the installed bootloader could very well be passed via kernel arguments. This way the bootloader would actively spread information about itself and this will work with any bootloader. 

We could use for example these two kernel arguments:

--bootloader-configuration-file-generator=grub-configuration-file --bootloader-configuration-file=/boot/grub.cfg

If we – like today – do not reinstall the bootloader when switching generations, these are the needed information: how and where to generate the configuration file.

If the guix in $HOME does not know the passed generator procedure, then it there would automatically be a fallback: /var/guix/profiles/system/parameters contains a kernel-arguments field already! That would contain a matching generator function then. If the generator is neither in kernel-arguments, then we could fall-back to the bootloader-name field and even reinstall the bootloader.

As it is today, the bootloader is a state. And to reduce the risk of failures (power-loss), it makes sense to install the bootloader only if absolutely necessary.


Bye

Stefan
Stefan May 24, 2020, 5:06 p.m. UTC | #10
Hi Danny!

> Am 24.05.2020 um 15:58 schrieb Danny Milosavljevic <dannym@scratchpost.org>:
> 
> All this complication is just because we don't have the file name of the os configuration (/etc/config.scm) in guix system delete-generations.

Hm, somehow true.

> What about us just requiring it from the user and then installing the bootloader&config that is specified there?  That would be a lot less magical--it basically would do the same that guix system reconfigure does now, then.

But it would mean to always install the bootloader without actual need, which is a point of failure. However, this is the case for reconfigure, too.


Bye

Stefan
Stefan June 6, 2020, 1:30 p.m. UTC | #11
Hi Mathieu!

I changed my mind. I think it is better in a first step to keep the convention to have a fixed /boot/grub/grub.cfg. Therefore I create another link at /<target>/efi/boot/grub.cfg pointing to that file. Then there is no trouble with the <boot-parameters> yet, and the grub-efi-net-bootloader doesn't need to know about the target directory before installation.

I removed the macro as well, hence fixing the path to efi/boot. To boot different systems – because of the grub.cfg link – there needs to be some redirection via PXE or DNS on some higher level. With the macro you could have several /<target>/efi/Guix<n> directories on the same tftp server, but their grub.cfg link would still all point to the same location: ../../../boot/grub/grub. So the macro makes no sense anymore.


Bye

Stefan
diff mbox series

Patch

diff --git a/gnu/bootloader/grub.scm b/gnu/bootloader/grub.scm
index 190b717163..9ca4f016f6 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)
@@ -53,6 +53,7 @@ 
 
             grub-bootloader
             grub-efi-bootloader
+            grub-efi-net-bootloader
             grub-mkrescue-bootloader
 
             grub-configuration))
@@ -142,34 +143,20 @@  WIDTH/HEIGHT, or #f if none was found."
                    #:width width #:height height))))
 
 (define* (eye-candy config store-device store-mount-point
-                    #:key system port)
+                    #:key 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\"."
+background image and fonts must be searched for."
   (define setup-gfxterm-body
-    (let ((gfxmode
-           (or (and-let* ((theme (bootloader-configuration-theme config))
-                          (gfxmode (grub-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 "
+    (format #f "
   set gfxmode=~a
   insmod all_video
-  insmod gfxterm~%" gfxmode)
-          "")))
+  insmod gfxterm~%"
+            (string-join
+             (grub-gfxmode (bootloader-theme config))
+             ";")))
 
   (define (setup-gfxterm config font-file)
     (if (memq 'gfxterm (bootloader-configuration-terminal-outputs config))
@@ -316,6 +303,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)))))
 
@@ -355,7 +345,6 @@  entries corresponding to old generations of the system."
     (eye-candy config
                (menu-entry-device (first all-entries))
                (menu-entry-device-mount-point (first all-entries))
-               #:system system
                #:port #~port))
 
   (define keyboard-layout-config
@@ -443,6 +432,68 @@  fi~%"))))
                       "--bootloader-id=Guix"
                       "--efi-directory" target-esp))))
 
+(define (install-grub-efi-net efi-subdir)
+  "Define a grub-efi bootloader installer for installation in EFI-SUBDIR,
+which is usually \"efi/guix\" or \"efi/boot\"."
+  (let* ((arch (car (string-split (or (%current-target-system)
+                                      (%current-system))
+                                  #\-)))
+         (efi-bootloader-link (string-append "boot"
+                                       (match arch
+                                         ("i686" "ia32")
+                                         ("x86_64" "x64")
+                                         ("armhf" "arm")
+                                         ("aarch64" "aa64")
+                                         ("riscv" "riscv32")
+                                         ("riscv64" "riscv64"))
+                                       ".efi"))
+         (efi-bootloader (string-append (match arch
+                                         ("i686" "i386")
+                                         ("x86_64" "x86_64")
+                                         ("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
+EFI-SUBDIR, which is usually \"efi/guix\" or \"efi/boot\" below the directory
+TARGET for the system whose root is mounted at MOUNT-POINT."
+        (let* ((mount-point-list (delete "" (string-split mount-point #\/)))
+               (target-list (delete "" (string-split target #\/)))
+               (net-dir
+                (string-append "/" (string-join (append
+                                                 mount-point-list
+                                                 target-list)
+                                                "/")))
+               (subdir #$efi-subdir)
+               (efi-bootloader-link
+                (string-append net-dir "/" subdir "/" #$efi-bootloader-link))
+               (store-name (car (delete "" (string-split bootloader #\/))))
+               (store
+                ;; Use target-list to construct a "../gnu" link with a correct
+                ;; number of "../" to the store.
+                (string-join (append (make-list (length target-list) "..")
+                                     (list store-name))
+                             "/"))
+               (store-link (string-append net-dir "/" store-name)))
+          ;; 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))
+          (catch 'system-error
+            (lambda () (delete-file efi-bootloader-link))
+            (lambda _ #f))
+          (symlink #$efi-bootloader
+                   efi-bootloader-link)
+          (catch 'system-error
+            (lambda () (delete-file store-link))
+            (lambda _ #f))
+          (symlink store
+                   store-link)))))
+
 ^L
 
 ;;;
@@ -464,6 +515,16 @@  fi~%"))))
    (name 'grub-efi)
    (package grub-efi)))
 
+(define* (grub-efi-net-bootloader #:key (target #f) (efi-subdir #f))
+  (let ((target (or target "boot"))
+        (efi-subdir (or efi-subdir "efi/boot")))
+    (bootloader
+     (inherit grub-bootloader)
+     (name 'grub-efi-net-bootloader)
+     (package grub-efi)
+     (installer (install-grub-efi-net efi-subdir))
+     (configuration-file (string-append target "/" efi-subdir "/grub.cfg")))))
+
 (define* grub-mkrescue-bootloader
   (bootloader
    (inherit grub-efi-bootloader)
diff --git a/gnu/system.scm b/gnu/system.scm
index 29e622872d..540f0e4a9e 100644
--- a/gnu/system.scm
+++ b/gnu/system.scm
@@ -297,7 +297,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)))))