2023-05-18 20:02:57 +00:00
|
|
|
#![feature(result_option_inspect)]
|
|
|
|
|
2023-05-11 12:19:36 +00:00
|
|
|
use clap::Parser;
|
2023-05-19 10:29:37 +00:00
|
|
|
use futures::stream::futures_unordered::FuturesUnordered;
|
2023-05-11 12:19:36 +00:00
|
|
|
use futures::stream::StreamExt;
|
|
|
|
use signal_hook_tokio::Signals;
|
2023-05-15 19:18:33 +00:00
|
|
|
use tokio::select;
|
|
|
|
use tokio::time;
|
2023-05-11 12:19:36 +00:00
|
|
|
|
2023-05-15 15:42:32 +00:00
|
|
|
use std::convert::Infallible;
|
2023-05-12 09:17:15 +00:00
|
|
|
use std::net::SocketAddr;
|
|
|
|
use std::str::FromStr;
|
2023-05-23 10:15:06 +00:00
|
|
|
use std::{future, path, sync};
|
2023-05-11 12:19:36 +00:00
|
|
|
|
|
|
|
#[derive(Parser, Debug)]
|
|
|
|
#[command(version)]
|
2023-05-13 14:39:54 +00:00
|
|
|
#[command(about = "A domiply to another dimension")]
|
2023-05-11 12:19:36 +00:00
|
|
|
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,
|
|
|
|
|
2023-05-18 20:02:57 +00:00
|
|
|
#[arg(long, required = true, env = "DOMIPLY_HTTP_DOMAIN")]
|
2023-05-19 10:36:01 +00:00
|
|
|
http_domain: domiply::domain::Name,
|
2023-05-18 20:02:57 +00:00
|
|
|
|
2023-05-15 16:23:53 +00:00
|
|
|
#[arg(long, default_value_t = SocketAddr::from_str("[::]:3030").unwrap(), env = "DOMIPLY_HTTP_LISTEN_ADDR")]
|
2023-05-12 09:17:15 +00:00
|
|
|
http_listen_addr: SocketAddr,
|
|
|
|
|
2023-05-18 20:02:57 +00:00
|
|
|
#[arg(
|
|
|
|
long,
|
|
|
|
help = "E.g. '[::]:443', if given then SSL certs will automatically be retrieved for all domains using LetsEncrypt",
|
2023-05-19 10:09:41 +00:00
|
|
|
env = "DOMIPLY_HTTPS_LISTEN_ADDR",
|
2023-05-20 12:51:36 +00:00
|
|
|
requires = "domain_acme_contact_email",
|
|
|
|
requires = "domain_acme_store_dir_path"
|
2023-05-18 20:02:57 +00:00
|
|
|
)]
|
|
|
|
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")]
|
2023-05-12 16:17:23 +00:00
|
|
|
passphrase: String,
|
|
|
|
|
2023-05-13 14:39:54 +00:00
|
|
|
#[arg(long, required = true, env = "DOMIPLY_ORIGIN_STORE_GIT_DIR_PATH")]
|
2023-05-12 13:19:24 +00:00
|
|
|
origin_store_git_dir_path: path::PathBuf,
|
2023-05-11 12:19:36 +00:00
|
|
|
|
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-11 12:19:36 +00:00
|
|
|
|
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")]
|
2023-05-11 12:19:36 +00:00
|
|
|
domain_checker_resolver_addr: String,
|
|
|
|
|
2023-05-13 14:39:54 +00:00
|
|
|
#[arg(long, required = true, env = "DOMIPLY_DOMAIN_CONFIG_STORE_DIR_PATH")]
|
2023-05-12 13:19:24 +00:00
|
|
|
domain_config_store_dir_path: path::PathBuf,
|
2023-05-18 20:02:57 +00:00
|
|
|
|
2023-05-20 12:51:36 +00:00
|
|
|
#[arg(long, env = "DOMIPLY_DOMAIN_ACME_STORE_DIR_PATH")]
|
|
|
|
domain_acme_store_dir_path: Option<path::PathBuf>,
|
2023-05-18 20:02:57 +00:00
|
|
|
|
2023-05-19 10:09:41 +00:00
|
|
|
#[arg(long, env = "DOMIPLY_DOMAIN_ACME_CONTACT_EMAIL")]
|
|
|
|
domain_acme_contact_email: Option<String>,
|
2023-05-11 12:19:36 +00:00
|
|
|
}
|
|
|
|
|
2023-05-20 12:51:36 +00:00
|
|
|
#[derive(Clone)]
|
2023-06-18 11:44:15 +00:00
|
|
|
struct HTTPSParams {
|
2023-05-20 12:51:36 +00:00
|
|
|
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:51:36 +00:00
|
|
|
}
|
|
|
|
|
2023-05-20 12:28:02 +00:00
|
|
|
#[tokio::main]
|
|
|
|
async fn main() {
|
2023-05-11 12:19:36 +00:00
|
|
|
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-19 10:29:37 +00:00
|
|
|
let mut wait_group = FuturesUnordered::new();
|
2023-05-20 12:28:02 +00:00
|
|
|
let canceller = tokio_util::sync::CancellationToken::new();
|
2023-05-14 09:18:36 +00:00
|
|
|
|
2023-05-15 19:18:33 +00:00
|
|
|
{
|
|
|
|
let canceller = canceller.clone();
|
2023-05-20 12:28:02 +00:00
|
|
|
|
|
|
|
tokio::spawn(async move {
|
2023-06-14 18:22:10 +00:00
|
|
|
let mut signals = Signals::new(signal_hook::consts::TERM_SIGNALS)
|
|
|
|
.expect("initializing signals failed");
|
2023-05-11 12:19:36 +00:00
|
|
|
|
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-11 12:19:36 +00:00
|
|
|
|
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-11 12:19:36 +00:00
|
|
|
|
2023-05-13 14:39:54 +00:00
|
|
|
let origin_store = domiply::origin::store::git::new(config.origin_store_git_dir_path)
|
2023-06-14 18:22:10 +00:00
|
|
|
.expect("git origin store initialization failed");
|
2023-05-11 12:19:36 +00:00
|
|
|
|
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
|
2023-06-14 18:22:10 +00:00
|
|
|
.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)
|
2023-06-14 18:22:10 +00:00
|
|
|
.expect("domain config store initialization failed");
|
2023-05-17 12:37:23 +00:00
|
|
|
|
2023-05-20 12:51:36 +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)
|
2023-06-14 18:22:10 +00:00
|
|
|
.expect("domain acme store initialization failed");
|
2023-05-18 20:02:57 +00:00
|
|
|
|
2023-05-19 10:09:41 +00:00
|
|
|
// 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
|
2023-06-14 18:22:10 +00:00
|
|
|
.expect("domain acme manager initialization failed");
|
2023-05-18 20:02:57 +00:00
|
|
|
|
2023-05-20 12:51:36 +00:00
|
|
|
Some(HTTPSParams {
|
|
|
|
https_listen_addr,
|
|
|
|
domain_acme_store,
|
|
|
|
domain_acme_manager,
|
|
|
|
})
|
2023-05-20 12:28:02 +00:00
|
|
|
} else {
|
2023-05-20 12:51:36 +00:00
|
|
|
None
|
2023-05-20 12:28:02 +00:00
|
|
|
};
|
2023-05-18 20:02:57 +00:00
|
|
|
|
2023-05-20 12:51:36 +00:00
|
|
|
let domain_manager = domiply::domain::manager::new(
|
2023-05-18 20:02:57 +00:00
|
|
|
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-18 20:02:57 +00:00
|
|
|
);
|
2023-05-17 12:37:23 +00:00
|
|
|
|
2023-05-13 14:39:54 +00:00
|
|
|
let service = domiply::service::new(
|
2023-05-20 12:51:36 +00:00
|
|
|
domain_manager.clone(),
|
2023-05-15 20:16:29 +00:00
|
|
|
config.domain_checker_target_a,
|
2023-05-12 16:17:23 +00:00
|
|
|
config.passphrase,
|
2023-05-15 20:16:29 +00:00
|
|
|
config.http_domain.clone(),
|
2023-05-15 15:42:32 +00:00
|
|
|
);
|
2023-05-11 12:19:36 +00:00
|
|
|
|
2023-05-15 15:42:32 +00:00
|
|
|
let service = sync::Arc::new(service);
|
|
|
|
|
2023-05-20 12:28:02 +00:00
|
|
|
wait_group.push({
|
|
|
|
let http_domain = config.http_domain.clone();
|
|
|
|
let canceller = canceller.clone();
|
|
|
|
let service = service.clone();
|
|
|
|
|
|
|
|
let make_service = hyper::service::make_service_fn(move |_| {
|
2023-05-15 15:42:32 +00:00
|
|
|
let service = service.clone();
|
|
|
|
|
|
|
|
// Create a `Service` for responding to the request.
|
|
|
|
let service = hyper::service::service_fn(move |req| {
|
|
|
|
domiply::service::handle_request(service.clone(), req)
|
2023-05-14 09:18:36 +00:00
|
|
|
});
|
2023-05-11 12:19:36 +00:00
|
|
|
|
2023-05-15 15:42:32 +00:00
|
|
|
// Return the service to hyper.
|
|
|
|
async move { Ok::<_, Infallible>(service) }
|
|
|
|
});
|
|
|
|
|
2023-05-20 12:28:02 +00:00
|
|
|
tokio::spawn(async move {
|
2023-05-15 19:18:33 +00:00
|
|
|
let addr = config.http_listen_addr;
|
2023-05-15 15:42:32 +00:00
|
|
|
|
2023-06-13 19:33:43 +00:00
|
|
|
log::info!(
|
2023-05-19 10:36:01 +00:00
|
|
|
"Listening on http://{}:{}",
|
|
|
|
http_domain.as_str(),
|
|
|
|
addr.port()
|
|
|
|
);
|
2023-05-15 19:18:33 +00:00
|
|
|
let server = hyper::Server::bind(&addr).serve(make_service);
|
2023-05-15 15:42:32 +00:00
|
|
|
|
2023-05-15 19:18:33 +00:00
|
|
|
let graceful = server.with_graceful_shutdown(async {
|
|
|
|
canceller.cancelled().await;
|
|
|
|
});
|
|
|
|
|
|
|
|
if let Err(e) = graceful.await {
|
|
|
|
panic!("server error: {}", e);
|
|
|
|
};
|
|
|
|
})
|
2023-05-19 10:29:37 +00:00
|
|
|
});
|
2023-05-15 15:42:32 +00:00
|
|
|
|
2023-05-20 12:51:36 +00:00
|
|
|
if let Some(https_params) = https_params {
|
2023-05-20 12:28:02 +00:00
|
|
|
// Periodically refresh all domain certs, including the http_domain passed in the Cli opts
|
|
|
|
wait_group.push({
|
2023-05-20 12:51:36 +00:00
|
|
|
let https_params = https_params.clone();
|
|
|
|
let domain_manager = domain_manager.clone();
|
2023-05-20 12:28:02 +00:00
|
|
|
let http_domain = config.http_domain.clone();
|
|
|
|
let canceller = canceller.clone();
|
|
|
|
|
|
|
|
tokio::spawn(async move {
|
|
|
|
let mut interval = time::interval(time::Duration::from_secs(60 * 60));
|
|
|
|
|
|
|
|
loop {
|
|
|
|
select! {
|
|
|
|
_ = interval.tick() => (),
|
|
|
|
_ = canceller.cancelled() => return,
|
|
|
|
}
|
|
|
|
|
2023-05-20 12:51:36 +00:00
|
|
|
_ = https_params
|
|
|
|
.domain_acme_manager
|
2023-05-20 12:28:02 +00:00
|
|
|
.sync_domain(http_domain.clone())
|
|
|
|
.await
|
|
|
|
.inspect_err(|err| {
|
2023-06-13 19:33:43 +00:00
|
|
|
log::error!(
|
2023-05-20 12:28:02 +00:00
|
|
|
"Error while getting cert for {}: {err}",
|
|
|
|
http_domain.as_str()
|
|
|
|
)
|
|
|
|
});
|
|
|
|
|
2023-05-20 12:51:36 +00:00
|
|
|
let domains_iter = domain_manager.all_domains();
|
2023-05-20 12:28:02 +00:00
|
|
|
|
|
|
|
if let Err(err) = domains_iter {
|
2023-06-13 19:33:43 +00:00
|
|
|
log::error!("Got error calling all_domains: {err}");
|
2023-05-20 12:28:02 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
for domain in domains_iter.unwrap().into_iter() {
|
|
|
|
match domain {
|
|
|
|
Ok(domain) => {
|
2023-05-20 12:51:36 +00:00
|
|
|
let _ = https_params
|
|
|
|
.domain_acme_manager
|
2023-05-20 12:28:02 +00:00
|
|
|
.sync_domain(domain.clone())
|
|
|
|
.await
|
|
|
|
.inspect_err(|err| {
|
2023-06-13 19:33:43 +00:00
|
|
|
log::error!(
|
2023-05-20 12:28:02 +00:00
|
|
|
"Error while getting cert for {}: {err}",
|
|
|
|
domain.as_str(),
|
|
|
|
)
|
|
|
|
});
|
|
|
|
}
|
2023-06-13 19:33:43 +00:00
|
|
|
Err(err) => log::error!("Error iterating through domains: {err}"),
|
2023-05-20 12:28:02 +00:00
|
|
|
};
|
|
|
|
}
|
2023-05-19 11:26:27 +00:00
|
|
|
}
|
2023-05-20 12:28:02 +00:00
|
|
|
})
|
|
|
|
});
|
2023-05-19 11:26:27 +00:00
|
|
|
|
2023-05-20 12:28:02 +00:00
|
|
|
// HTTPS server
|
|
|
|
wait_group.push({
|
|
|
|
let http_domain = config.http_domain.clone();
|
|
|
|
let canceller = canceller.clone();
|
|
|
|
let service = service.clone();
|
2023-05-19 11:26:27 +00:00
|
|
|
|
2023-05-20 12:28:02 +00:00
|
|
|
let make_service = hyper::service::make_service_fn(move |_| {
|
|
|
|
let service = service.clone();
|
2023-05-19 11:26:27 +00:00
|
|
|
|
2023-05-20 12:28:02 +00:00
|
|
|
// Create a `Service` for responding to the request.
|
|
|
|
let service = hyper::service::service_fn(move |req| {
|
|
|
|
domiply::service::handle_request(service.clone(), req)
|
|
|
|
});
|
2023-05-19 11:26:27 +00:00
|
|
|
|
2023-05-20 12:28:02 +00:00
|
|
|
// Return the service to hyper.
|
|
|
|
async move { Ok::<_, Infallible>(service) }
|
|
|
|
});
|
|
|
|
|
|
|
|
tokio::spawn(async move {
|
2023-06-18 11:44:15 +00:00
|
|
|
let cert_resolver =
|
|
|
|
domiply::domain::acme::resolver::new(https_params.domain_acme_store);
|
2023-05-20 12:28:02 +00:00
|
|
|
let canceller = canceller.clone();
|
2023-06-18 11:44:15 +00:00
|
|
|
|
2023-05-20 12:28:02 +00:00
|
|
|
let server_config: tokio_rustls::TlsAcceptor = sync::Arc::new(
|
|
|
|
rustls::server::ServerConfig::builder()
|
2023-05-23 10:15:06 +00:00
|
|
|
.with_safe_defaults()
|
2023-05-20 12:28:02 +00:00
|
|
|
.with_no_client_auth()
|
2023-06-18 11:44:15 +00:00
|
|
|
.with_cert_resolver(cert_resolver),
|
2023-05-20 12:28:02 +00:00
|
|
|
)
|
|
|
|
.into();
|
|
|
|
|
2023-05-20 12:51:36 +00:00
|
|
|
let addr = https_params.https_listen_addr;
|
2023-05-20 12:28:02 +00:00
|
|
|
let addr_incoming = hyper::server::conn::AddrIncoming::bind(&addr)
|
2023-06-14 18:22:10 +00:00
|
|
|
.expect("https listen socket creation failed");
|
2023-05-20 12:28:02 +00:00
|
|
|
|
2023-05-23 10:15:06 +00:00
|
|
|
let incoming =
|
|
|
|
tls_listener::TlsListener::new(server_config, addr_incoming).filter(|conn| {
|
|
|
|
if let Err(err) = conn {
|
2023-06-13 19:33:43 +00:00
|
|
|
log::error!("Error accepting TLS connection: {:?}", err);
|
2023-05-23 10:15:06 +00:00
|
|
|
future::ready(false)
|
|
|
|
} else {
|
|
|
|
future::ready(true)
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
let incoming = hyper::server::accept::from_stream(incoming);
|
2023-05-20 12:28:02 +00:00
|
|
|
|
2023-06-13 19:33:43 +00:00
|
|
|
log::info!(
|
2023-05-20 12:28:02 +00:00
|
|
|
"Listening on https://{}:{}",
|
|
|
|
http_domain.as_str(),
|
|
|
|
addr.port()
|
|
|
|
);
|
|
|
|
|
|
|
|
let server = hyper::Server::builder(incoming).serve(make_service);
|
|
|
|
|
|
|
|
let graceful = server.with_graceful_shutdown(async {
|
|
|
|
canceller.cancelled().await;
|
|
|
|
});
|
|
|
|
|
|
|
|
if let Err(e) = graceful.await {
|
|
|
|
panic!("server error: {}", e);
|
|
|
|
};
|
|
|
|
})
|
|
|
|
})
|
2023-05-18 20:02:57 +00:00
|
|
|
}
|
|
|
|
|
2023-05-20 12:34:45 +00:00
|
|
|
while wait_group.next().await.is_some() {}
|
2023-05-11 12:19:36 +00:00
|
|
|
|
2023-06-13 19:33:43 +00:00
|
|
|
log::info!("Graceful shutdown complete");
|
2023-05-11 12:19:36 +00:00
|
|
|
}
|