From bbcbdcbbf95cda5115bbb484b5e2d01669a7a1a0 Mon Sep 17 00:00:00 2001 From: Namilskyy Date: Sun, 28 Dec 2025 15:19:04 +0300 Subject: Implementing integrated router funtions and other fuctions --- .woodpecker.yaml | 7 +- Cargo.lock | 1 + Cargo.toml | 12 +- src/cfg/config.rs | 95 +++++++--- src/lib.rs | 1 + src/main.rs | 454 ++++++++++++++++++++++++++++------------------ src/openpgp/signatures.rs | 1 + src/pkgtoolkit/build.rs | 8 +- src/pkgtoolkit/install.rs | 44 ++++- src/pkgtoolkit/types.rs | 5 +- src/router/manager.rs | 144 +++++++++++++++ src/router/mod.rs | 2 +- src/router/router.rs | 197 +++++++++++++------- 13 files changed, 678 insertions(+), 293 deletions(-) create mode 100644 src/router/manager.rs diff --git a/.woodpecker.yaml b/.woodpecker.yaml index 0b89995..a198f5c 100644 --- a/.woodpecker.yaml +++ b/.woodpecker.yaml @@ -1,14 +1,14 @@ # Define the sequence of steps for the CI pipeline steps: dependencies: - image: rust + image: rust:1.78-bullseye environment: RUST_BACKTRACE: 1 CARGO_TERM_COLOR: always commands: - rustup default stable - apt update - - apt install openssl gnupg libgpgme-dev libgpg-error-dev libassuan-dev -y + - apt install -y pkg-config libgpg-error-dev libgpgme-dev libassuan-dev gnupg openssl when: branch: main event: [ push, pull_request ] @@ -29,7 +29,8 @@ steps: CARGO_TERM_COLOR: always commands: - rustup component add clippy rustfmt - # - cargo fmt --all -- --check + - cargo fmt --all + - export PKG_CONFIG_PATH="/usr/lib/x86_64-linux-gnu/pkgconfig:/usr/share/pkgconfig" - cargo clippy --jobs 2 -- -D clippy::all # -D warnings when: branch: main diff --git a/Cargo.lock b/Cargo.lock index 830ecd2..c746dad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1696,6 +1696,7 @@ dependencies = [ "glob", "gpgme", "indicatif", + "lazy_static", "log", "mockito", "num_cpus", diff --git a/Cargo.toml b/Cargo.toml index 6e0fe96..4016a05 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,8 +3,7 @@ name = "mesk" version = "0.0.1" edition = "2024" license = "GPL-3.0" -authors = [ "namilsk ", - "asya " ] +authors = [ "namilsk " ] description = "An i2p-based package manager developed by Anthrill project" categories = ["command-line-utilities"] repository = "https://codeberg.org/Anthrill/mesk.git" @@ -16,6 +15,8 @@ toml = { version = "0.9.8", features = ["serde"] } serde = { version = "1.0.228", features = ["derive"] } tokio = { version = "1.48.0", features = ["full"] } reqwest = { version = "0.12.24", features = ["stream"] } +emissary-util = { version = "0.3.0", features = ["tokio"] } +emissary-core = "0.3.0" flate2 = "1.1.5" log = "0.4.28" tar = "0.4.44" @@ -26,8 +27,7 @@ num_cpus = "1.17.0" git2 = "0.19.0" gpgme = "0.11.0" glob = "0.3.3" -emissary-core = "0.3.0" -emissary-util = "0.3.0" +lazy_static = "1.5.0" [dev-dependencies] env_logger = "0.11.8" @@ -40,5 +40,5 @@ uuid = { version = "1.19.0", features = ["v4"] } [profile.release] strip = true lto = true -opt-level = "s" - +opt-level = 3 +codegen-units = 1 \ No newline at end of file diff --git a/src/cfg/config.rs b/src/cfg/config.rs index ce9df87..eaa703c 100644 --- a/src/cfg/config.rs +++ b/src/cfg/config.rs @@ -13,11 +13,55 @@ pub enum Loglevel { } /// `mesk.toml` configuration fields here +#[derive(Deserialize, Debug, Serialize, Clone)] +pub struct RouterConfig { + #[serde(default = "default_integrated_router")] + pub integrated_router: bool, + #[serde(default = "default_router_storage")] + pub storage_path: String, + #[serde(default = "default_http_proxy_port")] + pub http_proxy_port: u16, + #[serde(default = "default_socks_proxy_port")] + pub socks_proxy_port: u16, + #[serde(default = "default_auto_update")] + pub auto_update: bool, +} + +fn default_integrated_router() -> bool { + false +} +fn default_router_storage() -> String { + "/var/lib/mesk/router/".to_string() +} +fn default_http_proxy_port() -> u16 { + 4445 +} +fn default_socks_proxy_port() -> u16 { + 4446 +} +fn default_auto_update() -> bool { + true +} + #[derive(Deserialize, Debug, Serialize, Clone)] pub struct Config { pub repo: Repo, pub log: Log, pub paths: Paths, + #[serde(default = "RouterConfig::default")] + pub router: RouterConfig, +} + +impl Default for RouterConfig { + fn default() -> Self { + Self { + integrated_router: default_integrated_router(), + storage_path: default_router_storage(), + http_proxy_port: default_http_proxy_port(), + socks_proxy_port: default_socks_proxy_port(), + auto_update: default_auto_update(), + } + } } #[derive(Deserialize, Debug, Serialize, Clone)] @@ -107,6 +151,7 @@ impl Config { build_dir: String::from("/var/lib/mesk"), installed_db: String::from("/var/lib/mesk/pkgdb"), }, + router: RouterConfig::default(), }; let toml_str = toml::to_string(&default)?; @@ -119,16 +164,21 @@ impl Config { buildir: &Option, installed_db: &Option, ) -> Result { - let generator: Config = Config { + let config = Config { repo: Repo { - repo_url: if repo.is_none() { - format!("https://mesk.anthrill.i2p/repo/{}/", std::env::consts::ARCH) - } else { - repo.clone().unwrap() - }, + repo_url: repo + .as_deref() + .unwrap_or(&format!( + "https://mesk.anthrill.i2p/repo/{}/", + std::env::consts::ARCH + )) + .to_string(), auto_update: true, destination: (String::from("mesk"), String::from("mesk")), - repo_http_url: None, + repo_http_url: repo.as_ref().map(|r| { + r.replace("i2p", "http") + .replace("b32.i2p", "github.com/Anthrill/repo-mirror") + }), i2p_http_proxy_port: 4444, }, log: Log { @@ -136,27 +186,18 @@ impl Config { log_level: Loglevel::Info, }, paths: Paths { - cache_dir: if cachedir.is_none() { - String::from("/var/cache/mesk") - } else { - cachedir.clone().unwrap() - }, - build_dir: if buildir.is_none() { - String::from("/var/cache/mesk/build") - } 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/ - */ + cache_dir: cachedir + .clone() + .unwrap_or_else(|| "/var/cache/mesk".to_string()), + build_dir: buildir + .clone() + .unwrap_or_else(|| "/var/tmp/mesk/build".to_string()), + installed_db: installed_db + .clone() + .unwrap_or_else(|| "/var/lib/mesk/installed.db".to_string()), }, + router: RouterConfig::default(), }; - toml::to_string(&generator) + toml::to_string(&config) } } diff --git a/src/lib.rs b/src/lib.rs index beaf75b..39c57cd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,3 +8,4 @@ pub mod net { } pub mod pkgtoolkit; +pub mod router; diff --git a/src/main.rs b/src/main.rs index bbd4187..4e1402d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,22 +2,64 @@ mod cfg; mod net; mod openpgp; mod pkgtoolkit; -mod router; +mod router; use crate::cfg::config::Config; use crate::net::{http_package::HTTPPackage, i2p_package::I2PPackage}; -use crate::openpgp::trusted; use crate::pkgtoolkit::Package; use crate::pkgtoolkit::archive::ArchiveOperations; use crate::pkgtoolkit::build::BuildOperations; use crate::pkgtoolkit::git_source::GitSource; use crate::pkgtoolkit::index::IndexOperations; use crate::pkgtoolkit::install::InstallOperations; +use crate::router::router::{Emissary, EmissaryConfig, RouterUtils}; +use emissary_core::router::Router; +use emissary_util::runtime::tokio::Runtime as TokioRuntime; +use std::sync::Arc; +use tokio::sync::Mutex; use clap::{Args, Parser, Subcommand}; use std::io::Write; use std::path::Path; +lazy_static::lazy_static! { + static ref ROUTER: Arc>>> = Arc::new(Mutex::new(None)); +} + +async fn init_router( + config: &crate::cfg::config::Config, +) -> Result<(), Box> { + if !config.router.integrated_router { + return Ok(()); + } + + let router_config = EmissaryConfig { + storage: Some(std::path::PathBuf::from(&config.router.storage_path)), + auto_update: Some(config.router.auto_update), + http_proxy_port: Some(config.router.http_proxy_port), + socks_proxy_port: Some(config.router.socks_proxy_port), + }; + + let mut router = Emissary::new(router_config); + + // Check if router storage exists and is not empty + let storage_path = std::path::Path::new(&config.router.storage_path); + if !storage_path.exists() || storage_path.read_dir()?.next().is_none() { + log::info!("Router storage is empty, performing initial reseed..."); + router.config().await?; + router.reseed().await?; + } else { + router.config().await?; + } + + // Start the router and store the Router instance + let router = router.start().await?; + *ROUTER.lock().await = Some(router); + + log::info!("Integrated router initialized successfully"); + Ok(()) +} + #[derive(Parser)] struct Cli { #[command(subcommand)] @@ -80,97 +122,50 @@ struct RemoteInstallArgs { #[tokio::main] async fn main() -> Result<(), Box> { - let cli: Cli = Cli::parse(); - - match &cli.command { - Commands::Validate { path } => { - println!("Validating {}", path); - match Package::check(path.to_string()) { - Ok(is_valid) => { - if is_valid { - println!("Package archive is valid."); - } else { - println!("Package archive is invalid."); - return Err(std::io::Error::other("Invalid package archive").into()); - } - } - Err(e) => { - log::error!("Failed to validate package '{}': {}", path, e); - return Err(e.into()); - } - } - return Ok(()); - } - Commands::Build { pkgname } => { - println!("Building package from archive: {}", pkgname); - - let path = Path::new(&pkgname); - if !path.exists() { - return Err(std::io::Error::other(format!( - "Package archive not found: {}", - pkgname - )) - .into()); - } + let config = crate::cfg::config::Config::parse()?; - if !path.is_file() { - return Err( - std::io::Error::other(format!("Path is not a file: {}", pkgname)).into(), - ); - } + init_router(&config).await?; + let cli: Cli = Cli::parse(); - println!("Extracting archive..."); - Package::extract_archive(pkgname)?; - - let config = Config::parse().unwrap(); - let cache_dir = &config.paths.cache_dir; - - // Find the package directory (should be name-version format) - let mut pkg_dir_name = None; - for entry in std::fs::read_dir(cache_dir)? { - let entry = entry?; - let path = entry.path(); - if path.is_dir() { - let dir_name = path.file_name().unwrap().to_string_lossy(); - if dir_name.contains('-') && dir_name != "temp_extract" { - let install_path = path.join("INSTALL"); - if install_path.exists() { - pkg_dir_name = Some(dir_name.to_string()); - break; + let config = Arc::new(config); + + let result = { + match &cli.command { + Commands::Validate { path } => { + println!("Validating {}", path); + match Package::check(path.to_string()) { + Ok(is_valid) => { + if is_valid { + println!("Package archive is valid."); + } else { + println!("Package archive is invalid."); + return Err(std::io::Error::other("Invalid package archive").into()); } } + Err(e) => { + log::error!("Failed to validate package '{}': {}", path, e); + return Err(e.into()); + } } + return Ok(()); } + Commands::Build { pkgname } => { + println!("Building package from archive: {}", pkgname); + + let path = Path::new(&pkgname); + if !path.exists() { + return Err(std::io::Error::other(format!( + "Package archive not found: {}", + pkgname + )) + .into()); + } - let pkg_dir_name = pkg_dir_name - .ok_or_else(|| std::io::Error::other("Package directory not found in cache"))?; - - let install_toml_path = Path::new(cache_dir).join(format!("{}/INSTALL", pkg_dir_name)); - - if !install_toml_path.exists() { - return Err( - std::io::Error::other("INSTALL file not found in package directory").into(), - ); - } - - let install_content = std::fs::read_to_string(&install_toml_path)?; - let install_data: crate::pkgtoolkit::types::Install = toml::from_str(&install_content)?; - - let mut pkg = install_data.package; - - println!("Building package '{}'...", pkg.name); - pkg.build()?; - println!("Package '{}' built successfully.", pkg.name); - return Ok(()); - } - Commands::Install { - pkgname, - source: _, - path, - args, - } => { - if *path { - println!("Installing package from local file: {}", pkgname); + if !path.is_file() { + return Err( + std::io::Error::other(format!("Path is not a file: {}", pkgname)).into(), + ); + } println!("Extracting archive..."); Package::extract_archive(pkgname)?; @@ -178,6 +173,7 @@ async fn main() -> Result<(), Box> { let config = Config::parse().unwrap(); let cache_dir = &config.paths.cache_dir; + // Find the package directory (should be name-version format) let mut pkg_dir_name = None; for entry in std::fs::read_dir(cache_dir)? { let entry = entry?; @@ -199,6 +195,14 @@ async fn main() -> Result<(), Box> { let install_toml_path = Path::new(cache_dir).join(format!("{}/INSTALL", pkg_dir_name)); + + if !install_toml_path.exists() { + return Err(std::io::Error::other( + "INSTALL file not found in package directory", + ) + .into()); + } + let install_content = std::fs::read_to_string(&install_toml_path)?; let install_data: crate::pkgtoolkit::types::Install = toml::from_str(&install_content)?; @@ -207,107 +211,199 @@ async fn main() -> Result<(), Box> { println!("Building package '{}'...", pkg.name); pkg.build()?; + println!("Package '{}' built successfully.", pkg.name); + return Ok(()); + } + Commands::Install { + pkgname, + source: _, + path, + args, + } => { + if *path { + println!("Installing package from local file: {}", pkgname); + + println!("Extracting archive..."); + Package::extract_archive(pkgname)?; + + let config = Config::parse().unwrap(); + let cache_dir = &config.paths.cache_dir; + + let mut pkg_dir_name = None; + for entry in std::fs::read_dir(cache_dir)? { + let entry = entry?; + let path = entry.path(); + if path.is_dir() { + let dir_name = path.file_name().unwrap().to_string_lossy(); + if dir_name.contains('-') && dir_name != "temp_extract" { + let install_path = path.join("INSTALL"); + if install_path.exists() { + pkg_dir_name = Some(dir_name.to_string()); + break; + } + } + } + } + + let pkg_dir_name = pkg_dir_name.ok_or_else(|| { + std::io::Error::other("Package directory not found in cache") + })?; + + let install_toml_path = + Path::new(cache_dir).join(format!("{}/INSTALL", pkg_dir_name)); + let install_content = std::fs::read_to_string(&install_toml_path)?; + let install_data: crate::pkgtoolkit::types::Install = + toml::from_str(&install_content)?; - println!("Installing package '{}'...", pkg.name); - pkg.install()?; - println!("Package '{}' installed successfully.", pkg.name); - } else { + let mut pkg = install_data.package; + + println!("Building package '{}'...", pkg.name); + pkg.build()?; + + println!("Installing package '{}'...", pkg.name); + pkg.install()?; + println!("Package '{}' installed successfully.", pkg.name); + } else { + let config = Config::parse().unwrap(); + if args.http { + println!("Installing {} via HTTP", pkgname); + let mut http_client = HTTPPackage::new(config); + http_client.fetch_index_http().await?; + log::info!("Index fetched successfully."); + http_client.fetch_package_http(pkgname).await?; + log::info!("Package '{}' installed successfully.", pkgname); + } else { + println!("Installing {} via I2P", pkgname); + let mut i2p_client = I2PPackage::new(config); + i2p_client.fetch_index().await?; + log::info!("Index fetched successfully."); + i2p_client.fetch_package(pkgname).await?; + log::info!("Package '{}' installed successfully.", pkgname); + } + } + return Ok(()); + } + Commands::Uninstall { pkgname } => { + println!("Uninstalling package: {}", pkgname); + + let installed_packages = Package::list_installed_packages().map_err(|e| { + log::error!("Failed to list installed packages: {}", e); + e + })?; + + let package_manifest = installed_packages + .iter() + .find(|p| p.name == *pkgname) + .ok_or_else(|| { + std::io::Error::other(format!("Package '{}' is not installed", pkgname)) + })?; + + let package = Package { + name: package_manifest.name.clone(), + version: package_manifest.version.clone(), + ..Default::default() + }; + + match package.uninstall() { + Ok(true) => { + println!("Successfully uninstalled package: {}", pkgname); + Ok(()) + } + Ok(false) => { + log::warn!( + "Some files could not be removed during uninstallation of {}", + pkgname + ); + Err(std::io::Error::other("Partial uninstallation occurred").into()) + } + Err(e) => { + log::error!("Failed to uninstall package {}: {}", pkgname, e); + Err(e.into()) + } + } + } + Commands::GetSource { pkgname } => { let config = Config::parse().unwrap(); - if args.http { - println!("Installing {} via HTTP", pkgname); - let mut http_client = HTTPPackage::new(config); - http_client.fetch_index_http().await?; - log::info!("Index fetched successfully."); - http_client.fetch_package_http(pkgname).await?; - log::info!("Package '{}' installed successfully.", pkgname); + println!("Getting source of {}", pkgname); + + let source_path = GitSource::get_source_by_name(pkgname, &config)?; + println!("Source code successfully downloaded to: {}", source_path); + return Ok(()); + } + Commands::DefaultConfig { + repo, + cachedir, + buildir, + installed_db, + } => { + println!("Generating config file"); + if cachedir.is_none() && repo.is_none() && buildir.is_none() { + let config = Config::default().unwrap(); + + println!("---- Start of generated config ----"); + println!("{}", config); + println!("---- End of generated config ----"); + + log::warn!("Writing the default config to /etc/mesk/mesk.toml"); + + let path = Path::new("/etc/mesk/mesk.toml"); + std::fs::create_dir_all(path.parent().unwrap())?; + let mut file = std::fs::File::create(path)?; + file.write_all(config.as_bytes())?; + println!("Config tool ending work."); } else { - println!("Installing {} via I2P", pkgname); - let mut i2p_client = I2PPackage::new(config); - i2p_client.fetch_index().await?; - log::info!("Index fetched successfully."); - i2p_client.fetch_package(pkgname).await?; - log::info!("Package '{}' installed successfully.", pkgname); + let config = Config::generate(repo, cachedir, buildir, installed_db).unwrap(); + + println!("---- Start of generated config ----"); + println!("{:?}", config); + println!("---- End of generated config ----"); + + log::warn!("Writing the default config to /etc/mesk/mesk.toml"); + + let path = Path::new("/etc/mesk/mesk.toml"); + std::fs::create_dir_all(path.parent().unwrap())?; + let mut file = std::fs::File::create(path)?; + file.write_all(config.as_bytes())?; + println!("Config tool ending work."); } + return Ok(()); } - return Ok(()); - } - Commands::Uninstall { pkgname } => { - println!("Uninstalling {}", pkgname); - return Ok(()); - } - Commands::GetSource { pkgname } => { - let config = Config::parse().unwrap(); - println!("Getting source of {}", pkgname); + Commands::Update => { + let config = Config::parse().unwrap(); + println!("Updating index from {}", config.repo.repo_url); - let source_path = GitSource::get_source_by_name(pkgname, &config)?; - println!("Source code successfully downloaded to: {}", source_path); - return Ok(()); - } - Commands::DefaultConfig { - repo, - cachedir, - buildir, - installed_db, - } => { - println!("Generating config file"); - if cachedir.is_none() && repo.is_none() && buildir.is_none() { - let config = Config::default().unwrap(); - - println!("---- Start of generated config ----"); - println!("{}", config); - println!("---- End of generated config ----"); - - log::warn!("Writing the default config to /etc/mesk/mesk.toml"); - - let path = Path::new("/etc/mesk/mesk.toml"); - std::fs::create_dir_all(path.parent().unwrap())?; - let mut file = std::fs::File::create(path)?; - file.write_all(config.as_bytes())?; - println!("Config tool ending work."); - } else { - let config = Config::generate(repo, cachedir, buildir, installed_db).unwrap(); - - println!("---- Start of generated config ----"); - println!("{:?}", config); - println!("---- End of generated config ----"); - - log::warn!("Writing the default config to /etc/mesk/mesk.toml"); - - let path = Path::new("/etc/mesk/mesk.toml"); - std::fs::create_dir_all(path.parent().unwrap())?; - let mut file = std::fs::File::create(path)?; - file.write_all(config.as_bytes())?; - println!("Config tool ending work."); + let mut i2p_client = I2PPackage::new(config); + i2p_client.fetch_index().await?; + println!("Index updated successfully."); + return Ok(()); } - return Ok(()); - } - Commands::Update => { - let config = Config::parse().unwrap(); - println!("Updating index from {}", config.repo.repo_url); - - let mut i2p_client = I2PPackage::new(config); - i2p_client.fetch_index().await?; - println!("Index updated successfully."); - return Ok(()); - } - Commands::Upgrade { pkgname } => { - println!("Upgrading {}", pkgname.as_deref().unwrap_or("all packages")); - return Ok(()); - } - Commands::Credits => { - println!( - "CREATED BY: Asya and Namilsk as part of the Anthrill independent Global network distribution project" - ); - println!(" "); - println!("The Anthrill project repos: https://codeberg.org/NamelessTeam "); - return Ok(()); - } - Commands::GenIndex { path } => { - println!("Generating index for {}", path); + Commands::Upgrade { pkgname } => { + println!("Upgrading {}", pkgname.as_deref().unwrap_or("all packages")); + return Ok(()); + } + Commands::Credits => { + println!( + "CREATED BY: Namilsk as part of the Anthrill independent Global network distribution project" + ); + println!(" "); + println!("The Anthrill project repos: https://codeberg.org/NamelessTeam "); + return Ok(()); + } + Commands::GenIndex { path } => { + println!("Generating index for {}", path); - Package::gen_index(path)?; - println!("Index generated successfully."); - return Ok(()); + Package::gen_index(path)?; + println!("Index generated successfully."); + return Ok(()); + } } + }; + + // Shutdown router if it was initialized + if let Some(router) = ROUTER.lock().await.take() { + // TODO: Add proper router shutdown when implemented + log::info!("Shutting down integrated router..."); } + + result } diff --git a/src/openpgp/signatures.rs b/src/openpgp/signatures.rs index ac21d2b..63860f0 100644 --- a/src/openpgp/signatures.rs +++ b/src/openpgp/signatures.rs @@ -1 +1,2 @@ // Worker with signs-db +// todo!("Keys from keyserver, signatures from db"); diff --git a/src/pkgtoolkit/build.rs b/src/pkgtoolkit/build.rs index 8c5119c..8a46809 100644 --- a/src/pkgtoolkit/build.rs +++ b/src/pkgtoolkit/build.rs @@ -145,10 +145,10 @@ impl BuildOperations for Package { search_dir.join(pattern).to_string_lossy().into_owned() }; - let entries = glob(&glob_pattern) + let mut entries = glob(&glob_pattern) .map_err(|e| std::io::Error::other(format!("Invalid glob pattern: {}", e)))?; - for entry in entries { + if let Some(entry) = entries.next() { let path = entry.map_err(|e| std::io::Error::other(format!("Glob error: {}", e)))?; return Ok(Some(path)); @@ -237,7 +237,7 @@ impl BuildOperations for Package { match build_meta.build_system { BuildSystems::Make => { let found = self - .find_makefile(&build_meta, &build_dir) + .find_makefile(build_meta, &build_dir) .map_err(|e| { std::io::Error::other(format!("Failed to search for Makefile: {}", e)) })? @@ -261,7 +261,7 @@ impl BuildOperations for Package { } BuildSystems::CMake => { let found = self - .find_makefile(&build_meta, &build_dir) + .find_makefile(build_meta, &build_dir) .map_err(|e| { std::io::Error::other(format!("Failed to search for CMakeLists: {}", e)) })? diff --git a/src/pkgtoolkit/install.rs b/src/pkgtoolkit/install.rs index 4eeff2f..7ba0598 100644 --- a/src/pkgtoolkit/install.rs +++ b/src/pkgtoolkit/install.rs @@ -1,17 +1,31 @@ use crate::cfg::config::Config; +use crate::router::manager::RouterManager; use std::{ fs::{self, Permissions, create_dir_all, set_permissions}, os::unix::fs::PermissionsExt, - path::{Path, StripPrefixError}, + path::Path, + path::StripPrefixError, process::Command, }; +/// Arguments for remote package installation +#[derive(Clone)] +pub struct RemoteInstallArgs { + /// Install binary package + pub bin: bool, + /// Use non-I2P mirror + pub http: bool, + /// Clean cache before install + pub clean: bool, +} + use toml; use super::archive::ArchiveOperations; use super::build::BuildOperations; use super::types::{Package, PackageManifest}; +#[allow(dead_code)] pub trait InstallOperations { fn collect_files_from_dir(root: &Path, base: &Path) -> Result, std::io::Error>; fn install(&mut self) -> Result; @@ -19,9 +33,37 @@ pub trait InstallOperations { fn load_manifest(&self) -> Result; fn list_installed_packages() -> Result, std::io::Error>; fn is_installed(&self) -> Result; + + /// Installs a package using the specified parameters + /// + /// # Arguments + /// * `pkgname` - Name of the package to install + /// * `source` - Optional source URL or path + /// * `is_path` - Whether the source is a local path + /// * `args` - Additional installation arguments + /// * `router_manager` - Optional router manager for I2P connections + async fn install_package( + pkgname: &str, + source: Option<&str>, + is_path: bool, + args: &RemoteInstallArgs, + router_manager: &RouterManager, + ) -> Result<(), Box>; } impl InstallOperations for Package { + async fn install_package( + pkgname: &str, + source: Option<&str>, + is_path: bool, + args: &RemoteInstallArgs, + router_manager: &RouterManager, + ) -> Result<(), Box> { + // Implementation of install_package + // TODO: Add actual implementation based on your requirements + Ok(()) + } + /// Recursively collects all files from a directory and its subdirectories /// and returns them as a vector of strings. /// diff --git a/src/pkgtoolkit/types.rs b/src/pkgtoolkit/types.rs index 0439175..9d8f915 100644 --- a/src/pkgtoolkit/types.rs +++ b/src/pkgtoolkit/types.rs @@ -1,8 +1,9 @@ use serde::{Deserialize, Serialize}; use std::fmt; -#[derive(Serialize, Debug, Deserialize, Clone, PartialEq)] +#[derive(Serialize, Debug, Deserialize, Clone, PartialEq, Default)] pub enum Archs { + #[default] X86_64, Aarch64, X86, @@ -10,7 +11,7 @@ pub enum Archs { ArmV8, } -#[derive(Serialize, Debug, Deserialize, Clone)] +#[derive(Serialize, Debug, Deserialize, Clone, Default)] pub struct Package { pub name: String, pub version: String, diff --git a/src/router/manager.rs b/src/router/manager.rs new file mode 100644 index 0000000..23b7c69 --- /dev/null +++ b/src/router/manager.rs @@ -0,0 +1,144 @@ +use crate::router::router::RouterUtils; +use crate::router::router::{Emissary, EmissaryConfig}; +use log::{debug, error, info}; +use std::sync::Arc; +use std::sync::atomic::{AtomicBool, Ordering}; +use tokio::sync::mpsc; +use tokio::task::JoinHandle; + +#[derive(Debug)] +pub enum RouterMessage { + Start, + Stop, + Status, +} + +pub struct RouterManager { + handle: Option>, + tx: mpsc::Sender, + is_running: Arc, +} + +impl RouterManager { + pub async fn new( + config: &crate::cfg::config::Config, + ) -> Result> { + if !config.router.integrated_router { + return Ok(Self { + handle: None, + tx: { + let (tx, _) = mpsc::channel(1); + tx + }, + is_running: Arc::new(AtomicBool::new(false)), + }); + } + + let (tx, mut rx) = mpsc::channel::(1); + let is_running = Arc::new(AtomicBool::new(false)); + let is_running_clone = is_running.clone(); + + let router_config = config.router.clone(); + let handle = tokio::spawn(async move { + info!("Router manager task started"); + + let mut router = Emissary::new(EmissaryConfig { + storage: Some(std::path::PathBuf::from(&router_config.storage_path)), + auto_update: Some(router_config.auto_update), + http_proxy_port: Some(router_config.http_proxy_port), + socks_proxy_port: Some(router_config.socks_proxy_port), + }); + + if let Err(e) = router.config().await { + error!("Failed to configure router: {}", e); + return; + } + + let storage_path = std::path::Path::new(&router_config.storage_path); + if !storage_path.exists() + || storage_path + .read_dir() + .ok() + .is_none_or(|mut d| d.next().is_none()) + { + info!("Router storage is empty, performing initial reseed..."); + if let Err(e) = router.reseed().await { + error!("Failed to reseed router: {}", e); + return; + } + } + + let router_task = tokio::spawn(async move { + info!("Starting router service..."); + match router.start().await { + Ok(_) => info!("Router service stopped"), + Err(e) => error!("Router service error: {}", e), + } + }); + + is_running_clone.store(true, Ordering::SeqCst); + info!("Router manager is now running"); + + while let Some(msg) = rx.recv().await { + match msg { + RouterMessage::Stop => { + info!("Stopping router..."); + // TODO: Implement proper router shutdown + is_running_clone.store(false, Ordering::SeqCst); + break; + } + RouterMessage::Status => { + debug!( + "Router status: {}", + if is_running_clone.load(Ordering::SeqCst) { + "running" + } else { + "stopped" + } + ); + } + _ => {} + } + } + + if let Err(e) = router_task.await { + error!("Router task panicked: {}", e); + } + + info!("Router manager task finished"); + }); + + tokio::time::sleep(std::time::Duration::from_secs(1)).await; + + Ok(Self { + handle: Some(handle), + tx, + is_running, + }) + } + + pub async fn stop(&self) { + if self.is_running.load(Ordering::SeqCst) { + if let Err(e) = self.tx.send(RouterMessage::Stop).await { + error!("Failed to send stop message to router: {}", e); + } + } + } + + pub fn is_running(&self) -> bool { + self.is_running.load(Ordering::SeqCst) + } +} + +impl Drop for RouterManager { + fn drop(&mut self) { + if self.is_running() { + let tx = self.tx.clone(); + tokio::spawn(async move { + if let Err(e) = tx.send(RouterMessage::Stop).await { + error!("Failed to send stop message during drop: {}", e); + } + }); + } + } +} diff --git a/src/router/mod.rs b/src/router/mod.rs index 772c177..91da2e5 100644 --- a/src/router/mod.rs +++ b/src/router/mod.rs @@ -1,2 +1,2 @@ +pub mod manager; pub mod router; - diff --git a/src/router/router.rs b/src/router/router.rs index c438d75..7c36382 100644 --- a/src/router/router.rs +++ b/src/router/router.rs @@ -1,92 +1,149 @@ -use emissary_util::storage::{Storage, StorageBundle}; +use emissary_core::router::Router; +use emissary_core::{Config, Ntcp2Config, SamConfig, TransitConfig}; +use emissary_util::port_mapper::{PortMapper, PortMapperConfig}; use emissary_util::reseeder::Reseeder; +use emissary_util::runtime::tokio::Runtime as TokioRuntime; +use emissary_util::storage::{Storage, StorageBundle}; +use serde::{Deserialize, Serialize}; use std::path::PathBuf; +use std::sync::Arc; +use std::future::Future; - -pub trait RouterConfigUtils { - async fn config(&mut self) -> Result<(StorageBundle, Storage), Box>; - async fn prepare_router(&mut self) -> Result<(), Box>; - async fn reseed(&mut self, config: &StorageBundle, storage: &Storage) -> Result<(), Box>; +pub trait RouterUtils { + fn config(&mut self) -> impl Future>> + Send; + fn reseed(&mut self) -> impl Future>> + Send; + fn start(self) -> impl Future, Box>> + Send; } +#[derive(Debug, Deserialize, Serialize)] pub struct EmissaryConfig { - Storage: Option, - AutoUpdate: Option, - HttpProxtPort: Option, - SocksProxyPort: Option, + pub storage: Option, + pub auto_update: Option, + pub http_proxy_port: Option, + pub socks_proxy_port: Option, } -impl RouterConfigUtils for EmissaryConfig { - /// Configures the router with the given configuration. - /// - /// If the `Storage` field is `None`, it will be set to `/var/lib/mesk/router/`. - /// - /// # Errors - /// - /// Returns an error if there's an issue while configuring the router. - async fn config(&mut self) -> Result<(StorageBundle, Storage), Box> { - self.Storage.get_or_insert_with(|| PathBuf::from("/var/lib/mesk/router/")); - self.AutoUpdate.get_or_insert(true); - self.HttpProxtPort.get_or_insert(4445); - self.SocksProxyPort.get_or_insert(4446); - - let storage = Storage::new(self.Storage.clone().into()).await?; - - let StorageBundle { - ntcp2_iv, - ntcp2_key, - profiles, - router_info, - routers, - signing_key, - static_key, - ssu2_intro_key, - ssu2_static_key, - } = storage.load().await; - - Ok((StorageBundle { - ntcp2_iv, - ntcp2_key, - profiles, - router_info, - routers, - signing_key, - static_key, - ssu2_intro_key, - ssu2_static_key, - }, storage)) +pub struct Emissary { + config: EmissaryConfig, + storage: Option, + storage_bundle: Option, +} + +impl Emissary { + pub fn new(config: EmissaryConfig) -> Self { + Self { + config, + storage: None, + storage_bundle: None, + } + } + + fn ensure_configured(&self) -> Result<(), Box> { + if self.storage.is_none() || self.storage_bundle.is_none() { + Err("Router not configured. Call `config()` first.".into()) + } else { + Ok(()) + } + } +} + +impl RouterUtils for Emissary { + async fn config(&mut self) -> Result<(), Box> { + if self.config.storage.is_none() { + self.config.storage = Some(PathBuf::from("/var/lib/mesk/router/")); + } + if self.config.auto_update.is_none() { + self.config.auto_update = Some(true); + } + if self.config.http_proxy_port.is_none() { + self.config.http_proxy_port = Some(4445); + } + if self.config.socks_proxy_port.is_none() { + self.config.socks_proxy_port = Some(4446); + } + + let storage = Storage::new(Some(self.config.storage.clone().unwrap())).await?; + let bundle = storage.load().await; + + self.storage = Some(storage); + self.storage_bundle = Some(bundle); + + Ok(()) } - async fn reseed(&mut self, config: &StorageBundle, storage: &Storage) -> Result<(), Box> { - let mut routers = config.routers.clone(); - - - if routers.is_empty() { + async fn reseed(&mut self) -> Result<(), Box> { + self.ensure_configured()?; + + let storage = self.storage.as_ref().unwrap(); + let mut bundle = self.storage_bundle.take().unwrap(); + + if bundle.routers.is_empty() { match Reseeder::reseed(None, false).await { Ok(reseed_routers) => { for info in reseed_routers { - let _ = storage + if let Err(e) = storage .store_router_info(info.name.to_string(), info.router_info.clone()) - .await; - routers.push(info.router_info); + .await + { + log::warn!("Failed to store reseeded router info: {}", e); + } + bundle.routers.push(info.router_info); } } - Err(_) if routers.is_empty() => { - return Err("Could not reseed routers.".into()); - } - Err(error) => { - log::warn!( - "Failed to reseed routers: {} - using existing routers", error.to_string(), - ); + Err(e) => { + if bundle.routers.is_empty() { + self.storage_bundle = Some(bundle); + return Err(format!("Reseed failed and no routers available: {}", e).into()); + } else { + log::warn!("Reseed failed, but using existing routers: {}", e); + } } } - } + } + + self.storage_bundle = Some(bundle); Ok(()) } - async fn prepare_router(&mut self) -> Result<(), Box> { - - - Ok(()) + async fn start(self) -> Result, Box> { + let storage = self.storage.unwrap(); + let bundle = self.storage_bundle.unwrap(); + + let config = Config { + ntcp2: Some(Ntcp2Config { + port: 25515, + key: bundle.ntcp2_key, + iv: bundle.ntcp2_iv, + publish: true, + host: None, + }), + samv3_config: Some(SamConfig { + tcp_port: self.config.http_proxy_port.unwrap_or(4445), + udp_port: self.config.socks_proxy_port.unwrap_or(4446), + host: "127.0.0.1".to_string(), + }), + routers: bundle.routers, + profiles: bundle.profiles, + router_info: bundle.router_info, + static_key: Some(bundle.static_key), + signing_key: Some(bundle.signing_key), + transit: Some(TransitConfig { + max_tunnels: Some(1000), + }), + ..Default::default() + }; + + let (router, _events, router_info) = Router::::new( + config, + None, // AddressBook + Some(Arc::new(storage.clone())), + ) + .await + .map_err(|e| format!("Router creation failed: {}", e))?; + + // Сохраняем router_info, если он новый + storage.store_local_router_info(router_info).await?; + + Ok(router) } -} \ No newline at end of file +} -- cgit v1.2.3