-
Notifications
You must be signed in to change notification settings - Fork 171
feat: Add bootc container ukify command
#1960
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,15 +12,7 @@ shift | |
| secrets=$1 | ||
| shift | ||
|
|
||
| # Compute the composefs digest from the target rootfs | ||
| composefs_digest=$(bootc container compute-composefs-digest "${target}") | ||
|
|
||
| # Build the kernel command line | ||
| # enforcing=0: https://github.com/bootc-dev/bootc/issues/1826 | ||
| # TODO: pick up kargs from /usr/lib/bootc/kargs.d | ||
| cmdline="composefs=${composefs_digest} console=ttyS0,115200n8 console=hvc0 enforcing=0 rw" | ||
|
|
||
| # Find the kernel version | ||
| # Find the kernel version (needed for output filename) | ||
| kver=$(bootc container inspect --rootfs "${target}" --json | jq -r '.kernel.version') | ||
| if [ -z "$kver" ] || [ "$kver" = "null" ]; then | ||
| echo "Error: No kernel found" >&2 | ||
|
|
@@ -29,12 +21,14 @@ fi | |
|
|
||
| mkdir -p "${output}" | ||
|
|
||
| ukify build \ | ||
| --linux "${target}/usr/lib/modules/${kver}/vmlinuz" \ | ||
| --initrd "${target}/usr/lib/modules/${kver}/initramfs.img" \ | ||
| --uname="${kver}" \ | ||
| --cmdline "${cmdline}" \ | ||
| --os-release "@${target}/usr/lib/os-release" \ | ||
| # Build the UKI using bootc container ukify | ||
| # This computes the composefs digest, reads kargs from kargs.d, and invokes ukify | ||
| # | ||
| # WORKAROUND: SELinux must be permissive for sealed UKI boot | ||
| # See https://github.com/bootc-dev/bootc/issues/1826 | ||
| bootc container ukify --rootfs "${target}" \ | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This definitely looks nicer! |
||
| --karg enforcing=0 \ | ||
| -- \ | ||
| --signtool sbsign \ | ||
| --secureboot-private-key "${secrets}/secureboot_key" \ | ||
| --secureboot-certificate "${secrets}/secureboot_cert" \ | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| # Mount the root filesystem read-write | ||
| kargs = ["rw"] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,2 +1,3 @@ | ||
| # https://bugzilla.redhat.com/show_bug.cgi?id=2353887 | ||
| kargs = ["console=hvc0"] | ||
| # console=ttyS0 for QEMU serial, console=hvc0 for virtio/Xen console | ||
| kargs = ["console=ttyS0,115200n8", "console=hvc0"] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -392,6 +392,29 @@ pub(crate) enum ContainerOpts { | |
| /// Identifier for image; if not provided, the running image will be used. | ||
| image: Option<String>, | ||
| }, | ||
| /// Build a Unified Kernel Image (UKI) using ukify. | ||
| /// | ||
| /// This command computes the necessary arguments from the container image | ||
| /// (kernel, initrd, cmdline, os-release) and invokes ukify with them. | ||
| /// Any additional arguments after `--` are passed through to ukify unchanged. | ||
| /// | ||
| /// Example: | ||
| /// bootc container ukify --rootfs /target -- --output /output/uki.efi | ||
| Ukify { | ||
| /// Operate on the provided rootfs. | ||
| #[clap(long, default_value = "/")] | ||
| rootfs: Utf8PathBuf, | ||
|
|
||
| /// Additional kernel arguments to append to the cmdline. | ||
| /// Can be specified multiple times. | ||
| /// This is a temporary workaround and will be removed. | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We might as well make it That said maybe there are sufficient use cases for dynamically extending the kargs (apart from a "static" kargs.d that we could just support it, it doesn't seem too onerous).
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah but that starts to stray from the whole "the image is the definitive source of truth" expectation. I mean I guess in this case the image is still the source of truth, it's just that you'd have to introspect the UKI to see what the actual cmdline is (and I'm almost positive that it's not going to match what
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Yes 100% the kernel commandline is in the UKI which is part of the image.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Spun off #1965 to track surfacing possible divergence between kargs.d and UKI. |
||
| #[clap(long = "karg", hide = true)] | ||
| kargs: Vec<String>, | ||
|
|
||
| /// Additional arguments to pass to ukify (after `--`). | ||
| #[clap(last = true)] | ||
| args: Vec<OsString>, | ||
| }, | ||
| } | ||
|
|
||
| /// Subcommands which operate on images. | ||
|
|
@@ -1598,6 +1621,11 @@ async fn run_from_opt(opt: Opt) -> Result<()> { | |
|
|
||
| Ok(()) | ||
| } | ||
| ContainerOpts::Ukify { | ||
| rootfs, | ||
| kargs, | ||
| args, | ||
| } => crate::ukify::build_ukify(&rootfs, &kargs, &args), | ||
| }, | ||
| Opt::Completion { shell } => { | ||
| use clap_complete::aot::generate; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,6 +7,7 @@ | |
| use std::path::Path; | ||
|
|
||
| use anyhow::Result; | ||
| use camino::Utf8PathBuf; | ||
| use cap_std_ext::cap_std::fs::Dir; | ||
| use cap_std_ext::dirext::CapStdExtDirExt; | ||
| use serde::Serialize; | ||
|
|
@@ -25,35 +26,67 @@ pub(crate) struct Kernel { | |
| pub(crate) unified: bool, | ||
| } | ||
|
|
||
| /// Internal-only kernel wrapper with extra information (paths to | ||
| /// vmlinuz, initramfs) that are useful but we don't want to leak out | ||
| /// via serialization to inspection. | ||
| /// | ||
| /// `Kernel` implements `From<KernelInternal>` so we can just `.into()` | ||
| /// to get the "public" form where needed. | ||
| pub(crate) struct KernelInternal { | ||
| pub(crate) kernel: Kernel, | ||
| /// Path to vmlinuz for traditional kernels. | ||
| /// This is `None` for UKI images. | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How about making this an |
||
| pub(crate) vmlinuz: Option<Utf8PathBuf>, | ||
| /// Path to initramfs.img for traditional kernels. | ||
| /// This is `None` for UKI images. | ||
| pub(crate) initramfs: Option<Utf8PathBuf>, | ||
| } | ||
|
|
||
| impl From<KernelInternal> for Kernel { | ||
| fn from(kernel_internal: KernelInternal) -> Self { | ||
| kernel_internal.kernel | ||
| } | ||
| } | ||
|
|
||
| /// Find the kernel in a container image root directory. | ||
| /// | ||
| /// This function first attempts to find a UKI in `/boot/EFI/Linux/*.efi`. | ||
| /// If that doesn't exist, it falls back to looking for a traditional kernel | ||
| /// layout with `/usr/lib/modules/<version>/vmlinuz`. | ||
| /// | ||
| /// Returns `None` if no kernel is found. | ||
| pub(crate) fn find_kernel(root: &Dir) -> Result<Option<Kernel>> { | ||
| pub(crate) fn find_kernel(root: &Dir) -> Result<Option<KernelInternal>> { | ||
| // First, try to find a UKI | ||
| if let Some(uki_filename) = find_uki_filename(root)? { | ||
| let version = uki_filename | ||
| .strip_suffix(".efi") | ||
| .unwrap_or(&uki_filename) | ||
| .to_owned(); | ||
| return Ok(Some(Kernel { | ||
| version, | ||
| unified: true, | ||
| return Ok(Some(KernelInternal { | ||
| kernel: Kernel { | ||
| version, | ||
| unified: true, | ||
| }, | ||
| vmlinuz: None, | ||
| initramfs: None, | ||
| })); | ||
| } | ||
|
|
||
| // Fall back to checking for a traditional kernel via ostree_ext | ||
| if let Some(kernel_dir) = ostree_ext::bootabletree::find_kernel_dir_fs(root)? { | ||
| let version = kernel_dir | ||
| if let Some(modules_dir) = ostree_ext::bootabletree::find_kernel_dir_fs(root)? { | ||
| let version = modules_dir | ||
| .file_name() | ||
| .ok_or_else(|| anyhow::anyhow!("kernel dir should have a file name: {kernel_dir}"))? | ||
| .ok_or_else(|| anyhow::anyhow!("kernel dir should have a file name: {modules_dir}"))? | ||
| .to_owned(); | ||
| return Ok(Some(Kernel { | ||
| version, | ||
| unified: false, | ||
| let vmlinuz = modules_dir.join("vmlinuz"); | ||
| let initramfs = modules_dir.join("initramfs.img"); | ||
| return Ok(Some(KernelInternal { | ||
| kernel: Kernel { | ||
| version, | ||
| unified: false, | ||
| }, | ||
| vmlinuz: Some(vmlinuz), | ||
| initramfs: Some(initramfs), | ||
| })); | ||
| } | ||
|
|
||
|
|
@@ -93,6 +126,7 @@ fn find_uki_filename(root: &Dir) -> Result<Option<String>> { | |
| #[cfg(test)] | ||
| mod tests { | ||
| use super::*; | ||
| use camino::Utf8Path; | ||
| use cap_std_ext::{cap_std, cap_tempfile, dirext::CapStdExtDirExt}; | ||
|
|
||
| #[test] | ||
|
|
@@ -111,9 +145,21 @@ mod tests { | |
| b"fake kernel", | ||
| )?; | ||
|
|
||
| let kernel = find_kernel(&tempdir)?.expect("should find kernel"); | ||
| assert_eq!(kernel.version, "6.12.0-100.fc41.x86_64"); | ||
| assert!(!kernel.unified); | ||
| let kernel_internal = find_kernel(&tempdir)?.expect("should find kernel"); | ||
| assert_eq!(kernel_internal.kernel.version, "6.12.0-100.fc41.x86_64"); | ||
| assert!(!kernel_internal.kernel.unified); | ||
| assert_eq!( | ||
| kernel_internal.vmlinuz.as_deref(), | ||
| Some(Utf8Path::new( | ||
| "usr/lib/modules/6.12.0-100.fc41.x86_64/vmlinuz" | ||
| )) | ||
| ); | ||
| assert_eq!( | ||
| kernel_internal.initramfs.as_deref(), | ||
| Some(Utf8Path::new( | ||
| "usr/lib/modules/6.12.0-100.fc41.x86_64/initramfs.img" | ||
| )) | ||
| ); | ||
| Ok(()) | ||
| } | ||
|
|
||
|
|
@@ -123,9 +169,11 @@ mod tests { | |
| tempdir.create_dir_all("boot/EFI/Linux")?; | ||
| tempdir.atomic_write("boot/EFI/Linux/fedora-6.12.0.efi", b"fake uki")?; | ||
|
|
||
| let kernel = find_kernel(&tempdir)?.expect("should find kernel"); | ||
| assert_eq!(kernel.version, "fedora-6.12.0"); | ||
| assert!(kernel.unified); | ||
| let kernel_internal = find_kernel(&tempdir)?.expect("should find kernel"); | ||
| assert_eq!(kernel_internal.kernel.version, "fedora-6.12.0"); | ||
| assert!(kernel_internal.kernel.unified); | ||
| assert!(kernel_internal.vmlinuz.is_none()); | ||
| assert!(kernel_internal.initramfs.is_none()); | ||
| Ok(()) | ||
| } | ||
|
|
||
|
|
@@ -141,10 +189,10 @@ mod tests { | |
| tempdir.create_dir_all("boot/EFI/Linux")?; | ||
| tempdir.atomic_write("boot/EFI/Linux/fedora-6.12.0.efi", b"fake uki")?; | ||
|
|
||
| let kernel = find_kernel(&tempdir)?.expect("should find kernel"); | ||
| let kernel_internal = find_kernel(&tempdir)?.expect("should find kernel"); | ||
| // UKI should take precedence | ||
| assert_eq!(kernel.version, "fedora-6.12.0"); | ||
| assert!(kernel.unified); | ||
| assert_eq!(kernel_internal.kernel.version, "fedora-6.12.0"); | ||
| assert!(kernel_internal.kernel.unified); | ||
| Ok(()) | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -93,6 +93,7 @@ pub mod spec; | |
| mod status; | ||
| mod store; | ||
| mod task; | ||
| mod ukify; | ||
| mod utils; | ||
|
|
||
| #[cfg(feature = "docgen")] | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Super minor can be followup, imo a nicer way to do this would be like
or so then we can associate clearly comments with args.