use log::{debug, error, info}; use crate::{ certs::{load_cert_from_fullchain, AcmeApiEndpoint, CertExt, CertRequester}, config::Config, http::{ChallengeServer, ChallengeManager}, haproxy::HaProxyApi, }; mod certs; mod config; mod http; mod haproxy; #[tokio::main] async fn main() -> Result<(), Box> { env_logger::init_from_env(env_logger::Env::default().filter_or("RUST_LOG", "info")); // Config path evaluation order: // - first cli argument // - LE_CONF environment variable // ./le-conf.toml let config_path = std::env::args() .skip(1) .next() .unwrap_or_else(|| std::env::var("LE_CONF").unwrap_or("./le-conf.toml".to_string())); info!("Loading config file '{}'", config_path); let s_conf = tokio::fs::read_to_string(&config_path) .await .expect(&format!("Failed to load config file: {}", config_path)); let conf: Config = toml::from_str(&s_conf)?; debug!("Config file: {:#?}", &conf); let haproxyapi = conf.clone().haproxy.map(|ha| HaProxyApi::new(&ha.ip, ha.port, &ha.cert_dir)); // Create the http server for serving the challenges let srv = ChallengeServer::new(&conf.http.ip, conf.http.port)?; let mgr = srv.clone_challenge_mgr(); // Start the server info!("Starting http server: {}:{}", &conf.http.ip, conf.http.port); tokio::spawn(async move { srv.start().await.unwrap().0.await; }); check_update_certs(&conf, &mgr, &haproxyapi).await; info!("All done. Shutting down"); Ok(()) } pub async fn check_update_certs(conf: &Config, mgr: &ChallengeManager, haproxyapi: &Option) { // See what certs are requested in the config file for (name, conf) in &conf.certs { // Check if the cert needs to be created / renewed let should_renew = load_cert_from_fullchain(&conf.fullchain_file) .await .map(|cert| { let expires_in_days = cert.expires_in_days(); let dns_names = cert.dns_names(); expires_in_days <= conf.renew_days || dns_names != conf.domains }) .unwrap_or(true); if should_renew { let endpoint = conf .endpoint .as_ref() .map(|ep| match ep.as_ref() { "LetsEncryptProduction" => AcmeApiEndpoint::LetsEncryptProduction, "LetsEncryptStaging" => AcmeApiEndpoint::LetsEncryptStaging, url => AcmeApiEndpoint::Custom(url.to_string()), }) .unwrap_or(AcmeApiEndpoint::LetsEncryptProduction); info!("Certificate {name} needs to be renewed. Using endpoint: {endpoint}"); let requester = CertRequester::new(endpoint, conf.clone(), mgr.clone()); match (requester.request_certs().await, haproxyapi) { (Ok(_), Some(api)) => { match api.update_cert(&conf.fullchain_file).await { Ok(()) => info!("Certificate update in haproxy completed"), Err(e) => error!("Certificate update in haproxy failed: {e}") } } (Err(e), _) => error!("Certificate request for {name} failed: {e}"), _ => () } } else { info!("Certificate {name} does not need to be renewed"); } } }