Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
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
1 change: 0 additions & 1 deletion .cci.jenkinsfile
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ parallel build: {
}
stage("Unit tests") {
shwrap("""
dnf install -y grub2-tools-minimal
cargo test --features rpm
cargo test
""")
Expand Down
55 changes: 47 additions & 8 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,33 @@ env:
CARGO_TERM_COLOR: always

jobs:
c9s-bootc-e2e:
bootc-e2e:
strategy:
matrix:
runner:
- ubuntu-24.04
- ubuntu-24.04-arm
grubcc:
- 0
- 1

runs-on: [ "${{ matrix.runner }}" ]

steps:
- name: Setup env
run: |
IMG_NAME=localhost/bootupd-grubcc-${{ matrix.grubcc }}
echo "IMG_NAME=${IMG_NAME}" >> $GITHUB_ENV

if [[ "${{ matrix.grubcc }}" == "1" ]]; then
# we only have grub-cc in feodra (for now)
OS_ID="fedora"
else
OS_ID="centos"
fi

echo "OS_ID=${OS_ID}" >> $GITHUB_ENV

- name: Get a newer podman for heredoc support (from debian testing)
run: |
set -eux
Expand All @@ -51,22 +68,34 @@ jobs:
sudo apt update -y
sudo apt install -y podman

- name: build
run: sudo podman build -t localhost/bootupd:latest -f Dockerfile .
- name: build with grubcc=${{ matrix.grubcc }}
run: |
dockerfile=Dockerfile
build_args=()

if [[ "${{ matrix.grubcc }}" == "1" ]]; then
dockerfile=Dockerfile.bls
build_args=(--build-arg "grubcc=${{ matrix.grubcc }}")
fi

sudo podman build "${build_args[@]}" . -t "$IMG_NAME" -f "$dockerfile"

- name: bootupctl status in container
run: |
set -xeuo pipefail
sudo podman run --rm -v $PWD:/run/src -w /run/src --privileged localhost/bootupd:latest tests/tests/bootupctl-status-in-bootc.sh
sudo podman run --rm -v $PWD:/run/src -w /run/src --privileged "$IMG_NAME" tests/tests/bootupctl-status-in-bootc.sh

# Make sure grub stuff works with and without grubcc in the container image
# TODO: Test GrubCC installation with composefs after a bootc release includes
# https://github.com/bootc-dev/bootc/pull/2223
- name: bootc install to disk
run: |
set -xeuo pipefail
sudo truncate -s 10G myimage.raw
sudo podman run --rm --privileged -v .:/target --pid=host --security-opt label=disable \
-v /var/lib/containers:/var/lib/containers \
-v /dev:/dev \
localhost/bootupd:latest bootc install to-disk --skip-fetch-check \
"$IMG_NAME" bootc install to-disk --filesystem ext4 --skip-fetch-check \
--disable-selinux --generic-image --via-loopback /target/myimage.raw
# Verify we installed grub.cfg and shim on the disk
sudo losetup -P -f myimage.raw
Expand All @@ -80,7 +109,7 @@ jobs:
# Assume aarch64 for now
shim="shimaa64.efi"
fi
sudo ls /mnt/EFI/centos/{grub.cfg,${shim}}
sudo ls /mnt/EFI/$OS_ID/{grub.cfg,${shim}}
sudo umount /mnt
# check /boot/grub2/grub.cfg permission
root_part=$(sudo sfdisk -l -J "${device}" | jq -r '.partitiontable.partitions[] | select(.name == "root").node')
Expand All @@ -99,7 +128,7 @@ jobs:
set -xeuo pipefail
sudo podman run --rm -ti --privileged -v /:/target --pid=host --security-opt label=disable \
-v /dev:/dev -v /var/lib/containers:/var/lib/containers \
localhost/bootupd:latest env BOOTC_BOOTLOADER_DEBUG=1 \
"$IMG_NAME" env BOOTC_BOOTLOADER_DEBUG=1 \
bootc install to-filesystem --skip-fetch-check \
--acknowledge-destructive \
--disable-selinux --replace=alongside /target
Expand All @@ -113,4 +142,14 @@ jobs:
- name: bootupctl generate-update-metadata
run: |
set -xeuo pipefail
sudo podman run --rm -v $PWD:/run/src -w /run/src --privileged localhost/bootupd:latest tests/tests/generate-update-metadata.sh
sudo podman run --rm -v $PWD:/run/src -w /run/src --privileged "$IMG_NAME" tests/tests/generate-update-metadata.sh

- name: Test GrubCC and SystemdBoot
run: |
if [[ "${{ matrix.grubcc }}" == "0" ]]; then
exit 0
fi

set -xeuo pipefail
sudo ./scripts/test-bootloader.sh "$IMG_NAME" "grub-cc"
sudo ./scripts/test-bootloader.sh "$IMG_NAME" "systemd"
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ path = "src/main.rs"
anyhow = "1.0"
bootc-internal-blockdev = "1.16.0"
bootc-internal-utils = "1.16.0"
bootc-internal-mount = "1.16.0"
cap-std-ext = "5.0.0"
camino = "1.2.2"
chrono = { version = "0.4.44", features = ["serde"] }
Expand Down
125 changes: 125 additions & 0 deletions Dockerfile.bls
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# Dockerfile to test GrubCC and systemd-boot
# Build from the current git into a fedora44 container image.

ARG base=quay.io/fedora/fedora-bootc:44

FROM $base as build
# This installs our package dependencies, and we want to cache it independently of the rest.
# Basically we don't want changing a .rs file to blow out the cache of packages.
RUN <<EORUN
set -xeuo pipefail
dnf -y install cargo git openssl-devel
EORUN
# Now copy the source
COPY . /build
WORKDIR /build
# See https://www.reddit.com/r/rust/comments/126xeyx/exploring_the_problem_of_faster_cargo_docker/
# We aren't using the full recommendations there, just the simple bits.
RUN --mount=type=cache,target=/build/target --mount=type=cache,target=/var/roothome \
make && make install-all DESTDIR=/out


# Get the latest GrubCC binary
FROM quay.io/fedora/eln:latest AS grub-cc-download
RUN --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp <<EOF
set -eux

# There isn't a generic grub2-efi-cc package that DNF can automatically resolve
case "$(uname -m)" in
x86_64)
dnf download grub2-efi-x64-cc
;;
aarch64)
dnf download grub2-efi-aa64-cc
;;
*)
echo "Unsupported architecture: $(uname -m)" >&2
exit 1
;;
esac

