domani/src/main.rs

176 lines
5.6 KiB
Rust
Raw Normal View History

use clap::Parser;
use futures::stream::StreamExt;
use signal_hook_tokio::Signals;
use std::net::SocketAddr;
use std::str::FromStr;
use std::{path, sync};
#[derive(Parser, Debug)]
#[command(version)]
2023-05-13 14:39:54 +00:00
#[command(about = "A domiply to another dimension")]
struct Cli {
2023-06-13 19:33:43 +00:00
#[arg(
long,
help = "OFF, ERROR, WARN, INFO, DEBUG, or TRACE",
default_value_t = log::LevelFilter::Info,
env = "DOMIPLY_LOG_LEVEL"
)]
log_level: log::LevelFilter,
#[arg(long, default_value_t = false, env = "DOMIPLY_LOG_TIMESTAMP")]
log_timestamp: bool,
#[arg(long, required = true, env = "DOMIPLY_HTTP_DOMAIN")]
2023-05-19 10:36:01 +00:00
http_domain: domiply::domain::Name,
2023-05-15 16:23:53 +00:00
#[arg(long, default_value_t = SocketAddr::from_str("[::]:3030").unwrap(), env = "DOMIPLY_HTTP_LISTEN_ADDR")]
http_listen_addr: SocketAddr,
#[arg(
long,
help = "E.g. '[::]:443', if given then SSL certs will automatically be retrieved for all domains using LetsEncrypt",
env = "DOMIPLY_HTTPS_LISTEN_ADDR",
requires = "domain_acme_contact_email",
requires = "domain_acme_store_dir_path"
)]
https_listen_addr: Option<SocketAddr>,
2023-05-15 18:25:07 +00:00
2023-05-13 14:39:54 +00:00
#[arg(long, required = true, env = "DOMIPLY_PASSPHRASE")]
passphrase: String,
2023-05-13 14:39:54 +00:00
#[arg(long, required = true, env = "DOMIPLY_ORIGIN_STORE_GIT_DIR_PATH")]
origin_store_git_dir_path: path::PathBuf,
2023-05-15 20:16:29 +00:00
#[arg(long, required = true, env = "DOMIPLY_DOMAIN_CHECKER_TARGET_A")]
domain_checker_target_a: std::net::Ipv4Addr,
2023-05-13 14:39:54 +00:00
#[arg(long, default_value_t = String::from("1.1.1.1:53"), env = "DOMIPLY_DOMAIN_CHECKER_RESOLVER_ADDR")]
domain_checker_resolver_addr: String,
2023-05-13 14:39:54 +00:00
#[arg(long, required = true, env = "DOMIPLY_DOMAIN_CONFIG_STORE_DIR_PATH")]
domain_config_store_dir_path: path::PathBuf,
#[arg(long, env = "DOMIPLY_DOMAIN_ACME_STORE_DIR_PATH")]
domain_acme_store_dir_path: Option<path::PathBuf>,
#[arg(long, env = "DOMIPLY_DOMAIN_ACME_CONTACT_EMAIL")]
domain_acme_contact_email: Option<String>,
}
#[derive(Clone)]
2023-06-18 11:44:15 +00:00
struct HTTPSParams {
https_listen_addr: SocketAddr,
2023-06-18 11:44:15 +00:00
domain_acme_store: sync::Arc<dyn domiply::domain::acme::store::Store>,
domain_acme_manager: sync::Arc<dyn domiply::domain::acme::manager::Manager>,
}
2023-05-20 12:28:02 +00:00
#[tokio::main]
async fn main() {
let config = Cli::parse();
2023-06-13 19:33:43 +00:00
env_logger::Builder::new()
.filter_level(config.log_level)
.format_timestamp(
config
.log_timestamp
.then_some(env_logger::TimestampPrecision::Micros),
)
.init();
2023-05-20 12:28:02 +00:00
let canceller = tokio_util::sync::CancellationToken::new();
2023-05-15 19:18:33 +00:00
{
let canceller = canceller.clone();
2023-05-20 12:28:02 +00:00
tokio::spawn(async move {
let mut signals = Signals::new(signal_hook::consts::TERM_SIGNALS)
.expect("initializing signals failed");
2023-05-16 14:20:01 +00:00
if (signals.next().await).is_some() {
2023-06-13 19:33:43 +00:00
log::info!("Gracefully shutting down...");
2023-05-15 19:18:33 +00:00
canceller.cancel();
}
2023-05-16 14:20:01 +00:00
if (signals.next().await).is_some() {
2023-06-13 19:33:43 +00:00
log::warn!("Forcefully shutting down");
2023-05-15 19:18:33 +00:00
std::process::exit(1);
};
});
}
2023-05-13 14:39:54 +00:00
let origin_store = domiply::origin::store::git::new(config.origin_store_git_dir_path)
.expect("git origin store initialization failed");
2023-05-17 12:37:23 +00:00
let domain_checker = domiply::domain::checker::new(
config.domain_checker_target_a,
&config.domain_checker_resolver_addr,
)
2023-05-20 12:28:02 +00:00
.await
.expect("domain checker initialization failed");
2023-05-17 12:37:23 +00:00
let domain_config_store = domiply::domain::config::new(&config.domain_config_store_dir_path)
.expect("domain config store initialization failed");
2023-05-17 12:37:23 +00:00
let https_params = if let Some(https_listen_addr) = config.https_listen_addr {
let domain_acme_store_dir_path = config.domain_acme_store_dir_path.unwrap();
let domain_acme_store = domiply::domain::acme::store::new(&domain_acme_store_dir_path)
.expect("domain acme store initialization failed");
// if https_listen_addr is set then domain_acme_contact_email is required, see the Cli/clap
// settings.
let domain_acme_contact_email = config.domain_acme_contact_email.unwrap();
2023-05-20 12:28:02 +00:00
let domain_acme_manager = domiply::domain::acme::manager::new(
domain_acme_store.clone(),
&domain_acme_contact_email,
)
.await
.expect("domain acme manager initialization failed");
Some(HTTPSParams {
https_listen_addr,
domain_acme_store,
domain_acme_manager,
})
2023-05-20 12:28:02 +00:00
} else {
None
2023-05-20 12:28:02 +00:00
};
let domain_manager = domiply::domain::manager::new(
origin_store,
domain_config_store,
domain_checker,
2023-06-13 19:36:12 +00:00
https_params.as_ref().map(|p| p.domain_acme_manager.clone()),
);
2023-05-17 12:37:23 +00:00
let domain_manager = sync::Arc::new(domain_manager);
{
let http_service = domiply::service::http::new(
domain_manager.clone(),
config.domain_checker_target_a,
config.passphrase,
config.http_listen_addr.clone(),
config.http_domain.clone(),
https_params.map(|p| domiply::service::http::HTTPSParams {
listen_addr: p.https_listen_addr,
cert_resolver: domiply::domain::acme::resolver::new(p.domain_acme_store),
}),
);
canceller.cancelled().await;
sync::Arc::into_inner(http_service).unwrap().stop().await;
}
sync::Arc::into_inner(domain_manager)
.unwrap()
.stop()
.await
.expect("domain manager failed to shutdown cleanly");
2023-06-13 19:33:43 +00:00
log::info!("Graceful shutdown complete");
}