diff --git a/src/cli/serve.rs b/src/cli/serve.rs index 2dcdb765..f2152e55 100644 --- a/src/cli/serve.rs +++ b/src/cli/serve.rs @@ -40,11 +40,14 @@ use crate::{ ruma_wrapper::{Ar, Ra}, server_server, well_known, }, - config, - config::{Config, ListenComponent, ListenTransport}, + config::{self, Config, ListenComponent, ListenTransport}, database::KeyValueDatabase, - error, observability, services, utils, - utils::error::{Error, Result}, + error, observability, services, + utils::{ + self, + error::{Error, Result}, + }, + Services, SERVICES, }; pub(crate) async fn run(args: ServeArgs) -> Result<(), error::ServeCommand> { @@ -70,8 +73,27 @@ pub(crate) async fn run(args: ServeArgs) -> Result<(), error::ServeCommand> { .expect("should be able to increase the soft limit to the hard limit"); info!("Loading database"); - let db = KeyValueDatabase::load_or_create(config, reload_handles) - .map_err(Error::DatabaseError)?; + let db = Box::leak(Box::new( + KeyValueDatabase::load_or_create(&config) + .map_err(Error::DatabaseError)?, + )); + + // This is the first and only time we initialize the SERVICE static + *SERVICES.write().unwrap() = Some(Box::leak(Box::new( + Services::build(db, config, reload_handles) + .map_err(Error::InitializeServices)?, + ))); + + // Matrix resource ownership is based on the server name; changing it + // requires recreating the database from scratch. This check needs to be + // done before background tasks are started to avoid data races. + if services().users.count().map(|x| x > 0).map_err(Error::NonZeroUsers)? { + let admin_bot = services().globals.admin_bot_user_id.as_ref(); + if !services().users.exists(admin_bot).map_err(Error::AdminBotExists)? { + return Err(Error::Renamed); + } + } + db.apply_migrations().await.map_err(Error::DatabaseError)?; info!("Starting background tasks"); diff --git a/src/database.rs b/src/database.rs index 160a3093..abfad85a 100644 --- a/src/database.rs +++ b/src/database.rs @@ -24,7 +24,6 @@ use tracing::{debug, error, info, info_span, warn, Instrument}; use crate::{ config::DatabaseBackend, - observability::FilterReloadHandles, service::{ media::MediaFileKey, rooms::{ @@ -33,7 +32,7 @@ use crate::{ timeline::PduCount, }, }, - services, utils, Config, Error, PduEvent, Result, Services, SERVICES, + services, utils, Config, Error, PduEvent, Result, }; pub(crate) struct KeyValueDatabase { @@ -319,11 +318,8 @@ impl KeyValueDatabase { allow(unreachable_code) )] #[allow(clippy::too_many_lines)] - pub(crate) fn load_or_create( - config: Config, - reload_handles: FilterReloadHandles, - ) -> Result<&'static KeyValueDatabase> { - Self::check_db_setup(&config)?; + pub(crate) fn load_or_create(config: &Config) -> Result { + Self::check_db_setup(config)?; if !Path::new(&config.database.path).exists() { fs::create_dir_all(&config.database.path).map_err(|_| { @@ -339,21 +335,19 @@ impl KeyValueDatabase { not(any(feature = "rocksdb", feature = "sqlite")), allow(unused_variables) )] - let builder: Arc = match config - .database - .backend - { - #[cfg(feature = "sqlite")] - DatabaseBackend::Sqlite => { - Arc::new(Arc::::open(&config)?) - } - #[cfg(feature = "rocksdb")] - DatabaseBackend::Rocksdb => { - Arc::new(Arc::::open(&config)?) - } - }; + let builder: Arc = + match config.database.backend { + #[cfg(feature = "sqlite")] + DatabaseBackend::Sqlite => { + Arc::new(Arc::::open(config)?) + } + #[cfg(feature = "rocksdb")] + DatabaseBackend::Rocksdb => { + Arc::new(Arc::::open(config)?) + } + }; - let db_raw = Box::new(Self { + let db = Self { db: builder.clone(), userid_password: builder.open_tree("userid_password")?, userid_displayname: builder.open_tree("userid_displayname")?, @@ -527,31 +521,7 @@ impl KeyValueDatabase { our_real_users_cache: RwLock::new(HashMap::new()), appservice_in_room_cache: RwLock::new(HashMap::new()), lasttimelinecount_cache: Mutex::new(HashMap::new()), - }); - - let db = Box::leak(db_raw); - - let services_raw = - Box::new(Services::build(db, config, reload_handles)?); - - // This is the first and only time we initialize the SERVICE static - *SERVICES.write().unwrap() = Some(Box::leak(services_raw)); - - // Matrix resource ownership is based on the server name; changing it - // requires recreating the database from scratch. - if services().users.count()? > 0 { - let admin_bot = services().globals.admin_bot_user_id.as_ref(); - if !services().users.exists(admin_bot)? { - error!( - user_id = %admin_bot, - "The admin bot does not exist and the database is not new", - ); - return Err(Error::bad_database( - "Cannot reuse an existing database after changing the \ - server name, please delete the old one first.", - )); - } - } + }; Ok(db) } diff --git a/src/error.rs b/src/error.rs index 180fc6a9..2ad565a3 100644 --- a/src/error.rs +++ b/src/error.rs @@ -61,6 +61,18 @@ pub(crate) enum ServeCommand { #[error("failed to serve requests")] Serve(#[from] Serve), + + #[error("failed to initialize services")] + InitializeServices(#[source] crate::utils::error::Error), + + #[error("failed to check if there are any users")] + NonZeroUsers(#[source] crate::utils::error::Error), + + #[error("failed to check if the admin bot exists")] + AdminBotExists(#[source] crate::utils::error::Error), + + #[error("`server_name` in the database and config file differ")] + Renamed, } /// Observability initialization errors