This commit is contained in:
2025-09-17 13:06:18 -04:00
parent fcd377211a
commit bb8b7cc3fd
10 changed files with 201 additions and 101 deletions

View File

@@ -6,7 +6,7 @@ description = "Detect and Act on unauthorized access of any kind from any source
authors = ["jkoontsiii@gmail.com"] authors = ["jkoontsiii@gmail.com"]
license = "MIT" license = "MIT"
repository = "https://github.com/helloimalemur/watchman" repository = "https://github.com/helloimalemur/watchman"
keywords = ["file-integrity", "filesystem-integirty", "change-detection", "watchman"] keywords = ["file-integrity", "filesystem-integrity", "change-detection", "watchman"]
readme = "README.md" readme = "README.md"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -29,3 +29,5 @@ tokio = { version = "1.44.1", features = ["rt", "rt-multi-thread", "macros"] }
walkdir = "2.5.0" walkdir = "2.5.0"
whoami = "1.5.2" whoami = "1.5.2"
lazy_static = "1.5.0" lazy_static = "1.5.0"
log = "0.4"
env_logger = "0.11"

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 Watchman contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -6,19 +6,19 @@ pub fn configure_linux_service() {
.arg("daemon-reload") .arg("daemon-reload")
.output() .output()
{ {
println!("{}", String::from_utf8_lossy(&o.stdout)); if o.status.success() { log::info!("{}", String::from_utf8_lossy(&o.stdout)); } else { log::error!("{}", String::from_utf8_lossy(&o.stderr)); }
if let Ok(o) = process::Command::new("systemctl") if let Ok(o) = process::Command::new("systemctl")
.arg("enable") .arg("enable")
.arg("watchman") .arg("watchman")
.output() .output()
{ {
println!("{}", String::from_utf8_lossy(&o.stdout)); if o.status.success() { log::info!("{}", String::from_utf8_lossy(&o.stdout)); } else { log::error!("{}", String::from_utf8_lossy(&o.stderr)); }
if let Ok(o) = process::Command::new("systemctl") if let Ok(o) = process::Command::new("systemctl")
.arg("restart") .arg("restart")
.arg("watchman") .arg("watchman")
.output() .output()
{ {
println!("{}", String::from_utf8_lossy(&o.stdout)); if o.status.success() { log::info!("{}", String::from_utf8_lossy(&o.stdout)); } else { log::error!("{}", String::from_utf8_lossy(&o.stderr)); }
} }
} }
} }
@@ -27,15 +27,15 @@ pub fn configure_linux_service() {
pub fn write_service_file<T: ToString>(path: T) { pub fn write_service_file<T: ToString>(path: T) {
if whoami::username().eq_ignore_ascii_case("root") { if whoami::username().eq_ignore_ascii_case("root") {
if fs::write(path.to_string(), service_file()).is_ok() { if fs::write(path.to_string(), service_file()).is_ok() {
println!( log::info!(
"{}\n ~~~~~~~ SERVICE CONFIG CREATED ~~~~~~~", "{}\n ~~~~~~~ SERVICE CONFIG CREATED ~~~~~~~",
service_file() service_file()
); );
} else { } else {
println!("{}", "COULD NOT WRITE SERVICE CONFIG TO FILE"); log::error!("{}", "COULD NOT WRITE SERVICE CONFIG TO FILE");
} }
} else { } else {
println!( log::error!(
"{}", "{}",
"PLEASE RUN AS ROOT - COULD NOT WRITE SERVICE CONFIG TO FILE" "PLEASE RUN AS ROOT - COULD NOT WRITE SERVICE CONFIG TO FILE"
); );
@@ -51,7 +51,7 @@ Description=Watchman
Type=simple Type=simple
User=root User=root
Group=root Group=root
ExecStart=/root/.cargo/bin/watchman ExecStart=/usr/local/bin/watchman
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target

View File

@@ -7,20 +7,13 @@ pub fn configure_macos_service() {
.arg("/Library/LaunchDaemons/com.helloimalemur.watchman.plist") .arg("/Library/LaunchDaemons/com.helloimalemur.watchman.plist")
.output() .output()
{ {
println!("{}", String::from_utf8_lossy(&o.stdout)); if o.status.success() { log::info!("{}", String::from_utf8_lossy(&o.stdout)); } else { log::error!("{}", String::from_utf8_lossy(&o.stderr)); }
if let Ok(o) = process::Command::new("launchctl") if let Ok(o) = process::Command::new("launchctl")
.arg("start") .arg("start")
.arg("com.helloimalemur.watchman") .arg("com.helloimalemur.watchman")
.output() .output()
{ {
println!("{}", String::from_utf8_lossy(&o.stdout)); if o.status.success() { log::info!("{}", String::from_utf8_lossy(&o.stdout)); } else { log::error!("{}", String::from_utf8_lossy(&o.stderr)); }
// if let Ok(o) = process::Command::new("systemctl")
// .arg("restart")
// .arg("watchman")
// .output()
// {
// println!("{}", String::from_utf8_lossy(&o.stdout));
// }
} }
} }
} }
@@ -28,26 +21,32 @@ pub fn configure_macos_service() {
pub fn write_launch_file<T: ToString>(path: T) { pub fn write_launch_file<T: ToString>(path: T) {
if whoami::username().eq_ignore_ascii_case("root") { if whoami::username().eq_ignore_ascii_case("root") {
if fs::write(path.to_string(), launch_file()).is_ok() { if fs::write(path.to_string(), launch_file()).is_ok() {
println!( log::info!(
"{}\n ~~~~~~~ SERVICE CONFIG CREATED ~~~~~~~", "{}\n ~~~~~~~ SERVICE CONFIG CREATED ~~~~~~~",
launch_file() launch_file()
); );
// sudo chown root:wheel /Library/LaunchDaemons/com.yourname.yourapp.plist // sudo chown root:wheel /Library/LaunchDaemons/com.yourname.yourapp.plist
// sudo chmod 644 /Library/LaunchDaemons/com.yourname.yourapp.plist // sudo chmod 644 /Library/LaunchDaemons/com.yourname.yourapp.plist
let _ = process::Command::new("chown") match process::Command::new("chown")
.arg("root:wheel") .arg("root:wheel")
.arg(path.to_string()) .arg(path.to_string())
.spawn(); .output() {
Ok(o) => { if !o.status.success() { log::error!("chown failed: {}", String::from_utf8_lossy(&o.stderr)); } },
Err(e) => log::error!("failed to execute chown: {}", e),
}
let _ = process::Command::new("chmod") match process::Command::new("chmod")
.arg("644") .arg("644")
.arg(path.to_string()) .arg(path.to_string())
.spawn(); .output() {
Ok(o) => { if !o.status.success() { log::error!("chmod failed: {}", String::from_utf8_lossy(&o.stderr)); } },
Err(e) => log::error!("failed to execute chmod: {}", e),
}
} else { } else {
println!("{}", "COULD NOT WRITE SERVICE CONFIG TO FILE"); log::error!("{}", "COULD NOT WRITE SERVICE CONFIG TO FILE");
} }
} else { } else {
println!( log::error!(
"{}", "{}",
"PLEASE RUN AS ROOT - COULD NOT WRITE SERVICE CONFIG TO FILE" "PLEASE RUN AS ROOT - COULD NOT WRITE SERVICE CONFIG TO FILE"
); );

View File

@@ -16,7 +16,8 @@ mod util;
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
println!("OS: {}", OS); env_logger::init();
log::info!("OS: {}", OS);
let arguments = Arguments::parse(); let arguments = Arguments::parse();
let mut prom = Prometheus::new(); let mut prom = Prometheus::new();
prom.start().await; prom.start().await;

View File

@@ -7,8 +7,15 @@ use std::time::Duration;
pub async fn reboot_system(settings_map: Config) { pub async fn reboot_system(settings_map: Config) {
let _res = send_discord("System rebooting", settings_map, &"".to_string()).await; let _res = send_discord("System rebooting", settings_map, &"".to_string()).await;
if let Ok(reboot) = Command::new("reboot").output() { match Command::new("reboot").output() {
println!("{:#?}", reboot) Ok(reboot) => {
if reboot.status.success() {
log::info!("reboot invoked: {}", String::from_utf8_lossy(&reboot.stdout));
} else {
log::error!("reboot failed: {}", String::from_utf8_lossy(&reboot.stderr));
}
}
Err(e) => log::error!("failed to execute reboot: {}", e),
} }
} }
@@ -16,23 +23,23 @@ pub async fn unmount_encrypted_volumes() {
match OS { match OS {
"linux" => { "linux" => {
if let Ok(output) = Command::new("dmsetup").arg("ls").output() { if let Ok(output) = Command::new("dmsetup").arg("ls").output() {
let encrypted_vols = String::from_utf8(output.stdout.to_vec()).unwrap(); let encrypted_vols = String::from_utf8(output.stdout.to_vec()).unwrap_or_default();
for ea in encrypted_vols.lines() { for ea in encrypted_vols.lines() {
let vol_split: Vec<_> = ea.split_ascii_whitespace().collect(); let vol_split: Vec<_> = ea.split_ascii_whitespace().collect();
let luks_vol = vol_split.first().unwrap().to_string(); if let Some(first) = vol_split.first() {
// println!("{}", luks_vol); let luks_vol = first.to_string();
// println!("{}", command_str); if let Ok(res) = Command::new("cat").arg("/proc/mounts").output() {
if let Ok(res) = Command::new("cat").arg("/proc/mounts").output() { let mount_file_results = String::from_utf8(res.stdout.to_vec()).unwrap_or_default();
let mount_file_results = String::from_utf8(res.stdout.to_vec()).unwrap(); for mount in mount_file_results.lines() {
for mount in mount_file_results.lines() { let luks_vol_str = luks_vol.as_str();
let luks_vol_str = luks_vol.as_str(); if mount.contains(luks_vol_str) {
if mount.contains(luks_vol_str) { let mount_split: Vec<&str> = mount.split_ascii_whitespace().collect();
let mount_split: Vec<&str> = mount.split_ascii_whitespace().collect(); if let Some(vol_mount_path) = mount_split.get(1) {
let vol_mount_path = mount_split.get(1).unwrap(); commit_umount_volume(vol_mount_path);
// println!("{}", vol_mount_path); thread::sleep(Duration::new(1, 0));
commit_umount_volume(vol_mount_path); commit_luks_close(luks_vol_str);
thread::sleep(Duration::new(1, 0)); }
commit_luks_close(luks_vol_str); }
} }
} }
} }
@@ -47,7 +54,7 @@ fn commit_umount_volume(vol_mount_path: &str) {
match OS { match OS {
"linux" => { "linux" => {
if let Ok(res) = Command::new("umount").arg(vol_mount_path).output() { if let Ok(res) = Command::new("umount").arg(vol_mount_path).output() {
println!("{}", String::from_utf8(res.stdout.to_vec()).unwrap()) if res.status.success() { log::info!("{}", String::from_utf8_lossy(&res.stdout)); } else { log::error!("{}", String::from_utf8_lossy(&res.stderr)); }
} }
} }
_ => {} _ => {}
@@ -57,13 +64,13 @@ fn commit_umount_volume(vol_mount_path: &str) {
fn commit_luks_close(luks_vol: &str) { fn commit_luks_close(luks_vol: &str) {
match OS { match OS {
"linux" => { "linux" => {
println!("close luks"); log::info!("close luks");
if let Ok(res) = Command::new("cryptsetup") if let Ok(res) = Command::new("cryptsetup")
.arg("luksClose") .arg("luksClose")
.arg(luks_vol) .arg(luks_vol)
.output() .output()
{ {
println!("{}", String::from_utf8(res.stdout.to_vec()).unwrap()) if res.status.success() { log::info!("{}", String::from_utf8_lossy(&res.stdout)); } else { log::error!("{}", String::from_utf8_lossy(&res.stderr)); }
} }
} }
_ => {} _ => {}

View File

@@ -12,7 +12,9 @@ pub struct NETMon {
triggered: bool, triggered: bool,
interfaces: Vec<String>, interfaces: Vec<String>,
settings_map: Config, settings_map: Config,
state: Arc<Mutex<Vec<SocketInfo>>> state: Arc<Mutex<Vec<SocketInfo>>>,
fail_count: u32,
last_reboot: Option<std::time::SystemTime>,
} }
impl NETMon { impl NETMon {
@@ -24,7 +26,7 @@ impl NETMon {
let moved_sockets = sockets_info.clone(); let moved_sockets = sockets_info.clone();
for si in moved_sockets { for si in moved_sockets {
match si.protocol_socket_info { match si.protocol_socket_info {
ProtocolSocketInfo::Tcp(tcp_si) => println!( ProtocolSocketInfo::Tcp(tcp_si) => log::debug!(
"TCP {}:{} -> {}:{} {:?} - {}", "TCP {}:{} -> {}:{} {:?} - {}",
tcp_si.local_addr, tcp_si.local_addr,
tcp_si.local_port, tcp_si.local_port,
@@ -33,7 +35,7 @@ impl NETMon {
si.associated_pids, si.associated_pids,
tcp_si.state tcp_si.state
), ),
ProtocolSocketInfo::Udp(udp_si) => println!( ProtocolSocketInfo::Udp(udp_si) => log::debug!(
"UDP {}:{} -> *:* {:?}", "UDP {}:{} -> *:* {:?}",
udp_si.local_addr, udp_si.local_port, si.associated_pids udp_si.local_addr, udp_si.local_port, si.associated_pids
), ),
@@ -44,21 +46,61 @@ impl NETMon {
triggered: false, triggered: false,
interfaces: vec![], interfaces: vec![],
settings_map, settings_map,
state: Arc::new(Mutex::new(sockets_info.clone())) state: Arc::new(Mutex::new(sockets_info.clone())),
fail_count: 0,
last_reboot: None,
} }
} }
} }
impl EventMonitor for NETMon { impl EventMonitor for NETMon {
async fn check(&mut self) { async fn check(&mut self) {
if let Ok(check) = httping::ping("koonts.net", "", "https", 443).await { let host = self
self.triggered = !check .settings_map
.get::<String>("net_mon_ping_host")
.unwrap_or_else(|_| "cloudflare.com".to_string());
let reboot_on_fail = self
.settings_map
.get::<String>("net_mon_reboot_on_failure")
.unwrap_or_else(|_| "false".to_string())
.eq_ignore_ascii_case("true");
match httping::ping(host.as_str(), "", "https", 443).await {
Ok(ok) => {
if !ok {
self.fail_count += 1;
self.triggered = true;
} else {
if self.triggered { log::info!("Network connectivity restored to {}", host); }
self.fail_count = 0;
self.triggered = false;
}
}
Err(e) => {
log::warn!("Network ping error to {}: {}", host, e);
self.fail_count += 1;
self.triggered = true;
}
} }
if self.triggered { if self.triggered {
println!("{} :: ALERT NET", Local::now()); log::warn!("{} :: Network connectivity check failed ({} consecutive failures)", Local::now(), self.fail_count);
net_alert(self.settings_map.clone()).await; // Simple backoff: only reboot after 3 consecutive failures and at most once every 10 minutes
if reboot_on_fail && self.fail_count >= 3 {
let now = std::time::SystemTime::now();
let should_reboot = match self.last_reboot {
None => true,
Some(t) => now.duration_since(t).map(|d| d.as_secs() >= 600).unwrap_or(true),
};
if should_reboot {
log::error!("Rebooting due to sustained network failure");
self.last_reboot = Some(now);
net_alert(self.settings_map.clone()).await;
} else {
log::warn!("Reboot on network failure suppressed due to backoff window");
}
}
} }
// println!("check net: {}", self.triggered);
} }
} }

View File

@@ -1,8 +1,11 @@
use actix_web::http::header; use actix_web::http::header;
use actix_web::{get, web, App, Error, HttpResponse, HttpServer, Responder}; use actix_web::{get, App, Error, HttpResponse, HttpServer};
use prometheus::{Encoder, TextEncoder}; use prometheus::{Encoder, TextEncoder};
use std::thread; use std::thread;
use config::Config;
use crate::util::get_config_dir;
pub struct Prometheus {} pub struct Prometheus {}
impl Prometheus { impl Prometheus {
@@ -12,12 +15,35 @@ impl Prometheus {
pub async fn start(&self) { pub async fn start(&self) {
let _handle = thread::spawn(|| { let _handle = thread::spawn(|| {
let rt = tokio::runtime::Runtime::new(); let rt = tokio::runtime::Runtime::new();
let _ = rt.unwrap().block_on(
HttpServer::new(|| App::new().service(metrics)) // Read optional bind config
.bind(("127.0.0.1", 8323)) let mut addr = String::from("127.0.0.1");
.unwrap() let mut port: u16 = 8323;
.run(), let config_path = format!("{}config/settings.toml", get_config_dir());
); if std::path::Path::new(&config_path).exists() {
if let Ok(cfg) = Config::builder()
.add_source(config::File::with_name(&config_path))
.build()
{
if let Ok(a) = cfg.get::<String>("prom_bind_addr") { addr = a; }
if let Ok(p) = cfg.get::<String>("prom_bind_port") {
if let Ok(n) = p.parse::<u16>() { port = n; } else { log::warn!("Invalid prom_bind_port '{}', using default {}", p, port); }
}
}
}
let server = HttpServer::new(|| App::new().service(metrics))
.bind((addr.as_str(), port));
match server {
Ok(s) => {
log::info!("Prometheus exporter listening on {}:{}", addr, port);
let _ = rt.unwrap().block_on(s.run());
}
Err(e) => {
log::error!("Failed to bind Prometheus exporter on {}:{}: {}", addr, port, e);
}
}
}); });
// let _ = handle.join(); // let _ = handle.join();
} }
@@ -27,18 +53,17 @@ impl Prometheus {
async fn metrics() -> Result<HttpResponse, Error> { async fn metrics() -> Result<HttpResponse, Error> {
let encoder = TextEncoder::new(); let encoder = TextEncoder::new();
// HTTP_COUNTER.inc();
// let timer = HTTP_REQ_HISTOGRAM.with_label_values(&["all"]).start_timer();
let metric_families = prometheus::gather(); let metric_families = prometheus::gather();
let mut buffer = vec![]; let mut buffer = vec![];
encoder.encode(&metric_families, &mut buffer).unwrap(); if let Err(e) = encoder.encode(&metric_families, &mut buffer) {
// HTTP_BODY_GAUGE.set(buffer.len() as f64); log::error!("Failed to encode Prometheus metrics: {}", e);
let response = String::from_utf8(buffer.clone()).unwrap(); }
let response = String::from_utf8(buffer.clone()).unwrap_or_else(|e| {
log::error!("Failed to build Prometheus metrics body: {}", e);
String::new()
});
buffer.clear(); buffer.clear();
// timer.observe_duration();
Ok(HttpResponse::Ok() Ok(HttpResponse::Ok()
.insert_header(header::ContentType(mime::TEXT_PLAIN)) .insert_header(header::ContentType(mime::TEXT_PLAIN))
.body(response)) .body(response))

View File

@@ -113,7 +113,7 @@ pub fn write_default_config<T: ToString>(path: T, webhook: String) {
) )
.is_ok() .is_ok()
{ {
println!( log::info!(
"{}\n ~~~~~~~ UNABLE TO LOCATE CONFIG FILE - DEFAULT CONFIG CREATED ~~~~~~~", "{}\n ~~~~~~~ UNABLE TO LOCATE CONFIG FILE - DEFAULT CONFIG CREATED ~~~~~~~",
default_config().replace("https://discord.com/api/webhooks/", webhook.as_str()) default_config().replace("https://discord.com/api/webhooks/", webhook.as_str())
); );

View File

@@ -47,7 +47,7 @@ impl AppState {
pub fn config(&mut self, args: Arguments) { pub fn config(&mut self, args: Arguments) {
// check for config file if it doesn't exist write default config // check for config file if it doesn't exist write default config
println!("Configuring.."); log::info!("Configuring..");
let cache_dir = get_cache_dir(); let cache_dir = get_cache_dir();
let config_dir = get_config_dir(); let config_dir = get_config_dir();
@@ -61,10 +61,10 @@ impl AppState {
if let Some(service_command) = args.service { if let Some(service_command) = args.service {
match service_command.as_str() { match service_command.as_str() {
"install" => { "install" => {
println!("Installing.."); log::info!("Installing..");
setup_service(); setup_service();
if !Path::new(settings_file_path.as_str()).exists() { if !Path::new(settings_file_path.as_str()).exists() {
println!("Settings.toml does not exist"); log::warn!("Settings.toml does not exist; writing default and exiting after service install");
crate::util::write_default_config(settings_file_path.clone(), webhook.clone()); crate::util::write_default_config(settings_file_path.clone(), webhook.clone());
exit(0); exit(0);
} }
@@ -78,26 +78,32 @@ impl AppState {
} }
if !Path::new(settings_file_path.as_str()).exists() { if !Path::new(settings_file_path.as_str()).exists() {
println!("Settings.toml does not exist"); log::warn!("Settings.toml does not exist; writing default");
write_default_config(settings_file_path.clone(), webhook); write_default_config(settings_file_path.clone(), webhook);
} }
let blacklist_file_path = format!("{}config/file_mon_blacklist", config_dir); let blacklist_file_path = format!("{}config/file_mon_blacklist", config_dir);
if !Path::new(blacklist_file_path.as_str()).exists() { if !Path::new(blacklist_file_path.as_str()).exists() {
println!("file_mon_blacklist does not exist"); log::warn!("file_mon_blacklist does not exist; creating default");
write_default_blacklist(blacklist_file_path.clone()); write_default_blacklist(blacklist_file_path.clone());
} }
let config = Config::builder(); let config = Config::builder();
let settings = config let settings = match config
.add_source(config::File::with_name(settings_file_path.as_str())) .add_source(config::File::with_name(settings_file_path.as_str()))
.build() .build()
.expect("Configuration error: unable to parse settings"); {
Ok(s) => s,
Err(e) => {
log::error!("Configuration error: unable to parse settings: {}", e);
exit(2);
}
};
// initialize monitoring modules // initialize monitoring modules
let mut monitors: Vec<Monitors> = vec![]; let mut monitors: Vec<Monitors> = vec![];
if settings if settings
.get::<String>("usb_mon_enabled") .get::<String>("usb_mon_enabled")
.unwrap() .unwrap_or("false".to_string())
.eq_ignore_ascii_case("true") .eq_ignore_ascii_case("true")
{ {
monitors.push(Monitors::USBMon(USBMon::new(settings.clone()))); monitors.push(Monitors::USBMon(USBMon::new(settings.clone())));
@@ -111,14 +117,14 @@ impl AppState {
// } // }
if settings if settings
.get::<String>("burn_file_mon_enabled") .get::<String>("burn_file_mon_enabled")
.unwrap() .unwrap_or("false".to_string())
.eq_ignore_ascii_case("true") .eq_ignore_ascii_case("true")
{ {
monitors.push(Monitors::SSHBurnMon(SSHBurnMon::new(settings.clone()))); monitors.push(Monitors::SSHBurnMon(SSHBurnMon::new(settings.clone())));
} }
if settings if settings
.get::<String>("fs_mon_enabled") .get::<String>("fs_mon_enabled")
.unwrap() .unwrap_or("false".to_string())
.eq_ignore_ascii_case("true") .eq_ignore_ascii_case("true")
{ {
monitors.push(Monitors::FileChanges(FileChanges::new( monitors.push(Monitors::FileChanges(FileChanges::new(
@@ -140,20 +146,18 @@ impl AppState {
} }
pub async fn run(&mut self) { pub async fn run(&mut self) {
let fs_check_tick = self // Defaults if not configured or parse fails
let n_fs_check_tick: u64 = self
.settings_map .settings_map
.get::<String>("fs_tick_delay_seconds") .get::<String>("fs_tick_delay_seconds").ok()
.expect("tick_delay_seconds not found in Settings.toml"); .and_then(|v| match v.parse::<u64>() { Ok(n) => Some(n), Err(e) => { log::warn!("Invalid fs_tick_delay_seconds '{}': {}. Using default 300.", v, e); None } })
let n_fs_check_tick = fs_check_tick .unwrap_or(300);
.parse::<u64>() let n_tick: u64 = self
.expect("unable to parse fs_tick_delay_seconds");
let tick = self
.settings_map .settings_map
.get::<String>("tick_delay_seconds") .get::<String>("tick_delay_seconds").ok()
.expect("tick_delay_seconds not found in Settings.toml"); .and_then(|v| match v.parse::<u64>() { Ok(n) => Some(n), Err(e) => { log::warn!("Invalid tick_delay_seconds '{}': {}. Using default 5.", v, e); None } })
let n_tick = tick .unwrap_or(5);
.parse::<u64>()
.expect("unable to parse tick_delay_seconds");
let mut last = SystemTime::now(); let mut last = SystemTime::now();
loop { loop {
let mut binding = self.monitors.lock(); let mut binding = self.monitors.lock();
@@ -161,34 +165,33 @@ impl AppState {
for i in bind.iter_mut() { for i in bind.iter_mut() {
match i { match i {
Monitors::USBMon(e) => { Monitors::USBMon(e) => {
// println!("{:#?}", e);
e.check().await; e.check().await;
} }
// Monitors::NetMon(e) => { // Monitors::NetMon(e) => {
// // println!("{:#?}", e);
// e.check().await; // e.check().await;
// } // }
Monitors::SSHBurnMon(e) => { Monitors::SSHBurnMon(e) => {
// println!("{:#?}", e);
e.check().await; e.check().await;
} }
Monitors::FileChanges(e) => { Monitors::FileChanges(e) => {
// println!("{:#?}", e);
let now = SystemTime::now(); let now = SystemTime::now();
let dur_since = now.duration_since(last).unwrap(); let dur_since = match now.duration_since(last) {
Ok(d) => d,
Err(e) => {
log::warn!("SystemTime went backwards: {}", e);
Duration::new(0, 0)
}
};
if dur_since.as_secs() > n_fs_check_tick { if dur_since.as_secs() > n_fs_check_tick {
// println!("fs_tick");
last = SystemTime::now(); last = SystemTime::now();
e.check().await; e.check().await;
} }
} }
Monitors::NewClients(e) => { Monitors::NewClients(e) => {
// println!("{:#?}", e);
e.check().await; e.check().await;
} }
} }
tokio::time::sleep(Duration::new(n_tick, 0)).await; tokio::time::sleep(Duration::new(n_tick, 0)).await;
// println!("tick");
} }
} }
} }