@@ -69,6 +69,8 @@ const BOOT: &str = "boot";
6969const RUN_BOOTC : & str = "/run/bootc" ;
7070/// The default path for the host rootfs
7171const ALONGSIDE_ROOT_MOUNT : & str = "/target" ;
72+ /// Global flag to signal the booted system was provisioned via an alongside bootc install
73+ const DESTRUCTIVE_CLEANUP : & str = "bootc-destructive-cleanup" ;
7274/// This is an ext4 special directory we need to ignore.
7375const LOST_AND_FOUND : & str = "lost+found" ;
7476/// The filename of the composefs EROFS superblock; TODO move this into ostree
@@ -335,6 +337,11 @@ pub(crate) struct InstallToExistingRootOpts {
335337 #[ clap( long) ]
336338 pub ( crate ) acknowledge_destructive : bool ,
337339
340+ /// Add the bootc-destructive-cleanup systemd service to delete files from
341+ /// the previous install on first boot
342+ #[ clap( long) ]
343+ pub ( crate ) cleanup : bool ,
344+
338345 /// Path to the mounted root; this is now not necessary to provide.
339346 /// Historically it was necessary to ensure the host rootfs was mounted at here
340347 /// via e.g. `-v /:/target`.
@@ -1460,7 +1467,11 @@ impl BoundImages {
14601467 }
14611468}
14621469
1463- async fn install_to_filesystem_impl ( state : & State , rootfs : & mut RootSetup ) -> Result < ( ) > {
1470+ async fn install_to_filesystem_impl (
1471+ state : & State ,
1472+ rootfs : & mut RootSetup ,
1473+ cleanup : Cleanup ,
1474+ ) -> Result < ( ) > {
14641475 if matches ! ( state. selinux_state, SELinuxFinalState :: ForceTargetDisabled ) {
14651476 rootfs. kargs . push ( "selinux=0" . to_string ( ) ) ;
14661477 }
@@ -1489,6 +1500,7 @@ async fn install_to_filesystem_impl(state: &State, rootfs: &mut RootSetup) -> Re
14891500 let bound_images = BoundImages :: from_state ( state) . await ?;
14901501
14911502 // Initialize the ostree sysroot (repo, stateroot, etc.)
1503+
14921504 {
14931505 let ( sysroot, has_ostree, imgstore) = initialize_ostree_root ( state, rootfs) . await ?;
14941506
@@ -1502,9 +1514,16 @@ async fn install_to_filesystem_impl(state: &State, rootfs: &mut RootSetup) -> Re
15021514 & imgstore,
15031515 )
15041516 . await ?;
1517+
1518+ if matches ! ( cleanup, Cleanup :: TriggerOnNextBoot ) {
1519+ let sysroot_dir = crate :: utils:: sysroot_dir ( & sysroot) ?;
1520+ tracing:: debug!( "Writing {DESTRUCTIVE_CLEANUP}" ) ;
1521+ sysroot_dir. atomic_write ( format ! ( "etc/{}" , DESTRUCTIVE_CLEANUP ) , b"" ) ?;
1522+ }
1523+
15051524 // We must drop the sysroot here in order to close any open file
15061525 // descriptors.
1507- }
1526+ } ;
15081527
15091528 // Run this on every install as the penultimate step
15101529 install_finalize ( & rootfs. physical_root_path ) . await ?;
@@ -1570,7 +1589,15 @@ pub(crate) async fn install_to_disk(mut opts: InstallToDiskOpts) -> Result<()> {
15701589 ( rootfs, loopback_dev)
15711590 } ;
15721591
1573- install_to_filesystem_impl ( & state, & mut rootfs) . await ?;
1592+ install_to_filesystem_impl ( & state, & mut rootfs, Cleanup :: Skip ) . await ?;
1593+
1594+ // Finalize mounted filesystems
1595+ if !rootfs. skip_finalize {
1596+ let bootfs = rootfs. boot . as_ref ( ) . map ( |_| ( "boot" , "boot" ) ) ;
1597+ for ( fsname, fs) in std:: iter:: once ( ( "root" , "." ) ) . chain ( bootfs) {
1598+ finalize_filesystem ( fsname, & rootfs. physical_root , fs) ?;
1599+ }
1600+ }
15741601
15751602 // Drop all data about the root except the bits we need to ensure any file descriptors etc. are closed.
15761603 let ( root_path, luksdev) = rootfs. into_storage ( ) ;
@@ -1740,11 +1767,17 @@ fn warn_on_host_root(rootfs_fd: &Dir) -> Result<()> {
17401767 Ok ( ( ) )
17411768}
17421769
1770+ pub enum Cleanup {
1771+ Skip ,
1772+ TriggerOnNextBoot ,
1773+ }
1774+
17431775/// Implementation of the `bootc install to-filsystem` CLI command.
17441776#[ context( "Installing to filesystem" ) ]
17451777pub ( crate ) async fn install_to_filesystem (
17461778 opts : InstallToFilesystemOpts ,
17471779 targeting_host_root : bool ,
1780+ cleanup : Cleanup ,
17481781) -> Result < ( ) > {
17491782 // Gather global state, destructuring the provided options.
17501783 // IMPORTANT: We might re-execute the current process in this function (for SELinux among other things)
@@ -1950,7 +1983,15 @@ pub(crate) async fn install_to_filesystem(
19501983 skip_finalize,
19511984 } ;
19521985
1953- install_to_filesystem_impl ( & state, & mut rootfs) . await ?;
1986+ install_to_filesystem_impl ( & state, & mut rootfs, cleanup) . await ?;
1987+
1988+ // Finalize mounted filesystems
1989+ if !rootfs. skip_finalize {
1990+ let bootfs = rootfs. boot . as_ref ( ) . map ( |_| ( "boot" , "boot" ) ) ;
1991+ for ( fsname, fs) in std:: iter:: once ( ( "root" , "." ) ) . chain ( bootfs) {
1992+ finalize_filesystem ( fsname, & rootfs. physical_root , fs) ?;
1993+ }
1994+ }
19541995
19551996 // Drop all data about the root except the path to ensure any file descriptors etc. are closed.
19561997 drop ( rootfs) ;
@@ -1961,6 +2002,11 @@ pub(crate) async fn install_to_filesystem(
19612002}
19622003
19632004pub ( crate ) async fn install_to_existing_root ( opts : InstallToExistingRootOpts ) -> Result < ( ) > {
2005+ let cleanup = match opts. cleanup {
2006+ true => Cleanup :: TriggerOnNextBoot ,
2007+ false => Cleanup :: Skip ,
2008+ } ;
2009+
19642010 let opts = InstallToFilesystemOpts {
19652011 filesystem_opts : InstallTargetFilesystemOpts {
19662012 root_path : opts. root_path ,
@@ -1975,7 +2021,7 @@ pub(crate) async fn install_to_existing_root(opts: InstallToExistingRootOpts) ->
19752021 config_opts : opts. config_opts ,
19762022 } ;
19772023
1978- install_to_filesystem ( opts, true ) . await
2024+ install_to_filesystem ( opts, true , cleanup ) . await
19792025}
19802026
19812027/// Implementation of `bootc install finalize`.
0 commit comments