Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
14 changes: 14 additions & 0 deletions contrib/scripts/fedora-bootc-destructive-cleanup
Original file line number Diff line number Diff line change
@@ -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
3 changes: 2 additions & 1 deletion lib/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
40 changes: 35 additions & 5 deletions lib/src/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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`.
Expand Down Expand Up @@ -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());
}
Expand Down Expand Up @@ -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?;

Expand All @@ -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)?;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fine as is, really. It's just interesting to note that it's equivalent to rootfs.physical_root and I think that one's clearer. would probably make sense to try to port more things to it.

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?;
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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);
Expand All @@ -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,
Expand All @@ -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`.
Expand Down
3 changes: 3 additions & 0 deletions system-reinstall-bootc/src/podman.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
12 changes: 12 additions & 0 deletions systemd/bootc-destructive-cleanup.service
Original file line number Diff line number Diff line change
@@ -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