From 77fd56ad3f536480ff9c0f4bfbaa68ff8d9c2dde Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Sat, 30 Sep 2023 15:39:08 -0400 Subject: [PATCH] WIP: Add support for `--replace-mode=alongside` for ostree target Ironically our support for `--replace-mode=alongside` breaks when we're targeting an already extant ostree host, because when we first blow away the `/boot` directory, this means the ostree stack loses its knowledge that we're in a booted deployment, and will attempt to GC it... https://github.com/ostreedev/ostree-rs-ext/pull/550/commits/8fa019bfa821303cfb7a7f069ae2320f4c3800fa is a key part of the fix for that. However, a notable improvement we can do here is to grow this whole thing into a real "factory reset" mode, and this will be a compelling answer to https://github.com/coreos/fedora-coreos-tracker/issues/399 To implement this though we need to support configuring the stateroot and not just hardcode `default`. Signed-off-by: Colin Walters --- lib/src/install.rs | 32 +++++++++++++++++++++++--------- lib/src/utils.rs | 28 +++++++++++++++++++++++++++- 2 files changed, 50 insertions(+), 10 deletions(-) diff --git a/lib/src/install.rs b/lib/src/install.rs index ae5a6d3e5..ca9ebc830 100644 --- a/lib/src/install.rs +++ b/lib/src/install.rs @@ -588,11 +588,17 @@ async fn initialize_ostree_root(state: &State, root_setup: &RootSetup) -> Result let stateroot = state.stateroot(); - Task::new_and_run( - "Initializing ostree layout", - "ostree", - ["admin", "init-fs", "--modern", rootfs.as_str()], - )?; + let has_ostree = rootfs_dir.try_exists("ostree/repo")?; + if !has_ostree { + Task::new_and_run( + "Initializing ostree layout", + "ostree", + ["admin", "init-fs", "--modern", rootfs.as_str()], + )?; + } else { + println!("Reusing extant ostree layout"); + let _ = crate::utils::open_dir_remount_rw(rootfs_dir, "sysroot".into())?; + } // And also label /boot AKA xbootldr, if it exists let bootdir = rootfs.join("boot"); @@ -617,9 +623,15 @@ async fn initialize_ostree_root(state: &State, root_setup: &RootSetup) -> Result let sysroot = ostree::Sysroot::new(Some(&gio::File::for_path(rootfs))); sysroot.load(cancellable)?; - sysroot - .init_osname(stateroot, cancellable) - .context("initializing stateroot")?; + let stateroot_exists = rootfs_dir.try_exists(format!("ostree/deploy/{stateroot}"))?; + if stateroot_exists { + anyhow::bail!("Cannot redeploy over extant stateroot {stateroot}"); + } else { + Task::new("Initializing stateroot", "ostree") + .args(["admin", "os-init", stateroot, "--sysroot", "."]) + .cwd(rootfs_dir)? + .run()?; + } let sysroot_dir = Dir::reopen_dir(&crate::utils::sysroot_fd(&sysroot))?; @@ -740,6 +752,7 @@ async fn install_container( options.kargs = Some(kargs.as_slice()); options.target_imgref = Some(&state.target_imgref); options.proxy_cfg = proxy_cfg; + options.no_clean = root_setup.has_ostree; let imgstate = crate::utils::async_task_with_spinner( "Deploying container image", ostree_container::deploy::deploy(&sysroot, stateroot, &src_imageref, Some(options)), @@ -1511,7 +1524,8 @@ fn remove_all_in_dir_no_xdev(d: &Dir) -> Result<()> { #[context("Removing boot directory content")] fn clean_boot_directories(rootfs: &Dir) -> Result<()> { - let bootdir = rootfs.open_dir(BOOT).context("Opening /boot")?; + let bootdir = + crate::utils::open_dir_remount_rw(rootfs, BOOT.into()).context("Opening /boot")?; // This should not remove /boot/efi note. remove_all_in_dir_no_xdev(&bootdir)?; if ARCH_USES_EFI { diff --git a/lib/src/utils.rs b/lib/src/utils.rs index 33403afec..99eae0808 100644 --- a/lib/src/utils.rs +++ b/lib/src/utils.rs @@ -5,7 +5,9 @@ use std::process::Command; use std::time::Duration; use anyhow::{Context, Result}; -use cap_std_ext::cap_std::fs::Dir; +use camino::Utf8Path; +use cap_std_ext::{cap_std::fs::Dir, prelude::CapStdExtCommandExt}; +use fn_error_context::context; use indicatif::HumanDuration; use libsystemd::logging::journal_print; use ostree::glib; @@ -57,6 +59,30 @@ pub(crate) fn find_mount_option<'a>( .next() } +/// Given a target directory, if it's a read-only mount, then remount it writable +#[context("Opening {target} with writable mount")] +pub(crate) fn open_dir_remount_rw(root: &Dir, target: &Utf8Path) -> Result { + if ostree_ext::mountutil::is_mountpoint(root, target)?.unwrap_or_default() { + tracing::debug!("Target {target} is a mountpoint, remounting rw"); + let st = Command::new("mount") + .args(["-o", "remount,rw", target.as_str()]) + .cwd_dir(root.try_clone()?) + .status()?; + if !st.success() { + anyhow::bail!("Failed to remount: {st:?}"); + } + } + root.open_dir(target).map_err(anyhow::Error::new) +} + +/// Run a command in the host mount namespace +#[allow(dead_code)] +pub(crate) fn run_in_host_mountns(cmd: &str) -> Command { + let mut c = Command::new("nsenter"); + c.args(["-m", "-t", "1", "--", cmd]); + c +} + pub(crate) fn spawn_editor(tmpf: &tempfile::NamedTempFile) -> Result<()> { let editor_variables = ["EDITOR"]; // These roughly match https://github.com/systemd/systemd/blob/769ca9ab557b19ee9fb5c5106995506cace4c68f/src/shared/edit-util.c#L275