mv ./*.rpm grub-cc.rpm

EOF

FROM $base
# Clean out the default to ensure we're using our updated content
RUN rpm -e bootupd
COPY --from=build /out/ /
COPY --from=grub-cc-download /grub-cc.rpm /var/grub-cc.rpm
# Install bootc from copr
RUN --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp <<EORUN
set -xeuo pipefail
dnf -y install dnf-plugins-core bootc systemd-boot tree
dnf clean all
rm -rf /var/log
rm -rf /var/lib
rm -rf /var/cache
rm -rf /run/rhsm
rm -rf /tmp/*
EORUN

# Install systemd-boot
RUN --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp <<EOF
set -eux
# epoch for systemd-boot-unsigned is "(none)"
evr=$(rpm -q --qf '%{EPOCH}:%{VERSION}-%{RELEASE}\n' systemd-boot-unsigned)
rpm -ql systemd-boot-unsigned

. /etc/os-release

# Create the expected directory structure at /usr/lib/efi/systemd-boot/<evr>/EFI/fedora/
mkdir -p "/usr/lib/efi/systemd-boot/${evr}/EFI/$ID"

if [[ $(uname -m) == x86_64 ]]; then
grubName="grubx64.efi"
else
grubName="grubaa64.efi"
fi

cp /usr/lib/systemd/boot/efi/systemd-boot*.efi "/usr/lib/efi/systemd-boot/${evr}/EFI/$ID/$grubName"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Hmmm, is this really required? I guess it must be - it's us taking over bootctl install and bootctl update.

That feels...unnecessary?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Now that I spend a bit of time on this, is this case actually "install systemd-boot pretending to be grub" so it gets chainloaded by shim? That looks like what this is.

That feels like a clearly distinct case of things here and really worth clearly documenting.

OK right, and we're doing the same with grub-cc?

Hmmm, I guess it's kind of an assumption woven into things here that we're using shim, which is probably the right one by default, but I think we do ultimately need to handle non-shim cases too.

I guess this isn't blocking per se, but we should have a note about this.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Now that I spend a bit of time on this, is this case actually "install systemd-boot pretending to be grub" so it gets chainloaded by shim? That looks like what this is.

yes, that's what it is. We will probably hardlink the actual systemd-boot binary at /usr/lib/efi/systemd-boot/** so that all bootloader live in the same place

OK right, and we're doing the same with grub-cc?

yes

but I think we do ultimately need to handle non-shim cases too

We do. On a call with @travier we decided to handle that case later

@travier travier Jul 9, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The name of the EFI binary loaded by shim is hardcoded in shim: https://src.fedoraproject.org/rpms/shim-unsigned-x64/blob/rawhide/f/shim-unsigned-x64.spec#_154

There are options to change it but they have a lot of downsides: #1080 (comment)

Pretending to be grub$arch.efi is apparently the simplest option.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We settled (at least for now) on leaving standalone systemd-boot (or potentially standalone grub-cc) support to systemd's bootctl as we don't need all the complexity from bootupd for those cases: #1080 (comment)


EOF

# Install grub-cc
RUN --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp <<EOF

set -eux

cleanup() {
# Clean up temporary files
rm -rvf /var/grub-cc*
}

# Extract and install grub-cc at the correct path
mkdir /var/grub-cc
rpm2archive /var/grub-cc.rpm | tar -xvz -C /var/grub-cc
file=$(find /var/grub-cc -name '*.efi')

# Get the EVR from the RPM filename
evr=$(rpm -qp --qf '%{EPOCH}:%{VERSION}-%{RELEASE}' /var/grub-cc.rpm)

. /etc/os-release

if [[ $(uname -m) == x86_64 ]]; then
grubName="grubx64.efi"
else
grubName="grubaa64.efi"
fi

# Create the expected directory structure at /usr/lib/efi/grub-cc/<evr>/EFI/fedora/
mkdir -p "/usr/lib/efi/grub-cc/${evr}/EFI/$ID/"
cp "$file" "/usr/lib/efi/grub-cc/${evr}/EFI/$ID/$grubName"

cleanup

# Regenerate metadata
bootupctl backend generate-update-metadata -vvv

# Set grub as default
bootupctl backend set-default-bootloader grub

EOF

# Sanity check this too
RUN bootc container lint --fatal-warnings
11 changes: 11 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
fn main() {
println!("cargo::rustc-check-cfg=cfg(efi_arch)");

if cfg!(any(
target_arch = "x86_64",
target_arch = "aarch64",
target_arch = "riscv64"
)) {
println!("cargo:rustc-cfg=efi_arch");
}
}
92 changes: 92 additions & 0 deletions scripts/test-bootloader.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
#!/bin/bash

set -eux

IMG_NAME=$1
BOOTLOADER=$2

case $BOOTLOADER in
systemd)
EFI_DIR_NAME=systemd-boot
;;
grub-cc)
EFI_DIR_NAME=grub-cc
;;
grub)
EFI_DIR_NAME=grub2
;;
Comment thread
Johan-Liebert1 marked this conversation as resolved.
*)
echo "Unknown bootloader $BOOTLOADER"
exit 1
;;
esac

cat <<-EOF > sfdisk-buf
label: gpt
label-id: 65be9332-59ba-11f1-9b26-6a8e2ab625e4
size=1Gib, type=C12A7328-F81F-11D2-BA4B-00A0C93EC93B, name="EFI-SYSTEM"
type=4F68BCE3-E8CD-4DB1-96E7-FBCAF984B709, name="root"
EOF

truncate -s4G "${BOOTLOADER}-test.img"

cat sfdisk-buf | sfdisk --wipe=always "${BOOTLOADER}-test.img"

mkdir -p /var/mnt

# Also update kernel partition tables
loopdev=$(losetup --find --show --partscan "${BOOTLOADER}-test.img")
sleep 1

mkfs.vfat "${loopdev}p1"
mkfs.ext4 "${loopdev}p2"

mount "${loopdev}p2" /var/mnt

ESP="/var/mnt/efi"

mkdir -p $ESP
mount "${loopdev}p1" $ESP


# Test installing the bootloader
podman run --rm --net=host --privileged --pid=host \
--privileged \
--security-opt label=type:unconfined_t \
--env RUST_LOG=trace \
-v /dev:/dev \
-v /var/mnt:/var/mnt \
"$IMG_NAME" \
bootupctl backend install --bootloader "$BOOTLOADER" /var/mnt -vvvv

# Make sure bootupd-state.json is in the esp
test -f "$ESP/bootupd-state.json"

cat "$ESP/bootupd-state.json" | jq

version=$(cat "$ESP/bootupd-state.json" | jq -r ".installed.EFI.meta.version")

if [[ $version != *shim* ]]; then echo "shim not found in version"; exit 1; fi
if [[ $version != *"$EFI_DIR_NAME"* ]]; then echo "$BOOTLOADER not found in version"; exit 1; fi

# Test if the correct binary has been installed
actualShasum=$(podman run --rm "$IMG_NAME" find "/usr/lib/efi/$EFI_DIR_NAME" -type f -exec sha512sum {} + | awk '{print $1}')
actualShasum="sha512:$actualShasum"

if [[ $(uname -m) == "x86_64" ]]; then
grubName="grubx64.efi"
else
grubName="grubaa64.efi"
fi

# TODO: Remove hardcoded "fedora" once we have support in centos
storedShasum=$(cat "$ESP/bootupd-state.json" | jq -r --arg grub "$grubName" '.installed.EFI.filetree.children["fedora/\($grub)"].sha512')

test "$actualShasum" == "$storedShasum"

efiBinShasum=$(find "$ESP" -type f -name "$grubName" -exec sha512sum {} + | awk '{print $1}')
efiBinShasum="sha512:$efiBinShasum"

test "$efiBinShasum" == "$actualShasum"

umount -Rl /var/mnt
Loading