From c95f4b9b39287f8ef4a93aa47da1ae29434d7bd5 Mon Sep 17 00:00:00 2001 From: Brian Picciano Date: Sun, 14 Jan 2024 15:34:13 +0100 Subject: [PATCH] Split domain manager "sync all domains" task into separate ones Each new task corresponds to syncing a particular kind of thing for each domain, e.g. origins, https certs, etc... --- src/domain/manager.rs | 250 ++++++++++++++++++++++++++++++------------ 1 file changed, 178 insertions(+), 72 deletions(-) diff --git a/src/domain/manager.rs b/src/domain/manager.rs index 8e92128..811bab2 100644 --- a/src/domain/manager.rs +++ b/src/domain/manager.rs @@ -182,12 +182,58 @@ impl ManagerImpl { config, }); + const SYNC_PERIOD_SECS: u64 = 20 * 60; // 20 minutes + task_stack.push_spawn_periodically( manager.clone(), - 20 * 60, - |_canceller, manager| async move { manager.sync_all_domains().await }, + 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 } @@ -200,99 +246,155 @@ 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() + async fn sync_origins(&self) -> unexpected::Result<()> { + let domains = self + .all_domains() + .or_unexpected_while("fetching all domains")? + .into_iter(); + + 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 - 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)?; + 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(()); + } + + // 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(()) } + 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( &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?; - } - - Ok(()) + log::info!("Syncing HTTPS certificate for external domain {domain}"); + self.acme_manager + .as_ref() + .unwrap() + .sync_domain(domain.clone(), Some(config.clone())) + .await } - async fn sync_all_domains(&self) -> unexpected::Result<()> { + async fn sync_external_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 + if let GetSettingsResult::External(config) = 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), - - // 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) + self.sync_external_domain(&domain, &config) .await - .map_unexpected_while(|| format!("syncing https cert for domain {domain}",))?; + .map_unexpected_while(|| format!("syncing external {domain}"))?; } } @@ -376,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)?;