Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide a command line option to install systemd-boot rather than grub2 on x86_64 and arm64 #4368

Merged
merged 9 commits into from
Mar 10, 2023

Conversation

jlinton
Copy link

@jlinton jlinton commented Oct 7, 2022

systemd-boot is a lightweight bootloader utility that understands the Linux loader syntax and populates a menu of installed boot options. It then utilizes UEFI-provided system services to run the given image. In the case of x86, arm64, riscv and others the linux kernel can be (is on fedora/RH distros) a standalone EFI executable image. With the initrd= option, the kernel loads its own initrd as well.

This simplifies the boot process significantly as UEFI already provides a fairly heavyweight environment and signing systemd-boot with platform-enrolled keys avoids much of the complexity of shim+grub loading linux filesystem drivers, console and keyboard mgmt, etc.

This set of patches has been tested on x86_64 and arm64, but it requires either an updated set of systemd packages (to name the bootloader, a stubby (aka systemd grubby) package which provides dnf ownership of some of the /boot/efi/ files, and removal of all the grub packages in the -comps.xml files. Once the system has been installed it should behave mostly as one would expect with 'dnf upgrade' and the like.

It's sorta RFC quality at the moment, as the root= options need some further tweaking, and there remains some stubby work for kdump and the like). I've also not included anything that modifies the default partitioning setups, but in the future, picking this option should remove the need for a dedicated /boot partition.

In theory, systemd-boot can then be used along with the systemd-stub to create unified signed images (as is being done at some large companies today!), but I've not tested anything in that path, and the short term goal is simply to clean up all the bits and pieces so that its possible in the F38/39 timeframe to clean install a fedora system without grub on x86 and arm while maintaining grub as the default bootloader.

@jlinton
Copy link
Author

jlinton commented Oct 7, 2022

Oh, I should add that if anyone gets far enough to try this out with libvirt/virtmanager/etc that x86 UEFI machines need to have secure boot disabled otherwise it will just silently fail to execute systemd-boot/etc.

# write the options above, and run a script which will merge the
# boot cmdline (after stripping inst. and BOOT_) with the anaconda settings
rc = util.execWithRedirect(
"/usr/sbin/updateloaderentries.sh",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see that the ZIPL class has an update_bls_args() function that does something similar. Maybe there could be a common helper instead of having a separate script?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the one piece I looked at for a half day or so, but haven't managed to merge together. I think in general you are right, both are doing something quite similar (updating existing BLS entries) but there are a few subtle differences that will need to be merged around how the command line itself is generated. Worse I suspect both have a fundamental error, in that they aren't filtering for newly installed entries which could be a problem.
I think at this point, this is probably the largest remaining issue.

data/anaconda.conf Outdated Show resolved Hide resolved

rc = util.execWithRedirect("bootctl", [ "install", "--esp-path=/boot/efi",
"--efi-boot-option-description=fedora",
"--loader-path=/EFI/fedora/" ],

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've got a local change for this at the moment, but its not clear what the upstream change will be, so I will likely drop the loader-path parts from v2, under the assumption it can be added back when upstream systemd accepts a patch.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That discussion seems to be here: systemd/systemd#24828 now that i've removed the arm-specific kernel-install bits, which should be redundant with the compressed EFI/kernel images.

@poncovka poncovka self-assigned this Oct 10, 2022
@poncovka poncovka added manual testing required This issue can't be merged without manual testing f38 Fedora 38 labels Oct 10, 2022
@jlinton jlinton force-pushed the allow_systemd_boot_as_bootloader branch from 2920492 to 98d1614 Compare October 14, 2022 17:55
@jlinton
Copy link
Author

jlinton commented Oct 14, 2022

I've updated all bits pointed out above to my own satisfaction, except for the updateloaderentries.sh bit.
This code is generally working as I expect it at this point. The problematic issues in v1 around the second stage partitioning and root= command line selection have been fixed, and a fair number of cleanups, including most of the ones pointed out above, are in place.

So, I would take the RFC note off, depending on whether we think the updateloaderentries.sh bit needs to be fixed before merging. (I'm on the fence here).

@martinezjavier
Copy link
Contributor

I've updated all bits pointed out above to my own satisfaction, except for the updateloaderentries.sh bit. This code is generally working as I expect it at this point. The problematic issues in v1 around the second stage partitioning and root= command line selection have been fixed, and a fair number of cleanups, including most of the ones pointed out above, are in place.

Thanks a lot for doing a respin and addressing all the issues I pointed out! It looks good to me and in my opinion we should just merge this. It's great to have this support and since isn't the default, people using it should know what they are doing anyways.

So, I would take the RFC note off, depending on whether we think the updateloaderentries.sh bit needs to be fixed before merging. (I'm on the fence here).

I honestly don't think this is a big issue. It can be cleaned up later and the need for this script dropped. At the end of the day, is just an implementation detail.

I've approved the changes but I'm not an Anaconda developer, just an occasional contributor. @jkonecny12 @poncovka could you folks please take a look to these patches?

@poncovka poncovka added the release note required Write a release note for this change. label Oct 17, 2022
@jlinton jlinton force-pushed the allow_systemd_boot_as_bootloader branch from 98d1614 to 442cbaf Compare October 17, 2022 15:25
@poncovka
Copy link
Contributor

Please, fix the pylint issues:

************* Module pyanaconda.modules.storage.bootloader.systemd
W0223(abstract-method):/tmp/anaconda/pyanaconda/modules/storage/bootloader/systemd.py:51,0: SYSTEMD: Method 'install' is abstract in class 'BootLoader' but is not overridden
W0223(abstract-method):/tmp/anaconda/pyanaconda/modules/storage/bootloader/systemd.py:51,0: SYSTEMD: Method 'write_config_images' is abstract in class 'BootLoader' but is not overridden
W0246(useless-parent-delegation):/tmp/anaconda/pyanaconda/modules/storage/bootloader/systemd.py:68,4: SYSTEMD.__init__: Useless parent or super() delegation in method '__init__'
W0611(unused-import):/tmp/anaconda/pyanaconda/modules/storage/bootloader/systemd.py:36,0: : Unused import os
W0611(unused-import):/tmp/anaconda/pyanaconda/modules/storage/bootloader/systemd.py:37,0: : Unused import re
W0611(unused-import):/tmp/anaconda/pyanaconda/modules/storage/bootloader/systemd.py:42,0: : Unused _ imported from pyanaconda.core.i18n
W0611(unused-import):/tmp/anaconda/pyanaconda/modules/storage/bootloader/systemd.py:43,0: : Unused open_with_perm imported from pyanaconda.core.path
************* Module pyanaconda.modules.storage.bootloader.installation
W0404(reimported):/tmp/anaconda/pyanaconda/modules/storage/bootloader/installation.py:33,0: : Reimport 'conf' (imported line 24)
************* Module pyanaconda.modules.storage.bootloader.efi
W0223(abstract-method):/tmp/anaconda/pyanaconda/modules/storage/bootloader/efi.py:198,0: EFISYSTEMD: Method 'write_config_images' is abstract in class 'BootLoader' but is not overridden
E1101(no-member):/tmp/anaconda/pyanaconda/modules/storage/bootloader/efi.py:225,15: EFISYSTEMD.packages: Instance of 'EFISYSTEMD' has no '_packages64' member
W0223(abstract-method):/tmp/anaconda/pyanaconda/modules/storage/bootloader/efi.py:257,0: Aarch64EFISystemdBoot: Method 'write_config_images' is abstract in class 'BootLoader' but is not overridden
W0223(abstract-method):/tmp/anaconda/pyanaconda/modules/storage/bootloader/efi.py:265,0: x64EFISystemdBoot: Method 'write_config_images' is abstract in class 'BootLoader' but is not overridden

************* Unused False Positives Found:

Also, the kickstart support should be implemented here: https://github.com/pykickstart/pykickstart

@jkonecny12
Copy link
Member

Hi all, if I have correct information (which might be wrong) this will not work on SilverBlue systems. Could you please disable this in fedora-silverblue.conf configuration if that is correct?

@travier
Copy link
Contributor

travier commented Oct 24, 2022

This is closely related to fedora-silverblue/issue-tracker#120 and I'm interested in making that work on Silverblue/Kinoite.
CC @cgwalters

@jlinton
Copy link
Author

jlinton commented Oct 24, 2022

I'm @jkonecny12 curious about where the problem with silverblue is? If anything, this should help with the immutable rootfs/etc because the entire config/boot process is contained on the ESP, and AFAIK loader updates can/would be via bootctl.

But before anyone gets excited, this commit doesn't replace grub (or any other bootloader, it just provides the framework for testing alternate paths).

@martinezjavier
Copy link
Contributor

I'm @jkonecny12 curious about where the problem with silverblue is? If anything, this should help with the immutable rootfs/etc because the entire config/boot process is contained on the ESP, and AFAIK loader updates can/would be via bootctl.

The issue that Jiri is mentioning is that ostree doesn't support sd-boot, or more generally, it requires the kernel, initrd and BLS snippets to be in a partition that support symbolic links. Because the current deployment is switched by renaming a symlink.

For context, take a look to ostreedev/ostree#1719 that's the RFE to support sd-boot in ostree. It's pointed out there that one option could be to create two directories in the ESP and rename exchange them atomically. There's a PR opened to do that ostreedev/ostree#1967.

But the problem was that vfat didn't have support for renameat2(..., EXCHANGE_REPLACE). I've implemented that and landed in Linux v6.0:

https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=019a0c9e377c9f7bd477a0742706d93cdddaee4d
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=da87e1725ae2136baeb9aac04c572c283afc917f

Copy link
Contributor

@poncovka poncovka left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have found some small issues, otherwise the code seems to be fine. The tests will require some changes though, I will post their output later.

pyanaconda/modules/storage/bootloader/efi.py Outdated Show resolved Hide resolved
pyanaconda/modules/storage/bootloader/installation.py Outdated Show resolved Hide resolved
pyanaconda/modules/storage/bootloader/systemd.py Outdated Show resolved Hide resolved
@poncovka
Copy link
Contributor

The tests pass with the following changes: poncovka@36f73f7
Please, change also pykickstartver in the anaconda.spec.in file.

@jlinton jlinton force-pushed the allow_systemd_boot_as_bootloader branch from 031d21c to 1fe7309 Compare January 25, 2023 22:57
@jlinton
Copy link
Author

jlinton commented Jan 27, 2023

I've applied the suggested changes as well as picked up test tweaks noted above, as well as adding systemd-boot to the dependencies list. I also bumped the pykickstart version in anaconda.spec.in in anticipation of the next version, but its probably causing additional failures above because the testing bot can't find a package with that version.


Systemd-boot is dead simple, it basically provides a second boot menu
and injects the kernel parms into an efi stubbed kernel, which in turn
(optionally) loads it's initrd. As such there aren't any filesystem or
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"its"

Hmm, so this description doesn't sound right. "second boot menu": I guess you're thinking about the firmware menu, but generally it wouldn't be shown, so for normal use it's just "boot menu". Also, the kerel doesn't load the initrd, it happens earlier. Kernels with an efi stub also usually contain their own commandline, so injection of the parameters is an optional step… So this text would require some editing.

But the bigger issue is that a description of systemd-boot features shouldn't be done here. Please instead refer to some docs (https://www.freedesktop.org/software/systemd/man/systemd-stub.html, https://www.freedesktop.org/software/systemd/man/systemd-boot.html ?). The feature set and details of operation of sd-stub/boot can change over time.

I think a one-sentence description of systemd-boot would be enough.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yah I can move it around, but to be pedantic, the kernel-provided stub has this parameter.
https://elixir.bootlin.com/linux/v6.2-rc6/source/Documentation/admin-guide/kernel-parameters.txt#L2031
which is one of the ways to load the initrd with systemd-boot.

Comment on lines +44 to +46
ESP. This requires a larger than normal ESP, but for now we assume that
this linux installer is creating the partitions, so where the space
is allocated doesn't matter.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"for now we assume" — the installer is in full control, so there is no need hedge like this. Instead: "When the Linux installer is creating the partitions, it can create the ESP as large as needed."

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure.

Comment on lines +121 to +126
rc = util.execWithRedirect(
"/usr/sbin/updateloaderentries",
[" "],
root=conf.target.system_root
)
if rc:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updateloaderentries is from https://bugzilla.redhat.com/show_bug.cgi?id=2134972.
As I wrote the ticket, please let's not do this at all. updateloaderentries is modifying some existing on-disk files. It would be nicer to just write the with the right contents from the start. But if we can't do that, then a bit of python should good enough:

def clean_up_loader_entry(filename, args):
  with open(filename, 'r+') as f:
    lines = []
    for line in f:
      if line.startswith('options'):
        filtered = (opt for opt in line.split()[1:]
                    if not opt.startswith('inst.') and not opt.startswith('BOOT_IMAGE'))
        lines += [' '.join((f'options    {args}',  *filtered)) + '\n']
      else:
        lines += [line]
    f.seek(0)
    f.write(''.join(lines))
    f.truncate()

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Except that bit of python doesn't replace the ability to edit the BLS entries after the machine has been booted (per grubby behavior), nor does it solve many of the other things you commented about in the above defect.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And I don't think your code is doing what the update script does anyway, which is merge and filter the current boot command line with the existing BLS entries.

rc = util.execWithRedirect("bootctl", [ "install", "--esp-path=/boot/efi",
"--efi-boot-option-description=" + productName.split("-")[0] ],
root=conf.target.system_root,
env_prune=['MALLOC_PERTURB_'])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

env_prune=['MALLOC_PERTURB_']

Why is this needed?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, now that is a good one. Its a copy paste job from grub2 install where it noted the dev/mapper gets garbage from the bios device names (see commit: 022b8b6) if MALLOC_PERTURB is set. I think I saw this, but with one of early revisions where the BLS rootfs was garbaged up when using /dev/mapper entires. So that code has been replaced now, its probably worth retesting and removing even though its probably harmless to pull out the environment variable.

Comment on lines +212 to +217
if self.get_fw_platform_size() == '32':
# not supported try a different bootloader
log.error("efi.py: systemd-boot is not supported on 32-bit platforms")
raise BootLoaderError(_("Systemd-boot is not supported on this platform"))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strictly speaking, sd-boot has a 32-bit variant. I think it isn't tested much, and it's fine not to even try it from Anaconda, but it exists.

@@ -0,0 +1,20 @@
:Type: Kickstart Installation
:Summary: Install an image using systemd-boot rather than grub (#2135531)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"grub2" ?

pyanaconda/modules/storage/bootloader/systemd.py Outdated Show resolved Hide resolved
Copy link
Contributor

@poncovka poncovka left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pull request meets all formal requirements, but the functionality requires a package that is not available in Fedora repositories at this moment (see the bug 2134972), so it cannot be fully tested and merged. I will mark it as blocked for now. Since the Beta Freeze of Fedora 38 started yesterday, I am affraid that this feature will have to be postponed to Fedora 39.

  • Unit tests and pylint pass.
  • Kickstart smoke tests pass.
  • Installations with EFIGRUB are bootable.
  • inst.sdboot cannot be tested yet.
  • bootloader --sdboot cannot be tested yet.

@poncovka poncovka added blocked Don't merge this pull request! f39 and removed release note required Write a release note for this change. f38 Fedora 38 labels Feb 22, 2023
@martinezjavier
Copy link
Contributor

The pull request meets all formal requirements, but the functionality requires a package that is not available in Fedora repositories at this moment (see the bug 2134972), so it cannot be fully tested and merged. I will mark it as blocked for now. Since the Beta Freeze of Fedora 38 started yesterday, I am affraid that this feature will have to be postponed to Fedora 39.

  • Unit tests and pylint pass.
  • Kickstart smoke tests pass.
  • Installations with EFIGRUB are bootable.
  • inst.sdboot cannot be tested yet.
  • bootloader --sdboot cannot be tested yet.

Folks, can we please merge this if only for rawhide in the meantime since this is really an experimental feature and @jlinton has been working on it for months already. I'ts not expected to be used by default so IMO resolving https://bugzilla.redhat.com/show_bug.cgi?id=2134972 shouldn't https://bugzilla.redhat.com/show_bug.cgi?id=2134972 for this PR.

Otherwise, the changes could bit rot and cause merge conflicts with would be more work for everyone than just merging as is now.

@poncovka
Copy link
Contributor

@martinezjavier @jlinton I talked to the team and they agreed with merging it as an experimental feature, so I will start to work on that. The code will need some small changes to get it ready for F39, but I will handle it.

@poncovka poncovka removed manual testing required This issue can't be merged without manual testing blocked Don't merge this pull request! labels Feb 28, 2023
@martinezjavier
Copy link
Contributor

@martinezjavier @jlinton I talked to the team and they agreed with merging it as an experimental feature, so I will start to work on that. The code will need some small changes to get it ready for F39, but I will handle it.

Perfect, thanks a lot!!

systemd-boot places all the files need for boot
on the ESP, removing the need for a dedicated stage2
boot partition to hold the initrd/etc. So, lets add
an option to skip the stage2 validation.

Signed-off-by: Jeremy Linton <jeremy.linton@arm.com>
Systemd-boot is a lightweight boot selector that only
runs in EFI enviroments. Compared to shim+grub its incredibly
simple as it utilizes efi boot services.

Lets add a subclass that anaconda can use to install it rather
than grub.

Signed-off-by: Jeremy Linton <jeremy.linton@arm.com>
EFIGRUB is checking fw_platform_size to choose
which grub, 32 or 64-bit, needs to be installed.

Lets hoist that check because systemd-boot will use
it to verify that the platform firmware is 64-bits.

Signed-off-by: Jeremy Linton <jeremy.linton@arm.com>
Now that we have a systemd bootloader class lets add
an efi class that can call it to install the bootloader.

Then create Aarch64EFISystemBoot class that can utilize it.

Signed-off-by: Jeremy Linton <jeremy.linton@arm.com>
Signed-off-by: Jeremy Linton <jeremy.linton@arm.com>
Signed-off-by: Jeremy Linton <jeremy.linton@arm.com>
Now, that we have all the infrastructure in place, lets
add and document a 'inst.systemd' option that can be used
on the boot commadline or placed in kickstart files.

Signed-off-by: Jeremy Linton <jeremy.linton@arm.com>
Place a release note for the next verison of anaconda which notes that
it is now possible given the right set of packages/etc to install
a system utilizing systemd-boot.

Signed-off-by: Jeremy Linton <jeremy.linton@arm.com>
The systemd-boot install feature is for the moment largly
expermental, but adding the ability to create a clean install
without grub drippings is important for testing.

Signed-off-by: Jeremy Linton <jeremy.linton@arm.com>
@poncovka poncovka force-pushed the allow_systemd_boot_as_bootloader branch from 1fe7309 to a9e0559 Compare March 9, 2023 16:30
@poncovka
Copy link
Contributor

poncovka commented Mar 9, 2023

Updated and rebased. We should have a functional CI tomorrow, but the tests pass locally.

@poncovka
Copy link
Contributor

/kickstart-test --testtype smoke

@poncovka poncovka merged commit 227afca into rhinstaller:master Mar 10, 2023
@@ -197,13 +203,70 @@ def write_config(self):
super().write_config()


class EFISystemdBoot(EFIBase, SystemdBoot):
"""EFI Systemd-boot"""
_packages_common = ["efibootmgr", "systemd-udev", "systemd-boot", "sdubby"]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sdubby does not exist

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jlinton should we remove the sdubby package or is this something special?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The package should be mandatory, but it is still in review (https://bugzilla.redhat.com/show_bug.cgi?id=2134972). See the comments above.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Formally it is under review. But I think that if the review is supposed to be more than just a rubber-stamp procedure, then IMO this particular package should not pass it. It provides a concentrated dose of bad technical choices and super super brittle workarounds for shortcomings in other packages. The good thing is that all of those other packages are under our control, so we can fix things: anaconda, grub2, kernel-install, kernel scriptlets, systemd-boot. We don't need a combination of rpm packaging, bash, python, and awk to serve as glue between those other packages.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels