Skip to content

Commit 91e0a70

Browse files
committed
install: Add a generic install finalize
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]>
1 parent 859bf9e commit 91e0a70

File tree

4 files changed

+54
-0
lines changed

4 files changed

+54
-0
lines changed

docs/src/bootc-install.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,26 @@ storage or filesystem setups, but reuses the "top half" of the logic.
161161
For example, a goal is to change [Anaconda](https://github.com/rhinstaller/anaconda/)
162162
to use this.
163163

164+
#### Postprocessing after to-filesystem
165+
166+
Some installation tools may want to inject additional data, such as adding
167+
an `/etc/hostname` into the target root. At the current time, bootc does
168+
not offer a direct API to do this. However, the backend for bootc is
169+
ostree, and it is possible to enumerate the deployments via ostree APIs.
170+
171+
We hope to provide a bootc-supported method to find the deployment in
172+
the future.
173+
174+
However, for tools that do perform any changes, there is a new
175+
`bootc install finalize` command which is optional, but recommended
176+
to run as the penultimate step before unmounting the target filesystem.
177+
178+
This command will perform some basic sanity checks and may also
179+
perform fixups on the target root. For example, a direction
180+
currently for bootc is to stop using `/etc/fstab`. While `install finalize`
181+
does not do this today, in the future it may automatically migrate
182+
`etc/fstab` to `rootflags` kernel arguments.
183+
164184
### Using `bootc install to-disk --via-loopback`
165185

166186
Because every `bootc` system comes with an opinionated default installation

lib/src/cli.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,12 @@ pub(crate) enum InstallOpts {
209209
/// will be wiped, but the content of the existing root will otherwise be retained, and will
210210
/// need to be cleaned up if desired when rebooted into the new root.
211211
ToExistingRoot(crate::install::InstallToExistingRootOpts),
212+
/// Execute this as the penultimate step of an installation using `install to-filesystem`.
213+
///
214+
Finalize {
215+
/// Path to the mounted root filesystem.
216+
root_path: Utf8PathBuf,
217+
},
212218
/// Intended for use in environments that are performing an ostree-based installation, not bootc.
213219
///
214220
/// In this scenario the installation may be missing bootc specific features such as
@@ -1121,6 +1127,9 @@ async fn run_from_opt(opt: Opt) -> Result<()> {
11211127
let rootfs = &Dir::open_ambient_dir("/", cap_std::ambient_authority())?;
11221128
crate::install::completion::run_from_anaconda(rootfs).await
11231129
}
1130+
InstallOpts::Finalize { root_path } => {
1131+
crate::install::install_finalize(&root_path).await
1132+
}
11241133
},
11251134
Opt::ExecInHostMountNamespace { args } => {
11261135
crate::install::exec_in_host_mountns(args.as_slice())

lib/src/install.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1452,6 +1452,9 @@ async fn install_to_filesystem_impl(state: &State, rootfs: &mut RootSetup) -> Re
14521452
// descriptors.
14531453
}
14541454

1455+
// Run this on every install as the penultimate step
1456+
install_finalize(&rootfs.physical_root_path).await?;
1457+
14551458
// Finalize mounted filesystems
14561459
if !rootfs.skip_finalize {
14571460
let bootfs = rootfs.boot.as_ref().map(|_| ("boot", "boot"));
@@ -1909,6 +1912,24 @@ pub(crate) async fn install_to_existing_root(opts: InstallToExistingRootOpts) ->
19091912

19101913
install_to_filesystem(opts, true).await
19111914
}
1915+
1916+
/// Implementation of `bootc install finalize`.
1917+
pub(crate) async fn install_finalize(target: &Utf8Path) -> Result<()> {
1918+
crate::cli::require_root(false)?;
1919+
let sysroot = ostree::Sysroot::new(Some(&gio::File::for_path(target)));
1920+
sysroot.load(gio::Cancellable::NONE)?;
1921+
let deployments = sysroot.deployments();
1922+
// Verify we find a deployment
1923+
if deployments.is_empty() {
1924+
anyhow::bail!("Failed to find deployment in {target}");
1925+
}
1926+
1927+
// For now that's it! We expect to add more validation/postprocessing
1928+
// later, such as munging `etc/fstab` if needed. See
1929+
1930+
Ok(())
1931+
}
1932+
19121933
#[cfg(test)]
19131934
mod tests {
19141935
use super::*;

tests-integration/src/install.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use std::{
22
os::fd::AsRawFd,
33
path::{Path, PathBuf},
4+
process::Command,
45
};
56

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

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

0 commit comments

Comments
 (0)