From 2f5feafa56288c7669dac93759c98dc1ed94cbc6 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 | 129 ++++++++++++++++++++++++++++++++++++++++++ lib/src/deploy.rs | 26 +++++---- lib/src/install.rs | 16 ++++++ lib/src/lib.rs | 1 + lib/src/task.rs | 9 ++- 5 files changed, 167 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 000000000..595fe050b --- /dev/null +++ b/lib/src/boundimage.rs @@ -0,0 +1,129 @@ +use anyhow::Result; + +use ostree_ext::ostree::Deployment; +use ostree_ext::sysroot::SysrootLock; + +use std::fs; +use std::path::Path; + +use crate::task::Task; + + +const BOOTC_SYSTEMD_DIR: &'static str = "/etc/systemd/system/bootc"; +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, +} + +impl BoundImageManager { + pub(crate) fn new(deployment: Deployment, sysroot: &SysrootLock) -> BoundImageManager { + let deployment_dir = sysroot.deployment_dirpath(&deployment); + let quadlet_unit_dir = "/".to_string() + deployment_dir.as_str() + BOOTC_QUADLET_DIR.to_string().as_str(); + + BoundImageManager { + quadlet_unit_dir, + units: Vec::new(), + } + } + + pub(crate) fn run(&mut self) -> Result<()> { + match self.sync_images() { + Ok(_) => println!("Successfully synced images"), + Err(e) => { + self.clean_up()?; + drop(e); + } + } + + Ok(()) + } + + fn sync_images(&mut self) -> Result<()> { + if Path::new(&self.quadlet_unit_dir).exists() { + self.run_quadlet()?; + self.move_units()?; + self.restart_systemd()?; + self.start_new_services()?; + self.clean_up()?; + } + + Ok(()) + } + + // Run podman-system-generator to generate the systemd units + // the output is written to /etc/systemd/system/bootc + // 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<()> { + fs::create_dir_all(BOOTC_SYSTEMD_DIR)?; + + Task::new( + format!("Running quadlet on {:#}", self.quadlet_unit_dir), + QUADLET_BINARY, + ) + .arg(BOOTC_SYSTEMD_DIR) + .env(&"QUADLET_UNIT_DIRS".to_string(), &self.quadlet_unit_dir) + .run()?; + + Ok(()) + } + + fn move_units(&mut self) -> Result<()> { + let entries = fs::read_dir(BOOTC_SYSTEMD_DIR)?; + 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::rename(bound_image_path.clone(), systemd_dst.clone())?; + } + + 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(()) + } + + fn clean_up(&self) -> Result<()> { + //remove the temp directory used for the generated units + if Path::new(BOOTC_SYSTEMD_DIR).exists() { + fs::remove_dir_all(BOOTC_SYSTEMD_DIR)? + } + + //remove the generated units from the root systemd directory + for unit in &self.units { + fs::remove_file(format!("{}/{}", SYSTEMD_DIR, unit))?; + } + + Ok(()) + } +} diff --git a/lib/src/deploy.rs b/lib/src/deploy.rs index f0508f4fe..e335e7005 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,9 @@ pub(crate) async fn stage( opts, ) .await?; + + crate::boundimage::BoundImageManager::new(deployment, sysroot).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 4369a4fa2..15bd6b1b2 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 f2f2c60d2..f7ae40049 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 19ebc4474..3768d74d7 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: &String, v: &String) -> Self { + self.cmd.env(k, v); + self + } + pub(crate) fn args>(mut self, args: impl IntoIterator) -> Self { self.cmd.args(args); self