Compare commits
3 Commits
dbb8e442c8
...
92254047b2
Author | SHA1 | Date | |
---|---|---|---|
92254047b2 | |||
c95f4b9b39 | |||
70c78b823a |
@ -82,8 +82,6 @@ domain:
|
|||||||
# External domains will have a TLS key/cert generated and signed for them, but
|
# External domains will have a TLS key/cert generated and signed for them, but
|
||||||
# which will not be served by domani itself. The key/cert files will be placed
|
# which will not be served by domani itself. The key/cert files will be placed
|
||||||
# in the configured paths.
|
# in the configured paths.
|
||||||
#
|
|
||||||
# HTTPS must be enabled for external_domains to be used.
|
|
||||||
#external_domains:
|
#external_domains:
|
||||||
#example.com
|
#example.com
|
||||||
# tls_key_path: /dir/path/key.pem
|
# tls_key_path: /dir/path/key.pem
|
||||||
|
@ -3,7 +3,6 @@ use crate::error::unexpected::{self, Mappable};
|
|||||||
use crate::{origin, task_stack, util};
|
use crate::{origin, task_stack, util};
|
||||||
|
|
||||||
use std::sync;
|
use std::sync;
|
||||||
use tokio_util::sync::CancellationToken;
|
|
||||||
|
|
||||||
pub enum GetSettingsResult {
|
pub enum GetSettingsResult {
|
||||||
Stored(domain::Settings),
|
Stored(domain::Settings),
|
||||||
@ -183,13 +182,57 @@ impl ManagerImpl {
|
|||||||
config,
|
config,
|
||||||
});
|
});
|
||||||
|
|
||||||
task_stack.push_spawn(|canceller| {
|
const SYNC_PERIOD_SECS: u64 = 20 * 60; // 20 minutes
|
||||||
let manager = manager.clone();
|
|
||||||
async move {
|
task_stack.push_spawn_periodically(
|
||||||
manager.sync_all_domains_job(canceller).await;
|
manager.clone(),
|
||||||
Ok(())
|
SYNC_PERIOD_SECS,
|
||||||
}
|
|_canceller, manager| async move {
|
||||||
});
|
manager
|
||||||
|
.sync_origins()
|
||||||
|
.await
|
||||||
|
.or_unexpected_while("syncing origins")
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if manager.can_sync_gemini_cert() {
|
||||||
|
task_stack.push_spawn_periodically(
|
||||||
|
manager.clone(),
|
||||||
|
SYNC_PERIOD_SECS,
|
||||||
|
|_canceller, manager| async move {
|
||||||
|
manager
|
||||||
|
.sync_gemini_certs()
|
||||||
|
.await
|
||||||
|
.or_unexpected_while("syncing gemini certs")
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if manager.can_sync_https_cert() {
|
||||||
|
task_stack.push_spawn_periodically(
|
||||||
|
manager.clone(),
|
||||||
|
SYNC_PERIOD_SECS,
|
||||||
|
|_canceller, manager| async move {
|
||||||
|
manager
|
||||||
|
.sync_https_certs()
|
||||||
|
.await
|
||||||
|
.or_unexpected_while("syncing https certs")
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if manager.can_sync_external_cert() {
|
||||||
|
task_stack.push_spawn_periodically(
|
||||||
|
manager.clone(),
|
||||||
|
SYNC_PERIOD_SECS,
|
||||||
|
|_canceller, manager| async move {
|
||||||
|
manager
|
||||||
|
.sync_external_certs()
|
||||||
|
.await
|
||||||
|
.or_unexpected_while("syncing external certs")
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
manager
|
manager
|
||||||
}
|
}
|
||||||
@ -203,116 +246,160 @@ impl ManagerImpl {
|
|||||||
self.origin_store.sync(origin_descr)
|
self.origin_store.sync(origin_descr)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sync_domain_gemini_cert(&self, domain: &domain::Name) -> unexpected::Result<()> {
|
async fn sync_origins(&self) -> unexpected::Result<()> {
|
||||||
if let Some(ref gemini_store) = self.gemini_store {
|
let domains = self
|
||||||
log::info!("Syncing gemini certificate for domain {domain}");
|
.all_domains()
|
||||||
if gemini_store
|
.or_unexpected_while("fetching all domains")?
|
||||||
.get_certificate(domain)
|
.into_iter();
|
||||||
.or_unexpected()?
|
|
||||||
.is_some()
|
for ManagedDomain { domain, .. } in domains {
|
||||||
|
let settings = match self
|
||||||
|
.get_settings(&domain)
|
||||||
|
.map_unexpected_while(|| format!("fetching settings for {domain}"))?
|
||||||
{
|
{
|
||||||
return Ok(());
|
GetSettingsResult::Stored(settings) => settings,
|
||||||
}
|
GetSettingsResult::Builtin(config) => config.settings,
|
||||||
|
_ => continue,
|
||||||
|
};
|
||||||
|
|
||||||
// no cert/key stored for the domain, generate and store it
|
self.sync_domain_origin(&domain, &settings.origin_descr)
|
||||||
let pkey = tls::PrivateKey::new();
|
.map_unexpected_while(|| {
|
||||||
let cert = tls::Certificate::new_self_signed(&pkey, domain)
|
format!(
|
||||||
.or_unexpected_while("creating self-signed cert")?;
|
"syncing origin {:?} for domain {domain}",
|
||||||
|
&settings.origin_descr,
|
||||||
gemini_store.set_certificate(domain, pkey, cert)?;
|
)
|
||||||
|
})?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn sync_domain_https_cert(&self, domain: &domain::Name) -> unexpected::Result<()> {
|
fn can_sync_gemini_cert(&self) -> bool {
|
||||||
if let Some(ref acme_manager) = self.acme_manager {
|
self.gemini_store.is_some()
|
||||||
log::info!("Syncing HTTPS certificate for domain {domain}");
|
}
|
||||||
acme_manager.sync_domain(domain.clone(), None).await?;
|
|
||||||
|
fn sync_domain_gemini_cert(&self, domain: &domain::Name) -> unexpected::Result<()> {
|
||||||
|
let gemini_store = self.gemini_store.as_ref().unwrap();
|
||||||
|
|
||||||
|
log::info!("Syncing gemini certificate for domain {domain}");
|
||||||
|
if gemini_store
|
||||||
|
.get_certificate(domain)
|
||||||
|
.or_unexpected_while("checking if cert is already stored")?
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// no cert/key stored for the domain, generate and store it
|
||||||
|
let pkey = tls::PrivateKey::new();
|
||||||
|
let cert = tls::Certificate::new_self_signed(&pkey, domain)
|
||||||
|
.or_unexpected_while("creating self-signed cert")?;
|
||||||
|
|
||||||
|
gemini_store.set_certificate(domain, pkey, cert)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn sync_gemini_certs(&self) -> unexpected::Result<()> {
|
||||||
|
let domains = self
|
||||||
|
.all_domains()
|
||||||
|
.or_unexpected_while("fetching all domains")?
|
||||||
|
.into_iter();
|
||||||
|
|
||||||
|
for ManagedDomain { domain, .. } in domains {
|
||||||
|
match self
|
||||||
|
.get_settings(&domain)
|
||||||
|
.map_unexpected_while(|| format!("fetching settings for {domain}"))?
|
||||||
|
{
|
||||||
|
GetSettingsResult::Stored(_) => (),
|
||||||
|
GetSettingsResult::Builtin(_) => (),
|
||||||
|
_ => continue,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.sync_domain_gemini_cert(&domain)
|
||||||
|
.map_unexpected_while(|| format!("syncing domain {domain}"))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn can_sync_https_cert(&self) -> bool {
|
||||||
|
self.acme_manager.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn sync_domain_https_cert(&self, domain: &domain::Name) -> unexpected::Result<()> {
|
||||||
|
log::info!("Syncing HTTPS certificate for domain {domain}");
|
||||||
|
self.acme_manager
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.sync_domain(domain.clone(), None)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn sync_https_certs(&self) -> unexpected::Result<()> {
|
||||||
|
let domains = self
|
||||||
|
.all_domains()
|
||||||
|
.or_unexpected_while("fetching all domains")?
|
||||||
|
.into_iter();
|
||||||
|
|
||||||
|
for ManagedDomain { domain, .. } in domains {
|
||||||
|
match self
|
||||||
|
.get_settings(&domain)
|
||||||
|
.map_unexpected_while(|| format!("fetching settings for {domain}"))?
|
||||||
|
{
|
||||||
|
GetSettingsResult::Stored(_) => (),
|
||||||
|
GetSettingsResult::Builtin(_) => (),
|
||||||
|
GetSettingsResult::Proxied(config) => {
|
||||||
|
if config.https_disabled {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
GetSettingsResult::Interface => (),
|
||||||
|
_ => continue,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.sync_domain_https_cert(&domain)
|
||||||
|
.await
|
||||||
|
.map_unexpected_while(|| format!("syncing domain {domain}",))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn can_sync_external_cert(&self) -> bool {
|
||||||
|
self.acme_manager.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
async fn sync_external_domain(
|
async fn sync_external_domain(
|
||||||
&self,
|
&self,
|
||||||
domain: &domain::Name,
|
domain: &domain::Name,
|
||||||
config: &domain::ConfigExternalDomain,
|
config: &domain::ConfigExternalDomain,
|
||||||
) -> unexpected::Result<()> {
|
) -> unexpected::Result<()> {
|
||||||
if let Some(ref acme_manager) = self.acme_manager {
|
log::info!("Syncing HTTPS certificate for external domain {domain}");
|
||||||
log::info!("Syncing HTTPS certificate for external domain {domain}");
|
self.acme_manager
|
||||||
acme_manager
|
.as_ref()
|
||||||
.sync_domain(domain.clone(), Some(config.clone()))
|
.unwrap()
|
||||||
.await?;
|
.sync_domain(domain.clone(), Some(config.clone()))
|
||||||
}
|
.await
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn sync_all_domains(&self) -> unexpected::Result<()> {
|
async fn sync_external_certs(&self) -> unexpected::Result<()> {
|
||||||
let domains = self
|
let domains = self
|
||||||
.all_domains()
|
.all_domains()
|
||||||
.or_unexpected_while("fetching all domains")?
|
.or_unexpected_while("fetching all domains")?
|
||||||
.into_iter();
|
.into_iter();
|
||||||
|
|
||||||
for ManagedDomain { domain, .. } in domains {
|
for ManagedDomain { domain, .. } in domains {
|
||||||
let (settings, https_cert, gemini_cert) = match self
|
if let GetSettingsResult::External(config) = self
|
||||||
.get_settings(&domain)
|
.get_settings(&domain)
|
||||||
.map_unexpected_while(|| format!("fetching settings for {domain}"))?
|
.map_unexpected_while(|| format!("fetching settings for {domain}"))?
|
||||||
{
|
{
|
||||||
GetSettingsResult::Stored(settings) => (Some(settings), true, true),
|
self.sync_external_domain(&domain, &config)
|
||||||
GetSettingsResult::Builtin(config) => (Some(config.settings), true, true),
|
|
||||||
|
|
||||||
// A proxied domain never needs gemini certs, since gemini requests will be
|
|
||||||
// transparently proxied to the backing server anyway.
|
|
||||||
GetSettingsResult::Proxied(config) => (None, !config.https_disabled, false),
|
|
||||||
|
|
||||||
GetSettingsResult::Interface => (None, true, false),
|
|
||||||
|
|
||||||
// External domains do their own thing, separate from the rest of this flow.
|
|
||||||
GetSettingsResult::External(config) => {
|
|
||||||
self.sync_external_domain(&domain, &config)
|
|
||||||
.await
|
|
||||||
.map_unexpected_while(|| format!("syncing external domain {domain}"))?;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(settings) = settings {
|
|
||||||
self.sync_domain_origin(&domain, &settings.origin_descr)
|
|
||||||
.map_unexpected_while(|| {
|
|
||||||
format!(
|
|
||||||
"syncing origin {:?} for domain {domain}",
|
|
||||||
&settings.origin_descr,
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if gemini_cert {
|
|
||||||
self.sync_domain_gemini_cert(&domain)
|
|
||||||
.map_unexpected_while(|| format!("syncing gemini cert for domain {domain}"))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if https_cert {
|
|
||||||
self.sync_domain_https_cert(&domain)
|
|
||||||
.await
|
.await
|
||||||
.map_unexpected_while(|| format!("syncing https cert for domain {domain}",))?;
|
.map_unexpected_while(|| format!("syncing external {domain}"))?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn sync_all_domains_job(&self, canceller: CancellationToken) {
|
|
||||||
let mut interval = tokio::time::interval(tokio::time::Duration::from_secs(20 * 60));
|
|
||||||
loop {
|
|
||||||
tokio::select! {
|
|
||||||
_ = canceller.cancelled() => return,
|
|
||||||
_ = interval.tick() => if let Err(err) = self.sync_all_domains().await {
|
|
||||||
log::error!("Failed to sync all domains: {err}")
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Manager for ManagerImpl {
|
impl Manager for ManagerImpl {
|
||||||
@ -391,12 +478,16 @@ impl Manager for ManagerImpl {
|
|||||||
|
|
||||||
self.sync_domain_origin(&domain, &settings.origin_descr)?;
|
self.sync_domain_origin(&domain, &settings.origin_descr)?;
|
||||||
|
|
||||||
self.sync_domain_gemini_cert(&domain)
|
if self.can_sync_gemini_cert() {
|
||||||
.or_unexpected_while("syncing domain gemini cert")?;
|
self.sync_domain_gemini_cert(&domain)
|
||||||
|
.or_unexpected_while("syncing domain gemini cert")?;
|
||||||
|
}
|
||||||
|
|
||||||
self.sync_domain_https_cert(&domain)
|
if self.can_sync_https_cert() {
|
||||||
.await
|
self.sync_domain_https_cert(&domain)
|
||||||
.or_unexpected_while("syncing domain https cert")?;
|
.await
|
||||||
|
.or_unexpected_while("syncing domain https cert")?;
|
||||||
|
}
|
||||||
|
|
||||||
self.domain_store.set(&domain, &settings)?;
|
self.domain_store.set(&domain, &settings)?;
|
||||||
|
|
||||||
|
@ -74,10 +74,6 @@ async fn main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !config.domain.external_domains.is_empty() && config.service.http.https_addr.is_none() {
|
|
||||||
panic!("https must be enabled to use external_domains")
|
|
||||||
}
|
|
||||||
|
|
||||||
config
|
config
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -87,7 +83,9 @@ async fn main() {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let https_enabled = config.service.http.https_addr.is_some();
|
||||||
let gemini_enabled = config.service.gemini.gemini_addr.is_some();
|
let gemini_enabled = config.service.gemini.gemini_addr.is_some();
|
||||||
|
let external_domains_enabled = !config.domain.external_domains.is_empty();
|
||||||
|
|
||||||
let origin_store = domani::origin::git::FSStore::new(&config.origin)
|
let origin_store = domani::origin::git::FSStore::new(&config.origin)
|
||||||
.expect("git origin store initialization failed");
|
.expect("git origin store initialization failed");
|
||||||
@ -103,7 +101,7 @@ async fn main() {
|
|||||||
domani::domain::store::FSStore::new(&config.domain.store_dir_path.join("domains"))
|
domani::domain::store::FSStore::new(&config.domain.store_dir_path.join("domains"))
|
||||||
.expect("domain config store initialization failed");
|
.expect("domain config store initialization failed");
|
||||||
|
|
||||||
let domain_acme_manager = if config.service.http.https_addr.is_some() {
|
let domain_acme_manager = if https_enabled || external_domains_enabled {
|
||||||
let acme_config = config
|
let acme_config = config
|
||||||
.domain
|
.domain
|
||||||
.acme
|
.acme
|
||||||
|
@ -66,8 +66,6 @@ impl Service {
|
|||||||
where
|
where
|
||||||
CertResolver: rustls::server::ResolvesServerCert + 'static,
|
CertResolver: rustls::server::ResolvesServerCert + 'static,
|
||||||
{
|
{
|
||||||
let https_enabled = config.http.https_addr.is_some();
|
|
||||||
|
|
||||||
let service = sync::Arc::new(Service {
|
let service = sync::Arc::new(Service {
|
||||||
domain_manager: domain_manager.clone(),
|
domain_manager: domain_manager.clone(),
|
||||||
cert_resolver: sync::Arc::from(cert_resolver),
|
cert_resolver: sync::Arc::from(cert_resolver),
|
||||||
@ -79,13 +77,17 @@ impl Service {
|
|||||||
|
|
||||||
task_stack.push_spawn(|canceller| tasks::listen_http(service.clone(), canceller));
|
task_stack.push_spawn(|canceller| tasks::listen_http(service.clone(), canceller));
|
||||||
|
|
||||||
if https_enabled {
|
if service.https_enabled() {
|
||||||
task_stack.push_spawn(|canceller| tasks::listen_https(service.clone(), canceller));
|
task_stack.push_spawn(|canceller| tasks::listen_https(service.clone(), canceller));
|
||||||
}
|
}
|
||||||
|
|
||||||
service
|
service
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn https_enabled(&self) -> bool {
|
||||||
|
self.config.http.https_addr.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
fn serve(&self, status_code: u16, path: &str, body: Body) -> Response<Body> {
|
fn serve(&self, status_code: u16, path: &str, body: Body) -> Response<Body> {
|
||||||
match Response::builder()
|
match Response::builder()
|
||||||
.status(status_code)
|
.status(status_code)
|
||||||
@ -125,7 +127,7 @@ impl Service {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn presenter_http_scheme(&self) -> &str {
|
fn presenter_http_scheme(&self) -> &str {
|
||||||
if self.config.http.https_addr.is_some() {
|
if self.https_enabled() {
|
||||||
return "https";
|
return "https";
|
||||||
}
|
}
|
||||||
"http"
|
"http"
|
||||||
@ -542,7 +544,7 @@ impl Service {
|
|||||||
// - /.well-known urls
|
// - /.well-known urls
|
||||||
// - proxied domains with https_disabled set on them
|
// - proxied domains with https_disabled set on them
|
||||||
// everything else must use https if possible.
|
// everything else must use https if possible.
|
||||||
let https_upgradable = self.config.http.https_addr.is_some() && !req_is_https;
|
let https_upgradable = self.https_enabled() && !req_is_https;
|
||||||
|
|
||||||
if let Some(config) = self.proxied_domains.get(&domain) {
|
if let Some(config) = self.proxied_domains.get(&domain) {
|
||||||
if config.http_url.is_none() {
|
if config.http_url.is_none() {
|
||||||
|
@ -42,6 +42,34 @@ where
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn push_spawn_periodically<State, F, Fut>(&mut self, state: State, period_secs: u64, f: F)
|
||||||
|
where
|
||||||
|
State: Clone + Send + Sync + 'static,
|
||||||
|
Fut: futures::Future<Output = Result<(), E>> + Send + 'static,
|
||||||
|
F: Fn(CancellationToken, State) -> Fut + Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
let canceller = CancellationToken::new();
|
||||||
|
let handle = {
|
||||||
|
let canceller = canceller.clone();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
let mut interval =
|
||||||
|
tokio::time::interval(tokio::time::Duration::from_secs(period_secs));
|
||||||
|
loop {
|
||||||
|
tokio::select! {
|
||||||
|
_ = canceller.cancelled() => return Ok(()),
|
||||||
|
_ = interval.tick() => if let Err(err) = f(canceller.clone(), state.clone()).await {
|
||||||
|
log::error!("Failed to sync all domains: {err}")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
self.push(async move {
|
||||||
|
canceller.cancel();
|
||||||
|
handle.await.expect("failed to join task")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/// stop will process all operations which have been pushed onto the stack in the reverse order
|
/// stop will process all operations which have been pushed onto the stack in the reverse order
|
||||||
/// they were pushed.
|
/// they were pushed.
|
||||||
pub async fn stop(mut self) -> Result<(), E> {
|
pub async fn stop(mut self) -> Result<(), E> {
|
||||||
|
Loading…
Reference in New Issue
Block a user