1
0
mirror of https://github.com/containers/bootc.git synced 2026-02-05 06:45:13 +01:00

install: Add cleanup option to install to-existing-root

When set, the bootc-destructive-cleanup flag is added to /sysroot/etc
which enables the bootc-destructive-cleanup systemd service to remove
the previous installation's rpm packages and podman containers/images.

The service is only installed on fedora based systems.

Signed-off-by: ckyrouac <ckyrouac@redhat.com>
This commit is contained in:
ckyrouac
2025-04-30 11:22:31 -04:00
parent b1fb35c0f4
commit 85b2419f09
5 changed files with 70 additions and 6 deletions

View File

@@ -37,6 +37,13 @@ install:
# Copy dracut and systemd config files
cp -Prf baseimage/dracut $(DESTDIR)$(prefix)/share/doc/bootc/baseimage/dracut
cp -Prf baseimage/systemd $(DESTDIR)$(prefix)/share/doc/bootc/baseimage/systemd
# Install fedora-bootc-destructive-cleanup in fedora derivatives
ID=$$(. /usr/lib/os-release && echo $$ID); \
ID_LIKE=$$(. /usr/lib/os-release && echo $$ID_LIKE); \
if [ "$$ID" = "fedora" ] || [[ "$$ID_LIKE" == *"fedora"* ]]; then \
ln -s ../bootc-destructive-cleanup.service $(DESTDIR)/$(prefix)/lib/systemd/system/multi-user.target.wants/bootc-destructive-cleanup.service; \
install -D -m 0755 -t $(DESTDIR)/$(prefix)/lib/bootc contrib/scripts/fedora-bootc-destructive-cleanup; \
fi
# Run this to also take over the functionality of `ostree container` for example.
# Only needed for OS/distros that have callers invoking `ostree container` and not bootc.

View File

@@ -0,0 +1,14 @@
#!/bin/bash
# An implementation of --cleanup for bootc installs on Fedora derivatives
set -xeuo pipefail
# Remove all RPMs installed in the physical root (i.e. the previous OS)
mount -o remount,rw /sysroot
rpm -qa --root=/sysroot --dbpath=/usr/lib/sysimage/rpm | xargs rpm -e --root=/sysroot --dbpath=/usr/lib/sysimage/rpm
# Remove all container images (including the one that was used to install)
# Note that this does not remove stopped containers, and so some storage
# may leak. This may change in the future.
mount --bind -o rw /sysroot/var/lib/containers /var/lib/containers
podman system prune --all -f

View File

