diff --git a/crates/common/src/manager/boot.rs b/crates/common/src/manager/boot.rs index 2d4ea7b6..63cd45d5 100644 --- a/crates/common/src/manager/boot.rs +++ b/crates/common/src/manager/boot.rs @@ -37,13 +37,12 @@ use utils::{ use crate::{ config::{server::Servers, tracers::Tracers}, - manager::SPAMFILTER_URL, Core, SharedCore, }; use super::{ config::{ConfigManager, Patterns}, - download_resource, WEBADMIN_KEY, WEBADMIN_URL, + WEBADMIN_KEY, }; pub struct BootManager { @@ -177,12 +176,11 @@ impl BootManager { .filter(|v| !v.is_empty()) .is_none() { - match manager.fetch_external_config(SPAMFILTER_URL).await { + match manager.fetch_config_resource("spam-filter").await { Ok(external_config) => { tracing::info!( context = "config", event = "import", - url = SPAMFILTER_URL, version = external_config.version, "Imported spam filter rules" ); @@ -221,13 +219,12 @@ impl BootManager { { match blob_store.get_blob(WEBADMIN_KEY, 0..usize::MAX).await { Ok(Some(_)) => (), - Ok(None) => match download_resource(WEBADMIN_URL).await { + Ok(None) => match manager.fetch_resource("webadmin").await { Ok(bytes) => match blob_store.put_blob(WEBADMIN_KEY, &bytes).await { Ok(_) => { tracing::info!( context = "webadmin", event = "download", - url = WEBADMIN_URL, "Downloaded webadmin bundle" ); } diff --git a/crates/common/src/manager/config.rs b/crates/common/src/manager/config.rs index a17cb1ee..99fbc429 100644 --- a/crates/common/src/manager/config.rs +++ b/crates/common/src/manager/config.rs @@ -37,8 +37,6 @@ use utils::{ glob::GlobPattern, }; -use super::download_resource; - #[derive(Default)] pub struct ConfigManager { pub cfg_local: ArcSwap>, @@ -320,9 +318,9 @@ impl ConfigManager { }) } - pub async fn update_external_config(&self, url: &str) -> store::Result> { + pub async fn update_config_resource(&self, resource_id: &str) -> store::Result> { let external = self - .fetch_external_config(url) + .fetch_config_resource(resource_id) .await .map_err(store::Error::InternalError)?; @@ -337,7 +335,7 @@ impl ConfigManager { tracing::debug!( context = "config", event = "update", - url = url, + resource_id = resource_id, version = external.version, "Configuration version is up-to-date" ); @@ -345,8 +343,11 @@ impl ConfigManager { } } - pub(crate) async fn fetch_external_config(&self, url: &str) -> Result { - let config = String::from_utf8(download_resource(url).await?) + pub(crate) async fn fetch_config_resource( + &self, + resource_id: &str, + ) -> Result { + let config = String::from_utf8(self.fetch_resource(resource_id).await?) .map_err(|err| format!("Configuration file has invalid UTF-8: {err}"))?; let config = Config::new(config) .map_err(|err| format!("Failed to parse external configuration: {err}"))?; @@ -375,7 +376,7 @@ impl ConfigManager { event = "import", key = key, value = value, - url = url, + resource_id = resource_id, "Ignoring key" ); } diff --git a/crates/common/src/manager/mod.rs b/crates/common/src/manager/mod.rs index dc7f53e5..6ca39137 100644 --- a/crates/common/src/manager/mod.rs +++ b/crates/common/src/manager/mod.rs @@ -25,28 +25,56 @@ use std::time::Duration; use crate::USER_AGENT; +use self::config::ConfigManager; + pub mod boot; pub mod config; pub mod reload; pub mod webadmin; -pub const SPAMFILTER_URL: &str = "https://get.stalw.art/resources/config/spamfilter.toml"; -pub const WEBADMIN_URL: &str = +const DEFAULT_SPAMFILTER_URL: &str = "https://get.stalw.art/resources/config/spamfilter.toml"; +const DEFAULT_WEBADMIN_URL: &str = "https://github.com/stalwartlabs/webadmin/releases/latest/download/webadmin.zip"; pub const WEBADMIN_KEY: &[u8] = "STALWART_WEBADMIN".as_bytes(); -async fn download_resource(url: &str) -> Result, String> { - reqwest::Client::builder() - .timeout(Duration::from_secs(60)) - .user_agent(USER_AGENT) - .build() - .unwrap_or_default() - .get(url) - .send() - .await - .map_err(|err| format!("Failed to fetch {url}: {err}"))? - .bytes() - .await - .map_err(|err| format!("Failed to fetch {url}: {err}")) - .map(|bytes| bytes.to_vec()) +impl ConfigManager { + pub async fn fetch_resource(&self, resource_id: &str) -> Result, String> { + if let Some(url) = self + .get(&format!("config.resource.{resource_id}")) + .await + .map_err(|err| { + format!("Failed to fetch configuration key 'resource.{resource_id}': {err}",) + })? + { + fetch_resource(&url).await + } else { + match resource_id { + "spam-filter" => fetch_resource(DEFAULT_SPAMFILTER_URL).await, + "webadmin" => fetch_resource(DEFAULT_WEBADMIN_URL).await, + _ => Err(format!("Unknown resource: {resource_id}")), + } + } + } +} + +async fn fetch_resource(url: &str) -> Result, String> { + if let Some(path) = url.strip_prefix("file://") { + tokio::fs::read(path) + .await + .map_err(|err| format!("Failed to read {path}: {err}")) + } else { + reqwest::Client::builder() + .timeout(Duration::from_secs(60)) + .user_agent(USER_AGENT) + .build() + .unwrap_or_default() + .get(url) + .send() + .await + .map_err(|err| format!("Failed to fetch {url}: {err}"))? + .bytes() + .await + .map_err(|err| format!("Failed to fetch {url}: {err}")) + .map(|bytes| bytes.to_vec()) + } } diff --git a/crates/common/src/manager/webadmin.rs b/crates/common/src/manager/webadmin.rs index ac813761..d9b19e4c 100644 --- a/crates/common/src/manager/webadmin.rs +++ b/crates/common/src/manager/webadmin.rs @@ -30,7 +30,9 @@ use ahash::AHashMap; use arc_swap::ArcSwap; use store::BlobStore; -use super::{download_resource, WEBADMIN_KEY, WEBADMIN_URL}; +use crate::Core; + +use super::WEBADMIN_KEY; pub struct WebAdminManager { bundle_path: TempDir, @@ -128,12 +130,17 @@ impl WebAdminManager { Ok(()) } - pub async fn update_and_unpack(&self, blob_store: &BlobStore) -> store::Result<()> { - let bytes = download_resource(WEBADMIN_URL).await.map_err(|err| { - store::Error::InternalError(format!("Failed to download webadmin: {err}")) - })?; - blob_store.put_blob(WEBADMIN_KEY, &bytes).await?; - self.unpack(blob_store).await + pub async fn update_and_unpack(&self, core: &Core) -> store::Result<()> { + let bytes = core + .storage + .config + .fetch_resource("webadmin") + .await + .map_err(|err| { + store::Error::InternalError(format!("Failed to download webadmin: {err}")) + })?; + core.storage.blob.put_blob(WEBADMIN_KEY, &bytes).await?; + self.unpack(&core.storage.blob).await } } diff --git a/crates/jmap/src/api/management/reload.rs b/crates/jmap/src/api/management/reload.rs index 3f84d561..2cdd323d 100644 --- a/crates/jmap/src/api/management/reload.rs +++ b/crates/jmap/src/api/management/reload.rs @@ -21,7 +21,6 @@ * for more details. */ -use common::manager::SPAMFILTER_URL; use hyper::Method; use jmap_proto::error::request::RequestError; use serde_json::json; @@ -96,7 +95,7 @@ impl JMAP { .core .storage .config - .update_external_config(SPAMFILTER_URL) + .update_config_resource("spam-filter") .await { Ok(result) => JsonResponse::new(json!({ @@ -107,12 +106,7 @@ impl JMAP { } } (Some("webadmin"), &Method::GET) => { - match self - .inner - .webadmin - .update_and_unpack(&self.core.storage.blob) - .await - { + match self.inner.webadmin.update_and_unpack(&self.core).await { Ok(_) => JsonResponse::new(json!({ "data": (), }))