use std::env::set_current_dir; use std::fs; use std::path::PathBuf; use fs_extra::dir; use futures_util::StreamExt; use futures_util::TryStreamExt; use glob::glob; use gpgme::{Context, Protocol, SignMode}; use names::Generator; use podman_api::opts::ContainerCreateOpts; use podman_api::opts::ImageBuildOpts; use podman_api::Podman; use crate::config::SecurityConfig; use crate::errors::AppResult; use crate::utils::{uid, ShellCommand}; pub const GENERATEFILE: &str = include_str!("../Generatefile"); #[derive(Clone, Debug)] pub enum GenerateKind { Podman { image: String }, Host, } impl GenerateKind { pub fn image(&self) -> Option<&str> { match self { Self::Podman { image } => Some(image), Self::Host => None, } } } pub struct PacmanRepository { pub kind: GenerateKind, pub name: String, pub tempdir: String, pub out: PathBuf, pub repo: PathBuf, pub security: SecurityConfig, } impl PacmanRepository { pub fn new>( kind: GenerateKind, name: S, out: P, repo: P, security: SecurityConfig, ) -> Self { let mut generator = Generator::with_naming(names::Name::Numbered); Self { kind, name: name.to_string(), tempdir: generator.next().unwrap(), out: out.into(), repo: repo.into(), security, } } pub async fn generate(&self) -> AppResult<()> { fs::create_dir_all(&self.repo)?; match &self.kind { GenerateKind::Podman { .. } => self.podman_generate().await?, GenerateKind::Host => self.host_generate()?, } if self.security.sign { let packages: Vec = glob(&format!("{}/*.pkg.tar.*", self.repo.display()))? .filter_map(Result::ok) .collect(); let mut gpg_ctx = Context::from_protocol(Protocol::OpenPgp)?; gpg_ctx.set_armor(true); for package in packages { let contents = fs::read(&package)?; let mut buffer = Vec::new(); gpg_ctx.sign(SignMode::Detached, &contents, &mut buffer)?; let sigfile = format!("{}.sig", package.display()); fs::write(sigfile, buffer)?; } } Ok(()) } pub fn host_generate(&self) -> AppResult<()> { if self.repo.exists() { fs::remove_dir_all(&self.repo)?; } fs::create_dir_all(&self.repo)?; let generate_path = std::env::temp_dir().join(&self.tempdir); fs::create_dir_all(&generate_path)?; let copy_opts = dir::CopyOptions { content_only: true, ..Default::default() }; dir::copy(&self.out, &generate_path, ©_opts)?; let mlc_dir = std::env::current_dir()?; set_current_dir(&generate_path)?; let files: Vec = glob("*.pkg.tar.*")? .filter_map(Result::ok) .map(|p| p.display().to_string()) .collect(); ShellCommand::repo_add() .arg(format!("{}.db.tar.gz", self.name)) .args(files) .spawn()? .wait()?; set_current_dir(mlc_dir)?; let copy_opts = dir::CopyOptions { content_only: true, ..Default::default() }; dir::copy(&generate_path, &self.repo, ©_opts)?; fs::remove_dir_all(&generate_path)?; Ok(()) } pub async fn podman_generate(&self) -> AppResult<()> { let uid = uid(); let podman = Podman::new(format!("unix:///run/user/{uid}/podman/podman.sock"))?; let image = self.kind.image().unwrap(); let generatefile = fs::read_to_string(".mlc/Generatefile")?; let generatefile_template = liquid::ParserBuilder::with_stdlib() .build()? .parse(&generatefile)?; let generatefile_values = liquid::object!({ "image": image, "name": &self.name, "repo": self.repo.file_name().unwrap().to_str().unwrap(), }); let generatefile = generatefile_template.render(&generatefile_values)?; let generate_path = std::env::temp_dir().join(&self.tempdir); fs::create_dir_all(&generate_path)?; fs::write(generate_path.join("Generatefile"), generatefile)?; dir::copy(&self.out, &generate_path, &dir::CopyOptions::new())?; let opts = &ImageBuildOpts::builder(generate_path.to_string_lossy()) .dockerfile("Generatefile") .tag(&self.tempdir) .build(); let images = podman.images(); match images.build(opts) { Ok(mut generate_stream) => { while let Some(chunk) = generate_stream.next().await { match chunk { Ok(chunk) => println!("{chunk:?}"), Err(e) => eprintln!("{e}"), } } } Err(e) => eprintln!("{e}"), }; podman .containers() .create( &ContainerCreateOpts::builder() .image(&self.tempdir) .name(&self.tempdir) .build(), ) .await?; let bytes = podman .containers() .get(&self.tempdir) .copy_from(format!( "/{}", self.repo.file_name().unwrap().to_str().unwrap() )) .try_concat() .await?; let mut archive = tar::Archive::new(&bytes[..]); archive.unpack(self.repo.parent().unwrap())?; podman.images().get(&self.tempdir).remove().await?; fs::remove_dir_all(&generate_path)?; Ok(()) } }