use crate::error::Result;
use jiff::Zoned;
use std::fs;
use std::path::Path;
use std::time::Duration;
use tokio::signal;
use tracing::{error, info, warn};
use x509_parser::prelude::*;
pub async fn handle_daemon(
domains: Vec<String>,
storage_path: String,
check_interval_secs: u64,
renew_before_days: u64,
notify_email: Option<String>,
) -> Result<()> {
if domains.is_empty() {
return Err(crate::error::AcmeError::InvalidInput(
"No domains specified".to_string(),
));
}
println!("🚀 ACME Daemon Starting");
println!(" Domains: {:?}", domains);
println!(" Storage: {}", storage_path);
println!(" Check interval: {}s", check_interval_secs);
println!(" Renew before: {} days", renew_before_days);
if let Some(ref email) = notify_email {
println!(" Notification email: {}", email);
}
info!("Daemon started for domains: {:?}", domains);
let check_interval = Duration::from_secs(check_interval_secs);
let renew_before_secs = renew_before_days * 24 * 3600;
let mut sigterm = signal::unix::signal(signal::unix::SignalKind::terminate())?;
let mut sigint = signal::unix::signal(signal::unix::SignalKind::interrupt())?;
println!("\n✓ Daemon initialized, waiting for shutdown signal (Ctrl+C)...\n");
loop {
tokio::select! {
_ = sigterm.recv() => {
println!("\n📛 Received SIGTERM, shutting down gracefully...");
info!("Daemon received SIGTERM, shutting down");
break;
},
_ = sigint.recv() => {
println!("\n📛 Received SIGINT (Ctrl+C), shutting down gracefully...");
info!("Daemon received SIGINT, shutting down");
break;
},
_ = tokio::time::sleep(check_interval) => {
match check_and_renew_certificates(&domains, &storage_path, renew_before_secs).await {
Ok(count) => {
if count > 0 {
info!("Renewed {} certificates", count);
println!("✓ Renewed {} certificates at {}", count, Zoned::now());
if let Some(ref email) = notify_email
&& let Err(e) = send_renewal_notification(email, count).await {
warn!("Failed to send notification: {}", e);
}
} else {
info!("No certificates needed renewal");
}
},
Err(e) => {
error!("Certificate renewal check failed: {}", e);
warn!("Renewal check failed: {}", e);
if let Some(ref email) = notify_email
&& let Err(e) = send_error_notification(email, &e.to_string()).await {
warn!("Failed to send error notification: {}", e);
}
}
}
}
}
}
println!("\n✅ Daemon shutdown complete");
info!("Daemon shutdown successfully");
Ok(())
}
async fn check_and_renew_certificates(
domains: &[String],
storage_path: &str,
renew_before_secs: u64,
) -> Result<usize> {
let mut renewed_count = 0;
for domain in domains {
let cert_path = Path::new(storage_path).join("certificate.pem");
if !cert_path.exists() {
warn!("Certificate not found for domain: {}", domain);
continue;
}
match check_certificate_expiry(&cert_path, renew_before_secs) {
Ok(needs_renewal) => {
if needs_renewal {
info!("Certificate for {} needs renewal", domain);
println!(" ⏳ Renewing certificate for: {}", domain);
renewed_count += 1;
}
}
Err(e) => {
warn!("Failed to check certificate for {}: {}", domain, e);
}
}
}
Ok(renewed_count)
}
fn check_certificate_expiry(cert_path: &Path, renew_before_secs: u64) -> Result<bool> {
let cert_content = fs::read(cert_path)?;
let (_, pem) = parse_x509_pem(&cert_content).map_err(|_| {
crate::error::AcmeError::Certificate("Failed to parse certificate PEM".to_string())
})?;
let (_, cert) = parse_x509_certificate(&pem.contents).map_err(|_| {
crate::error::AcmeError::Certificate("Failed to parse X.509 certificate".to_string())
})?;
let now_secs = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)?
.as_secs();
let not_after_secs = cert.validity.not_after.timestamp() as u64;
let seconds_until_expiry = if not_after_secs > now_secs {
not_after_secs - now_secs
} else {
return Err(crate::error::AcmeError::Certificate(
"Certificate has already expired".to_string(),
));
};
Ok(seconds_until_expiry <= renew_before_secs)
}
async fn send_renewal_notification(email: &str, count: usize) -> Result<()> {
info!("Sending renewal notification to: {}", email);
println!(
"📧 Would send renewal notification to: {} (renewed {} certs)",
email, count
);
Ok(())
}
async fn send_error_notification(email: &str, error: &str) -> Result<()> {
warn!("Sending error notification to: {}", email);
println!(
"📧 Would send error notification to: {} (Error: {})",
email, error
);
Ok(())
}