diff --git a/noxis-rs/.env.example b/noxis-rs/.env.example index 8efbd9b..1909961 100644 --- a/noxis-rs/.env.example +++ b/noxis-rs/.env.example @@ -11,4 +11,6 @@ NOXIS_REMOTE_SERVER_URL = "ip.ip.ip.ip:port" NOXIS_CONFIG_PATH = "./settings.json" NOXIS_METRICS_MODE = "full" NOXIS_SOCKET_PATH = "/path/to/noxis.sock" +NOXIS_BACKUP_FOLDER = "/path/to/backups/folder" + NOXIS_MAX_LOG_LEVEL = "TRACE" \ No newline at end of file diff --git a/noxis-rs/Cargo.toml b/noxis-rs/Cargo.toml index 73ed76b..086e0f1 100644 --- a/noxis-rs/Cargo.toml +++ b/noxis-rs/Cargo.toml @@ -22,3 +22,4 @@ futures = "0.3.31" async-trait = "0.1.88" crossbeam = { version = "0.8.4", features = ["crossbeam-channel"] } lazy_static = "1.5.0" +ulid = "1.2.1" diff --git a/noxis-rs/settings.json b/noxis-rs/settings.json index 263600a..7c74d01 100644 --- a/noxis-rs/settings.json +++ b/noxis-rs/settings.json @@ -11,7 +11,35 @@ "src": "./tests/examples/", "triggers": { "onDelete": "stop", - "onChange": "restart" + "onChange": "restart", + "doRestore" : true + } + }, + { + "filename": "none.json", + "src": "./tests/examples/", + "triggers": { + "onDelete": "stop", + "onChange": "restart", + "doRestore" : false + } + }, + { + "filename": "invalid_config.json", + "src": "./tests/examples/", + "triggers": { + "onDelete": "stop", + "onChange": "restart", + "doRestore" : false + } + }, + { + "filename": "save-conf.json", + "src": "./tests/examples/", + "triggers": { + "onDelete": "stop", + "onChange": "restart", + "doRestore" : true } } ], @@ -23,6 +51,14 @@ "wait": 2, "onLost": "stop" } + }, + { + "hostname": "8.8.8.8", + "port": 443, + "triggers": { + "wait": 2, + "onLost": "stop" + } } ] } diff --git a/noxis-rs/src/main.rs b/noxis-rs/src/main.rs index 5e92b4f..c39cbef 100644 --- a/noxis-rs/src/main.rs +++ b/noxis-rs/src/main.rs @@ -108,6 +108,7 @@ async fn main() -> anyhow::Result<()> { handler.push(ctrlc); let tx_bus = tx_to_bus.clone(); + let preboot_cli = preboot.clone(); let monitoring = tokio::spawn(async move { let config = { let mut tick = tokio::time::interval(Duration::from_millis(500)); @@ -119,7 +120,7 @@ async fn main() -> anyhow::Result<()> { }; } }; - if let Err(er) = init_monitoring(config, rx_to_supervisor, tx_bus).await { + if let Err(er) = init_monitoring(config, preboot_cli, rx_to_supervisor, tx_bus).await { error!("Monitoring mod failed due to {}", er); } }); diff --git a/noxis-rs/src/options/preboot.rs b/noxis-rs/src/options/preboot.rs index 47a9256..22eab0b 100644 --- a/noxis-rs/src/options/preboot.rs +++ b/noxis-rs/src/options/preboot.rs @@ -189,6 +189,7 @@ pub struct PrebootParams { pub config: PathBuf, pub metrics: MetricsPrebootParams, pub self_socket: PathBuf, + pub backup_folder: PathBuf, } /// # implementation for `MetricsPrebootParams` @@ -267,6 +268,29 @@ impl PrebootParams { } } }, + backup_folder: { + match var("NOXIS_BACKUP_FOLDER") { + Ok(val) => { + let path = PathBuf::from(val); + if path.exists() && path.is_dir() { + path + } else { + PathBuf::from(std::env::current_dir() + .expect("Crushed on getting current_dir path. Check fs state!") + ) + } + }, + Err(_) => { + let default = std::env::current_dir() + .expect("Crushed on getting current_dir path. Check fs state!"); + warn!( + "$NOXIS_BACKUP_FOLDER wans't set. Default value - {}", + default.display() + ); + PathBuf::from(default) + } + } + }, } } } diff --git a/noxis-rs/src/options/structs.rs b/noxis-rs/src/options/structs.rs index 0d710e2..46c3c23 100644 --- a/noxis-rs/src/options/structs.rs +++ b/noxis-rs/src/options/structs.rs @@ -470,6 +470,8 @@ pub struct FileTriggers { pub on_delete: String, #[serde(rename = "onChange")] pub on_change: String, + #[serde(rename = "doRestore")] + pub do_restore: bool, } /// # Metrics struct diff --git a/noxis-rs/src/utils.rs b/noxis-rs/src/utils.rs index 86f29ab..385e527 100644 --- a/noxis-rs/src/utils.rs +++ b/noxis-rs/src/utils.rs @@ -26,6 +26,7 @@ lazy_static! { pub mod v2 { use super::*; + use crate::options::preboot::PrebootParams; use crate::utils::metrics::processes::{ProcessesAll, ProcessesQuery}; use crate::{ options::structs::{ @@ -65,10 +66,14 @@ pub mod v2 { bus: (bus_reciever, bus_sender), } } - pub async fn with_config(mut self, config: Processes) -> Supervisor { + pub async fn with_config( + mut self, + config: Processes, + preboot : Arc + ) -> Supervisor { self.config = Arc::from(config); let _ = self.config.processes.iter().for_each(|prc| { - let (rx, tx) = mpsc::channel::(10); + let (rx, tx) = mpsc::channel::(100); let temp = ProcessesController::new(&prc.name, tx).with_exe(&prc.path); if !self.prcs.contains(&temp) { self.prcs.push_back(temp); @@ -84,8 +89,21 @@ pub mod v2 { }; hm.insert(proc_name.clone(), (triggers, rx.clone())); + let backup_file = { + if file.triggers.do_restore { + use ulid::Ulid; + format!("{}{}.bak", { + let path = preboot.backup_folder.to_string_lossy(); + if path.ends_with("/") { path.to_string() } + else { format!("{}/", path) } + }, Ulid::new()) + } else { + String::new() + } + }; + let tempfile = - FilesController::new(&file.filename.as_str(), hm).with_path(&file.src); + FilesController::new(&file.filename.as_str(), hm).with_path(&file.src, backup_file); if let Ok(file) = tempfile { if let Some(current_file) = self.files.iter_mut().find(|a| &&file == a) { @@ -164,6 +182,7 @@ pub mod v2 { name: file.filename.to_string(), path: file.src.to_string(), status: file_cont.get_state(), + backup_file : file_cont.get_backup_file(), triggers: file.triggers.to_owned(), }); } @@ -351,11 +370,12 @@ pub mod v2 { pub async fn init_monitoring( config: Processes, + preboot : Arc, bus_reciever: BusReciever, bus_sender: BusSender, ) -> anyhow::Result<()> { let mut supervisor = Supervisor::new(bus_reciever, bus_sender) - .with_config(config) + .with_config(config, preboot) .await; info!("Monitoring: {} ", &supervisor.get_stats()); supervisor.process().await; diff --git a/noxis-rs/src/utils/files.rs b/noxis-rs/src/utils/files.rs index 9309cc1..dcbf93e 100644 --- a/noxis-rs/src/utils/files.rs +++ b/noxis-rs/src/utils/files.rs @@ -29,6 +29,7 @@ pub mod v2 { name: Arc, path: String, code_name: Arc, + backup_file : String, state: FileState, watcher: Option, triggers: EventHandlers, @@ -51,10 +52,11 @@ pub mod v2 { watcher: None, triggers, code_name: name.clone(), + backup_file: String::new(), } } #[inline(always)] - pub fn with_path(mut self, path: impl AsRef) -> anyhow::Result { + pub fn with_path(mut self, path: impl AsRef, backup : String) -> anyhow::Result { self.path = path.as_ref().to_string_lossy().into_owned(); self.watcher = { match create_watcher(&self.name, &self.path) { @@ -69,6 +71,11 @@ pub mod v2 { } }; self.code_name = Arc::from(format!("{}{}", &self.path, &self.code_name)); + self.backup_file = backup; + match create_backup(&self.code_name, &self.backup_file) { + Ok(_) => info!("Backup file for {} was created ({})", &self.code_name, &self.backup_file), + Err(er) => warn!("{}. Ignoring ...", er), + } Ok(self) } pub fn add_event(&mut self, file_controller: FilesController) { @@ -97,6 +104,9 @@ pub mod v2 { pub fn get_code_name(&self) -> Arc { self.code_name.clone() } + pub fn get_backup_file(&self) -> String { + self.backup_file.to_string() + } } #[async_trait] impl ProcessUnit for FilesController { @@ -120,9 +130,24 @@ pub mod v2 { a || mask.mask == EventMask::DELETE_SELF, b || mask.mask == EventMask::MODIFY, ) - }); - if let (recreate_watcher, true) = (need_to_recreate, was_modifired) { - warn!("File {} ({}) was changed", self.name, &self.path); + } + ); + if self.backup_file.is_empty() { + + } else { + + } + if let (mut recreate_watcher, true) = (need_to_recreate, was_modifired) { + if self.backup_file.is_empty() { + warn!("File {} ({}) was changed", self.name, &self.path); + self.trigger_on(Some(FileTriggerType::OnChange)).await; + } else { + recreate_watcher = true; + match restore_file(&self.code_name, &self.backup_file).await { + Ok(_) => info!("File {} was successfully restored", &self.code_name), + Err(er) => error!("Cannot restore file {} : {}", &self.code_name, er), + } + } if recreate_watcher { self.watcher = match create_watcher(&self.name, &self.path) { Ok(notifier) => Some(notifier), @@ -135,21 +160,42 @@ pub mod v2 { } } } - self.trigger_on(Some(FileTriggerType::OnChange)).await; - return; } } } - None => { /* DEAD END */ } + None => return, } } else { if let FileState::Ok = self.state { - warn!( + if self.backup_file.is_empty() { + warn!( "File {} ({}) was not found in determined scope", self.name, &self.path - ); - self.state = FileState::NotFound; - self.trigger_on(Some(FileTriggerType::OnDelete)).await; + ); + self.state = FileState::NotFound; + self.trigger_on(Some(FileTriggerType::OnDelete)).await; + } else { + warn!( + "File {} ({}) was not found in determined scope. Restoring from backup-file ...", + self.name, &self.path + ); + match restore_file(&self.code_name, &self.backup_file).await { + Err(er) => error!("Cannot restore file {} : {}", &self.code_name, er), + Ok(_) => { + info!("File {} was successfully restored", &self.code_name); + self.watcher = match create_watcher(&self.name, &self.path) { + Ok(notifier) => Some(notifier), + Err(er) => { + error!( + "Failed to recreate watcher for {} ({}) : {}", + self.name, &self.path, er + ); + None + } + } + }, + } + } } return; } @@ -158,6 +204,18 @@ pub mod v2 { } } +pub fn create_backup(target: &str, backup: &str) -> anyhow::Result { + return if !backup.is_empty() { + Ok(std::fs::copy(target, backup)?) + } else { + Err(anyhow::Error::msg(format!("No need to create backup-file for {}", target))) + } +} + +pub async fn restore_file(target: &str, backup: &str) -> anyhow::Result { + Ok(tokio::fs::copy(backup, target).await?) +} + /// # Fn `create_watcher` /// ## for creating watcher on file's delete | update events /// diff --git a/noxis-rs/src/utils/metrics.rs b/noxis-rs/src/utils/metrics.rs index c066dd5..9147938 100644 --- a/noxis-rs/src/utils/metrics.rs +++ b/noxis-rs/src/utils/metrics.rs @@ -5,7 +5,6 @@ use crate::{ options::structs::ProcessState, utils::metrics::processes::{ProcessesGeneral, ProcessesQuery}, }; -use chrono::Datelike; use log::warn; use noxis_cli::metrics_models::MetricsMode; use std::{any::Any, collections::BTreeMap, sync::Arc}; @@ -327,6 +326,7 @@ pub mod processes { pub name: String, pub path: String, pub status: FileState, + pub backup_file : String, pub triggers: FileTriggers, } #[derive(Debug, serde::Serialize)]