96 lines
3.4 KiB
Rust
96 lines
3.4 KiB
Rust
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<dyn std::error::Error>> {
|
|
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<HaProxyApi>) {
|
|
// 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");
|
|
}
|
|
}
|
|
}
|