use crate::{AcmeClient, AcmeConfig, AcmeError, Result};
use std::sync::Arc;
use tokio::sync::RwLock;
use tokio::time::{interval, Duration};
use tracing::{error, info, warn};
pub struct RenewalManager {
client: Arc<AcmeClient>,
config: Arc<AcmeConfig>,
running: Arc<RwLock<bool>>,
}
impl RenewalManager {
pub fn new(client: AcmeClient, config: AcmeConfig) -> Self {
Self {
client: Arc::new(client),
config: Arc::new(config),
running: Arc::new(RwLock::new(false)),
}
}
pub async fn start(&self) -> Result<()> {
let mut running = self.running.write().await;
if *running {
return Err(AcmeError::Other(
"Renewal manager already running".to_string(),
));
}
*running = true;
drop(running);
info!("Starting automatic certificate renewal");
let client = self.client.clone();
let config = self.config.clone();
let running = self.running.clone();
tokio::spawn(async move {
Self::renewal_loop(client, config, running).await;
});
Ok(())
}
pub async fn stop(&self) {
let mut running = self.running.write().await;
*running = false;
info!("Stopping automatic certificate renewal");
}
pub async fn check_renewal(&self) -> Result<bool> {
info!("Checking if certificate renewal is needed");
let cert_result =
crate::cert::Certificate::load(&self.config.cert_path, &self.config.key_path).await;
match cert_result {
Ok(cert) => {
let should_renew = cert.should_renew(self.config.renewal_days_before_expiry);
let days_left = cert.days_until_expiry();
if should_renew {
warn!("Certificate expires in {} days, renewal needed", days_left);
} else {
info!("Certificate valid for {} days", days_left);
}
Ok(should_renew)
}
Err(e) => {
warn!("Failed to load certificate: {}, will request new one", e);
Ok(true)
}
}
}
pub async fn renew(&self) -> Result<()> {
info!("Renewing certificate");
let cert = self.client.request_certificate().await?;
cert.save(&self.config.cert_path, &self.config.key_path)
.await?;
info!("Certificate renewed successfully");
self.reload_certificate().await?;
Ok(())
}
async fn reload_certificate(&self) -> Result<()> {
info!("Reloading certificate");
Ok(())
}
async fn renewal_loop(
client: Arc<AcmeClient>,
config: Arc<AcmeConfig>,
running: Arc<RwLock<bool>>,
) {
let mut check_interval = interval(Duration::from_secs(config.renewal_check_interval));
loop {
check_interval.tick().await;
let is_running = *running.read().await;
if !is_running {
info!("Renewal loop stopping");
break;
}
let cert_result =
crate::cert::Certificate::load(&config.cert_path, &config.key_path).await;
match cert_result {
Ok(cert) => {
if cert.should_renew(config.renewal_days_before_expiry) {
info!("Certificate renewal needed");
match client.request_certificate().await {
Ok(new_cert) => {
if let Err(e) =
new_cert.save(&config.cert_path, &config.key_path).await
{
error!("Failed to save renewed certificate: {}", e);
} else {
info!("Certificate renewed successfully");
}
}
Err(e) => {
error!("Failed to renew certificate: {}", e);
}
}
}
}
Err(e) => {
warn!("Failed to load certificate: {}", e);
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn create_test_config() -> AcmeConfig {
AcmeConfig::new(
"test@example.com".to_string(),
vec!["example.com".to_string()],
)
.staging()
}
#[tokio::test]
async fn test_renewal_manager_creation() {
let config = create_test_config();
let client = AcmeClient::new(config.clone()).unwrap();
let manager = RenewalManager::new(client, config);
let running = *manager.running.read().await;
assert!(!running);
}
#[tokio::test]
async fn test_start_stop() {
let config = create_test_config();
let client = AcmeClient::new(config.clone()).unwrap();
let manager = RenewalManager::new(client, config);
manager.start().await.unwrap();
let running = *manager.running.read().await;
assert!(running);
manager.stop().await;
tokio::time::sleep(Duration::from_millis(100)).await;
let running = *manager.running.read().await;
assert!(!running);
}
#[tokio::test]
async fn test_check_renewal_no_cert() {
use tempfile::tempdir;
let dir = tempdir().unwrap();
let cert_path = dir.path().join("cert.pem");
let key_path = dir.path().join("key.pem");
let config = create_test_config().cert_paths(
cert_path.to_str().unwrap().to_string(),
key_path.to_str().unwrap().to_string(),
);
let client = AcmeClient::new(config.clone()).unwrap();
let manager = RenewalManager::new(client, config);
let should_renew = manager.check_renewal().await.unwrap();
assert!(should_renew); }
}