From 2c8b804cd61a480501069cdf4d4377f3c2cf30bb Mon Sep 17 00:00:00 2001 From: Namilskyy Date: Wed, 3 Dec 2025 21:00:31 +0300 Subject: Not much of the documentation has been implemented, as well as some minor changes --- src/cfg/config.rs | 38 ++++++---- src/main.rs | 4 +- src/net/emissary_i2p.rs | 60 ---------------- src/net/http_package.rs | 2 + src/net/i2p_package.rs | 1 - src/net/i2p_tools.rs | 0 src/net/mod.rs | 4 -- src/pkgtoolkit/pkgtools.rs | 176 +++++++++++++++++++++++++++++---------------- 8 files changed, 145 insertions(+), 140 deletions(-) delete mode 100644 src/net/emissary_i2p.rs delete mode 100644 src/net/i2p_tools.rs (limited to 'src') diff --git a/src/cfg/config.rs b/src/cfg/config.rs index 687c415..f14ec93 100644 --- a/src/cfg/config.rs +++ b/src/cfg/config.rs @@ -49,24 +49,29 @@ pub struct Paths { pub cache_dir: String, #[serde(rename = "build_dir")] pub build_dir: String, + pub installed_db: String, } impl Config { - /// Parse the /etc/mesk.toml file and return the Config object. + /// Parse configuration and return the Config object. /// - /// This function reads the /etc/mesk.toml file, parses it and returns the Config object. - /// If the file is not available, it falls back to the `MESK_CONFIG_TOML` environment - /// variable containing the full TOML configuration. + /// This function first tries to read the `MESK_CONFIG_TOML` environment variable + /// containing the full TOML configuration. If it is not set, it falls back to + /// reading the `/etc/mesk/mesk.toml` file. pub fn parse() -> Result { - let contents = match fs::read_to_string("/etc/mesk/mesk.toml") { - Ok(c) => c, - Err(_e) => std::env::var("MESK_CONFIG_TOML").map_err(|env_err| { - DeError::custom(format!( - "Failed to read /etc/mesk/mesk.toml and MESK_CONFIG_TOML is not set: {}", - env_err - )) - })?, - }; + // Prefer an explicit in-memory config for tests and overrides. + if let Ok(env_contents) = std::env::var("MESK_CONFIG_TOML") { + let result: Config = toml::from_str(&env_contents)?; + return Ok(result); + } + + // Fallback to the system-wide config file. + let contents = fs::read_to_string("/etc/mesk/mesk.toml").map_err(|io_err| { + DeError::custom(format!( + "Failed to read /etc/mesk/mesk.toml and MESK_CONFIG_TOML is not set: {}", + io_err + )) + })?; let result: Config = toml::from_str(&contents)?; Ok(result) @@ -97,6 +102,7 @@ impl Config { paths: Paths { cache_dir: String::from("/var/cache/mesk"), build_dir: String::from("/var/lib/mesk"), + installed_db: String::from("/var/lib/mesk/pkgdb"), }, }; @@ -108,6 +114,7 @@ impl Config { repo: &Option, cachedir: &Option, buildir: &Option, + installed_db: &Option, ) -> Result { let generator: Config = Config { repo: Repo { @@ -135,6 +142,11 @@ impl Config { } else { buildir.clone().unwrap() }, + installed_db: if installed_db.is_none() { + String::from("/var/cache/mesk/pkgdb") + } else { + installed_db.clone().unwrap() + }, /* FIXME: I can leave this parameter, but I think it would be better to make the build path in the /var/cache/mesk/$pkgname-$pkgver/BUILD/ diff --git a/src/main.rs b/src/main.rs index 2cff8d4..b9328a0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -45,6 +45,7 @@ enum Commands { repo: Option, cachedir: Option, buildir: Option, + installed_db: Option, }, #[command(about = "Maintaners, links, developers and more info")] Credits, @@ -145,6 +146,7 @@ async fn main() -> Result<(), Box> { repo, cachedir, buildir, + installed_db, } => { println!("Generating config file"); if cachedir.is_none() && repo.is_none() && buildir.is_none() { @@ -162,7 +164,7 @@ async fn main() -> Result<(), Box> { file.write_all(config.as_bytes())?; println!("Config tool ending work."); } else { - let config = Config::generate(repo, cachedir, buildir).unwrap(); + let config = Config::generate(repo, cachedir, buildir, installed_db).unwrap(); println!("---- Start of generated config ----"); println!("{:?}", config); diff --git a/src/net/emissary_i2p.rs b/src/net/emissary_i2p.rs deleted file mode 100644 index b9e5967..0000000 --- a/src/net/emissary_i2p.rs +++ /dev/null @@ -1,60 +0,0 @@ -use emissary_core::{ - I2cpConfig, - Ntcp2Config -}; - - -use crate::pkgtoolkit::pkgtools::Package; -use crate::Config; - -use indicatif::{ProgressBar, ProgressStyle}; -use std::{collections::HashMap, path::Path}; -use tokio::io::{AsyncReadExt, AsyncWriteExt, BufReader}; - -pub struct I2PPackage { - pub config: Config, - pub i2p_config: emissary_core::Config, - pub index_packages: Option>, -} - -impl I2PPackage { - pub fn new(cfg: Config, i2p_cfg: emissary_core::Config) -> Self { - I2PPackage { - config: cfg, - i2p_config: i2p_cfg, - index_packages: None, - } - } - - pub async fn fetch_index(&mut self) -> Result<(), Box> { - let cfg = Config::parse()?; - - let repo_url = &self.config.repo.repo_url; - let cache_dir = &self.config.paths.cache_dir; - - let utl = url::Url::parse(repo_url).unwrap(); - let host = utl.host_str().ok_or("No host in URL")?; - - let request_path = utl.path(); - let request_path = if request_path.ends_with(".tar.gz") { - request_path.to_string() - } else { - format!("{}/INDEX.tar.gz", request_path.trim_end_matches('/')) - }; - - - let StorageBundle { - ntcp2_iv, - ntcp2_key, - profiles, - router_info, - routers, - signing_key, - static_key, - ssu2_intro_key, - ssu2_static_key, - } = storage.load().await; - - Ok(()) - } -} \ No newline at end of file diff --git a/src/net/http_package.rs b/src/net/http_package.rs index 4b2aefa..8fb2e19 100644 --- a/src/net/http_package.rs +++ b/src/net/http_package.rs @@ -1,3 +1,5 @@ +// TODO: Add signatures checking and fix mixed sync/async use. + use crate::cfg::config::Config; use crate::pkgtoolkit::pkgtools::Package; use flate2::read::GzDecoder; diff --git a/src/net/i2p_package.rs b/src/net/i2p_package.rs index 2b847d6..f46ddc7 100644 --- a/src/net/i2p_package.rs +++ b/src/net/i2p_package.rs @@ -191,7 +191,6 @@ impl I2PPackage { content_length: u64, description: &str, ) -> Result<(), Box> { - // Прогресс-бар для загрузки let pb = if content_length > 0 { let pb = ProgressBar::new(content_length); pb.set_style(ProgressStyle::default_bar() diff --git a/src/net/i2p_tools.rs b/src/net/i2p_tools.rs deleted file mode 100644 index e69de29..0000000 diff --git a/src/net/mod.rs b/src/net/mod.rs index 20a050e..1a04189 100644 --- a/src/net/mod.rs +++ b/src/net/mod.rs @@ -1,6 +1,2 @@ pub mod http_package; pub mod i2p_package; -// unimplemented -// now i rewritting with emissary, SAMv3 not for download files btw -// but yosemite one of docemented lib for i2p (SAMv3 only), it-s better i try to use it -// pub mod emissary_i2p; \ No newline at end of file diff --git a/src/pkgtoolkit/pkgtools.rs b/src/pkgtoolkit/pkgtools.rs index a0433ac..2a8a18e 100644 --- a/src/pkgtoolkit/pkgtools.rs +++ b/src/pkgtoolkit/pkgtools.rs @@ -1,17 +1,13 @@ -// 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}, + fs::{self, File, Permissions, create_dir_all, set_permissions}, io, os::unix::fs::PermissionsExt, - path::Path, + path::{Path, StripPrefixError}, process::Command, str, }; - // use emissary_core::i2np::tunnel::build; - use flate2::read::GzDecoder; use serde::{Deserialize, Serialize}; use tar::Archive; @@ -38,12 +34,13 @@ pub struct Package { #[derive(Deserialize, Debug, Clone)] pub struct InstallMeta { - path: String, - user: String, - group: String, - mode: String, + pub path: String, + pub user: String, + pub group: String, + pub mode: String, // Cancels the previous fields and installs them using the shell script - custom_script: Option, + pub custom_script: Option, + // pub files: Option>, } #[allow(dead_code)] @@ -51,6 +48,8 @@ pub struct InstallMeta { struct Install { package: Package, install: InstallMeta, + #[serde(default)] + files: Vec, } #[allow(dead_code)] @@ -70,6 +69,13 @@ pub enum BuildSystems { Cargo, } +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct PackageManifest { + pub name: String, + pub version: String, + pub all_files: Vec, +} + #[allow(dead_code)] #[derive(Deserialize)] struct Build { @@ -153,6 +159,36 @@ impl Package { Ok(()) } + /// Recursively collects all files from a directory and its subdirectories + /// and returns them as a vector of strings. + /// + /// # Arguments + /// + /// * `root`: The root directory from which to collect files. + /// * `base`: The base directory from which to strip the prefix from the file paths. + /// + /// # Returns + /// + /// A vector of strings containing the file paths relative to the `base` directory. + fn collect_files_from_dir(root: &Path, base: &Path) -> Result, std::io::Error> { + let mut files = Vec::new(); + for entry in fs::read_dir(root)? { + let entry = entry?; + let path = entry.path(); + if path.is_dir() { + files.extend(Self::collect_files_from_dir(&path, base)?); + } else { + let rel_path = path + .strip_prefix(base) + .map_err(|e: StripPrefixError| io::Error::new(io::ErrorKind::InvalidData, e))? + .to_string_lossy() + .to_string(); + files.push(rel_path); + } + } + Ok(files) + } + /// Extracts a .tar.gz archive to the cache directory specified in Config. /// /// This function handles opening the archive file, decompressing it with GzDecoder, @@ -165,22 +201,19 @@ impl Package { /// * `Ok(())` if the archive is successfully unpacked. /// * `Err(std::io::Error)` if there's an issue opening, reading, or unpacking the archive. pub fn extract_archive(path_to_archive: &str) -> Result<(), std::io::Error> { - let config = Config::parse().unwrap(); + let config = Config::parse().map_err(|e| std::io::Error::other(e.to_string()))?; let cache_dir = &config.paths.cache_dir; create_dir_all(cache_dir)?; - // Очистим возможные мета-файлы предыдущих распаковок, чтобы не было утечек состояния for meta_name in ["INSTALL", "SETTS", "BUILD"] { let meta_path = Path::new(cache_dir).join(meta_name); if meta_path.exists() { - let _ = fs::remove_file(&meta_path); + fs::remove_file(&meta_path)?; } } - let file = File::open(path_to_archive)?; let gz = GzDecoder::new(file); let mut archive = Archive::new(gz); - // Unpack directly into the cache directory. Игнорируем AlreadyExists, чтобы не мешать валидации. match archive.unpack(cache_dir) { Ok(()) => Ok(()), @@ -206,7 +239,7 @@ impl Package { #[allow(clippy::type_complexity)] fn loadmeta( minimal_package_meta: &mut Self, - ) -> Result<(Install, Option, Option), Box> { + ) -> Result<(Install, Option, Option), std::io::Error> { // Changed return type for more flexibility /* Example INSTALL format: @@ -234,7 +267,7 @@ impl Package { # """ */ - let config = Config::parse()?; // Propagate error if parsing fails + let config = Config::parse().map_err(|e| std::io::Error::other(e.to_string()))?; // Propagate error if parsing fails // Ensure the cache directory exists fs::create_dir_all(&config.paths.cache_dir)?; @@ -245,43 +278,41 @@ impl Package { 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)?; + let install_meta: Install = toml::from_str(&install_content) + .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; - // 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)?); + setts_meta = Some( + toml::from_str(&setts_content) + .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?, + ); } - // 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)?); + build_meta = Some( + toml::from_str(&build_content) + .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?, + ); } - // Log if custom script is present if let Some(ref _script) = install_meta.install.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: {}", @@ -305,12 +336,11 @@ impl Package { pub fn check(path_to_archive: String) -> Result { Self::extract_archive(&path_to_archive)?; - let archive_path = Path::new(&path_to_archive); - let archive_dir = archive_path.parent().unwrap_or_else(|| Path::new(".")); + let config = Config::parse().map_err(|e| std::io::Error::other(e.to_string()))?; - let install_path = archive_dir.join("INSTALL"); - let setts_path = archive_dir.join("SETTS"); - let build_path = archive_dir.join("BUILD"); + 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( @@ -352,7 +382,6 @@ impl Package { let install_content: Result = toml::from_str(&content); log::info!("Validating arch..."); - let install_parsed = match install_content { Ok(v) => v, Err(e) => { @@ -363,7 +392,6 @@ impl Package { )); } }; - if std::env::consts::ARCH != install_parsed.package.arch.as_str() { let pkg_arch = &install_parsed.package.arch; log::error!( @@ -392,7 +420,7 @@ impl Package { /// /// 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 meta = Self::loadmeta(self)?; let install_meta = meta.0; // let setts_meta = meta.1; let build_meta = meta.2; @@ -449,8 +477,12 @@ impl Package { /// Returns an error if the BUILD file is invalid or if the build or install hook fails. pub fn install(&mut self) -> Result { let config = Config::parse().map_err(|e| std::io::Error::other(e.to_string()))?; - let (install_meta, _setts_meta, build_meta) = - Self::loadmeta(self).map_err(|e| std::io::Error::other(e.to_string()))?; + let (install_meta, _setts_meta, build_meta) = Self::loadmeta(self)?; + + let installed_db = Path::new(&config.paths.installed_db); + create_dir_all(installed_db)?; + + let mut all_files: Vec = Vec::new(); let is_build_present_and_not_empty = build_meta.is_some(); @@ -462,29 +494,29 @@ impl Package { let build_meta_ref = build_meta.as_ref().unwrap(); let _ = self.execute_build(build_meta_ref); - if matches!(build_meta_ref.build_system, BuildSystems::Make) { - log::info!("Running 'make install' for package: {}", self.name); - let build_dir = Path::new(&config.paths.cache_dir) - .join(format!("{}-{}", self.name, self.version)); - let output = Command::new("make") - .arg("install") - .current_dir(&build_dir) - .output() - .map_err(|e| std::io::Error::other(format!("'make install' failed: {}", e)))?; - - if !output.status.success() { - let stderr = String::from_utf8_lossy(&output.stderr); - return Err(std::io::Error::other(format!( - "'make install' failed:\n{}", - stderr - ))); + let staging_dir = Path::new(&config.paths.cache_dir).join("staging"); + create_dir_all(&staging_dir)?; + all_files = Self::collect_files_from_dir(&staging_dir, &staging_dir)? + .iter() + .map(|rel| format!("/{}", rel)) // Преобразуйте в абсолютные пути (предполагаем root=/) + .collect(); + + for file in &all_files { + let src = staging_dir.join(file.trim_start_matches('/')); + let dest = Path::new(file); + if let Some(parent) = dest.parent() { + create_dir_all(parent)?; } + fs::copy(&src, dest)?; + // TODO: Permission } + + // Cleanup staging + fs::remove_dir_all(&staging_dir)?; } else { log::info!( "No BUILD file or it's empty. Treating as binary package. Installing via INSTALL config or custom script." ); - // Установка бинарного пакета if let Some(ref script) = install_meta.install.custom_script { log::info!( "Executing custom install script for {}", @@ -502,6 +534,11 @@ impl Package { if !status.success() { return Err(std::io::Error::other("Custom install script failed")); } + + log::warn!( + "Custom script used; file list may be incomplete. Add manual tracking if needed." + ); + // Опционально: all_files = vec![]; или сканируйте систему (не рекомендуется) } else { log::info!( "No custom script. Running default install hook for {}", @@ -528,8 +565,8 @@ impl Package { "Invalid mode string in INSTALL", ) })?; - let perms = PermissionsExt::from_mode(mode); - fs::set_permissions(dest_path, perms).map_err(|e| { + let perms = Permissions::from_mode(mode); + set_permissions(dest_path, perms).map_err(|e| { std::io::Error::other(format!("Failed to set permissions: {}", e)) })?; @@ -549,12 +586,29 @@ impl Package { stderr ); } + + all_files = install_meta.files; } } - log::info!("Package {} installed successfully.", self.name); + let manifest = PackageManifest { + name: self.name.clone(), + version: self.version.clone(), + all_files, + }; + let manifest_path = installed_db.join(format!("{}-{}.toml", self.name, self.version)); + let manifest_toml = + toml::to_string(&manifest).map_err(|e| std::io::Error::other(e.to_string()))?; + fs::write(&manifest_path, manifest_toml)?; + + log::info!( + "Package {} installed successfully. Manifest generated at {:?}", + self.name, + manifest_path + ); Ok(true) } + pub fn gen_index() -> Result { todo!(); } -- cgit v1.2.3