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
20 changes: 16 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ jobs:
matrix:
# No fedora-44 due to https://bugzilla.redhat.com/show_bug.cgi?id=2429501
test_os: [fedora-43, centos-9, centos-10]
variant: [ostree, composefs-sealeduki-sdboot]
variant: [ostree, composefs-sealeduki-sdboot, composefs-sdboot, composefs-grub]
Copy link
Collaborator

Choose a reason for hiding this comment

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

Exciting!

exclude:
# centos-9 UKI is experimental/broken (https://github.com/bootc-dev/bootc/issues/1812)
- test_os: centos-9
Expand All @@ -178,7 +178,18 @@ jobs:
run: |
BASE=$(just pullspec-for-os base ${{ matrix.test_os }})
echo "BOOTC_base=${BASE}" >> $GITHUB_ENV
echo "BOOTC_variant=${{ matrix.variant }}" >> $GITHUB_ENV

case "${{ matrix.variant }}" in
composefs-grub|composefs-sdboot)
Comment on lines +182 to +183
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'd prefer we do this kind of thing in Justfile so it's easier to do locally.

echo "BOOTC_variant=composefs" >> $GITHUB_ENV
;;

*)
echo "BOOTC_variant=${{ matrix.variant }}" >> $GITHUB_ENV
;;
esac



if [ "${{ matrix.variant }}" = "composefs-sealeduki-sdboot" ]; then
BUILDROOTBASE=$(just pullspec-for-os buildroot-base ${{ matrix.test_os }})
Expand Down Expand Up @@ -207,11 +218,12 @@ jobs:

- name: Run TMT integration tests
run: |
if [ "${{ matrix.variant }}" = "composefs-sealeduki-sdboot" ]; then
just test-composefs
Comment on lines -210 to -211
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think at this point it'd be cleaner to have a mechainism to skip some tests (tmt already has metadata) so it's always just test-tmt integration and the variant detection does the right thing internally.

if [[ "${{ matrix.variant }}" = composefs* ]]; then
just "test-${{ matrix.variant }}"
else
just test-tmt integration
fi

just clean-local-images

- name: Archive TMT logs
Expand Down
7 changes: 5 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ WORKDIR /src
# We aren't using the full recommendations there, just the simple bits.
# First we download all of our Rust dependencies
# Note: Local path dependencies (from [patch] sections) are auto-detected and bind-mounted by the Justfile
RUN --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp --mount=type=cache,target=/src/target --mount=type=cache,target=/var/roothome cargo fetch
RUN --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp --mount=type=cache,target=/src/target --mount=type=cache,target=/var/roothome \
rm -rf /var/roothome/.cargo/registry; cargo fetch

# We always do a "from scratch" build
# https://docs.fedoraproject.org/en-US/bootc/building-from-scratch/
Expand Down Expand Up @@ -143,13 +144,15 @@ RUN --network=none --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp
# Perform all filesystem transformations except generating the sealed UKI (if configured)
FROM base as base-penultimate
ARG variant
# Switch to a signed systemd-boot, if configured
# Switch to systemd-boot (signed or unsigned), if configured
RUN --network=none --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp \
--mount=type=bind,from=packaging,src=/,target=/run/packaging \
--mount=type=bind,from=sdboot-signed,src=/,target=/run/sdboot-signed <<EORUN
set -xeuo pipefail
if test "${variant}" = "composefs-sealeduki-sdboot"; then
/run/packaging/switch-to-sdboot /run/sdboot-signed
elif test "${variant}" = "composefs"; then
/run/packaging/install-unsigned-sdboot
fi
EORUN
# Configure the rootfs
Expand Down
26 changes: 25 additions & 1 deletion Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,32 @@ test-container: build build-units

# Build and test sealed composefs images
[group('core')]
test-composefs:
test-composefs-sealeduki-sdboot:
just variant=composefs-sealeduki-sdboot test-tmt readonly local-upgrade-reboot

[group('core')]
test-composefs bootloader:
just variant=composefs test-tmt --composefs-backend --bootloader {{bootloader}} \
Copy link
Collaborator

Choose a reason for hiding this comment

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

Per above I think we can attach "supports composefs" as metadata to tests instead

readonly \
bib-build \
download-only \
image-pushpull-upgrade \
image-upgrade-reboot \
install-outside-container \
install-to-filesystem-var-mount \
soft-reboot \
usroverlay

