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
10 changes: 10 additions & 0 deletions CONTRIBUTING.rst
Original file line number Diff line number Diff line change
Expand Up @@ -256,3 +256,13 @@ Below is a list of pure community features, their community maintainers, and mai
* Description:

``Enable boot of the installed system from a BTRFS subvolume.``

systemd-boot as a bootloader
^^^^^^^^^^^^^^^^^^^^^^^^^^^^

* Origin: https://github.com/rhinstaller/anaconda/pull/4368
* Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=2135531
* Maintainer: Jeremy Linton <[email protected]>
* Description:

``Enable boot using systemd-boot rather than grub2.``
2 changes: 1 addition & 1 deletion anaconda.spec.in
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ Source0: https://github.com/rhinstaller/%{name}/releases/download/%{name}-%{vers
%define libxklavierver 5.4
%define mehver 0.23-1
%define nmver 1.0
%define pykickstartver 3.44-1
%define pykickstartver 3.45-1
%define pypartedver 2.5-2
%define pythonblivetver 1:3.6.0-1
%define rpmver 4.15.0
Expand Down
1 change: 1 addition & 0 deletions data/anaconda.conf
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ selinux = -1
#
# DEFAULT Choose the type by platform.
# EXTLINUX Use extlinux as the bootloader.
# SDBOOT Use systemd-boot as the bootloader.
#
type = DEFAULT

Expand Down
5 changes: 5 additions & 0 deletions data/anaconda_options.txt
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,11 @@ Use extlinux as the bootloader. Note that there's no attempt to validate
that this will work for your platform or anything; it assumes that if you
ask for it, you want to try.

sdboot
Use systemd-boot as the bootloader. Note that there's no attempt to validate
that this will work for your platform or anything; it assumes that if you
ask for it, you want to try.

nombr

If nombr is specified the grub2 bootloader will be installed but the
Expand Down
9 changes: 9 additions & 0 deletions docs/boot-options.rst
Original file line number Diff line number Diff line change
Expand Up @@ -669,6 +669,15 @@ Use extlinux as the bootloader. Note that there's no attempt to validate that
this will work for your platform or anything; it assumes that if you ask for it,
you want to try.

.. inst.sdboot:

inst.sdboot
^^^^^^^^^^^^^

Use systemd-boot as the bootloader. Note that there's no attempt to validate that
this will work for your platform or anything; it assumes that if you ask for it,
you want to try.

.. inst.leavebootorder:

inst.leavebootorder
Expand Down
20 changes: 20 additions & 0 deletions docs/release-notes/systemd-boot.rst
Original file line number Diff line number Diff line change
@@ -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" ?


:Description:
With this release, systemd-boot can be selected as an alternative boot
loader for testing and development purposes.

This can be done with 'inst.sdboot' from the grub/kernel command
line or with '--sdboot' in a kickstart file as part of the
bootloader command. The resulting machine should be free of grub,
shim, and grubby packages, with all the boot files on the EFI
System Partition (ESP). This may mean that it is wise to dedicate
the space previously allocated for /boot to the ESP in order to
assure that future kernel upgrades will have sufficient space.

For more information, refer to the anaconda and systemd-boot documentation.

:Links:
- https://bugzilla.redhat.com/show_bug.cgi?id=2135531
- https://github.com/rhinstaller/anaconda/pull/4368
2 changes: 1 addition & 1 deletion dracut/parse-kickstart
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ from pykickstart.commands.mediacheck import FC4_MediaCheck as MediaCheck
from pykickstart.commands.driverdisk import F14_DriverDisk as DriverDisk
from pykickstart.commands.network import F38_Network as Network
from pykickstart.commands.displaymode import F26_DisplayMode as DisplayMode
from pykickstart.commands.bootloader import F34_Bootloader as Bootloader
from pykickstart.commands.bootloader import F39_Bootloader as Bootloader

