diff --git a/DEPLOY.md b/DEPLOY.md index ec7dd46..4605a98 100644 --- a/DEPLOY.md +++ b/DEPLOY.md @@ -172,6 +172,7 @@ max_request_size = 20_000_000 # in bytes allow_registration = true allow_federation = true +allow_check_for_updates = true # Server to get public keys from. You probably shouldn't change this trusted_servers = ["matrix.org"] diff --git a/complement/Dockerfile b/complement/Dockerfile index 43416fa..50173a1 100644 --- a/complement/Dockerfile +++ b/complement/Dockerfile @@ -30,6 +30,7 @@ ENV CONDUIT_CONFIG=/workdir/conduit.toml RUN sed -i "s/port = 6167/port = 8008/g" conduit.toml RUN echo "allow_federation = true" >> conduit.toml +RUN echo "allow_check_for_updates = true" >> conduit.toml RUN echo "allow_encryption = true" >> conduit.toml RUN echo "allow_registration = true" >> conduit.toml RUN echo "log = \"warn,_=off,sled=off\"" >> conduit.toml diff --git a/conduit-example.toml b/conduit-example.toml index 6089aa5..836db65 100644 --- a/conduit-example.toml +++ b/conduit-example.toml @@ -39,6 +39,7 @@ max_request_size = 20_000_000 # in bytes allow_registration = true allow_federation = true +allow_check_for_updates = true # Enable the display name lightning bolt on registration. enable_lightning_bolt = true diff --git a/debian/postinst b/debian/postinst index dfa599d..69a766a 100644 --- a/debian/postinst +++ b/debian/postinst @@ -73,6 +73,7 @@ max_request_size = 20_000_000 # in bytes allow_registration = true allow_federation = true +allow_check_for_updates = true trusted_servers = ["matrix.org"] diff --git a/docker-compose.yml b/docker-compose.yml index d9c32b5..5bcf84f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -29,6 +29,7 @@ services: CONDUIT_MAX_REQUEST_SIZE: 20_000_000 # in bytes, ~20 MB CONDUIT_ALLOW_REGISTRATION: 'true' CONDUIT_ALLOW_FEDERATION: 'true' + CONDUIT_ALLOW_CHECK_FOR_UPDATES: 'true' CONDUIT_TRUSTED_SERVERS: '["matrix.org"]' #CONDUIT_MAX_CONCURRENT_REQUESTS: 100 #CONDUIT_LOG: warn,rocket=off,_=off,sled=off diff --git a/docker/docker-compose.for-traefik.yml b/docker/docker-compose.for-traefik.yml index 474299f..bed734f 100644 --- a/docker/docker-compose.for-traefik.yml +++ b/docker/docker-compose.for-traefik.yml @@ -29,6 +29,7 @@ services: CONDUIT_MAX_REQUEST_SIZE: 20_000_000 # in bytes, ~20 MB CONDUIT_ALLOW_REGISTRATION: 'true' CONDUIT_ALLOW_FEDERATION: 'true' + CONDUIT_ALLOW_CHECK_FOR_UPDATES: 'true' CONDUIT_TRUSTED_SERVERS: '["matrix.org"]' #CONDUIT_MAX_CONCURRENT_REQUESTS: 100 #CONDUIT_LOG: warn,rocket=off,_=off,sled=off diff --git a/docker/docker-compose.with-traefik.yml b/docker/docker-compose.with-traefik.yml index 79ebef4..fda942b 100644 --- a/docker/docker-compose.with-traefik.yml +++ b/docker/docker-compose.with-traefik.yml @@ -35,8 +35,9 @@ services: # Available levels are: error, warn, info, debug, trace - more info at: https://docs.rs/env_logger/*/env_logger/#enabling-logging # CONDUIT_LOG: info # default is: "warn,_=off,sled=off" # CONDUIT_ALLOW_JAEGER: 'false' - # CONDUIT_ALLOW_ENCRYPTION: 'false' - # CONDUIT_ALLOW_FEDERATION: 'false' + # CONDUIT_ALLOW_ENCRYPTION: 'true' + # CONDUIT_ALLOW_FEDERATION: 'true' + # CONDUIT_ALLOW_CHECK_FOR_UPDATES: 'true' # CONDUIT_DATABASE_PATH: /srv/conduit/.local/share/conduit # CONDUIT_WORKERS: 10 # CONDUIT_MAX_REQUEST_SIZE: 20_000_000 # in bytes, ~20 MB diff --git a/src/config/mod.rs b/src/config/mod.rs index 4dad9f7..e2c2ff1 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -28,6 +28,8 @@ pub struct Config { pub db_cache_capacity_mb: f64, #[serde(default = "true_fn")] pub enable_lightning_bolt: bool, + #[serde(default = "true_fn")] + pub allow_check_for_updates: bool, #[serde(default = "default_conduit_cache_capacity_modifier")] pub conduit_cache_capacity_modifier: f64, #[serde(default = "default_rocksdb_max_open_files")] diff --git a/src/database/key_value/globals.rs b/src/database/key_value/globals.rs index 1e02459..11aa064 100644 --- a/src/database/key_value/globals.rs +++ b/src/database/key_value/globals.rs @@ -12,6 +12,7 @@ use ruma::{ use crate::{database::KeyValueDatabase, service, services, utils, Error, Result}; pub const COUNTER: &[u8] = b"c"; +pub const LAST_CHECK_FOR_UPDATES_COUNT: &[u8] = b"u"; #[async_trait] impl service::globals::Data for KeyValueDatabase { @@ -27,6 +28,23 @@ impl service::globals::Data for KeyValueDatabase { }) } + fn last_check_for_updates_id(&self) -> Result { + self.global + .get(LAST_CHECK_FOR_UPDATES_COUNT)? + .map_or(Ok(0_u64), |bytes| { + utils::u64_from_bytes(&bytes).map_err(|_| { + Error::bad_database("last check for updates count has invalid bytes.") + }) + }) + } + + fn update_check_for_updates_id(&self, id: u64) -> Result<()> { + self.global + .insert(LAST_CHECK_FOR_UPDATES_COUNT, &id.to_be_bytes())?; + + Ok(()) + } + async fn watch(&self, user_id: &UserId, device_id: &DeviceId) -> Result<()> { let userid_bytes = user_id.as_bytes().to_vec(); let mut userid_prefix = userid_bytes.clone(); diff --git a/src/database/mod.rs b/src/database/mod.rs index 4e7bda6..b36347d 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -18,6 +18,7 @@ use ruma::{ CanonicalJsonValue, EventId, OwnedDeviceId, OwnedEventId, OwnedRoomId, OwnedUserId, RoomId, UserId, }; +use serde::Deserialize; use std::{ collections::{BTreeMap, HashMap, HashSet}, fs::{self, remove_dir_all}, @@ -25,7 +26,9 @@ use std::{ mem::size_of, path::Path, sync::{Arc, Mutex, RwLock}, + time::Duration, }; +use tokio::time::interval; use tracing::{debug, error, info, warn}; @@ -982,6 +985,9 @@ impl KeyValueDatabase { services().sending.start_handler(); Self::start_cleanup_task().await; + if services().globals.allow_check_for_updates() { + Self::start_check_for_updates_task(); + } Ok(()) } @@ -998,9 +1004,61 @@ impl KeyValueDatabase { } #[tracing::instrument] - pub async fn start_cleanup_task() { - use tokio::time::interval; + pub fn start_check_for_updates_task() { + tokio::spawn(async move { + let timer_interval = Duration::from_secs(60 * 60); + let mut i = interval(timer_interval); + loop { + i.tick().await; + let _ = Self::try_handle_updates().await; + } + }); + } + async fn try_handle_updates() -> Result<()> { + let response = services() + .globals + .default_client() + .get("https://conduit.rs/check-for-updates/stable") + .send() + .await?; + + #[derive(Deserialize)] + struct CheckForUpdatesResponseEntry { + id: u64, + date: String, + message: String, + } + #[derive(Deserialize)] + struct CheckForUpdatesResponse { + updates: Vec, + } + + let response = serde_json::from_str::(&response.text().await?) + .map_err(|_| Error::BadServerResponse("Bad version check response"))?; + + let mut last_update_id = services().globals.last_check_for_updates_id()?; + for update in response.updates { + last_update_id = last_update_id.max(update.id); + if update.id > services().globals.last_check_for_updates_id()? { + println!("{}", update.message); + services() + .admin + .send_message(RoomMessageEventContent::text_plain(format!( + "@room: The following is a message from the Conduit developers. It was sent on '{}':\n\n{}", + update.date, update.message + ))) + } + } + services() + .globals + .update_check_for_updates_id(last_update_id)?; + + Ok(()) + } + + #[tracing::instrument] + pub async fn start_cleanup_task() { #[cfg(unix)] use tokio::signal::unix::{signal, SignalKind}; diff --git a/src/service/globals/data.rs b/src/service/globals/data.rs index 171b3fe..8a66751 100644 --- a/src/service/globals/data.rs +++ b/src/service/globals/data.rs @@ -13,6 +13,8 @@ use crate::Result; pub trait Data: Send + Sync { fn next_count(&self) -> Result; fn current_count(&self) -> Result; + fn last_check_for_updates_id(&self) -> Result; + fn update_check_for_updates_id(&self, id: u64) -> Result<()>; async fn watch(&self, user_id: &UserId, device_id: &DeviceId) -> Result<()>; fn cleanup(&self) -> Result<()>; fn memory_usage(&self) -> String; diff --git a/src/service/globals/mod.rs b/src/service/globals/mod.rs index 44235b3..875a457 100644 --- a/src/service/globals/mod.rs +++ b/src/service/globals/mod.rs @@ -209,6 +209,16 @@ impl Service { self.db.current_count() } + #[tracing::instrument(skip(self))] + pub fn last_check_for_updates_id(&self) -> Result { + self.db.last_check_for_updates_id() + } + + #[tracing::instrument(skip(self))] + pub fn update_check_for_updates_id(&self, id: u64) -> Result<()> { + self.db.update_check_for_updates_id(id) + } + pub async fn watch(&self, user_id: &UserId, device_id: &DeviceId) -> Result<()> { self.db.watch(user_id, device_id).await } @@ -257,6 +267,10 @@ impl Service { self.config.enable_lightning_bolt } + pub fn allow_check_for_updates(&self) -> bool { + self.config.allow_check_for_updates + } + pub fn trusted_servers(&self) -> &[OwnedServerName] { &self.config.trusted_servers }