# Build and test composefs images booted using Type1 boot entries and systemd-boot as the bootloader
[group('core')]
test-composefs-sdboot:
just test-composefs systemd

# Build and test composefs images booted using Type1 boot entries and grub as the bootloader
[group('core')]
test-composefs-grub:
just test-composefs grub

# Run cargo fmt and clippy checks in container
[group('core')]
validate:
Expand Down Expand Up @@ -220,6 +243,7 @@ clean-local-images:
podman image prune -f
podman rmi {{fedora-coreos}} -f


# Build packages (RPM) into target/packages/
[group('maintenance')]
package:
Expand Down
13 changes: 13 additions & 0 deletions contrib/packaging/install-unsigned-sdboot
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/bin/bash
# Install unsigned systemd-boot RPM (already downloaded by tools stage)
set -xeuo pipefail

# Uninstall bootupd if present (we're switching to sd-boot managed differently)
if rpm -q bootupd &>/dev/null; then
rpm -e bootupd
rm -vrf /usr/lib/bootupd/updates
fi

# Install the unsigned systemd-boot RPM that was downloaded by the tools stage
# The RPM is available in /run/sdboot-signed/out (copied from tools stage)
rpm -Uvh /run/sdboot-signed/out/*.rpm
1 change: 1 addition & 0 deletions crates/lib/src/bootc_composefs/boot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1248,6 +1248,7 @@ pub(crate) async fn setup_composefs_boot(
&state.config_opts,
None,
get_secureboot_keys(&mounted_fs, BOOTC_AUTOENROLL_PATH)?,
&mounted_fs,
)?;
}

Expand Down
70 changes: 69 additions & 1 deletion crates/lib/src/bootloader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,13 +109,14 @@ pub(crate) fn install_via_bootupd(
.run_inherited_with_cmd_context()
}

#[context("Installing bootloader")]
#[context("Installing systemd boot")]
pub(crate) fn install_systemd_boot(
device: &PartitionTable,
_rootfs: &Utf8Path,
_configopts: &crate::install::InstallConfigOpts,
_deployment_path: Option<&str>,
autoenroll: Option<SecurebootKeys>,
mounted_erofs: &Dir,
) -> Result<()> {
let esp_part = device
.find_partition_of_type(discoverable_partition_specification::ESP)
Expand All @@ -131,6 +132,73 @@ pub(crate) fn install_systemd_boot(
.log_debug()
.run_inherited_with_cmd_context()?;

// Check for systemd-boot binaries in the EFI/systemd directory
// systemd v258 won't copy the binary if an EFI booted system is not detected
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think we really want bootctl --force-if-non-efi-booted or so...did you file a bug report about this? We should chase down the change and find the rationale.

The logic here feels too manual. (I mean if we had to I'd argue we just fake it out by creating /sys/firmware/efi as mount point or so - or wahtever is needed to make bootctl work)

let systemd_dir = esp_mount.fd.open_dir_optional("EFI/systemd")?;

let systemd_boot_found = if let Some(dir) = systemd_dir {
let mut found = false;
for entry in dir.entries()? {
let entry = entry?;
let name = entry.file_name();

if let Some(name_str) = name.to_str() {
if name_str.starts_with("systemd-boot") && name_str.ends_with(".efi") {
found = true;
break;
}
}
}

found
} else {
false
};

if !systemd_boot_found {
println!("Copying systemd-boot binary manually");

// Find the systemd-boot binary in the source directory
let boot_dir = mounted_erofs.open_dir("usr/lib/systemd/boot/efi")?;
let mut systemd_boot_binary = None;

for entry in boot_dir.entries()? {
let entry = entry?;
let name = entry.file_name();
if let Some(name_str) = name.to_str() {
if name_str.starts_with("systemd-boot") && name_str.ends_with(".efi") {
systemd_boot_binary = Some(name_str.to_string());
break;
}
}
}

let binary_name = systemd_boot_binary
.ok_or_else(|| anyhow::anyhow!("No systemd-boot binary found in source"))?;

let src_path = format!("usr/lib/systemd/boot/efi/{}", binary_name);
let systemd_dest_path = format!("EFI/systemd/{}", binary_name);

// Determine the appropriate BOOT binary name based on architecture
let boot_binary_name = if binary_name.contains("x64") {
"BOOTX64.EFI"
} else if binary_name.contains("ia32") {
"BOOTIA32.EFI"
} else if binary_name.contains("aa64") {
"BOOTAA64.EFI"
} else {
"BOOTX64.EFI" // Default fallback
};

mounted_erofs.copy(&src_path, &esp_mount.fd, &systemd_dest_path)?;

mounted_erofs.copy(
&src_path,
&esp_mount.fd,
&format!("EFI/BOOT/{}", boot_binary_name),
)?;
}

if let Some(SecurebootKeys { dir, keys }) = autoenroll {
let path = esp_path.join(SYSTEMD_KEY_DIR);
create_dir_all(&path)?;
Expand Down
6 changes: 3 additions & 3 deletions crates/tests-integration/src/container.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ pub(crate) fn test_bootc_container_inspect() -> Result<()> {
.expect("kernel.unified should be a boolean");
if let Some(variant) = std::env::var("BOOTC_variant").ok() {
match variant.as_str() {
"ostree" => {
assert!(!unified, "Expected unified=false for ostree variant");
v @ "ostree" | v @ "composefs" => {
assert!(!unified, "Expected unified=false for variant {v}");
// For traditional kernels, version should look like a uname (contains digits)
assert!(
version.chars().any(|c| c.is_ascii_digit()),
Expand Down Expand Up @@ -159,7 +159,7 @@ fn test_variant_base_crosscheck() -> Result<()> {
// TODO add this to `bootc status` or so?
let boot_efi = Utf8Path::new("/boot/EFI");
match variant.as_str() {
"ostree" => {
"composefs" | "ostree" => {
assert!(!boot_efi.try_exists()?);
}
"composefs-sealeduki-sdboot" => {
Expand Down
13 changes: 13 additions & 0 deletions crates/xtask/src/tmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ const ENV_BOOTC_UPGRADE_IMAGE: &str = "BOOTC_upgrade_image";
// Distro identifiers
const DISTRO_CENTOS_9: &str = "centos-9";

const COMPOSEFS_KERNEL_ARGS: [&str; 1] = ["--karg=enforcing=0"];

// Import the argument types from xtask.rs
use crate::{RunTmtArgs, TmtProvisionArgs};

Expand Down Expand Up @@ -430,6 +432,17 @@ pub(crate) fn run_tmt(sh: &Shell, args: &RunTmtArgs) -> Result<()> {
opts.push("--filesystem=xfs".to_string());
}
}

if args.composefs_backend {
opts.push("--filesystem=ext4".into());
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think this shouldn't be hardcoded here...actually "filesystem choice" is a whole new dimension to the matrix in general.

We absolutely should be testing (and support!) e.g. "insecure" (fsverity disabled) composefs-rs storage too.

I think we don't even have a mechanism to specify that, but we should. I guess it could be part of the install config?

opts.push("--composefs-backend".into());
opts.extend(COMPOSEFS_KERNEL_ARGS.map(|x| x.into()));
}

if let Some(b) = &args.bootloader {
opts.push(format!("--bootloader={b}"));
}

opts
};

Expand Down
28 changes: 27 additions & 1 deletion crates/xtask/src/xtask.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@
//! end up as a lot of nontrivial bash code.

use std::borrow::Cow;
use std::fmt::Display;
use std::fs::File;
use std::io::{BufRead, BufReader, BufWriter, Write};
use std::process::Command;

use anyhow::{Context, Result};
use camino::{Utf8Path, Utf8PathBuf};
use clap::{Args, Parser, Subcommand};
use clap::{Args, Parser, Subcommand, ValueEnum};
use fn_error_context::context;
use xshell::{Shell, cmd};

Expand Down Expand Up @@ -76,6 +77,25 @@ pub(crate) struct LocalRustDepsArgs {
pub(crate) format: String,
}

/// Bootloader passed as --bootloader param for composefs builds
// TODO: Find a better way to share this Enum between this and crates/lib
#[derive(Debug, Clone, ValueEnum)]
pub enum Bootloader {
/// grub as bootloader
Grub,
/// systemd-boot as bootloader
Systemd,
}

impl Display for Bootloader {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Bootloader::Grub => f.write_str("grub"),
Bootloader::Systemd => f.write_str("systemd"),
}
}
}

/// Arguments for run-tmt command
#[derive(Debug, Args)]
pub(crate) struct RunTmtArgs {
Expand All @@ -101,6 +121,12 @@ pub(crate) struct RunTmtArgs {
/// Preserve VMs after test completion (useful for debugging)
#[arg(long)]
pub(crate) preserve_vm: bool,

#[arg(long)]
pub(crate) composefs_backend: bool,

#[arg(long, requires = "composefs_backend")]
pub(crate) bootloader: Option<Bootloader>,
}

/// Arguments for tmt-provision command
Expand Down
2 changes: 1 addition & 1 deletion tmt/tests/booted/readonly/001-test-status.nu
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ assert ($opts | any { |o| $o == "ro" }) "/sysroot should be mounted read-only"

let st = bootc status --json | from json
# Detect composefs by checking if composefs field is present
let is_composefs = ($st.status.booted.composefs? != null)
let is_composefs = (tap is_composefs)

assert equal $st.apiVersion org.containers.bootc/v1

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ tap begin "verify bootc-owned container storage"

# Detect composefs by checking if composefs field is present
let st = bootc status --json | from json
let is_composefs = ($st.status.booted.composefs? != null)
let is_composefs = (tap is_composefs)

if $is_composefs {
print "# TODO composefs: skipping test - /usr/lib/bootc/storage doesn't exist with composefs"
Expand Down
2 changes: 1 addition & 1 deletion tmt/tests/booted/readonly/011-test-ostree-ext-cli.nu
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ tap begin "verify bootc wrapping ostree-ext"
# Parse the status and get the booted image
let st = bootc status --json | from json
# Detect composefs by checking if composefs field is present
let is_composefs = ($st.status.booted.composefs? != null)
let is_composefs = (tap is_composefs)
if $is_composefs {
print "# TODO composefs: skipping test - ostree-container commands don't work with composefs"
} else {
Expand Down
2 changes: 1 addition & 1 deletion tmt/tests/booted/readonly/011-test-resolvconf.nu
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ tap begin "verify there's not an empty /etc/resolv.conf in the image"
let st = bootc status --json | from json

# Detect composefs by checking if composefs field is present
let is_composefs = ($st.status.booted.composefs? != null)
let is_composefs = (tap is_composefs)
if $is_composefs {
print "# TODO composefs: skipping test - ostree commands don't work with composefs"
} else {
Expand Down
2 changes: 1 addition & 1 deletion tmt/tests/booted/readonly/012-test-unit-status.nu
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ tap begin "verify our systemd units"

# Detect composefs by checking if composefs field is present
let st = bootc status --json | from json
let is_composefs = ($st.status.booted.composefs? != null)
let is_composefs = (tap is_composefs)

if $is_composefs {
print "# TODO composefs: skipping test - bootc-status-updated.path watches /ostree/bootc which doesn't exist with composefs"
Expand Down
2 changes: 1 addition & 1 deletion tmt/tests/booted/readonly/015-test-fsck.nu
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ tap begin "Run fsck"

# Detect composefs by checking if composefs field is present
let st = bootc status --json | from json
let is_composefs = ($st.status.booted.composefs? != null)
let is_composefs = (tap is_composefs)

if $is_composefs {
print "# TODO composefs: skipping test - fsck requires ostree-booted host"
Expand Down
2 changes: 1 addition & 1 deletion tmt/tests/booted/readonly/017-test-bound-storage.nu
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ if not (bootc_testlib have_hostexports) {

bootc status
let st = bootc status --json | from json
let is_composefs = ($st.status.booted.composefs? != null)
let is_composefs = (tap is_composefs)
if $is_composefs {
# TODO we don't have imageDigest yet in status
exit 0
Expand Down
2 changes: 1 addition & 1 deletion tmt/tests/booted/readonly/030-test-composefs.nu
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ def parse_cmdline [] {

# Detect composefs by checking if composefs field is present
let st = bootc status --json | from json
let is_composefs = ($st.status.booted.composefs? != null)
let is_composefs = (tap is_composefs)
let expecting_composefs = ($env.BOOTC_variant? | default "" | find "composefs") != null
if $expecting_composefs {
assert $is_composefs
Expand Down
Loading
Loading