diff --git a/src/service/firstrun/mod.rs b/src/service/firstrun/mod.rs new file mode 100644 index 00000000..be237dd6 --- /dev/null +++ b/src/service/firstrun/mod.rs @@ -0,0 +1,109 @@ +use std::sync::{ + Arc, OnceLock, + atomic::{AtomicBool, Ordering}, +}; + +use async_trait::async_trait; +use conduwuit::Result; +use futures::StreamExt; + +use crate::{Dep, config, users}; + +pub struct Service { + services: Services, + /// Represents the state of first run mode. + /// + /// First run mode is either active or inactive at server start. It may + /// transition from active to inactive, but only once, and can never + /// transition the other way. Additionally, whether the server is in first + /// run mode or not can only be determined when all services are + /// constructed. The outer `OnceLock` represents the unknown state of first + /// run mode, and the inner `OnceLock` enforces the one-time transition from + /// active to inactive. + /// + /// Consequently, this marker may be in one of three states: + /// 1. OnceLock, representing the unknown state of first run + /// mode during server startup. Once server startup is complete, the + /// marker transitions to state 2 or directly to state 3. + /// 2. OnceLock>, representing first run mode being + /// active. The marker may only transition to state 3 from here. + /// 3. OnceLock>, representing first run mode being inactive. + /// The marker may not transition out of this state. + first_run_marker: OnceLock>, +} + +struct Services { + config: Dep, + users: Dep, +} + +#[async_trait] +impl crate::Service for Service { + fn build(args: crate::Args<'_>) -> Result> { + Ok(Arc::new(Self { + services: Services { + config: args.depend::("config"), + users: args.depend::("users"), + }, + // marker starts in an indeterminate state + first_run_marker: OnceLock::new(), + })) + } + + fn name(&self) -> &str { crate::service::make_name(std::module_path!()) } + + async fn worker(self: Arc) -> Result { + // first run mode will be enabled if there are no local users + let is_first_run = self + .services + .users + .list_local_users() + .next() + .await + .is_none(); + + self.first_run_marker + .set(if is_first_run { + // first run mode is active (empty inner lock) + OnceLock::new() + } else { + // first run mode is inactive (already filled inner lock) + OnceLock::from(()) + }) + .expect("Service worker should only be called once"); + + Ok(()) + } +} + +impl Service { + /// Check if first run mode is active. + pub fn is_first_run(&self) -> bool { + self.first_run_marker + .get() + .expect("First run mode should not be checked during server startup") + .get() + .is_none() + } + + /// Disable first run mode and begin normal operation. + pub fn disable_first_run(&self) { + self.first_run_marker + .get() + .expect("First run mode should not be disabled during server startup") + .set(()) + .expect("First run mode should not be disabled more than once"); + } + + pub(crate) fn print_banner(&self) { + // This function is specially called by the core after all other + // services have started. It runs last to ensure that the banner it + // prints comes after any other logging which may occur on startup. + + if !self.is_first_run() { + return; + } + + println!("meow"); + } +} diff --git a/src/service/mod.rs b/src/service/mod.rs index b0222c3e..b56c7354 100644 --- a/src/service/mod.rs +++ b/src/service/mod.rs @@ -18,6 +18,7 @@ pub mod client; pub mod config; pub mod emergency; pub mod federation; +pub mod firstrun; pub mod globals; pub mod key_backups; pub mod media; diff --git a/src/service/services.rs b/src/service/services.rs index 656be085..f6f077b1 100644 --- a/src/service/services.rs +++ b/src/service/services.rs @@ -9,7 +9,7 @@ use tokio::sync::Mutex; use crate::{ account_data, admin, announcements, antispam, appservice, client, config, emergency, - federation, globals, key_backups, + federation, firstrun, globals, key_backups, manager::Manager, media, moderation, presence, pusher, registration_tokens, resolver, rooms, sending, server_keys, @@ -33,6 +33,7 @@ pub struct Services { pub resolver: Arc, pub rooms: rooms::Service, pub federation: Arc, + pub firstrun: Arc, pub sending: Arc, pub server_keys: Arc, pub sync: Arc, @@ -67,6 +68,9 @@ impl Services { } Ok(Arc::new(Self { + // firstrun service should be built first so other services + // can check first-run state + firstrun: build!(firstrun::Service), account_data: build!(account_data::Service), admin: build!(admin::Service), appservice: build!(appservice::Service), @@ -144,6 +148,10 @@ impl Services { } debug_info!("Services startup complete."); + + // print first-run banner if necessary + self.firstrun.print_banner(); + Ok(Arc::clone(self)) }