|
|
|
@ -56,23 +56,17 @@ struct Cli { |
|
|
|
|
domain_acme_contact_email: Option<String>, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn main() { |
|
|
|
|
#[tokio::main] |
|
|
|
|
async fn main() { |
|
|
|
|
let config = Cli::parse(); |
|
|
|
|
|
|
|
|
|
let mut wait_group = FuturesUnordered::new(); |
|
|
|
|
|
|
|
|
|
let tokio_runtime = std::sync::Arc::new( |
|
|
|
|
tokio::runtime::Builder::new_multi_thread() |
|
|
|
|
.enable_all() |
|
|
|
|
.build() |
|
|
|
|
.unwrap(), |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
let canceller = tokio_runtime.block_on(async { tokio_util::sync::CancellationToken::new() }); |
|
|
|
|
let canceller = tokio_util::sync::CancellationToken::new(); |
|
|
|
|
|
|
|
|
|
{ |
|
|
|
|
let canceller = canceller.clone(); |
|
|
|
|
tokio_runtime.spawn(async move { |
|
|
|
|
|
|
|
|
|
tokio::spawn(async move { |
|
|
|
|
let mut signals = |
|
|
|
|
Signals::new(signal_hook::consts::TERM_SIGNALS).expect("initialized signals"); |
|
|
|
|
|
|
|
|
@ -92,16 +86,16 @@ fn main() { |
|
|
|
|
.expect("git origin store initialized"); |
|
|
|
|
|
|
|
|
|
let domain_checker = domiply::domain::checker::new( |
|
|
|
|
tokio_runtime.clone(), |
|
|
|
|
config.domain_checker_target_a, |
|
|
|
|
&config.domain_checker_resolver_addr, |
|
|
|
|
) |
|
|
|
|
.await |
|
|
|
|
.expect("domain checker initialized"); |
|
|
|
|
|
|
|
|
|
let domain_config_store = domiply::domain::config::new(&config.domain_config_store_dir_path) |
|
|
|
|
.expect("domain config store initialized"); |
|
|
|
|
|
|
|
|
|
let domain_acme_manager = config.https_listen_addr.and_then(|_addr| { |
|
|
|
|
let (domain_acme_store, domain_acme_manager) = if config.https_listen_addr.is_some() { |
|
|
|
|
let domain_acme_store = |
|
|
|
|
domiply::domain::acme::store::new(&config.domain_acme_store_dir_path) |
|
|
|
|
.expect("domain acme store initialized"); |
|
|
|
@ -110,14 +104,17 @@ fn main() { |
|
|
|
|
// settings.
|
|
|
|
|
let domain_acme_contact_email = config.domain_acme_contact_email.unwrap(); |
|
|
|
|
|
|
|
|
|
let domain_acme_manager = tokio_runtime.block_on(async { |
|
|
|
|
domiply::domain::acme::manager::new(domain_acme_store, &domain_acme_contact_email) |
|
|
|
|
.await |
|
|
|
|
.expect("domain acme manager initialized") |
|
|
|
|
}); |
|
|
|
|
let domain_acme_manager = domiply::domain::acme::manager::new( |
|
|
|
|
domain_acme_store.clone(), |
|
|
|
|
&domain_acme_contact_email, |
|
|
|
|
) |
|
|
|
|
.await |
|
|
|
|
.expect("domain acme manager initialized"); |
|
|
|
|
|
|
|
|
|
Some(domain_acme_manager) |
|
|
|
|
}); |
|
|
|
|
(Some(domain_acme_store), Some(domain_acme_manager)) |
|
|
|
|
} else { |
|
|
|
|
(None, None) |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
let manager = domiply::domain::manager::new( |
|
|
|
|
origin_store, |
|
|
|
@ -130,7 +127,7 @@ fn main() { |
|
|
|
|
let manager = manager.clone(); |
|
|
|
|
let canceller = canceller.clone(); |
|
|
|
|
|
|
|
|
|
tokio_runtime.spawn(async move { |
|
|
|
|
tokio::spawn(async move { |
|
|
|
|
let mut interval = time::interval(time::Duration::from_secs(20 * 60)); |
|
|
|
|
|
|
|
|
|
loop { |
|
|
|
@ -166,8 +163,12 @@ fn main() { |
|
|
|
|
|
|
|
|
|
let service = sync::Arc::new(service); |
|
|
|
|
|
|
|
|
|
let make_service = |
|
|
|
|
hyper::service::make_service_fn(move |_conn: &hyper::server::conn::AddrStream| { |
|
|
|
|
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 |_| { |
|
|
|
|
let service = service.clone(); |
|
|
|
|
|
|
|
|
|
// Create a `Service` for responding to the request.
|
|
|
|
@ -179,11 +180,7 @@ fn main() { |
|
|
|
|
async move { Ok::<_, Infallible>(service) } |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
wait_group.push({ |
|
|
|
|
let http_domain = config.http_domain.clone(); |
|
|
|
|
let canceller = canceller.clone(); |
|
|
|
|
|
|
|
|
|
tokio_runtime.spawn(async move { |
|
|
|
|
tokio::spawn(async move { |
|
|
|
|
let addr = config.http_listen_addr; |
|
|
|
|
|
|
|
|
|
println!( |
|
|
|
@ -203,61 +200,119 @@ fn main() { |
|
|
|
|
}) |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
// if there's an acme manager then it means that https is enabled, and we should ensure that
|
|
|
|
|
// the http domain for domiply itself has a valid certificate.
|
|
|
|
|
if let Some(domain_acme_manager) = domain_acme_manager { |
|
|
|
|
let manager = manager.clone(); |
|
|
|
|
let canceller = canceller.clone(); |
|
|
|
|
let http_domain = config.http_domain.clone(); |
|
|
|
|
|
|
|
|
|
// Periodically refresh all domain certs
|
|
|
|
|
wait_group.push(tokio_runtime.spawn(async move { |
|
|
|
|
let mut interval = time::interval(time::Duration::from_secs(60 * 60)); |
|
|
|
|
|
|
|
|
|
loop { |
|
|
|
|
select! { |
|
|
|
|
_ = interval.tick() => (), |
|
|
|
|
_ = canceller.cancelled() => return, |
|
|
|
|
// if there's an acme manager then it means that https is enabled
|
|
|
|
|
if let (Some(domain_acme_store), Some(domain_acme_manager)) = |
|
|
|
|
(domain_acme_store, domain_acme_manager) |
|
|
|
|
{ |
|
|
|
|
// Periodically refresh all domain certs, including the http_domain passed in the Cli opts
|
|
|
|
|
wait_group.push({ |
|
|
|
|
let manager = manager.clone(); |
|
|
|
|
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, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
_ = domain_acme_manager |
|
|
|
|
.sync_domain(http_domain.clone()) |
|
|
|
|
.await |
|
|
|
|
.inspect_err(|err| { |
|
|
|
|
println!( |
|
|
|
|
"Error while getting cert for {}: {err}", |
|
|
|
|
http_domain.as_str() |
|
|
|
|
) |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
let domains_iter = manager.all_domains(); |
|
|
|
|
|
|
|
|
|
if let Err(err) = domains_iter { |
|
|
|
|
println!("Got error calling all_domains: {err}"); |
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
for domain in domains_iter.unwrap().into_iter() { |
|
|
|
|
match domain { |
|
|
|
|
Ok(domain) => { |
|
|
|
|
let _ = domain_acme_manager |
|
|
|
|
.sync_domain(domain.clone()) |
|
|
|
|
.await |
|
|
|
|
.inspect_err(|err| { |
|
|
|
|
println!( |
|
|
|
|
"Error while getting cert for {}: {err}", |
|
|
|
|
domain.as_str(), |
|
|
|
|
) |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
Err(err) => println!("Error iterating through domains: {err}"), |
|
|
|
|
}; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
}) |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
_ = domain_acme_manager |
|
|
|
|
.sync_domain(http_domain.clone()) |
|
|
|
|
.await |
|
|
|
|
.inspect_err(|err| { |
|
|
|
|
println!( |
|
|
|
|
"Error while getting cert for {}: {err}", |
|
|
|
|
http_domain.as_str() |
|
|
|
|
) |
|
|
|
|
}); |
|
|
|
|
// HTTPS server
|
|
|
|
|
wait_group.push({ |
|
|
|
|
let http_domain = config.http_domain.clone(); |
|
|
|
|
let canceller = canceller.clone(); |
|
|
|
|
let service = service.clone(); |
|
|
|
|
|
|
|
|
|
let domains_iter = manager.all_domains(); |
|
|
|
|
let make_service = hyper::service::make_service_fn(move |_| { |
|
|
|
|
let service = service.clone(); |
|
|
|
|
|
|
|
|
|
if let Err(err) = domains_iter { |
|
|
|
|
println!("Got error calling all_domains: {err}"); |
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
// Create a `Service` for responding to the request.
|
|
|
|
|
let service = hyper::service::service_fn(move |req| { |
|
|
|
|
domiply::service::handle_request(service.clone(), req) |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
for domain in domains_iter.unwrap().into_iter() { |
|
|
|
|
match domain { |
|
|
|
|
Ok(domain) => { |
|
|
|
|
let _ = domain_acme_manager |
|
|
|
|
.sync_domain(domain.clone()) |
|
|
|
|
.await |
|
|
|
|
.inspect_err(|err| { |
|
|
|
|
println!( |
|
|
|
|
"Error while getting cert for {}: {err}", |
|
|
|
|
domain.as_str(), |
|
|
|
|
) |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
Err(err) => println!("Error iterating through domains: {err}"), |
|
|
|
|
}; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
})); |
|
|
|
|
// Return the service to hyper.
|
|
|
|
|
async move { Ok::<_, Infallible>(service) } |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
tokio::spawn(async move { |
|
|
|
|
let canceller = canceller.clone(); |
|
|
|
|
let server_config: tokio_rustls::TlsAcceptor = sync::Arc::new( |
|
|
|
|
rustls::server::ServerConfig::builder() |
|
|
|
|
.with_safe_default_cipher_suites() |
|
|
|
|
.with_safe_default_kx_groups() |
|
|
|
|
.with_safe_default_protocol_versions() |
|
|
|
|
.unwrap() |
|
|
|
|
.with_no_client_auth() |
|
|
|
|
.with_cert_resolver(sync::Arc::from(domain_acme_store)), |
|
|
|
|
) |
|
|
|
|
.into(); |
|
|
|
|
|
|
|
|
|
let addr = config.https_listen_addr.unwrap(); |
|
|
|
|
let addr_incoming = hyper::server::conn::AddrIncoming::bind(&addr) |
|
|
|
|
.expect("https listen socket created"); |
|
|
|
|
|
|
|
|
|
let incoming = tls_listener::TlsListener::new(server_config, addr_incoming); |
|
|
|
|
|
|
|
|
|
println!( |
|
|
|
|
"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); |
|
|
|
|
}; |
|
|
|
|
}) |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
tokio_runtime.block_on(async { while let Some(_) = wait_group.next().await {} }); |
|
|
|
|
while let Some(_) = wait_group.next().await {} |
|
|
|
|
|
|
|
|
|
println!("Graceful shutdown complete"); |
|
|
|
|
} |
|
|
|
|