Source code for kiwi.bootloader.config.systemd_boot

# Copyright (c) 2022 Marcus Schäfer
#
# This file is part of kiwi.
#
# kiwi is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# kiwi is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with kiwi.  If not, see <http://www.gnu.org/licenses/>
#
import os
import glob
from string import Template
from contextlib import ExitStack
from typing import Dict

# project
from kiwi.path import Path
from kiwi.bootloader.template.systemd_boot import BootLoaderTemplateSystemdBoot
from kiwi.bootloader.config.bootloader_spec_base import BootLoaderSpecBase
from kiwi.boot.image.base import BootImageBase
from kiwi.mount_manager import MountManager
from kiwi.storage.loop_device import LoopDevice
from kiwi.storage.disk import Disk
from kiwi.command import Command
from kiwi.utils.os_release import OsRelease

from kiwi.exceptions import (
    KiwiTemplateError,
    KiwiBootLoaderTargetError,
    KiwiKernelLookupError
)

import kiwi.defaults as defaults


[docs] class BootLoaderSystemdBoot(BootLoaderSpecBase):
[docs] def create_loader_image(self, target: str) -> None: """ Handle EFI images For systemd boot EFI images are provided along with systemd. Thus the only action taken care of is the creation of the ESP path :param str target: target identifier, one of disk, live(iso) or install(iso) """ self.efi_boot_path = self.create_efi_path()
[docs] def setup_loader(self, target: str) -> None: """ Setup ESP for systemd-boot using bootctl For disk images only, setup the ESP layout for systemd boot using bootctl. All data will be loaded from the ESP. :param str target: target identifier, one of disk, live(iso) or install(iso) Currently only the disk identifier is supported """ if target != self.target.disk: raise KiwiBootLoaderTargetError( 'systemd-boot is only supported with the EFI disk image target' ) boot_options = self.custom_args['boot_options'] self._mount_system( boot_options.get('root_device'), boot_options.get('boot_device'), boot_options.get('efi_device'), boot_options.get('system_volumes') ) self._run_bootctl(self.root_mount.mountpoint) self.set_loader_entry(self.root_mount.mountpoint, self.target.disk)
[docs] def set_loader_entry(self, root_dir: str, target: str) -> None: """ Setup/update loader entries :param str target: target identifier, one of disk, live(iso) or install(iso) """ os_release = OsRelease(root_dir) try: kernel_name = os.path.basename( list(glob.iglob(f'{root_dir}/lib/modules/*'))[0] ) except Exception as issue: raise KiwiKernelLookupError( f'Kernel lookup in {root_dir}/lib/modules failed with {issue}' ) default_entry = f'{os_release.get("ID")}-{kernel_name}.conf' BootLoaderSystemdBoot._write_config_file( BootLoaderTemplateSystemdBoot().get_entry_template(), root_dir + f'/boot/efi/loader/entries/{default_entry}', self._get_template_parameters(default_entry) )
def _create_embedded_fat_efi_image(self, path: str): """ Creates a EFI system partition image at the given path. Installs systemd-boot required EFI layout into the image """ fat_image_mbsize = int( self.xml_state.build_type .get_efifatimagesize() or defaults.EFI_FAT_IMAGE_SIZE ) Command.run( ['qemu-img', 'create', path, f'{fat_image_mbsize}M'] ) Command.run( ['sgdisk', '-n', ':1.0', '-t', '1:EF00', path] ) with LoopDevice(path) as loop_provider: loop_provider.create(overwrite=False) with Disk('gpt', loop_provider) as disk: disk.map_partitions() disk.partitioner.partition_id = 1 disk._add_to_map('efi') Command.run( ['mkdosfs', '-n', 'BOOT', disk.partition_map['efi']] ) Path.create(f'{self.root_dir}/boot/efi') with ExitStack() as stack: efi_mount = MountManager( device=disk.partition_map['efi'], mountpoint=f'{self.root_dir}/boot/efi' ) stack.push(efi_mount) device_mount = MountManager( device='/dev', mountpoint=f'{self.root_dir}/dev' ) stack.push(device_mount) proc_mount = MountManager( device='/proc', mountpoint=f'{self.root_dir}/proc' ) stack.push(proc_mount) sys_mount = MountManager( device='/sys', mountpoint=f'{self.root_dir}/sys' ) stack.push(sys_mount) efi_mount.mount() device_mount.bind_mount() proc_mount.bind_mount() sys_mount.bind_mount() self._run_bootctl(self.root_dir) self.set_loader_entry(self.root_dir, self.target.live) Command.run( ['dd', f'if={disk.partition_map["efi"]}', f'of={path}.img'] ) Command.run( ['mv', f'{path}.img', path] ) def _run_bootctl(self, root_dir: str) -> None: """ Setup ESP for systemd-boot using bootctl """ kernel_info = BootImageBase( self.xml_state, root_dir, root_dir ).get_boot_names() Command.run( [ 'chroot', root_dir, 'bootctl', 'install', '--esp-path=/boot/efi', '--no-variables', '--entry-token', 'os-id' ] ) Path.wipe(f'{root_dir}/boot/loader') self._write_kernel_cmdline_file(root_dir) # copy kernel and initrd entry_dir = f'{root_dir}/boot/efi/loader/entries' os_dir = f'{root_dir}/boot/efi/os' Path.create(entry_dir) Path.create(os_dir) self.custom_args['kernel'] = \ f'os/{os.path.basename(kernel_info.kernel_filename)}' self.custom_args['initrd'] = \ f'os/{kernel_info.initrd_name}' Command.run( ['cp', kernel_info.kernel_filename, os_dir] ) Command.run( ['cp', f'{root_dir}/boot/{kernel_info.initrd_name}', os_dir] ) # create loader.conf BootLoaderSystemdBoot._write_config_file( BootLoaderTemplateSystemdBoot().get_loader_template(), root_dir + '/boot/efi/loader/loader.conf', self._get_template_parameters() ) def _get_template_parameters( self, default_entry: str = 'main.conf' ) -> Dict[str, str]: return { 'kernel_file': self.custom_args['kernel'] or 'vmlinuz', 'initrd_file': self.custom_args['initrd'] or 'initrd', 'boot_options': self.cmdline, 'boot_timeout': self.timeout, 'bootpath': self.get_boot_path(), 'title': self.get_menu_entry_title(), 'default_entry': default_entry } @staticmethod def _write_config_file( template: Template, filename: str, parameters: Dict[str, str] ) -> None: try: config_data = template.substitute(parameters) Path.create(os.path.dirname(filename)) with open(filename, 'w') as config: config.write(config_data) except Exception as issue: raise KiwiTemplateError( '{0}: {1}'.format(type(issue).__name__, issue) ) def _write_kernel_cmdline_file(self, root_dir: str) -> None: kernel_cmdline = f'{root_dir}/etc/kernel/cmdline' Path.create(os.path.dirname(kernel_cmdline)) with open(kernel_cmdline, 'w') as cmdline: cmdline.write(self.cmdline)