From f116a60ca8d8f969dcd760bd08b3276131d83bd5 Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Wed, 20 Nov 2024 16:47:02 -0500 Subject: [PATCH] install: Add hidden `ensure-completion` verb This will be runnable via ``` %post --erroronfail bootc install ensure-completion %end ``` in Anaconda to work around the fact that it's not today using `bootc install to-filesystem`. Signed-off-by: Colin Walters --- lib/src/cli.rs | 16 ++++++++ lib/src/install.rs | 2 + lib/src/install/completion.rs | 76 +++++++++++++++++++++++++++++++++++ 3 files changed, 94 insertions(+) create mode 100644 lib/src/install/completion.rs diff --git a/lib/src/cli.rs b/lib/src/cli.rs index 1a9d47deb..cec5e4ee5 100644 --- a/lib/src/cli.rs +++ b/lib/src/cli.rs @@ -183,6 +183,15 @@ pub(crate) enum InstallOpts { /// will be wiped, but the content of the existing root will otherwise be retained, and will /// need to be cleaned up if desired when rebooted into the new root. ToExistingRoot(crate::install::InstallToExistingRootOpts), + /// Intended for use in environments that are performing an ostree-based installation, not bootc. + /// + /// In this scenario the installation may be missing bootc specific features such as + /// kernel arguments, logically bound images and more. This command can be used to attempt + /// to reconcile. At the current time, the only tested environment is Anaconda using `ostreecontainer` + /// and it is recommended to avoid usage outside of that environment. Instead, ensure your + /// code is using `bootc install to-filesystem` from the start. + #[clap(hide = true)] + EnsureCompletion, /// Output JSON to stdout that contains the merged installation configuration /// as it may be relevant to calling processes using `install to-filesystem` /// that in particular want to discover the desired root filesystem type from the container image. @@ -971,6 +980,13 @@ async fn run_from_opt(opt: Opt) -> Result<()> { crate::install::install_to_existing_root(opts).await } InstallOpts::PrintConfiguration => crate::install::print_configuration(), + InstallOpts::EnsureCompletion => { + tokio::task::spawn_blocking(move || { + let rootfs = &Dir::open_ambient_dir("/", cap_std::ambient_authority())?; + crate::install::completion::run(rootfs) + }) + .await? + } }, #[cfg(feature = "install")] Opt::ExecInHostMountNamespace { args } => { diff --git a/lib/src/install.rs b/lib/src/install.rs index 1893c0a70..53706c181 100644 --- a/lib/src/install.rs +++ b/lib/src/install.rs @@ -7,6 +7,7 @@ // This sub-module is the "basic" installer that handles creating basic block device // and filesystem setup. pub(crate) mod baseline; +pub(crate) mod completion; pub(crate) mod config; mod osbuild; pub(crate) mod osconfig; @@ -740,6 +741,7 @@ async fn install_container( )?; let kargsd = kargsd.iter().map(|s| s.as_str()); + // Keep this in sync with install/completion.rs for the Anaconda fixups let install_config_kargs = state .install_config .as_ref() diff --git a/lib/src/install/completion.rs b/lib/src/install/completion.rs new file mode 100644 index 000000000..1f9bc74fe --- /dev/null +++ b/lib/src/install/completion.rs @@ -0,0 +1,76 @@ +//! This module handles finishing/completion after an ostree-based +//! install from e.g. Anaconda. + +use std::os::fd::{AsFd, AsRawFd}; + +use anyhow::Result; +use cap_std_ext::cap_std::fs::Dir; +use ostree_ext::{gio, ostree}; + +use crate::utils::medium_visibility_warning; + +use super::config; + +/// An environment variable set by anaconda that hints +/// we are running as part of that environment. +const ANACONDA_ENV_HINT: &str = "ANA_INSTALL_PATH"; + +fn reconcile_kargs(rootfs: &Dir, sysroot: &ostree::Sysroot) -> Result<()> { + let cancellable = gio::Cancellable::NONE; + + let deployments = sysroot.deployments(); + let deployment = match deployments.as_slice() { + [d] => d, + o => anyhow::bail!("Expected exactly 1 deployment, not {}", o.len()), + }; + + let current_kargs = deployment + .bootconfig() + .expect("bootconfig for deployment") + .get("options"); + let current_kargs = current_kargs + .as_ref() + .map(|s| s.as_str()) + .unwrap_or_default(); + tracing::debug!("current_kargs={current_kargs}"); + let current_kargs = ostree::KernelArgs::from_string(¤t_kargs); + + // Keep this in sync with install_container + let install_config = config::load_config()?; + let install_config_kargs = install_config + .as_ref() + .and_then(|c| c.kargs.as_ref()) + .into_iter() + .flatten() + .map(|s| s.as_str()) + .collect::>(); + let kargsd = crate::kargs::get_kargs_in_root(rootfs, std::env::consts::ARCH)?; + let kargsd = kargsd.iter().map(|s| s.as_str()).collect::>(); + + current_kargs.append_argv(&install_config_kargs); + current_kargs.append_argv(&kargsd); + let new_kargs = current_kargs.to_string(); + tracing::debug!("new_kargs={new_kargs}"); + + sysroot.deployment_set_kargs_in_place(deployment, Some(&new_kargs), cancellable)?; + Ok(()) +} + +/// Core entrypoint. +pub(crate) fn run(rootfs: &Dir) -> Result<()> { + let cancellable = gio::Cancellable::NONE; + if std::env::var_os(ANACONDA_ENV_HINT).is_none() { + // Be loud if a user is invoking this outside of the expected setup. + medium_visibility_warning(&format!("Missing environment variable {ANACONDA_ENV_HINT}")); + } + + let sysroot = { + let path = format!("/proc/self/fd/{}", rootfs.as_fd().as_raw_fd()); + ostree::Sysroot::new(Some(&gio::File::for_path(path))) + }; + sysroot.load(cancellable)?; + + reconcile_kargs(rootfs, &sysroot)?; + + Ok(()) +}