// I think I should split this file into a few smaller ones. use crate::cfg::config::Config; use std::{ fs::{self, File, create_dir_all}, io, path::Path, process::Command, str}; // use emissary_core::i2np::tunnel::build; use flate2::read::GzDecoder; use serde::{Deserialize, Serialize}; use tar::Archive; use toml; use cc; #[derive(Serialize, Debug, Deserialize, Clone)] pub enum archs { X86_64, Aarch64, X86, ArmV7, ArmV8, } #[derive(Serialize, Debug, Deserialize, Clone)] pub struct Package { name: String, version: String, arch: archs, descr: Option, } #[allow(dead_code)] #[derive(Deserialize, Debug, Clone)] struct Install { package: Package, path: String, user: String, group: String, mode: String, //. Cancels the previous fields and installs them using the shell script custom_script: Option, } #[allow(dead_code)] #[derive(Deserialize, Debug)] struct Setts { env: Option, // Export environment variables if this needed test: Option, // Test the package after installation } #[derive(Deserialize, Serialize, Debug)] pub enum BuildSystems { Make, CMake, Meson, Cargo } #[allow(dead_code)] #[derive(Deserialize)] struct Build { build_system: BuildSystems, env: Option, script: Option, } impl archs { fn as_str(&self) -> &'static str { match self { archs::X86_64 => "x86_64", archs::Aarch64 => "aarch64", archs::X86 => "x86", archs::ArmV7 => "armv7", archs::ArmV8 => "armv8" } } } #[allow(dead_code)] impl Package { fn builder_backend(&mut self) -> Result { let config: Config = Config::parse().unwrap(); let metadata = Self::loadmeta(self).unwrap(); let path = Path::new(&config.paths.cache_dir).join(format!("{}-{}/BUILD", metadata.0.package.name, metadata.0.package.version)); let _ = create_dir_all(&path); if metadata.2.is_none() { Err(std::io::Error::new(std::io::ErrorKind::NotFound, "BUILD file not found"))? } match metadata.2.unwrap().build_system { BuildSystems::Make => { let _setup = Command::new("make") .arg("all") .arg("all") .output(); } BuildSystems::CMake => { let _setup = Command::new("cmake") .arg("-S") .arg(&path) .arg("-B") .arg(&path) .output(); let _make = Command::new("make") .arg("-C") .arg(&path) .output(); } _ => { Err(std::io::Error::new(std::io::ErrorKind::NotFound, "BUILD file not found"))? } } Ok(true) } /// Extracts a .tar.gz archive to the cache directory specified in Config. /// /// This function handles opening the archive file, decompressing it with GzDecoder, /// and unpacking the contents into the configured cache directory. /// /// # Arguments /// * `path_to_archive` - A string representing the path to the .tar.gz file. /// /// # Returns /// * `Ok(())` if the archive is successfully unpacked. /// * `Err(std::io::Error)` if there's an issue opening, reading, or unpacking the archive. fn extract_archive(path_to_archive: &str) -> Result<(), std::io::Error> { let config = Config::parse().unwrap(); create_dir_all(&config.paths.cache_dir)?; let file = File::open(path_to_archive)?; let gz = GzDecoder::new(file); let mut archive = Archive::new(gz); // Unpack directly into the cache directory archive.unpack(&config.paths.cache_dir)?; Ok(()) } /// Load meta information from the .mesk archive. /// /// This function parses the meta information from the .mesk archive, /// which includes the package name, version, architecture, description, /// installation path, user, group, mode, and custom installation script /// and deserializing this information. Returns (Install, Option, Option) /// /// The function expects the 'INSTALL', 'SETTS', and 'BUILD' files to be present /// in the `config.paths.cache_dir`. It specifically requires the 'INSTALL' file. /// /// # Errors /// /// Returns an error if the `cache_dir` cannot be created, if the required 'INSTALL' file /// is not found, or if the 'INSTALL' file is empty. fn loadmeta(minimal_package_meta: &mut Self) -> Result<(Install, Option, Option), Box> { // Changed return type for more flexibility /* Example INSTALL format: [package] name = "my-package" version = "1.0.0" arch = "X86_64" descr = "Just example INSTALL script" [install] path = "/usr/bin/my-package" user = "root" group = "root" mode = "755" # Also [install] can be # path = "/usr/bin/my-package" # user = "root" # group = "root" # mode = "755" # custom_script = "./install.sh" OR # custom_script = """ # echo "Installing my-package" # sudo apt-get install my-package # """ */ let config = Config::parse()?; // Propagate error if parsing fails // Ensure the cache directory exists fs::create_dir_all(&config.paths.cache_dir)?; let cache_dir = &config.paths.cache_dir; let install_path = Path::new(cache_dir).join(format!("{}/INSTALL", minimal_package_meta.name)); let setts_path = Path::new(cache_dir).join(format!("{}/SETTS", minimal_package_meta.name)); let build_path = Path::new(cache_dir).join(format!("{}/BUILD", minimal_package_meta.name)); // Check for required 'INSTALL' file if !install_path.exists() { return Err(io::Error::new( io::ErrorKind::NotFound, "File INSTALL not found in cache directory" ).into()); // Convert to Box } // Read and deserialize the INSTALL file let install_content = fs::read_to_string(&install_path)?; let install_meta: Install = toml::from_str(&install_content)?; // Initialize optional structures as None let mut setts_meta: Option = None; let mut build_meta: Option = None; // Attempt to read and deserialize the SETTS file if it exists if setts_path.exists() { let setts_content = fs::read_to_string(&setts_path)?; setts_meta = Some(toml::from_str(&setts_content)?); } // Attempt to read and deserialize the BUILD file if it exists if build_path.exists() { let build_content = fs::read_to_string(&build_path)?; build_meta = Some(toml::from_str(&build_content)?); } // Log if custom script is present if let Some(ref script) = install_meta.custom_script { println!("Custom script found for package: {}", install_meta.package.name); // Consider logging the script content or just its presence based on verbosity // e.g., log::debug!("Custom script content: {}", script); } else { println!("No custom script for package: {}", install_meta.package.name); } Ok((install_meta, setts_meta, build_meta)) } /// Checks if the archive contains INSTALL, SETTS and BUILD files. /// /// Checks if INSTALL file exists and is not empty. If it does not exist or is empty, returns an error. /// /// Checks if SETTS and BUILD files exist and are not empty. If they do not exist or are empty, logs a warning. /// # Errors /// * Returns an error if INSTALL file does not exist or is empty. /// * Returns an error if INSTALL file is empty. /// // TODO: Add meta-files validation here. /// Checks if the archive contains INSTALL, SETTS and BUILD files. /// /// Checks if INSTALL file exists and is not empty. If it does not exist or is empty, returns an error. /// /// Checks if SETTS and BUILD files exist and are not empty. If they do not exist or are empty, logs a warning. /// # Errors /// * Returns an error if INSTALL file does not exist or is empty. /// * Returns an error if INSTALL file is empty. /// // TODO: Add meta-files validation here. pub fn check(path_to_archive: String) -> Result { // Call the new extraction function Self::extract_archive(&path_to_archive)?; let config = Config::parse().unwrap(); let install_path = Path::new(&config.paths.cache_dir).join("INSTALL"); let setts_path = Path::new(&config.paths.cache_dir).join("SETTS"); let build_path = Path::new(&config.paths.cache_dir).join("BUILD"); if !install_path.exists() { return Err(std::io::Error::new( std::io::ErrorKind::NotFound, "INSTALL file not found in archive", )); } let install_content = std::fs::read_to_string(&install_path)?; if install_content.trim().is_empty() { return Err(std::io::Error::new( std::io::ErrorKind::InvalidData, "INSTALL file is empty", )); } if !setts_path.exists() { log::warn!("SETTS file not found in archive. Make sure you dont need this."); } else { let setts_content = std::fs::read_to_string(&setts_path)?; if setts_content.trim().is_empty() { log::warn!("SETTS file is empty. Make sure you dont need this."); } } if !build_path.exists() { log::warn!("BUILD file not found in archive. Make sure you dont need this."); } else { let build_content = std::fs::read_to_string(&build_path)?; if build_content.trim().is_empty() { log::warn!("BUILD file is empty. Make sure you dont need this."); } } let content = std::fs::read_to_string(&install_path) .map_err(|e| { log::warn!("Failed to read file: {}", e); e })?; let install_content: Result = toml::from_str(&content); log::info!("Validating arch..."); if std::env::consts::ARCH != install_content.as_ref().map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()))?.package.arch.as_str() { let pkg_arch = &install_content.unwrap().package.arch; // Safe because of previous check/unwrap log::error!("Arch mismatch. Package arch: {:?}, Host arch: {}", pkg_arch, std::env::consts::ARCH); return Err(std::io::Error::new( std::io::ErrorKind::InvalidData, "Arch mismatch", )); } Ok(true) } /// Builds the package according to the BUILD file in the archive. /// /// There are two strategies for building the package. If the BUILD file is empty, the package is assumed to be a binary package and the default install hook is skipped. If the BUILD file is not empty, the package is assumed to be a source package and the default build hook is skipped. /// /// If the BUILD file is empty and the INSTALL file contains a custom script, the custom script is run instead of the default install hook. /// If the BUILD file is not empty and the INSTALL file contains a custom script, the custom script is ignored. /// /// /// # Errors /// /// Returns an error if the BUILD file is invalid or if the build or install hook fails. pub fn build(&mut self) -> Result { let meta = Self::loadmeta(self).unwrap(); let install_meta = meta.0; let setts_meta = meta.1; let build_meta = meta.2; // BUILD NOT EMPTY. SOURCE: -> BUILD -> INSTALL -> SETTS // BUILD EMPTY. BIN: -> INSTALL -> SETTS enum Strategies { BIN, SOURCE } let strategy; //default if build_meta.is_none() { log::info!("BUILD file is empty. Skipping build, preparing to install"); strategy = Strategies::BIN; } else { strategy = Strategies::SOURCE; log::info!("BUILD file is not empty. Skipping install, preparing to build"); } match strategy { Strategies::BIN => { if install_meta.custom_script.is_none() { log::info!("Strategy: BIN; No custom script. Running default install hook."); } else { log::info!("Strategy: BIN; Running custom script."); let script = install_meta.custom_script.as_ref().unwrap(); if !script.starts_with("./") { let _output = std::process::Command::new(format!("{}", script)); } else { let _output = std::process::Command::new(format!("/bin/sh '{}'", script)); } } } Strategies::SOURCE => { log::info!("Strategy: SOURCE; Running default build hook."); todo!(); } } Ok(true) } pub fn install() -> Result { todo!(); } pub fn gen_index() -> Result { todo!(); } }