2026-02-12 18:24:24 -05:00
use std ::{
io ::IsTerminal ,
sync ::{ Arc , OnceLock } ,
2026-02-12 11:47:54 -05:00
} ;
2026-02-12 18:24:24 -05:00
use askama ::Template ;
2026-02-12 11:47:54 -05:00
use async_trait ::async_trait ;
2026-02-15 17:28:55 -05:00
use conduwuit ::{ Result , info , utils ::ReadyExt } ;
2026-04-07 18:21:18 +01:00
use futures ::{ FutureExt , StreamExt } ;
2026-02-12 18:24:24 -05:00
use ruma ::{ UserId , events ::room ::message ::RoomMessageEventContent } ;
2026-02-12 11:47:54 -05:00
2026-02-12 18:24:24 -05:00
use crate ::{
Dep , admin , config , globals ,
registration_tokens ::{ self , ValidToken , ValidTokenSource } ,
users ,
} ;
2026-02-12 11:47:54 -05:00
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<uninitialized>, 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<OnceLock<uninitialized>>, representing first run mode being
/// active. The marker may only transition to state 3 from here.
/// 3. OnceLock<OnceLock<()>>, representing first run mode being inactive.
/// The marker may not transition out of this state.
first_run_marker : OnceLock < OnceLock < ( ) > > ,
2026-02-12 18:24:24 -05:00
/// A single-use registration token which may be used to create the first
/// account.
first_account_token : String ,
2026-02-12 11:47:54 -05:00
}
struct Services {
config : Dep < config ::Service > ,
users : Dep < users ::Service > ,
2026-02-12 18:24:24 -05:00
globals : Dep < globals ::Service > ,
admin : Dep < admin ::Service > ,
2026-02-12 11:47:54 -05:00
}
#[ async_trait ]
impl crate ::Service for Service {
fn build ( args : crate ::Args < '_ > ) -> Result < Arc < Self > > {
Ok ( Arc ::new ( Self {
services : Services {
config : args . depend ::< config ::Service > ( " config " ) ,
users : args . depend ::< users ::Service > ( " users " ) ,
2026-02-12 18:24:24 -05:00
globals : args . depend ::< globals ::Service > ( " globals " ) ,
admin : args . depend ::< admin ::Service > ( " admin " ) ,
2026-02-12 11:47:54 -05:00
} ,
// marker starts in an indeterminate state
first_run_marker : OnceLock ::new ( ) ,
2026-02-12 18:24:24 -05:00
first_account_token : registration_tokens ::Service ::generate_token_string ( ) ,
2026-02-12 11:47:54 -05:00
} ) )
}
fn name ( & self ) -> & str { crate ::service ::make_name ( std ::module_path! ( ) ) }
async fn worker ( self : Arc < Self > ) -> Result {
2026-02-24 12:19:31 -05:00
// first run mode will be enabled if there are no local users, provided it's not
// forcibly disabled for Complement tests
let is_first_run = ! self . services . config . force_disable_first_run_mode
& & self
. services
. users
. list_local_users ( )
. ready_filter ( | user | * user ! = self . services . globals . server_user )
. next ( )
. await
. is_none ( ) ;
2026-02-12 11:47:54 -05:00
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.
2026-02-12 18:24:24 -05:00
///
/// Returns true if first run mode was successfully disabled, and false if
/// first run mode was already disabled.
fn disable_first_run ( & self ) -> bool {
2026-02-12 11:47:54 -05:00
self . first_run_marker
. get ( )
. expect ( " First run mode should not be disabled during server startup " )
. set ( ( ) )
2026-02-12 18:24:24 -05:00
. is_ok ( )
}
/// If first-run mode is active, grant admin powers to the specified user
/// and disable first-run mode.
///
/// Returns Ok(true) if the specified user was the first user, and Ok(false)
/// if they were not.
pub async fn empower_first_user ( & self , user : & UserId ) -> Result < bool > {
#[ derive(Template) ]
2026-03-22 19:34:13 -04:00
#[ template(path = " welcome.md " ) ]
2026-02-12 18:24:24 -05:00
struct WelcomeMessage < ' a > {
config : & ' a Dep < config ::Service > ,
domain : & ' a str ,
}
// If first run mode isn't active, do nothing.
if ! self . disable_first_run ( ) {
return Ok ( false ) ;
}
2026-04-07 18:21:18 +01:00
self . services . admin . make_user_admin ( user ) . boxed ( ) . await ? ;
2026-02-12 18:24:24 -05:00
// Send the welcome message
let welcome_message = WelcomeMessage {
config : & self . services . config ,
domain : self . services . globals . server_name ( ) . as_str ( ) ,
}
. render ( )
. expect ( " should have been able to render welcome message template " ) ;
self . services
. admin
. send_loud_message ( RoomMessageEventContent ::text_markdown ( welcome_message ) )
. await ? ;
2026-02-15 17:28:55 -05:00
info! ( " {user} has been invited to the admin room as the first user. " ) ;
2026-02-12 18:24:24 -05:00
Ok ( true )
}
/// Get the single-use registration token which may be used to create the
/// first account.
pub fn get_first_account_token ( & self ) -> Option < ValidToken > {
if self . is_first_run ( ) {
Some ( ValidToken {
token : self . first_account_token . clone ( ) ,
source : ValidTokenSource ::FirstAccount ,
} )
} else {
None
}
2026-02-12 11:47:54 -05:00
}
2026-02-15 17:28:55 -05:00
pub fn print_first_run_banner ( & self ) {
2026-02-12 18:24:24 -05:00
use yansi ::Paint ;
2026-02-12 11:47:54 -05:00
// 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 ;
}
2026-02-12 18:24:24 -05:00
eprintln! ( ) ;
eprintln! ( " {} " , " ============ " . bold ( ) ) ;
eprintln! (
" Welcome to {} {} ! " ,
" Continuwuity " . bold ( ) . bright_magenta ( ) ,
conduwuit ::version ::version ( ) . bold ( )
) ;
eprintln! ( ) ;
eprintln! (
" In order to use your new homeserver, you need to create its first user account. "
) ;
eprintln! (
" Open your Matrix client of choice and register an account on {} using the \
registration token {} . Pick your own username and password! " ,
self . services . globals . server_name ( ) . bold ( ) . green ( ) ,
self . first_account_token . as_str ( ) . bold ( ) . green ( )
) ;
match (
self . services . config . allow_registration ,
self . services . config . get_config_file_token ( ) . is_some ( ) ,
) {
| ( true , true ) = > {
eprintln! (
" {} until you create an account using the token above. " ,
" The registration token you set in your configuration will not function "
. red ( )
) ;
} ,
| ( true , false ) = > {
eprintln! (
" {} until you create an account using the token above. " ,
" Nobody else will be able to register " . green ( )
) ;
} ,
| ( false , true ) = > {
eprintln! (
" {} because you have disabled registration in your configuration. If this \
is not desired, set `allow_registration` to true and restart Continuwuity. " ,
" The registration token you set in your configuration will not be usable "
. yellow ( )
) ;
} ,
| ( false , false ) = > {
eprintln! (
" {} to allow you to create an account. Because registration is not enabled \
in your configuration, it will be disabled again once your account is \
created. " ,
" Registration has been temporarily enabled " . yellow ( )
) ;
} ,
}
if self . services . config . suspend_on_register {
eprintln! (
2026-03-30 13:16:18 -04:00
" {} Accounts created after yours will be suspended, as set in your \
configuration. " ,
2026-02-12 18:24:24 -05:00
" Your account will not be suspended when you register. " . green ( )
) ;
}
2026-03-30 16:02:43 +00:00
if let Some ( smtp ) = & self . services . config . smtp {
if smtp . require_email_for_registration | | smtp . require_email_for_token_registration {
eprintln! (
2026-03-30 13:16:18 -04:00
" {} Accounts created after yours may be required to provide an email \
address, as set in your configuration. " ,
2026-03-30 16:02:43 +00:00
" You will not be asked for your email address when you register. " . yellow ( ) ,
) ;
}
2026-03-30 13:16:18 -04:00
eprintln! (
" If you wish to associate an email address with your account, you may do so \
after registration in your client's settings (if supported). "
) ;
2026-03-30 16:02:43 +00:00
}
eprintln! (
" {} https://matrix.org/ecosystem/clients/ " ,
" Find a list of Matrix clients here: " . bold ( )
) ;
2026-02-12 18:24:24 -05:00
if self
. services
. config
. yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse
{
eprintln! ( ) ;
eprintln! (
" {} " ,
" You have enabled open registration in your configuration! You almost certainly \
do not want to do this. "
. bold ( )
. on_red ( )
) ;
eprintln! (
" {} " ,
" Servers with open, unrestricted registration are prone to abuse by spammers. \
Users on your server may be unable to join chatrooms which block open \
registration servers. "
. red ( )
) ;
eprintln! (
" If you enabled it only for the purpose of creating the first account, {} and \
create the first account using the token above. " ,
" disable it now, restart Continuwuity, " . red ( ) ,
) ;
// TODO link to a guide on setting up reCAPTCHA
}
if self . services . config . emergency_password . is_some ( ) {
eprintln! ( ) ;
eprintln! (
" {} " ,
" You have set an emergency password for the server user! You almost certainly \
do not want to do this. "
. red ( )
) ;
eprintln! (
" If you set the password only for the purpose of creating the first account, {} \
and create the first account using the token above. " ,
" disable it now, restart Continuwuity, " . red ( ) ,
) ;
}
eprintln! ( ) ;
if std ::io ::stdin ( ) . is_terminal ( ) & & self . services . config . admin_console_automatic {
eprintln! (
" You may also create the first user through the admin console below using the \
`users create-user` command. "
) ;
} else {
eprintln! (
" If you're running the server interactively, you may also create the first user \
through the admin console using the `users create-user` command. Press Ctrl-C \
to open the console. "
) ;
}
eprintln! ( " If you need assistance setting up your homeserver, make a Matrix account on another homeserver and join our chatroom: https://matrix.to/#/#continuwuity:continuwuity.org " ) ;
eprintln! ( " {} " , " ============ " . bold ( ) ) ;
2026-02-12 11:47:54 -05:00
}
}