From 6cfb4beda4fd0fe9abc0c7c4cef7842046aceb80 Mon Sep 17 00:00:00 2001 From: Pragyan Poudyal Date: Wed, 7 May 2025 10:56:43 +0530 Subject: [PATCH 1/4] cli/install: Add `composefs` option to `InstallToDiskOpts` Done to facilitate the installation of a composefs repository to disk Signed-off-by: Pragyan Poudyal --- Cargo.lock | 11 ++++++----- lib/src/install.rs | 3 +++ ostree-ext/Cargo.toml | 4 ++-- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b5ef2144..eec630ac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -518,7 +518,7 @@ dependencies = [ [[package]] name = "composefs" version = "0.2.0" -source = "git+https://github.com/containers/composefs-rs?rev=821eeae93e48f1ee381c49b8cd4d22fda92d27a2#821eeae93e48f1ee381c49b8cd4d22fda92d27a2" +source = "git+https://github.com/containers/composefs-rs?rev=b1bd9c1d253c9b15bbf8e04068ad96edd974ccd0#b1bd9c1d253c9b15bbf8e04068ad96edd974ccd0" dependencies = [ "anyhow", "async-compression", @@ -529,6 +529,7 @@ dependencies = [ "indicatif", "log", "oci-spec", + "once_cell", "regex-automata 0.4.9", "rustix 1.0.3", "serde", @@ -558,9 +559,9 @@ dependencies = [ [[package]] name = "containers-image-proxy" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1b4ec45d60513c498a40c69d89447d8bf91bbd17f71a32aa285b39e4dc03294" +checksum = "e366fb6e732b808c920cfdc758949a2f4d80445d413b040640aeb3d744cabcdb" dependencies = [ "cap-std-ext", "fn-error-context", @@ -1618,9 +1619,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.20.3" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "openssh-keys" diff --git a/lib/src/install.rs b/lib/src/install.rs index 395293c1..20b3d00a 100644 --- a/lib/src/install.rs +++ b/lib/src/install.rs @@ -241,6 +241,9 @@ pub(crate) struct InstallToDiskOpts { #[clap(long)] #[serde(default)] pub(crate) via_loopback: bool, + + #[clap(long)] + pub(crate) composefs: bool, } #[derive(ValueEnum, Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] diff --git a/ostree-ext/Cargo.toml b/ostree-ext/Cargo.toml index 6ba83425..221f804f 100644 --- a/ostree-ext/Cargo.toml +++ b/ostree-ext/Cargo.toml @@ -12,7 +12,7 @@ version = "0.15.3" # Note that we re-export the oci-spec types # that are exported by this crate, so when bumping # semver here you must also bump our semver. -containers-image-proxy = "0.7.0" +containers-image-proxy = "0.7.1" # We re-export this library too. ostree = { features = ["v2025_1"], version = "0.20.0" } @@ -20,7 +20,7 @@ ostree = { features = ["v2025_1"], version = "0.20.0" } anyhow = { workspace = true } bootc-utils = { path = "../utils" } camino = { workspace = true, features = ["serde1"] } -composefs = { git = "https://github.com/containers/composefs-rs", rev = "821eeae93e48f1ee381c49b8cd4d22fda92d27a2" } +composefs = { git = "https://github.com/containers/composefs-rs", rev = "b1bd9c1d253c9b15bbf8e04068ad96edd974ccd0" } chrono = { workspace = true } olpc-cjson = "0.1.1" clap = { workspace = true, features = ["derive","cargo"] } From d03feb283b54cf1c9605e7d14978c4958518738c Mon Sep 17 00:00:00 2001 From: Pragyan Poudyal Date: Wed, 7 May 2025 11:31:59 +0530 Subject: [PATCH 2/4] cli: Handle compatibility with latest composefs-rs Signed-off-by: Pragyan Poudyal --- lib/src/cli.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/cli.rs b/lib/src/cli.rs index 44919d31..a2e177ef 100644 --- a/lib/src/cli.rs +++ b/lib/src/cli.rs @@ -17,7 +17,7 @@ use fn_error_context::context; use indoc::indoc; use ostree::gio; use ostree_container::store::PrepareResult; -use ostree_ext::composefs::fsverity; +use ostree_ext::composefs::fsverity::{self, FsVerityHashValue}; use ostree_ext::container as ostree_container; use ostree_ext::container_utils::ostree_booted; use ostree_ext::keyfileext::KeyFileExt; @@ -1199,7 +1199,7 @@ async fn run_from_opt(opt: Opt) -> Result<()> { let fd = std::fs::File::open(&path).with_context(|| format!("Reading {path}"))?; let digest: fsverity::Sha256HashValue = fsverity::measure_verity(&fd)?; - let digest = hex::encode(digest); + let digest = digest.to_hex(); println!("{digest}"); Ok(()) } From 6be556015f6c2928366d43b482e2331b941877f4 Mon Sep 17 00:00:00 2001 From: Pragyan Poudyal Date: Wed, 7 May 2025 11:33:01 +0530 Subject: [PATCH 3/4] install: Pull composefs repository Signed-off-by: Pragyan Poudyal --- Cargo.lock | 1 - lib/src/install.rs | 96 +++++++++++++++++++++++++++++++++------------- 2 files changed, 69 insertions(+), 28 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index eec630ac..c5daf0c7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -518,7 +518,6 @@ dependencies = [ [[package]] name = "composefs" version = "0.2.0" -source = "git+https://github.com/containers/composefs-rs?rev=b1bd9c1d253c9b15bbf8e04068ad96edd974ccd0#b1bd9c1d253c9b15bbf8e04068ad96edd974ccd0" dependencies = [ "anyhow", "async-compression", diff --git a/lib/src/install.rs b/lib/src/install.rs index 20b3d00a..9a19cb08 100644 --- a/lib/src/install.rs +++ b/lib/src/install.rs @@ -38,12 +38,20 @@ use chrono::prelude::*; use clap::ValueEnum; use fn_error_context::context; use ostree::gio; +use ostree_ext::composefs::{ + fsverity::{FsVerityHashValue, Sha256HashValue}, + oci::pull as composefs_oci_pull, + repository::Repository as ComposefsRepository, + util::Sha256Digest, +}; use ostree_ext::oci_spec; use ostree_ext::ostree; use ostree_ext::ostree_prepareroot::{ComposefsState, Tristate}; use ostree_ext::prelude::Cast; use ostree_ext::sysroot::SysrootLock; -use ostree_ext::{container as ostree_container, ostree_prepareroot}; +use ostree_ext::{ + container as ostree_container, container::ImageReference as OstreeExtImgRef, ostree_prepareroot, +}; #[cfg(feature = "install-to-disk")] use rustix::fs::FileTypeExt; use rustix::fs::MetadataExt as _; @@ -366,6 +374,7 @@ pub(crate) struct SourceInfo { } // Shared read-only global state +#[derive(Debug)] pub(crate) struct State { pub(crate) source: SourceInfo, /// Force SELinux off in target system @@ -1428,10 +1437,32 @@ impl BoundImages { } } +async fn initialize_composefs_repository( + state: &State, + root_setup: &RootSetup, +) -> Result<(Sha256Digest, impl FsVerityHashValue)> { + let rootfs_dir = &root_setup.physical_root; + + rootfs_dir + .create_dir_all("composefs") + .context("Creating dir 'composefs'")?; + + tracing::warn!("STATE: {state:#?}"); + + let repo: ComposefsRepository = + ComposefsRepository::open_path(rootfs_dir, "composefs").expect("failed to open_path"); + + let OstreeExtImgRef { transport, name } = &state.target_imgref.imgref; + + // transport's display is already of type ":" + composefs_oci_pull(&Arc::new(repo), &format!("{transport}{name}",), None).await +} + async fn install_to_filesystem_impl( state: &State, rootfs: &mut RootSetup, cleanup: Cleanup, + composefs: bool, ) -> Result<()> { if matches!(state.selinux_state, SELinuxFinalState::ForceTargetDisabled) { rootfs.kargs.push("selinux=0".to_string()); @@ -1460,34 +1491,45 @@ async fn install_to_filesystem_impl( let bound_images = BoundImages::from_state(state).await?; - // Initialize the ostree sysroot (repo, stateroot, etc.) + if composefs { + // Load a fd for the mounted target physical root + let (id, verity) = initialize_composefs_repository(state, rootfs).await?; - { - let (sysroot, has_ostree, imgstore) = initialize_ostree_root(state, rootfs).await?; - - install_with_sysroot( - state, - rootfs, - &sysroot, - &boot_uuid, - bound_images, - has_ostree, - &imgstore, - ) - .await?; + tracing::warn!( + "id = {id}, verity = {verity}", + id = hex::encode(id), + verity = verity.to_hex() + ); + } else { + // Initialize the ostree sysroot (repo, stateroot, etc.) + + { + let (sysroot, has_ostree, imgstore) = initialize_ostree_root(state, rootfs).await?; + + install_with_sysroot( + state, + rootfs, + &sysroot, + &boot_uuid, + bound_images, + has_ostree, + &imgstore, + ) + .await?; - if matches!(cleanup, Cleanup::TriggerOnNextBoot) { - let sysroot_dir = crate::utils::sysroot_dir(&sysroot)?; - tracing::debug!("Writing {DESTRUCTIVE_CLEANUP}"); - sysroot_dir.atomic_write(format!("etc/{}", DESTRUCTIVE_CLEANUP), b"")?; - } + if matches!(cleanup, Cleanup::TriggerOnNextBoot) { + let sysroot_dir = crate::utils::sysroot_dir(&sysroot)?; + tracing::debug!("Writing {DESTRUCTIVE_CLEANUP}"); + sysroot_dir.atomic_write(format!("etc/{}", DESTRUCTIVE_CLEANUP), b"")?; + } - // We must drop the sysroot here in order to close any open file - // descriptors. - }; + // We must drop the sysroot here in order to close any open file + // descriptors. + }; - // Run this on every install as the penultimate step - install_finalize(&rootfs.physical_root_path).await?; + // Run this on every install as the penultimate step + install_finalize(&rootfs.physical_root_path).await?; + } // Finalize mounted filesystems if !rootfs.skip_finalize { @@ -1550,7 +1592,7 @@ pub(crate) async fn install_to_disk(mut opts: InstallToDiskOpts) -> Result<()> { (rootfs, loopback_dev) }; - install_to_filesystem_impl(&state, &mut rootfs, Cleanup::Skip).await?; + install_to_filesystem_impl(&state, &mut rootfs, Cleanup::Skip, opts.composefs).await?; // Drop all data about the root except the bits we need to ensure any file descriptors etc. are closed. let (root_path, luksdev) = rootfs.into_storage(); @@ -1936,7 +1978,7 @@ pub(crate) async fn install_to_filesystem( skip_finalize, }; - install_to_filesystem_impl(&state, &mut rootfs, cleanup).await?; + install_to_filesystem_impl(&state, &mut rootfs, cleanup, false).await?; // Drop all data about the root except the path to ensure any file descriptors etc. are closed. drop(rootfs); From b3d19529c396ea8086a5fe05c97fedf15891e902 Mon Sep 17 00:00:00 2001 From: Pragyan Poudyal Date: Tue, 13 May 2025 12:10:03 +0530 Subject: [PATCH 4/4] install/composefs: Write boot entries Signed-off-by: Pragyan Poudyal --- lib/src/install.rs | 79 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 77 insertions(+), 2 deletions(-) diff --git a/lib/src/install.rs b/lib/src/install.rs index 9a19cb08..4c789ac3 100644 --- a/lib/src/install.rs +++ b/lib/src/install.rs @@ -14,6 +14,7 @@ mod osbuild; pub(crate) mod osconfig; use std::collections::HashMap; +use std::fs::create_dir_all; use std::io::Write; use std::os::fd::{AsFd, AsRawFd}; use std::os::unix::process::CommandExt; @@ -40,9 +41,11 @@ use fn_error_context::context; use ostree::gio; use ostree_ext::composefs::{ fsverity::{FsVerityHashValue, Sha256HashValue}, + oci::image::create_filesystem as create_composefs_filesystem, oci::pull as composefs_oci_pull, repository::Repository as ComposefsRepository, util::Sha256Digest, + write_boot::write_boot_simple as composefs_write_boot_simple, }; use ostree_ext::oci_spec; use ostree_ext::ostree; @@ -1437,6 +1440,11 @@ impl BoundImages { } } +fn open_composefs_repo(rootfs_dir: &Dir) -> Result> { + ComposefsRepository::open_path(rootfs_dir, "composefs") + .context("Failed to open composefs repository") +} + async fn initialize_composefs_repository( state: &State, root_setup: &RootSetup, @@ -1449,8 +1457,7 @@ async fn initialize_composefs_repository( tracing::warn!("STATE: {state:#?}"); - let repo: ComposefsRepository = - ComposefsRepository::open_path(rootfs_dir, "composefs").expect("failed to open_path"); + let repo = open_composefs_repo(rootfs_dir)?; let OstreeExtImgRef { transport, name } = &state.target_imgref.imgref; @@ -1458,6 +1465,72 @@ async fn initialize_composefs_repository( composefs_oci_pull(&Arc::new(repo), &format!("{transport}{name}",), None).await } +#[context("Setting up composefs boot")] +fn setup_composefs_boot(root_setup: &RootSetup, state: &State, image_id: &str) -> Result<()> { + let boot_uuid = root_setup + .get_boot_uuid()? + .or(root_setup.rootfs_uuid.as_deref()) + .ok_or_else(|| anyhow!("No uuid for boot/root"))?; + + if cfg!(target_arch = "s390x") { + // TODO: Integrate s390x support into install_via_bootupd + crate::bootloader::install_via_zipl(&root_setup.device_info, boot_uuid)?; + } else { + crate::bootloader::install_via_bootupd( + &root_setup.device_info, + &root_setup.physical_root_path, + &state.config_opts, + )?; + } + + let repo = open_composefs_repo(&root_setup.physical_root)?; + + let mut fs = create_composefs_filesystem(&repo, image_id, None)?; + + let entries = fs.transform_for_boot(&repo)?; + let id = fs.commit_image(&repo, None)?; + + println!("{entries:#?}"); + + let Some(entry) = entries.into_iter().next() else { + anyhow::bail!("No boot entries!"); + }; + + let rootfs_uuid = match &root_setup.rootfs_uuid { + Some(u) => u, + None => anyhow::bail!("Expected rootfs to have a UUID by now"), + }; + + let cmdline_refs = [ + "console=ttyS0,115200", + &format!("root=UUID={rootfs_uuid}"), + "rw", + ]; + + let boot_dir = root_setup.physical_root_path.join("boot"); + create_dir_all(&boot_dir).context("Failed to create boot dir")?; + + composefs_write_boot_simple( + &repo, + entry, + &id, + boot_dir.as_std_path(), + Some(&format!("{}", id.to_hex())), + Some("/boot"), + &cmdline_refs, + )?; + + let state_path = root_setup + .physical_root_path + .join(format!("state/{}", id.to_hex())); + + create_dir_all(state_path.join("var"))?; + create_dir_all(state_path.join("etc/upper"))?; + create_dir_all(state_path.join("etc/work"))?; + + Ok(()) +} + async fn install_to_filesystem_impl( state: &State, rootfs: &mut RootSetup, @@ -1500,6 +1573,8 @@ async fn install_to_filesystem_impl( id = hex::encode(id), verity = verity.to_hex() ); + + setup_composefs_boot(rootfs, state, &hex::encode(id))?; } else { // Initialize the ostree sysroot (repo, stateroot, etc.)