From a8efeeaecce103e6b6d8b3430396c06581835549 Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Wed, 23 Sep 2026 06:49:26 -0400 Subject: [PATCH 1/5] lints: Don't require ostree bits on composefs-native images The baseimage-root lint insists on an /ostree -> sysroot/ostree symlink, and baseimage-composefs warns unless ostree's prepare-root.conf enables composefs. Both are meaningless for images that are only ever deployed with the composefs backend, and just force them to carry ostree cruft. Use the presence of /usr/lib/composefs/setup-root-conf.toml (even if empty) as the signal that an image is composefs-native, mirroring how prepare-root.conf signals ostree. If /ostree is present anyway it is still validated, and /sysroot is still required since both backends mount the physical root there. Closes: #2256 Generated-by: AI Signed-off-by: Colin Walters --- crates/lib/src/lints.rs | 51 ++++++++++++++++++++++++- docs/src/bootc-compatible-images.7.md | 6 ++- docs/src/man/bootc-setup-root-conf.5.md | 5 +++ 3 files changed, 59 insertions(+), 3 deletions(-) diff --git a/crates/lib/src/lints.rs b/crates/lib/src/lints.rs index aa41e9cfd4..fb0b2f73e2 100644 --- a/crates/lib/src/lints.rs +++ b/crates/lib/src/lints.rs @@ -573,12 +573,17 @@ fn check_api_dirs(root: &Dir, _config: &LintExecutionConfig) -> LintResult { static LINT_COMPOSEFS: Lint = Lint::new_warning( "baseimage-composefs", indoc! { r#" -Check that composefs is enabled for ostree. More in +Check that composefs is enabled for ostree. Skipped for composefs-native +images, i.e. those that ship /usr/lib/composefs/setup-root-conf.toml. More in . "#}, check_composefs, ); fn check_composefs(dir: &Dir, _config: &LintExecutionConfig) -> LintResult { + // ostree's prepare-root.conf is irrelevant for composefs-native images. + if is_composefs_native(dir)? { + return lint_ok(); + } if let Err(e) = check_prepareroot_composefs_norecurse(dir)? { return Ok(Err(e)); } @@ -592,6 +597,20 @@ fn check_composefs(dir: &Dir, _config: &LintExecutionConfig) -> LintResult { lint_ok() } +/// The setup-root configuration, relative to the root directory. +fn setup_root_conf_path() -> &'static str { + bootc_initramfs_setup::SETUP_ROOT_CONF_PATH.trim_start_matches('/') +} + +/// Whether the image is intended to be deployed only with the composefs +/// backend, which is signaled by the presence of a setup-root configuration +/// file (even if empty). +fn is_composefs_native(root: &Dir) -> Result { + Ok(root + .symlink_metadata_optional(setup_root_conf_path())? + .is_some()) +} + /// Check for a few files and directories we expect in the base image. fn check_baseimage_root_norecurse(dir: &Dir, _config: &LintExecutionConfig) -> LintResult { // Check /sysroot @@ -604,6 +623,10 @@ fn check_baseimage_root_norecurse(dir: &Dir, _config: &LintExecutionConfig) -> L // Check /ostree -> sysroot/ostree let Some(meta) = dir.symlink_metadata_optional("ostree")? else { + // Composefs-native images don't use ostree, so they don't need it. + if is_composefs_native(dir)? { + return lint_ok(); + } return lint_err("Missing ostree -> sysroot/ostree link"); }; if !meta.is_symlink() { @@ -624,7 +647,9 @@ static LINT_BASEIMAGE_ROOT: Lint = Lint::new_fatal( "baseimage-root", indoc! { r#" Check that expected files are present in the root of the filesystem; such -as /sysroot and a composefs configuration for ostree. More in +as /sysroot and a composefs configuration for ostree. The /ostree symlink +is not required for composefs-native images, i.e. those that ship +/usr/lib/composefs/setup-root-conf.toml. More in . "#}, check_baseimage_root, @@ -1365,6 +1390,21 @@ mod tests { drop(td); let td = passing_fixture()?; check_baseimage_root(&td, config).unwrap().unwrap(); + + // Composefs-native images don't need /ostree... + td.remove_file("ostree")?; + assert!(check_baseimage_root(&td, config).unwrap().is_err()); + let conf = Utf8Path::new(setup_root_conf_path()); + td.create_dir_all(conf.parent().unwrap())?; + td.write(conf, "")?; + check_baseimage_root(&td, config).unwrap().unwrap(); + // ...but if they have it, it must still be correct + td.create_dir("ostree")?; + assert!(check_baseimage_root(&td, config).unwrap().is_err()); + td.remove_dir("ostree")?; + // ...and they still need /sysroot + td.remove_dir("sysroot")?; + assert!(check_baseimage_root(&td, config).unwrap().is_err()); Ok(()) } @@ -1388,6 +1428,13 @@ mod tests { // Now it should fail because composefs is explicitly disabled. assert!(check_composefs(&td, config).unwrap().is_err()); + // ...unless the image is composefs-native, where ostree's + // configuration doesn't matter. + let conf = Utf8Path::new(setup_root_conf_path()); + td.create_dir_all(conf.parent().unwrap())?; + td.write(conf, "")?; + check_composefs(&td, config).unwrap().unwrap(); + Ok(()) } diff --git a/docs/src/bootc-compatible-images.7.md b/docs/src/bootc-compatible-images.7.md index b98705df46..64ebf04165 100644 --- a/docs/src/bootc-compatible-images.7.md +++ b/docs/src/bootc-compatible-images.7.md @@ -27,7 +27,11 @@ For the composefs backend, the UKI must be located at `/boot/EFI/Linux/$kver.efi ### /ostree symlink and `bootc container lint` -This is [a bug](https://github.com/bootc-dev/bootc/issues/2256): currently a `/ostree -> /sysroot/ostree` symlink is required just for `bootc container lint` to pass, even though it's not required for `/sysroot/ostree` to exist. +`bootc container lint` requires a `/ostree -> sysroot/ostree` symlink, +unless the image is composefs-native, which is signaled by the presence of +`/usr/lib/composefs/setup-root-conf.toml` (it may be empty); see +[bootc-setup-root-conf.toml(5)](man/bootc-setup-root-conf.5.md). Such +images also don't need ostree's `prepare-root.conf` to enable composefs. ## composefs backend diff --git a/docs/src/man/bootc-setup-root-conf.5.md b/docs/src/man/bootc-setup-root-conf.5.md index 3e7082f06b..08d6f405c9 100644 --- a/docs/src/man/bootc-setup-root-conf.5.md +++ b/docs/src/man/bootc-setup-root-conf.5.md @@ -15,6 +15,11 @@ mounted. If the file does not exist all options take their documented defaults. +The presence of this file (even if empty) also marks the image as +composefs-native; `bootc container lint` then no longer requires the +`/ostree` symlink or the ostree `prepare-root.conf` composefs configuration +used by the ostree backend. + The `51bootc` dracut module installs this file into the initramfs automatically when it is present on the host image. Image authors can therefore ship the file at this path in their container image and rebuild the initramfs with a From 35ff66eaf7d055659e0e1b2327cd0ad60ad68244 Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Fri, 25 Sep 2026 16:04:18 -0400 Subject: [PATCH 2/5] tests-integration: Fix race between the install config tests "printconfig --all" writes /run/bootc/install/10-test.toml and deletes it again, while "install config" runs `bootc install print-configuration` in parallel. When the drop-in vanishes between load_config()'s directory scan and its read, the latter fails with "Loading configuration: No such file or directory". This flakes the container integration tests on random PRs and variants. Merge the two into one test that checks both outputs with the drop-in in place. Generated-by: AI Signed-off-by: Colin Walters --- crates/tests-integration/src/container.rs | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/crates/tests-integration/src/container.rs b/crates/tests-integration/src/container.rs index 08716f475a..a5fa4fcf03 100644 --- a/crates/tests-integration/src/container.rs +++ b/crates/tests-integration/src/container.rs @@ -114,17 +114,11 @@ pub(crate) fn test_bootc_upgrade() -> Result<()> { Ok(()) } +/// This is a single test because it adds a drop-in to /run/bootc/install, +/// which would race with any other test loading the install configuration: +/// libtest runs tests in parallel, and a file that vanishes between the +/// directory scan and the read fails `print-configuration`. pub(crate) fn test_bootc_install_config() -> Result<()> { - let sh = &xshell::Shell::new()?; - let config = cmd!(sh, "bootc install print-configuration").read()?; - let config: serde_json::Value = - serde_json::from_str(&config).context("Parsing install config")?; - // check that it parses okay, but also ensure kargs is not available here (only via --all) - assert!(config.get("kargs").is_none()); - Ok(()) -} - -pub(crate) fn test_bootc_install_config_all() -> Result<()> { #[derive(Deserialize)] #[serde(rename_all = "kebab-case")] struct TestOstreeConfig { @@ -152,6 +146,12 @@ pub(crate) fn test_bootc_install_config_all() -> Result<()> { } let sh = &xshell::Shell::new()?; + let config = cmd!(sh, "bootc install print-configuration").read()?; + let config: serde_json::Value = + serde_json::from_str(&config).context("Parsing install config")?; + // check that it parses okay, but also ensure kargs is not available here (only via --all) + assert!(config.get("kargs").is_none()); + let config = cmd!(sh, "bootc install print-configuration --all").read()?; let config: TestInstallConfig = serde_json::from_str(&config).context("Parsing install config")?; @@ -531,7 +531,6 @@ pub(crate) fn run(testargs: libtest_mimic::Arguments) -> Result<()> { new_test("variant-base-crosscheck", test_variant_base_crosscheck), new_test("bootc upgrade", test_bootc_upgrade), new_test("install config", test_bootc_install_config), - new_test("printconfig --all", test_bootc_install_config_all), new_test("status", test_bootc_status), new_test("container inspect", test_bootc_container_inspect), new_test("system-reinstall --help", test_system_reinstall_help), From ad0ad2503ee0a86585af76dde0919ebb04e8e114 Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Tue, 29 Sep 2026 13:11:13 -0400 Subject: [PATCH 3/5] lints: Move composefs-native detection next to the composefs code Prep for bootc install using the same signal to pick its default backend. Generated-by: AI --- crates/lib/src/bootc_composefs/image.rs | 19 +++++++++++++++++++ crates/lib/src/bootc_composefs/mod.rs | 1 + crates/lib/src/lints.rs | 16 ++-------------- 3 files changed, 22 insertions(+), 14 deletions(-) create mode 100644 crates/lib/src/bootc_composefs/image.rs diff --git a/crates/lib/src/bootc_composefs/image.rs b/crates/lib/src/bootc_composefs/image.rs new file mode 100644 index 0000000000..2c7f3ac3ac --- /dev/null +++ b/crates/lib/src/bootc_composefs/image.rs @@ -0,0 +1,19 @@ +//! Detecting which backend a container image's root filesystem is built for. + +use anyhow::Result; +use cap_std_ext::cap_std::fs::Dir; +use cap_std_ext::dirext::CapStdExtDirExt as _; + +/// The setup-root configuration, relative to the root directory. +pub(crate) fn setup_root_conf_path() -> &'static str { + bootc_initramfs_setup::SETUP_ROOT_CONF_PATH.trim_start_matches('/') +} + +/// Whether the image is intended to be deployed with the composefs +/// backend, which is signaled by the presence of a setup-root configuration +/// file (even if empty). +pub(crate) fn is_composefs_native(root: &Dir) -> Result { + Ok(root + .symlink_metadata_optional(setup_root_conf_path())? + .is_some()) +} diff --git a/crates/lib/src/bootc_composefs/mod.rs b/crates/lib/src/bootc_composefs/mod.rs index 42d521150a..fe5bc9c6a5 100644 --- a/crates/lib/src/bootc_composefs/mod.rs +++ b/crates/lib/src/bootc_composefs/mod.rs @@ -5,6 +5,7 @@ pub(crate) mod digest; pub(crate) mod export; pub(crate) mod finalize; pub(crate) mod gc; +pub(crate) mod image; pub(crate) mod progress; pub(crate) mod repo; pub(crate) mod rollback; diff --git a/crates/lib/src/lints.rs b/crates/lib/src/lints.rs index fb0b2f73e2..b8df11a8ee 100644 --- a/crates/lib/src/lints.rs +++ b/crates/lib/src/lints.rs @@ -28,6 +28,7 @@ use ostree_ext::ostree_prepareroot; use serde::Serialize; use crate::bootc_composefs::boot::EFI_LINUX; +use crate::bootc_composefs::image::is_composefs_native; /// Create a default WalkConfiguration with noxdev enabled. /// @@ -597,20 +598,6 @@ fn check_composefs(dir: &Dir, _config: &LintExecutionConfig) -> LintResult { lint_ok() } -/// The setup-root configuration, relative to the root directory. -fn setup_root_conf_path() -> &'static str { - bootc_initramfs_setup::SETUP_ROOT_CONF_PATH.trim_start_matches('/') -} - -/// Whether the image is intended to be deployed only with the composefs -/// backend, which is signaled by the presence of a setup-root configuration -/// file (even if empty). -fn is_composefs_native(root: &Dir) -> Result { - Ok(root - .symlink_metadata_optional(setup_root_conf_path())? - .is_some()) -} - /// Check for a few files and directories we expect in the base image. fn check_baseimage_root_norecurse(dir: &Dir, _config: &LintExecutionConfig) -> LintResult { // Check /sysroot @@ -974,6 +961,7 @@ mod tests { use std::sync::LazyLock; use super::*; + use crate::bootc_composefs::image::setup_root_conf_path; static ALTROOT_LINTS: LazyLock = LazyLock::new(|| { LINTS From 3f6a639c54abe918b29f966d6a9d93b19176cdaa Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Tue, 29 Sep 2026 13:11:13 -0400 Subject: [PATCH 4/5] install: Default to the composefs backend for composefs-only images Today only a UKI selects the composefs backend automatically. An image built for composefs with a traditional kernel and initramfs (BLS) is installed with ostree unless every caller passes --composefs-backend, which bootc-image-builder and Anaconda don't. The lints already treat /usr/lib/composefs/setup-root-conf.toml as the marker of a composefs-native image. Use the same marker here: when an image ships it and has no ostree prepare-root.conf, it can't be installed with ostree anyway, so default to composefs. An image with both is still installed with ostree by default, since it may be meant for either backend: bootc's own composefs CI images, for example, add setup-root-conf.toml to a stock base image and pass the flag. Both files are read from the root bootc runs in, as the install configuration and prepare-root.conf already were, including with --source-imgref: bootc-image-builder runs bootc from the image it installs, so it gets the same default. Such an image also failed with --composefs-backend, since install required prepare-root.conf regardless of the backend; only the ostree backend reads it, so it's optional for composefs now. --allow-missing-verity and --uki-addon used to require --composefs-backend at the clap level, which would reject them for an image selecting the backend by itself, so check them after the backend is decided. Generated-by: AI --- crates/lib/src/bootc_composefs/image.rs | 53 +++++++++++++ crates/lib/src/install.rs | 79 ++++++++++++++----- docs/src/bootc-experimental-composefs.7.md | 7 ++ docs/src/man/bootc-install-to-disk.8.md | 4 +- .../man/bootc-install-to-existing-root.8.md | 4 +- docs/src/man/bootc-install-to-filesystem.8.md | 4 +- docs/src/man/bootc-setup-root-conf.5.md | 2 + 7 files changed, 129 insertions(+), 24 deletions(-) diff --git a/crates/lib/src/bootc_composefs/image.rs b/crates/lib/src/bootc_composefs/image.rs index 2c7f3ac3ac..f6691f75e3 100644 --- a/crates/lib/src/bootc_composefs/image.rs +++ b/crates/lib/src/bootc_composefs/image.rs @@ -17,3 +17,56 @@ pub(crate) fn is_composefs_native(root: &Dir) -> Result { .symlink_metadata_optional(setup_root_conf_path())? .is_some()) } + +/// Whether `bootc install` should default to the composefs backend for this +/// root: it is composefs-native, and it has no ostree `prepare-root.conf` +/// (`has_ostree_prepareroot`, which the caller loads anyway), without which +/// it can't be installed with the ostree backend. An image that has both +/// configurations still defaults to ostree. +pub(crate) fn defaults_to_composefs_backend( + root: &Dir, + has_ostree_prepareroot: bool, +) -> Result { + Ok(!has_ostree_prepareroot && is_composefs_native(root)?) +} + +#[cfg(test)] +mod tests { + use super::*; + use camino::Utf8Path; + use ostree_ext::ostree_prepareroot; + + const OSTREE_PREPAREROOT: &str = "usr/lib/ostree/prepare-root.conf"; + const OSTREE_PREPAREROOT_ETC: &str = "etc/ostree/prepare-root.conf"; + + #[test] + fn test_defaults_to_composefs_backend() -> Result<()> { + let setup_root = setup_root_conf_path(); + // (files present in the image) => (composefs-native, defaults to composefs) + let cases: &[(&[&str], bool, bool)] = &[ + (&[], false, false), + (&[setup_root], true, true), + (&[OSTREE_PREPAREROOT], false, false), + (&[OSTREE_PREPAREROOT_ETC], false, false), + (&[setup_root, OSTREE_PREPAREROOT], true, false), + (&[setup_root, OSTREE_PREPAREROOT_ETC], true, false), + ]; + for &(files, native, composefs) in cases { + let td = cap_std_ext::cap_tempfile::tempdir(cap_std_ext::cap_std::ambient_authority())?; + for f in files { + let f = Utf8Path::new(f); + td.create_dir_all(f.parent().unwrap())?; + // Both files are signals even if empty + td.write(f, "")?; + } + assert_eq!(is_composefs_native(&td)?, native, "{files:?}"); + let has_ostree = ostree_prepareroot::load_config_from_root(&td)?.is_some(); + assert_eq!( + defaults_to_composefs_backend(&td, has_ostree)?, + composefs, + "{files:?}" + ); + } + Ok(()) + } +} diff --git a/crates/lib/src/install.rs b/crates/lib/src/install.rs index 4dec3d02b6..feaad02d62 100644 --- a/crates/lib/src/install.rs +++ b/crates/lib/src/install.rs @@ -397,19 +397,24 @@ pub(crate) struct InstallConfigOpts { #[derive(Debug, Default, Clone, clap::Parser, Serialize, Deserialize, PartialEq, Eq)] pub(crate) struct InstallComposefsOpts { - /// If true, composefs backend is used, else ostree backend is used + /// If true, composefs backend is used, else ostree backend is used. + /// + /// Images with a UKI always use it, and so do images that ship + /// /usr/lib/composefs/setup-root-conf.toml but no ostree prepare-root.conf. #[clap(long, default_value_t)] #[serde(default)] pub(crate) composefs_backend: bool, /// Make fs-verity validation optional in case the filesystem doesn't support it - #[clap(long, default_value_t, requires = "composefs_backend")] + /// (composefs backend only) + #[clap(long, default_value_t)] #[serde(default)] pub(crate) allow_missing_verity: bool, /// Name of the UKI addons to install without the ".efi.addon" suffix. - /// This option can be provided multiple times if multiple addons are to be installed. - #[clap(long, requires = "composefs_backend")] + /// This option can be provided multiple times if multiple addons are to be installed + /// (composefs backend only). + #[clap(long)] #[serde(default)] pub(crate) uki_addon: Option>, } @@ -1720,13 +1725,54 @@ async fn prepare_install( tracing::debug!("Composefs required: {composefs_required}"); - if composefs_required { - composefs_options.composefs_backend = true; + // ostree's prepare-root.conf is read from the running root even with + // --source-imgref, like the install configuration: tools such as + // bootc-image-builder run bootc from the image they install. Convert the + // keyfile to a hashmap because GKeyFile isnt Send for probably bad reasons. + let prepareroot_config = ostree_prepareroot::load_config_from_root(&rootfs)? + .map(|kf| -> Result> { + let mut r = HashMap::new(); + for grp in kf.groups() { + for key in kf.keys(&grp)? { + let key = key.as_str(); + let value = kf.value(&grp, key)?; + r.insert(format!("{grp}.{key}"), value.to_string()); + } + } + Ok(r) + }) + .transpose()?; + + // A UKI requires the composefs backend, and a composefs-native image + // without ostree's configuration defaults to it. + let composefs_default = crate::bootc_composefs::image::defaults_to_composefs_backend( + &rootfs, + prepareroot_config.is_some(), + )?; + tracing::debug!("Composefs default: {composefs_default}"); + let composefs_explicit = composefs_options.composefs_backend; + composefs_options.composefs_backend |= composefs_required || composefs_default; + if !composefs_options.composefs_backend { + anyhow::ensure!( + !composefs_options.allow_missing_verity, + "--allow-missing-verity requires the composefs backend" + ); + anyhow::ensure!( + composefs_options.uki_addon.is_none(), + "--uki-addon requires the composefs backend" + ); } if composefs_options.composefs_backend && matches!(config_opts.bootloader, Some(Bootloader::None)) { + if !composefs_explicit && !composefs_required { + anyhow::bail!( + "Bootloader set to none is not supported with the composefs backend, which this image selects \ + because it has {} and no ostree prepare-root.conf", + bootc_initramfs_setup::SETUP_ROOT_CONF_PATH + ); + } anyhow::bail!("Bootloader set to none is not supported with the composefs backend"); } @@ -1831,18 +1877,15 @@ async fn prepare_install( } } - // Convert the keyfile to a hashmap because GKeyFile isnt Send for probably bad reasons. - let prepareroot_config = { - let kf = ostree_prepareroot::require_config_from_root(&rootfs)?; - let mut r = HashMap::new(); - for grp in kf.groups() { - for key in kf.keys(&grp)? { - let key = key.as_str(); - let value = kf.value(&grp, key)?; - r.insert(format!("{grp}.{key}"), value.to_string()); - } - } - r + // Only the ostree backend uses prepare-root.conf, and composefs-native + // images needn't have one. + let prepareroot_config = match prepareroot_config { + Some(c) => c, + None if composefs_options.composefs_backend => HashMap::new(), + None => anyhow::bail!( + "Failed to find {} in /usr/lib or /etc", + ostree_prepareroot::CONF_PATH + ), }; // Eagerly read the file now to ensure we error out early if e.g. it doesn't exist, diff --git a/docs/src/bootc-experimental-composefs.7.md b/docs/src/bootc-experimental-composefs.7.md index 2fe382f580..dbdb83704c 100644 --- a/docs/src/bootc-experimental-composefs.7.md +++ b/docs/src/bootc-experimental-composefs.7.md @@ -276,6 +276,13 @@ Composefs installs using a traditional `vmlinuz`/`initramfs.img` layout instead There is a `--composefs-backend` option for `bootc install` to explicitly select a composefs backend apart from sealed images; this is not as heavily tested yet. +An image built only for the composefs backend selects it by itself: if it ships +`/usr/lib/composefs/setup-root-conf.toml` (see [bootc-setup-root-conf.toml(5)](man/bootc-setup-root-conf.5.md); +it may be empty) and no ostree `prepare-root.conf` (in `/usr/lib/ostree` or `/etc/ostree`), +`bootc install` uses composefs without the flag. Like the install configuration, these files are +read from the root bootc runs in, also with `--source-imgref`, so this applies to tools like +bootc-image-builder that run bootc from the image they install. An image that ships both is installed with ostree unless the flag is passed. + ## Known issues The composefs backend is experimental; on-disk formats are subject to change. diff --git a/docs/src/man/bootc-install-to-disk.8.md b/docs/src/man/bootc-install-to-disk.8.md index 9c11739480..69e322e0dd 100644 --- a/docs/src/man/bootc-install-to-disk.8.md +++ b/docs/src/man/bootc-install-to-disk.8.md @@ -182,13 +182,13 @@ set `discoverable-partitions = true` in their install configuration **--allow-missing-verity** - Make fs-verity validation optional in case the filesystem doesn't support it + Make fs-verity validation optional in case the filesystem doesn't support it (composefs backend only) Default: false **--uki-addon**=*UKI_ADDON* - Name of the UKI addons to install without the ".efi.addon" suffix. This option can be provided multiple times if multiple addons are to be installed + Name of the UKI addons to install without the ".efi.addon" suffix. This option can be provided multiple times if multiple addons are to be installed (composefs backend only) diff --git a/docs/src/man/bootc-install-to-existing-root.8.md b/docs/src/man/bootc-install-to-existing-root.8.md index 973c174f84..5d857448af 100644 --- a/docs/src/man/bootc-install-to-existing-root.8.md +++ b/docs/src/man/bootc-install-to-existing-root.8.md @@ -223,13 +223,13 @@ of migrating the fstab entries. See the "Injecting kernel arguments" section abo **--allow-missing-verity** - Make fs-verity validation optional in case the filesystem doesn't support it + Make fs-verity validation optional in case the filesystem doesn't support it (composefs backend only) Default: false **--uki-addon**=*UKI_ADDON* - Name of the UKI addons to install without the ".efi.addon" suffix. This option can be provided multiple times if multiple addons are to be installed + Name of the UKI addons to install without the ".efi.addon" suffix. This option can be provided multiple times if multiple addons are to be installed (composefs backend only) diff --git a/docs/src/man/bootc-install-to-filesystem.8.md b/docs/src/man/bootc-install-to-filesystem.8.md index 2d2b8b6f62..02a23376a4 100644 --- a/docs/src/man/bootc-install-to-filesystem.8.md +++ b/docs/src/man/bootc-install-to-filesystem.8.md @@ -132,13 +132,13 @@ is currently expected to be empty by default. **--allow-missing-verity** - Make fs-verity validation optional in case the filesystem doesn't support it + Make fs-verity validation optional in case the filesystem doesn't support it (composefs backend only) Default: false **--uki-addon**=*UKI_ADDON* - Name of the UKI addons to install without the ".efi.addon" suffix. This option can be provided multiple times if multiple addons are to be installed + Name of the UKI addons to install without the ".efi.addon" suffix. This option can be provided multiple times if multiple addons are to be installed (composefs backend only) diff --git a/docs/src/man/bootc-setup-root-conf.5.md b/docs/src/man/bootc-setup-root-conf.5.md index 08d6f405c9..d190d2f7e7 100644 --- a/docs/src/man/bootc-setup-root-conf.5.md +++ b/docs/src/man/bootc-setup-root-conf.5.md @@ -19,6 +19,8 @@ The presence of this file (even if empty) also marks the image as composefs-native; `bootc container lint` then no longer requires the `/ostree` symlink or the ostree `prepare-root.conf` composefs configuration used by the ostree backend. +If the image also has no ostree `prepare-root.conf`, `bootc install` +defaults to the composefs backend, without `--composefs-backend`. The `51bootc` dracut module installs this file into the initramfs automatically when it is present on the host image. Image authors can therefore ship the From ff09bd08fd21242961d88b8ab9d8c6a7e56629da Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Tue, 29 Sep 2026 13:11:13 -0400 Subject: [PATCH 5/5] tests: Install a composefs-native image without --composefs-backend Build a derived image that adds an empty setup-root-conf.toml and drops ostree's prepare-root.conf, and check that `bootc install to-disk` run from it installs the composefs backend without the flag, both as a self-install and with --source-imgref as bootc-image-builder runs it. An image that keeps prepare-root.conf must still get ostree. On the ostree variant, nothing else selects composefs. Generated-by: AI --- tmt/plans/integration.fmf | 8 ++ .../test-52-install-composefs-native.nu | 77 +++++++++++++++++++ tmt/tests/tests.fmf | 5 ++ 3 files changed, 90 insertions(+) create mode 100644 tmt/tests/booted/test-52-install-composefs-native.nu diff --git a/tmt/plans/integration.fmf b/tmt/plans/integration.fmf index 06dadb2cf3..3e2ae3b667 100644 --- a/tmt/plans/integration.fmf +++ b/tmt/plans/integration.fmf @@ -331,4 +331,12 @@ execute: enabled: true extra-try_bind_storage: true extra-skip_if_ostree: true + +/plan-52-install-composefs-native: + summary: Test that composefs-native images default to the composefs backend + discover: + how: fmf + test: + - /tmt/tests/tests/test-52-install-composefs-native + extra-fixme_skip_if_uki: true # END GENERATED PLANS diff --git a/tmt/tests/booted/test-52-install-composefs-native.nu b/tmt/tests/booted/test-52-install-composefs-native.nu new file mode 100644 index 0000000000..65fb7155eb --- /dev/null +++ b/tmt/tests/booted/test-52-install-composefs-native.nu @@ -0,0 +1,77 @@ +# number: 52 +# tmt: +# summary: Test that composefs-native images default to the composefs backend +# duration: 45m +# extra: +# # A UKI selects the composefs backend by itself, and a derived layer +# # wouldn't match the composefs digest embedded in it. +# fixme_skip_if_uki: true +# +# An image that ships /usr/lib/composefs/setup-root-conf.toml and no ostree +# prepare-root.conf must be installed with the composefs backend by +# `bootc install` run from that image without `--composefs-backend`, both +# as a self-install and with --source-imgref (as bootc-image-builder does). +# An image with both files is still installed with ostree. This runs on the +# ostree variant too, where nothing else selects composefs. + +use std assert +use tap.nu + +const NATIVE = "localhost/bootc-composefs-native" +const BOTH = "localhost/bootc-composefs-both" +const DISK = "/var/tmp/composefs-native.img" +const MNT = "/var/mnt/composefs-native" + +def build [image: string, extra: string] { + let td = mktemp -d + $"FROM localhost/bootc +RUN rm -rf /usr/lib/bootc/bound-images.d/* +RUN mkdir -p /usr/lib/composefs && touch /usr/lib/composefs/setup-root-conf.toml +($extra) +" | save $"($td)/Containerfile" + # Keep an OCI manifest, see https://github.com/bootc-dev/bootc/issues/1703 + podman build --format oci -t $image $td + rm -rf $td +} + +# Install `image` from itself and return the backend found on the disk +def install [image: string, ...args: string] { + truncate -s 15G $DISK + (podman run --rm --privileged --pid=host + --security-opt label=type:unconfined_t + -v /dev:/dev -v /var/lib/containers:/var/lib/containers -v /var/tmp:/var/tmp + $image + bootc install to-disk --disable-selinux --via-loopback ...$args $DISK) + + # Inspect the root partition of the installed disk + let parts = sfdisk --json $DISK | from json | get partitiontable + let root = $parts.partitions | where name == "root" | first + let offset = $root.start * ($parts.sectorsize? | default 512) + mkdir $MNT + mount -o $"ro,loop,offset=($offset)" $DISK $MNT + let composefs = ($"($MNT)/composefs" | path exists) and ((ls $"($MNT)/state/deploy" | length) == 1) + let ostree = ($"($MNT)/ostree/deploy" | path exists) + umount $MNT + rm -f $DISK + match [$composefs $ostree] { + [true false] => "composefs", + [false true] => "ostree", + _ => $"unexpected: composefs=($composefs) ostree=($ostree)", + } +} + +def main [] { + tap begin "composefs-native images default to the composefs backend" + + bootc image copy-to-storage + build $NATIVE "RUN rm -f /usr/lib/ostree/prepare-root.conf /etc/ostree/prepare-root.conf" + build $BOTH "" + + assert equal (install $NATIVE) "composefs" "composefs-native self-install" + let src = $"--source-imgref=containers-storage:($NATIVE)" + assert equal (install $NATIVE $src) "composefs" "composefs-native with --source-imgref" + assert equal (install $BOTH) "ostree" "image with both configurations" + + podman rmi $NATIVE $BOTH + tap ok +} diff --git a/tmt/tests/tests.fmf b/tmt/tests/tests.fmf index f27e9addd5..3dfb3ece0c 100644 --- a/tmt/tests/tests.fmf +++ b/tmt/tests/tests.fmf @@ -206,3 +206,8 @@ check: - when: composefs_bridge == true enabled: true test: nu booted/test-49-composefs-1-16-bridge.nu + +/test-52-install-composefs-native: + summary: Test that composefs-native images default to the composefs backend + duration: 45m + test: nu booted/test-52-install-composefs-native.nu