@@ -1154,7 +1154,8 @@ async fn run_from_opt(opt: Opt) -> Result<()> {
#[cfg(feature = "install-to-disk")]
InstallOpts::ToDisk(opts) => crate::install::install_to_disk(opts).await,
InstallOpts::ToFilesystem(opts) => {
crate::install::install_to_filesystem(opts, false).await
crate::install::install_to_filesystem(opts, false, crate::install::Cleanup::Skip)
.await
}
InstallOpts::ToExistingRoot(opts) => {
crate::install::install_to_existing_root(opts).await

View File

@@ -69,6 +69,8 @@ const BOOT: &str = "boot";
const RUN_BOOTC: &str = "/run/bootc";
/// The default path for the host rootfs
const ALONGSIDE_ROOT_MOUNT: &str = "/target";
/// Global flag to signal the booted system was provisioned via an alongside bootc install
const DESTRUCTIVE_CLEANUP: &str = "bootc-destructive-cleanup";
/// This is an ext4 special directory we need to ignore.
const LOST_AND_FOUND: &str = "lost+found";
/// The filename of the composefs EROFS superblock; TODO move this into ostree
@@ -335,6 +337,11 @@ pub(crate) struct InstallToExistingRootOpts {
#[clap(long)]
pub(crate) acknowledge_destructive: bool,
/// Add the bootc-destructive-cleanup systemd service to delete files from
/// the previous install on first boot
#[clap(long)]
pub(crate) cleanup: bool,
/// Path to the mounted root; this is now not necessary to provide.
/// Historically it was necessary to ensure the host rootfs was mounted at here
/// via e.g. `-v /:/target`.
@@ -1460,7 +1467,11 @@ impl BoundImages {
}
}
async fn install_to_filesystem_impl(state: &State, rootfs: &mut RootSetup) -> Result<()> {
async fn install_to_filesystem_impl(
state: &State,
rootfs: &mut RootSetup,
cleanup: Cleanup,
) -> Result<()> {
if matches!(state.selinux_state, SELinuxFinalState::ForceTargetDisabled) {
rootfs.kargs.push("selinux=0".to_string());
}
@@ -1489,6 +1500,7 @@ async fn install_to_filesystem_impl(state: &State, rootfs: &mut RootSetup) -> Re
let bound_images = BoundImages::from_state(state).await?;
// Initialize the ostree sysroot (repo, stateroot, etc.)
{
let (sysroot, has_ostree, imgstore) = initialize_ostree_root(state, rootfs).await?;
@@ -1502,9 +1514,16 @@ async fn install_to_filesystem_impl(state: &State, rootfs: &mut RootSetup) -> Re
&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"")?;
}
// 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?;
@@ -1570,7 +1589,7 @@ pub(crate) async fn install_to_disk(mut opts: InstallToDiskOpts) -> Result<()> {
(rootfs, loopback_dev)
};
install_to_filesystem_impl(&state, &mut rootfs).await?;
install_to_filesystem_impl(&state, &mut rootfs, Cleanup::Skip).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();
@@ -1740,11 +1759,17 @@ fn warn_on_host_root(rootfs_fd: &Dir) -> Result<()> {
Ok(())
}
pub enum Cleanup {
Skip,
TriggerOnNextBoot,
}
/// Implementation of the `bootc install to-filsystem` CLI command.
#[context("Installing to filesystem")]
pub(crate) async fn install_to_filesystem(
opts: InstallToFilesystemOpts,
targeting_host_root: bool,
cleanup: Cleanup,
) -> Result<()> {
// Gather global state, destructuring the provided options.
// IMPORTANT: We might re-execute the current process in this function (for SELinux among other things)
@@ -1950,7 +1975,7 @@ pub(crate) async fn install_to_filesystem(
skip_finalize,
};
install_to_filesystem_impl(&state, &mut rootfs).await?;
install_to_filesystem_impl(&state, &mut rootfs, cleanup).await?;
// Drop all data about the root except the path to ensure any file descriptors etc. are closed.
drop(rootfs);
@@ -1961,6 +1986,11 @@ pub(crate) async fn install_to_filesystem(
}
pub(crate) async fn install_to_existing_root(opts: InstallToExistingRootOpts) -> Result<()> {
let cleanup = match opts.cleanup {
true => Cleanup::TriggerOnNextBoot,
false => Cleanup::Skip,
};
let opts = InstallToFilesystemOpts {
filesystem_opts: InstallTargetFilesystemOpts {
root_path: opts.root_path,
@@ -1975,7 +2005,7 @@ pub(crate) async fn install_to_existing_root(opts: InstallToExistingRootOpts) ->
config_opts: opts.config_opts,
};
install_to_filesystem(opts, true).await
install_to_filesystem(opts, true, cleanup).await
}
/// Implementation of `bootc install finalize`.

View File

@@ -0,0 +1,12 @@
[Unit]
Description=Cleanup previous the installation after an alongside installation
Documentation=man:bootc(8)
ConditionPathExists=/sysroot/etc/bootc-destructive-cleanup
[Service]
Type=oneshot
ExecStart=/usr/lib/bootc/fedora-bootc-destructive-cleanup
PrivateMounts=true
[Install]
WantedBy=multi-user.target