diff --git a/lib/src/install.rs b/lib/src/install.rs index 16439b6c..025293e9 100644 --- a/lib/src/install.rs +++ b/lib/src/install.rs @@ -8,6 +8,7 @@ // and filesystem setup. pub(crate) mod baseline; pub(crate) mod config; +mod osbuild; pub(crate) mod osconfig; use std::io::Write; @@ -997,36 +998,6 @@ fn ensure_var() -> Result<()> { Ok(()) } -/// Unfortunately today podman requires that /etc be writable for -/// `/etc/containers/networks`. Detect the situation where it's not -/// (the main usual cause will be how bootc-image-builder runs us -/// via a custom bwrap container today) and work around it by -/// mounting a writable transient overlayfs. -#[context("Ensuring writable /etc")] -fn ensure_writable_etc_containers(tempdir: &Dir) -> Result<()> { - let etc_containers = Utf8Path::new("/etc/containers"); - // If there's no /etc/containers, nothing to do - if !etc_containers.try_exists()? { - return Ok(()); - } - if rustix::fs::access(etc_containers.as_std_path(), rustix::fs::Access::WRITE_OK).is_ok() { - return Ok(()); - } - // Create dirs for the overlayfs upper and work in the install-global tmpdir. - tempdir.create_dir_all("etc-ovl/upper")?; - tempdir.create_dir("etc-ovl/work")?; - let opts = format!("lowerdir={etc_containers},workdir=etc-ovl/work,upperdir=etc-ovl/upper"); - let mut t = Task::new( - &format!("Mount transient overlayfs for {etc_containers}"), - "mount", - ) - .args(["-t", "overlay", "overlay", "-o", opts.as_str()]) - .arg(etc_containers); - t.cmd.cwd_dir(tempdir.try_clone()?); - t.run()?; - Ok(()) -} - /// We want to have proper /tmp and /var/tmp without requiring the caller to set them up /// in advance by manually specifying them via `podman run -v /tmp:/tmp` etc. /// Unfortunately, it's quite complex right now to "gracefully" dynamically reconfigure @@ -1214,7 +1185,7 @@ async fn prepare_install( // creating multiple. let tempdir = cap_std_ext::cap_tempfile::TempDir::new(cap_std::ambient_authority())?; // And continue to init global state - ensure_writable_etc_containers(&tempdir)?; + osbuild::adjust_for_bootc_image_builder(&rootfs, &tempdir)?; if !target_opts.skip_fetch_check { verify_target_fetch(&tempdir, &target_imgref).await?; diff --git a/lib/src/install/osbuild.rs b/lib/src/install/osbuild.rs new file mode 100644 index 00000000..1540b946 --- /dev/null +++ b/lib/src/install/osbuild.rs @@ -0,0 +1,73 @@ +//! # Helper APIs for interacting with bootc-image-builder +//! +//! See +//! + +use anyhow::Result; +use camino::Utf8Path; +use cap_std_ext::{cap_std::fs::Dir, cmdext::CapStdExtCommandExt}; +use fn_error_context::context; + +use crate::task::Task; + +/// Handle /etc/containers readonly mount. +/// +/// Ufortunately today podman requires that /etc be writable for +/// `/etc/containers/networks`. bib today creates this as a readonly mount: +/// https://github.com/osbuild/osbuild/blob/4edbe227d41c767441b9bf4390398afc6dc8f901/osbuild/buildroot.py#L243 +/// +/// Work around that by adding a transient, writable overlayfs. +fn adjust_etc_containers(tempdir: &Dir) -> Result<()> { + let etc_containers = Utf8Path::new("/etc/containers"); + // If there's no /etc/containers, nothing to do + if !etc_containers.try_exists()? { + return Ok(()); + } + if rustix::fs::access(etc_containers.as_std_path(), rustix::fs::Access::WRITE_OK).is_ok() { + return Ok(()); + } + // Create dirs for the overlayfs upper and work in the install-global tmpdir. + tempdir.create_dir_all("etc-ovl/upper")?; + tempdir.create_dir("etc-ovl/work")?; + let opts = format!("lowerdir={etc_containers},workdir=etc-ovl/work,upperdir=etc-ovl/upper"); + let mut t = Task::new( + &format!("Mount transient overlayfs for {etc_containers}"), + "mount", + ) + .args(["-t", "overlay", "overlay", "-o", opts.as_str()]) + .arg(etc_containers); + t.cmd.cwd_dir(tempdir.try_clone()?); + t.run()?; + Ok(()) +} + +/// osbuild mounts the host's /var/lib/containers at /run/osbuild/containers; mount +/// it back to /var/lib/containers where the default container stack expects to find it. +fn propagate_run_osbuild_containers(root: &Dir) -> Result<()> { + let osbuild_run_containers = Utf8Path::new("run/osbuild/containers"); + // If we're not apparently running under osbuild, then we no-op. + if !root.try_exists(osbuild_run_containers)? { + return Ok(()); + } + // If we do seem to have a valid container store though, use that + if crate::podman::storage_exists_default(root)? { + return Ok(()); + } + let relative_storage = Utf8Path::new(crate::podman::CONTAINER_STORAGE.trim_start_matches('/')); + root.create_dir_all(relative_storage)?; + Task::new("Creating bind mount for run/osbuild/containers", "mount") + .arg("--rbind") + .args([osbuild_run_containers, relative_storage]) + .cwd(root)? + .run()?; + Ok(()) +} + +/// bootc-image-builder today does a few things that we need to +/// deal with. +#[context("bootc-image-builder adjustments")] +pub(crate) fn adjust_for_bootc_image_builder(root: &Dir, tempdir: &Dir) -> Result<()> { + adjust_etc_containers(tempdir)?; + propagate_run_osbuild_containers(root)?; + Ok(()) +} diff --git a/lib/src/podman.rs b/lib/src/podman.rs index f5f7fd96..a2ea3650 100644 --- a/lib/src/podman.rs +++ b/lib/src/podman.rs @@ -1,4 +1,6 @@ use anyhow::{anyhow, Result}; +use camino::Utf8Path; +use cap_std_ext::cap_std::fs::Dir; use serde::Deserialize; use crate::install::run_in_host_mountns; @@ -27,3 +29,21 @@ pub(crate) fn imageid_to_digest(imgid: &str) -> Result { .ok_or_else(|| anyhow!("No images returned for inspect"))?; Ok(i.digest) } + +/// Return true if there is apparently an active container store at the target path. +pub(crate) fn storage_exists(root: &Dir, path: impl AsRef) -> Result { + fn impl_storage_exists(root: &Dir, path: &Utf8Path) -> Result { + let lock = "storage.lock"; + root.try_exists(path.join(lock)).map_err(Into::into) + } + impl_storage_exists(root, path.as_ref()) +} + +/// Return true if there is apparently an active container store in the default path +/// for the target root. +/// +/// Note this does not attempt to parse the root filesystem's container storage configuration, +/// this uses a hardcoded default path. +pub(crate) fn storage_exists_default(root: &Dir) -> Result { + storage_exists(root, CONTAINER_STORAGE.trim_start_matches('/')) +}