From 85b2419f09014e8e4e408be6aa8330cee5cb4bae Mon Sep 17 00:00:00 2001 From: ckyrouac Date: Wed, 30 Apr 2025 11:22:31 -0400 Subject: [PATCH 1/2] 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 --- Makefile | 7 ++++ .../scripts/fedora-bootc-destructive-cleanup | 14 +++++++ lib/src/cli.rs | 3 +- lib/src/install.rs | 40 ++++++++++++++++--- systemd/bootc-destructive-cleanup.service | 12 ++++++ 5 files changed, 70 insertions(+), 6 deletions(-) create mode 100644 contrib/scripts/fedora-bootc-destructive-cleanup create mode 100644 systemd/bootc-destructive-cleanup.service diff --git a/Makefile b/Makefile index 8d515eb89..9b2d29619 100644 --- a/Makefile +++ b/Makefile @@ -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. diff --git a/contrib/scripts/fedora-bootc-destructive-cleanup b/contrib/scripts/fedora-bootc-destructive-cleanup new file mode 100644 index 000000000..5a07b8021 --- /dev/null +++ b/contrib/scripts/fedora-bootc-destructive-cleanup @@ -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 diff --git a/lib/src/cli.rs b/lib/src/cli.rs index cc3fa808e..44919d312 100644 --- a/lib/src/cli.rs +++ b/lib/src/cli.rs @@ -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 diff --git a/lib/src/install.rs b/lib/src/install.rs index aa3a99f19..9329b8552 100644 --- a/lib/src/install.rs +++ b/lib/src/install.rs @@ -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`. diff --git a/systemd/bootc-destructive-cleanup.service b/systemd/bootc-destructive-cleanup.service new file mode 100644 index 000000000..97cc93bc3 --- /dev/null +++ b/systemd/bootc-destructive-cleanup.service @@ -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 From b6300d858eabadd767afa77a4ea7a062977eade4 Mon Sep 17 00:00:00 2001 From: ckyrouac Date: Wed, 30 Apr 2025 11:25:54 -0400 Subject: [PATCH 2/2] reinstall: Enable post installation cleanup service Signed-off-by: ckyrouac --- system-reinstall-bootc/src/podman.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/system-reinstall-bootc/src/podman.rs b/system-reinstall-bootc/src/podman.rs index 05c418fe2..b5ddc5b8b 100644 --- a/system-reinstall-bootc/src/podman.rs +++ b/system-reinstall-bootc/src/podman.rs @@ -44,6 +44,9 @@ pub(crate) fn reinstall_command(image: &str, ssh_key_file: &str) -> Command { // The image is always pulled first, so let's avoid requiring the credentials to be baked // in the image for this check. "--skip-fetch-check", + // Always enable the systemd service to cleanup the previous install after booting into the + // bootc system for the first time + "--cleanup", ] .map(String::from) .to_vec();