Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions contrib/packaging/switch-to-sdboot
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,8 @@ sdboot=$(ls /usr/lib/systemd/boot/efi/systemd-boot*.efi)
sdboot_bn=$(basename "${sdboot}")
# Override with our signed binary
install -m 0644 "${src}/${sdboot_bn}" "${sdboot}"

mkdir -p /usr/lib/bootc/install/systemd-boot
cat <<EOF > /usr/lib/bootc/install/systemd-boot/loader.conf
timeout 1
EOF
11 changes: 0 additions & 11 deletions crates/lib/src/bootc_composefs/boot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,10 +135,6 @@ pub(crate) const EFI_UUID_FILE: &str = "efiuuid.cfg";
/// The EFI Linux directory
pub(crate) const EFI_LINUX: &str = "EFI/Linux";

/// Timeout for systemd-boot bootloader menu
const SYSTEMD_TIMEOUT: &str = "timeout 5";
const SYSTEMD_LOADER_CONF_PATH: &str = "loader/loader.conf";

pub(crate) const INITRD: &str = "initrd";
pub(crate) const VMLINUZ: &str = "vmlinuz";

Expand Down Expand Up @@ -1795,13 +1791,6 @@ fn write_systemd_uki_config(
)?;
}

// Write the timeout for bootloader menu if not exists
if !esp_dir.exists(SYSTEMD_LOADER_CONF_PATH) {
esp_dir
.atomic_write(SYSTEMD_LOADER_CONF_PATH, SYSTEMD_TIMEOUT)
.with_context(|| format!("Writing to {SYSTEMD_LOADER_CONF_PATH}"))?;
}

let esp_dir = esp_dir
.reopen_as_ownedfd()
.context("Reopening as owned fd")?;
Expand Down
23 changes: 23 additions & 0 deletions crates/lib/src/bootloader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,13 @@ pub(crate) fn install_via_bootupd(
}
}

/// Path in the container image where an image author may ship an initial
/// systemd-boot configuration. When present, it is copied verbatim onto the
/// ESP after `bootctl install`
const SYSTEMD_LOADER_CONF_SRC: &str = "usr/lib/bootc/install/systemd-boot/loader.conf";
/// Destination of the systemd-boot loader configuration on the ESP
const SYSTEMD_LOADER_CONF_PATH: &str = "loader/loader.conf";

/// Install systemd-boot using a pre-prepared boot root.
#[context("Installing bootloader")]
pub(crate) fn install_systemd_boot(
Expand Down Expand Up @@ -358,6 +365,22 @@ pub(crate) fn install_systemd_boot(
}
}

// If the container image ships an initial systemd-boot configuration,
// copy it verbatim onto the ESP, overwriting the one generated by bootctl
if let Some(mut src) = prepared_root
.dir()
.open_optional(SYSTEMD_LOADER_CONF_SRC)
.context("Opening image systemd-boot loader config")?
Comment thread
Johan-Liebert1 marked this conversation as resolved.
Resolved
{
let esp_dir = prepared_root.open_esp_dir()?;

esp_dir
.atomic_replace_with(SYSTEMD_LOADER_CONF_PATH, |writer| {
std::io::copy(&mut src, writer)
})
.with_context(|| format!("Writing to {SYSTEMD_LOADER_CONF_PATH}"))?;
}

Ok(())
}

Expand Down
11 changes: 11 additions & 0 deletions docs/src/bootc-bootloaders.7.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,17 @@ NOTE: systemd-boot is only supported for Composefs Backend and not for Ostree
If bootupd is not present in the input container image, then systemd-boot will be used
by default (except on s390x).

### Initial loader configuration

`bootc` does not synthesize a systemd-boot [`loader.conf`](https://www.freedesktop.org/software/systemd/man/latest/loader.conf.html).
Instead, if the container image ships one at `/usr/lib/bootc/install/systemd-boot/loader.conf`,
`bootc` copies it verbatim onto the ESP (as `loader/loader.conf`) after running
`bootctl install`. This lets the base image or a package define the initial
configuration for eg. the boot menu timeout.

If the image does not ship this file, the commented-out template written by `bootctl install`
is left in place.

## s390x

bootc uses `zipl`.
Expand Down
7 changes: 7 additions & 0 deletions tmt/tests/booted/readonly/030-test-composefs.nu
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@ if $expecting_composefs {
let bootctl_output = (bootctl)

if ($bootctl_output | str contains 'Product: systemd-boot') {
# Make sure our "timeout 1" is actually present (see ../../../../contrib/packaging/switch-to-sdboot:41)

mkdir /var/tmp/efi
mount /dev/disk/by-partlabel/EFI-SYSTEM /var/tmp/efi

assert (open /var/tmp/efi/loader/loader.conf | lines | any {|s| $s == "timeout 1" })

find_root_eq_in_cmdline "systemd-boot"
}
}
Expand Down
2 changes: 1 addition & 1 deletion tmt/tests/booted/test-install-composefs-native.nu
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def install [image: string, ...args: string] {
let dropin = glob $"($MNT)/state/deploy/*/etc/tmpfiles.d/bootc-root-ssh.conf"
| append (glob $"($MNT)/ostree/deploy/*/deploy/*/etc/tmpfiles.d/bootc-root-ssh.conf")
| each {|p| {content: (open --raw $p | decode utf-8), mode: (stat -c %a $p | str trim)}}
umount $MNT
umount -l $MNT
rm -f $DISK
assert equal $dropin [{content: $"f~ /var/roothome/.ssh/authorized_keys 600 root root - ($KEY | encode base64)\n", mode: "644"}] "root ssh drop-in"
match [$composefs $ostree] {
Expand Down
10 changes: 5 additions & 5 deletions tmt/tests/booted/test-multi-device-esp.nu
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const ROOT_TYPE = "4F68BCE3-E8CD-4DB1-96E7-FBCAF984B709"
# Cleanup function for LVM and loop devices
def cleanup [vg_name: string, loops: list<string>, mountpoint: string] {
# Unmount if mounted
do { umount $mountpoint } | complete | ignore
do { umount -l $mountpoint } | complete | ignore
do { rmdir $mountpoint } | complete | ignore

# Deactivate and remove LVM
Expand Down Expand Up @@ -126,7 +126,7 @@ def setup_disk_with_root [

# Simple cleanup for non-LVM scenarios (single loop device, no VG)
def cleanup_simple [loop: string, mountpoint: string] {
do { umount $mountpoint } | complete | ignore
do { umount -l $mountpoint } | complete | ignore
do { rmdir $mountpoint } | complete | ignore

if ($loop | path exists) {
Expand All @@ -143,14 +143,14 @@ def validate_esp [esp_partition: string] {
# Check for EFI directory with bootloader files
let efi_dir = $"($esp_mount)/EFI"
if not ($efi_dir | path exists) {
umount $esp_mount
umount -l $esp_mount
rmdir $esp_mount
error make {msg: $"ESP validation failed: EFI directory not found on ($esp_partition)"}
}

# Verify there's actual content in EFI (not just empty)
let efi_contents = (ls $efi_dir | length)
umount $esp_mount
umount -l $esp_mount
rmdir $esp_mount

if $efi_contents == 0 {
Expand All @@ -164,7 +164,7 @@ def with_esp [esp_partition: string, f: closure] {
mkdir $esp_mount
mount $esp_partition $esp_mount
let r = (do $f $esp_mount)
umount $esp_mount
umount -l $esp_mount
rmdir $esp_mount
$r
}
Expand Down
Loading