bws_web_server/monitoring/
certificates.rs

1use log::{error, info, warn};
2use notify::{Event, EventKind, RecommendedWatcher, RecursiveMode, Watcher};
3use std::path::Path;
4use tokio::sync::mpsc;
5
6pub struct CertificateWatcher {
7    cert_dir: String,
8    domains: Vec<String>,
9    _watcher: Option<RecommendedWatcher>, // Keep watcher alive
10}
11
12impl CertificateWatcher {
13    pub fn new(cert_dir: String, domains: Vec<String>) -> Self {
14        Self {
15            cert_dir,
16            domains,
17            _watcher: None,
18        }
19    }
20
21    pub fn start_watching(&mut self) -> Result<(), Box<dyn std::error::Error>> {
22        let (tx, mut rx) = mpsc::unbounded_channel();
23
24        let mut watcher = RecommendedWatcher::new(
25            move |res: Result<Event, notify::Error>| match res {
26                Ok(event) => {
27                    if let Err(e) = tx.send(event) {
28                        error!("Failed to send file watcher event: {e}");
29                    }
30                }
31                Err(e) => {
32                    error!("File watcher error: {e}");
33                }
34            },
35            notify::Config::default(),
36        )?;
37
38        // Watch the certificate directory
39        watcher.watch(Path::new(&self.cert_dir), RecursiveMode::Recursive)?;
40        info!(
41            "Certificate watcher started for directory: {}",
42            self.cert_dir
43        );
44
45        // Store watcher to keep it alive
46        self._watcher = Some(watcher);
47
48        let domains = self.domains.clone();
49        let cert_dir = self.cert_dir.clone();
50
51        tokio::spawn(async move {
52            while let Some(event) = rx.recv().await {
53                match event.kind {
54                    EventKind::Create(_) | EventKind::Modify(_) => {
55                        for path in event.paths {
56                            if let Some(filename) = path.file_name() {
57                                if let Some(filename_str) = filename.to_str() {
58                                    // Check if this is a certificate file for one of our domains
59                                    for domain in &domains {
60                                        let cert_file = format!("{domain}.crt");
61                                        let key_file = format!("{domain}.key");
62
63                                        if filename_str == cert_file || filename_str == key_file {
64                                            info!("Certificate file changed: {path:?}");
65
66                                            // Check if both cert and key exist now
67                                            let cert_path = Path::new(&cert_dir).join(&cert_file);
68                                            let key_path = Path::new(&cert_dir).join(&key_file);
69
70                                            if cert_path.exists() && key_path.exists() {
71                                                info!("Both certificate and key files exist for domain: {domain}");
72                                                warn!("🔄 HTTPS UPGRADE AVAILABLE!");
73                                                warn!(
74                                                    "To enable HTTPS for {domain}, restart the server:"
75                                                );
76                                                warn!("   pkill -f bws");
77                                                warn!("   ./target/x86_64-unknown-linux-musl/release/bws --config config-auto-acme.toml");
78                                            }
79                                        }
80                                    }
81                                }
82                            }
83                        }
84                    }
85                    _ => {}
86                }
87            }
88            info!("Certificate watcher task terminated");
89        });
90
91        Ok(())
92    }
93}