# Default logging: none
log = logging.getLogger('parse-kickstart').addHandler(logging.NullHandler())
Expand Down
2 changes: 2 additions & 0 deletions pyanaconda/argument_parsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -591,6 +591,8 @@ def __call__(self, parser, namespace, values, option_string=None):
help=help_parser.help_text("noeject"))
ap.add_argument("--extlinux", action="store_true", default=False,
help=help_parser.help_text("extlinux"))
ap.add_argument("--sdboot", action="store_true", default=False,
help=help_parser.help_text("sdboot"))
ap.add_argument("--nombr", action="store_true", default=False,
help=help_parser.help_text("nombr"))
ap.add_argument("--mpathfriendlynames", dest="multipath_friendly_names", action="store_true",
Expand Down
2 changes: 2 additions & 0 deletions pyanaconda/core/configuration/anaconda.py
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,8 @@ def set_from_opts(self, opts):
# Set the bootloader type.
if opts.extlinux:
self.bootloader._set_option("type", BootloaderType.EXTLINUX.value)
if opts.sdboot:
self.bootloader._set_option("type", BootloaderType.SDBOOT.value)

# Set the boot loader flags.
self.bootloader._set_option("nonibft_iscsi_boot", opts.nonibftiscsiboot)
Expand Down
4 changes: 3 additions & 1 deletion pyanaconda/core/configuration/bootloader.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@

class BootloaderType(Enum):
"""Type of the bootloader."""
DEFAULT = "DEFAULT"
DEFAULT = "DEFAULT"
EXTLINUX = "EXTLINUX"
SDBOOT = "SDBOOT"


class BootloaderSection(Section):
Expand All @@ -38,6 +39,7 @@ def type(self):

DEFAULT Choose the type by platform.
EXTLINUX Use extlinux as the bootloader.
SDBOOT Use systemd-boot as the bootloader.

:return: an instance of BootloaderType
"""
Expand Down
2 changes: 1 addition & 1 deletion pyanaconda/core/kickstart/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
from pykickstart.commands.authselect import F28_Authselect as Authselect
from pykickstart.commands.autopart import F38_AutoPart as AutoPart
from pykickstart.commands.autostep import F34_AutoStep as AutoStep
from pykickstart.commands.bootloader import F34_Bootloader as Bootloader
from pykickstart.commands.bootloader import F39_Bootloader as Bootloader
from pykickstart.commands.btrfs import F23_BTRFS as BTRFS
from pykickstart.commands.cdrom import FC3_Cdrom as Cdrom
from pykickstart.commands.clearpart import F28_ClearPart as ClearPart
Expand Down
5 changes: 5 additions & 0 deletions pyanaconda/modules/storage/bootloader/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ class BootLoader(object):
image_label_attr = "label"
encryption_support = False
stage2_is_valid_stage1 = False
stage2_required = True

# requirements for stage2 devices
stage2_device = None
Expand Down Expand Up @@ -645,6 +646,10 @@ def is_valid_stage2_device(self, device, linux=True, non_linux=False):

log.debug("Is %s a valid stage2 target device?", device.name)

if not self.stage2_required:
log.debug("stage2 not required")
return True

if device.protected:
valid = False

Expand Down
7 changes: 7 additions & 0 deletions pyanaconda/modules/storage/bootloader/bootloader.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,9 @@ def _set_module_from_kickstart(self, data):
if data.bootloader.extlinux:
self.set_default_type(BootloaderType.EXTLINUX)

if data.bootloader.sdboot:
self.set_default_type(BootloaderType.SDBOOT)

if data.bootloader.bootDrive:
self.set_drive(data.bootloader.bootDrive)

Expand Down Expand Up @@ -184,6 +187,9 @@ def setup_kickstart(self, data):
if self.get_default_type() == BootloaderType.EXTLINUX:
data.bootloader.extlinux = True

if self.get_default_type() == BootloaderType.SDBOOT:
data.bootloader.sdboot = True

if self.bootloader_mode == BootloaderMode.DISABLED:
data.bootloader.disabled = True
data.bootloader.location = "none"
Expand Down Expand Up @@ -503,6 +509,7 @@ def generate_initramfs_with_tasks(self, payload_type, kernel_versions):
"""
return [
RecreateInitrdsTask(
storage=self.storage,
payload_type=payload_type,
kernel_versions=kernel_versions,
sysroot=conf.target.system_root
Expand Down
87 changes: 75 additions & 12 deletions pyanaconda/modules/storage/bootloader/efi.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,18 @@

from pyanaconda.modules.storage.bootloader.base import BootLoaderError
from pyanaconda.modules.storage.bootloader.grub2 import GRUB2
from pyanaconda.modules.storage.bootloader.systemd import SystemdBoot
from pyanaconda.core import util
from pyanaconda.core.kernel import kernel_arguments
from pyanaconda.core.i18n import _
from pyanaconda.core.configuration.anaconda import conf
from pyanaconda.core.kernel import kernel_arguments
from pyanaconda.core.path import join_paths
from pyanaconda.product import productName

from pyanaconda.anaconda_loggers import get_module_logger
log = get_module_logger(__name__)

__all__ = ["EFIBase", "EFIGRUB", "Aarch64EFIGRUB", "ArmEFIGRUB", "MacEFIGRUB"]
__all__ = ["EFIBase", "EFIGRUB", "Aarch64EFIGRUB", "ArmEFIGRUB", "MacEFIGRUB", "Aarch64EFISystemdBoot", "X64EFISystemdBoot"]


class EFIBase(object):
Expand All @@ -42,6 +45,16 @@ def efi_config_dir(self):
def _efi_config_dir(self):
return "efi/EFI/{}".format(conf.bootloader.efi_dir)

def get_fw_platform_size(self):
try:
with open("/sys/firmware/efi/fw_platform_size", "r") as f:
value = f.readline().strip()
except OSError:
log.info("Reading /sys/firmware/efi/fw_platform_size failed, "
"defaulting to 64-bit install.")
value = '64'
return value

def efibootmgr(self, *args, **kwargs):
if not conf.target.is_hardware:
log.info("Skipping efibootmgr for image/directory install.")
Expand Down Expand Up @@ -134,7 +147,7 @@ def install(self, args=None):
class EFIGRUB(EFIBase, GRUB2):
"""EFI GRUBv2"""
_packages32 = [ "grub2-efi-ia32", "shim-ia32" ]
_packages_common = [ "efibootmgr", "grub2-tools" ]
_packages_common = ["efibootmgr", "grub2-tools", "grub2-tools-extra", "grubby" ]
stage2_is_valid_stage1 = False
stage2_bootable = False

Expand All @@ -144,14 +157,7 @@ def __init__(self):
super().__init__()
self._packages64 = [ "grub2-efi-x64", "shim-x64" ]

try:
f = open("/sys/firmware/efi/fw_platform_size", "r")
value = f.readline().strip()
except OSError:
log.info("Reading /sys/firmware/efi/fw_platform_size failed, "
"defaulting to 64-bit install.")
value = '64'
if value == '32':
if self.get_fw_platform_size() == '32':
self._is_32bit_firmware = True

@property
Expand Down Expand Up @@ -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
Contributor

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
Contributor

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.

_packages64 = []

def __init__(self):
super().__init__()

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"))
Comment on lines +214 to +217
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.


@property
def packages(self):
return self._packages64 + self._packages_common

@property
def efi_config_file(self):
""" Full path to EFI configuration file. """
return join_paths(self.efi_config_dir, self._config_file)

def write_config(self):
""" Write the config settings to config file (ex: grub.cfg) not needed for systemd. """
config_path = join_paths(conf.target.system_root, self.efi_config_file)

log.info("efi.py: (systemd) write_config systemd : %s ", config_path)

super().write_config()

def install(self, args=None):
log.info("efi.py: (systemd) install")
# force the resolution order, we don't want to:
# efibootmgr remove old "fedora"
# or use efiboot mgr to install a new one
# lets just use `bootctl install` directly.
# which will fix the efi boot variables too.
SystemdBoot.install(self)


class Aarch64EFIGRUB(EFIGRUB):
_serial_consoles = ["ttyAMA", "ttyS"]
_efi_binary = "\\shimaa64.efi"

def __init__(self):
super().__init__()
self._packages64 = ["grub2-efi-aa64", "shim-aa64"]
self._packages64 = ["grub2-efi-aa64", "shim-aa64", "grub2-efi-aa64-cdboot"]


class Aarch64EFISystemdBoot(EFISystemdBoot):
_serial_consoles = ["ttyAMA", "ttyS"]
_efi_binary = "\\systemd-bootaa64.efi"

def __init__(self):
super().__init__()
self._packages64 = []

class X64EFISystemdBoot(EFISystemdBoot):
_efi_binary = "\\systemd-bootx64.efi"

def __init__(self):
super().__init__()
self._packages64 = []



class ArmEFIGRUB(EFIGRUB):
Expand Down
10 changes: 10 additions & 0 deletions pyanaconda/modules/storage/bootloader/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ def get_class_by_name(cls, name):

Supported values:
EXTLINUX
SDBOOT

:param name: a boot loader name or None
:return: a boot loader class or None
Expand All @@ -90,6 +91,15 @@ def get_class_by_name(cls, name):
from pyanaconda.modules.storage.bootloader.extlinux import EXTLINUX
return EXTLINUX

if name == "SDBOOT":
jlinton marked this conversation as resolved.
Show resolved Hide resolved
platform_class = platform.platform.__class__
if platform_class is platform.Aarch64EFI:
from pyanaconda.modules.storage.bootloader.efi import Aarch64EFISystemdBoot
return Aarch64EFISystemdBoot
if platform_class is platform.EFI:
from pyanaconda.modules.storage.bootloader.efi import X64EFISystemdBoot
return X64EFISystemdBoot

return None

@classmethod
Expand Down
8 changes: 6 additions & 2 deletions pyanaconda/modules/storage/bootloader/installation.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from blivet.devices import BTRFSDevice
from pyanaconda.core.constants import PAYLOAD_TYPE_RPM_OSTREE, PAYLOAD_LIVE_TYPES
from pyanaconda.modules.storage.bootloader import BootLoaderError

from pyanaconda.modules.storage.bootloader.systemd import SystemdBoot
from pyanaconda.core.util import execWithRedirect
from pyanaconda.modules.common.errors.installation import BootloaderInstallationError
from pyanaconda.modules.storage.constants import BootloaderMode
Expand Down Expand Up @@ -173,9 +173,10 @@ def run(self):
class RecreateInitrdsTask(Task):
"""Installation task that recreates the initrds."""

def __init__(self, payload_type, kernel_versions, sysroot):
def __init__(self, storage, payload_type, kernel_versions, sysroot):
"""Create a new task."""
super().__init__()
self._storage = storage
self._payload_type = payload_type
self._versions = kernel_versions
self._sysroot = sysroot
Expand All @@ -192,6 +193,9 @@ def run(self):
if self._payload_type == PAYLOAD_TYPE_RPM_OSTREE:
log.debug("Don't regenerate initramfs on rpm-ostree systems.")
return
if isinstance(self._storage.bootloader, SystemdBoot):
log.debug("Don't regenerate initramfs on systemd-boot systems.")
return

recreate_initrds(
sysroot=self._sysroot,
Expand Down
Loading