summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/i2impl/mi2p.rs162
-rw-r--r--src/i2impl/mod.rs1
-rw-r--r--src/main.rs37
-rw-r--r--src/net/http_packages.rs92
-rw-r--r--src/net/i2p_package.rs120
-rw-r--r--src/net/i2p_tools.rs (renamed from src/i2impl/i2tools.rs)0
-rw-r--r--src/net/mod.rs2
-rw-r--r--src/pkgtoolkit/pkgtools.rs205
8 files changed, 403 insertions, 216 deletions
diff --git a/src/i2impl/mi2p.rs b/src/i2impl/mi2p.rs
deleted file mode 100644
index cf2b8e5..0000000
--- a/src/i2impl/mi2p.rs
+++ /dev/null
@@ -1,162 +0,0 @@
-
-use crate::cfg::config::Config;
-
-use tokio;
-use emissary_core::runtime::{
- AsyncRead,
- AsyncWrite,
-};
-
-use std::{io,
- fs::File,
- path::Path,
- io::Write};
-// use emissary_core::Profile;
-// use emissary_core::i2np::Message;
-use tokio::io::{AsyncReadExt, AsyncWriteExt};
-use yosemite::SessionOptions;
-use yosemite::{Session, style::Stream};
-/*
-use i2p_client::ClientType;
-use i2p_client::I2PClient;
-use i2p_client::SessionStyle::Stream;
-use i2p_client::Session;
-
-struct I2PStatus {
- Connected: bool,
-}
-
-impl I2PStatus {
- pub fn connect(&self) -> Result<bool, std::io::Error> {
-
- let config: Config = Config::parse().unwrap();
- let client= I2PClient::new(true, "MeskPKG-manager".to_string(), "2.0", "2.58.0", 10);
- // let destination = Session::r#gen(&mut self, SigType::EdDsaSha512Ed25519)
- let session = Session::create(config.repo.repo_url,
- &config.repo.destination.0,
- "MeskPKG-manager",
- Stream,
- "2.0",
- "2.58");
-
- Ok(true)
- }
-}
-*/
-
-pub struct I2P<Stream> {
- session: Option<Session<Stream>>,
- connected: bool,
- config: Config,
-
-}
-
-impl I2P<Stream> {
- /// Creates a new I2P object with the given configuration.
- ///
- /// # Returns
- ///
- /// A new I2P object with the given configuration. The session is initially set to None and the connected status is set to false.
- pub fn new(config: Config) -> Self {
- I2P {
- session: None,
- connected: false,
- config: config,
- }
- }
-
- /// Fetches the list of packages from the repository specified in the configuration.
- ///
- /// This function connects to the repository specified in the configuration, sends a GET request for the repository list and returns true if the request is successful, false otherwise.
- ///
- /// # Errors
- ///
- /// Returns an error if the repository URL is invalid or if the request fails.
- ///
- /// # Shortcomings
- /// - Currently, &str is used instead of Path.
- /// - An incomplete I2P structure, the session is stored in the structure but
- /// is recreated when called, perhaps the connection/disconnection methods should be encapsulated,
- /// although the function is not used often so it will not carry major changes
- pub async fn fetch_index(&mut self) -> Result<bool, std::io::Error> {
- let repo_url_str = &self.config.repo.repo_url;
- let cache_dir = &self.config.paths.cache_dir;
-
- let repo_pos = repo_url_str.find("/repo").ok_or_else(|| {
- io::Error::new(io::ErrorKind::InvalidData, "URL does not contain '/repo'")
- })?;
- // BaseURL
- let base_url = &repo_url_str[..repo_pos];
- // Url after /repo
- let path_suffix = &repo_url_str[repo_pos + "/repo".len()..];
- // HTTP path
- let request_path = format!("/repo{}{}", path_suffix, if path_suffix.ends_with(".tar.gz") { "" } else { "/INDEX.tar.gz" });
-
- let opts = SessionOptions::default();
- // FIXME: Make sure, opts setted to I2P
- // opts.set_host("127.0.0.1");
- // opts.set_port(7656);
- let mut session = Session::<Stream>::new(opts).await.map_err(|e| {
- io::Error::new(io::ErrorKind::Other, format!("Failed to create session: {}", e))
- })?;
-
- let mut stream = session.connect(base_url).await.map_err(|e| {
- io::Error::new(io::ErrorKind::ConnectionAborted, format!("Failed to connect: {}", e))
- })?;
-
- let request = format!("GET {} HTTP/1.1\r\nHost: {}\r\n\r\n", request_path, base_url);
- stream.write_all(request.as_bytes()).await.map_err(|e| {
- io::Error::new(io::ErrorKind::Other, format!("Failed to write request: {}", e))
- })?;
-
- let mut response_buffer = Vec::new();
- let mut chunk = [0u8; 1024];
-
- loop {
- // FIXME: Check docs and make suro stream allows to AsyncReadExt
- let bytes_read = stream.read(&mut chunk).await.map_err(|e| {
- io::Error::new(io::ErrorKind::Other, format!("Failed to read response: {}", e))
- })?;
-
- if bytes_read == 0 {
- break;
- }
-
- response_buffer.extend_from_slice(&chunk[..bytes_read]);
-
- if let Some(headers_end) = Self::find_double_crlf(&response_buffer) {
- let body_start = headers_end + 4;
- let headers_str = std::str::from_utf8(&response_buffer[..headers_end])
- .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "Invalid header encoding"))?;
-
- if !headers_str.starts_with("HTTP/1.1 200") && !headers_str.starts_with("HTTP/1.0 200") {
- return Err(io::Error::new(io::ErrorKind::Other, format!("HTTP Error: {}", headers_str.lines().next().unwrap_or("Unknown"))));
- }
-
- let file_path = Path::new(cache_dir).join("INDEX.tar.gz");
- let mut file = File::create(&file_path)?;
- file.write_all(&response_buffer[body_start..])?;
- while let Ok(bytes_read) = stream.read(&mut chunk).await {
- if bytes_read == 0 {
- break;
- }
- file.write_all(&chunk[..bytes_read])?;
- }
-
- file.flush()?;
- break;
- }
- }
-
- Ok(true)
- }
-
- fn find_double_crlf(buf: &[u8]) -> Option<usize> {
- for i in 0..buf.len().saturating_sub(3) {
- if buf[i] == b'\r' && buf[i+1] == b'\n' && buf[i+2] == b'\r' && buf[i+3] == b'\n' {
- return Some(i);
- }
- }
- None
- }
-} \ No newline at end of file
diff --git a/src/i2impl/mod.rs b/src/i2impl/mod.rs
deleted file mode 100644
index 4fa58ff..0000000
--- a/src/i2impl/mod.rs
+++ /dev/null
@@ -1 +0,0 @@
-pub mod mi2p; \ No newline at end of file
diff --git a/src/main.rs b/src/main.rs
index c38d265..463bd24 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,14 +1,13 @@
mod cfg;
-mod i2impl;
+mod net;
mod pkgtoolkit;
use crate::cfg::config::Config;
#[allow(unused_imports)]
use crate::pkgtoolkit::pkgtools::Package;
-use crate::i2impl::mi2p::I2P;
+use crate::net::i2p_package::I2PPackage;
use clap::{Args, Command, Parser, Subcommand};
-use yosemite::Stream;
use std::io::Write;
use std::path::Path;
use std::fs::create_dir_all;
@@ -41,6 +40,9 @@ enum Commands {
Install{
pkgname: String,
source: Option<String>,
+ #[command(flatten)]
+ args: RemoteInstallArgs
+
},
#[command(about = "Uninstall package")]
Uninstall{
@@ -62,12 +64,13 @@ enum Commands {
#[derive(Args, Clone)]
#[command(about = "Remote install arguments")]
-struct RemoteInstallArgs {
- verbose: bool,
- debug: bool,
+struct RemoteInstallArgs {
+ #[arg(short = 'b', long = "bin" )]
bin: bool,
- source: bool,
- i2p: bool,
+ #[arg(short = 'h', long = "http" )]
+ http: bool,
+ #[arg(short = 'c', long = "clean" )]
+ clean: bool
}
#[tokio::main]
@@ -85,7 +88,7 @@ async fn main() -> Result<(), std::io::Error> {
println!("Building {}", pkgname);
return Ok(())
},
- Commands::Install { pkgname, source} => {
+ Commands::Install { pkgname, source, args} => {
println!("Installing {}", pkgname);
return Ok(())
},
@@ -109,9 +112,9 @@ async fn main() -> Result<(), std::io::Error> {
log::warn!("Writing the default config to /etc/mesk/mesk.toml");
let path = Path::new("/etc/mesk/mesk.toml");
- create_dir_all(path.parent().unwrap()).unwrap();
- let mut file = File::create(path).unwrap();
- file.write(config.as_bytes()).unwrap();
+ create_dir_all(path.parent().unwrap())?;
+ let mut file = File::create(path)?;
+ file.write(config.as_bytes())?;
println!("Config tool ending work.");
} else {
@@ -124,9 +127,9 @@ async fn main() -> Result<(), std::io::Error> {
log::warn!("Writing the default config to /etc/mesk/mesk.toml");
let path = Path::new("/etc/mesk/mesk.toml");
- create_dir_all(path.parent().unwrap()).unwrap();
- let mut file = File::create(path).unwrap();
- file.write_all(config.as_bytes()).unwrap();
+ create_dir_all(path.parent().unwrap())?;
+ let mut file = File::create(path)?;
+ file.write_all(config.as_bytes())?;
println!("Config tool ending work.");
}
@@ -135,8 +138,8 @@ async fn main() -> Result<(), std::io::Error> {
Commands::Update => {
let config = Config::parse().unwrap();
println!("Updating index from {}", config.repo.repo_url);
- let mut i2pd = I2P::new(config);
- let _index= I2P::fetch_index(&mut i2pd).await?;
+ let mut i2pd = I2PPackage::new(config);
+ let _index= I2PPackage::fetch_index(&mut i2pd).await?;
println!("Index updated");
return Ok(())
},
diff --git a/src/net/http_packages.rs b/src/net/http_packages.rs
new file mode 100644
index 0000000..1e3c6ad
--- /dev/null
+++ b/src/net/http_packages.rs
@@ -0,0 +1,92 @@
+use reqwest;
+use std::fs::File;
+use std::io::Write;
+use std::path::Path;
+use crate::cfg::config::Config;
+
+pub struct HTTPPackage {
+ config: Config,
+}
+
+impl HTTPPackage {
+ /// Creates a new Downloader object with the given configuration.
+ ///
+ /// # Arguments
+ ///
+ /// * `config` - The full mesk
+ ///
+ /// # Returns
+ ///
+ /// A new Downloader object with the given configuration.
+ pub fn new(config: Config) -> Self {
+ Self { config }
+ }
+
+ /// Parse the mesk configuration file and return the Config object.
+ ///
+ /// This function reads the file at `config_path`, parses it and returns the Config object.
+ ///
+ /// # Arguments
+ ///
+ /// * `config_path` - A string representing the path to the mesk configuration file.
+ ///
+ /// # Returns
+ ///
+ /// A new Config object with the parsed configuration. If there's an error while reading or parsing the file, returns an Err containing a Box<dyn std::error::Error>.
+ pub fn parse_config(config_path: &str) -> Result<Config, Box<dyn std::error::Error>> {
+ let config = Config::parse()?;
+ Ok(config)
+ }
+
+
+ /// Downloads the INDEX.tar.gz file from the configured repository
+ /// and stores it in the configured cache directory.
+ ///
+ /// # Errors
+ ///
+ /// Returns an error if the request fails, if the response status is not successful, or if there's an issue while reading or writing the file.
+ ///
+ pub async fn fetch_index_http(&mut self) -> Result<bool, std::io::Error> {
+ let repo_url_str = &self.config.repo.repo_url;
+ let cache_dir = &self.config.paths.cache_dir;
+
+ let index_url = if repo_url_str.ends_with(".tar.gz") {
+ repo_url_str.clone()
+ } else {
+ format!("{}/INDEX.tar.gz", repo_url_str.trim_end_matches('/'))
+ };
+
+ let client = reqwest::Client::new();
+
+ let response = client
+ .get(&index_url)
+ .send()
+ .await
+ .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, format!("Request failed: {}", e)))?;
+
+ if !response.status().is_success() {
+ return Err(std::io::Error::new(
+ std::io::ErrorKind::Other,
+ format!("HTTP Error: {}", response.status()),
+ ));
+ }
+
+ let index_data = response
+ .bytes()
+ .await
+ .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, format!("Failed to read response body: {}", e)))?
+ .to_vec();
+
+ let file_path = Path::new(cache_dir).join("INDEX.tar.gz");
+
+ let mut file = File::create(&file_path)
+ .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, format!("Failed to create file: {}", e)))?;
+ file.write_all(&index_data)
+ .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, format!("Failed to write file: {}", e)))?;
+ file.flush()
+ .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, format!("Failed to flush file: {}", e)))?;
+
+ Ok(true)
+ }
+}
+
diff --git a/src/net/i2p_package.rs b/src/net/i2p_package.rs
new file mode 100644
index 0000000..2776eea
--- /dev/null
+++ b/src/net/i2p_package.rs
@@ -0,0 +1,120 @@
+
+use crate::cfg::config::Config;
+
+use tokio;
+/*
+use emissary_core::runtime::{
+ AsyncRead,
+ AsyncWrite,
+};
+*/
+
+use std::{fs::File,
+ path::Path,
+ io::Write};
+// use emissary_core::Profile;
+// use emissary_core::i2np::Message;
+use tokio::io::{AsyncReadExt,
+ AsyncWriteExt,
+ BufReader};
+
+use yosemite::SessionOptions;
+use yosemite::{Session, style::Stream};
+use url;
+
+pub struct I2PPackage {
+ config: Config,
+
+}
+
+
+impl I2PPackage {
+ /// Creates a new I2P object with the given configuration.
+ ///
+ /// # Returns
+ ///
+ /// A new I2P object with the given configuration. The session is initially set to None and the connected status is set to false.
+ pub fn new(config: Config) -> Self {
+ I2PPackage{
+
+ config: config,
+ }
+ }
+
+
+ /// Downloads the INDEX.tar.gz file from the configured repository
+ /// and stores it in the configured cache directory.
+ ///
+ /// # Errors
+ ///
+ /// Returns an error if the request fails, if the response status is not successful, or if there's an issue while reading or writing the file.
+ pub async fn fetch_index(&mut self) -> Result<bool, std::io::Error> {
+ let repo_url_str = &self.config.repo.repo_url;
+ let cache_dir = &self.config.paths.cache_dir;
+
+ let url = url::Url::parse(repo_url_str)
+ .map_err(|_| std::io::Error::new(std::io::ErrorKind::InvalidInput, "Invalid repo URL"))?;
+
+ let host = url.host_str()
+ .ok_or_else(|| std::io::Error::new(std::io::ErrorKind::InvalidInput, "No host in URL"))?;
+
+ let request_path = url.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 sam_host = "127.0.0.1";
+ let sam_port = 7656;
+ let session_options = SessionOptions::default();
+ let mut session = Session::new(session_options).await
+ .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, format!("Failed to create SAM session: {}", e)))?;
+
+ let mut stream = session.connect(host).await
+ .map_err(|e| std::io::Error::new(std::io::ErrorKind::ConnectionAborted, format!("Failed to connect: {}", e)))?;
+
+ let request = format!(
+ "GET {} HTTP/1.1\r\nHost: {}\r\nConnection: close\r\n\r\n",
+ request_path, host
+ );
+
+
+ stream.write_all(request.as_bytes()).await
+ .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, format!("Failed to write request: {}", e)))?;
+
+ let mut reader = BufReader::new(stream);
+ let mut response_buffer = Vec::new();
+ reader.read_to_end(&mut response_buffer).await
+ .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, format!("Failed to read response: {}", e)))?;
+
+
+ let headers_end = response_buffer
+ .windows(4)
+ .position(|window| window == b"\r\n\r\n")
+ .ok_or_else(|| std::io::Error::new(std::io::ErrorKind::InvalidData, "Invalid response: no headers end"))?;
+
+ let headers_str = std::str::from_utf8(&response_buffer[..headers_end])
+ .map_err(|_| std::io::Error::new(std::io::ErrorKind::InvalidData, "Invalid header encoding"))?;
+
+ if !headers_str.starts_with("HTTP/1.1 200") && !headers_str.starts_with("HTTP/1.0 200") {
+ return Err(std::io::Error::new(
+ std::io::ErrorKind::Other,
+ format!("HTTP Error: {}", headers_str.lines().next().unwrap_or("Unknown")),
+ ));
+ }
+
+ let body_start = headers_end + 4;
+ let file_path = Path::new(cache_dir).join("INDEX.tar.gz");
+
+
+ let mut file = File::create(&file_path)
+ .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, format!("Failed to create file: {}", e)))?;
+ file.write_all(&response_buffer[body_start..])
+ .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, format!("Failed to write file: {}", e)))?;
+ file.flush()
+ .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, format!("Failed to flush file: {}", e)))?;
+
+ Ok(true)
+ }
+}
diff --git a/src/i2impl/i2tools.rs b/src/net/i2p_tools.rs
index e69de29..e69de29 100644
--- a/src/i2impl/i2tools.rs
+++ b/src/net/i2p_tools.rs
diff --git a/src/net/mod.rs b/src/net/mod.rs
new file mode 100644
index 0000000..54f5e7f
--- /dev/null
+++ b/src/net/mod.rs
@@ -0,0 +1,2 @@
+pub mod i2p_package;
+pub mod http_packages; \ No newline at end of file
diff --git a/src/pkgtoolkit/pkgtools.rs b/src/pkgtoolkit/pkgtools.rs
index 5acbd11..cfd3d59 100644
--- a/src/pkgtoolkit/pkgtools.rs
+++ b/src/pkgtoolkit/pkgtools.rs
@@ -6,8 +6,10 @@ use std::{
io,
path::Path,
process::Command,
- str};
+ str,
+ os::unix::fs::PermissionsExt};
+ use emissary_core::i2np::tunnel::build;
// use emissary_core::i2np::tunnel::build;
use flate2::read::GzDecoder;
use serde::{Deserialize, Serialize};
@@ -81,44 +83,74 @@ impl archs {
#[allow(dead_code)]
impl Package {
- fn builder_backend(&mut self) -> Result<bool, std::io::Error> {
- 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 {
+ /// Execute the build script for the package.
+ ///
+ /// This function takes the `Build` meta information as an argument and
+ /// executes the build script accordingly. It also handles the different
+ /// build systems supported (Make, CMake, Meson, Cargo).
+ ///
+ /// # Errors
+ ///
+ /// Returns an error if the build command fails.
+ fn execute_build(&self, build_meta: &Build) -> Result<(), std::io::Error> {
+ let config = Config::parse()
+ .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))?;
+ let build_dir = Path::new(&config.paths.cache_dir)
+ .join(format!("{}-{}", self.name, self.version));
+
+ let mut cmd = match build_meta.build_system {
BuildSystems::Make => {
- let _setup = Command::new("make")
- .arg("all")
- .arg("all")
- .output();
+ let mut c = Command::new("make");
+ c.current_dir(&build_dir);
+ if build_meta.script.is_some() {
+ c.arg("-f").arg(build_meta.script.as_ref().unwrap());
+ }
+ c.arg("all");
+ c
}
-
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();
+ let build_dir_build = build_dir.join("build");
+ create_dir_all(&build_dir_build)?;
+ let mut c = Command::new("cmake");
+ c.arg("-S").arg(&build_dir)
+ .arg("-B").arg(&build_dir_build)
+ .current_dir(&build_dir);
+ c
}
-
- _ => {
- Err(std::io::Error::new(std::io::ErrorKind::NotFound, "BUILD file not found"))?
+ BuildSystems::Meson => {
+ let build_dir_build = build_dir.join("build");
+ create_dir_all(&build_dir_build)?;
+ let mut c = Command::new("meson");
+ c.arg("setup")
+ .arg(&build_dir_build)
+ .current_dir(&build_dir);
+ c
+ }
+ BuildSystems::Cargo => {
+ let mut c = Command::new("cargo");
+ c.arg("build")
+ .arg("--release")
+ .current_dir(&build_dir);
+ c
}
+ };
+
+ let output = cmd.output().map_err(|e| {
+ std::io::Error::new(
+ std::io::ErrorKind::Other,
+ format!("Build command failed: {}", e),
+ )
+ })?;
+
+ if !output.status.success() {
+ let stderr = String::from_utf8_lossy(&output.stderr);
+ return Err(std::io::Error::new(
+ std::io::ErrorKind::Other,
+ format!("Build failed:\n{}", stderr),
+ ));
}
- Ok(true)
+ Ok(())
}
/// Extracts a .tar.gz archive to the cache directory specified in Config.
@@ -366,15 +398,116 @@ impl Package {
}
Strategies::SOURCE => {
log::info!("Strategy: SOURCE; Running default build hook.");
- todo!();
+ let _ = self.execute_build(&build_meta.unwrap());
}
}
Ok(true)
}
- pub fn install() -> Result<bool, std::io::Error> {
- todo!();
- }
+
+
+ /// Installs the package according to the INSTALL file in the archive.
+ ///
+ /// There are two strategies for installing 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 install(&mut self) -> Result<bool, std::io::Error> {
+ let config = Config::parse()
+ .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))?;
+ let (install_meta, _setts_meta, build_meta) = Self::loadmeta(self)
+ .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))?;
+
+
+ let is_build_present_and_not_empty = build_meta.is_some();
+
+ if is_build_present_and_not_empty {
+ log::info!("Found BUILD file, preparing to build and install package: {}", self.name);
+ let build_meta_ref = build_meta.as_ref().unwrap();
+ 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::new(std::io::ErrorKind::Other, format!("'make install' failed: {}", e)))?;
+
+ if !output.status.success() {
+ let stderr = String::from_utf8_lossy(&output.stderr);
+ return Err(std::io::Error::new(
+ std::io::ErrorKind::Other,
+ format!("'make install' failed:\n{}", stderr),
+ ));
+ }
+ }
+ } 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.custom_script {
+ log::info!("Executing custom install script for {}", install_meta.package.name);
+ let status = if script.starts_with("./") || script.contains('/') {
+ Command::new("/bin/sh").arg("-c").arg(script).status()
+ } else {
+ Command::new(script).status()
+ }
+ .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, format!("Failed to run custom script: {}", e)))?;
+
+ if !status.success() {
+ return Err(std::io::Error::new(
+ std::io::ErrorKind::Other,
+ "Custom install script failed",
+ ));
+ }
+ } else {
+ log::info!("No custom script. Running default install hook for {}", install_meta.package.name);
+ // --- Дефолтный хук установки ---
+ // 1. Копируем файл из build_dir (предположим, что бинарный файл лежит в корне распакованного архива)
+ let source_file_name = &self.name; // Предполагаем имя файла = имя пакета
+ let build_dir = Path::new(&config.paths.cache_dir)
+ .join(format!("{}-{}", self.name, self.version));
+ let src_path = build_dir.join(source_file_name);
+ let dest_path = Path::new(&install_meta.path);
+
+ // Убедимся, что целевая директория существует
+ if let Some(parent) = dest_path.parent() {
+ create_dir_all(parent)
+ .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, format!("Failed to create parent dir: {}", e)))?;
+ }
+
+ fs::copy(&src_path, dest_path)
+ .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, format!("Failed to copy file: {}", e)))?;
+
+ let mode = u32::from_str_radix(&install_meta.mode, 8)
+ .map_err(|_| std::io::Error::new(std::io::ErrorKind::InvalidData, "Invalid mode string in INSTALL"))?;
+ let perms = PermissionsExt::from_mode(mode);
+ fs::set_permissions(dest_path, perms)
+ .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, format!("Failed to set permissions: {}", e)))?;
+
+ let output = Command::new("chown")
+ .arg(&format!("{}:{}", install_meta.user, install_meta.group))
+ .arg(dest_path)
+ .output()
+ .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, format!("'chown' command failed: {}", e)))?;
+
+ if !output.status.success() {
+ let stderr = String::from_utf8_lossy(&output.stderr);
+ log::warn!("Warning: 'chown' command failed (requires root?):\n{}", stderr);
+ }
+ }
+ }
+
+ log::info!("Package {} installed successfully.", self.name);
+ Ok(true)
+ }
pub fn gen_index() -> Result<bool, std::io::Error> {
todo!();