|
|
|
@ -3,7 +3,6 @@ use crate::error::unexpected::{self, Mappable}; |
|
|
|
|
use crate::{origin, task_stack, util}; |
|
|
|
|
|
|
|
|
|
use std::sync; |
|
|
|
|
use tokio_util::sync::CancellationToken; |
|
|
|
|
|
|
|
|
|
pub enum GetSettingsResult { |
|
|
|
|
Stored(domain::Settings), |
|
|
|
@ -183,13 +182,57 @@ impl ManagerImpl { |
|
|
|
|
config, |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
task_stack.push_spawn(|canceller| { |
|
|
|
|
let manager = manager.clone(); |
|
|
|
|
async move { |
|
|
|
|
manager.sync_all_domains_job(canceller).await; |
|
|
|
|
Ok(()) |
|
|
|
|
} |
|
|
|
|
}); |
|
|
|
|
const SYNC_PERIOD_SECS: u64 = 20 * 60; // 20 minutes
|
|
|
|
|
|
|
|
|
|
task_stack.push_spawn_periodically( |
|
|
|
|
manager.clone(), |
|
|
|
|
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 |
|
|
|
|
} |
|
|
|
@ -203,116 +246,160 @@ impl ManagerImpl { |
|
|
|
|
self.origin_store.sync(origin_descr) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn sync_domain_gemini_cert(&self, domain: &domain::Name) -> unexpected::Result<()> { |
|
|
|
|
if let Some(ref gemini_store) = self.gemini_store { |
|
|
|
|
log::info!("Syncing gemini certificate for domain {domain}"); |
|
|
|
|
if gemini_store |
|
|
|
|
.get_certificate(domain) |
|
|
|
|
.or_unexpected()? |
|
|
|
|
.is_some() |
|
|
|
|
{ |
|
|
|
|
return Ok(()); |
|
|
|
|
} |
|
|
|
|
async fn sync_origins(&self) -> unexpected::Result<()> { |
|
|
|
|
let domains = self |
|
|
|
|
.all_domains() |
|
|
|
|
.or_unexpected_while("fetching all domains")? |
|
|
|
|
.into_iter(); |
|
|
|
|
|
|
|
|
|
// 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")?; |
|
|
|
|
for ManagedDomain { domain, .. } in domains { |
|
|
|
|
let settings = match self |
|
|
|
|
.get_settings(&domain) |
|
|
|
|
.map_unexpected_while(|| format!("fetching settings for {domain}"))? |
|
|
|
|
{ |
|
|
|
|
GetSettingsResult::Stored(settings) => settings, |
|
|
|
|
GetSettingsResult::Builtin(config) => config.settings, |
|
|
|
|
_ => continue, |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
gemini_store.set_certificate(domain, pkey, cert)?; |
|
|
|
|
self.sync_domain_origin(&domain, &settings.origin_descr) |
|
|
|
|
.map_unexpected_while(|| { |
|
|
|
|
format!( |
|
|
|
|
"syncing origin {:?} for domain {domain}", |
|
|
|
|
&settings.origin_descr, |
|
|
|
|
) |
|
|
|
|
})?; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
Ok(()) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
async fn sync_domain_https_cert(&self, domain: &domain::Name) -> unexpected::Result<()> { |
|
|
|
|
if let Some(ref acme_manager) = self.acme_manager { |
|
|
|
|
log::info!("Syncing HTTPS certificate for domain {domain}"); |
|
|
|
|
acme_manager.sync_domain(domain.clone(), None).await?; |
|
|
|
|
fn can_sync_gemini_cert(&self) -> bool { |
|
|
|
|
self.gemini_store.is_some() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
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(()); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
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_external_domain( |
|
|
|
|
&self, |
|
|
|
|
domain: &domain::Name, |
|
|
|
|
config: &domain::ConfigExternalDomain, |
|
|
|
|
) -> unexpected::Result<()> { |
|
|
|
|
if let Some(ref acme_manager) = self.acme_manager { |
|
|
|
|
log::info!("Syncing HTTPS certificate for external domain {domain}"); |
|
|
|
|
acme_manager |
|
|
|
|
.sync_domain(domain.clone(), Some(config.clone())) |
|
|
|
|
.await?; |
|
|
|
|
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(()) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
async fn sync_all_domains(&self) -> unexpected::Result<()> { |
|
|
|
|
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 { |
|
|
|
|
let (settings, https_cert, gemini_cert) = match self |
|
|
|
|
match self |
|
|
|
|
.get_settings(&domain) |
|
|
|
|
.map_unexpected_while(|| format!("fetching settings for {domain}"))? |
|
|
|
|
{ |
|
|
|
|
GetSettingsResult::Stored(settings) => (Some(settings), true, true), |
|
|
|
|
GetSettingsResult::Builtin(config) => (Some(config.settings), true, true), |
|
|
|
|
GetSettingsResult::Stored(_) => (), |
|
|
|
|
GetSettingsResult::Builtin(_) => (), |
|
|
|
|
GetSettingsResult::Proxied(config) => { |
|
|
|
|
if config.https_disabled { |
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
GetSettingsResult::Interface => (), |
|
|
|
|
_ => continue, |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
// 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), |
|
|
|
|
self.sync_domain_https_cert(&domain) |
|
|
|
|
.await |
|
|
|
|
.map_unexpected_while(|| format!("syncing domain {domain}",))?; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
GetSettingsResult::Interface => (None, true, false), |
|
|
|
|
Ok(()) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 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; |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
fn can_sync_external_cert(&self) -> bool { |
|
|
|
|
self.acme_manager.is_some() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
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, |
|
|
|
|
) |
|
|
|
|
})?; |
|
|
|
|
} |
|
|
|
|
async fn sync_external_domain( |
|
|
|
|
&self, |
|
|
|
|
domain: &domain::Name, |
|
|
|
|
config: &domain::ConfigExternalDomain, |
|
|
|
|
) -> unexpected::Result<()> { |
|
|
|
|
log::info!("Syncing HTTPS certificate for external domain {domain}"); |
|
|
|
|
self.acme_manager |
|
|
|
|
.as_ref() |
|
|
|
|
.unwrap() |
|
|
|
|
.sync_domain(domain.clone(), Some(config.clone())) |
|
|
|
|
.await |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if gemini_cert { |
|
|
|
|
self.sync_domain_gemini_cert(&domain) |
|
|
|
|
.map_unexpected_while(|| format!("syncing gemini cert for domain {domain}"))?; |
|
|
|
|
} |
|
|
|
|
async fn sync_external_certs(&self) -> unexpected::Result<()> { |
|
|
|
|
let domains = self |
|
|
|
|
.all_domains() |
|
|
|
|
.or_unexpected_while("fetching all domains")? |
|
|
|
|
.into_iter(); |
|
|
|
|
|
|
|
|
|
if https_cert { |
|
|
|
|
self.sync_domain_https_cert(&domain) |
|
|
|
|
for ManagedDomain { domain, .. } in domains { |
|
|
|
|
if let GetSettingsResult::External(config) = self |
|
|
|
|
.get_settings(&domain) |
|
|
|
|
.map_unexpected_while(|| format!("fetching settings for {domain}"))? |
|
|
|
|
{ |
|
|
|
|
self.sync_external_domain(&domain, &config) |
|
|
|
|
.await |
|
|
|
|
.map_unexpected_while(|| format!("syncing https cert for domain {domain}",))?; |
|
|
|
|
.map_unexpected_while(|| format!("syncing external {domain}"))?; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
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 { |
|
|
|
@ -391,12 +478,16 @@ impl Manager for ManagerImpl { |
|
|
|
|
|
|
|
|
|
self.sync_domain_origin(&domain, &settings.origin_descr)?; |
|
|
|
|
|
|
|
|
|
self.sync_domain_gemini_cert(&domain) |
|
|
|
|
.or_unexpected_while("syncing domain gemini cert")?; |
|
|
|
|
if self.can_sync_gemini_cert() { |
|
|
|
|
self.sync_domain_gemini_cert(&domain) |
|
|
|
|
.or_unexpected_while("syncing domain gemini cert")?; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
self.sync_domain_https_cert(&domain) |
|
|
|
|
.await |
|
|
|
|
.or_unexpected_while("syncing domain https cert")?; |
|
|
|
|
if self.can_sync_https_cert() { |
|
|
|
|
self.sync_domain_https_cert(&domain) |
|
|
|
|
.await |
|
|
|
|
.or_unexpected_while("syncing domain https cert")?; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
self.domain_store.set(&domain, &settings)?; |
|
|
|
|
|
|
|
|
|