From 29d0b99cf948103becbbfcb5f4cb73859ca7d40d Mon Sep 17 00:00:00 2001 From: Chris Kyrouac Date: Mon, 1 Jul 2024 13:29:08 -0400 Subject: [PATCH] WIP: Retrieve bound images when staging new image Signed-off-by: Chris Kyrouac --- lib/src/boundimage.rs | 116 ++++++++++++++++++++++++++++++++++++++++++ lib/src/deploy.rs | 28 ++++++---- lib/src/install.rs | 16 ++++++ lib/src/lib.rs | 1 + lib/src/task.rs | 9 ++-- 5 files changed, 156 insertions(+), 14 deletions(-) create mode 100644 lib/src/boundimage.rs diff --git a/lib/src/boundimage.rs b/lib/src/boundimage.rs new file mode 100644 index 00000000..ceaa76fa --- /dev/null +++ b/lib/src/boundimage.rs @@ -0,0 +1,116 @@ +use anyhow::Result; + +use ostree_ext::ostree::Deployment; +use ostree_ext::sysroot::SysrootLock; + +use std::fs; +use std::path::Path; + +use crate::task::Task; + +use tempfile::TempDir; + +const BOOTC_QUADLET_DIR: &'static str = "/etc/containers/systemd/bootc"; +const QUADLET_BINARY: &'static str = "/usr/lib/systemd/system-generators/podman-system-generator"; +const SYSTEMD_DIR: &'static str = "/etc/systemd/system"; + +pub(crate) struct BoundImageManager { + quadlet_unit_dir: String, + units: Vec, + temp_dir: TempDir, +} + +impl BoundImageManager { + pub(crate) fn new(deployment: &Deployment, sysroot: &SysrootLock) -> Result { + let deployment_dir = sysroot.deployment_dirpath(&deployment); + let quadlet_unit_dir = format!("/{deployment_dir}/{BOOTC_QUADLET_DIR}"); + + let temp_dir = TempDir::new()?; + let bound_image_manager = BoundImageManager { + quadlet_unit_dir, + units: Vec::new(), + temp_dir, + }; + Ok(bound_image_manager) + } + + pub(crate) fn run(&mut self) -> Result<()> { + if Path::new(&self.quadlet_unit_dir).exists() { + self.run_quadlet()?; + self.move_units()?; + self.restart_systemd()?; + self.start_new_services()?; + } + + Ok(()) + } + + // Run podman-system-generator to generate the systemd units + // the output is written to a temporary directory + // in order to track the generated units. + // The generated units need to be moved to /etc/systemd/system + // to be started by systemd. + fn run_quadlet(&self) -> Result<()> { + Task::new( + format!("Running quadlet on {:#}", self.quadlet_unit_dir), + QUADLET_BINARY, + ) + .arg(self.temp_dir.path()) + .env(&"QUADLET_UNIT_DIRS".to_string(), &self.quadlet_unit_dir) + .run()?; + + Ok(()) + } + + fn move_units(&mut self) -> Result<()> { + let entries = fs::read_dir(self.temp_dir.path())?; + for bound_image in entries { + let bound_image = bound_image?; + let bound_image_path = bound_image.path(); + let unit_name = bound_image_path.file_name().unwrap().to_str().unwrap(); + + //move the unit file from the bootc subdirectory to the root systemd directory + let systemd_dst = format!("{SYSTEMD_DIR}/{unit_name}"); + if !Path::new(systemd_dst.as_str()).exists() { + fs::copy(&bound_image_path, systemd_dst)?; + } + + self.units.push(unit_name.to_string()); + } + + Ok(()) + } + + fn restart_systemd(&self) -> Result<()> { + Task::new_and_run("Reloading systemd", "/usr/bin/systemctl", ["daemon-reload"])?; + Ok(()) + } + + fn start_new_services(&self) -> Result<()> { + //TODO: do this in parallel + for unit in &self.units { + Task::new_and_run( + format!("Starting target: {:#}", unit), + "/usr/bin/systemctl", + ["start", unit], + )?; + } + Ok(()) + } +} + +impl Drop for BoundImageManager { + //remove the generated units from the root systemd directory + //and stop them to remove the services from systemd + fn drop(&mut self) { + for unit in &self.units { + //TODO: error handling + let _ = fs::remove_file(format!("{SYSTEMD_DIR}/{unit}")); + let _ = Task::new_and_run( + format!("Starting target: {:#}", unit), + "/usr/bin/systemctl", + ["stop", unit], + ); + } + } +} diff --git a/lib/src/deploy.rs b/lib/src/deploy.rs index f0508f4f..6f7f9a86 100644 --- a/lib/src/deploy.rs +++ b/lib/src/deploy.rs @@ -278,20 +278,21 @@ async fn deploy( image: &ImageState, origin: &glib::KeyFile, opts: Option>, -) -> Result<()> { +) -> Result { let stateroot = Some(stateroot); let opts = opts.unwrap_or_default(); // Copy to move into thread let cancellable = gio::Cancellable::NONE; - let _new_deployment = sysroot.stage_tree_with_options( - stateroot, - image.ostree_commit.as_str(), - Some(origin), - merge_deployment, - &opts, - cancellable, - )?; - Ok(()) + return sysroot + .stage_tree_with_options( + stateroot, + image.ostree_commit.as_str(), + Some(origin), + merge_deployment, + &opts, + cancellable, + ) + .map_err(Into::into); } #[context("Generating origin")] @@ -317,7 +318,7 @@ pub(crate) async fn stage( ) -> Result<()> { let merge_deployment = sysroot.merge_deployment(Some(stateroot)); let origin = origin_from_imageref(spec.image)?; - crate::deploy::deploy( + let deployment = crate::deploy::deploy( sysroot, merge_deployment.as_ref(), stateroot, @@ -326,6 +327,11 @@ pub(crate) async fn stage( opts, ) .await?; + + //TODO: does this need to be mutable? + let mut bound_image_manager = crate::boundimage::BoundImageManager::new(&deployment, sysroot)?; + bound_image_manager.run()?; + crate::deploy::cleanup(sysroot).await?; println!("Queued for next boot: {:#}", spec.image); if let Some(version) = image.version.as_deref() { diff --git a/lib/src/install.rs b/lib/src/install.rs index 4369a4fa..15bd6b1b 100644 --- a/lib/src/install.rs +++ b/lib/src/install.rs @@ -1204,6 +1204,22 @@ async fn install_to_filesystem_impl(state: &State, rootfs: &mut RootSetup) -> Re anyhow::Ok(()) }) .context("Writing aleph version")?; + + // TODO: add code to run quadlet/systemd against the bootc-bound-image directory + // let bound = query_bound_state(&inst.deployment)?; + // bound.print(); + // if !bound.is_empty() { + // println!(); + // Task::new("Mounting deployment /var", "mount") + // .args(["--bind", ".", "/var"]) + // .cwd(&inst.var)? + // .run()?; + // // podman needs this + // Task::new("Initializing /var/tmp", "systemd-tmpfiles") + // .args(["--create", "--boot", "--prefix=/var/tmp"]) + // .verbose() + // .run()?; + // crate::deploy::fetch_bound_state(&bound).await?; } crate::bootloader::install_via_bootupd(&rootfs.device, &rootfs.rootfs, &state.config_opts)?; diff --git a/lib/src/lib.rs b/lib/src/lib.rs index f2f2c60d..f7ae4004 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -18,6 +18,7 @@ #![allow(clippy::needless_borrows_for_generic_args)] pub mod cli; +mod boundimage; pub(crate) mod deploy; pub(crate) mod generator; pub(crate) mod journal; diff --git a/lib/src/task.rs b/lib/src/task.rs index 19ebc447..93561d11 100644 --- a/lib/src/task.rs +++ b/lib/src/task.rs @@ -1,7 +1,5 @@ use std::{ - ffi::OsStr, - io::{Seek, Write}, - process::{Command, Stdio}, + ffi::OsStr, io::{Seek, Write}, process::{Command, Stdio} }; use anyhow::{Context, Result}; @@ -76,6 +74,11 @@ impl Task { self } + pub(crate) fn env(mut self, k: &str, v: &str) -> Self { + self.cmd.env(k, v); + self + } + pub(crate) fn args>(mut self, args: impl IntoIterator) -> Self { self.cmd.args(args); self