Skip to content

Commit

Permalink
install: Add a generic install finalize
Browse files Browse the repository at this point in the history
Basically I want to get Anaconda to run this, then we
can perform arbitrary fixups on whatever it did
between the install and reboot without changing Anaconda's
code.

This also applies to user `%post` scripts for example;
maybe those break the bootloader entries in /boot;
we have the opportunity to catch such things here.

Or we may choose to start forcibly relabeling the target
`/etc`.

Signed-off-by: Colin Walters <[email protected]>
  • Loading branch information
cgwalters committed Feb 28, 2025
1 parent 859bf9e commit 91e0a70
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 0 deletions.
20 changes: 20 additions & 0 deletions docs/src/bootc-install.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,26 @@ storage or filesystem setups, but reuses the "top half" of the logic.
For example, a goal is to change [Anaconda](https://github.com/rhinstaller/anaconda/)
to use this.

#### Postprocessing after to-filesystem

Some installation tools may want to inject additional data, such as adding
an `/etc/hostname` into the target root. At the current time, bootc does
not offer a direct API to do this. However, the backend for bootc is
ostree, and it is possible to enumerate the deployments via ostree APIs.

We hope to provide a bootc-supported method to find the deployment in
the future.

However, for tools that do perform any changes, there is a new
`bootc install finalize` command which is optional, but recommended
to run as the penultimate step before unmounting the target filesystem.

This command will perform some basic sanity checks and may also
perform fixups on the target root. For example, a direction
currently for bootc is to stop using `/etc/fstab`. While `install finalize`
does not do this today, in the future it may automatically migrate
`etc/fstab` to `rootflags` kernel arguments.

### Using `bootc install to-disk --via-loopback`

Because every `bootc` system comes with an opinionated default installation
Expand Down
9 changes: 9 additions & 0 deletions lib/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,12 @@ 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),
/// Execute this as the penultimate step of an installation using `install to-filesystem`.
///
Finalize {
/// Path to the mounted root filesystem.
root_path: Utf8PathBuf,
},
/// 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
Expand Down Expand Up @@ -1121,6 +1127,9 @@ async fn run_from_opt(opt: Opt) -> Result<()> {
let rootfs = &Dir::open_ambient_dir("/", cap_std::ambient_authority())?;
crate::install::completion::run_from_anaconda(rootfs).await
}
InstallOpts::Finalize { root_path } => {
crate::install::install_finalize(&root_path).await
}
},
Opt::ExecInHostMountNamespace { args } => {
crate::install::exec_in_host_mountns(args.as_slice())
Expand Down
21 changes: 21 additions & 0 deletions lib/src/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1452,6 +1452,9 @@ async fn install_to_filesystem_impl(state: &State, rootfs: &mut RootSetup) -> Re
// descriptors.
}

// Run this on every install as the penultimate step
install_finalize(&rootfs.physical_root_path).await?;

// Finalize mounted filesystems
if !rootfs.skip_finalize {
let bootfs = rootfs.boot.as_ref().map(|_| ("boot", "boot"));
Expand Down Expand Up @@ -1909,6 +1912,24 @@ pub(crate) async fn install_to_existing_root(opts: InstallToExistingRootOpts) ->

install_to_filesystem(opts, true).await
}

/// Implementation of `bootc install finalize`.
pub(crate) async fn install_finalize(target: &Utf8Path) -> Result<()> {
crate::cli::require_root(false)?;
let sysroot = ostree::Sysroot::new(Some(&gio::File::for_path(target)));
sysroot.load(gio::Cancellable::NONE)?;
let deployments = sysroot.deployments();
// Verify we find a deployment
if deployments.is_empty() {
anyhow::bail!("Failed to find deployment in {target}");
}

// For now that's it! We expect to add more validation/postprocessing
// later, such as munging `etc/fstab` if needed. See

Ok(())
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
4 changes: 4 additions & 0 deletions tests-integration/src/install.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::{
os::fd::AsRawFd,
path::{Path, PathBuf},
process::Command,
};

use anyhow::Result;
Expand Down Expand Up @@ -64,6 +65,9 @@ fn find_deployment_root() -> Result<Dir> {

// Hook relatively cheap post-install tests here
fn generic_post_install_verification() -> Result<()> {
let sh = xshell::Shell::new()?;
// This is our final sanity check
cmd!(sh, "bootc install finalize /").run()?;
assert!(Utf8Path::new("/ostree/repo").try_exists()?);
assert!(Utf8Path::new("/ostree/bootc/storage/overlay").try_exists()?);
Ok(())
Expand Down

0 comments on commit 91e0a70

Please sign in to comment.